-
-
Notifications
You must be signed in to change notification settings - Fork 48
/
build.clj
341 lines (308 loc) · 13.9 KB
/
build.clj
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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
(ns build
"The build script for the Polylith project.
Primary targets:
* create-artifacts
- creates uberjar and homebrew scripts
* deploy
- creates & deploys project JARs
- perform a dry run with :installer :local
Additional targets:
* jar :project PROJECT
- creates a library JAR for the given project
* scripts :project PROJECT
- creates the homebrew scripts for the given project
* uberjar :project PROJECT
- creates an uberjar for the given project
For help, run:
clojure -A:deps -T:build help/doc"
(:require [clojure.java.io :as io]
[clojure.set :as set]
[clojure.string :as str]
[clojure.tools.build.api :as b]
[clojure.tools.deps :as t]
[clojure.tools.deps.util.dir :refer [with-dir]]
[deps-deploy.deps-deploy :as d]
[babashka.http-client :as http]
[polylith.clj.core.api.interface :as api]
[polylith.clj.core.git.interface :as git]
[polylith.clj.core.version.interface :as version]))
(defn- get-project-aliases []
(let [edn-fn (juxt :root-edn :project-edn)]
(-> (t/find-edn-maps)
(edn-fn)
(t/merge-edns)
:aliases)))
(defn- exec->out
"Given options for b/process, run the command with stdout
and stderr captured, returning stdout if successful.
If the command fails, display stdout, stderr, and then
exit with a non-zero status."
[opts]
(let [{:keys [exit out err]}
(b/process (assoc opts :out :capture :err :capture))]
(when-not (zero? exit)
(println "Failed to execute:" (str/join " " (:command-args opts)))
(when (seq out)
(println "Output:")
(println out))
(when (seq err)
(println "Errors:")
(println err))
(System/exit 1))
out))
(defn- ensure-project-root
"Given a task name and a project name, ensure the project
exists and seems valid, and return the absolute path to it."
[task project]
(let [project-root (str (System/getProperty "user.dir") "/projects/" project)]
(when-not (and project
(.exists (io/file project-root))
(.exists (io/file (str project-root "/deps.edn"))))
(throw (ex-info (str task " task requires a valid :project option") {:project project})))
project-root))
(defn- latest-committed-sha
"Get the latest committed SHA from current branch."
[]
(let [branch (git/current-branch)]
(git/latest-polylith-sha branch)))
(defn- projects-to-deploy
"Returns the projects to deploy.
Read more in the api/projects-to-deploy doc on how this works under the hood."
[]
(filterv #{"poly"}
(api/projects-to-deploy "previous-release")))
(defn scripts
"Produce the artifacts scripts for the specified project.
Returns: the input opts with :artifact-path and :artifact-name
computed as the path and just the filename of the expected
(uber) JAR file."
[{:keys [project] :as opts}]
(ensure-project-root "scripts" project)
(let [project (name project)
project-dir (str "artifacts/" project)]
(b/copy-dir {:src-dirs [(.getFile (io/resource "brew"))]
:target-dir project-dir
:replace {"{{PROJECT}}" project
"{{PROJECT_}}" (str/replace project #"-" "_")
"{{VERSION}}" version/name}})
(b/copy-file {:src (str project-dir "/exec")
:target (str project-dir "/" project)})
(b/delete {:path (str project-dir "/exec")})
(exec->out {:command-args ["chmod" "+x" "install.sh" project]
:dir project-dir})
(let [artifact-name (str project "-" version/name ".jar")]
(assoc opts
:artifact-path (str project-dir "/" artifact-name)
:artifact-name artifact-name))))
(defn- lifted-basis
"This creates a basis where source deps have their primary
external dependencies lifted to the top-level, such as is
needed by Polylith and possibly other monorepo setups."
[]
(let [default-libs (:libs (b/create-basis))
source-dep? #(not (:mvn/version (get default-libs %)))
lifted-deps
(reduce-kv (fn [deps lib {:keys [dependents] :as coords}]
(if (and (contains? coords :mvn/version) (some source-dep? dependents))
(assoc deps lib (select-keys coords [:mvn/version :exclusions]))
deps))
{}
default-libs)]
(-> (b/create-basis {:extra {:deps lifted-deps}})
(update :libs #(into {} (filter (comp :mvn/version val)) %)))))
(defn jar
"Builds a library jar for the specified project.
Options:
* :project - required, the name of the project to build,
* :jar-file - optional, the path of the JAR file to build,
relative to the project folder; can also be specified in
the :jar alias in the project's deps.edn file; will
default to target/PROJECT-thin.jar if not specified.
Returns:
* the input opts with :class-dir, :jar-file, :lib, :pom-file,
and :version computed.
Because we build JARs from Polylith projects, all the source
code we want in the JAR comes from :local/root dependencies of
the project and the actual dependencies are transitive to those
:local/root dependencies, so we create a 'lifted' basis.
Example: clojure -T:build jar :project poly"
[{:keys [project jar-file] :as opts}]
(let [project-root (ensure-project-root "jar" project)
aliases (with-dir (io/file project-root) (get-project-aliases))]
(b/with-project-root project-root
(let [basis (lifted-basis)
class-dir "target/classes"
jar-file (or jar-file
(-> aliases :jar :jar-file)
(str "target/" project "-thin.jar"))
lib (symbol "polylith" (str "clj-" project))
current-dir (System/getProperty "user.dir")
current-rel #(str/replace % (str current-dir "/") "")
directory? #(let [f (java.io.File. %)]
(and (.exists f) (.isDirectory f)))
src+dirs (filter directory? (:classpath-roots basis))
opts (merge opts
{:basis basis
:class-dir class-dir
:jar-file jar-file
:lib lib
:scm {:tag (if (= "SNAPSHOT" version/revision)
(latest-committed-sha)
(str "v" version/name))
:name "git"
:url "https://github.com/polyfy/polylith"}
:src-pom "partial_pom.xml"
:version version/name})]
(b/delete {:path class-dir})
(println "\nWriting pom.xml...")
(b/write-pom opts)
(println "Copying" (str (str/join ", " (map current-rel src+dirs)) "..."))
(b/copy-dir {:src-dirs src+dirs
:target-dir class-dir})
(println "Building jar" (str jar-file "..."))
(b/jar opts)
;; we want the pom.xml file in the project folder for deployment:
(b/copy-file {:src (b/pom-path {:class-dir class-dir
:lib lib})
:target "pom.xml"})
(b/delete {:path class-dir})
(println "Jar is built.")
(-> opts
(assoc :pom-file (str project-root "/pom.xml"))
;; account for project root relative paths:
(update :jar-file (comp #(.getCanonicalPath %) b/resolve-path)))))))
(defn uberjar
"Builds an uberjar for the specified project.
Options:
* :project - required, the name of the project to build,
* :uber-file - optional, the path of the JAR file to build,
relative to the project folder; can also be specified in
the :uberjar alias in the project's deps.edn file; will
default to target/PROJECT.jar if not specified.
Returns:
* the input opts with :class-dir, :compile-opts, :main, and :uber-file
computed.
The project's deps.edn file must contain an :uberjar alias
which must contain at least :main, specifying the main ns
(to compile and to invoke)."
[{:keys [project uber-file] :as opts}]
(let [project-root (ensure-project-root "uberjar" project)
aliases (with-dir (io/file project-root) (get-project-aliases))
main (-> aliases :uberjar :main)]
(when-not main
(throw (ex-info (str "the " project " project's deps.edn file does not specify the :main namespace in its :uberjar alias")
{:aliases aliases})))
(b/with-project-root project-root
(let [class-dir "target/classes"
uber-file (or uber-file
(-> aliases :uberjar :uber-file)
(str "target/" project ".jar"))
opts (merge opts
{:basis (b/create-basis)
:class-dir class-dir
:compile-opts {:direct-linking true}
:main main
:ns-compile [main]
:uber-file uber-file
:exclude [#"(?i)^META-INF/license/.*"
#"^license/.*"]})]
(b/delete {:path class-dir})
;; no src or resources to copy
(println "\nCompiling" (str main "..."))
(b/compile-clj opts)
(println "Building uberjar" (str uber-file "..."))
(b/uber opts)
(b/delete {:path class-dir})
(println "Uberjar is built.")
opts))))
(defn version
"Prints the current version"
[opts]
(println version/name))
(defn install
"Create and locally install library JAR files for the Polylith project
Options:
* :project - required, the name of the project to build"
[{:keys [project] :as opts}]
(let [project-opts (assoc opts
:project project
:installer (get opts :installer :local))]
(println (str "Starting install for " project " project."))
(-> project-opts
(jar)
(set/rename-keys {:jar-file :artifact})
(d/deploy))
(println (str "Local install completed for " project " project."))))
(defn deploy
"Create and deploy library JAR files for the Polylith project.
Currently, creates 'poly'.
You can do a dry run by passing :installer :local which will
deploy the JARs into your local Maven cache instead of to Clojars."
[opts]
(let [projects (projects-to-deploy)]
(when (empty? projects)
(throw (ex-info "Cannot deploy projects. No projects have changed." {})))
(doseq [project projects]
(let [project-opts (assoc opts
:project project
:installer (get opts :installer :remote))]
(println (str "Starting deployment for " project " project."))
(-> project-opts
(jar)
(set/rename-keys {:jar-file :artifact})
(d/deploy))
(println (str "Deployment completed for " project " project."))))))
(defn deploy-snapshot
"Deploys clj-poly to Clojars and requests a build from cljdoc.org."
[opts]
(when-not version/snapshot?
(throw (ex-info "Cannot deploy a snapshot. The revision must be set to SNAPSHOT in `polylith.clj.core.version.interface` before triggering a snapshot deployment."
{:version (version/version)})))
(println "Triggering a deployment to Clojars for the changed projects.")
(deploy opts)
(println "Deployment to Clojars completed for the changed projects.")
(println "Requesting a new documentation build from cljdoc.org for the new SNAPSHOT.")
(let [{:keys [status body]} (http/post "https://cljdoc.org/api/request-build2"
{:throw false
:form-params {"project" "polylith/clj-poly"
"version" version/name}})]
(if (= 200 status)
(println (str "Request completed for polylith/clj-poly version " version/name "."))
(throw (ex-info (str "Could not request a new build from cljdoc.org for polylith/clj-poly version " version/name ".")
{:version version/name
:status status
:body body})))))
(defn create-version-txt
"Spits the current version to version.txt"
[opts]
(spit "version.txt" version/name))
(defn create-artifacts
"Create the artifacts for the Polylith project.
Currently, this only creates artifacts for 'poly'.
It creates an artifacts directory, containing an uberjar,
a '.tar.gz' of the uberjar and install.sh and a project
executable script, and a '.sha1' file for that '.tar.gz'."
[opts]
(let [projects (projects-to-deploy)]
(when (empty? projects)
(throw (ex-info "Cannot create artifacts. No projects have changed." {})))
(b/delete {:path "artifacts"})
(doseq [project projects]
(println (str "Creating artifacts for: " project))
(let [project-opts (assoc opts :project project)
{:keys [uber-file]} (uberjar project-opts)
{:keys [artifact-path artifact-name]} (scripts project-opts)]
(b/copy-file {:src (str "projects/" project "/" uber-file)
:target artifact-path})
(b/copy-file {:src artifact-path
:target (str "artifacts/" artifact-name)})
(let [tar-file (str/replace artifact-name #"\.jar$" ".tar.gz")
_ (exec->out {:command-args ["tar" "cvfz" tar-file project]
:dir "artifacts"})
sha-sum (-> (exec->out {:command-args ["shasum" "-a" "256" tar-file]
:dir "artifacts"})
(str/split #" ")
(first))]
(println (str "Shasum for " project ": " sha-sum))
(spit (str "artifacts/" tar-file ".sha256") (str sha-sum " " tar-file)))
(b/delete {:path (str "artifacts/" project)})))))