-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathbuild.clj
More file actions
217 lines (188 loc) · 8.21 KB
/
build.clj
File metadata and controls
217 lines (188 loc) · 8.21 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
(ns build
"tools.build script for psi.
Usage:
clojure -T:build lib # build target/psi-VERSION.jar (library jar for Clojars)
clojure -T:build uber # build target/psi.jar + target/psi wrapper (standalone)
clojure -T:build deploy # deploy library jar to Clojars
clojure -T:build clean # remove target/"
(:require
[clojure.edn :as edn]
[clojure.java.io :as io]
[clojure.string :as str]
[clojure.tools.build.api :as b]
[psi.build-manifest :as build-manifest]))
;; ---------------------------------------------------------------------------
;; Config
;; ---------------------------------------------------------------------------
(def psi-lib 'org.hugoduncan/psi)
(def class-dir "target/classes")
(def jar-file "target/psi.jar")
(def wrapper "target/psi")
(def basis
(delay (b/create-basis {:project "deps.edn" :aliases [:run]})))
;; ---------------------------------------------------------------------------
;; Version
;; ---------------------------------------------------------------------------
(defn- version-string
[]
(-> "bases/main/resources/psi/version.edn"
slurp
edn/read-string
:version
(or "unreleased")))
;; ---------------------------------------------------------------------------
;; Source paths — derive from the authoritative runtime alias
;; ---------------------------------------------------------------------------
(def src-dirs
(build-manifest/build-lib-src-dirs))
;; ---------------------------------------------------------------------------
;; Tasks
;; ---------------------------------------------------------------------------
(defn- lib-jar-file
[version]
(str "target/psi-" version ".jar"))
(defn clean
"Remove the target/ directory."
[_]
(b/delete {:path "target"}))
(defn lib
"Build a library jar for Clojars: target/psi-VERSION.jar.
Sources + resources only — no AOT, no bundled deps."
[_]
(let [version (version-string)
jar (lib-jar-file version)]
(println (str "Building library jar " jar " ..."))
;; 1. Clean
(b/delete {:path "target"})
;; 2. Write pom.xml
(println " Writing pom.xml ...")
(b/write-pom {:class-dir class-dir
:lib psi-lib
:version version
:basis @basis
:src-dirs src-dirs
:pom-data [[:description "Psi — AI coding agent"]
[:url "https://github.com/hugoduncan/psi"]
[:licenses
[:license
[:name "Eclipse Public License 2.0"]
[:url "https://www.eclipse.org/legal/epl-2.0/"]]]
[:scm
[:url "https://github.com/hugoduncan/psi"]
[:connection "scm:git:https://github.com/hugoduncan/psi.git"]
[:developerConnection "scm:git:ssh://git@github.com/hugoduncan/psi.git"]]]})
;; 3. Copy sources + resources
(println " Copying sources ...")
(b/copy-dir {:src-dirs src-dirs
:target-dir class-dir})
;; 3b. Copy bb.edn so bbin can read :bbin/bin at install time
(println " Copying bb.edn ...")
(b/copy-file {:src "bb.edn"
:target (str class-dir "/bb.edn")})
;; 3c. Write release-owned runtime dep metadata for jar policy launcher startup
(println " Writing release deps metadata ...")
(let [release-deps-target (io/file class-dir build-manifest/release-deps-resource-path)]
(io/make-parents release-deps-target)
(spit release-deps-target (build-manifest/release-deps-edn)))
;; 4. Build thin jar (no AOT)
(println " Assembling library jar ...")
(b/jar {:class-dir class-dir
:jar-file jar})
(println)
(println (str "Built: " jar))
(println (str " " class-dir "/META-INF/maven/" (namespace psi-lib) "/" (name psi-lib) "/pom.xml"))
jar))
(defn install-local
"Build the library jar and install it into the current local Maven repo.
Intended for local/CI smoke tests that exercise bbin installation against an
isolated HOME/XDG environment."
[opts]
(let [version (version-string)
jar (lib-jar-file version)]
(when-not (.exists (io/file jar))
(println (str "Library jar not found — building first ..."))
(lib opts))
(println (str "Installing " jar " to local Maven repo as " psi-lib " " version " ..."))
(b/install {:class-dir class-dir
:lib psi-lib
:version version
:basis @basis
:jar-file jar})
(println "Done.")))
(defn deploy
"Deploy the library jar to Clojars.
Builds the library jar first if it is not already present.
Requires CLOJARS_USERNAME and CLOJARS_PASSWORD env vars."
[opts]
(let [version (version-string)
jar (lib-jar-file version)]
(when (= "unreleased" version)
(throw (ex-info "Cannot deploy: version resource is 'unreleased'. Run bb release:tag first."
{:version version})))
(when-not (.exists (io/file jar))
(println (str "Library jar not found — building first ..."))
(lib opts))
(println (str "Deploying " jar " to Clojars as " psi-lib " " version " ..."))
;; deps-deploy is loaded dynamically so :deploy alias must be active
(let [deploy-fn (requiring-resolve 'deps-deploy.deps-deploy/deploy)]
(deploy-fn {:installer :remote
:artifact jar
:pom-file (b/pom-path {:lib psi-lib
:class-dir class-dir})}))
(println "Done.")))
(defn uber
"Build target/psi.jar and target/psi wrapper script.
Requires Java 22+. The wrapper passes --enable-native-access=ALL-UNNAMED
which is required by jline-terminal-ffm (TUI dependency)."
[_]
(let [version (version-string)]
(println (str "Building psi " version " ..."))
;; 1. Clean
(b/delete {:path "target"})
;; 2. Copy all source + resource trees into class-dir
(println " Copying sources ...")
(b/copy-dir {:src-dirs src-dirs
:target-dir class-dir})
;; 3. AOT compile psi.main (has :gen-class) and psi.app-runtime (has :gen-class)
;; Everything else loads dynamically at runtime.
(println " AOT compiling entry points ...")
(b/compile-clj {:basis @basis
:src-dirs src-dirs
:class-dir class-dir
:ns-compile '[psi.main psi.app-runtime]
:compile-opts {:direct-linking true}})
;; 4. Assemble uberjar
(println " Assembling uberjar ...")
(b/uber {:class-dir class-dir
:basis @basis
:uber-file jar-file
:main 'psi.main
:manifest {"Implementation-Title" "psi"
"Implementation-Version" version}
;; Merge duplicate service files (common with Clojure libs)
:conflict-handlers {"^META-INF/services/.*" :append
"^META-INF/.*\\.SF$" :ignore
"^META-INF/.*\\.DSA$" :ignore
"^META-INF/.*\\.RSA$" :ignore}})
;; 5. Write wrapper script
(println " Writing wrapper script ...")
(let [wrapper-file (io/file wrapper)]
(spit wrapper-file
(str/join "\n"
["#!/usr/bin/env bash"
"# psi launcher wrapper — requires Java 22+"
"# Generated by bb build:jar"
(str "# Version: " version)
"set -euo pipefail"
"SCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\""
"exec java --enable-native-access=ALL-UNNAMED \\"
" -jar \"${SCRIPT_DIR}/psi.jar\" \"$@\""
""]))
(.setExecutable wrapper-file true false))
(println)
(println (str "Built: " jar-file))
(println (str " " wrapper " (wrapper script)"))
(println)
(println "Run with:")
(println (str " java --enable-native-access=ALL-UNNAMED -jar " jar-file))
(println (str " " wrapper))))