diff --git a/.cljfmt.edn b/.cljfmt.edn new file mode 100644 index 0000000..d88d142 --- /dev/null +++ b/.cljfmt.edn @@ -0,0 +1,2 @@ +{:remove-multiple-non-indenting-spaces? true + :sort-ns-references? true} diff --git a/.github/workflows/release-cli.yml b/.github/workflows/release-cli.yml new file mode 100644 index 0000000..0ec26f6 --- /dev/null +++ b/.github/workflows/release-cli.yml @@ -0,0 +1,92 @@ +name: Release CLI + +on: + push: + pull_request: + workflow_dispatch: + +jobs: + build-cli: + strategy: + matrix: + include: + - arch: linux-amd64 + runs-on: ubuntu-latest + - arch: linux-arm64 + runs-on: arm-c8 + - arch: macos-amd64 + runs-on: macos-latest + - arch: macos-arm64 + runs-on: macos-latest-xlarge + + runs-on: ${{ matrix.runs-on }} + steps: + - name: Checkout git repo + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Fetch Tags + run: git fetch --tags origin + + - name: Setup Java + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 21 + cache: 'maven' + cache-dependency-path: '**/deps.edn' + + - uses: graalvm/setup-graalvm@v1 + with: + java-version: '21' + distribution: 'graalvm' + github-token: ${{ secrets.GITHUB_TOKEN }} + + - uses: extractions/setup-just@v2 + - uses: DeLaGuardo/setup-clojure@12.5 + with: + cli: latest + + - name: Build + run: | + just build + just build-cli + + - name: Pack binary + run: | + tar -czf kmono-${{ matrix.arch }}.tar.gz -C ./target/bin kmono + + - uses: actions/upload-artifact@v3 + with: + name: binaries + if-no-files-found: error + path: kmono-${{ matrix.arch }}.tar.gz + + release-cli: + runs-on: ubuntu-latest + needs: [build-cli] + if: ${{ github.ref_type == 'tag' && startsWith(github.ref_name, 'v') }} + steps: + - uses: actions/checkout@v4 + + - name: Download Binary Artifacts + uses: actions/download-artifact@v3 + with: + name: binaries + path: bin + + - name: Calculate checksums + run: | + for file in bin/*; do + shasum -a 256 "$file" >> checksums.txt + done + + mv checksums.txt bin/checksums.txt + + - name: Release + uses: softprops/action-gh-release@v2 + if: startsWith(github.ref, 'refs/tags/') + with: + files: | + bin/* diff --git a/.github/workflows/release-libs.yml b/.github/workflows/release-libs.yml new file mode 100644 index 0000000..8c417cc --- /dev/null +++ b/.github/workflows/release-libs.yml @@ -0,0 +1,38 @@ +name: Build and Release Libs + +on: + push: + pull_request: + workflow_dispatch: + +jobs: + build-and-release-libs: + runs-on: ubuntu-latest + steps: + - name: Checkout git repo + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Fetch Tags + run: git fetch --tags origin + + - name: Setup Java + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 21 + cache: 'maven' + cache-dependency-path: '**/deps.edn' + + - uses: extractions/setup-just@v2 + - uses: DeLaGuardo/setup-clojure@12.5 + with: + cli: latest + + - name: Build + run: just build + + - name: Release + if: ${{ github.ref_type == 'tag' && startsWith(github.ref_name, 'v') }} + run: just release diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index f692a99..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,62 +0,0 @@ -name: Publish kmono 👘 -on: [push] - -jobs: - build-and-publish: - runs-on: ubuntu-latest - - steps: - - name: Checkout git repo - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - - name: Cache clojure dependencies - uses: actions/cache@v3 - with: - path: | - ~/.m2/repository - ~/.gitlibs - ~/.deps.clj - key: cljdeps-${{ hashFiles('deps.edn') }} - restore-keys: cljdeps- - - - uses: actions/setup-java@v2 - with: - distribution: 'temurin' - java-version: '17' - - - uses: extractions/setup-just@v1 - - - uses: DeLaGuardo/setup-clojure@10.1 - with: - cli: latest - - - uses: s4u/maven-settings-action@v2.4.1 - with: - githubServer: false - servers: | - [{"id": "github-kepler", - "username": "${{ secrets.ORG_GITHUB_ACTOR }}", - "password": "${{ secrets.ORG_GITHUB_TOKEN }}"}, - {"id": "github-kepler-kmono", - "username": "${{ github.actor }}", - "password": "${{ secrets.GITHUB_TOKEN }}"}] - - - name: Test - run: | - just test - - - name: Build - run: | - just build ":snapshot?" ${{ github.ref_name != 'master' }} - - - name: Release - env: - CLOJARS_USERNAME: infrastructure-kepler16-com - CLOJARS_PASSWORD: ${{ secrets.CLOJARS_PASSWORD }} - run: | - just release \ - ":snapshot?" ${{ github.ref_name != 'master' }} \ - ":create-tags?" ${{ github.ref_name == 'master' }} - diff --git a/.github/workflows/release_binary.yml b/.github/workflows/release_binary.yml deleted file mode 100644 index 212b455..0000000 --- a/.github/workflows/release_binary.yml +++ /dev/null @@ -1,121 +0,0 @@ -name: Release Binary 👘 - -on: - workflow_run: - workflows: ["Publish kmono 👘"] - types: - - completed - branches: - - master - -jobs: - build: - if: ${{ github.event.workflow_run.conclusion == 'success' }} - - outputs: - release_exists: ${{ steps.check-release-exists.outputs.release_exists }} - release_tag: ${{ steps.release-tag.outputs.release_tag }} - - strategy: - matrix: - os: [ubuntu-latest, macos-latest, macos-latest-xlarge] - include: - - os: ubuntu-latest - binary-name: kmono-linux-amd64 - - os: macos-latest - binary-name: kmono-macos-amd64 - - os: macos-latest-xlarge - binary-name: kmono-macos-arm64 - - env: - GITHUB_USERNAME: ${{ secrets.ORG_GITHUB_ACTOR }} - GITHUB_TOKEN: ${{ secrets.ORG_GITHUB_TOKEN }} - - runs-on: ${{ matrix.os }} - steps: - - name: Checkout git repo - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - - name: Fetch Tags - run: git fetch --tags origin - - - name: Set output release tag - id: release-tag - run: echo "release_tag=$(git tag -l 'com.kepler16/kmono@*' | sort -V | tail -n 1)" >> $GITHUB_OUTPUT - - - name: Check gh release exists - id: check-release-exists - run: | - if gh release view ${{ steps.release-tag.outputs.release_tag }} --json tagName; then - echo "release_exists=true" >> $GITHUB_OUTPUT - exit 0 - else - echo "release_exists=false" >> $GITHUB_OUTPUT - fi - - - uses: extractions/setup-just@v1 - - uses: DeLaGuardo/setup-clojure@9.5 - with: - cli: latest - - - uses: graalvm/setup-graalvm@v1 - with: - java-version: '21' - distribution: 'graalvm' - github-token: ${{ secrets.GITHUB_TOKEN }} - - - uses: s4u/maven-settings-action@v2.4.1 - with: - githubServer: false - servers: | - [{"id": "github-kepler", - "username": "${{ secrets.ORG_GITHUB_ACTOR }}", - "password": "${{ secrets.ORG_GITHUB_TOKEN }}"}, - {"id": "github-kepler-kmono", - "username": "${{ github.actor }}", - "password": "${{ secrets.GITHUB_TOKEN }}"}] - - - name: Build Native Image - run: just build-native ":snapshot?" false - - - name: Pack binary - run: | - tar -czf ${{ matrix.binary-name }}.tar.gz -C ./bin kmono - - - uses: actions/upload-artifact@v3 - with: - name: kmono-native-images - if-no-files-found: error - path: ${{ matrix.binary-name }}.tar.gz - - release: - runs-on: ubuntu-latest - needs: build - if: needs.build.outputs.release_exists == 'false' - env: - GITHUB_USERNAME: ${{ secrets.ORG_GITHUB_ACTOR }} - GITHUB_TOKEN: ${{ secrets.ORG_GITHUB_TOKEN }} - steps: - - uses: actions/checkout@v3 - - - name: Download Binary Artifacts - uses: actions/download-artifact@v2 - with: - name: kmono-native-images - path: bin - - - name: Calculate checksums - run: | - for file in bin/*; do - shasum -a 256 "$file" >> checksums.txt - done - - mv checksums.txt bin/checksums.txt - - - name: GH Release - run: | - gh release create ${{ needs.build.outputs.release_tag }} bin/* \ - --title "${{ needs.build.outputs.release_tag }}" - diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..4d32f0e --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,32 @@ +name: Test +on: [push] + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout git repo + uses: actions/checkout@v4 + + - name: Setup Java + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 21 + cache: 'maven' + cache-dependency-path: '**/deps.edn' + + - uses: extractions/setup-just@v2 + - uses: DeLaGuardo/setup-clojure@12.5 + with: + cli: latest + + - name: Setup Git + run: | + git config --global user.email "ci@kepler16.com" + git config --global user.name "Kmono CI" + + - name: Test + run: | + just cli run --ordered false -M ':*/test' diff --git a/.gitignore b/.gitignore index 793a679..588fa30 100644 --- a/.gitignore +++ b/.gitignore @@ -1,32 +1,13 @@ -# resources/ccloud.properties -.cache -/.clj-kondo -!/.clj-kondo/config.edn -.cpcache/ -.kbuild/ -.metabuild/ -.nrepl-port -/telepresence.log -/classes/ -target/ -# /resources/zeus-service-account.json -/.log/ -node_modules -config-override.edn -*.log.[0-9][0-9][0-9] -*.log -/resources/auth/ -/.idea/ -.tool-versions -*.iml -/.calva/ -/.DS_Store/ -/logs/ -/data/ -/deps.local.edn -/local +.idea +.vscode +.DS_Store + +.cpcache .lsp -.portal +.clj-kondo +.nrepl-port +.test-repos +deps.local.edn -!.github -bin +local +target diff --git a/.mise.toml b/.mise.toml deleted file mode 100644 index 91526be..0000000 --- a/.mise.toml +++ /dev/null @@ -1,2 +0,0 @@ -[tools] -java = "oracle-graalvm-21.0.2" diff --git a/build.clj b/build.clj new file mode 100644 index 0000000..5307357 --- /dev/null +++ b/build.clj @@ -0,0 +1,82 @@ +(ns build + (:require + [clojure.tools.build.api :as b] + [deps-deploy.deps-deploy :as deps-deploy] + [k16.kmono.build :as kmono.build] + [k16.kmono.core.config :as core.config] + [k16.kmono.core.fs :as core.fs] + [k16.kmono.core.packages :as core.packages] + [k16.kmono.git.tags :as git.tags])) + +(defn- load-packages [] + (let [project-root (core.fs/find-project-root) + workspace-config (core.config/resolve-workspace-config project-root) + + version (->> (git.tags/get-sorted-tags project-root) + (map (fn [tag] + (second (re-matches #"v(.*)" tag)))) + (remove nil?) + last) + + version (or version "0.0.0")] + + (->> (core.packages/resolve-packages project-root workspace-config) + (reduce + (fn [packages [pkg-name pkg]] + (assoc packages pkg-name (assoc pkg :version version))) + {})))) + +(defn build [_] + (b/delete {:path "target"}) + + (let [packages (load-packages)] + (kmono.build/exec + "Building" packages + (fn [pkg] + (let [pkg-name (:fqn pkg) + {:keys [basis paths]} + (kmono.build/create-build-context packages pkg) + + class-dir (str "target/" (kmono.build/relativize pkg "classes")) + jar-file (str "target/" (kmono.build/relativize pkg "lib.jar"))] + + (b/copy-dir {:src-dirs paths + :target-dir class-dir}) + + (b/write-pom {:class-dir class-dir + :lib pkg-name + :version (:version pkg) + :basis basis}) + + (b/jar {:class-dir class-dir + :jar-file jar-file}) + + (when-let [build (get-in pkg [:deps-edn :kmono/build])] + (let [basis (b/create-basis + {:dir (:relative-path pkg) + :aliases (:aliases build)})] + (b/compile-clj + {:basis basis + :compile-opts {:direct-linking true} + :ns-compile [(:main build)] + :class-dir class-dir}) + + (b/uber + {:class-dir class-dir + :uber-file (str "target/" (kmono.build/relativize pkg (:file build))) + :basis basis + :main (:main build)})))))))) + +(defn release [_] + (b/delete {:path "target"}) + + (let [packages (load-packages)] + (when release + (kmono.build/exec + "Releasing" packages + (fn [pkg] + (deps-deploy/deploy + {:installer :remote + :artifact (b/resolve-path (str "target/" (kmono.build/relativize pkg "lib.jar"))) + :pom-file (b/pom-path {:lib (:fqn pkg) + :class-dir (str "target/" (kmono.build/relativize pkg "classes"))})})))))) diff --git a/build/build.clj b/build/build.clj deleted file mode 100644 index d39bf32..0000000 --- a/build/build.clj +++ /dev/null @@ -1,63 +0,0 @@ -(ns build - (:require - [clojure.edn :as edn] - [clojure.java.io :as io] - [clojure.tools.build.api :as b] - [deps-deploy.deps-deploy :as deps-deploy])) - -(def basis (delay (b/create-basis {}))) - -(def kmono-config - (-> (io/file "deps.edn") - slurp - edn/read-string - :kmono/package)) - -(def lib (symbol (or (System/getenv "KMONO_PKG_NAME") - (str (:group kmono-config) "/" (:artifact kmono-config))))) -(def version (or (System/getenv "KMONO_PKG_VERSION") "0.0.0")) -(def class-dir "target/classes") -(def jar-file "target/lib.jar") - -(defn clean [_] - (b/delete {:path "target"})) - -(defn build [opts] - (clean opts) - (b/write-pom {:class-dir class-dir - :lib lib - :version version - :basis (b/create-basis) - :src-dirs ["src"] - :pom-data [[:description "Clojure monorepo tools"] - [:url "https://github.com/kepler16/kmono"] - [:licenses - [:license - [:name "MIT"] - [:url "https://opensource.org/license/mit"]]]]}) - - (b/copy-dir {:src-dirs ["src"] - :target-dir class-dir}) - - (b/jar {:class-dir class-dir - :jar-file jar-file})) - -(defn uber-for-native [_] - (let [basis (b/create-basis {:aliases #{:native}})] - (clean nil) - (b/copy-dir {:src-dirs ["src"] - :target-dir class-dir}) - (b/compile-clj {:basis basis - :compile-opts {:direct-linking true} - :ns-compile '[k16.kmono.main] - :class-dir class-dir}) - (b/uber {:class-dir class-dir - :uber-file "target/kmono-uber.jar" - :basis basis - :main 'k16.kmono.main}))) - -(defn release [_] - (deps-deploy/deploy {:installer :remote - :artifact (b/resolve-path jar-file) - :pom-file (b/pom-path {:lib lib - :class-dir class-dir})})) diff --git a/build/deps.edn b/build/deps.edn deleted file mode 100644 index b77496b..0000000 --- a/build/deps.edn +++ /dev/null @@ -1,3 +0,0 @@ -{:paths ["./"] - :deps {io.github.clojure/tools.build {:mvn/version "0.9.6"} - slipset/deps-deploy {:mvn/version "0.2.2"}}} diff --git a/deps.edn b/deps.edn index 022ab5d..8bad1f0 100644 --- a/deps.edn +++ b/deps.edn @@ -1,22 +1,11 @@ -{:kmono/package {:group com.kepler16 - :artifact kmono - :build-cmd "clojure -T:build build" - :release-cmd "clojure -T:build release"} - :paths ["src" "resources"] - :deps {babashka/process {:mvn/version "0.5.22"} - babashka/fs {:mvn/version "0.5.21"} - org.slf4j/slf4j-api {:mvn/version "1.7.36"} - org.slf4j/slf4j-simple {:mvn/version "1.7.36"} - org.clojure/tools.deps {:mvn/version "0.19.1432"} - metosin/malli {:mvn/version "0.16.1"} - org.flatland/ordered {:mvn/version "1.15.12"} - org.clojure/tools.cli {:mvn/version "1.1.230"}} +{:kmono/workspace {:group com.kepler16} - :aliases {:build {:deps {local/build {:local/root "./build"}} - :ns-default build} - :native {:extra-deps {com.github.clj-easy/graal-build-time {:mvn/version "1.0.5"}}} - :test {:extra-paths ["test"] - :extra-deps {lambdaisland/kaocha {:mvn/version "1.91.1392"}}} - :kmono {:deps {com.kepler16/kmono {:local/root "."}} - :main-opts [-m k16.kmono.main] - :ns-default k16.kmono.api}}} + :aliases {:build {:deps {io.github.clojure/tools.build {:mvn/version "0.10.5"} + slipset/deps-deploy {:mvn/version "0.2.2"} + + com.keperl16/kmono-core {:local/root "packages/kmono-core"} + com.keperl16/kmono-version {:local/root "packages/kmono-version"} + com.keperl16/kmono-build {:local/root "packages/kmono-build"} + com.keperl16/kmono-git {:local/root "packages/kmono-git"}} + :ns-default build + :extra-paths ["./build.clj"]}}} diff --git a/examples/build-2.clj b/examples/build-2.clj new file mode 100644 index 0000000..f86e676 --- /dev/null +++ b/examples/build-2.clj @@ -0,0 +1,95 @@ +(ns build + (:require + [clojure.tools.build.api :as b] + [deps-deploy.deps-deploy :as deps-deploy] + [k16.kmono.build :as kmono.build] + [k16.kmono.core.config :as core.config] + [k16.kmono.core.fs :as core.fs] + [k16.kmono.core.graph :as core.graph] + [k16.kmono.core.packages :as core.packages] + [k16.kmono.git.commit :as git.commit] + [k16.kmono.git.tags :as git.tags] + [k16.kmono.version :as kmono.version] + [k16.kmono.version.alg.semantic :as semantic-commits])) + +(defn build [{:keys [snapshot release]}] + (b/delete {:path "target"}) + + (let [project-root (core.fs/find-project-root) + workspace-config (core.config/resolve-workspace-config project-root) + + sha (git.commit/get-current-commit-short project-root) + + packages + (->> (core.packages/resolve-packages project-root workspace-config) + (core.graph/filter-by #(not (get-in % [:deps-edn :kmono/private]))) + (kmono.version/resolve-package-versions project-root) + (kmono.version/resolve-package-changes project-root)) + + suffix (when snapshot (str sha "-SNAPSHOT")) + + versioned-packages + (kmono.version/inc-package-versions + semantic-commits/version-type + suffix + packages) + + changed-packages + (->> versioned-packages + (core.graph/filter-by #(seq (:commits %))) + (core.graph/filter-by kmono.build/not-published?))] + + (kmono.build/exec + "Building" changed-packages + (fn [pkg] + (let [pkg-name (:fqn pkg) + {:keys [basis paths]} + (kmono.build/create-build-context + versioned-packages pkg) + + class-dir (str "target/" (kmono.build/relativize pkg "classes")) + jar-file (str "target/" (kmono.build/relativize pkg "lib.jar"))] + + (b/copy-dir {:src-dirs paths + :target-dir class-dir}) + + (b/write-pom {:class-dir class-dir + :lib pkg-name + :version (:version pkg) + :basis basis}) + + (b/jar {:class-dir class-dir + :jar-file jar-file}) + + (when-let [build (get-in pkg [:deps-edn :kmono/build])] + (let [basis (b/create-basis + {:dir (:relative-path pkg) + :aliases (:aliases build)})] + (b/compile-clj + {:basis basis + :compile-opts {:direct-linking true} + :ns-compile [(:main build)] + :class-dir class-dir}) + + (b/uber + {:class-dir class-dir + :uber-file (str "target/" (kmono.build/relativize pkg (:file build))) + :basis basis + :main (:main build)})))))) + + (when release + (kmono.build/exec + "Releasing" changed-packages + (fn [pkg] + (deps-deploy/deploy + {:installer :remote + :artifact (b/resolve-path (str "target/" (kmono.build/relativize pkg "lib.jar"))) + :pom-file (b/pom-path {:lib (:fqn pkg) + :class-dir (str "target/" (kmono.build/relativize pkg "classes"))})}))) + + (git.tags/create-tags + project-root + (map + (fn [pkg] + (str (:fqn pkg) "@" (:version pkg))) + changed-packages))))) diff --git a/examples/build.clj b/examples/build.clj new file mode 100644 index 0000000..8acd5c8 --- /dev/null +++ b/examples/build.clj @@ -0,0 +1,134 @@ +(ns build + (:require + [clojure.tools.build.api :as b] + [deps-deploy.deps-deploy :as deps-deploy] + [k16.kmono.build :as kmono.build] + [k16.kmono.core.config :as core.config] + [k16.kmono.core.fs :as core.fs] + [k16.kmono.core.graph :as core.graph] + [k16.kmono.core.packages :as core.packages] + [k16.kmono.git.commit :as git.commit] + [k16.kmono.version :as kmono.version] + [k16.kmono.version.alg.semantic :as version.semantic-commits] + [k16.kmono.git.tags :as git.tags])) + +(defn build-unreleased [_] + (b/delete {:path "target"}) + + (let [project-root (core.fs/find-project-root) + workspace-config (core.config/resolve-workspace-config project-root) + + packages + (->> (core.packages/resolve-packages project-root workspace-config) + (kmono.version/with-package-versions project-root)) + + changed-packages + (core.graph/filter-by kmono.build/package-version-unpublished? packages)] + + (kmono.build/with-each-package changed-packages + (fn [pkg] + (let [pkg-name (:fqn pkg) + {:keys [basis paths]} + (kmono.build/create-build-context packages pkg) + + class-dir (str "target/" (kmono.build/relativize pkg "classes")) + jar-file (str "target/" (kmono.build/relativize pkg "lib.jar"))] + + (b/copy-dir {:src-dirs paths + :target-dir class-dir}) + + (b/write-pom + {:class-dir class-dir + :lib pkg-name + :version (:version pkg) + :basis basis}) + + (b/jar {:class-dir class-dir + :jar-file jar-file})))))) + +(defn build-normal [{:keys [snapshot]}] + (b/delete {:path "target"}) + + (let [project-root (core.fs/find-project-root) + workspace-config (core.config/resolve-workspace-config project-root) + + packages + (time + (->> (core.packages/resolve-packages project-root workspace-config) + (kmono.version/with-package-versions project-root) + (kmono.version/with-package-changes project-root) + (kmono.version/calculate-new-versions version.semantic-commits/semantic-commits))) + + changed-packages + (time + (->> packages + (core.graph/filter-by (fn [package] (seq (:commits package)))) + (core.graph/filter-by kmono.build/package-version-unpublished?)))] + + (kmono.build/with-each-package changed-packages + (fn [pkg] + (let [pkg-name (:fqn pkg) + {:keys [basis paths]} + (kmono.build/create-build-context packages pkg) + + class-dir (str "target/" (kmono.build/relativize pkg "classes")) + jar-file (str "target/" (kmono.build/relativize pkg "lib.jar"))] + + (b/copy-dir {:src-dirs paths + :target-dir class-dir}) + + (b/write-pom + {:class-dir class-dir + :lib pkg-name + :version (:version pkg) + :basis basis}) + + (b/jar {:class-dir class-dir + :jar-file jar-file})))) + + (git.tags/create-tags project-root []) + )) + +(defn build-snapshot [_] + (b/delete {:path "target"}) + + (let [project-root (core.fs/find-project-root) + workspace-config (core.config/resolve-workspace-config project-root) + sha (subs (git.commit/get-current-commit project-root) 0 7) + + packages + (->> (core.packages/resolve-packages project-root workspace-config) + (kmono.version/with-package-versions project-root) + (kmono.version/with-package-changes project-root) + (kmono.version/calculate-new-versions (fn [_pkg] :patch) (str sha "-SNAPSHOT"))) + + changed-packages + (->> packages + (core.graph/filter-by (fn [package] (seq (:commits package)))) + (core.graph/filter-by kmono.build/package-version-unpublished?))] + + (kmono.build/with-each-package changed-packages + (fn [pkg] + (let [pkg-name (:fqn pkg) + {:keys [basis paths]} + (kmono.build/create-build-context packages pkg) + + class-dir (str "target/" (kmono.build/relativize pkg "classes")) + jar-file (str "target/" (kmono.build/relativize pkg "lib.jar"))] + + (b/copy-dir {:src-dirs paths + :target-dir class-dir}) + + (b/write-pom + {:class-dir class-dir + :lib pkg-name + :version (:version pkg) + :basis basis}) + + (b/jar {:class-dir class-dir + :jar-file jar-file})))))) + + + +(comment + (build-snapshot nil)) diff --git a/justfile b/justfile index c087638..f41ce88 100644 --- a/justfile +++ b/justfile @@ -1,38 +1,18 @@ default: @just --choose -clean: - clojure -T:build clean +build *args: + clojure -T:build build {{args}} -build-uber-native *ARGS: clean - clojure -T:kmono run :exec '"clojure -T:build uber-for-native"' {{ ARGS }} +build-cli *args: + mkdir -p target/bin/ + $GRAALVM_HOME/bin/native-image -jar target/packages/kmono-cli/cli.jar target/bin/kmono -native-image: - $GRAALVM_HOME/bin/native-image \ - -jar target/kmono-uber.jar \ - --no-fallback \ - --features=clj_easy.graal_build_time.InitClojureClasses \ - --report-unsupported-elements-at-runtime \ - --install-exit-handlers \ - -o target/kmono \ - -H:+UnlockExperimentalVMOptions \ - -H:+ReportExceptionStackTraces \ - --initialize-at-build-time=org.eclipse.aether.transport.http.HttpTransporterFactory - -build-native *ARGS: - just build-uber-native {{ ARGS }} && \ - just native-image && \ - rm -rf ./bin && mkdir bin && \ - cp target/kmono ./bin/kmono - -build *ARGS: - clojure -T:kmono run :exec :build {{ ARGS }} - -release *ARGS: - clojure -T:kmono run :exec :release {{ ARGS }} +release *args: + clojure -T:build release {{args}} test: - clojure -M:test -m "kaocha.runner" + kmono run --ordered false -M ':*/test' -repl *ARGS: - clojure -M:kmono:test{{ ARGS }} repl +cli *args: + cd packages/kmono-cli && clojure -M -m k16.kmono.cli.main {{args}} diff --git a/packages/kmono-build/deps.edn b/packages/kmono-build/deps.edn new file mode 100644 index 0000000..53f6e06 --- /dev/null +++ b/packages/kmono-build/deps.edn @@ -0,0 +1,8 @@ +{:kmono/package {} + + :deps {io.github.clojure/tools.build {:mvn/version "0.10.5"} + org.clojure/tools.deps {:mvn/version "0.20.1440"} + babashka/fs {:mvn/version "0.5.22"} + + com.kepler16/kmono-core {:local/root "../kmono-core"} + com.kepler16/kmono-log {:local/root "../kmono-log"}}} diff --git a/packages/kmono-build/src/k16/kmono/build.clj b/packages/kmono-build/src/k16/kmono/build.clj new file mode 100644 index 0000000..5934262 --- /dev/null +++ b/packages/kmono-build/src/k16/kmono/build.clj @@ -0,0 +1,92 @@ +(ns k16.kmono.build + (:require + [babashka.fs :as fs] + [clojure.tools.build.api :as b] + [clojure.tools.deps.extensions :as deps.ext] + [clojure.tools.deps.extensions.maven] + [clojure.tools.deps.util.maven :as deps.util.maven] + [clojure.tools.deps.util.session :as deps.util.session] + [k16.kmono.core.graph :as core.graph] + [k16.kmono.log :as log] + [k16.kmono.log.render :as log.render]) + (:import + java.util.concurrent.Semaphore)) + +(defn published? [package] + (let [local-repo-override (str (fs/create-temp-dir))] + (try + (deps.util.session/with-session + (let [versions (->> (deps.ext/find-versions + (:fqn package) + nil + :mvn {:mvn/local-repo local-repo-override + :mvn/repos + (merge deps.util.maven/standard-repos + (get-in package [:deps-edn :mvn/repos]))}) + (map :mvn/version) + (set))] + (contains? versions (:version package)))) + (finally + (fs/delete-tree local-repo-override))))) + +(defn not-published? [package] + (not (published? package))) + +(defn filter-by-published [packages] + (core.graph/filter-by published? packages)) + +(defn relativize [package path] + (str (fs/file (:relative-path package) path))) + +(defn create-build-context [versioned-packages package] + (let [dependencies + (into + {} + (map (fn [pkg-name] + (let [version (get-in versioned-packages [pkg-name :version]) + dep + {:deps/manifest :mvn + :mvn/version version + :parents #{[]} + :paths []}] + + [pkg-name dep]))) + (:depends-on package)) + + basis + (b/create-basis + {:dir (:relative-path package)}) + + libs + (merge (:libs basis) dependencies) + + basis + (assoc basis :libs libs) + + paths + (map (partial relativize package) (:paths basis))] + + {:basis basis + :paths paths + :dependencies dependencies})) + +(defn exec [title packages build-fn] + (requiring-resolve 'clojure.tools.build.tasks.copy/copy) + (requiring-resolve 'clojure.tools.build.tasks.write-pom/write-pom) + (requiring-resolve 'clojure.tools.build.tasks.jar/jar) + (requiring-resolve 'clojure.tools.build.tasks.create-basis/create-basis) + + (let [semaphore (Semaphore. 10)] + (->> packages + (mapv (fn [[pkg-name pkg]] + (future + (.acquire semaphore) + (log/info (str title " " + (log.render/render-package-name pkg-name) + "@|magenta " (:version pkg) "|@")) + + (try + (build-fn pkg) + (finally + (.release semaphore)))))) + (mapv deref)))) diff --git a/packages/kmono-build/src/k16/kmono/release.clj b/packages/kmono-build/src/k16/kmono/release.clj new file mode 100644 index 0000000..1f49753 --- /dev/null +++ b/packages/kmono-build/src/k16/kmono/release.clj @@ -0,0 +1 @@ +(ns k16.kmono.release) diff --git a/packages/kmono-cli/deps.edn b/packages/kmono-cli/deps.edn new file mode 100644 index 0000000..4647121 --- /dev/null +++ b/packages/kmono-cli/deps.edn @@ -0,0 +1,20 @@ +{:kmono/package {:artifact kmono} + + ; :kmono/private true + :kmono/build {:main k16.kmono.cli.main + :file "cli.jar" + :aliases [:native]} + + :paths ["src" "resources"] + + :deps {org.clojure/clojure {:mvn/version "1.12.0-rc2"} + cli-matic/cli-matic {:mvn/version "0.5.4"} + babashka/process {:mvn/version "0.5.22"} + jansi-clj/jansi-clj {:mvn/version "1.0.3"} + + com.kepler16/kmono-core {:local/root "../kmono-core"} + com.kepler16/kmono-cp {:local/root "../kmono-cp"} + com.kepler16/kmono-exec {:local/root "../kmono-exec"} + com.kepler16/kmono-log {:local/root "../kmono-log"}} + + :aliases {:native {:extra-deps {com.github.clj-easy/graal-build-time {:mvn/version "1.0.5"}}}}} diff --git a/packages/kmono-cli/resources/META-INF/native-image/k16.kmono.cli/native-image.properties b/packages/kmono-cli/resources/META-INF/native-image/k16.kmono.cli/native-image.properties new file mode 100644 index 0000000..9bc0a05 --- /dev/null +++ b/packages/kmono-cli/resources/META-INF/native-image/k16.kmono.cli/native-image.properties @@ -0,0 +1,7 @@ +Args = --no-fallback \ + --features=clj_easy.graal_build_time.InitClojureClasses \ + --report-unsupported-elements-at-runtime \ + --install-exit-handlers \ + -H:+UnlockExperimentalVMOptions \ + -H:+ReportExceptionStackTraces \ + --initialize-at-build-time=org.eclipse.aether.transport.http.HttpTransporterFactory diff --git a/packages/kmono-cli/src/k16/kmono/cli/commands/cp.clj b/packages/kmono-cli/src/k16/kmono/cli/commands/cp.clj new file mode 100644 index 0000000..d657413 --- /dev/null +++ b/packages/kmono-cli/src/k16/kmono/cli/commands/cp.clj @@ -0,0 +1,35 @@ +(ns k16.kmono.cli.commands.cp + (:require + [k16.kmono.cli.common.config :as common.config] + [k16.kmono.cli.common.opts :as opts] + [k16.kmono.core.config :as core.config] + [k16.kmono.core.fs :as core.fs] + [k16.kmono.core.packages :as core.packages] + [k16.kmono.cp :as kmono.cp] + [k16.kmono.log :as log])) + +(set! *warn-on-reflection* true) + +(defn- cp-command [props] + (let [project-root (core.fs/find-project-root (:dir props)) + workspace-config (-> (core.config/resolve-workspace-config project-root) + (common.config/merge-workspace-config props)) + packages (core.packages/resolve-packages project-root workspace-config)] + + (when (:verbose props) + (log/debug "Running clojure command") + (log/log-raw (kmono.cp/generate-classpath-command project-root workspace-config packages)) + (log/log-raw "")) + + (log/log-raw + (kmono.cp/resolve-classpath project-root workspace-config packages)))) + +(def command + {:command "cp" + :description "Produce a classpath string from a clojure project" + :opts [opts/packages-opt + opts/main-aliases-opt + opts/aliases-opt + opts/package-aliases-opt + opts/verbose-opt] + :runs cp-command}) diff --git a/packages/kmono-cli/src/k16/kmono/cli/commands/exec.clj b/packages/kmono-cli/src/k16/kmono/cli/commands/exec.clj new file mode 100644 index 0000000..efc72ec --- /dev/null +++ b/packages/kmono-cli/src/k16/kmono/cli/commands/exec.clj @@ -0,0 +1,46 @@ +(ns k16.kmono.cli.commands.exec + (:require + [k16.kmono.cli.common.log :as common.log] + [k16.kmono.cli.common.opts :as opts] + [k16.kmono.core.config :as core.config] + [k16.kmono.core.fs :as core.fs] + [k16.kmono.core.packages :as core.packages] + [k16.kmono.exec :as kmono.exec] + [k16.kmono.log :as log])) + +(set! *warn-on-reflection* true) + +(defn- run-command [props] + (let [project-root (core.fs/find-project-root (:dir props)) + workspace-config (core.config/resolve-workspace-config project-root) + packages (core.packages/resolve-packages project-root workspace-config) + + results + (kmono.exec/run-external-cmds + {:packages packages + :ordered (:ordered props) + :command (:_arguments props) + :concurrency (:concurrency props) + :on-event common.log/handle-event}) + + failed? (some + (fn [{:keys [success]}] + (not success)) + results)] + + (when failed? + (log/error "Command failed in one or more packages") + (System/exit 1)))) + +(def command + {:command "exec" + :description "Run a command in each package" + :opts [opts/packages-opt + opts/verbose-opt + opts/order-opt + + {:as "Maximum number of commands to run in parallel. Defaults to number of cores" + :option "concurrency" + :short "c" + :type :int}] + :runs run-command}) diff --git a/packages/kmono-cli/src/k16/kmono/cli/commands/repl.clj b/packages/kmono-cli/src/k16/kmono/cli/commands/repl.clj new file mode 100644 index 0000000..d540361 --- /dev/null +++ b/packages/kmono-cli/src/k16/kmono/cli/commands/repl.clj @@ -0,0 +1,78 @@ +(ns k16.kmono.cli.commands.repl + (:require + [babashka.process :as proc] + [clojure.string :as str] + [k16.kmono.cli.common.config :as common.config] + [k16.kmono.cli.common.opts :as opts] + [k16.kmono.core.config :as core.config] + [k16.kmono.core.fs :as core.fs] + [k16.kmono.core.packages :as core.packages] + [k16.kmono.cp :as kmono.cp] + [k16.kmono.log :as log])) + +(set! *warn-on-reflection* true) + +(defn- render-aliases [aliases] + (str "@|black,bold [|@" + + (->> aliases + (map + (fn [alias] + (str "@|yellow " alias "|@"))) + (str/join "@|black,bold , |@")) + + "@|black,bold ]|@")) + +(defn- repl-command [props] + (let [project-root (core.fs/find-project-root (:dir props)) + workspace-config (-> (core.config/resolve-workspace-config project-root) + (common.config/merge-workspace-config props)) + packages (core.packages/resolve-packages project-root workspace-config) + + {:keys [sdeps aliases]} + (kmono.cp/generate-aliases + project-root workspace-config packages) + + aliases (concat aliases (:main-aliases workspace-config)) + + command + ["clojure" + "-Sdeps" (str "'" (str/trim (prn-str sdeps)) "'") + (str "-M" (kmono.cp/serialize-aliases aliases))] + + _ (log/info (str "Main aliases: " (render-aliases (:main-aliases workspace-config)))) + _ (log/info (str "Aliases: " (render-aliases (:aliases workspace-config)))) + _ (log/info (str "Package Aliases: " (render-aliases (:package-aliases workspace-config)))) + + command (str/join " " command) + + _ (when (:verbose props) + (log/debug "Running repl command") + (log/log-raw command) + (log/log-raw "")) + + proc (proc/process + {:dir project-root + :inherit true} + command)] + + (doto (Runtime/getRuntime) + (.addShutdownHook + (Thread. + (fn [] + (try + (proc/destroy proc) + (proc/check proc) + (catch Throwable _ nil)))))) + + (proc/check proc))) + +(def command + {:command "repl" + :description "Start a clojure repl" + :opts [opts/packages-opt + opts/main-aliases-opt + opts/package-aliases-opt + opts/aliases-opt + opts/verbose-opt] + :runs repl-command}) diff --git a/packages/kmono-cli/src/k16/kmono/cli/commands/run.clj b/packages/kmono-cli/src/k16/kmono/cli/commands/run.clj new file mode 100644 index 0000000..75bdd45 --- /dev/null +++ b/packages/kmono-cli/src/k16/kmono/cli/commands/run.clj @@ -0,0 +1,88 @@ +(ns k16.kmono.cli.commands.run + (:require + [clojure.string :as str] + [k16.kmono.cli.common.log :as common.log] + [k16.kmono.cli.common.opts :as opts] + [k16.kmono.core.config :as core.config] + [k16.kmono.core.deps :as core.deps] + [k16.kmono.core.fs :as core.fs] + [k16.kmono.core.graph :as core.graph] + [k16.kmono.core.packages :as core.packages] + [k16.kmono.exec :as kmono.exec] + [k16.kmono.log :as log])) + +(set! *warn-on-reflection* true) + +(defn- run-command [props] + (let [project-root (core.fs/find-project-root (:dir props)) + workspace-config (core.config/resolve-workspace-config project-root) + packages (core.packages/resolve-packages project-root workspace-config) + + globs (or (:M props) + (:T props) + (:X props)) + + packages (core.graph/filter-by + (fn [pkg] + (-> (core.deps/filter-package-aliases + (core.deps/generate-package-aliases + project-root pkg) + globs) + seq)) + + packages) + + results + (kmono.exec/run-external-cmds + {:packages packages + :ordered (:ordered props) + :command (fn [pkg] + (let [aliases (core.deps/filter-package-aliases + (core.deps/generate-package-aliases + project-root pkg) + globs) + names (->> (keys aliases) + (map (fn [alias] + (name alias))) + (str/join ":")) + + flag (cond + (:M props) "-M" + (:T props) "-T" + (:X props) "-X")] + + (concat ["clojure" (str flag ":" names)] (:_arguments props)))) + :concurrency (:concurrency props) + :on-event common.log/handle-event}) + + failed? (some + (fn [{:keys [success]}] + (not success)) + results)] + + (when failed? + (log/error "Command failed in one or more packages") + (System/exit 1)))) + +(def aliases-opt + {:as "List of aliases from packages" + :multiple true + :type :keyword}) + +(def command + {:command "run" + :description "Run an alias in project packages" + :opts [opts/packages-opt + opts/verbose-opt + opts/order-opt + + {:as "Maximum number of commands to run in parallel. Defaults to number of cores" + :option "concurrency" + :short "c" + :type :int} + + (assoc aliases-opt :option "M" :short "M") + (assoc aliases-opt :option "T" :short "T") + (assoc aliases-opt :option "X" :short "X")] + + :runs run-command}) diff --git a/packages/kmono-cli/src/k16/kmono/cli/common/config.clj b/packages/kmono-cli/src/k16/kmono/cli/common/config.clj new file mode 100644 index 0000000..bcd21d7 --- /dev/null +++ b/packages/kmono-cli/src/k16/kmono/cli/common/config.clj @@ -0,0 +1,8 @@ +(ns k16.kmono.cli.common.config) + +(defn merge-workspace-config + [workspace-config {:keys [package-aliases main-aliases aliases]}] + (cond-> workspace-config + package-aliases (assoc :package-aliases package-aliases) + main-aliases (assoc :main-aliases main-aliases) + aliases (assoc :aliases aliases))) diff --git a/packages/kmono-cli/src/k16/kmono/cli/common/log.clj b/packages/kmono-cli/src/k16/kmono/cli/common/log.clj new file mode 100644 index 0000000..78ae0dd --- /dev/null +++ b/packages/kmono-cli/src/k16/kmono/cli/common/log.clj @@ -0,0 +1,44 @@ +(ns k16.kmono.cli.common.log + (:require + [clojure.string :as str] + [k16.kmono.log :as log] + [k16.kmono.log.render :as log.render])) + +(defn- stage-started [{:keys [id total]}] + (log/info (str "Stage " id " of " total " started"))) + +(defn- render-command [command] + (let [command (str/join " " command) + shortened (subs command 0 (min (count command) 20)) + with-elide (if (not= (count shortened) (count command)) + (str shortened "...") + shortened)] + + (str "@|white " with-elide "|@"))) + +(defn- proc-started [{:keys [package command]}] + (log/log " " + (log.render/render-package-name (:fqn package)) + " @|cyan [running] |@" + (render-command command))) + +(defn- proc-finished [{:keys [package success out err]}] + (let [[color message] (if success + ["green" "[succes]"] + ["red" "[failed]"])] + (log/log " " + (log.render/render-package-name (:fqn package)) + " @|" color " " message "|@") + + (when (seq out) + (log/log-raw out)) + (when (seq err) + (log/log-raw err)))) + +(defn handle-event [event] + (case (:type event) + :stage-start (stage-started event) + :stage-finish (log/info "Stage finished") + + :proc-start (proc-started event) + :proc-finish (proc-finished event))) diff --git a/packages/kmono-cli/src/k16/kmono/cli/common/opts.clj b/packages/kmono-cli/src/k16/kmono/cli/common/opts.clj new file mode 100644 index 0000000..c7dc0f5 --- /dev/null +++ b/packages/kmono-cli/src/k16/kmono/cli/common/opts.clj @@ -0,0 +1,42 @@ +(ns k16.kmono.cli.common.opts) + +(set! *warn-on-reflection* true) + +(def packages-opt + {:as "A glob string describing where to search for packages (default: 'packages/*')" + :option "packages" + :short "p" + :type :string}) + +(def aliases-opt + {:as "List of aliases from the root deps.edn" + :option "aliases" + :short "A" + :multiple true + :type :keyword}) + +(def main-aliases-opt + {:as "List of main aliases from the root deps.edn" + :option "main-aliases" + :short "M" + :multiple true + :type :keyword}) + +(def package-aliases-opt + {:as "List of aliases from packages" + :option "package-aliases" + :short "P" + :multiple true + :type :keyword}) + +(def order-opt + {:as "Run tests in dependency order" + :option "ordered" + :type :flag + :default true}) + +(def verbose-opt + {:as "Verbose output" + :option "verbose" + :short "v" + :type :with-flag}) diff --git a/packages/kmono-cli/src/k16/kmono/cli/main.clj b/packages/kmono-cli/src/k16/kmono/cli/main.clj new file mode 100644 index 0000000..0c5d1d3 --- /dev/null +++ b/packages/kmono-cli/src/k16/kmono/cli/main.clj @@ -0,0 +1,26 @@ +(ns k16.kmono.cli.main + (:require + [cli-matic.core :as cli] + [k16.kmono.cli.commands.cp :as commands.cp] + [k16.kmono.cli.commands.repl :as commands.repl] + [k16.kmono.cli.commands.exec :as commands.run] + [k16.kmono.cli.commands.run :as commands.tool]) + (:gen-class)) + +(set! *warn-on-reflection* true) + +(def cli-configuration + {:command "kmono" + :description "A cli for managing clojure (mono)repos" + :version "0.0.0" + :opts [{:as "Run commands as if in this directory" + :option "dir" + :short "d" + :type :string}] + :subcommands [commands.cp/command + commands.repl/command + commands.run/command + commands.tool/command]}) + +(defn- -main [& args] + (cli/run-cmd args cli-configuration)) diff --git a/packages/kmono-core/deps.edn b/packages/kmono-core/deps.edn new file mode 100644 index 0000000..4aa55e6 --- /dev/null +++ b/packages/kmono-core/deps.edn @@ -0,0 +1,13 @@ +{:kmono/package {} + + :deps {babashka/fs {:mvn/version "0.5.22"} + meta-merge/meta-merge {:mvn/version "1.0.0"} + ;; TODO: This can technically be dropped + org.flatland/ordered {:mvn/version "1.15.12"} + metosin/malli {:mvn/version "0.16.4"}} + + :aliases {:test {:extra-paths ["test"] + :extra-deps {lambdaisland/kaocha {:mvn/version "1.91.1392"} + nubank/matcher-combinators {:mvn/version "3.9.1"} + local/kmono-test {:local/root "../kmono-test"}} + :main-opts ["-m" "kaocha.runner" "-c" "../../tests.edn"]}}} diff --git a/packages/kmono-core/src/k16/kmono/core/config.clj b/packages/kmono-core/src/k16/kmono/core/config.clj new file mode 100644 index 0000000..5200412 --- /dev/null +++ b/packages/kmono-core/src/k16/kmono/core/config.clj @@ -0,0 +1,57 @@ +(ns k16.kmono.core.config + (:require + [babashka.fs :as fs] + [k16.kmono.core.fs :as core.fs] + [k16.kmono.core.schema :as core.schema] + [malli.core :as malli] + [malli.error :as malli.error] + [malli.transform :as malli.transform] + [meta-merge.core :as metamerge])) + +(set! *warn-on-reflection* true) + +(defn validate! + ([?schema data] + (validate! ?schema data "Schema validation failed")) + ([?schema data message] + (when-not (malli/validate ?schema data) + (throw (ex-info message {:type :kmono/validation-error + :errors (->> data + (malli/explain ?schema) + malli.error/humanize)}))) + + data)) + +(defn- read-kmono-config [deps-file-path key] + (some-> (when (fs/exists? deps-file-path) + (core.fs/read-edn-file! deps-file-path)) + (get key))) + +(defn resolve-workspace-config [root] + (let [root-workspace-config (read-kmono-config (fs/file root "deps.edn") :kmono/workspace) + local-workspace-config (read-kmono-config (fs/file root "deps.local.edn") :kmono/workspace) + workspace-config (metamerge/meta-merge root-workspace-config local-workspace-config)] + + (when workspace-config + (validate! core.schema/?WorkspaceConfig workspace-config "Workspace config invalid") + + (malli/encode core.schema/?WorkspaceConfig + workspace-config + (malli.transform/default-value-transformer + {::malli.transform/add-optional-keys true}))))) + +(defn resolve-package-config [workspace-config package-path] + (let [deps-file-path (fs/file package-path "deps.edn") + + deps-edn (when (fs/exists? deps-file-path) + (core.fs/read-edn-file! deps-file-path)) + + package-config (:kmono/package deps-edn) + package-config (merge (select-keys workspace-config [:group]) + {:deps-edn deps-edn} + package-config)] + + (when (:kmono/package deps-edn) + (validate! core.schema/?PackageConfig package-config "Package config invalid") + + package-config))) diff --git a/packages/kmono-core/src/k16/kmono/core/deps.clj b/packages/kmono-core/src/k16/kmono/core/deps.clj new file mode 100644 index 0000000..9cb2210 --- /dev/null +++ b/packages/kmono-core/src/k16/kmono/core/deps.clj @@ -0,0 +1,122 @@ +(ns k16.kmono.core.deps + (:require + [babashka.fs :as fs] + [k16.kmono.core.fs :as core.fs])) + +(set! *warn-on-reflection* true) + +(defn- generate-extra-deps [packages] + (reduce + (fn [deps [fqn pkg]] + (assoc + deps fqn {:local/root (:relative-path pkg)})) + {} + packages)) + +(defn- scope-package-alias [package alias-name] + (let [ns' (namespace alias-name) + alias (str + (when ns' (str ns' ".")) + (name alias-name))] + (-> (str (:name package) "/" alias) + keyword))) + +(defn- alter-path [project-root package path] + (let [resolved-path + (fs/normalize + (fs/file (:absolute-path package) path)) + + relative-path + (fs/relativize project-root resolved-path)] + + (str relative-path))) + +(defn- alter-paths [project-root package paths] + (mapv + (partial alter-path project-root package) + paths)) + +(defn- alter-deps-paths [project-root package deps] + (reduce + (fn [deps [fqn coord]] + (if-let [path (:local/root coord)] + (assoc deps fqn {:local/root (alter-path project-root package path)}) + (assoc deps fqn coord))) + {} + deps)) + +(defn- alter-alias-paths + [project-root package {:keys [extra-deps replace-deps + extra-paths replace-paths] :as alias}] + (cond-> alias + extra-deps (assoc :extra-deps (alter-deps-paths + project-root package extra-deps)) + + replace-deps (assoc :replace-deps (alter-deps-paths + project-root package replace-deps)) + + extra-paths (assoc :extra-paths (alter-paths + project-root package extra-paths)) + + replace-paths (assoc :replace-paths (alter-paths + project-root package replace-paths)))) + +(defn generate-package-aliases [project-root package] + (reduce + (fn [aliases [alias-name alias]] + (assoc aliases + (scope-package-alias package alias-name) + (alter-alias-paths project-root package alias))) + {} + (get-in package [:deps-edn :aliases]))) + +(defn- generate-all-package-aliases [project-root packages] + (reduce + (fn [aliases [_ package]] + (merge aliases (generate-package-aliases project-root package))) + {} + packages)) + +(defn resolve-aliases [project-root packages] + (let [local-deps (fs/file project-root "deps.local.edn") + + aliases (if (fs/exists? local-deps) + (:aliases (core.fs/read-edn-file! local-deps)) + {}) + + extra-deps {:extra-deps (generate-extra-deps packages)} + package-aliases (generate-all-package-aliases project-root packages)] + + {:aliases aliases + :packages extra-deps + :package-aliases package-aliases + + :combined (merge aliases + {:kmono/packages extra-deps} + package-aliases)})) + +(defn- glob-to-pattern [glob] + (if (= "*" glob) + ".*" + glob)) + +(defn- alias-matches-glob [alias globs] + (some + (fn [glob] + (let [pkg-glob (namespace glob) + alias-glob (name glob) + + pkg-match (glob-to-pattern pkg-glob) + alias-match (glob-to-pattern alias-glob) + pattern (re-pattern (str pkg-match "/" alias-match))] + + (re-matches pattern (str (namespace alias) "/" (name alias))))) + + globs)) + +(defn filter-package-aliases [aliases globs] + (into {} + (filter + (fn [[alias-name]] + (alias-matches-glob alias-name globs))) + aliases)) diff --git a/packages/kmono-core/src/k16/kmono/core/fs.clj b/packages/kmono-core/src/k16/kmono/core/fs.clj new file mode 100644 index 0000000..b2fc9bb --- /dev/null +++ b/packages/kmono-core/src/k16/kmono/core/fs.clj @@ -0,0 +1,50 @@ +(ns k16.kmono.core.fs + (:require + [babashka.fs :as fs] + [clojure.edn :as edn] + [clojure.string :as str])) + +(set! *warn-on-reflection* true) + +(defn- is-workspace-root [file] + (let [lines (fs/read-all-lines file)] + (some (fn [line] + (str/includes? line ":kmono/workspace")) + lines))) + +(defn find-project-root + ([] (find-project-root nil nil)) + ([dir] (find-project-root dir nil)) + ([dir current-root] + (let [dir (or dir (fs/cwd)) + deps-file (fs/file dir "deps.edn")] + (cond + (not (fs/starts-with? dir (fs/home))) + (when current-root + (str (fs/absolutize current-root))) + + (and (fs/exists? deps-file) + (is-workspace-root deps-file)) + (str (fs/absolutize dir)) + + (fs/exists? deps-file) + (find-project-root (fs/parent dir) dir) + + :else + (find-project-root (fs/parent dir) current-root))))) + +(defn read-edn-file! [file-path] + (try + (-> (fs/file file-path) + (slurp) + (edn/read-string)) + (catch Exception ex + (throw (ex-info (str "Could not read " file-path) + {:file-path file-path} + ex))))) + +(defn find-packages [root packages-glob] + (conj (fs/glob root packages-glob) + (-> (fs/path root) + (fs/normalize) + (fs/absolutize)))) diff --git a/packages/kmono-core/src/k16/kmono/core/graph.clj b/packages/kmono-core/src/k16/kmono/core/graph.clj new file mode 100644 index 0000000..6456524 --- /dev/null +++ b/packages/kmono-core/src/k16/kmono/core/graph.clj @@ -0,0 +1,97 @@ +(ns k16.kmono.core.graph + (:require + [flatland.ordered.set :as set.ordered] + [k16.kmono.core.schema :as core.schema])) + +(set! *warn-on-reflection* true) + +(def ?ExecOrder + [:vector [:vector :symbol]]) + +(defn find-cycles [packages] + (loop [nodes (keys packages) + node (first nodes) + path (set.ordered/ordered-set)] + (when node + (let [package (get packages node)] + (if (seq (:depends-on package)) + (if (contains? path node) + (conj (set path) node) + (recur (:depends-on package) + (first (:depends-on package)) + (conj path node))) + (recur (rest nodes) + (first (rest nodes)) + (disj path node))))))) + +(defn ensure-no-cycles! [packages] + (let [cycles (find-cycles packages)] + (when (seq cycles) + (throw (ex-info "Cicrlar dependencies found" + {:type :kmono/circular-dependencies + :cycles cycles})))) + packages) + +(defn parallel-topo-sort + {:malli/schema [:=> [:cat core.schema/?PackageMap] [:maybe ?ExecOrder]]} + [packages] + (when (seq packages) + (when-let [ks (->> packages + (keep + (fn [[fqn package]] + (when (empty? (:depends-on package)) + fqn))) + seq)] + (into [(vec ks)] + (parallel-topo-sort + (into {} + (map (fn [[fqn package]] + [fqn (assoc package + :depends-on (apply disj (:depends-on package) ks))])) + (apply dissoc packages ks))))))) + +(defn query-dependents + {:malli/schema [:=> [:cat core.schema/?PackageMap :symbol] [:set :symbol]]} + [packages pkg-name] + + (let [pkg (get packages pkg-name) + dependents + (mapcat + (fn [dependent-pkg-name] + (query-dependents packages dependent-pkg-name)) + (:dependents pkg))] + + (set (concat (:dependents pkg) dependents)))) + +(defn filter-by + ([filter-fn packages] (filter-by filter-fn {} packages)) + ([filter-fn {:keys [include-dependents]} packages] + (let [filtered + (->> packages + (mapv (fn [[pkg-name pkg]] + (future + [pkg-name (filter-fn pkg)]))) + (mapv deref)) + + filtered + (into #{} + (comp + (filter second) + (map first)) + filtered) + + filtered + (if include-dependents + (into #{} + (mapcat + (fn [pkg-name] + (concat [pkg-name] (query-dependents packages pkg-name)))) + filtered) + filtered)] + + (into {} + (map + (fn [pkg] + [pkg (get packages pkg)])) + + filtered)))) diff --git a/packages/kmono-core/src/k16/kmono/core/packages.clj b/packages/kmono-core/src/k16/kmono/core/packages.clj new file mode 100644 index 0000000..4e7af28 --- /dev/null +++ b/packages/kmono-core/src/k16/kmono/core/packages.clj @@ -0,0 +1,88 @@ +(ns k16.kmono.core.packages + (:require + [babashka.fs :as fs] + [k16.kmono.core.config :as core.config] + [k16.kmono.core.fs :as core.fs] + [k16.kmono.core.graph :as core.graph] + [k16.kmono.core.schema :as core.schema])) + +(set! *warn-on-reflection* true) + +(defn- create-package [project-root workspace-config package-path] + (when-let [config (core.config/resolve-package-config workspace-config package-path)] + (let [package + (merge {:name (symbol (fs/file-name package-path))} + (select-keys config [:group :name :deps-edn]) + {:absolute-path (str package-path) + :relative-path (str (fs/relativize project-root package-path)) + :depends-on #{}})] + + (assoc package :fqn (symbol (str (:group package) "/" (:name package))))))) + +(defn- filter-dependencies [packages package] + (into #{} + (comp + (map #(-> % second :local/root)) + (remove nil?) + + (map (fn [path] + (-> (fs/file (:absolute-path package) path) + fs/normalize + str))) + + (map + (fn [path] + (some + (fn [[fqn package]] + (when (= (:absolute-path package) path) + fqn)) + packages))) + (remove nil?)) + (get-in package [:deps-edn :deps]))) + +(defn- find-dependencies + {:malli/schema [:=> [:cat core.schema/?PackageMap] core.schema/?PackageMap]} + [packages] + (reduce (fn [acc [fqn package]] + (assoc acc + fqn (assoc package + :depends-on (filter-dependencies + packages package)))) + {} + packages)) + +(defn- package-dependents [pkg-name packages] + (into #{} + (comp + (filter (fn [[_ pkg]] + (contains? (:depends-on pkg) + pkg-name))) + (map first)) + packages)) + +(defn- find-dependents + {:malli/schema [:=> [:cat core.schema/?PackageMap] core.schema/?PackageMap]} + [packages] + (reduce + (fn [packages [pkg-name pkg]] + (let [dependents (package-dependents pkg-name packages)] + (assoc packages pkg-name (assoc pkg :dependents dependents)))) + packages + packages)) + +(defn resolve-packages [project-root workspace-config] + (let [dirs (core.fs/find-packages project-root (:packages workspace-config)) + + packages + (into {} + (comp + (map (partial create-package project-root workspace-config)) + (remove nil?) + (map (juxt :fqn identity))) + + dirs)] + + (->> packages + find-dependencies + find-dependents + core.graph/ensure-no-cycles!))) diff --git a/packages/kmono-core/src/k16/kmono/core/schema.clj b/packages/kmono-core/src/k16/kmono/core/schema.clj new file mode 100644 index 0000000..215ca22 --- /dev/null +++ b/packages/kmono-core/src/k16/kmono/core/schema.clj @@ -0,0 +1,49 @@ +(ns k16.kmono.core.schema) + +(set! *warn-on-reflection* true) + +(def ?WorkspaceConfig + [:map + [:packages {:optional true + :default "packages/*"} + :string] + + [:group {:optional true} + :symbol] + + [:main-aliases {:optional true} + [:vector :keyword]] + [:aliases {:optional true} + [:vector :keyword]] + [:package-aliases {:optional true} + [:vector :keyword]]]) + +(def ?PackageConfig + [:map + [:group :symbol] + [:name {:optional true} + [:maybe [:or :string :symbol]]]]) + +(def ?Coordinate + [:map + [[:= :local/root] {:optional true} :string]]) + +(def ?Package + [:map + [:group :symbol] + [:name :symbol] + [:fqn :symbol] + + [:deps-edn + [:map + [:deps {:optional true} [:map-of :symbol ?Coordinate]] + [:aliases {:optional true} [:map-of :keyword :map]]]] + + [:depends-on [:set :symbol]] + [:dependents [:set :symbol]] + + [:absolute-path :string] + [:relative-path :string]]) + +(def ?PackageMap + [:map-of :symbol ?Package]) diff --git a/packages/kmono-core/test/k16/kmono/core/config_test.clj b/packages/kmono-core/test/k16/kmono/core/config_test.clj new file mode 100644 index 0000000..e632bfd --- /dev/null +++ b/packages/kmono-core/test/k16/kmono/core/config_test.clj @@ -0,0 +1,42 @@ +(ns k16.kmono.core.config-test + (:require + [babashka.fs :as fs] + [clojure.test :refer [deftest is use-fixtures]] + [k16.kmono.core.config :as core.config] + [k16.kmono.test.helpers.repo :refer [*repo* with-test-repo] :as helpers.repo] + [matcher-combinators.test])) + +(use-fixtures :each with-test-repo) + +(deftest resolves-default-workpace-config-test + (is (= {:packages "packages/*" + :group 'com.kepler16} + (core.config/resolve-workspace-config *repo*)))) + +(deftest merge-with-local-test + (fs/write-bytes (fs/file *repo* "deps.local.edn") + (.getBytes (prn-str {:kmono/workspace {:group 'a.b.c}}))) + (is (= {:packages "packages/*" + :group 'a.b.c} + (core.config/resolve-workspace-config *repo*)))) + +(deftest workspace-config-validation-test + (fs/write-bytes (fs/file *repo* "deps.local.edn") + (.getBytes (prn-str {:kmono/workspace {:group 'a.b.c + :main-aliases "invalid"}}))) + (is (thrown-match? clojure.lang.ExceptionInfo + {:type :kmono/validation-error + :errors {:main-aliases ["invalid type"]}} + (core.config/resolve-workspace-config *repo*)))) + +(deftest package-config-test + (is (match? {:group 'a} + (core.config/resolve-package-config + {:group 'a} (fs/file *repo* "packages/a"))))) + +(deftest package-validation-test + (is (thrown-match? clojure.lang.ExceptionInfo + {:type :kmono/validation-error + :errors {:group ["missing required key"]}} + (core.config/resolve-package-config + {} (fs/file *repo* "packages/a"))))) diff --git a/packages/kmono-core/test/k16/kmono/core/deps_test.clj b/packages/kmono-core/test/k16/kmono/core/deps_test.clj new file mode 100644 index 0000000..7980bdd --- /dev/null +++ b/packages/kmono-core/test/k16/kmono/core/deps_test.clj @@ -0,0 +1,51 @@ +(ns k16.kmono.core.deps-test + (:require + [clojure.test :refer [deftest is use-fixtures]] + [k16.kmono.core.config :as core.config] + [k16.kmono.core.deps :as core.deps] + [k16.kmono.core.packages :as core.packages] + [k16.kmono.test.helpers.repo :refer [*repo* with-test-repo] :as helpers.repo] + [matcher-combinators.test] + [babashka.fs :as fs])) + +(use-fixtures :each with-test-repo) + +(deftest resolve-package-aliases-test + (fs/write-bytes (fs/file *repo* "deps.local.edn") + (.getBytes (prn-str {:aliases {:local {:extra-paths ["local"]}}}))) + + (let [config (core.config/resolve-workspace-config *repo*) + + packages (core.packages/resolve-packages *repo* config) + + aliases (core.deps/resolve-aliases *repo* packages)] + + (is (match? {:aliases {:local {:extra-paths ["local"]}} + + :packages {:extra-deps {'com.kepler16/a {:local/root "packages/a"} + 'com.kepler16/b {:local/root "packages/b"}}} + + :package-aliases {:a/test + {:extra-paths ["packages/a/test"] + :extra-deps {'local/excluded {:local/root "packages/excluded"}}}}} + aliases)))) + +(deftest filter-package-aliases-test + (let [config (core.config/resolve-workspace-config *repo*) + packages (core.packages/resolve-packages *repo* config) + aliases (core.deps/resolve-aliases *repo* packages)] + + (is (= [:a/test] + (keys (core.deps/filter-package-aliases (:package-aliases aliases) [:*/*])))) + + (is (= [:a/test] + (keys (core.deps/filter-package-aliases (:package-aliases aliases) [:a/*])))) + + (is (= [:a/test] + (keys (core.deps/filter-package-aliases (:package-aliases aliases) [:*/test])))) + + (is (= [:a/test] + (keys (core.deps/filter-package-aliases (:package-aliases aliases) [:a/test])))) + + (is (not + (keys (core.deps/filter-package-aliases (:package-aliases aliases) [:*/unknown])))))) diff --git a/packages/kmono-core/test/k16/kmono/core/fs_test.clj b/packages/kmono-core/test/k16/kmono/core/fs_test.clj new file mode 100644 index 0000000..2211d29 --- /dev/null +++ b/packages/kmono-core/test/k16/kmono/core/fs_test.clj @@ -0,0 +1,17 @@ +(ns k16.kmono.core.fs-test + (:require + [babashka.fs :as fs] + [clojure.test :refer [deftest is use-fixtures]] + [k16.kmono.core.fs :as core.fs] + [k16.kmono.test.helpers.repo :refer [*repo* with-test-repo] :as helpers.repo])) + +(use-fixtures :once with-test-repo) + +(deftest stops-at-workspace-root-test + (is (= *repo* (core.fs/find-project-root *repo*)))) + +(deftest stops-at-workspace-root-from-subdir-test + (is (= *repo* (core.fs/find-project-root (fs/file *repo* "packages/a"))))) + +(deftest stops-at-homedir-test + (is (= nil (core.fs/find-project-root (fs/file (fs/home) "some-subdir"))))) diff --git a/packages/kmono-core/test/k16/kmono/core/graph_test.clj b/packages/kmono-core/test/k16/kmono/core/graph_test.clj new file mode 100644 index 0000000..c24ce54 --- /dev/null +++ b/packages/kmono-core/test/k16/kmono/core/graph_test.clj @@ -0,0 +1,73 @@ +(ns k16.kmono.core.graph-test + (:require + [babashka.fs :as fs] + [clojure.test :refer [deftest is use-fixtures]] + [k16.kmono.core.config :as core.config] + [k16.kmono.core.graph :as core.graph] + [k16.kmono.core.packages :as core.packages] + [k16.kmono.test.helpers.repo :refer [*repo* with-test-repo] :as helpers.repo] + [matcher-combinators.test])) + +(use-fixtures :each with-test-repo) + +(deftest no-cycles-test + (let [config (core.config/resolve-workspace-config *repo*) + packages (core.packages/resolve-packages *repo* config)] + + (is (not (core.graph/find-cycles packages))))) + +(deftest finds-cycles-test + (fs/write-bytes (fs/file *repo* "packages/a/deps.edn") + (.getBytes (prn-str {:kmono/package {} + :deps {'com.keperl16/b {:local/root "../b"}}}))) + + (let [config (core.config/resolve-workspace-config *repo*)] + + (is (thrown-match? + clojure.lang.ExceptionInfo + {:type :kmono/circular-dependencies + :cycles #{'com.kepler16/a 'com.kepler16/b}} + (core.packages/resolve-packages *repo* config))))) + +(deftest topological-sort-test + (fs/create-dirs (fs/file *repo* "packages/c")) + (fs/write-bytes (fs/file *repo* "packages/c/deps.edn") + (.getBytes (prn-str {:kmono/package {} + :deps {}}))) + + (let [config (core.config/resolve-workspace-config *repo*) + packages (core.packages/resolve-packages *repo* config)] + + (is (= [['com.kepler16/a 'com.kepler16/c] ['com.kepler16/b]] + (core.graph/parallel-topo-sort packages))))) + +(deftest query-dependents-test + (fs/create-dirs (fs/file *repo* "packages/c")) + (fs/write-bytes (fs/file *repo* "packages/c/deps.edn") + (.getBytes (prn-str {:kmono/package {} + :deps {'com.kepler16/b {:local/root "../b"}}}))) + + (let [config (core.config/resolve-workspace-config *repo*) + packages (core.packages/resolve-packages *repo* config)] + + (is (match? #{'com.kepler16/b 'com.kepler16/c} + (core.graph/query-dependents packages 'com.kepler16/a))) + + (is (match? #{'com.kepler16/c} + (core.graph/query-dependents packages 'com.kepler16/b))) + + (is (match? #{} + (core.graph/query-dependents packages 'com.kepler16/c))))) + +(deftest filter-packages-test + (let [config (core.config/resolve-workspace-config *repo*) + packages (core.packages/resolve-packages *repo* config)] + + (is (= ['com.kepler16/a] + (keys (core.graph/filter-by #(= 'com.kepler16/a (:fqn %)) packages)))) + + (is (= ['com.kepler16/b 'com.kepler16/a] + (keys (core.graph/filter-by + #(= 'com.kepler16/a (:fqn %)) + {:include-dependents true} + packages)))))) diff --git a/packages/kmono-core/test/k16/kmono/core/packages_test.clj b/packages/kmono-core/test/k16/kmono/core/packages_test.clj new file mode 100644 index 0000000..f2bbbdb --- /dev/null +++ b/packages/kmono-core/test/k16/kmono/core/packages_test.clj @@ -0,0 +1,36 @@ +(ns k16.kmono.core.packages-test + (:require + [babashka.fs :as fs] + [clojure.test :refer [deftest is use-fixtures]] + [k16.kmono.core.config :as core.config] + [k16.kmono.core.packages :as core.packages] + [k16.kmono.test.helpers.repo :refer [*repo* with-test-repo] :as helpers.repo] + [matcher-combinators.test])) + +(use-fixtures :each with-test-repo) + +(deftest load-packages-test + (let [config (core.config/resolve-workspace-config *repo*)] + (is (match? {'com.kepler16/a {:group 'com.kepler16 + :name 'a + :fqn 'com.kepler16/a + + :deps-edn {} + :depends-on #{} + :dependents #{'com.kepler16/b} + + :absolute-path (str (fs/file *repo* "packages/a")) + :relative-path "packages/a"} + 'com.kepler16/b {:group 'com.kepler16 + :name 'b + :fqn 'com.kepler16/b + + :deps-edn {:deps {'a/b {:mvn/version "RELEASE"} + 'local/excluded {:local/root "../excluded"} + 'com.kepler16/a {:local/root "../a"}}} + :depends-on #{'com.kepler16/a} + :dependents #{} + + :absolute-path (str (fs/file *repo* "packages/b")) + :relative-path "packages/b"}} + (core.packages/resolve-packages *repo* config))))) diff --git a/packages/kmono-cp/deps.edn b/packages/kmono-cp/deps.edn new file mode 100644 index 0000000..771873c --- /dev/null +++ b/packages/kmono-cp/deps.edn @@ -0,0 +1,10 @@ +{:kmono/package {} + + :deps {babashka/process {:mvn/version "0.5.22"} + com.kepler16/kmono-core {:local/root "../kmono-core"}} + + :aliases {:test {:extra-paths ["test"] + :extra-deps {lambdaisland/kaocha {:mvn/version "1.91.1392"} + nubank/matcher-combinators {:mvn/version "3.9.1"} + local/kmono-test {:local/root "../kmono-test"}} + :main-opts ["-m" "kaocha.runner" "-c" "../../tests.edn"]}}} diff --git a/packages/kmono-cp/src/k16/kmono/cp.clj b/packages/kmono-cp/src/k16/kmono/cp.clj new file mode 100644 index 0000000..6a9a547 --- /dev/null +++ b/packages/kmono-cp/src/k16/kmono/cp.clj @@ -0,0 +1,59 @@ +(ns k16.kmono.cp + (:require + [babashka.process :as proc] + [clojure.string :as str] + [k16.kmono.core.deps :as core.deps])) + +(set! *warn-on-reflection* true) + +(defn serialize-aliases [aliases] + (reduce + (fn [acc alias] + + (if (namespace alias) + (str acc ":" (namespace alias) "/" (name alias)) + (str acc ":" (name alias)))) + + "" + aliases)) + +(defn generate-aliases + [project-root workspace-config packages] + + (let [{:keys [combined package-aliases]} + (core.deps/resolve-aliases project-root packages) + + root-aliases (:aliases workspace-config) + package-alias-globs (:package-aliases workspace-config) + package-aliases (core.deps/filter-package-aliases + package-aliases package-alias-globs) + + aliases (concat [:kmono/packages] + root-aliases + (keys package-aliases))] + + {:sdeps {:aliases combined} + :aliases aliases})) + +(defn generate-classpath-command + [project-root workspace-config packages] + + (let [{:keys [sdeps aliases]} + (generate-aliases + project-root workspace-config packages) + + command + ["clojure" + "-Sdeps" (str "'" (str/trim (prn-str sdeps)) "'") + (str "-A" (serialize-aliases aliases)) + "-Spath"]] + + (str/join " " command))) + +(defn resolve-classpath [project-root workspace-config packages] + (:out + (proc/shell + {:dir (str project-root) + :out :string + :err :string} + (generate-classpath-command project-root workspace-config packages)))) diff --git a/packages/kmono-cp/test/k16/kmono/cp_test.clj b/packages/kmono-cp/test/k16/kmono/cp_test.clj new file mode 100644 index 0000000..0a828a2 --- /dev/null +++ b/packages/kmono-cp/test/k16/kmono/cp_test.clj @@ -0,0 +1,32 @@ +(ns k16.kmono.cp-test + (:require + [clojure.test :refer [deftest is use-fixtures]] + [k16.kmono.core.config :as core.config] + [k16.kmono.core.packages :as core.packages] + [k16.kmono.cp :as kmono.cp] + [k16.kmono.test.helpers.repo :refer [*repo* with-test-repo] :as helpers.repo] + [matcher-combinators.test])) + +(use-fixtures :once with-test-repo) + +(deftest generate-cp-command-test + (let [config (core.config/resolve-workspace-config *repo*) + packages (core.packages/resolve-packages *repo* config) + + cmd (kmono.cp/generate-classpath-command *repo* config packages)] + + (is (= "clojure -Sdeps '{:aliases {:kmono/packages {:extra-deps #:com.kepler16{a #:local{:root \"packages/a\"}, b #:local{:root \"packages/b\"}}}, :a/test {:extra-paths [\"packages/a/test\"], :extra-deps #:local{excluded #:local{:root \"packages/excluded\"}}}}}' -A:kmono/packages -Spath" + cmd)))) + +(deftest generate-cp-command-with-aliases-test + (let [config (core.config/resolve-workspace-config *repo*) + packages (core.packages/resolve-packages *repo* config) + + cmd (kmono.cp/generate-classpath-command + *repo* + (merge config + {:aliases [:local]}) + packages)] + + (is (= "clojure -Sdeps '{:aliases {:kmono/packages {:extra-deps #:com.kepler16{a #:local{:root \"packages/a\"}, b #:local{:root \"packages/b\"}}}, :a/test {:extra-paths [\"packages/a/test\"], :extra-deps #:local{excluded #:local{:root \"packages/excluded\"}}}}}' -A:kmono/packages:local -Spath" + cmd)))) diff --git a/packages/kmono-exec/deps.edn b/packages/kmono-exec/deps.edn new file mode 100644 index 0000000..d341beb --- /dev/null +++ b/packages/kmono-exec/deps.edn @@ -0,0 +1,3 @@ +{:kmono/package {} + + :deps {com.kepler16/kmono-core {:local/root "../kmono-core"}}} diff --git a/packages/kmono-exec/src/k16/kmono/exec.clj b/packages/kmono-exec/src/k16/kmono/exec.clj new file mode 100644 index 0000000..c70c4c2 --- /dev/null +++ b/packages/kmono-exec/src/k16/kmono/exec.clj @@ -0,0 +1,101 @@ +(ns k16.kmono.exec + (:require + [babashka.process :as proc] + [clojure.string :as str] + [k16.kmono.core.graph :as core.graph] + [k16.kmono.core.schema :as core.schema]) + (:import + java.util.concurrent.Semaphore)) + +(def ?ProcsResult + [:vector + [:map-of + :string + [:map + [:success? :boolean] + [:output :string]]]]) + +(def ?JobResult + [:tuple :boolean ?ProcsResult]) + +(defn- run-external-cmd [{:keys [package command on-event]}] + (on-event {:type :proc-start + :package package + :command command}) + + (let [result (proc/sh {:dir (:absolute-path package)} + (str/join " " command))] + + (on-event {:type :proc-finish + :success (= 0 (:exit result)) + :exit (:exit result) + :out (:out result) + :err (:err result) + :package package + :command command}) + + (assoc result :package package))) + +(defn- await-procs [procs] + (let [results (mapv deref procs) + failed? (some + (fn [result] + (not= 0 (:exit result))) + results)] + + [(not failed?) results])) + +(defn run-external-cmds + {:malli/schema [:=> [:cat core.schema/?PackageMap] ?JobResult]} + [{:keys [packages command concurrency ordered on-event]}] + (let [exec-order (if (or (not (boolean? ordered)) + ordered) + (core.graph/parallel-topo-sort packages) + [(keys packages)]) + + cores (.availableProcessors (Runtime/getRuntime)) + semaphore (Semaphore. (or concurrency cores)) + + total-stages (count exec-order)] + + (loop [stages exec-order + idx 1 + stage-results []] + (if (seq stages) + (let [stage (first stages) + + _ (on-event {:type :stage-start + :id idx + :total total-stages + :stage stage}) + + op-procs + (mapv + (fn [pkg-name] + (future + (.acquire semaphore) + + (try + (let [pkg (get packages pkg-name)] + (run-external-cmd + {:package pkg + :command (if (fn? command) + (command pkg) + command) + :on-event on-event})) + (finally + (.release semaphore))))) + stage) + + [success? results] (await-procs op-procs)] + + (on-event {:type :stage-finish + :stage stage + :success success? + :results results}) + + (recur (rest stages) + (inc idx) + (conj stage-results {:success success? + :results results}))) + stage-results)))) diff --git a/packages/kmono-git/deps.edn b/packages/kmono-git/deps.edn new file mode 100644 index 0000000..b26c9a9 --- /dev/null +++ b/packages/kmono-git/deps.edn @@ -0,0 +1,10 @@ +{:kmono/package {} + + :deps {babashka/process {:mvn/version "0.5.22"} + babashka/fs {:mvn/version "0.5.22"}} + + :aliases {:test {:extra-paths ["test"] + :extra-deps {lambdaisland/kaocha {:mvn/version "1.91.1392"} + nubank/matcher-combinators {:mvn/version "3.9.1"} + local/kmono-test {:local/root "../kmono-test"}} + :main-opts ["-m" "kaocha.runner" "-c" "../../tests.edn"]}}} diff --git a/packages/kmono-git/src/k16/kmono/git.clj b/packages/kmono-git/src/k16/kmono/git.clj new file mode 100644 index 0000000..a50c0f1 --- /dev/null +++ b/packages/kmono-git/src/k16/kmono/git.clj @@ -0,0 +1,33 @@ +(ns k16.kmono.git + (:require + [babashka.fs :as fs] + [babashka.process :as proc] + [clojure.string :as string])) + +(set! *warn-on-reflection* true) + +(defn- -is-git-repo? [repo-root] + (let [git-dir (fs/file repo-root ".git")] + (and (fs/exists? git-dir) + (fs/directory? git-dir)))) + +(def is-git-repo? + (memoize -is-git-repo?)) + +(defn- out->strings + [{:keys [out err exit] :as result}] + (when-not (= 0 exit) + (throw (ex-info err (select-keys result [:out :err :exit])))) + + (let [out (string/trim out)] + (when (seq out) + (-> out + (string/split-lines) + (vec))))) + +(defn run-cmd! [dir & cmd] + (-> (proc/shell {:dir (str dir) + :out :string + :err :string} + (string/join " " (filter identity cmd))) + (out->strings))) diff --git a/packages/kmono-git/src/k16/kmono/git/commit.clj b/packages/kmono-git/src/k16/kmono/git/commit.clj new file mode 100644 index 0000000..b83e5f3 --- /dev/null +++ b/packages/kmono-git/src/k16/kmono/git/commit.clj @@ -0,0 +1,26 @@ +(ns k16.kmono.git.commit + (:require + [clojure.string :as str] + [k16.kmono.git :as git])) + +(set! *warn-on-reflection* true) + +(defn get-current-commit [repo] + (first + (git/run-cmd! repo "git rev-parse HEAD"))) + +(defn get-current-commit-short [repo] + (subs (get-current-commit repo) 0 7)) + +(defn find-commits-since [repo {:keys [ref subdir]}] + (let [subdir (when subdir + (str "-- " subdir))] + (git/run-cmd! repo + "git log --pretty=format:\"%H\"" + (when ref (str ref "..HEAD")) + subdir))) + +(defn get-commit-message [repo sha] + (let [res (git/run-cmd! repo "git show -s --format=%B" sha)] + {:message (first res) + :body (str/join \newline (rest res))})) diff --git a/packages/kmono-git/src/k16/kmono/git/files.clj b/packages/kmono-git/src/k16/kmono/git/files.clj new file mode 100644 index 0000000..7b869f8 --- /dev/null +++ b/packages/kmono-git/src/k16/kmono/git/files.clj @@ -0,0 +1,10 @@ +(ns k16.kmono.git.files + (:require + [k16.kmono.git :as git])) + +(set! *warn-on-reflection* true) + +(defn find-changed-files-since [repo {:keys [ref subdir]}] + (let [subdir (when subdir + (str "-- " subdir))] + (git/run-cmd! repo "git diff --name-only" (str ref "..HEAD") subdir))) diff --git a/packages/kmono-git/src/k16/kmono/git/tags.clj b/packages/kmono-git/src/k16/kmono/git/tags.clj new file mode 100644 index 0000000..218f3a0 --- /dev/null +++ b/packages/kmono-git/src/k16/kmono/git/tags.clj @@ -0,0 +1,24 @@ +(ns k16.kmono.git.tags + (:require + [k16.kmono.git :as git.cmd] + [k16.kmono.git.commit :as git.commit])) + +(set! *warn-on-reflection* true) + +(defn get-sorted-tags + "Returns all tags that are present in the current or any ancestor of + the given `sha`. + + The returned tags are sorted by commit date (descending, most recent commit first)" + ([repo-root] + (get-sorted-tags repo-root (git.commit/get-current-commit repo-root))) + ([repo-root ref] + (git.cmd/run-cmd! repo-root "git" "tag" "--merged" ref "--sort=-committerdate"))) + +(defn get-all-tags-for-ref [repo-root ref] + (git.cmd/run-cmd! repo-root "git" "tag" "--points-at" ref)) + +(defn create-tags + [repo-root {:keys [ref tags]}] + (doseq [tag tags] + (git.cmd/run-cmd! repo-root "git" "tag" tag ref))) diff --git a/packages/kmono-git/test/k16/kmono/git/commit_test.clj b/packages/kmono-git/test/k16/kmono/git/commit_test.clj new file mode 100644 index 0000000..336f687 --- /dev/null +++ b/packages/kmono-git/test/k16/kmono/git/commit_test.clj @@ -0,0 +1,71 @@ +(ns k16.kmono.git.commit-test + (:require + [babashka.fs :as fs] + [clojure.test :refer [deftest is use-fixtures]] + [k16.kmono.git.commit :as git.commit] + [k16.kmono.test.helpers.commit :refer [commit]] + [k16.kmono.test.helpers.repo :refer [*repo* with-test-repo] :as helpers.repo] + [matcher-combinators.test])) + +(use-fixtures :each with-test-repo) + +(defn prepare-repo [] + (let [root-commit (git.commit/get-current-commit *repo*) + + _ (fs/create-file (fs/file *repo* "packages/a/change-1")) + _ (fs/create-file (fs/file *repo* "packages/b/change-1")) + + commit-1 (commit *repo* "change-1") + + _ (fs/create-file (fs/file *repo* "packages/a/change-2")) + _ (fs/create-file (fs/file *repo* "packages/b/change-2")) + + _ (commit *repo* "change-2")] + + [root-commit commit-1])) + +(deftest query-commits-since-ref + (let [[root-commit commit-1] (prepare-repo) + + files-since-root + (git.commit/find-commits-since + *repo* {:ref root-commit}) + + files-since-change-1 + (git.commit/find-commits-since + *repo* {:ref commit-1})] + + (is (match? [#"[a-f0-9]{40}" + #"[a-f0-9]{40}"] + files-since-root)) + + (is (match? [#"[a-f0-9]{40}"] + files-since-change-1)))) + +(deftest query-commits-since-ref-in-subdir + (let [[root-commit commit-1] (prepare-repo) + + files-since-root-a + (git.commit/find-commits-since + *repo* {:ref root-commit + :subdir "packages/a"}) + + files-since-change-1-b + (git.commit/find-commits-since + *repo* {:ref commit-1 + :subdir "packages/b"})] + + (is (match? [#"[a-f0-9]{40}" + #"[a-f0-9]{40}"] + files-since-root-a)) + + (is (match? [#"[a-f0-9]{40}"] + files-since-change-1-b)))) + +(deftest query-commit-message + (let [commit-sha (commit *repo* "this is the message\nthis is\nthe\nbody") + commit (git.commit/get-commit-message *repo* commit-sha)] + + (is (match? {:message "this is the message" + :body "this is\nthe\nbody"} + commit)))) diff --git a/packages/kmono-git/test/k16/kmono/git/files_test.clj b/packages/kmono-git/test/k16/kmono/git/files_test.clj new file mode 100644 index 0000000..0395cc5 --- /dev/null +++ b/packages/kmono-git/test/k16/kmono/git/files_test.clj @@ -0,0 +1,66 @@ +(ns k16.kmono.git.files-test + (:require + [babashka.fs :as fs] + [clojure.test :refer [deftest is use-fixtures]] + [k16.kmono.git.commit :as git.commit] + [k16.kmono.git.files :as git.files] + [k16.kmono.test.helpers.commit :refer [commit]] + [k16.kmono.test.helpers.repo :refer [*repo* with-test-repo] :as helpers.repo])) + +(use-fixtures :each with-test-repo) + +(defn prepare-repo [] + (let [root-commit (git.commit/get-current-commit *repo*) + + _ (fs/create-file (fs/file *repo* "packages/a/change-1")) + _ (fs/create-file (fs/file *repo* "packages/b/change-1")) + + commit-1 (commit *repo* "change-1") + + _ (fs/create-file (fs/file *repo* "packages/a/change-2")) + _ (fs/create-file (fs/file *repo* "packages/b/change-2")) + + _ (commit *repo* "change-2")] + + [root-commit commit-1])) + +(deftest query-files-since-ref + (let [[root-commit commit-1] (prepare-repo) + + files-since-root + (git.files/find-changed-files-since + *repo* {:ref root-commit}) + + files-since-change-1 + (git.files/find-changed-files-since + *repo* {:ref commit-1})] + + (is (= ["packages/a/change-1" + "packages/a/change-2" + "packages/b/change-1" + "packages/b/change-2"] + files-since-root)) + + (is (= ["packages/a/change-2" + "packages/b/change-2"] + files-since-change-1)))) + +(deftest query-files-since-ref-in-subdir + (let [[root-commit commit-1] (prepare-repo) + + files-since-root-a + (git.files/find-changed-files-since + *repo* {:ref root-commit + :subdir "packages/a"}) + + files-since-change-1-b + (git.files/find-changed-files-since + *repo* {:ref commit-1 + :subdir "packages/b"})] + + (is (= ["packages/a/change-1" + "packages/a/change-2"] + files-since-root-a)) + + (is (= ["packages/b/change-2"] + files-since-change-1-b)))) diff --git a/packages/kmono-git/test/k16/kmono/git/tag_test.clj b/packages/kmono-git/test/k16/kmono/git/tag_test.clj new file mode 100644 index 0000000..bd366c1 --- /dev/null +++ b/packages/kmono-git/test/k16/kmono/git/tag_test.clj @@ -0,0 +1,35 @@ +(ns k16.kmono.git.tag-test + (:require + [clojure.test :refer [deftest is use-fixtures]] + [k16.kmono.git :as git] + [k16.kmono.git.commit :as git.commit] + [k16.kmono.git.tags :as git.tags] + [k16.kmono.test.helpers.commit :refer [commit]] + [k16.kmono.test.helpers.repo :refer [*repo* with-test-repo] :as helpers.repo])) + +(use-fixtures :each with-test-repo) + +(deftest query-ordered-tags + (let [initial-commit (git.commit/get-current-commit *repo*)] + ;; Git commit timestamps have a 1s resolution. To get stable sorting + ;; we need to wait at least 1s. + (Thread/sleep 1000) + + (git.tags/create-tags *repo* {:ref initial-commit + :tags ["a"]}) + + (commit *repo* "change-1") + + (git.tags/create-tags *repo* {:ref (git.commit/get-current-commit *repo*) + :tags ["b-1"]}) + + (git/run-cmd! *repo* "git" "checkout" initial-commit) + + (commit *repo* "change-2") + + (git.tags/create-tags *repo* {:ref (git.commit/get-current-commit *repo*) + :tags ["b-2"]}) + + (let [tags (git.tags/get-sorted-tags *repo* (git.commit/get-current-commit *repo*))] + + (is (= ["b-2" "a"] tags))))) diff --git a/packages/kmono-log/deps.edn b/packages/kmono-log/deps.edn new file mode 100644 index 0000000..8712234 --- /dev/null +++ b/packages/kmono-log/deps.edn @@ -0,0 +1,3 @@ +{:kmono/package {} + + :deps {jansi-clj/jansi-clj {:mvn/version "1.0.3"}}} diff --git a/packages/kmono-log/src/k16/kmono/log.clj b/packages/kmono-log/src/k16/kmono/log.clj new file mode 100644 index 0000000..eb34e11 --- /dev/null +++ b/packages/kmono-log/src/k16/kmono/log.clj @@ -0,0 +1,22 @@ +(ns k16.kmono.log + (:require + [jansi-clj.core :as color])) + +(def ^:private lock + (Object.)) + +(defn log-raw [data] + (locking lock + (.println System/out data))) + +(defn log [& msg] + (log-raw (apply color/render msg))) + +(defn info [msg] + (log (str "@|blue [I] " "|@") (color/render msg))) + +(defn error [msg] + (log (str "@|red [E] " "|@") (color/render msg))) + +(defn debug [msg] + (log (str "@|white [D] " (color/render msg) "|@"))) diff --git a/packages/kmono-log/src/k16/kmono/log/render.clj b/packages/kmono-log/src/k16/kmono/log/render.clj new file mode 100644 index 0000000..1544661 --- /dev/null +++ b/packages/kmono-log/src/k16/kmono/log/render.clj @@ -0,0 +1,6 @@ +(ns k16.kmono.log.render) + +(defn render-package-name [pkg] + (str "@|white " (namespace pkg) "|@" + "@|bold /|@" + "@|yellow " (name pkg) "|@")) diff --git a/packages/kmono-test/deps.edn b/packages/kmono-test/deps.edn new file mode 100644 index 0000000..892a854 --- /dev/null +++ b/packages/kmono-test/deps.edn @@ -0,0 +1,4 @@ +{:paths ["src" "resources"] + + :deps {babashka/fs {:mvn/version "0.5.22"} + babashka/process {:mvn/version "0.5.22"}}} diff --git a/packages/kmono-test/resources/kmono/templates/monorepo/deps.edn b/packages/kmono-test/resources/kmono/templates/monorepo/deps.edn new file mode 100644 index 0000000..c6e0655 --- /dev/null +++ b/packages/kmono-test/resources/kmono/templates/monorepo/deps.edn @@ -0,0 +1 @@ +{:kmono/workspace {:group com.kepler16}} diff --git a/packages/kmono-test/resources/kmono/templates/monorepo/packages/a/deps.edn b/packages/kmono-test/resources/kmono/templates/monorepo/packages/a/deps.edn new file mode 100644 index 0000000..4562e6c --- /dev/null +++ b/packages/kmono-test/resources/kmono/templates/monorepo/packages/a/deps.edn @@ -0,0 +1,4 @@ +{:kmono/package {} + + :aliases {:test {:extra-paths ["test"] + :extra-deps {local/excluded {:local/root "../excluded"}}}}} diff --git a/packages/kmono-test/resources/kmono/templates/monorepo/packages/b/deps.edn b/packages/kmono-test/resources/kmono/templates/monorepo/packages/b/deps.edn new file mode 100644 index 0000000..4d70c12 --- /dev/null +++ b/packages/kmono-test/resources/kmono/templates/monorepo/packages/b/deps.edn @@ -0,0 +1,5 @@ +{:kmono/package {} + + :deps {a/b {:mvn/version "RELEASE"} + local/excluded {:local/root "../excluded"} + com.kepler16/a {:local/root "../a"}}} diff --git a/packages/kmono-test/resources/kmono/templates/monorepo/packages/excluded/deps.edn b/packages/kmono-test/resources/kmono/templates/monorepo/packages/excluded/deps.edn new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/packages/kmono-test/resources/kmono/templates/monorepo/packages/excluded/deps.edn @@ -0,0 +1 @@ +{} diff --git a/packages/kmono-test/src/k16/kmono/test/helpers/cmd.clj b/packages/kmono-test/src/k16/kmono/test/helpers/cmd.clj new file mode 100644 index 0000000..0e1931f --- /dev/null +++ b/packages/kmono-test/src/k16/kmono/test/helpers/cmd.clj @@ -0,0 +1,24 @@ +(ns k16.kmono.test.helpers.cmd + (:require + [babashka.process :as proc] + [clojure.string :as string])) + +(set! *warn-on-reflection* true) + +(defn- out->strings + [{:keys [out err exit] :as result}] + (when-not (= 0 exit) + (throw (ex-info err (select-keys result [:out :err :exit])))) + + (let [out (string/trim out)] + (when (seq out) + (-> out + (string/split-lines) + (vec))))) + +(defn run-cmd! [dir & cmd] + (-> (proc/shell {:dir (str dir) + :out :string + :err :string} + (string/join " " (filter identity cmd))) + (out->strings))) diff --git a/packages/kmono-test/src/k16/kmono/test/helpers/commit.clj b/packages/kmono-test/src/k16/kmono/test/helpers/commit.clj new file mode 100644 index 0000000..31d0fec --- /dev/null +++ b/packages/kmono-test/src/k16/kmono/test/helpers/commit.clj @@ -0,0 +1,14 @@ +(ns k16.kmono.test.helpers.commit + (:require + [k16.kmono.test.helpers.cmd :as cmd])) + +(defn- get-current-commit [repo] + (first + (cmd/run-cmd! repo "git rev-parse HEAD"))) + +(defn commit + ([repo] (commit repo "commit")) + ([repo message] + (cmd/run-cmd! repo "git add .") + (cmd/run-cmd! repo "git commit --allow-empty" "-m" (str "'" message "'")) + (get-current-commit repo))) diff --git a/packages/kmono-test/src/k16/kmono/test/helpers/repo.clj b/packages/kmono-test/src/k16/kmono/test/helpers/repo.clj new file mode 100644 index 0000000..d127393 --- /dev/null +++ b/packages/kmono-test/src/k16/kmono/test/helpers/repo.clj @@ -0,0 +1,31 @@ +(ns k16.kmono.test.helpers.repo + (:require + [babashka.fs :as fs] + [clojure.java.io :as io] + [k16.kmono.test.helpers.cmd :as cmd])) + +(defn setup-multi-package-repo [] + (let [uuid (str (random-uuid)) + + repo (fs/file (str ".test-repos/" uuid))] + (fs/create-dirs repo) + (fs/copy-tree (io/resource "kmono/templates/monorepo") + repo) + + (cmd/run-cmd! repo "git" "init") + (cmd/run-cmd! repo "git" "add" ".") + (cmd/run-cmd! repo "git" "commit" "-m" "init") + + (cmd/run-cmd! repo "git config advice.detachedHead false") + + (.getAbsolutePath repo))) + +(def ^:dynamic *repo* nil) + +(defn with-test-repo [test] + (let [repo (setup-multi-package-repo)] + (try + (binding [*repo* repo] + (test)) + (finally + (fs/delete-tree repo))))) diff --git a/packages/kmono-version/deps.edn b/packages/kmono-version/deps.edn new file mode 100644 index 0000000..32fb33d --- /dev/null +++ b/packages/kmono-version/deps.edn @@ -0,0 +1,10 @@ +{:kmono/package {} + + :deps {com.kepler16/kmono-core {:local/root "../kmono-core"}} + + :aliases {:test {:extra-paths ["test"] + :extra-deps {lambdaisland/kaocha {:mvn/version "1.91.1392"} + nubank/matcher-combinators {:mvn/version "3.9.1"} + local/kmono-test {:local/root "../kmono-test"} + local/kmono-git {:local/root "../kmono-git"}} + :main-opts ["-m" "kaocha.runner" "-c" "../../tests.edn"]}}} diff --git a/packages/kmono-version/src/k16/kmono/version.clj b/packages/kmono-version/src/k16/kmono/version.clj new file mode 100644 index 0000000..045d412 --- /dev/null +++ b/packages/kmono-version/src/k16/kmono/version.clj @@ -0,0 +1,103 @@ +(ns k16.kmono.version + (:require + [clojure.string :as str] + [k16.kmono.core.graph :as core.graph] + [k16.kmono.core.schema :as core.schema] + [k16.kmono.git.commit :as git.commit] + [k16.kmono.git.tags :as git.tags] + [k16.kmono.version.semver :as semver])) + +(def ?VersionOp + [:enum nil :patch :minor :major]) + +(defn resolve-package-versions + {:malli/schema [:=> [:cat :string core.schema/?PackageMap] core.schema/?PackageMap]} + [project-root packages] + (let [tags (git.tags/get-sorted-tags project-root)] + (->> packages + (reduce + (fn [packages [pkg-name pkg]] + (let [escaped-name (str/replace (str pkg-name) #"\." "\\.") + pattern (re-pattern (str escaped-name "@(.*)")) + latest (some + (fn [tag] + (when-let [[_ version] (re-matches pattern tag)] + version)) + tags) + pkg (assoc pkg :version latest)] + (assoc! packages pkg-name pkg))) + (transient {})) + persistent!))) + +(defn resolve-package-changes + {:malli/schema [:=> [:cat :string core.schema/?PackageMap] core.schema/?PackageMap]} + [project-root packages] + (->> packages + (reduce + (fn [packages [pkg-name pkg]] + (let [commits (git.commit/find-commits-since + project-root {:ref (when (:version pkg) + (str pkg-name "@" (:version pkg))) + :subdir (:relative-path pkg)}) + + commits (pmap + (fn [commit-sha] + (git.commit/get-commit-message project-root commit-sha)) + commits) + + changed? (or (not (:version pkg)) + (seq commits)) + + pkg (cond-> pkg + changed? (assoc :dirty true + :commits commits))] + + (assoc! packages pkg-name pkg))) + (transient {})) + persistent!)) + +(defn- version-changed? [packages-a packages-b pkg-name] + (not= (get-in packages-a [pkg-name :version]) + (get-in packages-b [pkg-name :version]))) + +(defn inc-package-versions + {:malli/schema + [:function + [:=> [:cat ifn? core.schema/?PackageMap] core.schema/?PackageMap] + [:=> [:cat ifn? [:maybe :string] core.schema/?PackageMap] core.schema/?PackageMap]]} + ([version-fn packages] (inc-package-versions version-fn nil packages)) + ([version-fn suffix packages] + (let [bumped + (->> packages + (reduce + (fn [packages [pkg-name pkg]] + (let [inc-type (version-fn pkg) + current-version (or (:version pkg) "0.0.0") + version (semver/inc-version + current-version + inc-type + suffix) + pkg (assoc pkg :version version)] + (assoc! packages pkg-name pkg))) + (transient {})) + persistent!)] + + (reduce + (fn [bumped [pkg-name]] + (if (version-changed? packages bumped pkg-name) + (let [dependents (core.graph/query-dependents packages pkg-name)] + (reduce + (fn [bumped dependent] + (if-not (version-changed? packages bumped dependent) + (update-in bumped + [dependent :version] + (fn [version] + (semver/inc-version version :patch suffix))) + bumped)) + + bumped + dependents)) + bumped)) + + bumped + bumped)))) diff --git a/packages/kmono-version/src/k16/kmono/version/alg/semantic.clj b/packages/kmono-version/src/k16/kmono/version/alg/semantic.clj new file mode 100644 index 0000000..8e440c1 --- /dev/null +++ b/packages/kmono-version/src/k16/kmono/version/alg/semantic.clj @@ -0,0 +1,47 @@ +(ns k16.kmono.version.alg.semantic + (:require + [clojure.string :as str])) + +(def ^:private commit-pattern + #"^(?\w+)(?:\([^\)]+\))?(?!?):\s*(?.*)$") + +(defn ^:private match-commit [commit] + (let [[_ type breaking] (re-matches commit-pattern (:message commit)) + contains-breaking (str/includes? (:body commit) "BREAKING CHANGE:")] + {:type type + :breaking (or contains-breaking + (= "!" breaking))})) + +(def ^:private commit-type->version-type + {:fix :patch + :feat :minor}) + +(def ^:private version-type->weight + {:patch 1 + :minor 2 + :major 3}) + +(defn version-type [package] + (reduce + (fn [current-version-type commit] + (let [match (match-commit commit) + version-type (if (:breaking match) + :major + (when (:type match) + ((keyword (:type match)) commit-type->version-type)))] + + (cond + (not version-type) + current-version-type + + (not current-version-type) + version-type + + (> (version-type version-type->weight) (current-version-type version-type->weight)) + version-type + + :else + current-version-type))) + + nil + (:commits package))) diff --git a/packages/kmono-version/src/k16/kmono/version/semver.clj b/packages/kmono-version/src/k16/kmono/version/semver.clj new file mode 100644 index 0000000..6f12a57 --- /dev/null +++ b/packages/kmono-version/src/k16/kmono/version/semver.clj @@ -0,0 +1,40 @@ +(ns k16.kmono.version.semver + (:require + [clojure.string :as str])) + +(def version-pattern + (re-pattern #"(?:(?:[^\d]*))(\d+)\.(\d+)\.(\d+)(?:\.(\d+))?(?:[^\d].*)?")) + +(defn parse-version-string [version] + (when-let [[_ major minor patch build] + (re-matches version-pattern version)] + {:major (parse-long major) + :minor (parse-long minor) + :patch (parse-long patch) + :build (when build (parse-long build))})) + +(defn inc-version + ([version inc-type] (inc-version version inc-type nil)) + ([version inc-type suffix] + (if-let [{:keys [major minor patch build]} + (parse-version-string version)] + (let [new-version (case inc-type + :major [(inc major) 0 0 0] + :minor [major (inc minor) 0 0] + :patch [major minor (inc patch) 0] + :build [major minor patch (inc (or build 0))] + [major minor patch build]) + + new-version (if (or build + (= :build inc-type)) + new-version + (drop-last new-version)) + + version-string (str/join "." new-version)] + + (if suffix + (str version-string "-" suffix) + version-string)) + + (throw (ex-info "Version does not match pattern `major.minor.patch[.build]`" + {:body (str "version: " version)}))))) diff --git a/packages/kmono-version/test/k16/kmono/alg/semantic_test.clj b/packages/kmono-version/test/k16/kmono/alg/semantic_test.clj new file mode 100644 index 0000000..fb0dbb4 --- /dev/null +++ b/packages/kmono-version/test/k16/kmono/alg/semantic_test.clj @@ -0,0 +1,46 @@ +(ns k16.kmono.alg.semantic-test + (:require + [clojure.test :refer [deftest is]] + [k16.kmono.version.alg.semantic :as semantic])) + +(deftest empty-commits-test + (let [pkg {:commits []}] + (is (= nil (semantic/version-type pkg))))) + +(deftest patch-test + (let [pkg {:commits [{:message "fix: message" + :body ""}]}] + (is (= :patch (semantic/version-type pkg))))) + +(deftest minor-test + (let [pkg {:commits [{:message "feat: message" + :body ""}]}] + (is (= :minor (semantic/version-type pkg))))) + +(deftest major-bang-test + (let [pkg {:commits [{:message "feat!: message" + :body ""}]}] + (is (= :major (semantic/version-type pkg))))) + +(deftest major-bang-with-scope-test + (let [pkg {:commits [{:message "feat(api)!: message" + :body ""}]}] + (is (= :major (semantic/version-type pkg))))) + +(deftest major-body-test + (let [pkg {:commits [{:message "feat: message" + :body "The body\n\nBREAKING CHANGE: Changed API"}]}] + (is (= :major (semantic/version-type pkg))))) + +(deftest largest-type-test + (let [pkg {:commits [{:message "fix: message" + :body ""} + {:message "feat: message" + :body ""}]}] + + (is (= :minor (semantic/version-type pkg))))) + +(deftest non-semantic-commits-test + (let [pkg {:commits [{:message "message" + :body ""}]}] + (is (= nil (semantic/version-type pkg))))) diff --git a/packages/kmono-version/test/k16/kmono/version_test.clj b/packages/kmono-version/test/k16/kmono/version_test.clj new file mode 100644 index 0000000..ac59fa2 --- /dev/null +++ b/packages/kmono-version/test/k16/kmono/version_test.clj @@ -0,0 +1,93 @@ +(ns k16.kmono.version-test + (:require + [babashka.fs :as fs] + [clojure.test :refer [deftest is use-fixtures]] + [k16.kmono.core.config :as core.config] + [k16.kmono.core.packages :as core.packages] + [k16.kmono.git.tags :as git.tags] + [k16.kmono.test.helpers.commit :refer [commit]] + [k16.kmono.test.helpers.repo :refer [*repo* with-test-repo] :as helpers.repo] + [k16.kmono.version :as kmono.version] + [k16.kmono.version.alg.semantic :as semantic] + [matcher-combinators.matchers :as matchers] + [matcher-combinators.test])) + +(use-fixtures :each with-test-repo) + +(deftest no-package-versions-test + (let [config (core.config/resolve-workspace-config *repo*) + packages (core.packages/resolve-packages *repo* config)] + (is (match? {'com.kepler16/a {:version nil} + 'com.kepler16/b {:version nil}} + (kmono.version/resolve-package-versions *repo* packages))))) + +(deftest load-package-versions-test + (git.tags/create-tags *repo* {:tags ["com.kepler16/a@1.0.0" + "com.kepler16/b@1.1.0"]}) + + (let [config (core.config/resolve-workspace-config *repo*) + packages (core.packages/resolve-packages *repo* config)] + (is (match? {'com.kepler16/a {:version "1.0.0"} + 'com.kepler16/b {:version "1.1.0"}} + (kmono.version/resolve-package-versions *repo* packages))))) + +(deftest load-package-changes-test + (git.tags/create-tags *repo* {:tags ["com.kepler16/a@1.0.0" + "com.kepler16/b@1.1.0"]}) + + (fs/create-file (fs/file *repo* "packages/a/change-1")) + (commit *repo* "fix: changed package a") + + (let [config (core.config/resolve-workspace-config *repo*) + packages (->> (core.packages/resolve-packages *repo* config) + (kmono.version/resolve-package-versions *repo*) + (kmono.version/resolve-package-changes *repo*))] + (is (match? {'com.kepler16/a {:version "1.0.0" + :commits [{:message "fix: changed package a" + :body ""}]} + 'com.kepler16/b {:version "1.1.0" + :commits matchers/absent}} + packages)))) + +(deftest load-package-changes-no-version-test + + (let [config (core.config/resolve-workspace-config *repo*) + packages (->> (core.packages/resolve-packages *repo* config) + (kmono.version/resolve-package-changes *repo*))] + (is (match? {'com.kepler16/a {:commits [{:message "init" + :body ""}]} + 'com.kepler16/b {:commits [{:message "init" + :body ""}]}} + packages)))) + +(deftest inc-package-versions-test + (git.tags/create-tags *repo* {:tags ["com.kepler16/a@1.0.0" + "com.kepler16/b@1.1.0"]}) + + (fs/create-file (fs/file *repo* "packages/b/change-1")) + (commit *repo* "fix: changed package b") + + (let [config (core.config/resolve-workspace-config *repo*) + packages (->> (core.packages/resolve-packages *repo* config) + (kmono.version/resolve-package-versions *repo*) + (kmono.version/resolve-package-changes *repo*) + (kmono.version/inc-package-versions semantic/version-type))] + (is (match? {'com.kepler16/a {:version "1.0.0"} + 'com.kepler16/b {:version "1.1.1"}} + packages)))) + +(deftest inc-dependent-package-versions-test + (git.tags/create-tags *repo* {:tags ["com.kepler16/a@1.0.0" + "com.kepler16/b@1.1.0"]}) + + (fs/create-file (fs/file *repo* "packages/a/change-1")) + (commit *repo* "fix: changed package a") + + (let [config (core.config/resolve-workspace-config *repo*) + packages (->> (core.packages/resolve-packages *repo* config) + (kmono.version/resolve-package-versions *repo*) + (kmono.version/resolve-package-changes *repo*) + (kmono.version/inc-package-versions semantic/version-type))] + (is (match? {'com.kepler16/a {:version "1.0.1"} + 'com.kepler16/b {:version "1.1.1"}} + packages)))) diff --git a/src/k16/kmono/adapter.clj b/src/k16/kmono/adapter.clj deleted file mode 100644 index e2affef..0000000 --- a/src/k16/kmono/adapter.clj +++ /dev/null @@ -1,15 +0,0 @@ -(ns k16.kmono.adapter - (:require - [babashka.fs :as fs])) - -(defprotocol Adapter - (get-managed-deps [this] "Returns a list (package names) of repo-local dependencies for package") - (prepare-deps-env [this changes] "Returns an env string representing deps overrides for build/release") - (release-published? [this version] "Returns a promise containing boolean value") - (get-kmono-config [this] "Returns a kmono config for package")) - -(defn ensure-artifact - [config package-path] - (if-not (:artifact config) - (assoc config :artifact (symbol (fs/file-name package-path))) - config)) diff --git a/src/k16/kmono/adapters/clojure_deps.clj b/src/k16/kmono/adapters/clojure_deps.clj deleted file mode 100644 index b0a2e2f..0000000 --- a/src/k16/kmono/adapters/clojure_deps.clj +++ /dev/null @@ -1,90 +0,0 @@ -(ns k16.kmono.adapters.clojure-deps - (:require - [babashka.fs :as fs] - [clojure.tools.deps.extensions :as deps.ext] - [clojure.tools.deps.extensions.maven] - [clojure.tools.deps.util.maven :as deps.util.maven] - [clojure.tools.deps.util.session :as deps.util.session] - [k16.kmono.adapter :as adapter :refer [Adapter]] - [k16.kmono.util :as util])) - -(defn- local? - [[_ coord]] - (boolean (:local/root coord))) - -(defn- get-local-deps - [config deps-edn] - (into - (->> deps-edn - :deps - (filter local?) - (mapv (comp str first))) - (when-let [manage-aliases (seq (:aliases config))] - (->> (map #(vals (select-keys % [:deps :extra-deps :replace-deps])) - (-> deps-edn - :aliases - (select-keys manage-aliases) - (vals))) - (flatten) - (apply merge) - (filter local?) - (map first))))) - -(defn read-pkg-deps! - [package-path] - (let [deps-file (fs/path package-path "deps.edn")] - (when (fs/exists? deps-file) - (util/read-deps-edn! deps-file)))) - -(defn ->adapter - ([package-path] - (->adapter package-path 10000)) - ([package-path timeout-ms] - (when-let [deps-edn (read-pkg-deps! package-path)] - (when-let [kmono-config (some-> deps-edn :kmono/package)] - (let [{:keys [group artifact] :as config} - (-> kmono-config - (adapter/ensure-artifact package-path)) - coord (str group "/" artifact) - managed-deps (get-local-deps config deps-edn)] - (reify Adapter - - (prepare-deps-env [_ changes] - (binding [*print-namespace-maps* false] - (pr-str {:deps - (into {} (map - (fn [dep] - [(symbol dep) - {:mvn/version - (get-in changes [dep :version])}])) - managed-deps)}))) - - (get-managed-deps [_] managed-deps) - - (get-kmono-config [_] config) - - (release-published? [_ version] - (let [fut (future - (let [;; ignore user's local repository cache - local-repo (str package-path "/.kmono/" artifact "/.m2")] - (try (deps.util.session/with-session - (let [;; ignoring user's machine local m2 repo - versions (->> (deps.ext/find-versions - (symbol coord) - nil - :mvn {:mvn/local-repo local-repo - :mvn/repos - (merge deps.util.maven/standard-repos - (:mvn/repos deps-edn))}) - (map :mvn/version) - (set))] - (contains? versions version))) - (finally - (try (fs/delete-tree local-repo) - (catch Throwable _)))))) - res (deref fut timeout-ms ::timed-out)] - (if (= ::timed-out res) - (throw (ex-info "Timed out requesting remote repository for version check" - {:lib coord - :version version})) - res))))))))) diff --git a/src/k16/kmono/adapters/kmono_edn.clj b/src/k16/kmono/adapters/kmono_edn.clj deleted file mode 100644 index 9b5d332..0000000 --- a/src/k16/kmono/adapters/kmono_edn.clj +++ /dev/null @@ -1,36 +0,0 @@ -(ns k16.kmono.adapters.kmono-edn - (:require - [babashka.fs :as fs] - [clojure.edn :as edn] - [clojure.string :as string] - [clojure.tools.deps.extensions.maven] - [k16.kmono.adapter :as adapter :refer [Adapter]])) - -(defn ->adapter - ([package-path] - (->adapter package-path 10000)) - ([package-path _] - (let [kmono-file (fs/file package-path "kmono.edn")] - (when (fs/exists? kmono-file) - (let [kmono-config (-> kmono-file - (slurp) - (edn/read-string)) - config (adapter/ensure-artifact kmono-config package-path) - managed-deps (get config :local-deps [])] - (reify Adapter - - (prepare-deps-env [_ changes] - (string/join - ";" - (map - (fn [dep] - (str (symbol dep) - "@" - (get-in changes [dep :version]))) - managed-deps))) - - (get-managed-deps [_] managed-deps) - - (get-kmono-config [_] config) - - (release-published? [_ _] false))))))) diff --git a/src/k16/kmono/ansi.clj b/src/k16/kmono/ansi.clj deleted file mode 100644 index d33d9e8..0000000 --- a/src/k16/kmono/ansi.clj +++ /dev/null @@ -1,70 +0,0 @@ -(ns k16.kmono.ansi - (:require - [clojure.string :as string])) - -(def ^:dynamic *logs-enabled* true) - -(defn red - [& chunks] - (str "\033[31m" - (apply str chunks) - "\033[0m")) - -(defn green - [& chunks] - (str "\033[32m" - (apply str chunks) - "\033[0m")) - -(defn cyan - [& chunks] - (str "\033[36m" - (apply str chunks) - "\033[0m")) - -(defn blue - [& chunks] - (str "\033[34m" - (apply str chunks) - "\033[0m")) - -(def ERROR_PREFIX (red "[ERROR]")) -(def SUCCESS_PREFIX (green "[OK]")) -(def INFO_PREFIX (blue "[INFO]")) - -(defn print-success - [& v] - (when *logs-enabled* - (apply println SUCCESS_PREFIX v))) - -(defn print-error - [& v] - (when *logs-enabled* - (apply println ERROR_PREFIX v))) - -(defn print-info - [& v] - (when *logs-enabled* - (apply println INFO_PREFIX v))) - -(defn assert-err! - [v msg] - (when-not v - (println ERROR_PREFIX msg) - (throw (ex-info msg {:type :errors/assertion})))) - -(defn print-raw - [output] - (when *logs-enabled* - (println output))) - -(defn print-shifted - [output] - (when *logs-enabled* - (->> output - (string/split-lines) - (map #(str "\t" %)) - (string/join "\n") - (println)) - (println))) - diff --git a/src/k16/kmono/api.clj b/src/k16/kmono/api.clj deleted file mode 100644 index bb40d87..0000000 --- a/src/k16/kmono/api.clj +++ /dev/null @@ -1,185 +0,0 @@ -(ns k16.kmono.api - (:require - [babashka.fs :as fs] - [clojure.string :as string] - [k16.kmono.ansi :as ansi] - [k16.kmono.config :as config] - [k16.kmono.config-schema :as schema] - [k16.kmono.exec :as exec] - [k16.kmono.git :as git] - [k16.kmono.repl.deps :as repl.deps] - [malli.core :as m] - [malli.transform :as mt])) - -(defmacro with-assertion-error - [& body] - `(try - ~@body - (catch clojure.lang.ExceptionInfo ex# - (let [data# (ex-data ex#)] - (when-not (= :errors/assertion (:type data#)) - (ansi/print-error (ex-message ex#))) - (when-let [body# (:body data#)] - (ansi/print-shifted (str body#))) - (System/exit 1))))) - -(defn- print-stage-results - [stage-results] - (doseq [stage-result stage-results] - (doseq [[pkg-name {:keys [success? output]}] stage-result] - (if success? - (ansi/print-success pkg-name) - (ansi/print-error pkg-name)) - (ansi/print-shifted output)))) - -(defn- run-build - [config changes] - (let [[success? stage-results] (exec/build config changes)] - (print-stage-results stage-results) - [success? stage-results])) - -(defn- run-custom-cmd - [config changes] - (ansi/assert-err! (:exec config) "custom command not specified") - (let [[_ stage-results] (exec/custom-command config changes) - success? (->> stage-results - (apply merge) - (vals) - (map :success?) - (filter not) - (seq) - (nil?))] - (print-stage-results stage-results) - [success? stage-results])) - -(defn- run-release - [config changes] - (ansi/print-info "releasing...") - (let [[_ stage-results] (exec/release config changes) - all-results (apply merge stage-results) - failed-releases (into {} - (filter (fn [[_ result]] - (not (:success? result)))) - all-results) - released-packages (into [] - (comp - (filter (fn [[_ result]] - (:success? result))) - (map first)) - all-results) - create-tags? (:create-tags? config) - snapshot? (:snapshot? config) - tags-to-create (->> released-packages - (map (fn [pkg-name] - (let [{:keys [changed? version]} (get changes pkg-name)] - (when (and create-tags? changed? (not snapshot?)) - (str pkg-name "@" version))))) - (remove nil?))] - (doseq [[pkg-name result] failed-releases] - (ansi/print-error pkg-name "failed to release") - (ansi/print-shifted (:output result))) - (if (seq tags-to-create) - (try - (ansi/print-info "creating tags for successful results") - (git/create-tags! config tags-to-create) - (git/push-tags! config) - (ansi/print-success "tags created and pushed") - (ansi/print-shifted (string/join "\n" tags-to-create)) - (catch Throwable ex - (ansi/print-error "creating and pushing git tags") - (ansi/print-shifted (ex-message ex)) - (ansi/print-shifted (:body (ex-data ex))))) - (ansi/print-info "no tags has been created")) - [(empty? failed-releases) stage-results])) - -(def ?RunOpts - [:map - [:exec [:or :string [:enum :build :release]]] - [:repo-root {:default "."} - :string] - [:glob {:default "packages/*"} - :string] - [:dry-run? {:default false} - :boolean] - [:snapshot? {:default true} - :boolean] - [:create-tags? {:default false} - :boolean] - [:include-unchanged? {:default true} - :boolean] - [:build-cmd {:optional true} - [:maybe :string]] - [:release-cmd {:optional true} - [:maybe :string]]]) - -(defn arg->exec - [[_ exec-cmd]] - (if (or (= "build" exec-cmd) - (= "release" exec-cmd)) - (keyword exec-cmd) - exec-cmd)) - -(defn -run [{:keys [exec dry-run?] :as opts} arguments] - (if dry-run? - (ansi/print-info "Starting kmono in dry mode...") - (ansi/print-info "Starting kmono...")) - (let [opts' (if (and (not exec) (seq arguments)) - (assoc opts :exec (arg->exec arguments)) - opts) - {:keys [repo-root glob exec] - :as run-params} (m/decode ?RunOpts opts' mt/default-value-transformer) - config (->> run-params - (merge (config/load-config repo-root glob)) - (config/validate-config!)) - changes (git/scan-for-changes config) - _ (ansi/assert-err! (seq (:build-order config)) "no packages to execute found")] - (case exec - :build (run-build config changes) - :release (run-release config changes) - (run-custom-cmd config changes)))) - -(defn run - ([opts] (run opts nil)) - ([opts arguments] - (with-assertion-error - (let [[success?] (-run (schema/->internal-config opts) arguments)] - (if success? - (System/exit 0) - (System/exit 1)))))) - -(defn- relativize-to-repo-root - [repo-root path] - (when path - (if (fs/absolute? path) - (fs/path path) - (fs/path repo-root path)))) - -(defn repl - ([opts] - (repl opts nil)) - ([{:keys [repo-root cp-file configure-lsp?] :as opts} _] - (with-assertion-error - (let [cp-file' (when configure-lsp? - (or (relativize-to-repo-root repo-root cp-file) - (relativize-to-repo-root repo-root ".lsp/.kmonocp"))) - opts' (-> opts - (schema/->internal-config) - (assoc :cp-file (str cp-file')))] - (repl.deps/run-repl opts'))))) - -(defn generate-classpath! - ([opts] - (generate-classpath! opts nil)) - ([opts _] - (binding [ansi/*logs-enabled* (:cp-file opts)] - (with-assertion-error - (repl.deps/generate-classpath! (schema/->internal-config opts)))))) - -(defn generate-deps! - ([opts] - (generate-deps! opts nil)) - ([opts _] - (binding [ansi/*logs-enabled* (:deps-file opts)] - (with-assertion-error - (repl.deps/generate-deps! (schema/->internal-config opts)))))) - diff --git a/src/k16/kmono/config.clj b/src/k16/kmono/config.clj deleted file mode 100644 index 9660895..0000000 --- a/src/k16/kmono/config.clj +++ /dev/null @@ -1,200 +0,0 @@ -(ns k16.kmono.config - (:require - [babashka.fs :as fs] - [clojure.set :as set] - [clojure.string :as string] - [flatland.ordered.set :as oset] - [k16.kmono.adapter :as adapter] - [k16.kmono.adapters.clojure-deps :as clj.deps] - [k16.kmono.adapters.kmono-edn :as kmono.edn] - [k16.kmono.ansi :as ansi] - [k16.kmono.config-schema :as schema] - [k16.kmono.git :as git] - [k16.kmono.util :as util] - [malli.core :as m] - [malli.error :as me] - [malli.transform :as mt])) - -(defn- read-workspace-config - [repo-root deps-file] - (let [wp-deps-file (fs/file repo-root deps-file)] - (some-> (when (fs/exists? wp-deps-file) - (util/read-deps-edn! wp-deps-file)) - (select-keys [:kmono/workspace :kmono/package])))) - -(defn get-workspace-config - [repo-root] - (let [kmono-props (read-workspace-config repo-root "deps.edn") - kmono-props-local (read-workspace-config repo-root "deps.local.edn") - kmono-merged-props (merge-with merge kmono-props kmono-props-local) - workspace-config (:kmono/workspace kmono-merged-props)] - (when (seq workspace-config) - (schema/assert-schema! - schema/?KmonoWorkspaceUserConfig "Workspace config error" workspace-config)) - (m/encode schema/?KmonoWorkspaceConfig - (schema/->internal-config (or workspace-config {})) - (mt/default-value-transformer - {::mt/add-optional-keys true})))) - -(defn get-adapter - [pkg-dir] - (or (kmono.edn/->adapter (fs/file pkg-dir)) - (clj.deps/->adapter (fs/file pkg-dir)))) - -(defn validate-config! - [config] - (schema/assert-schema! schema/?Config config)) - -(defn- read-package-config - [workspace-config package-dir] - (when-let [adapter (get-adapter package-dir)] - (let [derived-fields (cond-> (select-keys workspace-config - [:group :build-cmd :release-cmd]) - :always (assoc :dir (str package-dir) - :adapter adapter))] - (some->> (adapter/get-kmono-config adapter) - (merge derived-fields) - (schema/assert-schema! schema/?KmonoPackageConfig - "Package config init error"))))) - -(defn- create-package-config - [repo-root glob {:keys [adapter] :as pkg-config}] - (when pkg-config - (let [package-dir (:dir pkg-config) - git-repo? (git/git-initialzied? repo-root) - artifact (or (:artifact pkg-config) - (symbol (fs/file-name package-dir))) - pkg-name (str (:group pkg-config) "/" artifact) - root-package? (fs/same-file? repo-root package-dir) - exclusions (when root-package? - (str ":!:" glob)) - pkg-commit-sha (or (when git-repo? - (git/subdir-commit-sha exclusions package-dir)) - "untracked") - pkg-config (merge pkg-config - {:artifact (or (:artifact pkg-config) - (symbol (fs/file-name package-dir))) - :name pkg-name - :commit-sha pkg-commit-sha - :root-package? root-package?})] - (->> (assoc pkg-config :depends-on (adapter/get-managed-deps adapter)) - (schema/assert-schema! schema/?Package "Package config init error"))))) - -(defn- create-config - [repo-root glob] - (let [workspace-config (get-workspace-config repo-root) - glob' (or glob (:glob workspace-config)) - package-dirs (conj (fs/glob repo-root glob') - (-> (fs/path repo-root) - (fs/normalize) - (fs/absolutize)))] - (merge - workspace-config - {:glob glob' - :repo-root repo-root - :packages (into [] - (comp - (map (partial read-package-config workspace-config)) - (map (partial create-package-config repo-root glob')) - (remove nil?)) - package-dirs)}))) - -(defn create-graph - {:malli/schema [:=> [:cat schema/?Packages] schema/?Graph]} - [packages] - (ansi/print-info "creating graph...") - (reduce (fn [acc {:keys [name depends-on]}] - (assoc acc name (or (set depends-on) #{}))) - {} - packages)) - -(defn find-cycles - [graph] - (loop [nodes (keys graph) - node (first nodes) - path (oset/ordered-set)] - (when node - (let [links (get graph node)] - (if (seq links) - (if (contains? path node) - (conj (vec path) node) - (recur links - (first links) - (conj path node))) - (recur (rest nodes) - (first (rest nodes)) - (disj path node))))))) - -(defn assert-cycles! - [graph] - (let [cycle-path (find-cycles graph)] - (when (seq cycle-path) - (throw (ex-info (str "Cicrlar dependency error") - {:body cycle-path}))))) - -(defn find-missing-deps - [graph] - (let [defined (set (keys graph)) - all-deps (->> graph - (vals) - (reduce into []) - (set))] - (set/difference all-deps defined))) - -(defn assert-missing-deps! - [graph] - (let [diff (find-missing-deps graph)] - (when (seq diff) - (throw (ex-info "Unknown dependencies" - {:body (str "- " (string/join "\n- " diff))}))))) - -(defn parallel-topo-sort - {:malli/schema [:=> [:cat schema/?Graph] [:maybe schema/?BuildOrder]]} - [graph] - (when (seq graph) - (when-let [ks (seq (keep (fn [[k v]] (when (empty? v) k)) graph))] - (vec (into [(vec ks)] - (parallel-topo-sort - (into {} - (map (fn [[k v]] [k (apply disj v ks)])) - (apply dissoc graph ks)))))))) - -(defn- ->pkg-map - {:malli/schema [:=> [:cat schema/?Config] schema/?PackageMap]} - [packages] - (into {} (map (juxt :name identity)) packages)) - -(defn load-config - "Loads config from a file, accepts a directory where config is located, - defaults to current dir. Returns a map with parsed config and build order. - Build order is a list of parallel builds, where parallel build - is a list of package names which can run simultaneously" - {:malli/schema [:function - [:=> :cat schema/?Config] - [:=> [:cat :string] schema/?Config] - [:=> [:cat :string :string] schema/?Config]]} - ([] - (load-config "." nil)) - ([repo-root] - (load-config repo-root nil)) - ([repo-root glob] - (ansi/assert-err! repo-root "config dir is not specified") - (ansi/print-info "loading config...") - (let [config (create-config repo-root glob) - packages (:packages config)] - (ansi/print-info (count packages) "packages found: ") - (doseq [pkg packages] - (ansi/print-info "\t" (:name pkg))) - (if-let [err (m/explain schema/?Packages packages)] - (throw (ex-info "Config validation error" {:body (me/humanize err)})) - (let [graph (create-graph packages)] - (assert-missing-deps! graph) - (assert-cycles! graph) - (merge config {:package-map (->pkg-map packages) - :graph graph - :build-order (parallel-topo-sort graph)})))))) - -(comment - (get-workspace-config ".") - (load-config "." nil) - nil) diff --git a/src/k16/kmono/config_schema.clj b/src/k16/kmono/config_schema.clj deleted file mode 100644 index 06fb994..0000000 --- a/src/k16/kmono/config_schema.clj +++ /dev/null @@ -1,108 +0,0 @@ -(ns k16.kmono.config-schema - (:require - [clojure.pprint :as pp] - [clojure.set :as set] - [k16.kmono.ansi :as ansi] - [malli.core :as m] - [malli.error :as me] - [malli.util :as mu])) - -(def ?CommandConfig - [:map - [:glob {:optional true - :default "packages/*"} - :string] - [:snapshot? {:optional true - :default true} - :boolean] - [:include-unchanged? {:optional true - :default true} - :boolean] - [:main-aliases {:optional true} - [:vector :keyword]] - [:aliases {:optional true} - [:vector :keyword]] - [:package-aliases {:optional true} - [:vector :keyword]]]) - -(def ?KmonoWorkspaceConfig - (mu/merge ?CommandConfig - [:map - [:group {:optional true} - [:or :string :symbol]]])) - -(def ?KmonoWorkspaceUserConfig - (mu/rename-keys ?KmonoWorkspaceConfig {:glob :packages})) - -(def ?KmonoPackageConfig - [:map - [:group [:or :string :symbol]] - [:artifact {:optional true} - [:maybe [:or :string :symbol]]] - [:aliases {:optional true} - [:vector :keyword]] - [:release-cmd {:optional true} - [:or [:= :skip] :string]] - [:local-deps {:optional true} - [:or - [:vector :symbol] - [:vector :string]]] - [:build-cmd {:optional true} - :string]]) - -(def ?Package - (-> ?KmonoPackageConfig - (mu/required-keys [:artifact]) - (mu/merge [:map - [:depends-on [:vector :string]] - [:commit-sha :string] - [:name :string] - [:dir :string]]))) - -(def ?Packages - [:vector ?Package]) - -(def ?PackageMap - [:map-of :string ?Package]) - -(def ?Graph - [:map-of :string [:set :string]]) - -(def ?BuildOrder - [:vector [:vector :string]]) - -(def ?Config - (mu/merge - (mu/required-keys - ?KmonoWorkspaceConfig - [:glob :snapshot? :include-unchanged?]) - [:map {:closed true} - [:exec [:or :string [:enum :build :release]]] - [:workspace-config {:optional true} - ?KmonoWorkspaceConfig] - [:dry-run? :boolean] - [:create-tags? :boolean] - [:repo-root :string] - [:packages ?Packages] - [:package-map ?PackageMap] - [:graph ?Graph] - [:build-order [:maybe ?BuildOrder]]])) - -(defn assert-schema! - ([?schema value] - (assert-schema! ?schema "Schema error" value)) - ([?schema title value] - (binding [ansi/*logs-enabled* true] - (if-not (m/validate ?schema value) - (do (ansi/print-error title) - (ansi/print-shifted - (with-out-str - (pp/pprint (me/humanize (m/explain ?schema value))))) - (throw (ex-info title {:type :errors/assertion}))) - value)))) - -(defn ->internal-config - "We don't want packages key internally, because we already have it, so rename it to glob" - [config] - (set/rename-keys config {:packages :glob})) - diff --git a/src/k16/kmono/dry.clj b/src/k16/kmono/dry.clj deleted file mode 100644 index 53b0468..0000000 --- a/src/k16/kmono/dry.clj +++ /dev/null @@ -1,7 +0,0 @@ -(ns k16.kmono.dry) - -(def fake-release-cmd "ping k42.com -c 2") -(def fake-custom-cmd "ping k42.com -c 5") -(def fake-build-cmd "ping k42.com -c 10") -(def fake-git-tag-cmd "date") -(def fake-git-push-cmd "ping k42.com -c 5") diff --git a/src/k16/kmono/exec.clj b/src/k16/kmono/exec.clj deleted file mode 100644 index df1a479..0000000 --- a/src/k16/kmono/exec.clj +++ /dev/null @@ -1,118 +0,0 @@ -(ns k16.kmono.exec - (:require - [babashka.process :as bp] - [k16.kmono.adapter :as adapter] - [k16.kmono.ansi :as ansi] - [k16.kmono.config-schema :as config.schema] - [k16.kmono.dry :as dry] - [k16.kmono.git :as git] - [k16.kmono.proc :as kmono.proc])) - -(def ?JobResult - [:tuple :boolean kmono.proc/?ProcsResult]) - -(defn- run-external-cmd - [config changes pkg-name cmd-type] - (let [pkg-map (:package-map config) - pkg (get pkg-map pkg-name) - pkg-deps-env (-> pkg :adapter (adapter/prepare-deps-env changes)) - version (get-in changes [pkg-name :version]) - ext-cmd (if (:dry-run? config) - (case cmd-type - :build-cmd dry/fake-build-cmd - :release-cmd dry/fake-release-cmd - dry/fake-custom-cmd) - (or (get pkg cmd-type) - (get config cmd-type))) - _ (ansi/assert-err! ext-cmd (str "Command of type [" cmd-type "] could not be found")) - _ (ansi/print-info "\t" (str pkg-name "@" version " => " ext-cmd)) - build-result (if (or (nil? ext-cmd) (= :skip ext-cmd)) - {:skipped? true} - (bp/process {:extra-env - {"KMONO_PKG_DEPS" pkg-deps-env - "KMONO_PKG_VERSION" version - "KMONO_PKG_NAME" pkg-name} - :out :string - :err :string - :dir (:dir pkg)} - ext-cmd))] - [pkg-name build-result])) - -(defn get-milis - [] - (.getTime (java.util.Date.))) - -(defn run-external-cmds - {:malli/schema [:=> [:cat config.schema/?Config git/?Changes] ?JobResult]} - [config changes operation-fn terminate-on-fail?] - (let [exec-order (:build-order config) - global-start (get-milis)] - (ansi/print-info (count exec-order) "parallel stages to run...") - (loop [stages exec-order - idx 1 - stage-results []] - (if (seq stages) - (let [stage (first stages) - prefix (str "#" idx) - start-time (get-milis) - _ (println) - _ (ansi/print-info prefix "stage started" (str "(" (count stage) " packages)")) - op-procs (->> stage - (map (partial operation-fn config changes)) - (remove nil?)) - [success? stage-result] (kmono.proc/await-procs - op-procs terminate-on-fail?)] - (if success? - (do (ansi/print-info prefix "stage finished in" - (- (get-milis) start-time) - "ms\n") - (recur (rest stages) (inc idx) (conj stage-results stage-result))) - (do (ansi/print-error prefix "stage failed") - (ansi/print-error "terminating") - (doseq [[_ proc] op-procs] - (when-not (:skipped? proc) - (bp/destroy-tree proc))) - [false (conj stage-results stage-result)]))) - (do (ansi/print-info "Total time:" (- (get-milis) global-start) "ms") - [true stage-results]))))) - -(defn build-package - [config changes pkg-name] - (let [change (get changes pkg-name)] - (if (or (:changed? change) (:include-unchanged? config)) - (run-external-cmd config changes pkg-name :build-cmd) - [pkg-name {:skipped? true}]))) - -(defn release-package - [config changes pkg-name] - (let [pkg-map (:package-map config) - pkg (get pkg-map pkg-name) - version (get-in changes [pkg-name :version]) - changed? (get-in changes [pkg-name :changed?])] - (if (and changed? - (-> pkg :adapter (adapter/release-published? version) (not))) - (run-external-cmd config changes pkg-name :release-cmd) - [pkg-name {:skipped? true}]))) - -(defn package-custom-command - [config changes pkg-name] - (let [change (get changes pkg-name)] - (if (or (:changed? change) (:include-unchanged? config)) - (run-external-cmd config changes pkg-name :exec) - [pkg-name {:skipped? true}]))) - -(defn build - {:malli/schema [:=> [:cat config.schema/?Config git/?Changes] ?JobResult]} - [config changes] - (run-external-cmds config changes build-package true)) - -(defn release - {:malli/schema [:=> [:cat config.schema/?Config git/?Changes] ?JobResult]} - [config changes] - (run-external-cmds config changes release-package true)) - -(defn custom-command - {:malli/schema [:=> [:cat config.schema/?Config git/?Changes] ?JobResult]} - [config changes] - (run-external-cmds config changes package-custom-command false)) - diff --git a/src/k16/kmono/git.clj b/src/k16/kmono/git.clj deleted file mode 100644 index efcde31..0000000 --- a/src/k16/kmono/git.clj +++ /dev/null @@ -1,227 +0,0 @@ -(ns k16.kmono.git - (:require - [babashka.fs :as fs] - [babashka.process :as bp] - [clojure.set :as set] - [clojure.string :as string] - [k16.kmono.config-schema :as config.schema] - [k16.kmono.dry :as dry])) - -(defn git-initialzied? - [repo-root] - (let [git-dir (fs/file repo-root ".git")] - (and (fs/exists? git-dir) (fs/directory? git-dir)))) - -(defn- out->strings - [{:keys [out err] :as result}] - (if (seq err) - (throw (ex-info err {:body result})) - (let [out (string/trim out)] - (when (seq out) - (-> out - (string/split-lines) - (vec)))))) - -(defn run-cmd! [dir & cmd] - (-> (bp/shell {:dir (str dir) - :out :string - :err :string} - (string/join " " cmd)) - (out->strings))) - -(defn get-sorted-tags - "Returns all tags sorted by creation date (descending)" - [repo-root] - (run-cmd! repo-root "git tag --sort=-creatordate")) - -(defn subdir-commit-sha - [exclusions sub-dir] - (first (run-cmd! - (str sub-dir) - "git log -n 1 --pretty=format:\"%h\" -- ." - exclusions))) - -(defn subdir-changes - [sub-dir tag exclusions] - (when-let [out (run-cmd! sub-dir - "git log --pretty=format:\"%s\" " - (str tag "..HEAD -- .") - exclusions)] - (if (coll? out) - (vec out) - [out]))) - -(def change-type - {:patch #{"fix" "patch" "release"} - :minor #{"minor" "feat"} - :major #{"major" "breaking"}}) - -(def prefixes (apply set/union (vals change-type))) - -(defn find-prefix - [message] - (when-let [msg-prefix (second (re-find #"(:?.+):.+" message))] - (some (fn [p] (when (string/starts-with? msg-prefix p) p)) - prefixes))) - -(defn bump-type - [changes] - (if-let [chahge-prefixes (some->> changes (seq) (map find-prefix))] - (condp some (set chahge-prefixes) - (change-type :major) :major - (change-type :minor) :minor - (change-type :patch) :patch - :build) - :none)) - -(def version-pattern - (re-pattern #"(?:(?:[^\d]*))(\d+)\.(\d+)\.(\d+)(?:\.(\d+))?(?:[^\d].*)?")) - -(defn version? - [v] - (boolean (re-matches version-pattern v))) - -(def ?Changes - [:map-of - :string - [:map - [:version :string] - [:package-name :string] - [:published? :boolean]]]) - -(defn bump - "Bumps the version depending of type of bump [major, minor, patch, build] - strips out any characters not related to version, e.g. v1.1.1-foo becomes 1.1.2" - [{:keys [version bump-type commit-sha snapshot?]}] - (if-let [[_ major minor patch build] (re-matches version-pattern version)] - (let [major (parse-long major) - minor (parse-long minor) - patch (parse-long patch) - build' (if build (parse-long build) 0) - new-version (case bump-type - :major (string/join "." [(inc major) 0 0 0]) - :minor (string/join "." [major (inc minor) 0 0]) - :patch (string/join "." [major minor (inc patch) 0]) - :build (string/join "." [major minor patch (inc build')]) - version)] - (if snapshot? - (str new-version "-" commit-sha "-SNAPSHOT") - new-version)) - (throw (ex-info "Version does not match pattern `major.minor.patch[.build]`" - {:body (str "version: " version)})))) - -(defn non-git-pkg-changes [snapshot? pkg] - {:version (if snapshot? - (str "0.0.0.0-" (:commit-sha pkg) "-SNAPSHOT") - "0.0.0.0") - :changed? true - :package-name (:name pkg)}) - -(defn package-changes - [{:keys [repo-root snapshot? glob include-unchanged?]} - {:keys [commit-sha dir] :as pkg}] - (or (and (git-initialzied? repo-root) - (when-let [latest-tag (some->> - repo-root - (get-sorted-tags) - (filter #(string/starts-with? - % (str (:name pkg) "@"))) - (first))] - (let [[_ current-version] (string/split latest-tag #"@") - exclusions (when (fs/same-file? repo-root dir) - (str ":!:" glob)) - bump-type (-> (subdir-changes dir latest-tag exclusions) - (bump-type))] - (when (version? current-version) - (let [changed? (or (not= :none bump-type) - include-unchanged?) - version (if changed? - (bump {:version current-version - :bump-type bump-type - :commit-sha commit-sha - :snapshot? snapshot?}) - current-version)] - {:version version - :changed? changed? - :package-name (:name pkg)}))))) - (non-git-pkg-changes snapshot? pkg))) - -(defn- bump-dependand - [dependant config dependant-name] - (let [{:keys [version]} dependant - {:keys [snapshot? package-map]} config - dpkg (get package-map dependant-name) - new-version (bump {:version version - :bump-type :build - :commit-sha (:commit-sha dpkg) - :snapshot? snapshot?})] - (assoc dependant - :version new-version - :changed? (not= new-version version)))) - -(defn ensure-dependend-builds - [config changes] - (loop [changes' changes - cursor (keys changes)] - (if-let [{:keys [changed? package-name]} (get changes' (first cursor))] - (if changed? - (let [dependands-to-bump (->> (:graph config) - (map (fn [[pkg-name deps]] - (when (and (contains? deps package-name) - (-> changes - (get pkg-name) - :changed? - (not))) - pkg-name))) - (remove nil?))] - (recur (reduce (fn [chgs dpn-name] - ;; NOTE: we want to bump only once - (if (= (get-in chgs [dpn-name :version]) - (get-in changes [dpn-name :version])) - (update chgs dpn-name bump-dependand config dpn-name) - chgs)) - changes' - dependands-to-bump) - (rest cursor))) - (recur changes' (rest cursor))) - changes'))) - -(defn scan-for-changes - "Takes a repo path and kmono config map. Scans all packages and determines - version, tag, and should it be built. Build is always true in case of - fallback_version. Returns a promise containing changes" - {:malli/schema [:=> [:cat config.schema/?Config] ?Changes]} - [{:keys [packages] :as config}] - (package-changes config (second packages)) - (->> packages - (into {} (map (fn [pkg] [(:name pkg) (package-changes config pkg)]))) - (ensure-dependend-builds config))) - -(defn create-tags! - {:malli/schema [:=> [:cat config.schema/?Config [:sequential :string]] :boolean]} - [{:keys [dry-run? repo-root]} tags] - (try - (loop [tags tags] - (if (seq tags) - (let [tag (first tags)] - (run-cmd! repo-root (if dry-run? - dry/fake-git-tag-cmd - (str "git tag " tag))) - (recur (rest tags))) - true)) - (catch Throwable ex - (do (println (ex-message ex)) - false)))) - -(defn push-tags! - {:malli/schema [:=> [:cat config.schema/?Config] :boolean]} - [{:keys [dry-run? repo-root]}] - (try - (run-cmd! repo-root (if dry-run? - dry/fake-git-push-cmd - "git push origin --tags")) - true - (catch Throwable ex - (do (println (ex-message ex)) - false)))) - diff --git a/src/k16/kmono/main.clj b/src/k16/kmono/main.clj deleted file mode 100644 index cd3d230..0000000 --- a/src/k16/kmono/main.clj +++ /dev/null @@ -1,157 +0,0 @@ -(ns k16.kmono.main - (:require - [clojure.tools.cli :as tools.cli] - [k16.kmono.api :as api]) - (:gen-class)) - -(defmacro package-version [] - `(let [v# ~(or (System/getenv "KMONO_PKG_VERSION") "unset")] - v#)) - -(defn get-version [] (package-version)) - -(def run-cli-spec - [["-h" "--help" - "Show this help" - :id :show-help?] - ["-x" "--exec CMD" - "Command to exec [build, release, ]" - :parse-fn (fn [cmd] - (if (or (= cmd "release") (= cmd "build")) - (keyword cmd) - cmd))] - ["-r" "--repo-root PATH" - "Repository root (default: '.')" - :default "."] - ["-p" "--packages GLOB" - "A glob string describing where to search for packages, (default: 'packages/*')" - :id :glob] - ["-s" "--snapshot FLAG" - "A snapshot flag (default: false)" - :id :snapshot? - :default false - :parse-fn #(Boolean/parseBoolean %)] - ["-t" "--create-tags FLAG" - "Should create tags flag (default: false)" - :id :create-tags? - :default false - :parse-fn #(Boolean/parseBoolean %)] - ["-i" "--include-unchanged FLAG" - "Should include unchanged packages flag (default: true)" - :id :include-unchanged? - :default true - :parse-fn #(Boolean/parseBoolean %)]]) - -(defn parse-aliases - [aliases-arg] - (->> aliases-arg (re-seq #"[^:]+") (mapv keyword))) - -(defn- cp-option - [default-value description] - ["-f" "--cp-file FILENAME" - description - :default default-value]) - -(defn- deps-option - [default-value description] - ["-d" "--deps-file FILENAME" - description - :default default-value]) - -(def repl-cli-spec - [["-h" "--help" - "Show this help" - :id :show-help?] - ["-v" "--verbose" - "Print additional info" - :id :verbose?] - ["-A" "--aliases :alias1:alias2:namespace/alias3" - "List of aliases from root deps.edn" - :parse-fn parse-aliases] - ["-M" "--main-aliases :alias1:alias2" - "List of main aliases from root deps.edn" - :parse-fn parse-aliases] - ["-P" "--package-aliases :package/alias1:package/alias2:*/alias3" - "List of aliases from packages (asterisk means all packages)" - :parse-fn parse-aliases] - ["-r" "--repo-root PATH" - "Repository root (default: '.')" - :default "."] - ["-p" "--packages GLOB" - "A glob string describing where to search for packages, (default: 'packages/*')" - :id :glob] - ["-l" "--configure-lsp" - (str "Set repl specific `:project-specs` in `.lsp/config.edn`, " - "requires cp-file to be set (default: false)") - :id :configure-lsp? - :default false] - (cp-option nil "Save classpath file for LSP use (default: do nothing)")]) - -(def cp-cli-spec - (conj (drop-last 2 repl-cli-spec) - (cp-option nil "Classpath file name (default: print to output)"))) - -(def deps-cli-spec - (conj (drop-last 2 repl-cli-spec) - (deps-option nil "Creates a deps file"))) - -(defn print-help - [title summary] - (println title) - (println summary) - (println)) - -(def run-title - (str "=== run - execute command for monorepo ===\n" - " run [opts...]\n" - " run -x [opts...]")) -(def repl-title "=== repl - start a REPL for monorepo ===") -(def cp-title "=== cp - save/print a classpath ===") -(def deps-title "=== deps - save/print a combined deps file ===") - -(defn make-handler - [cli-spec help-title run-fn] - (fn [opts] - (let [{:keys [options summary errors arguments]} - (tools.cli/parse-opts opts cli-spec)] - (cond - errors (do - (print-help help-title summary) - (println errors)) - - (:show-help? options) - (print-help help-title summary) - - :else (run-fn options arguments))))) - -(def modes - {"--version" (fn [_] - (println (str "kmono v" (get-version)))) - "run" (make-handler run-cli-spec run-title api/run) - "repl" (make-handler repl-cli-spec repl-title api/repl) - "cp" (make-handler cp-cli-spec cp-title api/generate-classpath!) - "deps" (make-handler deps-cli-spec deps-title api/generate-deps!) - "help" (fn [_] - (let [run-summary (-> (tools.cli/parse-opts [] run-cli-spec) - :summary) - repl-summary (-> (tools.cli/parse-opts [] repl-cli-spec) - :summary) - cp-summary (-> (tools.cli/parse-opts [] cp-cli-spec) - :summary) - deps-summary (-> (tools.cli/parse-opts [] deps-cli-spec) - :summary)] - (println (str "kmono v" (get-version) "\n")) - (println "USAGE: kmono opts...\n") - (println "MODES:") - (print-help run-title run-summary) - (print-help repl-title repl-summary) - (print-help cp-title cp-summary) - (print-help deps-title deps-summary)))}) - -(defn -main [& args] - (let [mode (first args) - mode-handler (or (get modes mode) - (get modes "help"))] - (mode-handler args) - (System/exit 0))) - diff --git a/src/k16/kmono/proc.clj b/src/k16/kmono/proc.clj deleted file mode 100644 index c4f8fe3..0000000 --- a/src/k16/kmono/proc.clj +++ /dev/null @@ -1,85 +0,0 @@ -(ns k16.kmono.proc - (:require - [babashka.process :as bp] - [k16.kmono.ansi :as ansi])) - -(def ?PackageName :string) - -(def ?BabashkaProcess :map) - -(def ?ProcsResult - [:vector - [:map-of - :string - [:map - [:success? :boolean] - [:output :string]]]]) - -(def ?ProcResult - [:map - [:success? :boolean] - [:output :string]]) - -(def ?ProcAwaitResults - [:map-of ?PackageName ?ProcResult]) - -(def ?CmdProc - [:tuple ?PackageName ?BabashkaProcess]) - -(defn- failed? - [proc] - (not (-> proc (deref) :exit (zero?)))) - -(defn- rotate [v] - (into (vec (drop 1 v)) (take 1 v))) - -(defn- ->success - {:malli/schema [:=> [:cat ?BabashkaProcess] ?ProcResult]} - [proc] - {:success? true - :output @(:out proc)}) - -(defn- ->failure - {:malli/schema [:=> [:cat ?BabashkaProcess] ?ProcResult]} - [proc] - {:success? false - :output @(:err proc)}) - -(defn- ->skipped - {:malli/schema [:=> [:cat ?BabashkaProcess] ?ProcResult]} - [] - {:success? true - :output "no changes"}) - -(defn await-procs - {:malli/schema [:=> - [:cat [:sequential ?CmdProc] :boolean] - [:tuple :boolean ?ProcAwaitResults]]} - [package-procs terminate-on-failure?] - (loop [package-procs package-procs - results {}] - (let [[pkg-name proc] (first package-procs)] - (if proc - (cond - (:skipped? proc) - (do (println "\t" (ansi/cyan "skipped") pkg-name) - (recur (rest package-procs) - (assoc results pkg-name (->skipped)))) - - (bp/alive? proc) - (do (Thread/sleep 200) - (recur (rotate package-procs) results)) - - (failed? proc) - (if terminate-on-failure? - [false (assoc results pkg-name (->failure proc))] - (do (println "\t" (ansi/red "failure") pkg-name) - (recur (rest package-procs) - (assoc results pkg-name (->failure proc))))) - - :else (do (println "\t" (ansi/green "success") pkg-name) - (recur (rest package-procs) - (assoc results pkg-name (->success proc))))) - - [true results])))) - diff --git a/src/k16/kmono/repl/deps.clj b/src/k16/kmono/repl/deps.clj deleted file mode 100644 index ef14247..0000000 --- a/src/k16/kmono/repl/deps.clj +++ /dev/null @@ -1,279 +0,0 @@ -(ns k16.kmono.repl.deps - (:require - [babashka.fs :as fs] - [babashka.process :as bp] - [clojure.edn :as edn] - [clojure.pprint :as pprint] - [clojure.string :as string] - [clojure.walk :as walk] - [k16.kmono.adapters.clojure-deps :as clj.deps] - [k16.kmono.ansi :as ansi] - [k16.kmono.config :as config] - [k16.kmono.config-schema :as config.schema] - [k16.kmono.util :as util] - [malli.core :as m])) - -(def path-aliases #{:paths :extra-paths :replace-paths :local/root}) - -(defn relativize-path - [root package-dir path] - (assert (and root package-dir path) - {:root root - :package-dir package-dir - :path path}) - (if (fs/same-file? root package-dir) - (fs/path package-dir path) - (fs/relativize root (fs/path package-dir path)))) - -(defn- strip-extra-parent - "JRE 8 workaround" - [str-path] - (if (string/starts-with? str-path "../") - (subs str-path 3) - str-path)) - -(defn relativize-paths - "Relativise paths from package alias opts to work in repo root context" - [repo-root package-dir aliases-map] - (when (seq aliases-map) - (letfn [(relativize [path] - (-> (relativize-path repo-root package-dir path) - (str) - (strip-extra-parent)))] - (walk/postwalk - (fn [form] - (if (map-entry? form) - (let [[k v] form] - (if (contains? path-aliases k) - [k (if (string? v) - (relativize v) - (mapv relativize v))] - form)) - form)) - aliases-map)))) - -(defn all-packages-deps-alias - [packages] - (let [pkg-deps (reduce (fn [deps {:keys [name dir]}] - (assoc deps (symbol name) {:local/root dir})) - {} - packages)] - (when (seq pkg-deps) - {:kmono/package-deps {:extra-deps pkg-deps}}))) - -(defn get-package-alias-map - "Returns alias map from a given package/alias pair. - Accepts kmono config (`config.schema/?Config`) and a package/alias pair as - a namespaced keyord where namespace is a package name and a name is an alias, - e.g. `:my-package/test`. - Returns an extracted alias map from package's deps.edn" - [{:keys [package-dirs repo-root]} package-alias] - (let [package-name (or (namespace package-alias) (name package-alias)) - package-dir (get package-dirs package-name) - alias-key (-> package-alias (name) (keyword)) - _ (assert package-name (str "Could not get package from package-alias [" - package-alias "]")) - _ (assert alias-key (str "Could not get alias from package-alias [" - package-alias "]")) - deps-edn (clj.deps/read-pkg-deps! package-dir) - alias-name (keyword (str "kmono.pkg/" package-name "." (name alias-key)))] - {alias-name (or (relativize-paths - repo-root - package-dir - (get-in deps-edn [:aliases alias-key])) - {})})) - -(defn- expand-package-alias-pairs - [packages package-alias-pairs] - (->> package-alias-pairs - (map (fn [pair] - (let [pair' (keyword pair)] - (if (= "*" (namespace pair')) - (map (fn [p] - (keyword p (name pair'))) - packages) - pair')))) - (flatten) - (vec))) - -(defn construct-sdeps-overrides! - "Accepts kmono config and a collection pairs of package/alias and - returns a string for -Sdeps argument" - [config] - (let [package-alias-pairs (:package-aliases config) - package-dirs (->> config - :packages - (map (fn [pkg] - [(fs/file-name (fs/normalize - (fs/path (:dir pkg)))) - (-> (:dir pkg) - (fs/normalize) - (str))])) - (into {})) - package-deps (all-packages-deps-alias (:packages config)) - package-alias-pairs' (expand-package-alias-pairs - (keys package-dirs) - package-alias-pairs) - package-aliases (into {} - (comp - (map (partial get-package-alias-map - {:package-dirs package-dirs - :repo-root (:repo-root config)}))) - package-alias-pairs') - override {:aliases (merge package-deps package-aliases)}] - override)) - -(def ?ReplParams - [:map - [:main-aliases {:optional true} - [:vector :keyword]] - [:aliases {:optional true} - [:vector :keyword]] - [:packages-aliases {:optional true} - [:vector :keyword]]]) - -(def nrepl-alias - {:kmono/nrepl {:extra-deps {'cider/cider-nrepl {:mvn/version "0.44.0"}} - :main-opts ["-m" - "nrepl.cmdline" - "--middleware" - "[cider.nrepl/cider-middleware]"]}}) - -(defn- print-clojure-cmd - [sdeps-overrides args-str] - (ansi/print-shifted (ansi/green "\nclojure -Sdeps")) - (ansi/print-shifted (ansi/green (str (with-out-str - (pprint/pprint sdeps-overrides)) - "\n" args-str)))) - -(defn cp! - [{:keys [verbose? package-aliases aliases repo-root cp-file]} sdeps-overrides] - (let [all-aliases (str (string/join package-aliases) - (string/join aliases)) - cp-aliases-opt (when (seq all-aliases) - (str "-A" all-aliases)) - ;; to avoid "use -M instead of -A" deprecation warning - sdeps-overrides' (update sdeps-overrides - :aliases - update-vals - #(dissoc % :main-opts)) - sdeps (str "-Sdeps '" (pr-str sdeps-overrides') "'") - clojure-cmd (string/join " " ["clojure" sdeps cp-aliases-opt "-Spath"])] - (if (seq cp-file) - (do - (ansi/print-info "Saving classpath to a file:" cp-file) - (when verbose? - (print-clojure-cmd sdeps-overrides' (str cp-aliases-opt " -Spath"))) - (bp/shell {:dir repo-root :out cp-file} clojure-cmd)) - (bp/shell {:dir repo-root} clojure-cmd)))) - -(defn- make-cp-params - [config] - (let [package-overrides (construct-sdeps-overrides! config) - local-deps-file (fs/file (:repo-root config) "deps.local.edn") - deps-local-overrides (when (fs/exists? local-deps-file) - (util/read-deps-edn! local-deps-file))] - {:package-overrides package-overrides - :cp-params (assoc config - :package-aliases - (-> package-overrides :aliases (keys))) - :sdeps-overrides (update package-overrides - :aliases - merge - (:aliases deps-local-overrides))})) - -(defn generate-classpath! - [{:keys [repo-root glob] :as params}] - (ansi/print-info "Generating kmono REPL classpath...") - (assert (m/validate ?ReplParams params) (m/explain ?ReplParams params)) - (let [config (merge (config/load-config repo-root glob) params) - {:keys [cp-params sdeps-overrides]} - (make-cp-params config)] - (cp! cp-params sdeps-overrides))) - -(defn generate-deps! - [{:keys [repo-root glob deps-file] :as params}] - (ansi/print-info "Generating kmono deps.edn...") - (assert (m/validate ?ReplParams params) (m/explain ?ReplParams params)) - (let [config (merge (config/load-config repo-root glob) params) - {:keys [sdeps-overrides]} (make-cp-params config) - project-deps-file (fs/file repo-root "deps.edn") - project-deps (when (fs/exists? project-deps-file) - (util/read-deps-edn! project-deps-file)) - kmono-deps (binding [*print-namespace-maps* false] - (with-out-str - (pprint/pprint - (merge project-deps sdeps-overrides))))] - (if deps-file - (spit deps-file kmono-deps) - (do - (println "kmono deps.edn") - (println kmono-deps))))) - -(defn create-project-specs - [cp-file] - [{:project-path "deps.edn" - :classpath-cmd ["cat" cp-file]}]) - -(defn configure-lsp! - [{:keys [repo-root cp-file]}] - (when (seq cp-file) - (let [lsp-config-file (fs/file repo-root ".lsp/config.edn") - lsp-config (when (fs/exists? lsp-config-file) - (try - (-> lsp-config-file - (slurp) - (edn/read-string)) - (catch Throwable _ {}))) - project-specs (create-project-specs cp-file) - with-project-specs - (assoc lsp-config :project-specs project-specs)] - (ansi/print-info "Setting project-specs for lsp config") - (spit lsp-config-file - (with-out-str - (binding [*print-namespace-maps* false] - (pprint/pprint with-project-specs))))))) - -(defn run-repl - [{:keys [repo-root glob] :as params}] - (ansi/print-info "Starting kmono REPL...") - (binding [*print-namespace-maps* false] - (let [{:keys [main-aliases aliases cp-file configure-lsp? verbose?] - :as config} - (merge (config/load-config repo-root glob) params) - _ (config.schema/assert-schema! ?ReplParams "REPL params error" config) - {:keys [cp-params package-overrides sdeps-overrides]} - (make-cp-params config) - main-opts (str "-M" - (string/join (-> package-overrides :aliases (keys))) - (string/join aliases) - (if (seq main-aliases) - (do (ansi/print-info - "Using custom main aliases" main-aliases) - (string/join main-aliases)) - (do (ansi/print-info - "Using default main alias :kmono/repl") - ":kmono/nrepl"))) - with-nrepl-alias (if-not (seq main-aliases) - (update sdeps-overrides :aliases merge nrepl-alias) - sdeps-overrides) - sdeps (str "-Sdeps '" (pr-str with-nrepl-alias) "'") - clojure-cmd (string/join " " ["clojure" sdeps main-opts])] - (when (seq cp-file) - (cp! cp-params sdeps-overrides)) - (when configure-lsp? - (configure-lsp! config)) - (ansi/print-info "Running clojure...") - (when verbose? - (print-clojure-cmd sdeps-overrides main-opts)) - (let [proc (bp/process {:inherit true} clojure-cmd)] - (doto (Runtime/getRuntime) - (.addShutdownHook - (Thread. - (fn [] - (try - (bp/destroy proc) - (bp/check proc) - (catch Throwable _ nil)))))) - (bp/check proc))))) - diff --git a/src/k16/kmono/util.clj b/src/k16/kmono/util.clj deleted file mode 100644 index df75a90..0000000 --- a/src/k16/kmono/util.clj +++ /dev/null @@ -1,16 +0,0 @@ -(ns k16.kmono.util - (:require - [babashka.fs :as fs] - [clojure.edn :as edn])) - -(defn read-deps-edn! - [file-path] - (try - (-> (fs/file file-path) - (slurp) - (edn/read-string)) - (catch Throwable e - (throw (ex-info "Could not read deps.edn file" - {:file-path file-path - :event "read-deps-edn"} - e))))) diff --git a/test/fixtures/dependents_config.edn b/test/fixtures/dependents_config.edn deleted file mode 100644 index 365bc0f..0000000 --- a/test/fixtures/dependents_config.edn +++ /dev/null @@ -1,172 +0,0 @@ -{:include-unchanged? false, - :package-map - {"transit-engineering/runtime-api" - {:build-cmd "just install", - :group transit-engineering, - :dir "../../k42/runtime/packages/runtime-api", - :release-cmd "just release", - :name "transit-engineering/runtime-api", - :artifact runtime-api, - :commit-sha "dcbffa9", - :depends-on []}, - "transit-engineering/jollibee.ext" - {:build-cmd "just install", - :group transit-engineering, - :dir "../../k42/runtime/packages/jollibee.ext", - :release-cmd "just release", - :name "transit-engineering/jollibee.ext", - :artifact jollibee.ext, - :commit-sha "f9d17d9", - :depends-on - ["transit-engineering/runtime-api" - "transit-engineering/agent.ext"]}, - "transit-engineering/session-notification.ext" - {:build-cmd "just install", - :group transit-engineering, - :dir "../../k42/runtime/packages/session-notification.ext", - :release-cmd "just release", - :name "transit-engineering/session-notification.ext", - :artifact session-notification.ext, - :commit-sha "71b3a42", - :depends-on - ["transit-engineering/runtime-api" - "transit-engineering/swing.ext" - "transit-engineering/agent.ext"]}, - "transit-engineering/runtime" - {:build-cmd "just package", - :group transit-engineering, - :dir "../../k42/runtime/packages/runtime", - :release-cmd "just release", - :name "transit-engineering/runtime", - :artifact runtime, - :commit-sha "6a07da1", - :depends-on - ["transit-engineering/runtime-api" - "transit-engineering/jollibee.ext" - "transit-engineering/robinsons-loyalty.ext" - "transit-engineering/scan-hook.ext" - "transit-engineering/swing.ext" - "transit-engineering/agent.ext" - "transit-engineering/telemetry.ext" - "transit-engineering/session-notification.ext" - "transit-engineering/scan-training.ext" - "transit-engineering/healthchecker.ext"]}, - "transit-engineering/robinsons-loyalty.ext" - {:build-cmd "just install", - :group transit-engineering, - :dir "../../k42/runtime/packages/robinsons-loyalty.ext", - :release-cmd "just release", - :name "transit-engineering/robinsons-loyalty.ext", - :artifact robinsons-loyalty.ext, - :commit-sha "4fdb491", - :depends-on - ["transit-engineering/runtime-api" - "transit-engineering/swing.ext" - "transit-engineering/agent.ext"]}, - "transit-engineering/swing.ext" - {:build-cmd "just install", - :group transit-engineering, - :dir "../../k42/runtime/packages/swing.ext", - :release-cmd "just release", - :name "transit-engineering/swing.ext", - :artifact swing.ext, - :commit-sha "f9d17d9", - :depends-on ["transit-engineering/runtime-api"]}, - "transit-engineering/telemetry.ext" - {:build-cmd "just install", - :group transit-engineering, - :dir "../../k42/runtime/packages/telemetry.ext", - :release-cmd "just release", - :name "transit-engineering/telemetry.ext", - :artifact telemetry.ext, - :commit-sha "e9847c6", - :depends-on ["transit-engineering/runtime-api"]}, - "transit-engineering/healthchecker.ext" - {:build-cmd "just install", - :group transit-engineering, - :dir "../../k42/runtime/packages/healthchecker.ext", - :release-cmd "just release", - :name "transit-engineering/healthchecker.ext", - :artifact healthchecker.ext, - :commit-sha "f9d17d9", - :depends-on - ["transit-engineering/runtime-api" - "transit-engineering/agent.ext"]}, - "transit-engineering/scan-hook.ext" - {:build-cmd "just install", - :group transit-engineering, - :dir "../../k42/runtime/packages/scan-hook.ext", - :release-cmd "just release", - :name "transit-engineering/scan-hook.ext", - :artifact scan-hook.ext, - :commit-sha "1ac977f", - :depends-on - ["transit-engineering/runtime-api" - "transit-engineering/swing.ext" - "transit-engineering/agent.ext"]}, - "transit-engineering/agent.ext" - {:build-cmd "just install", - :group transit-engineering, - :dir "../../k42/runtime/packages/agent.ext", - :release-cmd "just release", - :name "transit-engineering/agent.ext", - :artifact agent.ext, - :commit-sha "6d56059", - :depends-on - ["transit-engineering/runtime-api" - "transit-engineering/swing.ext"]}, - "transit-engineering/scan-training.ext" - {:build-cmd "just install", - :group transit-engineering, - :dir "../../k42/runtime/packages/scan-training.ext", - :release-cmd "just release", - :name "transit-engineering/scan-training.ext", - :artifact scan-training.ext, - :commit-sha "f9d17d9", - :depends-on - ["transit-engineering/runtime-api" - "transit-engineering/swing.ext" - "transit-engineering/agent.ext"]}}, - :repo-root "../../k42/runtime", - :glob "packages/*", - :graph - {"transit-engineering/runtime-api" #{}, - "transit-engineering/jollibee.ext" - #{"transit-engineering/runtime-api" "transit-engineering/agent.ext"}, - "transit-engineering/session-notification.ext" - #{"transit-engineering/runtime-api" - "transit-engineering/swing.ext" - "transit-engineering/agent.ext"}, - "transit-engineering/runtime" - #{"transit-engineering/runtime-api" - "transit-engineering/jollibee.ext" - "transit-engineering/session-notification.ext" - "transit-engineering/robinsons-loyalty.ext" - "transit-engineering/swing.ext" - "transit-engineering/telemetry.ext" - "transit-engineering/healthchecker.ext" - "transit-engineering/scan-hook.ext" - "transit-engineering/agent.ext" - "transit-engineering/scan-training.ext"}, - "transit-engineering/robinsons-loyalty.ext" - #{"transit-engineering/runtime-api" - "transit-engineering/swing.ext" - "transit-engineering/agent.ext"}, - "transit-engineering/swing.ext" #{"transit-engineering/runtime-api"}, - "transit-engineering/telemetry.ext" - #{"transit-engineering/runtime-api"}, - "transit-engineering/healthchecker.ext" - #{"transit-engineering/runtime-api" "transit-engineering/agent.ext"}, - "transit-engineering/scan-hook.ext" - #{"transit-engineering/runtime-api" - "transit-engineering/swing.ext" - "transit-engineering/agent.ext"}, - "transit-engineering/agent.ext" - #{"transit-engineering/runtime-api" "transit-engineering/swing.ext"}, - "transit-engineering/scan-training.ext" - #{"transit-engineering/runtime-api" - "transit-engineering/swing.ext" - "transit-engineering/agent.ext"}}, - :create-tags? false, - :snapshot? false, - } diff --git a/test/fixtures/example_repo/packages/bar/deps.edn b/test/fixtures/example_repo/packages/bar/deps.edn deleted file mode 100644 index 2d117e3..0000000 --- a/test/fixtures/example_repo/packages/bar/deps.edn +++ /dev/null @@ -1,8 +0,0 @@ -{:kmono/package {:group kepler16 - :artifact bar-lib - :build-cmd "just build-and-install" - :release-cmd "just release"} - :paths ["src"] - :aliases {:test {:extra-paths ["test"] - :extra-deps {some/dependency {:mvn/version "1.0.0"}}} - :dev {:extra-paths ["dev"]}}} diff --git a/test/fixtures/example_repo/packages/bar/src/.gitkeep b/test/fixtures/example_repo/packages/bar/src/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/test/fixtures/example_repo/packages/bar/test/.gitkeep b/test/fixtures/example_repo/packages/bar/test/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/test/fixtures/example_repo/packages/foo/deps.edn b/test/fixtures/example_repo/packages/foo/deps.edn deleted file mode 100644 index f8f041e..0000000 --- a/test/fixtures/example_repo/packages/foo/deps.edn +++ /dev/null @@ -1,7 +0,0 @@ -{:kmono/package {:group kepler16 - :artifact foo-lib - :build-cmd "just build-and-install" - :release-cmd "just release"} - :paths ["src"] - :aliases {:test {:extra-paths ["test"] - :extra-deps {kepler16/bar {:local/root "../bar"}}}}} diff --git a/test/fixtures/example_repo/packages/foo/src/.gitkeep b/test/fixtures/example_repo/packages/foo/src/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/test/fixtures/example_repo/packages/foo/test/.gitkeep b/test/fixtures/example_repo/packages/foo/test/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/test/k16/kmono/api_test.clj b/test/k16/kmono/api_test.clj deleted file mode 100644 index 7fdb34c..0000000 --- a/test/k16/kmono/api_test.clj +++ /dev/null @@ -1,120 +0,0 @@ -(ns k16.kmono.api-test - (:require - [babashka.fs :as fs] - [clojure.test :refer [deftest is testing use-fixtures]] - [k16.kmono.api :as api] - [k16.kmono.repl.deps :as repl.deps] - [k16.kmono.test-utils :as test-utils :refer [repo-root]])) - -(use-fixtures :each test-utils/repo-fixture) - -(deftest run-build-test - (test-utils/initialize-git!) - (testing "Build should be successful" - (is (= [true - [{"kmono-test/p2" {:success? true, - :output "build p2\n"}, - "kmono-test/p1" {:success? true, - :output "build p1\n"}, - "kmono-test/root-module" {:success? true, - :output "build root\n"}}]] - (api/-run {:repo-root repo-root - :exec :build} - nil))))) - -(deftest run-release-test - (test-utils/initialize-git!) - (let [release-opts {:repo-root repo-root - :create-tags? true - :include-unchanged? false - :snapshot? false - :exec :release}] - (testing "Release + create initial tags" - (is (= [true - [{"kmono-test/p2" {:success? true, - :output "release p2\n"}, - "kmono-test/p1" {:success? true, - :output "release p1\n"}, - "kmono-test/root-module" {:success? true, - :output "release root\n"}}]] - (api/-run release-opts nil))) - (is (= #{"kmono-test/p1@0.0.0.0" - "kmono-test/p2@0.0.0.0" - "kmono-test/root-module@0.0.0.0"} - (set (test-utils/get-tags))))) - - (testing "Make feat changes to p1 and release again" - (spit (fs/file repo-root "packages/p1/src/foo.clj") - "(ns foo)\n(println :hello)") - (test-utils/shell-commands! ["git add ." - "git commit -m 'feat: p1 foo added'"]) - (Thread/sleep 100) - (is (= [true - [{"kmono-test/p2" {:success? true, :output "no changes"}, - "kmono-test/root-module" {:success? true, :output "no changes"}, - "kmono-test/p1" {:success? true, :output "release p1\n"}}]] - (api/-run release-opts nil))) - (is (= #{"kmono-test/p1@0.1.0.0" - "kmono-test/p1@0.0.0.0" - "kmono-test/p2@0.0.0.0" - "kmono-test/root-module@0.0.0.0"} - (set (test-utils/get-tags))))) - - (testing "Make fix changes to root, p2 and release" - (spit (fs/file repo-root "packages/p2/src/bar.clj") - "(ns bar)\n(println :hello_bar)") - (spit (fs/file repo-root "src/lol.clj") "(ns lol)") - (test-utils/shell-commands! ["git add ." - "git commit -m 'fix: root and p2 bugs'"]) - (Thread/sleep 100) - (is (= [true - [{"kmono-test/p2" {:success? true, - :output "release p2\n"}, - "kmono-test/root-module" {:success? true, - :output "release root\n"}, - "kmono-test/p1" {:success? true, - :output "no changes"}}]] - (api/-run release-opts nil))) - (is (= #{"kmono-test/p2@0.0.1.0" - "kmono-test/root-module@0.0.0.0" - "kmono-test/p1@0.0.0.0" - "kmono-test/p1@0.1.0.0" - "kmono-test/p2@0.0.0.0" - "kmono-test/root-module@0.0.1.0"} - (set (test-utils/get-tags))))))) - -(deftest root-only-test - (fs/delete-tree (fs/file repo-root "packages")) - (test-utils/initialize-git!) - (let [release-opts {:repo-root repo-root - :create-tags? true - :include-unchanged? false - :snapshot? false - :exec :release}] - (testing "Release + create initial tags" - (is (= [true - [{"kmono-test/root-module" {:success? true, - :output "release root\n"}}]] - (api/-run release-opts nil))) - (is (= #{"kmono-test/root-module@0.0.0.0"} - (set (test-utils/get-tags))))))) - -(deftest cp-workplace-test - (let [release-opts {:repo-root repo-root}] - (spit (fs/file repo-root "deps.edn") - (str {:kmono/workspace {:group "kmono-wp-test" - :packages "packages/*" - :aliases [:dev] - :package-aliases [:*/test] - :build-cmd "echo 'build root'" - :release-cmd "echo 'release root'"} - :deps {} - :paths ["src"]})) - (testing "Derive params from workspace" - (with-redefs [repl.deps/cp! (fn [{:keys [package-aliases aliases]} _] - (is (= (set [:kmono/package-deps - :kmono.pkg/p2.test - :kmono.pkg/p1.test]) - (set package-aliases))) - (is (= [:dev] aliases)))] - (api/generate-classpath! release-opts nil))))) diff --git a/test/k16/kmono/config_test.clj b/test/k16/kmono/config_test.clj deleted file mode 100644 index 492cde2..0000000 --- a/test/k16/kmono/config_test.clj +++ /dev/null @@ -1,79 +0,0 @@ -(ns k16.kmono.config-test - (:require - [babashka.fs :as fs] - [clojure.test :refer [deftest is testing use-fixtures]] - [k16.kmono.config :as config] - [k16.kmono.test-utils :as test-utils :refer [repo-root]])) - -(use-fixtures :each test-utils/repo-fixture) - -(deftest load-config-test - (let [config (config/load-config repo-root)] - (testing "Should be 3 packages including root which :root-package? = true" - (is (= {"kmono-test/p1" false - "kmono-test/p2" false - "kmono-test/root-module" true} - (-> config - :package-map - (update-vals :root-package?))))) - (testing "All packages are untracked" - (is (= {"kmono-test/p1" "untracked" - "kmono-test/p2" "untracked" - "kmono-test/root-module" "untracked"} - (-> config - :package-map - (update-vals :commit-sha))))))) - -(deftest tracked-packages-test - (test-utils/initialize-git!) - (let [config (config/load-config repo-root)] - (testing "All packages are tracked and has same sha" - (is (not= #{"untracked"} - (->> config - :package-map - (map :commit-sha) - (set))))))) - -(deftest no-root-config-test - (testing "Should ignore root package if there is no kmono config found" - (spit (fs/file repo-root "deps.edn") - (str {:deps {} - :paths ["src"]})) - (let [config (config/load-config repo-root)] - (is (= {"kmono-test/p1" false - "kmono-test/p2" false} - (-> config - :package-map - (update-vals :root-package?))))))) - -(deftest workspace-test - (testing "Packages should derive opts from workspace if not set" - (let [p1-dir "packages/p1" - p2-dir "packages/p2"] - (spit (fs/file repo-root "deps.edn") - (str {:kmono/workspace {:group "kmono-wp-test" - :package-aliases [:*/test] - :build-cmd "echo 'build root'" - :release-cmd "echo 'release root'"} - :deps {} - :paths ["src"]})) - (spit (fs/file repo-root p1-dir "deps.edn") - (str {:kmono/package {:build-cmd "echo 'build p1'" - :release-cmd "echo 'release p1'"} - :deps {} - :paths ["src"]})) - - (spit (fs/file repo-root p2-dir "deps.edn") - (str {:kmono/package {:group "my-own-group" - :build-cmd "echo 'build p2'" - :release-cmd "echo 'release p2'"} - :deps {} - :paths ["src"]})) - (let [config (config/load-config repo-root)] - (is (= 2 (count (:packages config)))) - (is (= #{"my-own-group/p2" "kmono-wp-test/p1"} - (->> config - :packages - (map :name) - (set)))))))) - diff --git a/test/k16/kmono/git_test.clj b/test/k16/kmono/git_test.clj deleted file mode 100644 index 8da8f7c..0000000 --- a/test/k16/kmono/git_test.clj +++ /dev/null @@ -1,136 +0,0 @@ -(ns k16.kmono.git-test - (:require - [clojure.edn :as edn] - [clojure.java.io :as io] - [clojure.test :refer [deftest is testing]] - [k16.kmono.git :as git])) - -(def config - {:packages [{:name "Z"} - {:name "B"} - {:name "C"} - {:name "D"}] - :graph {"Z" #{} - "D" #{"Z"} - "B" #{"Z"} - "C" #{"B"}}}) - -(deftest dependands-build-test - (testing "It should bump and rebuild all dependands" - (let [changes {"Z" {:changed? false - :package-name "Z" - :version "1.0.0.0"} - "B" {:changed? true - :package-name "B" - :version "1.0.0.0"} - "C" {:changed? false - :package-name "C" - :version "1.0.0.0"} - "D" {:changed? false - :package-name "D" - :version "1.0.0.0"}}] - (is (= {"Z" {:changed? false, :package-name "Z", :version "1.0.0.0"}, - "B" {:changed? true, :package-name "B", :version "1.0.0.0"}, - "C" {:changed? true, :package-name "C", :version "1.0.0.1"} - "D" {:changed? false, :package-name "D", :version "1.0.0.0"}} - (git/ensure-dependend-builds config changes)) - "C should be marked as changed")) - - (let [changes {"Z" {:changed? true - :package-name "Z" - :version "1.0.0.0"} - "B" {:changed? false - :package-name "B" - :version "1.0.0.0"} - "C" {:changed? false - :package-name "C" - :version "1.0.0.0"} - "D" {:changed? false - :package-name "D" - :version "1.0.0.0"}}] - (is (= {"Z" {:changed? true, :package-name "Z", :version "1.0.0.0"}, - "B" {:changed? true, :package-name "B", :version "1.0.0.1"}, - "C" {:changed? true, :package-name "C", :version "1.0.0.1"} - "D" {:changed? true, :package-name "D", :version "1.0.0.1"}} - (git/ensure-dependend-builds config changes)) - "B, C and D should be marked as changed")))) - -(deftest bump-test - (let [version "1.77.2.3"] - - (is (= "2.0.0.0" (git/bump {:version version - :bump-type :major - :commit-sha "deadbee" - :snapshot? false}))) - (is (= "1.78.0.0" (git/bump {:version version - :bump-type :minor - :commit-sha "deadbee" - :snapshot? false}))) - (is (= "1.77.3.0" (git/bump {:version version - :bump-type :patch - :commit-sha "deadbee" - :snapshot? false}))) - (is (= "1.77.3.0-deadbee-SNAPSHOT" (git/bump {:version version - :bump-type :patch - :commit-sha "deadbee" - :snapshot? true}))) - (is (= "1.77.2.4" (git/bump {:version version - :bump-type :build - :commit-sha "deadbee" - :snapshot? false}))) - (is (= "1.77.2.3" (git/bump {:version version - :bump-type :none - :commit-sha "deadbee" - :snapshot? false}))))) - -(deftest dependent-single-bump-test - (let [config (edn/read-string - (slurp (io/resource "fixtures/dependents_config.edn"))) - changes {"transit-engineering/runtime-api" - {:version "0.15.1.0", - :changed? true, - :package-name "transit-engineering/runtime-api"}, - "transit-engineering/jollibee.ext" - {:version "0.0.0.9", - :changed? false, - :package-name "transit-engineering/jollibee.ext"}, - "transit-engineering/session-notification.ext" - {:version "0.6.0.3", - :changed? false, - :package-name "transit-engineering/session-notification.ext"}, - "transit-engineering/runtime" - {:version "2.10.0.30", - :changed? false, - :package-name "transit-engineering/runtime"}, - "transit-engineering/robinsons-loyalty.ext" - {:version "0.1.1.0", - :changed? false, - :package-name "transit-engineering/robinsons-loyalty.ext"}, - "transit-engineering/swing.ext" - {:version "0.1.0.5", - :changed? false, - :package-name "transit-engineering/swing.ext"}, - "transit-engineering/telemetry.ext" - {:version "0.0.1.0", - :changed? true, - :package-name "transit-engineering/telemetry.ext"}, - "transit-engineering/healthchecker.ext" - {:version "0.0.2.9", - :changed? false, - :package-name "transit-engineering/healthchecker.ext"}, - "transit-engineering/scan-hook.ext" - {:version "0.4.0.0", - :changed? true, - :package-name "transit-engineering/scan-hook.ext"}, - "transit-engineering/agent.ext" - {:version "0.12.0.6", - :changed? false, - :package-name "transit-engineering/agent.ext"}, - "transit-engineering/scan-training.ext" - {:version "0.0.0.19", - :changed? false, - :package-name "transit-engineering/scan-training.ext"}}] - (is (= "2.10.0.31" (-> (git/ensure-dependend-builds config changes) - (get "transit-engineering/runtime") - :version))))) - diff --git a/test/k16/kmono/repl/deps_test.clj b/test/k16/kmono/repl/deps_test.clj deleted file mode 100644 index 150e628..0000000 --- a/test/k16/kmono/repl/deps_test.clj +++ /dev/null @@ -1,48 +0,0 @@ -(ns k16.kmono.repl.deps-test - (:require - [clojure.test :refer [deftest is testing]] - [k16.kmono.config :as config] - [k16.kmono.main :as main] - [k16.kmono.repl.deps :as repl.deps])) - -(deftest parse-aliases-test - (testing "Aliases should be parsed correctly" - (is (= [:foo :kmono.pkg/bar] - (main/parse-aliases ":foo:kmono.pkg/bar"))))) - -(deftest some-test - (let [config (config/load-config "test/fixtures/example_repo")] - (testing "Should extract aliases and relativize paths" - (is (= '{:aliases - {:kmono/package-deps - {:extra-deps {kepler16/bar-lib - {:local/root - "test/fixtures/example_repo/packages/bar"}, - kepler16/foo-lib - {:local/root - "test/fixtures/example_repo/packages/foo"}}} - :kmono.pkg/bar.test - {:extra-deps {some/dependency {:mvn/version "1.0.0"}} - :extra-paths ["packages/bar/test"]} - :kmono.pkg/foo.test - {:extra-deps {kepler16/bar {:local/root "packages/bar"}} - :extra-paths ["packages/foo/test"]}}} - (repl.deps/construct-sdeps-overrides! - (assoc config :package-aliases [:bar/test - :foo/test])))) - (is (= '{:aliases - {:kmono/package-deps - {:extra-deps {kepler16/bar-lib - {:local/root - "test/fixtures/example_repo/packages/bar"}, - kepler16/foo-lib - {:local/root - "test/fixtures/example_repo/packages/foo"}}} - :kmono.pkg/bar.test - {:extra-deps {some/dependency {:mvn/version "1.0.0"}} - :extra-paths ["packages/bar/test"]} - :kmono.pkg/foo.test - {:extra-deps {kepler16/bar {:local/root "packages/bar"}} - :extra-paths ["packages/foo/test"]}}} - (repl.deps/construct-sdeps-overrides! - (assoc config :package-aliases [:*/test]))))))) diff --git a/test/k16/kmono/test_utils.clj b/test/k16/kmono/test_utils.clj deleted file mode 100644 index 73d1c84..0000000 --- a/test/k16/kmono/test_utils.clj +++ /dev/null @@ -1,69 +0,0 @@ -(ns k16.kmono.test-utils - (:require - [babashka.fs :as fs] - [babashka.process :as bp] - [clojure.string :as string])) - -(def repo-root ".kmono_test") - -(defn delete-repo [] - (fs/delete-tree repo-root)) - -(defn init-repo [] - (let [p1-dir "packages/p1" - p2-dir "packages/p2" - dirs ["src" - (fs/path p1-dir "src") - (fs/path p2-dir "src")]] - (doseq [d dirs] - (fs/create-dirs (fs/path repo-root d))) - - (spit (fs/file repo-root "deps.edn") - (str {:kmono/package {:group "kmono-test" - :artifact "root-module" - :build-cmd "echo 'build root'" - :release-cmd "echo 'release root'"} - :deps {} - :paths ["src"]})) - - (spit (fs/file repo-root p1-dir "deps.edn") - (str {:kmono/package {:group "kmono-test" - :build-cmd "echo 'build p1'" - :release-cmd "echo 'release p1'"} - :deps {} - :paths ["src"]})) - - (spit (fs/file repo-root p2-dir "deps.edn") - (str {:kmono/package {:group "kmono-test" - :build-cmd "echo 'build p2'" - :release-cmd "echo 'release p2'"} - :deps {} - :paths ["src"]})))) - -(defn repo-fixture [t] - (init-repo) - (t) - (delete-repo)) - -(defn shell-commands! [cmds] - (into {} - (mapv (fn [cmd] - (let [result (bp/shell {:dir repo-root - :out :string} cmd)] - (Thread/sleep 50) - [cmd result])) - cmds))) - -(defn initialize-git! [] - (shell-commands! ["git init -q --initial-branch=main" - "git config user.email \"kmono@test.com\"" - "git config user.name \"kmono\"" - "git add ." - "git commit -m 'initial commit'"])) - -(defn get-tags [] - (let [tags-cmd "git tag --list"] - (-> (shell-commands! [tags-cmd]) - (get tags-cmd) - :out - (string/split-lines)))) diff --git a/tests.edn b/tests.edn index 23dc437..ccfac9f 100644 --- a/tests.edn +++ b/tests.edn @@ -1,3 +1,2 @@ #kaocha/v1 - {:tests [{:id :unit}] - :plugins [:print-invocations :profiling]} + {:reporter [kaocha.report/documentation]}