|
1 | 1 | (ns lambdaisland.launchpad
|
2 |
| - (:require [babashka.curl :as curl] |
3 |
| - [babashka.process :refer [process]] |
4 |
| - [babashka.wait :as wait] |
5 |
| - [cheshire.core :as json] |
6 |
| - [clojure.core.async :as async] |
7 |
| - [clojure.edn :as edn] |
8 |
| - [clojure.java.io :as io] |
9 |
| - [clojure.java.shell :as shell] |
10 |
| - [clojure.pprint :as pprint] |
11 |
| - [clojure.string :as str] |
12 |
| - [clojure.tools.cli :as tools-cli] |
13 |
| - [lambdaisland.dotenv :as dotenv]) |
14 |
| - (:import java.net.ServerSocket)) |
| 2 | + (:require |
| 3 | + [babashka.process :refer [process]] |
| 4 | + [babashka.wait :as wait] |
| 5 | + [clojure.edn :as edn] |
| 6 | + [clojure.java.io :as io] |
| 7 | + [clojure.java.shell :as shell] |
| 8 | + [clojure.pprint :as pprint] |
| 9 | + [clojure.string :as str] |
| 10 | + [clojure.tools.cli :as tools-cli] |
| 11 | + [lambdaisland.dotenv :as dotenv]) |
| 12 | + (:import |
| 13 | + (java.io InputStream OutputStream) |
| 14 | + (java.lang Process ProcessBuilder) |
| 15 | + (java.net ServerSocket) |
| 16 | + (java.util.concurrent TimeUnit))) |
15 | 17 |
|
16 | 18 | (def cli-opts
|
17 | 19 | [["-h" "--help"]
|
|
122 | 124 | (read-string
|
123 | 125 | (eval-emacs 'cljr-injected-middleware-version))))
|
124 | 126 |
|
| 127 | +(defn add-nrepl-middleware [& mws] |
| 128 | + (fn [ctx] |
| 129 | + (update ctx :middleware (fnil into []) mws))) |
| 130 | + |
125 | 131 | (defn compute-middleware
|
126 | 132 | "Figure out the nREPL middleware based on CLI flags"
|
127 | 133 | [{:keys [options] :as ctx}]
|
128 |
| - (let [add-mw #(update %1 :middleware (fnil conj []) %2)] |
129 |
| - (cond-> ctx |
130 |
| - (:cider-nrepl options) |
131 |
| - (add-mw 'cider.nrepl/cider-middleware) |
132 |
| - (:refactor-nrepl options) |
133 |
| - (add-mw 'refactor-nrepl.middleware/wrap-refactor)))) |
| 134 | + (cond-> ctx |
| 135 | + (:cider-nrepl options) |
| 136 | + ((add-nrepl-middleware 'cider.nrepl/cider-middleware)) |
| 137 | + (:refactor-nrepl options) |
| 138 | + ((add-nrepl-middleware 'refactor-nrepl.middleware/wrap-refactor)))) |
134 | 139 |
|
135 | 140 | (defn compute-extra-deps [{:keys [options] :as ctx}]
|
136 | 141 | (let [assoc-dep #(update %1 :extra-deps assoc %2 %3)]
|
|
353 | 358 | (defn include-launchpad-deps [{:keys [extra-deps] :as ctx}]
|
354 | 359 | (update ctx :extra-deps assoc 'com.lambdaisland/launchpad (find-launchpad-coords)))
|
355 | 360 |
|
356 |
| -(defn start-process [{:keys [options aliases nrepl-port env] :as ctx}] |
357 |
| - (let [args (clojure-cli-args ctx)] |
358 |
| - (apply debug (map shellquote args)) |
359 |
| - (let [process (process args {:env env :out :inherit :err :inherit})] |
360 |
| - (assoc ctx :clojure-process process)))) |
361 |
| - |
362 | 361 | (defn maybe-connect-emacs [{:keys [options nrepl-port project-root] :as ctx}]
|
363 | 362 | (when (:cider-connect options)
|
364 | 363 | (debug "Connecting CIDER with project-dir" project-root)
|
|
398 | 397 | (:extra-deps ctx)))
|
399 | 398 | ctx)
|
400 | 399 |
|
| 400 | +(defn pipe-process-output |
| 401 | + "Prefix output from a process with, prefixing it" |
| 402 | + [^java.lang.Process proc prefix] |
| 403 | + (let [out (.getInputStream proc) |
| 404 | + err (.getErrorStream proc) |
| 405 | + newline? (volatile! true) |
| 406 | + ^bytes buffer (make-array Byte/TYPE 1024)] |
| 407 | + (doseq [[^InputStream from ^OutputStream to] [[out System/out] [err System/err]]] |
| 408 | + (future |
| 409 | + (loop [] |
| 410 | + (let [size (.read from buffer)] |
| 411 | + (when (pos? size) |
| 412 | + (dotimes [i size] |
| 413 | + (when @newline? |
| 414 | + (.write to (.getBytes prefix)) |
| 415 | + (vreset! newline? false)) |
| 416 | + (let [b (aget buffer i)] |
| 417 | + (.write to (int b)) |
| 418 | + (when (= (int \newline) b) |
| 419 | + (vreset! newline? true)))))) |
| 420 | + (Thread/sleep 100) |
| 421 | + (recur)))) |
| 422 | + proc)) |
| 423 | + |
| 424 | +(defn run-process [{:keys [cmd prefix working-dir |
| 425 | + background? timeout-ms check-exit-code? env |
| 426 | + color] |
| 427 | + :or {working-dir "." |
| 428 | + check-exit-code? true}}] |
| 429 | + (fn [ctx] |
| 430 | + (let [working-dir (io/file working-dir) |
| 431 | + proc-builder (doto (ProcessBuilder. (map str cmd)) |
| 432 | + (.directory working-dir)) |
| 433 | + _ (.putAll (.environment proc-builder) (or env (:env ctx))) |
| 434 | + color (mod (hash (or prefix (first cmd))) 8) |
| 435 | + prefix (str "\u001b[" (+ 30 color) "m[" (or prefix (first cmd)) "]\u001b[0m ") |
| 436 | + process (pipe-process-output (.start proc-builder) prefix) |
| 437 | + ctx (update ctx :processes (fnil conj []) process)] |
| 438 | + (if background? |
| 439 | + ctx |
| 440 | + (let [exit (if timeout-ms |
| 441 | + (.waitFor process timeout-ms TimeUnit/MILLISECONDS) |
| 442 | + (.waitFor process))] |
| 443 | + (when (and check-exit-code? (not= 0 exit)) |
| 444 | + (do |
| 445 | + (println (str prefix) "Exited with non-zero exit code: " exit) |
| 446 | + (System/exit exit))) |
| 447 | + ctx))))) |
| 448 | + |
| 449 | +(defn start-clojure-process [{:keys [options aliases nrepl-port] :as ctx}] |
| 450 | + (let [args (clojure-cli-args ctx)] |
| 451 | + (apply debug (map shellquote args)) |
| 452 | + ((run-process {:cmd args |
| 453 | + :ctx-process-key :clojure-process |
| 454 | + :background? true}) ctx))) |
| 455 | + |
401 | 456 | (def before-steps [read-deps-edn
|
402 | 457 | handle-cli-args
|
403 | 458 | get-nrepl-port
|
|
414 | 469 | disable-stack-trace-elision
|
415 | 470 | inject-aliases-as-property
|
416 | 471 | include-watcher
|
417 |
| - run-nrepl-server |
418 |
| - print-summary]) |
| 472 | + print-summary |
| 473 | + run-nrepl-server]) |
419 | 474 |
|
420 | 475 | (def after-steps [wait-for-nrepl
|
421 | 476 | ;; stuff that happens after the server is up
|
422 | 477 | maybe-connect-emacs])
|
423 | 478 |
|
424 | 479 | (def default-steps (concat before-steps
|
425 |
| - [start-process] |
| 480 | + [start-clojure-process] |
426 | 481 | after-steps))
|
427 | 482 |
|
| 483 | +(def ^:deprecated start-process start-clojure-process) |
| 484 | + |
428 | 485 | (defn find-project-root []
|
429 | 486 | (loop [dir (.getParent (io/file *file*))]
|
430 | 487 | (if (or (not dir)
|
431 | 488 | (.exists (io/file dir "deps.edn")))
|
432 | 489 | dir
|
433 | 490 | (recur (.getParent (io/file dir))))))
|
434 | 491 |
|
435 |
| -(defn initial-context [{:keys [steps executable project-root] |
| 492 | +(defn initial-context [{:keys [steps executable project-root |
| 493 | + middleware |
| 494 | + java-args |
| 495 | + eval-forms] |
436 | 496 | :or {steps default-steps
|
437 |
| - project-root (find-project-root)}}] |
| 497 | + project-root (find-project-root) |
| 498 | + middleware [] |
| 499 | + java-args [] |
| 500 | + eval-forms []}}] |
438 | 501 | {:main-opts *command-line-args*
|
439 | 502 | :executable (or executable
|
440 | 503 | (str/replace *file*
|
441 | 504 | (str project-root "/")
|
442 | 505 | ""))
|
443 |
| - :project-root project-root}) |
| 506 | + :project-root project-root |
| 507 | + :middleware middleware |
| 508 | + :java-args java-args |
| 509 | + :eval-forms eval-forms}) |
444 | 510 |
|
445 | 511 | (defn process-steps [ctx steps]
|
446 | 512 | (reduce #(%2 %1) ctx steps))
|
447 | 513 |
|
448 | 514 | (defn main
|
449 | 515 | ([{:keys [steps] :or {steps default-steps} :as opts}]
|
450 | 516 | (let [ctx (process-steps (initial-context opts) steps)
|
451 |
| - process (:clojure-process ctx)] |
| 517 | + processes (:processes ctx)] |
452 | 518 | (.addShutdownHook (Runtime/getRuntime)
|
453 |
| - (Thread. (fn [] (.destroy (:proc process))))) |
454 |
| - (System/exit (:exit @process))))) |
| 519 | + (Thread. (fn [] (run! #(.destroy %) processes)))) |
| 520 | + (System/exit (apply min (for [p processes] |
| 521 | + (.waitFor p))))))) |
0 commit comments