diff --git a/.github/workflows/commit_validation.yml b/.github/workflows/commit_validation.yml index 2b50215..9c0b1f7 100644 --- a/.github/workflows/commit_validation.yml +++ b/.github/workflows/commit_validation.yml @@ -16,11 +16,11 @@ jobs: runs-on: ${{ matrix.os }} steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 - name: Install java - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: 'temurin' java-version: '21' @@ -29,9 +29,9 @@ jobs: # See https://github.com/DeLaGuardo/setup-clojure/commits/main/ uses: DeLaGuardo/setup-clojure@3fe9b3ae632c6758d0b7757b0838606ef4287b08 #v13.2 with: - cli: 1.12.1.1550 - bb: 1.12.207 - clj-kondo: 2025.07.28 + cli: 1.12.2.1565 + bb: 1.12.208 + clj-kondo: 2025.09.19 zprint: 1.3.0 - name: Lint run: bb lint -v @@ -51,7 +51,7 @@ jobs: run: git diff - name: Pushed code should already be formatted # See https://github.com/CatChen/check-git-status-action - uses: CatChen/check-git-status-action@7ff019ee29f2307dca95397ae225037aa88eb4c7 #v1.4.4 + uses: CatChen/check-git-status-action@d75bdbea704869e2a3b01111b4a82c49e9bd348d #v1.4.4 with: fail-if-not-clean: true request-changes-if-not-clean: false diff --git a/.github/workflows/deploy_clojar.yml b/.github/workflows/deploy_clojar.yml index 0e0a75d..0e3ae66 100644 --- a/.github/workflows/deploy_clojar.yml +++ b/.github/workflows/deploy_clojar.yml @@ -14,7 +14,7 @@ jobs: steps: # This step checks out a copy of your repository. - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: jlesquembre/clojars-publish-action@89a4eb7bdbe1270621e6643250afce152701699e # v0.3 env: diff --git a/bb.edn b/bb.edn index dc3eb54..323537e 100644 --- a/bb.edn +++ b/bb.edn @@ -2,10 +2,12 @@ :paths ["src"] :tasks {:requires [[auto-build.os.exit-codes] [auto-build.os.exiting :refer [exit]]] - bp {:doc "Before push" + bp {:doc "To fail fast before git pushing a version" + :requires [[auto-build.echo :refer [level1-header]] [auto-build.tasks.deps :as td]] :task (binding [exit (fn [exit-code & _] (when-not (zero? exit-code) exit-code))] (-> (or (run 'format) (run 'lint) + (td/task level1-header ".") (binding [*command-line-args* (conj *command-line-args* "all")] (run 'clj-test)) auto-build.os.exit-codes/ok) @@ -19,7 +21,7 @@ [".cpcache" ".shadow-cljs"] (current-task)) exit)} - clj-test {:doc "Test with clj" + clj-test {:doc "Run clojure test" :requires [[auto-build.tasks.clj-test :as build-clj-test] [auto-build.echo :refer [level1-header]]] :task (-> (build-clj-test/clj-test level1-header @@ -28,31 +30,31 @@ (current-task) ["test-bb" "test-unit"]) exit)} - deploy {:doc "Deploy" + deploy {:doc "Add tags to the main branch to create a new version" :requires [[auto-build.tasks.deploy :as task-deploy] [auto-build.echo :refer [level1-header]]] :task (-> (task-deploy/deploy level1-header "." "main" (current-task)) exit)} - deps {:doc "Update deps" + deps {:doc "Update dependencies (clojure ones)" :task (shell "clj -M:antq")} docs {:doc "Docs" :requires [[auto-build.tasks.docs :as build-docs] [auto-build.echo :refer [level1-header]]] :task (-> (build-docs/docs level1-header "." (current-task) "gh-pages" ":codox:for-clj-repl") exit)} - format {:doc "Format" + format {:doc "Format source code" :requires [[auto-build.tasks.formatting :as task-format] [auto-build.echo :refer [level1-header]]] :task (-> (task-format/format level1-header "." (current-task)) exit)} - gha {:doc "For github action tests" + gha {:doc "To be run by github action" :task (shell "bb clj-test test-unit test-bb -v")} heph-info {:doc "Hephaistox project info" :requires [[auto-build.tasks.heph-info :as heph-info] [auto-build.echo :refer [level1-header]]] :task (-> (heph-info/heph-info level1-header (current-task) ".") exit)} - lint {:doc "Lint" + lint {:doc "Lint source code" :requires [[auto-build.tasks.lint :as task-lint] [auto-build.echo :refer [level1-header]]] :task (-> (task-lint/lint level1-header ["src" "test" "env/unit/test"] "." (current-task)) exit)} diff --git a/deps.edn b/deps.edn index 7afa0ca..1773ad3 100644 --- a/deps.edn +++ b/deps.edn @@ -15,12 +15,12 @@ :exec-fn codox.main/generate-docs :extra-deps {codox/codox {:mvn/version "0.10.8"}}} :for-clj-repl {:doc "Dependencies necessary to use bb in clojure" - :extra-deps {babashka/fs {:mvn/version "0.5.26"} + :extra-deps {babashka/fs {:mvn/version "0.5.27"} babashka/process {:mvn/version "0.6.23"}}} :test-bb {:extra-paths ["test/src" "test/resources"] :main-opts ["-m" "cognitect.test-runner" "-r" ".*-test.*" "-d" "test/src"]} :test-unit {:extra-paths ["env/unit/test/" "env/unit/resources"] :main-opts ["-m" "cognitect.test-runner" "-r" ".*-test.*" "-d" "env/unit/test/"]}} - :deps {nrepl/nrepl {:mvn/version "1.3.1"} + :deps {nrepl/nrepl {:mvn/version "1.4.0"} org.clojure/tools.cli {:mvn/version "1.1.230"}} :paths ["src"]} diff --git a/env/unit/test/auto_build/os/edn_utils_test.clj b/env/unit/test/auto_build/os/edn_utils_test.clj index 776e5db..963878d 100644 --- a/env/unit/test/auto_build/os/edn_utils_test.clj +++ b/env/unit/test/auto_build/os/edn_utils_test.clj @@ -43,12 +43,12 @@ (is (str/includes? (with-out-str (sut/read-edn {:errorln println :exception-msg #(println (ex-message %))} "non-existing-file")) - "No such file or directory)\n") + "Impossible to load file") "For a non existing file") (is (str/includes? (with-out-str (sut/read-edn {:errorln println :exception-msg #(println (ex-message %))} (io/resource "invalid_content.edn"))) - "is not a valid edn.\nMap literal must contain an even number of forms\n") + "is not a valid edn.") "For a content which is not a valid edn") (is (= "" (with-out-str (sut/read-edn {:errorln println @@ -93,10 +93,10 @@ (sut/write-edn {} "foo") with-out-str)) "What a successful file writing is printing if no printer is provided") - (is (= "File nil could not be written\nCannot open as a Writer.\n" - (-> nil - (sut/write-edn {:errorln println - :exception-msg (comp println ex-message)} - "foo") - with-out-str)) + (is (str/includes? (-> nil + (sut/write-edn {:errorln println + :exception-msg (comp println ex-message)} + "foo") + with-out-str) + "File nil could not be written") "What a failing file writing is printing"))) diff --git a/env/unit/test/auto_build/os/file_test.clj b/env/unit/test/auto_build/os/file_test.clj index fde708c..7a2d512 100644 --- a/env/unit/test/auto_build/os/file_test.clj +++ b/env/unit/test/auto_build/os/file_test.clj @@ -149,14 +149,12 @@ (io/resource "valid_content.edn")) with-out-str)) "What a valid file is printing") - (is - (= - "Impossible to load file non-existing-file.edn\nnon-existing-file.edn (No such file or directory)\n" - (-> (sut/read-file {:errorln println - :exception-msg (comp println ex-message)} - "non-existing-file.edn") - with-out-str)) - "What an invalid file is printing"))) + (is (str/includes? (-> (sut/read-file {:errorln println + :exception-msg (comp println ex-message)} + "non-existing-file.edn") + with-out-str) + "Impossible to load file non-existing-file.edn\n") + "What an invalid file is printing"))) (deftest write-file-test (testing "Returned data" @@ -203,12 +201,12 @@ (sut/write-file {} "foo") with-out-str)) "What a successful file writing is printing if no printer is provided") - (is (= "File nil could not be written\nCannot open as a Writer.\n" - (-> nil - (sut/write-file {:errorln println - :exception-msg (comp println ex-message)} - "foo") - with-out-str)) + (is (str/includes? (-> nil + (sut/write-file {:errorln println + :exception-msg (comp println ex-message)} + "foo") + with-out-str) + "File nil could not be written\n") "What a failing file writing is printing"))) ;; ******************************************************************************** diff --git a/src/auto_build/os/edn_utils/impl/reader.clj b/src/auto_build/os/edn_utils/impl/reader.clj index c8fbb8a..ea9aaf4 100644 --- a/src/auto_build/os/edn_utils/impl/reader.clj +++ b/src/auto_build/os/edn_utils/impl/reader.clj @@ -24,6 +24,6 @@ ((if (fn? uri-str) uri-str identity)) build-filename/absolutize) "is not a valid edn.") - (when exception-msg (exception-msg e))) + (when exception-msg (exception-msg (.getMessage e)))) {:exception e :status :edn-failed})))))) diff --git a/src/auto_build/os/file.clj b/src/auto_build/os/file.clj index b75824b..d0e2994 100644 --- a/src/auto_build/os/file.clj +++ b/src/auto_build/os/file.clj @@ -170,7 +170,7 @@ Returns: (when (fn? errorln) (errorln "Impossible to load file" ((if (fn? uri-str) uri-str identity) filepath))) - (when exception-msg (exception-msg e)) + (when exception-msg (exception-msg (.getMessage e))) {:exception e :status :file-loading-fail}))))) @@ -198,7 +198,7 @@ Returns: (catch Exception e (when (fn? errorln) (errorln "File" ((if (fn? uri-str) uri-str identity) filepath) "could not be written")) - (when (fn? exception-msg) (exception-msg e)) + (when (fn? exception-msg) (exception-msg (.getMessage e))) {:exception e :status :file-writing-fail})))) diff --git a/src/auto_build/project/bb.clj b/src/auto_build/project/bb.clj new file mode 100644 index 0000000..d910dfd --- /dev/null +++ b/src/auto_build/project/bb.clj @@ -0,0 +1,34 @@ +(ns auto-build.project.bb + "Utilities for `bb.edn` files." + (:refer-clojure :exclude [read]) + (:require + [auto-build.os.edn-utils :as build-edn-utils] + [auto-build.os.filename :as build-filename])) + +(defn bb-edn-filename + "A `bb.edn` fullname based on the `app-dir` - the directory containing the application" + [app-dir] + (build-filename/create-file-path app-dir "bb.edn")) + +(defn read + "Returns a map with: + * `:filepath` + * `:afilepath` absolute path to the file + * `:raw-content` if file can be read + * `:invalid?` is boolean + * `:exception` if something wrong happened + * `:edn` if the translation is successful" + [printers app-dir] + (->> app-dir + bb-edn-filename + (build-edn-utils/read-edn printers))) + +(comment + (bb-edn-filename ".") + ; + (read {:errorln println + :uri-str #(format "`%s`" %) + :exception-msg #(println "Error: " %)} + ".") + ; +) diff --git a/src/auto_build/project/cfg_mgt.clj b/src/auto_build/project/cfg_mgt.clj new file mode 100644 index 0000000..73f4144 --- /dev/null +++ b/src/auto_build/project/cfg_mgt.clj @@ -0,0 +1,86 @@ +(ns auto-build.project.cfg-mgt + "Configuration management utilities" + (:require + [auto-build.os.cmd :refer [as-string]] + [babashka.fs :as fs] + [clojure.string :as str])) + +(defn is-git-repo? + "Returns `true` if `dir` is a directory containing a git repo" + [dir] + (every? true? ((juxt fs/exists? fs/directory?) (fs/file dir ".git")))) + +(defn git-data + "Returns a map of `git` informations from the repo in `dir`: + - `:git-statuts` + - `:actual-branch` + - `actual-sha`" + [dir] + (let [pstatus (-> ["git" "status" "-s"] + (as-string dir)) + status (:out-stream pstatus)] + (when (and (fs/exists? dir) (fs/directory? dir) (is-git-repo? dir)) + {:git-status status + :app-dir dir + :actual-branch (-> ["git" "rev-parse" "--abbrev-ref" "HEAD"] + (as-string dir) + :out-stream + first) + :actual-sha (-> ["git" "rev-parse" "HEAD"] + (as-string dir) + :out-stream + first)}))) + +(comment + (git-data "auto_build") + (-> ["git" "status" "-s"] + (as-string "landing") + :out-stream) + (println "In dir" "landing" + "These are the status:" (-> ["git" "status" "-s"] + (as-string "landing") + :out-stream)) + ; +) + +(defn local-repos + "List of project directories containing repos. + + A local repo is a subdirectory, that contains a `.git` folder." + [dir] + (->> (fs/list-dir dir) + (filter fs/directory?) + (map #(let [dir (-> % + fs/components + last + str)] + dir)) + (filter is-git-repo?))) + + +(comment + (local-repos ".") + ; +) + +;; ******************************************************************************** +;; +;; ******************************************************************************** + +(defn extract-project + "Extract the project from the project symbol." + [app-symbol] + (->> app-symbol + (re-find #"(.*)/(.*)") + last)) + +(defn project-dir + "Extract the project directory from the project symbol." + [app-name] + (str/replace app-name #"-" "_")) + +(comment + (extract-project "com.github.hephaistox/auto-web") + (project-dir "auto-web") + ;; +) diff --git a/src/auto_build/project/deps.clj b/src/auto_build/project/deps.clj index db65644..6c0013b 100644 --- a/src/auto_build/project/deps.clj +++ b/src/auto_build/project/deps.clj @@ -1,30 +1,130 @@ (ns auto-build.project.deps + "Utilities for `deps.edn` files." (:refer-clojure :exclude [read]) (:require [auto-build.os.edn-utils :as build-edn-utils] [auto-build.os.filename :as build-filename])) -(defn deps-edn-filename [app-dir] (build-filename/create-file-path app-dir "deps.edn")) +(defn deps-edn-filename + "A `deps.edn` fullname based on the `app-dir` - the directory containing the application" + [app-dir] + (build-filename/create-file-path app-dir "deps.edn")) (defn read - "Returns a map with + "Returns a map with: * `:filepath` - * `:afilepath` - * `:raw-content` if file can be read. + * `:afilepath` absolute path to the file + * `:raw-content` if file can be read * `:invalid?` is boolean - * `:exception` if something wrong happened. - * `:edn` if the translation is succesful." + * `:exception` if something wrong happened + * `:edn` if the translation is successful" [printers app-dir] (->> app-dir deps-edn-filename (build-edn-utils/read-edn printers))) +(comment + (deps-edn-filename ".") + ; + (read {:errorln println + :uri-str #(format "`%s`" %) + :exception-msg #(println "Error: " %)} + ".") + ; +) + (defn write-edn - "Returns + "Returns a map with: * `:filepath` as given as a parameter - * `:afilepath` file with absolute path + * `:afilepath` absolute path to the file * `:status` is `:success` or `:fail` * `:raw-content` * `:exception` (only if `:status` is `:fail`)" [printers app-dir content] - (build-edn-utils/write-edn printers (deps-edn-filename app-dir) content)) + (build-edn-utils/write-edn (deps-edn-filename app-dir) printers content)) + +(comment + (write-edn {:errorln println + :uri-str #(format "`%s`" %) + :exception-msg #(println "Error: " %)} + "monorepo" + {:a :b}) + ; +) + +(defn extract-paths-to-deps + "List of dependencies found in the `deps` map, is a list of pair : + * The path to the deps + * The dependency description" + [deps] + (->> (map (fn [[dep-alias {:keys [extra-deps]}]] [[:aliases dep-alias :extra-deps] extra-deps]) + (:aliases deps)) + (concat {[:deps] (:deps deps)}) + vec)) + +(defn flatten-deps + "Returns a map for each dependency. Its paths, dep-alias, dep-desc." + [path-to-deps] + (->> path-to-deps + (mapcat (fn [[path dep-map]] + (for [[dep-alias dep-desc] dep-map] + {:path path + :dep-alias dep-alias + :dep-desc dep-desc}))) + vec)) + +(comment + (-> (read {:errorln println + :uri-str #(format "`%s`" %) + :exception-msg #(println "Error: " %)} + "landing") + :edn + extract-paths-to-deps + flatten-deps) + ;; +) + +(defn dependant-projects + "Map of project symbol associated to their maven reference (`:git/sha`, `:mvn/version` or `:local/root`). + + Gather all dependencies from the main project and all its aliases." + [printers app-dir] + (let [deps (:edn (read printers app-dir)) + aliases (:aliases deps)] + (apply concat + (->> (:deps deps) + (mapv #(update % 1 assoc :deps-path ["deps.edn" :deps]))) + (map #(let [v (second %)] + (->> (:extra-deps v) + (map (fn [x] + (update x + 1 assoc + :deps-path ["deps.edn" :aliases :extra-deps (first %)]))))) + aliases)))) + +(comment + (dependant-projects {:errorln println + :uri-str #(format "`%s`" %) + :exception-msg #(println "Error: " %)} + "landing") + ;; +) + +(defn update-deps-edn + "Assign `value` at path `path` for the app in `app-dir`" + [printers app-dir path value] + (let [deps-filedesc (read printers app-dir) + old-content (:edn deps-filedesc) + content (assoc-in old-content path value)] + (when-not (and (= :success (:status deps-filedesc)) (= old-content content)) + (write-edn printers app-dir content)))) + +(comment + (update-deps-edn {:errorln println + :uri-str #(format "`%s`" %) + :exception-msg #(println "Error: " %)} + "landing" + [:deps 'com.github.hephaistox/auto-web] + {:local/root "../auto_web"}) + ; +) diff --git a/src/auto_build/tasks/copy.clj b/src/auto_build/tasks/copy.clj new file mode 100644 index 0000000..5629555 --- /dev/null +++ b/src/auto_build/tasks/copy.clj @@ -0,0 +1,65 @@ +(ns auto-build.tasks.copy + "Copy files from a reference. + + Assume the repository is copied locally, at the right expected commit, under `..` directory." + (:require + [auto-build.os.cmd :as build-cmd + :refer [muted]] + [babashka.fs :as fs] + [clojure.edn :as edn] + [clojure.string :as str])) + +(defn extract-project + "Extract the project from the project symbol." + [app-symbol] + (->> app-symbol + str + (re-find #"(.*)/(.*)") + last)) + +(comment + (extract-project 'com.github.hephaistox/auto-build) + ; +) + +(defn project-dir + "Turn project name to project directory name." + [app-name] + (str/replace app-name #"-" "_")) + +(comment + (project-dir "auto-build") + ; +) + +(defn run + "Task copying files as defined in `copy-filename`." + [{:keys [title normalln errorln uri-str subtitle] + :as _printers} + copy-filename] + (when-let [raw-content + (try (slurp copy-filename) + (catch Exception _ (errorln "File" (uri-str copy-filename) "not found") nil))] + (when-let [content (try (edn/read-string raw-content) + (catch Exception e + (errorln "File" (uri-str copy-filename) "is not a valid edn") + (normalln e) + nil))] + (title "Read" (uri-str copy-filename)) + (doseq [[alias {:keys [files]}] content] + (let [app-dir (str "../" + (-> alias + str + extract-project + project-dir))] + (subtitle "Copy from alias" alias "in dir" (uri-str app-dir)) + (doseq [file files] + (let [src-filename (str app-dir "/" file) + dst-dir (str (fs/parent file)) + dst-dir (if (= "" dst-dir) "." dst-dir)] + (if (= :success + (-> (muted ["cp" "-fr" src-filename dst-dir] ".") + :status)) + (normalln "Copy file" (uri-str src-filename) "into" (uri-str dst-dir)) + (errorln "Error when copying file" (uri-str src-filename) + "into" (uri-str dst-dir)))))))))) diff --git a/src/auto_build/tasks/deps.clj b/src/auto_build/tasks/deps.clj new file mode 100644 index 0000000..2bdbd08 --- /dev/null +++ b/src/auto_build/tasks/deps.clj @@ -0,0 +1,77 @@ +(ns auto-build.tasks.deps + (:require + [auto-build.os.exit-codes] + [auto-build.project.bb :as pb] + [auto-build.project.deps :as pd])) + +(defn read-bb + [printers app-dir] + (-> (pb/read printers app-dir) + :edn)) + +(defn read-deps + [printers app-dir] + (-> (pd/read printers app-dir) + :edn)) + +(defn local-deps + "Return local dependencies" + [deps] + (-> (filter #(= :local/root + (-> % + second + ffirst)) + deps) + keys)) + +(defn extract-deps-to-check + [printers app-dir] + (let [bb-content (read-bb printers app-dir) + deps-content (read-deps printers app-dir)] + (concat [{:path ["bb.edn"] + :deps (:deps bb-content)} + {:path ["deps.edn"] + :deps (:deps deps-content)}] + (keep (fn [[alias alias-deps]] + (when (:deps alias-deps) + {:path ["deps.edn" :aliases alias :deps] + :deps (:deps alias-deps)})) + (:aliases deps-content)) + (keep (fn [[alias alias-deps]] + (when (:extra-deps alias-deps) + {:path ["deps.edn" :aliases alias :extra-deps] + :deps (:extra-deps alias-deps)})) + (:aliases deps-content))))) + +(comment + (extract-deps-to-check {:errorln println + :uri-str #(format "`%s`" %) + :exception-msg #(println "Error: " %)} + ".") + ; +) + +(defn task + [{:keys [errorln subtitle uri-str] + :as printers} + app-dir] + (first (keep (fn [{:keys [path deps]}] + (if-let [local-dep-found (seq (local-deps deps))] + (do (subtitle "Local deps are found in" path) + (doseq [local-dep local-dep-found] + (errorln (uri-str local-dep) "refers to a local repo")) + auto-build.os.exit-codes/general-errors) + (do (subtitle "Deps refers to remote repo in" path) nil))) + (extract-deps-to-check printers app-dir)))) + +(comment + (read-bb {:errorln println + :uri-str #(format "`%s`" %) + :exception-msg #(println "Error: " %)} + ".") + (read-deps {:errorln println + :uri-str #(format "`%s`" %) + :exception-msg #(println "Error: " %)} + ".") + ; +) diff --git a/test/auto_build/tasks/deps_test.clj b/test/auto_build/tasks/deps_test.clj new file mode 100644 index 0000000..565761d --- /dev/null +++ b/test/auto_build/tasks/deps_test.clj @@ -0,0 +1,12 @@ +(ns auto-build.tasks.deps-test + (:require + [auto-build.tasks.deps :as sut] + [clojure.test :refer [deftest is]])) + +(deftest local-deps-test + (is (empty? (sut/local-deps {'nrepl/nrepl {:mvn/version "1.3.1"} + 'org.clojure/tools.cli {:mvn/version "1.1.230"}})) + "No local root") + (is (sut/local-deps {'nrepl/nrepl {:local/root "../aa"} + 'org.clojure/tools.cli {:mvn/version "1.1.230"}}) + "A local root is detected, symbol is returned"))