-
-
Notifications
You must be signed in to change notification settings - Fork 180
/
built_in.clj
1135 lines (988 loc) · 54.1 KB
/
built_in.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
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
(ns boot.task.built-in
(:require
[clojure.pprint :as pp]
[clojure.java.io :as io]
[clojure.set :as set]
[clojure.string :as string]
[boot.filesystem :as fs]
[boot.gpg :as gpg]
[boot.pod :as pod]
[boot.jar :as jar]
[boot.git :as git]
[boot.file :as file]
[boot.repl :as repl]
[boot.core :as core]
[boot.main :as main]
[boot.util :as util]
[boot.tmpdir :as tmpd]
[boot.from.table.core :as table]
[boot.from.digest :as digest]
[boot.task-helpers :as helpers]
[boot.task-helpers.notify :as notify]
[boot.pedantic :as pedantic])
(:import
[java.io File]
[java.nio.file.attribute PosixFilePermissions]
[java.util Arrays]
[java.util.concurrent LinkedBlockingQueue TimeUnit]
[javax.tools ToolProvider DiagnosticCollector Diagnostic$Kind]))
;; Tasks ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(core/deftask help
"Print usage info and list available tasks."
[]
(core/with-pass-thru [_]
(let [tasks (#'helpers/available-tasks 'boot.user)
opts (->> main/cli-opts (mapv (fn [[x y z]] ["" (str x " " y) z])))
envs [["" "BOOT_AS_ROOT" "Set to 'yes' to allow boot to run as root."]
["" "BOOT_CERTIFICATES" "Specify certificate file paths."]
["" "BOOT_CLOJARS_REPO" "Specify the url for the 'clojars' Maven repo."]
["" "BOOT_CLOJARS_MIRROR" "Specify the mirror url for the 'clojars' Maven repo."]
["" "BOOT_CLOJURE_VERSION" "The version of Clojure boot will provide (1.8.0)."]
["" "BOOT_CLOJURE_NAME" "The artifact name of Clojure boot will provide (org.clojure/clojure)."]
["" "BOOT_COLOR" "Set to 'no' to turn colorized output off."]
["" "BOOT_FILE" "Build script name (build.boot)."]
["" "BOOT_GPG_COMMAND" "System gpg command (gpg)."]
["" "BOOT_HOME" "Directory where boot stores global state (~/.boot)."]
["" "BOOT_WATCHERS_DISABLE" "Set to 'yes' to turn off inotify/FSEvents watches."]
["" "BOOT_JAVA_COMMAND" "Specify the Java executable (java)."]
["" "BOOT_JVM_OPTIONS" "Specify JVM options (Unix/Linux/OSX only)."]
["" "BOOT_LOCAL_REPO" "The local Maven repo path (~/.m2/repository)."]
["" "BOOT_MAVEN_CENTRAL_REPO" "Specify the url for the 'maven-central' Maven repo."]
["" "BOOT_MAVEN_CENTRAL_MIRROR" "Specify the mirror url for the 'maven-central' Maven repo."]
["" "BOOT_VERSION" "Specify the version of boot core to use."]
["" "BOOT_WARN_DEPRECATED" "Set to 'no' to suppress deprecation warnings."]]
files [["" "./boot.properties" "Specify boot options for this project."]
["" "./profile.boot" "A script to run after the global profile.boot but before the build script."]
["" "BOOT_HOME/boot.properties" "Specify global boot options."]
["" "BOOT_HOME/profile.boot" "A script to run before running the build script."]]
br #(conj % ["" "" ""])]
(printf "\n%s\n"
(-> [["" ""] ["Usage:" "boot OPTS <task> TASK_OPTS <task> TASK_OPTS ..."]]
(table/table :style :none)
with-out-str))
(printf "%s\n\nDo `boot <task> -h` to see usage info and TASK_OPTS for <task>.\n"
(->> (-> [["" "" ""]]
(into (#'helpers/set-title opts "OPTS:")) (br)
(into (#'helpers/set-title (#'helpers/tasks-table tasks) "Tasks:")) (br)
(into (#'helpers/set-title envs "Env:")) (br)
(into (#'helpers/set-title files "Files:"))
(table/table :style :none)
with-out-str
(string/split #"\n"))
(map string/trimr)
(string/join "\n"))))))
(core/deftask ^{:deprecated "2.6.0"} checkout
"Checkout dependencies task. DEPRECATED. (Use -c, --checkouts Boot option.)
This task facilitates working on a project and its dependencies at the same
time, by extracting the dependency jar contents into the fileset. Transitive
dependencies will be added to the class path automatically.
You'll need at least two boot instances---one to build the dependency jar and
the other to build the project. For example:
$ boot watch pom -p foo/bar -v 1.2.3-SNAPSHOT jar install
to build the dependency jar, and
$ boot repl -s watch checkout -d foo/bar:1.2.3-SNAPSHOT cljs serve
to build the project with the checkout dependency [foo/bar \"1.2.3\"]."
[d dependencies ID:VER [[sym str]] "The vector of checkout dependencies."]
(let [env (core/get-env)
prev (atom nil)
jars (->> dependencies
(map (comp io/file #(pod/resolve-dependency-jar env %))))
deps (->> dependencies
(mapcat #(pod/resolve-nontransitive-dependencies env %))
(map :dep)
(remove pod/dependency-loaded?))
names (map (memfn getName) jars)
dirs (map (memfn getParent) jars)
tmps (reduce #(assoc %1 %2 (core/tmp-dir!)) {} names)
warn (delay
(util/warn-deprecated
"The checkout task is deprecated. Please use the --checkouts boot option instead.\n"))
adder #(core/add-source %1 %2 :exclude pod/standard-jar-exclusions)]
(when (seq deps)
@warn
(util/info "Adding checkout dependencies:\n")
(doseq [dep deps]
(util/info "\u2022 %s\n" (pr-str dep))))
(core/merge-env! :dependencies (vec deps)
:source-paths (set dirs))
(core/cleanup (core/set-env! :source-paths #(apply disj % dirs)))
(core/with-pre-wrap [fs]
(let [diff (->> (core/fileset-diff @prev fs)
core/input-files
(filter (comp (set names) core/tmp-path))
(map (juxt core/tmp-path core/tmp-file)))]
(reset! prev fs)
(doseq [[path file] diff]
(let [tmp (tmps path)]
(core/empty-dir! tmp)
(util/info "Checking out %s...\n" path)
(pod/unpack-jar (.getPath file) tmp)))
(->> tmps vals (reduce adder fs) core/commit!)))))
(core/deftask ^{:deprecated "2.7.0"} speak
"Audible notifications during build. DEPRECATED. Use the notify task.
Default themes: system (the default), ordinance, pillsbury, and
woodblock. New themes can be included via jar dependency with the
sound files as resources:
boot
└── notify
├── <theme-name>_failure.mp3
├── <theme-name>_success.mp3
└── <theme-name>_warning.mp3
Sound files specified individually take precedence over theme sounds."
[t theme NAME str "The notification sound theme."
s success FILE str "The sound file to play when the build is successful."
w warning FILE str "The sound file to play when there are warnings reported."
f failure FILE str "The sound file to play when the build fails."]
(let [tmp (core/tmp-dir!)
resource #(vector %2 (format "boot/notify/%s_%s.mp3" %1 %2))
resources #(map resource (repeat %) ["success" "warning" "failure"])
themefiles (into {}
(let [rs (when theme (resources theme))]
(when (and (seq rs) (every? (comp io/resource second) rs))
(for [[x r] rs]
(let [f (io/file tmp (.getName (io/file r)))]
(pod/copy-resource r f)
[(keyword x) (.getPath f)])))))
success (or success (:success themefiles))
warning (or warning (:warning themefiles))
failure (or failure (:failure themefiles))]
(fn [next-task]
(fn [fileset]
(try
(util/with-let [_ (next-task fileset)]
(if (zero? @core/*warnings*)
(pod/with-call-worker (boot.notify/success! ~theme ~success))
(pod/with-call-worker (boot.notify/warning! ~theme ~(deref core/*warnings*) ~warning))))
(catch Throwable t
(pod/with-call-worker (boot.notify/failure! ~theme ~failure))
(throw t)))))))
(core/deftask ^{:boot/from :jeluard/boot-notify} notify
"Audible and visual notifications during build.
Default audio themes: system (the default), ordinance, pillsbury and
woodblock. New themes can be included via jar dependency with the
sound files as resources:
boot
└── notify
├── <theme-name>_failure.mp3
├── <theme-name>_success.mp3
└── <theme-name>_warning.mp3
Sound files specified individually take precedence over theme sounds.
For visual notifications, there is a default implementation that
tries to use the `terminal-notifier' or `osascript` programs on OS X
systems, and the `notify-send' program on Linux systems.
You can also supply custom notification functions via the *-notify-fn
options. Both are functions that take one argument which is a map of
options.
The audible notification function will receive a map with three keys
- :type, :file, and :theme.
The visual notification function will receive a map with four keys
- :title, :uid, :icon, and :message.
The `notify` task always attempts to catch any exceptions that are
thrown so as not to cause your build to fail. If no notifications
are happening even if you specify `audible` or `visual`, you can see
any exceptions that are being caught by changing
`boot.util/*verbosity*` (at the command-line use: `boot -v` or `boot
-vv`)."
[a audible bool "Play an audible notification"
v visual bool "Display a visual notification"
A audible-notify-fn FN sym "A function to be used for audible notifications in place of the default method."
V visual-notify-fn FN sym "A function to be used for visual notifications in place of the default method"
T theme NAME str "The name of the audible notification sound theme"
s soundfiles KEY=VAL {kw str} "Sound files overriding theme sounds. Keys can be :success, :warning or :failure"
m messages KEY=VAL {kw str} "Templates overriding default messages. Keys can be :success, :warning or :failure"
t title TITLE str "Title of the notification"
i icon ICON str "Full path of the file used as notification icon"
u uid UID str "Unique ID identifying this boot process"]
(let [themefiles (notify/get-themefiles theme (core/tmp-dir!))
sounds (merge themefiles soundfiles)
title (or title "Boot")
base-visual-opts {:title title
:uid (or uid title)
:icon (or icon (notify/boot-logo))}
messages (merge {:success "Success!" :warning "%s warning/s" :failure "%s"}
messages)
audible-notify! (or audible-notify-fn notify/audible-notify!)
visual-notify! (or visual-notify-fn notify/visual-notify!)
notify! (fn [type message]
(when audible
(audible-notify! {:type type
:file (get sounds type)
:theme theme}))
(when visual
(visual-notify! (assoc base-visual-opts
:message message))))]
(fn [next-task]
(fn [fileset]
(try
(util/with-let [_ (next-task fileset)]
(if (zero? @core/*warnings*)
(notify! :success (:success messages))
(notify! :warning (format (:warning messages)
(deref core/*warnings*)))))
(catch Throwable t
(notify! :failure (format (:failure messages)
(.getMessage t)))
(throw t)))))))
(core/deftask show
"Print project/build info (e.g. dependency graph, etc)."
[C fake-classpath bool "Print the project's fake classpath."
c classpath bool "Print the project's full classpath."
d deps bool "Print project dependency graph."
e env bool "Print the boot env map."
f fileset bool "Print the build fileset object."
l list-pods bool "Print the names of all active pods."
p pedantic bool "Print graph of dependency conflicts."
P pods REGEX regex "The name filter used to select which pods to inspect."
U update-snapshots bool "Include snapshot versions in updates searches."
u updates bool "Print newer releases of outdated dependencies."
v verify-deps bool "Include signature status of each dependency in graph."]
(let [usage (delay (*usage*))
pretty-str #(with-out-str (pp/pprint %))
updates (or updates update-snapshots)
sort-pods #(sort-by (memfn getName) %)]
(core/with-pass-thru [fs]
(cond fileset (helpers/print-fileset fs)
classpath (println (or (core/get-env :boot-class-path) ""))
fake-classpath (println (or (core/get-env :fake-class-path) ""))
list-pods (doseq [p (->> pod/pods (map key) sort-pods)]
(println (.getName p)))
:else
(let [pattern (or pods #"^core$")]
(doseq [p (->> pattern pod/get-pods sort-pods)
:let [pod-name (.getName p)]
:when (not (#{"worker" "aether"} pod-name))]
(let [pod-env (if (= pod-name "core")
(core/get-env)
(pod/with-eval-in p boot.pod/env))]
(when pods (util/info "\nPod: %s\n\n" pod-name))
(cond (or deps verify-deps) (print (pod/with-call-worker (boot.aether/dep-tree ~pod-env ~verify-deps)))
env (println (pretty-str (assoc pod-env :config (boot.App/config))))
updates (mapv prn (pod/outdated pod-env :snapshots update-snapshots))
pedantic (pedantic/prn-conflicts pod-env)
:else @usage))))))))
(defn- relativize
[local-repo path]
(.getPath
(if-not local-repo
(io/file path)
(let [canonical-local-repo (.getCanonicalFile (io/file local-repo))]
(io/file local-repo (file/relative-to canonical-local-repo path))))))
(defn- dep-conflicts
[{:keys [dependencies] :as env}]
(let [non-transitive? (set (map first dependencies))]
(into {} (->> (pedantic/dep-conflicts env)
(remove (comp non-transitive? first))))))
(core/deftask with-cp
"Specify Boot's classpath in a file instead of as Maven coordinates.
The --file option is required -- this specifies the PATH of the file that
will contain the JAR paths as a string suitable for passing to java -cp. Use
the minus sign, like --file -, to indicate stdin, stdout.
The default behavior if the --write flag is not specified is to read the
file specified with --file and load all the JARs onto the classpath. If the
--write flag is given the --dependencies (or the default depenedncies from
the boot env, e.g. (get-env :dependencies), if --dependencies isn't provided)
are resolved and the resulting list of JAR paths are written to the file in
a format suitable for passing to java -cp.
The --safe flag configures the task to throw an exception when writing the
classpath file if there are any unresolved dependency conflicts. These
conflicts can be resolved by adding :exclusions and by overriding transitive
dependencies with direct dependencies.
The --local-repo option specifies the PATH where the dependency JARs are
stashed. The default if this option is not given is to use the Maven local
repository setting from the boot environment. This option only applies in
combination with the --write option.
The --scopes option can be used to specify which dependency scopes to include
in the classpath file. The default scopes are compile, runtime, and provided."
[s safe bool "Throw an exception if there are unresolved dependency conflicts."
w write bool "Resolve dependencies and write the classpath file."
d dependencies EDN edn "The (optional) Maven dependencies to resolve and write to the classpath file."
e exclusions SYM [sym] "The vector of Maven group/artifact ids to globally exclude."
f file PATH str "The file containing JARs in java -cp format."
l local-repo PATH str "The (optional) project directory in which to stash resolved JARs."
S scopes SCOPE #{str} "The set of dependency scopes to include (default compile, runtime, provided)."]
(core/with-pass-thru [_]
(let [scopes (or scopes #{"compile" "runtime" "provided"})
file-in (if (= file "-") (System/in) file)
file-out (if (= file "-") (System/out) file)
include? #(scopes (:scope (util/dep-as-map %)))
env-opts (select-keys *opts* [:local-repo :exclusions :dependencies])
exclude (partial pod/apply-global-exclusions exclusions)]
(assert file "Expected --file option.")
(if-not write
(doseq [path (string/split (slurp file-in) #":")]
(pod/add-classpath path))
(let [env (-> (merge-with #(or %2 %1) pod/env env-opts)
(update-in [:dependencies] #(exclude (filter include? %))))]
(if-let [conflicts (and safe (not-empty (dep-conflicts env)))]
(throw (ex-info "Unresolved dependency conflicts." {:conflicts conflicts}))
(let [resolved (pod/resolve-dependency-jars env)
relative-paths (map (partial relativize local-repo) resolved)
source-paths (:source-paths env)]
(spit file-out (apply str (->> (concat source-paths relative-paths)
(interpose ":")
(into [])))))))))))
(core/deftask wait
"Wait before calling the next handler.
Waits forever if the --time option is not specified."
[t time MSEC int "The interval in milliseconds."]
(if (zero? (or time 0))
(core/with-post-wrap [_] @(promise))
(core/with-pass-thru [fs] (Thread/sleep time))))
(defn- mk-posix-file-permissions [posix-string]
(try
(when posix-string
(if (boot.App/isWindows)
(util/warn "Filemode not supported on Windows\n")
(PosixFilePermissions/fromString posix-string)))
(catch IllegalArgumentException _
(util/warn "Could not parse mode string, ignoring...\n"))))
(core/deftask target
"Writes output files to the given directory on the filesystem."
[d dir PATH #{str} "The set of directories to write to (target)."
m mode VAL str "The mode of written files in 'rwxrwxrwx' format"
L no-link bool "Don't create hard links."
C no-clean bool "Don't clean target before writing project files."]
(let [dir (or (seq dir) ["target"])
sync! (#'core/fileset-syncer dir :clean (not no-clean))]
(core/with-pass-thru [fs]
(util/info "Writing target dir(s)...\n")
(sync! fs :link (not no-link) :mode (mk-posix-file-permissions mode)))))
(core/deftask watch
"Call the next handler when source files change."
[q quiet bool "Suppress all output from running jobs."
v verbose bool "Print which files have changed."
M manual bool "Use a manual trigger instead of a file watcher."
d debounce MS int "Debounce time (how long to wait for filesystem events) in milliseconds."
i include REGEX #{regex} "The set of regexes the paths of changed files must match for watch to fire."
e exclude REGEX #{regex} "The set of regexes the paths of changed files must not match for watch to fire."]
(fn [next-task]
(fn [fileset]
(let [q (LinkedBlockingQueue.)
k (gensym)
return (atom fileset)
srcdirs (->> (map (comp :dir val) (core/get-checkouts))
(into (core/user-dirs fileset))
(map (memfn getPath)))
watcher (apply file/watcher! :time srcdirs)
incl-excl (if-not (or (seq include) (seq exclude))
identity
(let [f (partial file/keep-filters? include exclude)]
(partial filter (comp f io/file second))))
watch-target (if manual core/new-build-at core/last-file-change)]
(.offer q (System/currentTimeMillis))
(add-watch watch-target k #(.offer q %4))
(core/cleanup (remove-watch watch-target k))
(when-not quiet (util/info "\nStarting file watcher (CTRL-C to quit)...\n\n"))
(loop [ret (util/guard [(.take q)])]
(when ret
(if-let [more (.poll q (or debounce 10) TimeUnit/MILLISECONDS)]
(recur (conj ret more))
(let [start (System/currentTimeMillis)
etime #(- (System/currentTimeMillis) start)
changed (when-not manual (incl-excl (watcher)))
should-fire? (or manual (not (empty? changed)))]
(when should-fire?
(when verbose
(doseq [[op p _] changed]
(util/info (format "\u25C9 %s %s\n" op p)))
(util/info "\n"))
(binding [*out* (if quiet (new java.io.StringWriter) *out*)
*err* (if quiet (new java.io.StringWriter) *err*)]
(core/reset-build!)
(try (reset! return (-> fileset core/reset-fileset core/commit! next-task))
(catch Throwable ex (util/print-ex ex)))
(util/info "Elapsed time: %.3f sec\n\n" (float (/ (etime) 1000)))))
(recur (util/guard [(.take q)]))))))
@return))))
(core/deftask repl
"Start a REPL session for the current project.
If no bind/host is specified the REPL server will listen on 127.0.0.1 and
the client will connect to 127.0.0.1.
If no port is specified the server will choose a random one and the client
will read the .nrepl-port file and use that.
The *default-middleware* and *default-dependencies* atoms in the boot.repl
namespace contain vectors of default REPL middleware and REPL dependencies to
be loaded when starting the server. You may modify these in your build.boot
file."
[s server bool "Start REPL server only."
c client bool "Start REPL client only."
C no-color bool "Disable colored REPL client output."
e eval EXPR edn "The form the client will evaluate in the boot.user ns."
b bind ADDR str "The address server listens on."
H host HOST str "The host client connects to."
i init PATH str "The file to evaluate in the boot.user ns."
I skip-init bool "Skip default client initialization code."
p port PORT int "The port to listen on and/or connect to."
P pod NAME str "The name of the pod to start nREPL server in (core)."
n init-ns NS sym "The initial REPL namespace."
m middleware SYM [sym] "The REPL middleware vector."
x handler SYM sym "The REPL handler (overrides middleware options)."]
(when (string? eval)
(util/warn "When passing :eval to the repl task in your build.boot, use a quoted form instead of a string\n"))
(let [cpl-path (.getPath (core/tmp-dir!))
srv-opts (->> [:bind :port :init-ns :middleware :handler :pod]
(select-keys *opts*))
cli-opts (-> *opts*
(select-keys [:host :port :history])
(assoc :standalone true
:custom-eval eval
:custom-init init
:color (and @util/*colorize?* (not no-color))
:skip-default-init skip-init))
deps (remove pod/dependency-loaded? @repl/*default-dependencies*)
repl-svr (delay (apply core/launch-nrepl (mapcat identity srv-opts)))
repl-cli (delay (pod/with-call-worker (boot.repl-client/client ~cli-opts)))]
(comp (core/with-pass-thru [fs]
(when (or server (not client)) @repl-svr))
(core/with-post-wrap [_]
(when (or client (not server)) @repl-cli)))))
(core/deftask bare-repl
"Start a bare REPL session for the current project.
Compared to the repl task, the bare-repl task starts up more quickly but
lacks features such as nREPL connectivity and colored stacktraces.
Use the rlwrap Unix tool to add readline functionality:
# rlwrap boot bare-repl"
[e eval EXPR edn "The form the client will evaluate in the boot.user ns."
i init PATH str "The file to evaluate in the boot.user ns."
n init-ns NS sym "The initial REPL namespace."]
(core/with-pass-thru [_]
(repl/launch-bare-repl *opts*)))
(core/deftask socket-server
"Start a socket server.
The default behavior is to serve a simple REPL handled by
clojure.core.server/repl. To serve a different handler function, specify a
symbol using `--accept'.
If no bind address is specified, the socket server will listen on 127.0.0.1.
If no port is specified, an open port will be chosen automatically. The port
number is written to .socket-port in the current directory.
The REPL can be accessed with the command
$ nc localhost $(cat .socket-port)"
[b bind ADDR str "The address server listens on."
p port PORT int "The port to listen to."
a accept ACCEPT sym "Namespaced symbol of the accept function to invoke."]
(let [repl-soc (delay (repl/launch-socket-server *opts*))]
(core/with-pass-thru [fs]
@repl-soc)))
(core/deftask prepl-server
"Start a prepl server.
This task is a thin wrapper around the `socket-server` task. See the
docstring for `socket-server` for details."
[b bind ADDR str "The address server listens on."
p port PORT int "The port to listen to."]
(socket-server
:bind bind
:accept 'clojure.core.server/io-prepl
:port port))
(core/deftask pom
"Create project pom.xml file.
The project and version must be specified to make a pom.xml.
Note that if you want to install some other artifact along with the main one,
for instance the classic sources or javadoc artifact, you have to add the
classifier to your pom.xml, which translates to adding :classifier to this
task."
[p project SYM sym "The project id (eg. foo/bar)."
v version VER str "The project version."
d description DESC str "The project description."
c classifier STR str "The project classifier."
P packaging STR str "The project packaging type, i.e. war, pom"
u url URL str "The project homepage url."
s scm KEY=VAL {kw str} "The project scm map (KEY is one of url, tag, connection, developerConnection)."
l license NAME:URL {str str} "The map {name url} of project licenses."
o developers NAME:EMAIL {str str} "The map {name email} of project developers."
D dependencies SYM:VER [[sym str]] "The project dependencies vector (overrides boot env dependencies)."
a parent SYM:VER=PATH [sym str str] "The project dependency vector of the parent project, path included."]
(let [tgt (core/tmp-dir!)
tag (or (:tag scm) (util/guard (git/last-commit)))
scm (when scm (assoc scm :tag tag))
deps (or dependencies (:dependencies (core/get-env)))
opts (assoc *opts*
:scm scm
:dependencies deps
:developers developers
:classifier classifier
:packaging (or packaging "jar")
:parent parent)]
(when-not (and project version)
(throw (Exception. "need project and version to create pom.xml")))
(let [[project version] (pod/canonical-coord [project version])
[gid aid] (util/extract-ids project)
pomdir (io/file tgt "META-INF" "maven" gid aid)
xmlfile (io/file pomdir "pom.xml")
propfile (io/file pomdir "pom.properties")]
(pod/with-call-worker
(boot.pom/spit-pom! ~(.getPath xmlfile) ~(.getPath propfile) ~opts))
(core/with-pre-wrap [fs]
(util/info "Writing %s and %s...\n" (.getName xmlfile) (.getName propfile))
(-> fs (core/add-resource tgt :meta {:project project}) core/commit!)))))
(core/deftask sift
"Transform the fileset, matching paths against regexes.
The --to-asset, --to-resource, and --to-source options move matching paths
to the corresponding section of the fileset. This can be used to make source
files into resource files, for example, etc. If --invert is also specified
the transformation is done to paths that DO NOT match.
The --add-asset, --add-resource, and --add-source options add the contents
of a directory to the fileset as assets, resources, or sources, respectively.
The --invert option has no effect on these options.
The --add-jar option extracts the contents of a jar file on the classpath
and adds them to the fileset. The PROJECT part of the argument specifies the
group-id/artifact-id symbol associated with the jar, and the MATCH portion
selects which entries in the jar will be extracted. If --invert is also
specified then entries whose paths DO NOT match the regex will be extracted.
The --with-meta option specifies a set of metadata keys files in the fileset
must have. Files without one of these keys will be filtered out. If --invert
is also specified then files that DO have one of these keys will be filtered
out, instead.
The --add-meta option adds a key to the metadata map associated with paths
matching the regex portion of the argument. For example:
boot sift --add-meta 'foo$':bar
merges {:bar true} into the metadata map associated with all paths that end
with 'foo'. If --invert is also specified the metadata is added to paths
that DO NOT match the regex portion.
The --move option applies a find/replace transformation on all paths in the
output fileset. The --invert option has no effect on this operation.
The --include option specifies a set of regexes that will be used to filter
the fileset. Only paths matching one of these will be kept. If --invert is
also specified then only paths NOT matching one of the regexes will be kept."
[a to-asset MATCH #{regex} "The set of regexes of paths to move to assets."
r to-resource MATCH #{regex} "The set of regexes of paths to move to resources."
s to-source MATCH #{regex} "The set of regexes of paths to move to sources."
A add-asset PATH #{str} "The set of directory paths to add to assets."
R add-resource PATH #{str} "The set of directory paths to add to resources."
S add-source PATH #{str} "The set of directory paths to add to sources."
j add-jar PROJECT:MATCH {sym regex} "The map of jar to path regex of entries in jar to unpack."
w with-meta KEY #{kw} "The set of metadata keys files must have."
M add-meta MATCH:KEY {regex kw} "The map of path regex to meta key to add."
m move MATCH:REPLACE {regex str} "The map of regex to replacement path strings."
i include MATCH #{regex} "The set of regexes that paths must match."
v invert bool "Invert the sense of matching."]
(let [v? (:invert *opts*)
*opts* (dissoc *opts* :invert)
action (partial helpers/sift-action v?)
process (reduce-kv #(comp (action %2 %3) %1) identity *opts*)]
(core/with-pre-wrap [fs]
(util/info "Sifting output files...\n")
(util/dbug* "%s\n" (util/pp-str (assoc *opts* :invert v?)))
(-> fs process core/commit!))))
(core/deftask add-repo
"Add all files in project git repo to fileset.
The ref option (default HEAD) facilitates pulling files from tags or specific
commits."
[u untracked bool "Add untracked (but not ignored) files."
r ref REF str "The git reference for the desired file tree."]
(let [tgt (core/tmp-dir!)]
(core/with-pre-wrap [fs]
(core/empty-dir! tgt)
(util/info "Adding repo files...\n")
(doseq [p (core/git-files :ref ref :untracked untracked)]
(file/copy-with-lastmod (io/file p) (io/file tgt p)))
(-> fs (core/add-resource tgt) core/commit!))))
(core/deftask uber
"Add jar entries from dependencies to fileset.
Use this task before the packaging task (jar, war, etc.) to create
uberjars, uberwars, etc. This provides the means to package the project
with all of its dependencies included.
By default, entries from dependencies with the following scopes will be
copied to the fileset: compile, runtime, and provided. The --include-scope
and --exclude-scope options may be used to add or remove scope(s) from this
set.
The --as-jars option pulls in dependency jars without exploding them such
that the jarfiles themselves are copied into the fileset. When using the
--as-jars option you need a special classloader like a servlet container
(e.g. Tomcat, Jetty) that will add the jars to the application classloader.
When jars are exploded, the --include and --exclude options control which
paths are added to the uberjar; a path is only added if it matches an
--include regex and does not match any --exclude regexes.
The --exclude option default is:
#{ #\"(?i)^META-INF/INDEX.LIST$\"
#\"(?i)^META-INF/[^/]*\\.(MF|SF|RSA|DSA)$\" }
And --include option default is:
#{ #\".*\" }
If exploding the jars results in duplicate entries, they will be merged
using the rules specified by the --merge option. A merge rule is a
[regex fn] pair, where fn takes three parameters:
- an InputStream for the previous entry,
- an InputStream of the new entry,
- and an OutputStream that will replace the entry.
The --merge option default is:
[[ #\"data_readers.clj$\" into-merger ]
[ #\"META-INF/services/.*\" concat-merger ]
[ #\".*\" first-wins-merger ]]
The merge rule regular expressions are tested in order, and the fn from
the first match is applied.
Setting the --include, --exclude, or --merge options replaces the default."
[j as-jars bool "Copy entire jar files instead of exploding them."
s include-scope SCOPE #{str} "The set of scopes to add."
S exclude-scope SCOPE #{str} "The set of scopes to remove."
i include MATCH #{regex} "The set of regexes that paths must match."
e exclude MATCH #{regex} "The set of regexes that paths must not match."
m merge REGEX=FN [[regex code]] "The list of duplicate file mergers."]
(let [tgt (core/tmp-dir!)
cache (core/cache-dir! ::uber :global true)
dfl-scopes #{"compile" "runtime" "provided"}
scopes (-> dfl-scopes
(set/union include-scope)
(set/difference exclude-scope))
scope? #(contains? scopes (:scope (util/dep-as-map %)))
jars (-> (core/get-env)
(update-in [:dependencies] (partial filter scope?))
pod/resolve-dependency-jars)
jars (remove #(.endsWith (.getName %) ".pom") jars)
checkouts (->> (core/get-checkouts)
(filter (comp scope? :dep val))
(into {}))
co-jars (->> checkouts (map (comp :jar val)))
co-dirs (->> checkouts (map (comp :dir val)))
exclude (or exclude pod/standard-jar-exclusions)
merge (or merge pod/standard-jar-mergers)
reducer (fn [xs jar]
(core/add-cached-resource
xs (digest/md5 jar) (partial pod/unpack-jar jar)
:include include :exclude exclude :mergers merge))
co-reducer #(core/add-resource
%1 %2 :include include :exclude exclude :mergers merge)]
(core/with-pre-wrap [fs]
(when (seq jars)
(util/info "Adding uberjar entries...\n"))
(when as-jars
(doseq [jar (reduce into [] [jars co-jars])]
(let [hash (digest/md5 jar)
name (str hash "-" (.getName jar))
src (io/file cache hash)]
(when-not (.exists src)
(util/dbug* "Caching jar %s...\n" name)
(file/copy-atomically jar src))
(util/dbug* "Adding cached jar %s...\n" name)
(file/hard-link src (io/file tgt name)))))
(core/commit! (if as-jars
(core/add-resource fs tgt)
(reduce co-reducer (reduce reducer fs jars) co-dirs))))))
(core/deftask web
"Create project web.xml file.
The --serve option is required. The others are optional."
[s serve SYM sym "The 'serve' callback function."
c create SYM sym "The 'create' callback function."
d destroy SYM sym "The 'destroy' callback function."
C context-create SYM sym "The context 'create' callback function, called when the servlet is first loaded by the container."
D context-destroy SYM sym "The context 'destroyed' callback function, called when the servlet is unloaded by the container."]
(let [tgt (core/tmp-dir!)
xmlfile (io/file tgt "WEB-INF" "web.xml")
implp 'tailrecursion/clojure-adapter-servlet
implv "0.2.1"
classes #"^tailrecursion/.*\.(class|clj)$"
webxml (delay
(util/info "Adding servlet impl...\n")
(pod/copy-dependency-jar-entries
(core/get-env) tgt [implp implv] classes)
(util/info "Writing %s...\n" (.getName xmlfile))
(pod/with-call-worker
(boot.web/spit-web! ~(.getPath xmlfile)
~serve
~create
~destroy
~context-create
~context-destroy)))]
(core/with-pre-wrap [fs]
(assert (and (symbol? serve) (namespace serve))
(format "serve function must be namespaced symbol (%s)" serve))
@webxml
(-> fs (core/add-resource tgt) core/commit!))))
(core/deftask aot
"Perform AOT compilation of Clojure namespaces."
[a all bool "Compile all namespaces."
n namespace NS #{sym} "The set of namespaces to compile."]
(when (empty? *opts*)
(util/warn "No flags specified for aot task, skipping...\n"))
(let [tgt (core/tmp-dir!)
pod-env (update-in (core/get-env) [:directories] conj (.getPath tgt))
compile-pod (future (pod/make-pod pod-env))]
(core/with-pre-wrap [fs]
(core/empty-dir! tgt)
(let [all-nses (->> fs core/fileset-namespaces)
nses (->> all-nses (set/intersection (if all all-nses namespace)) sort)]
(pod/with-eval-in @compile-pod
(binding [*compile-path* ~(.getPath tgt)]
(doseq [[idx ns] (map-indexed vector '~nses)]
(boot.util/info "Compiling %s/%s %s...\n" (inc idx) (count '~nses) ns)
(compile ns)))))
(-> fs (core/add-resource tgt) core/commit!))))
(core/deftask javac
"Compile java sources."
[o options OPTIONS [str] "List of options passed to the java compiler."]
(let [tgt (core/tmp-dir!)]
(core/with-pre-wrap [fs]
(let [throw? (atom nil)
diag-coll (DiagnosticCollector.)
compiler (or (ToolProvider/getSystemJavaCompiler)
(throw (Exception. "The java compiler is not working. Please make sure you use a JDK!")))
file-mgr (.getStandardFileManager compiler diag-coll nil nil)
opts (->> ["-d" (.getPath tgt)
"-cp" (core/get-env :boot-class-path)]
(concat options)
(into-array String) Arrays/asList)
handler {Diagnostic$Kind/ERROR util/fail
Diagnostic$Kind/WARNING util/warn
Diagnostic$Kind/MANDATORY_WARNING util/warn}
srcs (some->> (core/input-files fs)
(core/by-ext [".java"])
(map core/tmp-file)
(into-array File)
Arrays/asList
(.getJavaFileObjectsFromFiles file-mgr))]
(when (seq srcs)
(util/info "Compiling %d Java source files...\n" (count srcs))
(-> compiler (.getTask *err* file-mgr diag-coll opts nil srcs) .call)
(doseq [d (.getDiagnostics diag-coll) :let [k (.getKind d)]]
(when (= Diagnostic$Kind/ERROR k) (reset! throw? true))
(let [log (handler k util/info)]
(if (nil? (.getSource d))
(log "%s: %s\n"
(.toString k)
(.getMessage d nil))
(log "%s: %s, line %d: %s\n"
(.toString k)
(.. d getSource getName)
(.getLineNumber d)
(.getMessage d nil)))))
(.close file-mgr)
(when @throw? (throw (Exception. "java compiler error")))))
(-> fs (core/add-resource tgt) core/commit!))))
(defn- sift-poms
[fileset project]
(let [poms (->> (core/output-files fileset)
(core/by-name ["pom.xml"]))
prj-match (when project
(str "/" (pod/full-id project) "/pom.xml"))
project? #(if-not prj-match
(:project %)
(.endsWith (core/tmp-path %) prj-match))]
(if (< (count poms) 2) poms (->> poms (filter project?)))))
(core/deftask jar
"Build a jar file for the project."
[f file PATH str "The target jar file name."
M manifest KEY=VAL {str str} "The jar manifest map."
m main MAIN sym "The namespace containing the -main function."
p project SYM sym "The project symbol -- used to find the correct pom.xml file."]
(let [old-fs (atom nil)
tgt (core/tmp-dir!)
out (atom nil)]
(core/with-pre-wrap [fs]
(let [new-fs (core/output-fileset fs)
[pom & p] (map core/tmp-file (sift-poms fs project))
{:keys [project version]}
(when (and pom (not (seq p)))
(pod/with-call-worker
(boot.pom/pom-xml-parse-string ~(slurp pom))))
pomname (when (and project version)
(str (name project) "-" version ".jar"))
fname (or file pomname "project.jar")
out* (io/file tgt fname)]
(when (and project (seq p))
(util/warn "Multiple pom.xml files for project: %s\n" project))
(when (not= @out out*)
(when (and @out (.exists @out))
(file/move @out out*))
(reset! out out*))
(util/info "Writing %s...\n" fname)
(jar/update-jar! @out @old-fs (reset! old-fs new-fs) manifest main)
(-> fs (core/add-resource tgt) core/commit!)))))
(core/deftask war
"Create war file for web deployment."
[f file PATH str "The target war file name."]
(let [tgt (core/tmp-dir!)]
(core/with-pre-wrap [fs]
(core/empty-dir! tgt)
(let [warname (or file "project.war")
warfile (io/file tgt warname)
inf? #(contains? #{"META-INF" "WEB-INF"} %)
->war #(let [r (core/tmp-path %)
r' (file/split-path r)
path (->> (if (.endsWith r ".jar")
["lib" (last r')]
(into ["classes"] r'))
(into ["WEB-INF"]))]
(if (inf? (first r')) r (.getPath (apply io/file path))))
entries (core/output-files fs)
index (->> entries (mapv (juxt ->war #(.getPath (core/tmp-file %)))))]
(util/info "Writing %s...\n" (.getName warfile))
(jar/spit-jar! (.getPath warfile) index {} nil)
(-> fs (core/add-resource tgt) core/commit!)))))
(core/deftask zip
"Build a zip file for the project."
[f file PATH str "The target zip file name."]
(let [old-fs (atom nil)
tgt (core/tmp-dir!)
fname (or file "project.zip")
out (io/file tgt fname)]
(core/with-pre-wrap [fs]
(let [new-fs (core/output-fileset fs)]
(util/info "Writing %s...\n" fname)
(jar/update-zip! out @old-fs (reset! old-fs new-fs))
(-> fs (core/add-resource tgt) core/commit!)))))
(core/deftask install
"Install project jar to local Maven repository.
The --file option allows installation of arbitrary jar files. If no
file option is given then any jar artifacts created during the build
will be installed.
The pom.xml file that's required when installing a jar can usually be
found in the jar itself. However, sometimes a jar might contain more
than one pom.xml file or may not contain one at all.
The --pom option can be used in these situations to specify which
pom.xml file to use. The optarg denotes either the path to a pom.xml
file in the filesystem or a subdir of the META-INF/maven/ dir in which
the pom.xml contained in the jar resides.
Example:
Given a jar file (warp-0.1.0.jar) with the following contents:
.
├── META-INF
│ ├── MANIFEST.MF
│ └── maven
│ └── tailrecursion
│ └── warp
│ ├── pom.properties
│ └── pom.xml
└── tailrecursion
└── warp.clj
The jar could be installed with the following boot command:
$ boot install -f warp-0.1.0.jar -p tailrecursion/warp
Note that if you want to install some other artifact along with the main one,
for instance the classic sources or javadoc artifact, you have to add the
classifier to your pom.xml, which translates to adding :classifier to the pom
task."
[f file PATH str "The jar file to install."
p pom PATH str "The pom.xml file to use."]
(core/with-pass-thru [fs]
(let [jarfiles (or (and file [(io/file file)])
(->> (core/output-files fs)
(core/by-ext [".jar"])
(map core/tmp-file)))]
(when-not (seq jarfiles) (throw (Exception. "can't find jar file")))
(doseq [jarfile jarfiles]
(util/info "Installing %s...\n" (.getName jarfile))
(pod/with-call-worker
(boot.aether/install ~(core/get-env) ~(.getPath jarfile) ~pom))))))