Last active
September 23, 2021 12:39
-
-
Save borkdude/c34e8e44eb5b4a6ca735bf8a86ff64fa to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bb | |
;; Ported from https://gist.github.com/pyr/d5e17af9c572b681a57de52895437298 to babashka | |
;; klein aims to be a small joker script to mimick | |
;; most of leiningen's default behavior while minimizing | |
;; divergence from standard facilities provided by | |
;; tools.deps | |
;; This is built as a single file script to simplify | |
;; deployment and will avoid requiring any code beyond | |
;; facilities provided by joker itself | |
;; klein is configured in the same deps.edn file as your | |
;; project and tries to provide sensible defaults | |
;; | |
;; configuration can be provided in at the `:klein/config` | |
;; key | |
;; | |
;; {:paths ["src"] | |
;; :deps {org.clojure/clojure {:mvn/version "1.10.2"} | |
;; | |
;; :klein/config | |
;; {:artifact spootnik/foo | |
;; :version :git | |
;; :description "Trying out klein" | |
;; :main foo.core | |
;; :clean-targets ["pom.xml"]}} | |
(require '[babashka.deps :as deps] | |
'[babashka.fs :as fs] | |
'[babashka.process :as p] | |
'[clojure.java.shell :refer [sh]] | |
'[clojure.pprint :as pp] | |
'[clojure.string :as str] | |
'[clojure.tools.cli :as cli]) | |
;; Global configuration | |
(def version | |
"0.1.0-alpha1") | |
(def argdefs | |
"Argument definitions for klein. `:in-order true` is passed to allow | |
tasks to use `parse-opts` again if need be." | |
[["-h" "--help" "Print this help or help for a specific task" :id :help?]]) | |
(def tasks | |
"List of known top-level tasks." | |
{:clean {:desc "Clean transient files for a project" | |
:need-project? true} | |
:repl {:desc "Start a repl in the current project" | |
:need-project? true} | |
:deps {:desc "Show dependency tree for a project" | |
:need-project? true} | |
:jar {:desc "Build JAR for a project" | |
:need-project? true} | |
:uberjar {:desc "Build standalone JAR for a project" | |
:need-project? true} | |
:test {:desc "Run project test suite" | |
:need-project? true} | |
:run {:desc "Run project main function" | |
:need-project? true} | |
:pprint {:desc "Show runtime data and exit" | |
:need-project? true} | |
:help {:desc "Show usage and exit"} | |
:version {:desc "Show version and exit"}}) | |
(def default-clean-targets ["target"]) | |
(defmulti run-task | |
"This function is called once a command line invocation has been resolved | |
into a valid klein task. | |
All tasks receive a map of the following structure: | |
{:id task-id ;; #{:clean :repl :deps :jar ...} | |
:arguments ;; map of extra arguments given | |
:summary ;; summary of the task | |
:project} ;; The content of the project's deps.edn file" | |
:id) | |
(defn usage! | |
"Show usage and exit" | |
[summary errors] | |
(let [error? (some? errors)] | |
(binding [*out* (if error? *err* *out*)] | |
(when error? | |
(println "errors:") | |
(doseq [e errors] | |
(println " " e)) | |
(println "")) | |
(println "usage: klein [options] task") | |
(println "available tasks:") | |
(doseq [[k v] tasks] | |
(printf " %-10s %s\n" (name k) (:desc v))) | |
(println "global options:") | |
(println summary) | |
(System/exit (if error? 1 0))))) | |
(defn die! | |
"Show error on stderr and exit" | |
[msg] | |
(binding [*out* *err*] | |
(println msg) | |
(System/exit 1))) | |
(defn clojure-task | |
"Invoke clojure with args." | |
[{:keys [main? alias args]}] | |
(let [extra (when (some? alias) {:aliases {:klein alias}})] | |
(some-> (deps/clojure (cond->> args | |
(some? extra) | |
(concat ["-Sdeps" | |
(pr-str extra) | |
(if main? "-M:klein" "-X:klein")]))) | |
p/check) | |
nil)) | |
(defn match-task | |
"Find task by name in the main task map" | |
[tname] | |
(first | |
(for [[k v] tasks :when (= tname (name k)) :let [t (assoc v :id k)]] | |
t))) | |
(defn parent-dir | |
"Yield the parent of the current dir, if any" | |
[path] | |
(fs/parent path)) | |
(defn git-version | |
"Infer project version from git" | |
[] | |
(let [{:keys [exit out]} (apply sh "git" "describe" "--always" | |
"--dirty" "--tags")] | |
(if (or (not (zero? exit)) | |
(str/blank? out)) | |
(die! "cannot infer version from git") | |
(str/trim out)))) | |
(defn update-version | |
"Allow inferring version from git, if the provided | |
version is :git" | |
[{:keys [version] :as project}] | |
(cond | |
(nil? version) (die! "no project version supplied") | |
(= :git version) (assoc project :version (git-version)) | |
:else project)) | |
(defn find-project-in-dir | |
"Try to find a project file in the provided path" | |
[path] | |
(let [filepath (fs/path path "deps.edn")] | |
(when (fs/exists? filepath) | |
(-> filepath | |
fs/file | |
slurp | |
read-string | |
:klein/config | |
update-version | |
(assoc :basedir (str (-> path | |
fs/absolutize | |
fs/normalize))))))) | |
(defn find-project | |
"Walk up the directory hierarchy, looking for a deps.edn file" | |
[] | |
(loop [path (fs/path ".")] | |
(when-not (nil? path) | |
(or (find-project-in-dir path) | |
(recur (parent-dir path)) | |
(die! "no project file found, cannot continue"))))) | |
(defmethod run-task :clean | |
[{:keys [project]}] | |
(let [clean-targets (:clean-targets project) | |
replace-targets? (:replace-targets? project) | |
basedir (:basedir project) | |
targets (cond-> clean-targets (not replace-targets?) | |
(concat default-clean-targets)) | |
dirs (map (partial format "%s/%s" basedir) targets)] | |
(when (seq targets) | |
(doseq [dir dirs] | |
(fs/delete-tree dir))))) | |
(defmethod run-task :repl | |
[_] | |
(clojure-task | |
{:interactive? true})) | |
(defmethod run-task :run | |
[{:keys [project]}] | |
(clojure-task | |
{:args ["-M" "-m" (:main project)]})) | |
(defmethod run-task :jar | |
[{:keys [project]}] | |
(let [jar-name (or (:jar-name project) | |
(format "target/%s-%s.jar" | |
(name (:artifact project)) | |
(:version project)))] | |
(clojure-task | |
{:alias {:replace-deps {'seancorfield/depstar | |
{:mvn/version "2.0.165"}} | |
:ns-default 'hf.depstar} | |
:args ["jar" :jar jar-name]}))) | |
(defmethod run-task :uberjar | |
[{:keys [project]}] | |
(let [jar-name (or (:jar-name project) | |
(format "target/%s-%s-standalone.jar" | |
(name (:artifact project)) | |
(:version project))) | |
version (:version project) | |
group-id (namespace (:artifact project)) | |
artifact-id (name (:artifact project)) | |
main (when-let [class (:main project)] | |
[":main-class" (str class) ":aot" "true" | |
":sync-pom" "true" ":group-id" group-id | |
":artifact-id" artifact-id ":version" (pr-str version)])] | |
(clojure-task | |
{:alias {:replace-deps {'seancorfield/depstar | |
{:mvn/version "2.0.165"}} | |
:ns-default 'hf.depstar} | |
:args (concat ["uberjar" ":jar" jar-name] main)}))) | |
(defmethod run-task :test | |
[{:keys [arguments]}] | |
(clojure-task | |
{:alias {:extra-paths ["test"] | |
:extra-deps {'com.cognitect/test-runner | |
{:git/url "https://github.com/cognitect-labs/test-runner.git" | |
:sha "209b64504cb3bd3b99ecfec7937b358a879f55c1"}} | |
:main-opts ["-m" "cognitect.test-runner"]} | |
:args arguments | |
:main? true})) | |
(defmethod run-task :deps | |
[{:keys [arguments]}] | |
(clojure-task | |
{:args (concat ["-X:deps" "tree"] arguments)})) | |
(defmethod run-task :help | |
[{:keys [summary]}] | |
(usage! summary nil)) | |
(defmethod run-task :version | |
[_] | |
(println version)) | |
(defmethod run-task :pprint | |
[{:keys [project]}] | |
(pp/pprint project)) | |
(let [{:keys [options] :as res} (cli/parse-opts *command-line-args* argdefs | |
:in-order true) | |
arguments (:arguments res) | |
errors (:errors res) | |
summary (:summary res) | |
task (match-task (first arguments))] | |
(when (or (some? errors) (empty? arguments) (nil? task) (:help? options)) | |
(usage! summary errors)) | |
(let [project (find-project)] | |
(when (and (:need-project? task) (nil? project)) | |
(binding [*out* *err*] | |
(println "could not find project root") | |
(System/exit 1))) | |
(run-task (assoc task | |
:arguments (rest arguments) | |
:summary summary | |
:project project)))) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment