├── .clj-kondo ├── config.edn └── imports │ ├── babashka │ └── fs │ │ └── config.edn │ ├── http-kit │ └── http-kit │ │ ├── config.edn │ │ └── httpkit │ │ └── with_channel.clj │ ├── lread │ └── status-line │ │ └── config.edn │ ├── rewrite-clj │ └── rewrite-clj │ │ └── config.edn │ └── taoensso │ └── encore │ ├── config.edn │ └── taoensso │ └── encore.clj ├── .github ├── CODEOWNERS └── workflows │ ├── nvd_scanner.yml │ ├── publish.yml │ ├── shared-setup │ └── action.yml │ └── tests.yml ├── .gitignore ├── CHANGELOG.adoc ├── ORIGINATOR ├── README.adoc ├── bb.edn ├── build.clj ├── build └── build_shared.clj ├── deps.edn ├── doc ├── 01-user-guide.adoc ├── 02-developer-guide.adoc ├── 03-maintainer-guide.adoc └── cljdoc.edn ├── epl-v10.html ├── nvd_check_helper_project ├── deps.edn ├── nvd-clojure.edn └── suppressions.xml ├── pom.xml ├── script ├── ci_publish.clj ├── download_deps.clj ├── lint.clj ├── publish.clj └── test_clj.clj ├── src ├── main │ └── clojure │ │ └── cemerick │ │ ├── pomegranate.clj │ │ └── pomegranate │ │ └── aether.clj ├── test-isolated │ └── clojure │ │ └── cemerick │ │ └── pomegranate_isolated_test.clj └── test │ └── clojure │ └── cemerick │ ├── pomegranate │ ├── aether_test.clj │ └── test_report.clj │ └── pomegranate_test.clj └── test-repo ├── demo ├── demo │ ├── 1.0.0 │ │ ├── demo-1.0.0.jar │ │ ├── demo-1.0.0.jar.asc │ │ ├── demo-1.0.0.jar.asc.md5 │ │ ├── demo-1.0.0.jar.asc.sha1 │ │ ├── demo-1.0.0.jar.md5 │ │ ├── demo-1.0.0.jar.sha1 │ │ ├── demo-1.0.0.pom │ │ ├── demo-1.0.0.pom.asc │ │ ├── demo-1.0.0.pom.asc.md5 │ │ ├── demo-1.0.0.pom.asc.sha1 │ │ ├── demo-1.0.0.pom.md5 │ │ └── demo-1.0.0.pom.sha1 │ ├── 1.0.1 │ │ ├── demo-1.0.1-test.jar │ │ ├── demo-1.0.1-test.jar.md5 │ │ ├── demo-1.0.1-test.jar.sha1 │ │ ├── demo-1.0.1.jar │ │ ├── demo-1.0.1.jar.md5 │ │ ├── demo-1.0.1.jar.sha1 │ │ ├── demo-1.0.1.pom │ │ ├── demo-1.0.1.pom.md5 │ │ └── demo-1.0.1.pom.sha1 │ ├── maven-metadata.xml │ ├── maven-metadata.xml.md5 │ └── maven-metadata.xml.sha1 └── demo2 │ ├── 1.0.0 │ ├── demo2-1.0.0.jar │ ├── demo2-1.0.0.jar.md5 │ ├── demo2-1.0.0.jar.sha1 │ ├── demo2-1.0.0.pom │ ├── demo2-1.0.0.pom.md5 │ └── demo2-1.0.0.pom.sha1 │ ├── maven-metadata.xml │ ├── maven-metadata.xml.md5 │ └── maven-metadata.xml.sha1 └── tester └── tester ├── 0.1.0-SNAPSHOT ├── maven-metadata.xml ├── maven-metadata.xml.md5 ├── maven-metadata.xml.sha1 ├── tester-0.1.0-20120403.012847-1.jar ├── tester-0.1.0-20120403.012847-1.jar.md5 ├── tester-0.1.0-20120403.012847-1.jar.sha1 ├── tester-0.1.0-20120403.012847-1.pom ├── tester-0.1.0-20120403.012847-1.pom.md5 └── tester-0.1.0-20120403.012847-1.pom.sha1 ├── maven-metadata.xml ├── maven-metadata.xml.md5 └── maven-metadata.xml.sha1 /.clj-kondo/config.edn: -------------------------------------------------------------------------------- 1 | {:config-paths ^:replace [] ;; don't include user configs 2 | :linters {:used-underscored-binding {:level :warning} 3 | :aliased-namespace-symbol {:level :warning}}} 4 | -------------------------------------------------------------------------------- /.clj-kondo/imports/babashka/fs/config.edn: -------------------------------------------------------------------------------- 1 | {:lint-as {babashka.fs/with-temp-dir clojure.core/let}} 2 | -------------------------------------------------------------------------------- /.clj-kondo/imports/http-kit/http-kit/config.edn: -------------------------------------------------------------------------------- 1 | 2 | {:hooks 3 | {:analyze-call {org.httpkit.server/with-channel httpkit.with-channel/with-channel}}} 4 | -------------------------------------------------------------------------------- /.clj-kondo/imports/http-kit/http-kit/httpkit/with_channel.clj: -------------------------------------------------------------------------------- 1 | (ns httpkit.with-channel 2 | (:require [clj-kondo.hooks-api :as api])) 3 | 4 | (defn with-channel [{node :node}] 5 | (let [[request channel & body] (rest (:children node))] 6 | (when-not (and request channel) (throw (ex-info "No request or channel provided" {}))) 7 | (when-not (api/token-node? channel) (throw (ex-info "Missing channel argument" {}))) 8 | (let [new-node 9 | (api/list-node 10 | (list* 11 | (api/token-node 'let) 12 | (api/vector-node [channel (api/vector-node [])]) 13 | request 14 | body))] 15 | 16 | {:node new-node}))) 17 | -------------------------------------------------------------------------------- /.clj-kondo/imports/lread/status-line/config.edn: -------------------------------------------------------------------------------- 1 | {:lint-as {lread.status-line/line clojure.tools.logging/infof 2 | lread.status-line/die clojure.tools.logging/infof}} 3 | -------------------------------------------------------------------------------- /.clj-kondo/imports/rewrite-clj/rewrite-clj/config.edn: -------------------------------------------------------------------------------- 1 | {:lint-as 2 | {rewrite-clj.zip/subedit-> clojure.core/-> 3 | rewrite-clj.zip/subedit->> clojure.core/->> 4 | rewrite-clj.zip/edit-> clojure.core/-> 5 | rewrite-clj.zip/edit->> clojure.core/->>}} 6 | -------------------------------------------------------------------------------- /.clj-kondo/imports/taoensso/encore/config.edn: -------------------------------------------------------------------------------- 1 | {:hooks 2 | {:analyze-call 3 | {taoensso.encore/defalias taoensso.encore/defalias 4 | taoensso.encore/defn-cached taoensso.encore/defn-cached 5 | taoensso.encore/defonce taoensso.encore/defonce}}} 6 | -------------------------------------------------------------------------------- /.clj-kondo/imports/taoensso/encore/taoensso/encore.clj: -------------------------------------------------------------------------------- 1 | (ns taoensso.encore 2 | "I don't personally use clj-kondo, so these hooks are 3 | kindly authored and maintained by contributors. 4 | PRs very welcome! - Peter Taoussanis" 5 | (:refer-clojure :exclude [defonce]) 6 | (:require 7 | [clj-kondo.hooks-api :as hooks])) 8 | 9 | (defn defalias 10 | [{:keys [node]}] 11 | (let [[sym-raw src-raw] (rest (:children node)) 12 | src (or src-raw sym-raw) 13 | sym (if src-raw sym-raw (symbol (name (hooks/sexpr src))))] 14 | {:node 15 | (with-meta 16 | (hooks/list-node 17 | [(hooks/token-node 'def) 18 | (hooks/token-node (hooks/sexpr sym)) 19 | (hooks/token-node (hooks/sexpr src))]) 20 | (meta src))})) 21 | 22 | (defn defn-cached 23 | [{:keys [node]}] 24 | (let [[sym _opts binding-vec & body] (rest (:children node))] 25 | {:node 26 | (hooks/list-node 27 | (list 28 | (hooks/token-node 'def) 29 | sym 30 | (hooks/list-node 31 | (list* 32 | (hooks/token-node 'fn) 33 | binding-vec 34 | body))))})) 35 | 36 | (defn defonce 37 | [{:keys [node]}] 38 | ;; args = [sym doc-string? attr-map? init-expr] 39 | (let [[sym & args] (rest (:children node)) 40 | [doc-string args] (if (and (hooks/string-node? (first args)) (next args)) [(hooks/sexpr (first args)) (next args)] [nil args]) 41 | [attr-map init-expr] (if (and (hooks/map-node? (first args)) (next args)) [(hooks/sexpr (first args)) (fnext args)] [nil (first args)]) 42 | 43 | attr-map (if doc-string (assoc attr-map :doc doc-string) attr-map) 44 | sym+meta (if attr-map (with-meta sym attr-map) sym) 45 | rewritten 46 | (hooks/list-node 47 | [(hooks/token-node 'clojure.core/defonce) 48 | sym+meta 49 | init-expr])] 50 | 51 | {:node rewritten})) 52 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Code owners are auto-invited to review PRs including files matching specified pattern(s). 2 | # We opt out of this by only matching the CODEOWNERS file itself. 3 | .github/CODEOWNERS @slipset @lread 4 | # For a CODEOWNERS file to be valid, owners must have repo write privs, for this reason remove @glts for now 5 | -------------------------------------------------------------------------------- /.github/workflows/nvd_scanner.yml: -------------------------------------------------------------------------------- 1 | name: Vulnerability Scan 2 | 3 | on: 4 | schedule: 5 | - cron: "5 0 * * *" 6 | push: 7 | branches: 8 | - master 9 | 10 | jobs: 11 | build: 12 | environment: nvd 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v4 17 | 18 | - name: Setup Java 19 | uses: actions/setup-java@v4 20 | with: 21 | distribution: 'temurin' 22 | java-version: 21 23 | 24 | - name: Install Clojure Tools 25 | uses: DeLaGuardo/setup-clojure@13.4 26 | with: 27 | cli: 'latest' 28 | bb: 'latest' 29 | 30 | - name: Generate Cache Key File 31 | run: | 32 | # We are using RELEASE for deps, grab current versions for CI cache key 33 | mkdir -p target 34 | VERSION_NVD_CLOJURE=$(curl -s --fail https://clojars.org/api/artifacts/nvd-clojure/nvd-clojure | jq -r '.recent_versions[0].version') 35 | VERSION_DEPENDENCY_CHECK=$(curl -s --fail 'https://search.maven.org/solrsearch/select?q=g:org.owasp+AND+a:dependency-check-core&core=gav&rows=1&wt=json' | jq '.response.docs[0].v') 36 | echo "nvd-clojure=${VERSION_NVD_CLOJURE}" > target/ci-versions.txt 37 | echo "dependency-check=${VERSION_DEPENDENCY_CHECK}" >> target/ci-versions.txt 38 | cat target/ci-versions.txt 39 | working-directory: nvd_check_helper_project 40 | 41 | - name: Restore NVD DB & Clojure Deps Cache 42 | # nvd caches its db under ~/.m2/repository/org/owasp so that it can 43 | # conveniently be cached with deps 44 | uses: actions/cache/restore@v4 45 | with: 46 | path: | 47 | ~/.m2/repository 48 | ~/.deps.clj 49 | ~/.gitlibs 50 | # because we are using a RELEASE version of nvd-clojure 51 | # we also include its version 52 | key: | 53 | nvd-${{ hashFiles( 54 | 'nvd_check_helper_project/target/ci-versions.txt', 55 | 'nvd_check_helper_project/deps.edn', 56 | 'nvd_check_helper_project/bb.edn', 57 | 'bb.edn') }} 58 | restore-keys: | 59 | nvd- 60 | 61 | - name: Download Clojure deps 62 | run: clojure -X:deps prep 63 | working-directory: nvd_check_helper_project 64 | 65 | - name: Run NVD Scanner 66 | env: 67 | NVD_API_TOKEN: ${{ secrets.NVD_API_TOKEN }} 68 | run: bb nvd-scan 69 | 70 | - name: Save NVD DB & Clojure Deps Cache 71 | if: always() # always cache regardless of outcome of nvd scan 72 | uses: actions/cache/save@v4 73 | with: 74 | path: | 75 | ~/.m2/repository 76 | ~/.deps.clj 77 | ~/.gitlibs 78 | # we tack on github.run_id to uniquely identify the cache 79 | # the next cache restore will find the best (and most current) match 80 | key: | 81 | nvd-${{ hashFiles( 82 | 'nvd_check_helper_project/target/ci-versions.txt', 83 | 'nvd_check_helper_project/deps.edn', 84 | 'nvd_check_helper_project/bb.edn', 85 | 'bb.edn') }}-${{ github.run_id }} 86 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: publish 2 | on: 3 | push: 4 | tags: 5 | - 'v\d+.*' 6 | 7 | jobs: 8 | test: 9 | uses: ./.github/workflows/tests.yml 10 | 11 | publish: 12 | environment: publish 13 | runs-on: ubuntu-latest 14 | needs: [test] 15 | 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v4 19 | 20 | - name: Setup 21 | uses: ./.github/workflows/shared-setup 22 | with: 23 | jdk: '11' 24 | 25 | - name: Deploy to clojars 26 | env: 27 | CLOJARS_USERNAME: ${{ secrets.CLOJARS_USERNAME }} 28 | CLOJARS_PASSWORD: ${{ secrets.CLOJARS_PASSWORD }} 29 | run: bb -ci-clojars-deploy 30 | 31 | - name: Create GitHub Release 32 | env: 33 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 34 | run: bb -ci-github-create-release 35 | 36 | - name: Inform Cljdoc 37 | run: bb -ci-cljdoc-request-build 38 | -------------------------------------------------------------------------------- /.github/workflows/shared-setup/action.yml: -------------------------------------------------------------------------------- 1 | name: 'shared setup' 2 | inputs: 3 | jdk: 4 | description: 'jdk version' 5 | required: true 6 | shell: 7 | # shell must be specified for run:s for composite actions 8 | description: 'which shell to use' 9 | required: false 10 | default: bash 11 | 12 | runs: 13 | using: 'composite' 14 | 15 | steps: 16 | - name: Clojure deps cache 17 | uses: actions/cache@v4 18 | with: 19 | path: | 20 | ~/.m2/repository 21 | ~/.deps.clj 22 | ~/.gitlibs 23 | key: cljdeps-${{ hashFiles('deps.edn', 'bb.edn') }} 24 | restore-keys: cljdeps- 25 | 26 | - name: Setup Java 27 | uses: actions/setup-java@v4 28 | with: 29 | distribution: 'temurin' 30 | java-version: ${{ inputs.jdk }} 31 | 32 | - name: Install Clojure Tools 33 | uses: DeLaGuardo/setup-clojure@13.4 34 | with: 35 | cli: 'latest' 36 | bb: 'latest' 37 | 38 | - name: Tools Versions 39 | shell: ${{ inputs.shell }} 40 | run: | 41 | echo "java -version" 42 | java -version 43 | echo "bb --version" 44 | bb --version 45 | echo "clojure --version" 46 | clojure --version 47 | 48 | - name: Download Clojure Dependencies 49 | shell: ${{ inputs.shell }} 50 | run: bb download-deps 51 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: tests 2 | on: 3 | # allow this workflow to be called from other workflows, namely: publish 4 | workflow_call: 5 | push: 6 | branches: [ "master" ] 7 | pull_request: 8 | branches: [ "master" ] 9 | 10 | jobs: 11 | lint-kondo: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v4 17 | 18 | - name: Setup 19 | uses: ./.github/workflows/shared-setup 20 | with: 21 | jdk: '11' 22 | 23 | - name: Lint 24 | run: bb lint-kondo 25 | 26 | lint-eastwood: 27 | runs-on: ubuntu-latest 28 | 29 | steps: 30 | - name: Checkout 31 | uses: actions/checkout@v4 32 | 33 | - name: Setup 34 | uses: ./.github/workflows/shared-setup 35 | with: 36 | jdk: '24' 37 | 38 | - name: Lint 39 | run: bb lint-eastwood 40 | 41 | test: 42 | runs-on: ${{ matrix.os.name }}-latest 43 | strategy: 44 | fail-fast: false 45 | matrix: 46 | os: [{name: 'windows', shell: 'pwsh'}, {name: 'ubuntu', shell: 'bash'}] 47 | jdk: ['8', '11', '17', '21', '24'] 48 | 49 | name: ${{matrix.os.name}} - jdk ${{ matrix.jdk }} 50 | 51 | steps: 52 | # 53 | # Tell git not to convert newlines on checkout for Windows 54 | # Our tests include a maven test repo whose checksums would be affected by newline 55 | # conversion. 56 | # 57 | - name: Prepare git (Windows) 58 | run: git config --global core.autocrlf false 59 | if: matrix.os.name == 'windows' 60 | 61 | - name: Checkout 62 | uses: actions/checkout@v4 63 | 64 | - name: Setup 65 | uses: ./.github/workflows/shared-setup 66 | with: 67 | jdk: ${{ matrix.jdk }} 68 | shell: ${{ matrix.os.shell }} 69 | 70 | - name: Run tests 71 | run: bb test --clj-version :all 72 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # emacs + vi backup files 2 | *~ 3 | .*.sw* 4 | 5 | # various IDE junk 6 | *.ipr 7 | *.iml 8 | *.iws 9 | .project 10 | .classpath 11 | .settings 12 | .nrepl-port 13 | .vscode 14 | 15 | target 16 | classes 17 | it-repo 18 | tmp 19 | .cpcache 20 | .externalToolBuilders 21 | .eastwood 22 | # for kondo and lsp caches 23 | .cache 24 | 25 | # a spot for fiddly stuff you want to keep but not commit 26 | /fiddle 27 | -------------------------------------------------------------------------------- /CHANGELOG.adoc: -------------------------------------------------------------------------------- 1 | = Changelog 2 | 3 | A release with known breaking changes is marked with: 4 | 5 | * [breaking] you probably need to change your code 6 | * [minor breaking] you likely don't need to change your code 7 | 8 | // DO NOT EDIT: the "Unreleased" section header is automatically updated by bb publish 9 | // bb publish will fail on any of: 10 | // - unreleased section not found, 11 | // - unreleased section empty 12 | // - optional attribute is not [breaking] or [minor breaking] 13 | // (adjust these in publish.clj as you see fit) 14 | == Unreleased 15 | 16 | == v1.2.24 - 2024-05-07 [[v1.2.24]] 17 | 18 | * Fix: Default scope for managed dependencies should not be hard-coded to `compile` 19 | (https://github.com/clj-commons/pomegranate/issues/125[#125]) 20 | (https://github.com/marcobiscaro2112[@marcobiscaro2112]) 21 | * Docs: minor updates/corrections 22 | 23 | https://github.com/clj-commons/pomegranate/compare/v1.2.23\...v1.2.24[commit log] 24 | 25 | == v1.2.23 - 2023-02-13 [[v1.2.23]] 26 | 27 | * Bump stale deps, some of which had vulnerabilities 28 | (https://github.com/clj-commons/pomegranate/issues/134[#134]) 29 | (https://github.com/lread[@lread]) 30 | * Don't accept empty but truthy `:repositories` 31 | (https://github.com/clj-commons/pomegranate/pull/133[#133]) 32 | (https://github.com/vemv[@vemv]) 33 | * General quality 34 | ** Review and update docs and docstrings. 35 | (https://github.com/clj-commons/pomegranate/issues/149[#149]) 36 | (https://github.com/clj-commons/pomegranate/issues/153[#153]) 37 | (https://github.com/lread[@lread]) 38 | ** Update automated testing to cover Linux, Windows, current JDKs, and Clojure v1.4+ 39 | (https://github.com/clj-commons/pomegranate/issues/137[#137]) 40 | (https://github.com/lread[@lread]) 41 | ** Add automated check for vulnerabilities in dependencies 42 | (https://github.com/clj-commons/pomegranate/pull/135[#135]) 43 | (https://github.com/lread[@lread]) 44 | ** Address all reflection warnings 45 | (https://github.com/clj-commons/pomegranate/pull/131[#131]) 46 | (https://github.com/vemv[@vemv]) 47 | ** Add automated clj-kondo linting 48 | (https://github.com/clj-commons/pomegranate/pull/139[#139]) 49 | (https://github.com/lread[@lread]) 50 | ** Add automated release workflow 51 | (https://github.com/clj-commons/pomegranate/pull/138[#138]) 52 | (https://github.com/lread[@lread]) 53 | ** Dependencies now specified only once 54 | (https://github.com/clj-commons/pomegranate/pull/136[#136]) 55 | (https://github.com/lread[@lread]) 56 | ** Review and update classpath related tests 57 | (https://github.com/clj-commons/pomegranate/pull/154[#154]) 58 | (https://github.com/lread[@lread]) 59 | 60 | https://github.com/clj-commons/pomegranate/compare/Release-1.2.1\...v1.2.23[commit log] 61 | 62 | == v1.2.1 - 2021-04-12 63 | 64 | * Upgrade wagon libs from 3.2.2 \-> 3.3.4 65 | 66 | == v1.2.0 - 2020-01-10 67 | 68 | * Bump deps 69 | * Add `deps.edn` 70 | (https://github.com/slipset[@slipset]) 71 | * Configure session to download signature checksums by default 72 | (https://github.com/clj-commons/pomegranate/issues/113[#113]) 73 | (https://github.com/glts[@glts]) 74 | * Prep for first release from clj-commons 75 | (https://github.com/slipset[@slipset]) 76 | ** Change deploy coordinates from cemerick to clj-commons 77 | ** Change deploy destination from Maven Central to Clojars 78 | ** Switch from Travis CI to CircleCI for tests 79 | 80 | == v1.1.1 - 2020-01-10 81 | 82 | * Bad release, replaced by v1.2.0 83 | 84 | == https://github.com/cemerick/pomegranate/milestone/9?closed=1[v1.1.0] - 2018-10-02 85 | 86 | * Upgrade Maven Resolver dependencies (gh-103) 87 | 88 | == https://github.com/cemerick/pomegranate/milestone/8?closed=1[v1.0.0] [breaking] - 2017-11-03 89 | 90 | * Ensure JDK 9 compatibility via dynapath 1.0.0. 91 | A non-trivial breaking change. 92 | Change is to avoid JDK 9's warnings re: reflective calls into non-public methods, etc. 93 | This means ``URLClassLoader``s are no longer modifiable by default. (gh-92) 94 | 95 | == https://github.com/cemerick/pomegranate/issues?q=milestone%3A0.4.0+is%3Aclosed[v0.4.0] [minor breaking] - 2017-08-25 96 | 97 | * Potentially breaking: Non-TLS HTTP repositories are unsupported; using a "bare" HTTP repository now requires registering a "wagon" for the insecure transport method (gh-83) 98 | * Switched/upgraded from Sonatype Aether to Maven-resolver (gh-80) 99 | * Upgraded to Maven `3.5.0` (gh-83) 100 | * `add-dependencies` now allows you to specify which `ClassLoader` to modify (gh-63) 101 | * Repository names can now be any `Named` value (strings, keywords, or symbols) (gh-84) 102 | * Some previously-internal functions are now marked as public: 103 | `merge-versions-from-managed-coords` (gh-74), and `dependency` and `make-repository` (gh-69) 104 | 105 | == https://github.com/cemerick/pomegranate/issues?q=milestone%3A0.3.1+is%3Aclosed[v0.3.1] - 2016-03-22 106 | 107 | * Add support for "managed" dependencies in `resolve-dependencies` (gh-72) 108 | 109 | == https://github.com/cemerick/pomegranate/issues?milestone=5&page=1&state=closed[v0.3.0] - 2014-02-17 110 | 111 | * Added `cemerick.pomegranate.aether/resolve-artifacts`, which allows for the resolution of a sequence of artifacts, without doing transitive resolution on their dependencies. (gh-57) 112 | * Provide an error if a registered wagon factory fails to instantiate (gh-58) 113 | 114 | == v0.2.0 [minor breaking] - 2013-03-18 115 | 116 | _This release contains breaking changes from `0.1.3`, though it's expected that 117 | the affected APIs are rarely used. 118 | 119 | *Dynamic classpath support (`cemerick.pomegranate`)* 120 | 121 | * The `URLClasspath` protocol has been removed in favor of using 122 | https://github.com/tobias/dynapath/[dynapath]'s `DynamicClasspath` protocol. 123 | (gh-43) _breaking change_ 124 | * `classloader-hierarchy` now starts at the current thread context classloader instead of Clojure's "baseLoader". _breaking change_ 125 | * New `resources` and `classloader-resources` fns for determining from which classloader(s) a given resource is available (gh-48) 126 | 127 | *Aether API (`cemerick.pomegranate.aether` and friends)* 128 | 129 | * `install-artifacts` and `deploy-artifacts` are now generalized to operate over multiple files (vs. the prior assumptions re: an artifact + a POM) (gh-52) 130 | * `resolve-dependencies*` now available that returns the bare Aether results of dependency resolution, _sans_ Clojure-friendly graphification (gh-50) 131 | * `resolve-dependencies`, `install-artifacts`, and `deploy-artifacts` now accept an optional `:repository-session-fn` to potentially modify the Aether `RespositorySystemSession` prior to its use (gh-56) 132 | -------------------------------------------------------------------------------- /ORIGINATOR: -------------------------------------------------------------------------------- 1 | @cemerick 2 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | = Pomegranate 2 | :project-coords: clj-commons/pomegranate 3 | 4 | A sane Clojure API for Maven Artifact Resolver + dynamic runtime modification of the classpath. 5 | 6 | //Badges 7 | https://clojars.org/{project-coords}[image:https://img.shields.io/clojars/v/{project-coords}.svg[Clojars Project]] 8 | https://cljdoc.org/d/{project-coords}[image:https://cljdoc.org/badge/{project-coords}[cljdoc badge]] 9 | https://github.com/{project-coords}/actions/workflows/tests.yml[image:https://github.com/{project-coords}/actions/workflows/tests.yml/badge.svg[CI tests]] 10 | https://github.com/{project-coords}/actions/workflows/nvd_scanner.yml[image:https://github.com/{project-coords}/actions/workflows/nvd_scanner.yml/badge.svg[Vulnerability Scan]] 11 | https://clojurians.slack.com/archives/C04KJME1UPL[image:https://img.shields.io/badge/slack-join_chat-brightgreen.svg[Slack chat]] 12 | 13 | == Documentation 14 | 15 | * link:doc/01-user-guide.adoc[User Guide] 16 | * link:CHANGELOG.adoc[Changelog] 17 | * link:doc/02-developer-guide.adoc[Developer Guide] 18 | * link:doc/03-maintainer-guide.adoc[Maintainer Guide] 19 | 20 | == Used In... 21 | 22 | Some projects that use Pomegranate are: 23 | 24 | * https://leiningen.org/[Leiningen] - For automating Clojure projects without setting your hair on fire 25 | * https://github.com/slipset/deps-deploy[deps-deploy] - Deploy your stuff 26 | * https://github.com/seancorfield/clj-new[clj-new] - Generate new projects based on clj, Boot, or Leiningen Templates! 27 | * https://github.com/benedekfazekas/mranderson[MrAnderson] - Dependency inlining and shadowing 28 | * https://github.com/clojure-emacs/enrich-classpath[enrich-classpath] - Enriches Lein/deps.edn dependency trees with Java sources, JDK sources, javadocs, etc 29 | * https://github.com/pink-gorilla/nrepl-middleware[nrepl-middleware] - Websocket relay, Eval sniffing, core.async client, notebook rendering 30 | * https://github.com/jsa-aerial/saite[saite] - Interactive document creation for exploratory graphics and visualizations 31 | * https://github.com/ndevreeze/genie[Genie] - Run Clojure scripts with a daemon process 32 | 33 | Are you using Pomegranate? Let us know and we'll add your project here! 34 | 35 | == People 36 | 37 | === Contributors 38 | A huge thank you to https://github.com/clj-commons/pomegranate/graphs/contributors[all the people who have contributed directly to Pomegranate]! 39 | 40 | === Founders 41 | 42 | * https://github.com/cemerick[@cemerick] - the creator of Pomegranate 43 | 44 | === Current Active Maintainers 45 | 46 | * https://github.com/lread[@lread] 47 | 48 | == License 49 | 50 | Copyright © 2011-2017 https://cemerick.com[Chas Emerick] and all other contributors. + 51 | Licensed under the EPL, see link:epl-v10.html[epl-v10.html] for details. 52 | -------------------------------------------------------------------------------- /bb.edn: -------------------------------------------------------------------------------- 1 | {:paths ["script" "build"] 2 | :deps {lread/status-line {:git/url "https://github.com/lread/status-line.git" 3 | :sha "cf44c15f30ea3867227fa61ceb823e5e942c707f"} 4 | io.github.babashka/neil {:git/tag "v0.3.68" :git/sha "78ffab1"}} 5 | :tasks {;; setup 6 | :requires ([babashka.fs :as fs] 7 | [clojure.string :as string] 8 | [lread.status-line :as status]) 9 | :enter (let [{:keys [name]} (current-task)] (status/line :head "TASK %s %s" name (string/join " " *command-line-args*))) 10 | :leave (let [{:keys [name]} (current-task)] (status/line :detail "\nTASK %s done." name)) 11 | 12 | ;; tasks 13 | clean 14 | {:doc "delete build work" 15 | :task (when (fs/exists? "target") 16 | (fs/delete-tree "target"))} 17 | download-deps 18 | {:doc "bring down Clojure deps" 19 | :task download-deps/-main} 20 | test 21 | {:doc "Runs tests under Clojure [--clj-version] [--suite] [--help] (recognizes cognitect test-runner args)" 22 | :requires ([test-clj]) 23 | :task (apply test-clj/-main *command-line-args*)} 24 | lint-kondo 25 | {:doc "[--rebuild] Lint source code with clj-kondo" 26 | :task lint/-main} 27 | lint-eastwood 28 | {:doc "Lint source code with Eastwood" 29 | :task (clojure "-M:test:isolated:eastwood")} 30 | lint 31 | {:doc "Run all lints" 32 | :depends [lint-kondo lint-eastwood]} 33 | outdated 34 | {:doc "Report on outdated dependencies" 35 | :task (do 36 | (clojure {:continue true} "-M:outdated --directory=.:nvd_check_helper_project") 37 | (status/line :warn "Do be sure there is a good reason to ugprade org.apache.maven.* deps") )} 38 | nvd-scan 39 | {:doc "Check for security vulnerabilities in dependencies" 40 | :task (let [cp (with-out-str (clojure "-Spath"))] 41 | (clojure {:dir "./nvd_check_helper_project"} 42 | "-J-Dclojure.main.report=stderr -M -m nvd.task.check" 43 | "./nvd-clojure.edn" 44 | cp))} 45 | pubcheck 46 | {:doc "Run only publish checks (without publishing)" 47 | :task publish/pubcheck} 48 | publish 49 | {:doc "Publish a release (for maintainers)" 50 | :task publish/-main} 51 | install 52 | {:doc "Install to local maven repo (usually for local adhoc testing)" 53 | :task (clojure "-T:build install")} 54 | neil ;; let's not rely on a random version of neil 55 | {:doc "Pinned version of babashka/neil (used in scripting)" 56 | :task babashka.neil/-main} 57 | 58 | ;; hidden tasks, no need for folks to be trying these ci invoked tasks 59 | -ci-clojars-deploy 60 | {:doc "triggered on ci by release tag" 61 | :task ci-publish/clojars-deploy} 62 | -ci-github-create-release 63 | {:doc "triggered on ci by release tag" 64 | :task ci-publish/github-create-release} 65 | -ci-cljdoc-request-build 66 | {:doc "ask cljdoc to build docs for new release" 67 | :task ci-publish/cljdoc-request-build}}} 68 | -------------------------------------------------------------------------------- /build.clj: -------------------------------------------------------------------------------- 1 | (ns build 2 | (:require [build-shared] 3 | [clojure.tools.build.api :as b])) 4 | 5 | (def version (build-shared/lib-version)) 6 | (def lib (build-shared/lib-artifact-name)) 7 | 8 | ;; build constants 9 | (def class-dir "target/classes") 10 | (def basis (b/create-basis {:project "deps.edn"})) 11 | (def jar-file (format "target/%s-%s.jar" (name lib) version)) 12 | 13 | (defn jar [_] 14 | (println "jarring version" version) 15 | (b/write-pom {:class-dir class-dir 16 | :lib lib 17 | :version version 18 | :scm {:tag (build-shared/version->tag version)} 19 | :basis basis 20 | :src-dirs ["src/main/clojure"]}) 21 | (b/copy-dir {:src-dirs ["src/main/clojure"] 22 | :target-dir class-dir}) 23 | (b/jar {:class-dir class-dir 24 | :jar-file jar-file})) 25 | 26 | (defn install [_] 27 | (jar {}) 28 | (println "installing version" version) 29 | (b/install {:basis basis 30 | :lib lib 31 | :version version 32 | :jar-file jar-file 33 | :class-dir class-dir})) 34 | 35 | (defn deploy [opts] 36 | (jar opts) 37 | (println "deploy") 38 | ((requiring-resolve 'deps-deploy.deps-deploy/deploy) 39 | (merge {:installer :remote 40 | :artifact jar-file 41 | :pom-file (b/pom-path {:lib lib :class-dir class-dir})} 42 | opts)) 43 | opts) 44 | -------------------------------------------------------------------------------- /build/build_shared.clj: -------------------------------------------------------------------------------- 1 | (ns build-shared 2 | "a few things that are both needed by bb script code and build.clj" 3 | (:require [clojure.string :as string] 4 | [clojure.edn :as edn])) 5 | 6 | (defn- project-info [] 7 | (-> (edn/read-string (slurp "deps.edn")) 8 | :aliases :neil :project)) 9 | 10 | (def version-tag-prefix "v") 11 | 12 | (defn lib-version [] 13 | (-> (project-info) :version)) 14 | 15 | (defn lib-artifact-name [] 16 | (-> (project-info) :name)) 17 | 18 | ;; happens to be the same for this project 19 | (def lib-github-coords lib-artifact-name) 20 | 21 | (defn version->tag [version] 22 | (str version-tag-prefix version)) 23 | 24 | (defn tag->version [ci-tag] 25 | (and (string/starts-with? ci-tag version-tag-prefix) 26 | (string/replace-first ci-tag version-tag-prefix ""))) 27 | -------------------------------------------------------------------------------- /deps.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src/main/clojure"] 2 | :deps {;; our current minimum supported Clojure version 3 | org.clojure/clojure {:mvn/version "1.4.0"} 4 | ;; TIP: be sure you really want to bump org.apache.maven deps 5 | org.apache.maven/maven-resolver-provider {:mvn/version "3.8.7"} 6 | org.apache.maven.resolver/maven-resolver-api {:mvn/version "1.9.4"} 7 | org.apache.maven.resolver/maven-resolver-spi {:mvn/version "1.9.4"} 8 | org.apache.maven.resolver/maven-resolver-util {:mvn/version "1.9.4"} 9 | org.apache.maven.resolver/maven-resolver-impl {:mvn/version "1.9.4"} 10 | org.apache.maven.resolver/maven-resolver-transport-file {:mvn/version "1.9.4"} 11 | org.apache.maven.resolver/maven-resolver-transport-http {:mvn/version "1.9.4"} 12 | org.apache.maven.resolver/maven-resolver-transport-wagon {:mvn/version "1.9.4"} 13 | org.apache.maven.resolver/maven-resolver-connector-basic {:mvn/version "1.9.4"} 14 | org.apache.maven.wagon/wagon-provider-api {:mvn/version "3.5.3" 15 | :exclusions [org.codehaus.plexus/plexus-utils]} 16 | org.apache.maven.wagon/wagon-http {:mvn/version "3.5.3"} 17 | org.apache.maven.wagon/wagon-ssh {:mvn/version "3.5.3"} 18 | 19 | org.tcrawley/dynapath {:mvn/version "1.1.0"} 20 | org.apache.httpcomponents/httpclient {:mvn/version "4.5.14"} 21 | org.apache.httpcomponents/httpcore {:mvn/version "4.4.16"}} 22 | 23 | :aliases {;; we use babashka/neil for project attributes 24 | ;; publish workflow references these values (and automatically bumps patch component of version) 25 | :neil {:project {:version "1.2.24" 26 | ;; artifact deploy name (and also, by chance, GitHub coordinates) 27 | :name clj-commons/pomegranate}} 28 | 29 | ;; support testing against specific versions of Clojure 30 | :1.4 {:override-deps {org.clojure/clojure {:mvn/version "1.4.0"}}} 31 | :1.5 {:override-deps {org.clojure/clojure {:mvn/version "1.5.1"}}} 32 | :1.6 {:override-deps {org.clojure/clojure {:mvn/version "1.6.0"}}} 33 | :1.7 {:override-deps {org.clojure/clojure {:mvn/version "1.7.0"}}} 34 | :1.8 {:override-deps {org.clojure/clojure {:mvn/version "1.8.0"}}} 35 | :1.9 {:override-deps {org.clojure/clojure {:mvn/version "1.9.0"}}} 36 | :1.10 {:override-deps {org.clojure/clojure {:mvn/version "1.10.3"}}} 37 | :1.11 {:override-deps {org.clojure/clojure {:mvn/version "1.11.4"}}} 38 | :1.12 {:override-deps {org.clojure/clojure {:mvn/version "1.12.1"}}} 39 | 40 | :test {:extra-paths ["src/test/clojure"] 41 | :extra-deps {io.github.cognitect-labs/test-runner {:git/tag "v0.5.1" :git/sha "dfb30dd"} 42 | org.slf4j/slf4j-simple {:mvn/version "2.0.17"}} 43 | :main-opts ["-m" "cognitect.test-runner" "-d" "src/test/clojure"]} 44 | ;; some tests affect classloaders and classpaths, we run them separately to not pollute jvm state 45 | ;; ex usage: clojure -M:1.10:test:isolated 46 | :isolated {:extra-paths ["src/test-isolated/clojure"] 47 | ;; override :test :main-opts 48 | :main-opts ["-m" "cognitect.test-runner" "-d" "src/test-isolated/clojure"]} 49 | ;; user older runner for compatibility with Clojure < 1.8, 50 | ;; example usage: clojure -M:1.4:test:old-runner 51 | :old-runner {:override-deps {io.github.cognitect-labs/test-runner 52 | ^:antq/exclude 53 | {:git/sha "209b64504cb3bd3b99ecfec7937b358a879f55c1"}}} 54 | 55 | :build {:extra-paths ["build"] 56 | :deps {io.github.clojure/tools.build {:mvn/version "0.10.9"} 57 | slipset/deps-deploy {:mvn/version "0.2.2"}} 58 | :ns-default build} 59 | 60 | ;; for consistent linting we use a specific version of clj-kondo through the jvm 61 | :clj-kondo {:extra-deps {clj-kondo/clj-kondo {:mvn/version "2025.06.05"}} 62 | :override-deps {org.clojure/clojure {:mvn/version "1.12.1"}} 63 | :main-opts ["-m" "clj-kondo.main"]} 64 | :eastwood {:main-opts ["-m" "eastwood.lint" {:exclude-namespaces [cognitect.test-runner] 65 | :ignored-faults {:local-shadows-var {cemerick.pomegranate.aether true}}}] 66 | :override-deps {org.clojure/clojure {:mvn/version "1.12.1"}} 67 | :extra-deps {jonase/eastwood {:mvn/version "1.4.3"}}} 68 | 69 | :outdated {:extra-deps {com.github.liquidz/antq {:mvn/version "2.11.1276"} 70 | org.clojure/clojure {:mvn/version "1.12.1"} 71 | org.slf4j/slf4j-simple {:mvn/version "2.0.17"} ;; to rid ourselves of logger warnings 72 | } 73 | :main-opts ["-m" "antq.core"]}}} 74 | -------------------------------------------------------------------------------- /doc/01-user-guide.adoc: -------------------------------------------------------------------------------- 1 | = User Guide 2 | :toclevels: 5 3 | :toc: 4 | // DO NOT EDIT: the lib-version parameter is automatically updated by bb publish 5 | :lib-version: 1.2.24 6 | 7 | == Audience 8 | You want to learn more about how to use the Pomegranate library from your app or library. 9 | 10 | == Introduction 11 | 12 | Pomegranate is a library that provides: 13 | 14 | 1. A sane Clojure API for the https://maven.apache.org/resolver[Maven Artifact Resolver]. 15 | 2. A re-implementation of Clojure core's deprecated https://clojure.github.io/clojure/clojure.core-api.html#clojure.core/add-classpath[`add-classpath`] that: 16 | ** is a little more comprehensive, it should work as expected in more circumstances 17 | ** optionally uses the Maven Artifact Resolver to add a Maven artifact (and all of its transitive dependencies) to your Clojure runtime's classpath dynamically. 18 | 19 | NOTE: When Pomegranate was created, the Maven Artifact Resolver was called Aether, so you'll see `aether` in our APIs and docs. 20 | 21 | === Interesting Alternatives 22 | 23 | If Pomegranate is not your cup of tea, consider having a look at: 24 | 25 | * https://github.com/clojure/tools.deps[clojure/tools.deps] 26 | + 27 | Like Pomegranate, Clojure's tools.deps resolves dependencies using the Maven Artifact Resolver. 28 | Unlike Pomegranate, by design, it is focused on runtime dependencies only. 29 | + 30 | Clojure v1.12, includes support for https://github.com/clojure/clojure/blob/master/changes.md#21-add-libraries-for-interactive-use[adding libraries at runtime]. 31 | * https://github.com/borkdude/deps.add-lib[borkdude/deps.add-lib] 32 | + 33 | Clojure v1.12's add-lib feature for leiningen and/or other environments without a specific version of the clojure CLI 34 | * https://github.com/lambdaisland/classpath[com.lambdaisland.classpath] 35 | + 36 | Experimental utilities for dealing with "the classpath", and dynamically loading libraries. 37 | 38 | === History 39 | 40 | * Oct 2011 - The first version of `cemerick/pomegranate` is released to Maven Central. 41 | * Jan 2020 - Clj-commons adopts Pomegranate where it can get some ongoing love and care. 42 | It makes its first release under `clj-commons/pomegranate` to Clojars. 43 | 44 | == Installation 45 | 46 | === Leiningen `project.clj` 47 | 48 | [source,clojure,subs="attributes+"] 49 | ---- 50 | [clj-commons/pomegranate "{lib-version}"] 51 | ---- 52 | 53 | === Clojure CLI `deps.edn` 54 | 55 | [source,clojure,subs="attributes+"] 56 | ---- 57 | clj-commons/pomegranate {:mvn/version "{lib-version}"} 58 | ---- 59 | 60 | === As a Git Dependency 61 | 62 | To get the latest changes that are not yet released to Clojars, you can use this library as a git dependency, for example: 63 | 64 | [source,clojure] 65 | ---- 66 | $ cat deps.edn 67 | {:deps {clj-commons/pomegranate {:git/url "https://github.com/clj-commons/pomegranate.git" 68 | :git/sha "4db42b2091f363bff48cbb80bc5230c3afa598d9"}}} 69 | ---- 70 | 71 | Replace the `:git/sha` value as appropriate. 72 | 73 | == Usage 74 | 75 | === Modifying the Classpath 76 | 77 | To set the stage: you're at the REPL, and you've got some useful data that you'd like to munge and analyze in various ways. 78 | Maybe it's something you've generated locally, maybe it's data on a production machine, and you're logged in via https://github.com/clojure/tools.nrepl[nREPL]. 79 | In any case, you'd like to work with the data, but realize that you don't have the libraries you need do what you want. 80 | Your choices at this point are: 81 | 82 | 1. Dump the data to disk via `pr` (assuming it's just Clojure data structures!), and start up a new Clojure process with the appropriate libraries on the classpath. 83 | This can really suck if the data is in a remote environment. 84 | 2. There is no second choice. 85 | You _could_ use Clojure's deprecated `add-claspath`, but the libraries you want to add have 12 bajillion dependencies, and there's no way you're going to hunt them down manually. 86 | 87 | Let's say we want to use https://github.com/liebke/incanter[Incanter]. 88 | Incanter has roughly 40 dependencies — far too many for us to reasonably locate and add via `add-classpath` manually: 89 | 90 | [source,clojure] 91 | ---- 92 | user=> (require '[incanter core stats charts]) 93 | Execution error (FileNotFoundException) at user/eval1 (REPL:1). 94 | Could not locate incanter/core__init.class, incanter/core.clj or incanter/core.cljc on classpath. 95 | ---- 96 | 97 | Looks bleak. 98 | Assuming you've got Pomegranate on your classpath already, you can do this though: 99 | 100 | [source,clojure] 101 | ---- 102 | user=> (require '[cemerick.pomegranate :as pom] 103 | '[cemerick.pomegranate.aether :as aether]) 104 | nil 105 | user=> (pom/add-dependencies :coordinates '[[incanter "1.9.2"]] 106 | :repositories (merge aether/maven-central 107 | {"clojars" "https://clojars.org/repo"})) 108 | ;...add-dependencies returns full dependency graph here... 109 | user=> (require '[incanter core stats charts]) 110 | nil 111 | ---- 112 | 113 | Now you can analyze and chart away, Incanter having been added to your runtime. 114 | Note that `add-dependencies` may crunch along for a while — it may need to download dependencies, so you're waiting on the network. 115 | 116 | All resolved dependencies are stored in the default local maven repository (`~/.m2/repository`). 117 | A dependency is only downloaded if it does not already exist in the local repository. 118 | 119 | The arguments to `add-dependencies` look like Leiningen-style notation, and they are. 120 | 121 | [TIP] 122 | ==== 123 | **There are a number of scenarios in which `add-dependencies` will not work, or will not work as you'd expect**. 124 | Many of these are due to the nature of JVM classloaders (e.g. adding jars containing conflicting versions of a particular dependency will rarely end well), which Pomegranate does not currently attempt to hide. 125 | Thus, `add-classpath` and `add-dependencies` should be considered escape hatches to be used when necessary, rather than a regular part of your development workflow. 126 | ==== 127 | 128 | === Modifying the Classpath and JDK 9+ 129 | When Pomegranate was created, the JDK was amenable to inspecting and modifying class loaders. 130 | This changed starting with JDK version 9. 131 | Reflection API restrictions, modules, and encapsulation have given us less wiggle room. 132 | 133 | Pomegranate `1.0.0` adapted to the new reality by no longer attempting to modify `java.net.URLClassLoader` instances via reflection. 134 | 135 | Pomegranate now leans on the modifiability of `clojure.lang.DynamicClassLoader`. 136 | As long as this classloader is available, we can modify the classpath. 137 | 138 | If you find yourself in a situation where you want to use Pomegranate but have no dynamic classloader available, you might consider: 139 | 140 | * creating your own modifiable classloader, per the https://github.com/tobias/dynapath#note-on-urlclassloader[dynapath README], https://github.com/boot-clj/boot/commit/a046a497a8bb7f3d1e7aa8d4db4a81c51beaef7d[like boot did]. 141 | * ensuring Clojure's dynamic classloader available like https://github.com/lambdaisland/kaocha/blob/7fb8134ecc2f282300c797efe83cd9fd105eb8b4/src/kaocha/classpath.clj#L11-L24[like kaocha did]. 142 | 143 | === The Aether API 144 | 145 | Here we go over some simple example usages to get your feet wet. 146 | Please consult the API docs, they describe all available options. 147 | 148 | ==== Dependency Resolution 149 | 150 | We'll do some setup in our REPL first: 151 | 152 | [source,clojure] 153 | ---- 154 | (require '[cemerick.pomegranate.aether :as aether]) 155 | 156 | ;; by default Pomegranate consults maven central, let's include clojars: 157 | (alter-var-root #'aether/maven-central assoc "clojars" "https://repo.clojars.org") 158 | ;; => {"central" "https://repo1.maven.org/maven2/", "clojars" "https://repo.clojars.org"} 159 | ---- 160 | 161 | Let's try resolving an artifact: 162 | 163 | [source,clojure] 164 | ---- 165 | (aether/resolve-artifacts :coordinates '[[metosin/malli "0.10.0"]]) 166 | ;; => ([metosin/malli "0.10.0"]) 167 | ---- 168 | 169 | Okay, not too exciting, maybe, but now let's resolve dependencies for that artifact: 170 | 171 | [source,clojure] 172 | ---- 173 | (aether/resolve-dependencies :coordinates '[[metosin/malli "0.10.0"]]) 174 | ;; => {[org.clojure/clojure "1.8.0"] nil, 175 | ;; [org.clojure/test.check "1.1.1"] nil, 176 | ;; [org.clojure/core.rrb-vector "0.1.2"] nil, 177 | ;; [fipp "0.6.26"] #{[org.clojure/clojure "1.8.0"] [org.clojure/core.rrb-vector "0.1.2"]}, 178 | ;; [borkdude/edamame "1.0.0"] #{[org.clojure/tools.reader "1.3.4"]}, 179 | ;; [metosin/malli "0.10.0"] #{[org.clojure/test.check "1.1.1"] 180 | ;; [fipp "0.6.26"] 181 | ;; [borkdude/edamame "1.0.0"] 182 | ;; [borkdude/dynaload "0.3.5"] 183 | ;; [mvxcvi/arrangement "2.0.0"]}, 184 | ;; [org.clojure/tools.reader "1.3.4"] nil, 185 | ;; [borkdude/dynaload "0.3.5"] nil, 186 | ;; [mvxcvi/arrangement "2.0.0"] nil} 187 | ---- 188 | 189 | Interesting. 190 | Also note that there are some details hiding in metadata: 191 | 192 | [source,clojure] 193 | ---- 194 | (-> (aether/resolve-dependencies :coordinates '[[metosin/malli "0.10.0"]]) 195 | ffirst 196 | ((juxt identity meta))) 197 | ;; => [[org.clojure/clojure "1.8.0"] 198 | ;; {:dependency 199 | ;; #object[org.eclipse.aether.graph.Dependency 0x7e70e8a0 "org.clojure:clojure:jar:1.8.0 (compile)"], 200 | ;; :file 201 | ;; #object[java.io.File 0x501ed01a "/home/lee/.m2/repository/org/clojure/clojure/1.8.0/clojure-1.8.0.jar"]}] 202 | ---- 203 | 204 | We can conveniently get to the `:file` info like so: 205 | 206 | [source,clojure] 207 | ---- 208 | (->> (aether/resolve-dependencies :coordinates '[[metosin/malli "0.10.0"]]) 209 | aether/dependency-files 210 | (map str)) 211 | ;; => ("/home/lee/.m2/repository/org/clojure/clojure/1.8.0/clojure-1.8.0.jar" 212 | ;; "/home/lee/.m2/repository/org/clojure/test.check/1.1.1/test.check-1.1.1.jar" 213 | ;; "/home/lee/.m2/repository/org/clojure/core.rrb-vector/0.1.2/core.rrb-vector-0.1.2.jar" 214 | ;; "/home/lee/.m2/repository/fipp/fipp/0.6.26/fipp-0.6.26.jar" 215 | ;; "/home/lee/.m2/repository/borkdude/edamame/1.0.0/edamame-1.0.0.jar" 216 | ;; "/home/lee/.m2/repository/metosin/malli/0.10.0/malli-0.10.0.jar" 217 | ;; "/home/lee/.m2/repository/org/clojure/tools.reader/1.3.4/tools.reader-1.3.4.jar" 218 | ;; "/home/lee/.m2/repository/borkdude/dynaload/0.3.5/dynaload-0.3.5.jar" 219 | ;; "/home/lee/.m2/repository/mvxcvi/arrangement/2.0.0/arrangement-2.0.0.jar") 220 | ---- 221 | 222 | Let's have Pomegranate express dependencies for malli using malli as the root dependency: 223 | 224 | [source,clojure] 225 | ---- 226 | (->> (aether/resolve-dependencies :coordinates '[[metosin/malli "0.10.0"]]) 227 | (aether/dependency-hierarchy '[[metosin/malli "0.10.0"]])) 228 | ;; => {[metosin/malli "0.10.0"] 229 | ;; {[borkdude/dynaload "0.3.5"] nil, 230 | ;; [borkdude/edamame "1.0.0"] {[org.clojure/tools.reader "1.3.4"] nil}, 231 | ;; [fipp "0.6.26"] {[org.clojure/clojure "1.8.0"] nil, 232 | ;; [org.clojure/core.rrb-vector "0.1.2"] nil}, 233 | ;; [mvxcvi/arrangement "2.0.0"] nil, 234 | ;; [org.clojure/test.check "1.1.1"] nil}} 235 | ---- 236 | 237 | Cool! 238 | 239 | ==== Supporting other Protocols via Wagons 240 | 241 | Out of the box, Pomegranate can communicate with maven repositories over HTTPS. 242 | 243 | If you need to hit a maven repository that speaks some other protocol, you can do so via https://maven.apache.org/wagon/[Maven Wagon]. 244 | 245 | For example, by default, for security reasons, Pomegranate no longer has plain old unsecure HTTP support built available. 246 | But, if you understand the risks (don't do this if you don't), and want to re-enable this support, you can do so by registering an HTTP wagon like so: 247 | 248 | [source,clojure] 249 | ---- 250 | (aether/register-wagon-factory! "http" #(org.apache.maven.wagon.providers.http.HttpWagon.)) 251 | ---- 252 | 253 | And now you can hit your unsecure HTTP maven repo too. 254 | Maybe you are running a local instance for caching. 255 | 256 | [source,clojure] 257 | ---- 258 | (aether/resolve-artifacts :coordinates '[[metosin/malli "0.10.0"]] 259 | :repositories {"local-nexus" "http://localhost:8081/repository/maven-public"}) 260 | ---- 261 | 262 | ==== Deploying Artifacts 263 | 264 | TIP: If you want a tool that does this well that uses the Pomegranate to do so, consider using https://github.com/slipset/deps-deploy[deps-deploy]. 265 | Fun fact: To deploy itself to clojars, Pomegranate uses deps-deploy, which uses Pomegranate. 266 | 267 | ===== To Local Maven Repo 268 | Assuming `pom.xml` and `target/some-library.jar` files, exist: 269 | 270 | [source,clojure] 271 | ---- 272 | (aether/install :coordinates '[lread/mucking-around "1.2.3"] 273 | :jar-file (io/file "target" "some-library.jar") 274 | :pom-file (io/file "pom.xml")) 275 | ---- 276 | 277 | After this completes, you'll see something like: 278 | [source,shell] 279 | ---- 280 | $ tree ~/.m2/repository/lread/mucking-around 281 | /home/lee/.m2/repository/lread/mucking-around 282 | ├── 1.2.3 283 | │  ├── mucking-around-1.2.3.jar 284 | │  ├── mucking-around-1.2.3.pom 285 | │  └── _remote.repositories 286 | └── maven-metadata-local.xml 287 | 288 | 1 directory, 4 files 289 | ---- 290 | 291 | ===== To Remote Maven Repo 292 | 293 | Assuming `pom.xml` and `target/some-library.jar`, exist, a deploy to clojars could look something like this: 294 | 295 | [source,clojure] 296 | ---- 297 | (aether/deploy :coordinates '[lread/mucking-around "1.2.3"] 298 | :jar-file (io/file "target" "some-library.jar") 299 | :pom-file (io/file "pom.xml") 300 | :repository {"clojars" {:url "https://repo.clojars.org" 301 | :username (System/getenv "CLOJARS_USERNAME") 302 | :password (System/getenv "CLOJARS_PASSWORD")}}) 303 | ---- 304 | -------------------------------------------------------------------------------- /doc/02-developer-guide.adoc: -------------------------------------------------------------------------------- 1 | = Developer Guide 2 | :toclevels: 5 3 | :toc: 4 | 5 | == Audience 6 | You want contribute to, or learn about, the development of the Pomegranate library. 7 | 8 | == Contributing 9 | 10 | We very much appreciate contributions from the community. 11 | 12 | === Issue First Please 13 | 14 | If you have an idea or a fix, please do raise a GitHub issue before investing in any coding effort. 15 | That way we can discuss first. 16 | Writing code is the easy part, maintaining it forever is the hard part. 17 | 18 | That said, if you notice a simple typo, a PR without an issue is fine. 19 | 20 | === Submitting a Pull Request 21 | 22 | Please never force push on your PR, as this makes reviewing incremental changes impossible for us. 23 | When we merge your PR, we'll usually squash it, so that will clean up any rambling work in progress. 24 | 25 | == Environmental Overview 26 | 27 | === Developer Prerequisites 28 | 29 | The current version of Babashka. 30 | The current version of Clojure. 31 | JDK8+ 32 | 33 | === Foundational Library 34 | 35 | Leiningen and other tools rely on Pomegranate behaving the way it does. 36 | We must be very careful when making changes. 37 | 38 | == Docs 39 | 40 | All documentation is written in AsciiDoc. 41 | @lread likes to follow https://asciidoctor.org/docs/asciidoc-recommended-practices/#one-sentence-per-line[AsciiDoc best practice of one sentence per line] but won't be entirely pedantic about that. 42 | 43 | We host our docs on cljdoc. 44 | 45 | == The Public API 46 | 47 | When making changes to Pomegranate, understand that currently any public method is considered part of the public API. 48 | 49 | We must be careful not to expose what we feel are implementation details. 50 | 51 | == Babashka Tasks 52 | 53 | We use Babashka tasks, to see all available tasks run: 54 | 55 | [source,shell] 56 | ---- 57 | bb tasks 58 | ---- 59 | 60 | Optionally: 61 | 62 | [source,shell] 63 | ---- 64 | $ bb clean 65 | $ bb download-deps 66 | ---- 67 | 68 | === Testing 69 | Run Clojure tests. 70 | We have 2 suites: 71 | 72 | * `:unit` - general unit tests 73 | * `:isolated` - tests that pollute classloaders and classpath, and are therefore run separately 74 | 75 | To run all test suites under Clojure `1.4` (our minimum supported version): 76 | [source,shell] 77 | ---- 78 | $ bb test 79 | ---- 80 | 81 | To only run a single suite: 82 | [source,shell] 83 | ---- 84 | $ bb test --suite :unit 85 | ---- 86 | 87 | You can also include Cognitect test runner options: 88 | 89 | [source,shell] 90 | ---- 91 | $ bb test --suite :unit --var cemerick.pomegranate.aether-test/live-resolution 92 | ---- 93 | 94 | ...and/or Clojure version: 95 | 96 | [source,shell] 97 | ---- 98 | $ bb test --clj-version 1.9 99 | ---- 100 | (specify `:all` to test against all supported Clojure versions) 101 | 102 | === Linting 103 | Our CI workflow lints sources with clj-kondo, and eastwood - and you can too! 104 | 105 | [source,shell] 106 | ---- 107 | $ bb lint-kondo 108 | $ bb lint-eastwood 109 | ---- 110 | 111 | To run both: `bb lint` 112 | 113 | === Vulnerability scanning 114 | We automatically scan for vulnerabilities in our dependencies on CI. 115 | If you want to run this work locally, you can for example: 116 | 117 | [source,shell] 118 | ---- 119 | $ NVD_API_TOKEN=your-token-here bb nvd-scan 120 | ---- 121 | 122 | Replace `your-token-here` with your personal nvd api token which you can easily request from https://nvd.nist.gov/developers/request-an-api-key. 123 | 124 | === Outdated Deps 125 | You can check for outdated dependencies via: 126 | 127 | [source,shell] 128 | ---- 129 | $ bb outdated 130 | ---- 131 | 132 | Before upgrading `+org.apache.maven.*+` deps, make sure there is a good reason to do so. 133 | Otherwise, don't bother bumping these deps. 134 | Good reasons to bump `+org.apache.maven.*+` deps might be: 135 | 136 | * to overcome a security vulnerability 137 | * there is some necessary new/corrected behaviour 138 | -------------------------------------------------------------------------------- /doc/03-maintainer-guide.adoc: -------------------------------------------------------------------------------- 1 | = Maintainer Guide 2 | :toclevels: 5 3 | :toc: 4 | 5 | == Audience 6 | You are a maintainer of this project. 7 | 8 | == Publishing a New Release 9 | Is invoked from the command line via: 10 | 11 | [source,shell] 12 | ---- 13 | bb publish 14 | ---- 15 | 16 | The publish task locally validates: 17 | 18 | * local git 19 | ** you are not on a fork 20 | ** you are on master branch 21 | ** do not have any uncommitted code 22 | ** do not have any unpushed commits 23 | ** local head sha matches matches remote head sha 24 | * changelog 25 | ** Has an "Unreleased" section with content 26 | 27 | TIP: to run these validations without publishing, run `bb pubcheck` 28 | 29 | Then also locally: 30 | 31 | . bumps the version `` (our scheme is `major.minor.`) 32 | ** Our version is stored in `deps.edn` under `:aliases` `:neil` `:project` `:version` 33 | . applies version to: 34 | .. `doc/01-user-guide.adoc` 35 | .. `CHANGELOG.adoc` 36 | . git commits: `deps.edn` `doc/01-user-guide.adoc` `CHANGELOG.adoc` 37 | . git tags with release tag `v` 38 | . pushes commit 39 | . pushes tag 40 | 41 | Then up on CI, the CI publish workflow is only triggered when it sees a release tag: 42 | 43 | . CI tests workflow is invoked 44 | . a release jar is published to clojars 45 | . a GitHub release is created 46 | . cljdoc is informed of the new release 47 | 48 | == Relevant Sources 49 | 50 | Scripts: 51 | 52 | . `bb.edn` - tasks entry point 53 | . `script/publish.clj` - client side work 54 | . `script/ci_publish.clj` - ci side work 55 | 56 | CI - We use GitHub Actions for this project 57 | 58 | . `.github/workflows/tests.yml` 59 | . `.github/workflows/publish.yml` 60 | 61 | == CI Config 62 | 63 | Clojars secrets are protected under the `publish` environment which is only referenced by `publish.yml`. 64 | 65 | The nvd api token is stored under the `nvd` environment and refernced by `nvd_scanner.yml`. 66 | Should you need to update the token, you can request one here: https://nvd.nist.gov/developers/request-an-api-key. 67 | If you are using gmail, you can request a unique token for pomegranate CI by including `+pomegranate` in your email address, ex. `bob@gmail.com` becomes `bob+pomegranate@gmail.com`. 68 | 69 | == Expected Oddities 70 | 71 | When publishing, you will see both the `tests` workflow triggered and the `publish` workflow triggered (which also invokes the `tests` workflow). 72 | 73 | This extra running of the `tests` workflow is GitHub Actions responding to changes committed as part of the publishing work. 74 | A bit annoying, but harmless. 75 | -------------------------------------------------------------------------------- /doc/cljdoc.edn: -------------------------------------------------------------------------------- 1 | {:cljdoc.doc/tree 2 | [["Readme" {:file "README.adoc"}] 3 | ["Changelog" {:file "CHANGELOG.adoc"}] 4 | ["User Guide" {:file "doc/01-user-guide.adoc"}] 5 | ["Developer Guide" {:file "doc/02-developer-guide.adoc"}] 6 | ["Maintainer Guide" {:file "doc/03-maintainer-guide.adoc"}]]} 7 | -------------------------------------------------------------------------------- /epl-v10.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Eclipse Public License - Version 1.0 8 | 25 | 26 | 27 | 28 | 29 | 30 |

Eclipse Public License - v 1.0

31 | 32 |

THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE 33 | PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR 34 | DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS 35 | AGREEMENT.

36 | 37 |

1. DEFINITIONS

38 | 39 |

"Contribution" means:

40 | 41 |

a) in the case of the initial Contributor, the initial 42 | code and documentation distributed under this Agreement, and

43 |

b) in the case of each subsequent Contributor:

44 |

i) changes to the Program, and

45 |

ii) additions to the Program;

46 |

where such changes and/or additions to the Program 47 | originate from and are distributed by that particular Contributor. A 48 | Contribution 'originates' from a Contributor if it was added to the 49 | Program by such Contributor itself or anyone acting on such 50 | Contributor's behalf. Contributions do not include additions to the 51 | Program which: (i) are separate modules of software distributed in 52 | conjunction with the Program under their own license agreement, and (ii) 53 | are not derivative works of the Program.

54 | 55 |

"Contributor" means any person or entity that distributes 56 | the Program.

57 | 58 |

"Licensed Patents" mean patent claims licensable by a 59 | Contributor which are necessarily infringed by the use or sale of its 60 | Contribution alone or when combined with the Program.

61 | 62 |

"Program" means the Contributions distributed in accordance 63 | with this Agreement.

64 | 65 |

"Recipient" means anyone who receives the Program under 66 | this Agreement, including all Contributors.

67 | 68 |

2. GRANT OF RIGHTS

69 | 70 |

a) Subject to the terms of this Agreement, each 71 | Contributor hereby grants Recipient a non-exclusive, worldwide, 72 | royalty-free copyright license to reproduce, prepare derivative works 73 | of, publicly display, publicly perform, distribute and sublicense the 74 | Contribution of such Contributor, if any, and such derivative works, in 75 | source code and object code form.

76 | 77 |

b) Subject to the terms of this Agreement, each 78 | Contributor hereby grants Recipient a non-exclusive, worldwide, 79 | royalty-free patent license under Licensed Patents to make, use, sell, 80 | offer to sell, import and otherwise transfer the Contribution of such 81 | Contributor, if any, in source code and object code form. This patent 82 | license shall apply to the combination of the Contribution and the 83 | Program if, at the time the Contribution is added by the Contributor, 84 | such addition of the Contribution causes such combination to be covered 85 | by the Licensed Patents. The patent license shall not apply to any other 86 | combinations which include the Contribution. No hardware per se is 87 | licensed hereunder.

88 | 89 |

c) Recipient understands that although each Contributor 90 | grants the licenses to its Contributions set forth herein, no assurances 91 | are provided by any Contributor that the Program does not infringe the 92 | patent or other intellectual property rights of any other entity. Each 93 | Contributor disclaims any liability to Recipient for claims brought by 94 | any other entity based on infringement of intellectual property rights 95 | or otherwise. As a condition to exercising the rights and licenses 96 | granted hereunder, each Recipient hereby assumes sole responsibility to 97 | secure any other intellectual property rights needed, if any. For 98 | example, if a third party patent license is required to allow Recipient 99 | to distribute the Program, it is Recipient's responsibility to acquire 100 | that license before distributing the Program.

101 | 102 |

d) Each Contributor represents that to its knowledge it 103 | has sufficient copyright rights in its Contribution, if any, to grant 104 | the copyright license set forth in this Agreement.

105 | 106 |

3. REQUIREMENTS

107 | 108 |

A Contributor may choose to distribute the Program in object code 109 | form under its own license agreement, provided that:

110 | 111 |

a) it complies with the terms and conditions of this 112 | Agreement; and

113 | 114 |

b) its license agreement:

115 | 116 |

i) effectively disclaims on behalf of all Contributors 117 | all warranties and conditions, express and implied, including warranties 118 | or conditions of title and non-infringement, and implied warranties or 119 | conditions of merchantability and fitness for a particular purpose;

120 | 121 |

ii) effectively excludes on behalf of all Contributors 122 | all liability for damages, including direct, indirect, special, 123 | incidental and consequential damages, such as lost profits;

124 | 125 |

iii) states that any provisions which differ from this 126 | Agreement are offered by that Contributor alone and not by any other 127 | party; and

128 | 129 |

iv) states that source code for the Program is available 130 | from such Contributor, and informs licensees how to obtain it in a 131 | reasonable manner on or through a medium customarily used for software 132 | exchange.

133 | 134 |

When the Program is made available in source code form:

135 | 136 |

a) it must be made available under this Agreement; and

137 | 138 |

b) a copy of this Agreement must be included with each 139 | copy of the Program.

140 | 141 |

Contributors may not remove or alter any copyright notices contained 142 | within the Program.

143 | 144 |

Each Contributor must identify itself as the originator of its 145 | Contribution, if any, in a manner that reasonably allows subsequent 146 | Recipients to identify the originator of the Contribution.

147 | 148 |

4. COMMERCIAL DISTRIBUTION

149 | 150 |

Commercial distributors of software may accept certain 151 | responsibilities with respect to end users, business partners and the 152 | like. While this license is intended to facilitate the commercial use of 153 | the Program, the Contributor who includes the Program in a commercial 154 | product offering should do so in a manner which does not create 155 | potential liability for other Contributors. Therefore, if a Contributor 156 | includes the Program in a commercial product offering, such Contributor 157 | ("Commercial Contributor") hereby agrees to defend and 158 | indemnify every other Contributor ("Indemnified Contributor") 159 | against any losses, damages and costs (collectively "Losses") 160 | arising from claims, lawsuits and other legal actions brought by a third 161 | party against the Indemnified Contributor to the extent caused by the 162 | acts or omissions of such Commercial Contributor in connection with its 163 | distribution of the Program in a commercial product offering. The 164 | obligations in this section do not apply to any claims or Losses 165 | relating to any actual or alleged intellectual property infringement. In 166 | order to qualify, an Indemnified Contributor must: a) promptly notify 167 | the Commercial Contributor in writing of such claim, and b) allow the 168 | Commercial Contributor to control, and cooperate with the Commercial 169 | Contributor in, the defense and any related settlement negotiations. The 170 | Indemnified Contributor may participate in any such claim at its own 171 | expense.

172 | 173 |

For example, a Contributor might include the Program in a commercial 174 | product offering, Product X. That Contributor is then a Commercial 175 | Contributor. If that Commercial Contributor then makes performance 176 | claims, or offers warranties related to Product X, those performance 177 | claims and warranties are such Commercial Contributor's responsibility 178 | alone. Under this section, the Commercial Contributor would have to 179 | defend claims against the other Contributors related to those 180 | performance claims and warranties, and if a court requires any other 181 | Contributor to pay any damages as a result, the Commercial Contributor 182 | must pay those damages.

183 | 184 |

5. NO WARRANTY

185 | 186 |

EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS 187 | PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS 188 | OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, 189 | ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY 190 | OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely 191 | responsible for determining the appropriateness of using and 192 | distributing the Program and assumes all risks associated with its 193 | exercise of rights under this Agreement , including but not limited to 194 | the risks and costs of program errors, compliance with applicable laws, 195 | damage to or loss of data, programs or equipment, and unavailability or 196 | interruption of operations.

197 | 198 |

6. DISCLAIMER OF LIABILITY

199 | 200 |

EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT 201 | NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, 202 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING 203 | WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF 204 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 205 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR 206 | DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED 207 | HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.

208 | 209 |

7. GENERAL

210 | 211 |

If any provision of this Agreement is invalid or unenforceable under 212 | applicable law, it shall not affect the validity or enforceability of 213 | the remainder of the terms of this Agreement, and without further action 214 | by the parties hereto, such provision shall be reformed to the minimum 215 | extent necessary to make such provision valid and enforceable.

216 | 217 |

If Recipient institutes patent litigation against any entity 218 | (including a cross-claim or counterclaim in a lawsuit) alleging that the 219 | Program itself (excluding combinations of the Program with other 220 | software or hardware) infringes such Recipient's patent(s), then such 221 | Recipient's rights granted under Section 2(b) shall terminate as of the 222 | date such litigation is filed.

223 | 224 |

All Recipient's rights under this Agreement shall terminate if it 225 | fails to comply with any of the material terms or conditions of this 226 | Agreement and does not cure such failure in a reasonable period of time 227 | after becoming aware of such noncompliance. If all Recipient's rights 228 | under this Agreement terminate, Recipient agrees to cease use and 229 | distribution of the Program as soon as reasonably practicable. However, 230 | Recipient's obligations under this Agreement and any licenses granted by 231 | Recipient relating to the Program shall continue and survive.

232 | 233 |

Everyone is permitted to copy and distribute copies of this 234 | Agreement, but in order to avoid inconsistency the Agreement is 235 | copyrighted and may only be modified in the following manner. The 236 | Agreement Steward reserves the right to publish new versions (including 237 | revisions) of this Agreement from time to time. No one other than the 238 | Agreement Steward has the right to modify this Agreement. The Eclipse 239 | Foundation is the initial Agreement Steward. The Eclipse Foundation may 240 | assign the responsibility to serve as the Agreement Steward to a 241 | suitable separate entity. Each new version of the Agreement will be 242 | given a distinguishing version number. The Program (including 243 | Contributions) may always be distributed subject to the version of the 244 | Agreement under which it was received. In addition, after a new version 245 | of the Agreement is published, Contributor may elect to distribute the 246 | Program (including its Contributions) under the new version. Except as 247 | expressly stated in Sections 2(a) and 2(b) above, Recipient receives no 248 | rights or licenses to the intellectual property of any Contributor under 249 | this Agreement, whether expressly, by implication, estoppel or 250 | otherwise. All rights in the Program not expressly granted under this 251 | Agreement are reserved.

252 | 253 |

This Agreement is governed by the laws of the State of New York and 254 | the intellectual property laws of the United States of America. No party 255 | to this Agreement will bring a legal action under this Agreement more 256 | than one year after the cause of action arose. Each party waives its 257 | rights to a jury trial in any resulting litigation.

258 | 259 | 260 | 261 | -------------------------------------------------------------------------------- /nvd_check_helper_project/deps.edn: -------------------------------------------------------------------------------- 1 | ;; it is generally considered bad practice to use RELEASE, but we always want the latest 2 | ;; security tooling 3 | {:deps {nvd-clojure/nvd-clojure #_:clj-kondo/ignore {:mvn/version "RELEASE"} 4 | org.owasp/dependency-check-core #_:clj-kondo/ignore {:mvn/version "RELEASE"}}} 5 | -------------------------------------------------------------------------------- /nvd_check_helper_project/nvd-clojure.edn: -------------------------------------------------------------------------------- 1 | {:delete-config? false 2 | :nvd {:nvd-api {:max-retry-count 30}} 3 | :suppression-file "./suppressions.xml"} 4 | -------------------------------------------------------------------------------- /nvd_check_helper_project/suppressions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | .*\bjsch.agentproxy.usocket-nc-0.0.9.jar 10 | CVE-2008-5730 11 | CVE-2008-5727 12 | CVE-2008-5728 13 | CVE-2015-2214 14 | CVE-2008-5729 15 | CVE-2008-5742 16 | 17 | 18 | 21 | ^pkg:maven/org\.codehaus\.plexus/plexus\-(container-default|interpolation|interactivity-api)@.*$ 22 | CVE-2022-4244 23 | CVE-2022-4245 24 | 25 | 26 | 31 | CVE-2017-20189 32 | 33 | 34 | 39 | CVE-2024-22871 40 | 41 | 42 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | clj-commons/pomegranate 5 | A sane Clojure API for Maven Artifact Resolver + dynamic runtime modification of the classpath 6 | https://github.com/clj-commons/pomegranate 7 | 8 | 9 | Eclipse Public License 10 | http://www.eclipse.org/legal/epl-v10.html 11 | 12 | 13 | 14 | 15 | Chas Emerick 16 | http://cemerick.com 17 | cemerick@snowtide.com 18 | -5 19 | 20 | 21 | 22 | https://github.com/clj-commons/pomegranate 23 | scm:git:git://github.com:clj-commons/pomegranate.git 24 | scm:git:ssh://git@github.com:clj-commons/pomegranate.git 25 | 26 | 27 | UTF-8 28 | 29 | 30 | 31 | clojars 32 | https://repo.clojars.org/ 33 | 34 | 35 | 36 | 37 | clojars 38 | Clojars repository 39 | https://clojars.org/repo 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /script/ci_publish.clj: -------------------------------------------------------------------------------- 1 | (ns ci-publish 2 | "Publish work we invoke from GitHub Actions. 3 | Separated out here: 4 | - to make it clear what is happening on ci 5 | - rate of change here should be less/different than in publish namespace" 6 | (:require [babashka.tasks :as t] 7 | [lread.status-line :as status] 8 | [build-shared])) 9 | 10 | (def ^:private changelog-url (format "https://github.com/%s/blob/master/CHANGELOG.adoc" (build-shared/lib-github-coords))) 11 | 12 | (defn- assert-on-ci [] 13 | (when (not (System/getenv "CI")) 14 | (status/die 1 "to be run from continuous integration server only"))) 15 | 16 | (defn- ci-tag [] 17 | (when (= "tag" (System/getenv "GITHUB_REF_TYPE")) 18 | (System/getenv "GITHUB_REF_NAME"))) 19 | 20 | (defn- analyze-ci-tag [] 21 | (let [tag (ci-tag)] 22 | (if (not tag) 23 | (status/die 1 "CI tag not found") 24 | (let [version-from-tag (build-shared/tag->version tag) 25 | lib-version (build-shared/lib-version)] 26 | (cond 27 | (not version-from-tag) 28 | (status/die 1 "Not recognized as version tag: %s" tag) 29 | 30 | (not= version-from-tag lib-version) 31 | (status/die 1 "Lib version %s does not match version from tag %s" 32 | lib-version version-from-tag) 33 | :else 34 | {:tag tag 35 | :version lib-version}))))) 36 | 37 | ;; 38 | ;; Task entry points 39 | ;; 40 | 41 | (defn clojars-deploy [] 42 | (assert-on-ci) 43 | (analyze-ci-tag) ;; fail on unexpected version tag 44 | (t/shell "clojure -T:build deploy")) 45 | 46 | (defn github-create-release [] 47 | (assert-on-ci) 48 | (let [{:keys [tag]} (analyze-ci-tag)] 49 | (t/shell "gh release create" 50 | tag 51 | "--title" tag 52 | "--notes" (format "[Changelog](%s#%s)" changelog-url tag)))) 53 | 54 | (defn cljdoc-request-build [] 55 | (assert-on-ci) 56 | (let [{:keys [version]} (analyze-ci-tag) 57 | lib (build-shared/lib-artifact-name)] 58 | (status/line :head "Informing cljdoc of %s version %s" lib version) 59 | (assert-on-ci) 60 | (let [exit-code (-> (t/shell {:continue true} 61 | "curl" "-X" "POST" 62 | "-d" (str "project=" lib) 63 | "-d" (str "version=" version) 64 | "https://cljdoc.org/api/request-build2") 65 | :exit)] 66 | (when (not (zero? exit-code)) 67 | (status/line :warn (str "Informing cljdoc did not seem to work, exited with " exit-code)))))) 68 | -------------------------------------------------------------------------------- /script/download_deps.clj: -------------------------------------------------------------------------------- 1 | (ns download-deps 2 | (:require [babashka.tasks :as t] 3 | [clojure.edn :as edn] 4 | [lread.status-line :as status])) 5 | 6 | ;; clojure has a -P command, but to bring down all deps we need to specify all aliases 7 | ;; bb deps will be brought down just from running bb (which assumedly is how this code is run) 8 | 9 | (defn -main [& _args] 10 | (let [aliases (->> "deps.edn" 11 | slurp 12 | edn/read-string 13 | :aliases 14 | keys)] 15 | ;; one at a time because aliases with :replace-deps will... well... you know. 16 | (status/line :detail "Bring down default deps") 17 | (t/clojure "-P") 18 | (doseq [a aliases] 19 | (status/line :detail "Bring down deps for alias: %s" a) 20 | (t/clojure "-P" (str "-M" a))))) 21 | -------------------------------------------------------------------------------- /script/lint.clj: -------------------------------------------------------------------------------- 1 | (ns lint 2 | (:require [babashka.classpath :as bbcp] 3 | [babashka.cli :as cli] 4 | [babashka.fs :as fs] 5 | [babashka.tasks :as t] 6 | [clojure.string :as string] 7 | [lread.status-line :as status])) 8 | 9 | (def clj-kondo-cache ".clj-kondo/.cache") 10 | 11 | (defn- cache-exists? [] 12 | (fs/exists? clj-kondo-cache)) 13 | 14 | (defn- delete-cache [] 15 | (when (cache-exists?) 16 | (fs/delete-tree clj-kondo-cache))) 17 | 18 | (defn- build-cache [] 19 | (when (cache-exists?) 20 | (delete-cache)) 21 | (let [clj-cp (-> (t/clojure {:out :string} 22 | "-Spath -M:test:isolated") 23 | with-out-str 24 | string/trim) 25 | bb-cp (bbcp/get-classpath)] 26 | (status/line :detail "- copying configs") 27 | (t/clojure "-M:clj-kondo --skip-lint --copy-configs --lint" clj-cp bb-cp) 28 | (status/line :detail "- creating cache") 29 | (t/clojure "-M:clj-kondo --dependencies --lint" clj-cp bb-cp))) 30 | 31 | (defn- check-cache [{:keys [rebuild]}] 32 | (status/line :head "clj-kondo: cache check") 33 | (if-let [rebuild-reason (cond 34 | rebuild 35 | "Rebuild requested" 36 | 37 | (not (cache-exists?)) 38 | "Cache not found" 39 | 40 | :else 41 | (let [updated-dep-files (fs/modified-since clj-kondo-cache ["deps.edn" "bb.edn" "nvd_check_helper_project/deps.edn"])] 42 | (when (seq updated-dep-files) 43 | (format "Found deps files newer than lint cache: %s" (mapv str updated-dep-files)))))] 44 | (do (status/line :detail rebuild-reason) 45 | (build-cache)) 46 | (status/line :detail "Using existing cache"))) 47 | 48 | (defn- lint [opts] 49 | (check-cache opts) 50 | (status/line :head "clj-kondo: linting") 51 | (let [{:keys [exit]} 52 | (t/clojure {:continue true} 53 | "-M:clj-kondo --lint src script deps.edn bb.edn nvd_check_helper_project/deps.edn")] 54 | (cond 55 | (= 2 exit) (status/die exit "clj-kondo found one or more lint errors") 56 | (= 3 exit) (status/die exit "clj-kondo found one or more lint warnings") 57 | (> exit 0) (status/die exit "clj-kondo returned unexpected exit code")))) 58 | 59 | (defn -main [& args] 60 | (when-let [opts (cli/parse-opts args)] 61 | (lint opts))) 62 | 63 | (when (= *file* (System/getProperty "babashka.file")) 64 | (apply -main *command-line-args*)) 65 | -------------------------------------------------------------------------------- /script/publish.clj: -------------------------------------------------------------------------------- 1 | (ns publish 2 | "Publish work that happens locally on a maintainer's workstation" 3 | (:require [babashka.tasks :as t] 4 | [build-shared] 5 | [clojure.string :as string] 6 | [lread.status-line :as status] 7 | [version-clj.core :as v])) 8 | 9 | ;; Note to lurkers: doc updates are geared to AsciiDoc files. 10 | 11 | (def changelog-fname "CHANGELOG.adoc") 12 | (def user-guide-fname "doc/01-user-guide.adoc") 13 | ;; this project started with "pomegranate-", then moved to "Release-" but we prefer "v" as a version tag prefix 14 | (def legacy-version-tag-prefixes ["pomegranate-" "Release-"]) 15 | 16 | (defn- raw-tags[] 17 | (->> (t/shell {:out :string} 18 | ;; specifying the url ensures we don't get tags for some dev fork 19 | "git ls-remote --tags --refs" (format "https://github.com/%s.git" (build-shared/lib-github-coords))) 20 | :out 21 | string/split-lines)) 22 | 23 | (defn- parse-raw-tag [raw-tag-line] 24 | (let [pattern (re-pattern (str "refs/tags/((?:" 25 | (string/join "|" legacy-version-tag-prefixes) "|" 26 | build-shared/version-tag-prefix ")(\\d+\\..*))"))] 27 | (some->> (re-find pattern raw-tag-line) 28 | rest 29 | (zipmap [:tag :version])))) 30 | 31 | (defn- most-recent-tag [parsed-tags] 32 | (->> parsed-tags 33 | (sort-by :version v/version-compare) 34 | reverse 35 | first 36 | :tag)) 37 | 38 | (defn last-release-tag [] 39 | (->> (raw-tags) 40 | (keep parse-raw-tag) 41 | (most-recent-tag))) 42 | 43 | (defn- not-fork? [] 44 | (let [origin-url (->> (t/shell {:out :string} "git remote get-url origin") 45 | :out 46 | string/trim)] 47 | (some #{origin-url} [(format "https://github.com/%s.git" (build-shared/lib-github-coords)) 48 | (format "git@github.com:%s.git" (build-shared/lib-github-coords))]))) 49 | 50 | (defn- master-branch? [] 51 | (let [current-branch (->> (t/shell {:out :string} "git rev-parse --abbrev-ref HEAD") 52 | :out 53 | string/trim)] 54 | (= "master" current-branch))) 55 | 56 | (defn- uncommitted-code? [] 57 | (-> (t/shell {:out :string} 58 | "git status --porcelain") 59 | :out 60 | string/trim 61 | seq)) 62 | 63 | (defn- local-branch? [] 64 | (let [{:keys [exit]} (t/shell {:continue true :out :string :err :out} 65 | "git rev-parse --symbolic-full-name @{u}")] 66 | (not (zero? exit)))) 67 | 68 | (defn- unpushed-commits? [] 69 | (let [{:keys [exit :out]} (t/shell {:continue true :out :string} 70 | "git cherry -v")] 71 | (and (zero? exit) (-> out string/trim seq)))) 72 | 73 | (defn- commit-matches-master? [] 74 | (let [master-head-sha (-> (t/shell {:out :string} (format "git ls-remote https://github.com/%s.git master" (build-shared/lib-github-coords))) 75 | :out 76 | (string/split #"\s+") 77 | first) 78 | local-head-sha (-> (t/shell {:out :string} "git rev-parse HEAD") 79 | :out 80 | string/trim)] 81 | (= master-head-sha local-head-sha))) 82 | 83 | (defn- analyze-changelog 84 | "Certainly not fool proof, but should help for common mistakes" 85 | [] 86 | (let [content (slurp changelog-fname) 87 | valid-attrs ["[minor breaking]" "[breaking]"] 88 | [_ attr content :as match] (re-find #"(?ims)^== Unreleased ?(.*?)$(.*?)(== v\d|\z)" content)] 89 | (if (not match) 90 | [{:error :section-missing}] 91 | (cond-> [] 92 | (and attr 93 | (not (string/blank? attr)) 94 | (not (contains? (set valid-attrs) attr))) 95 | (conj {:error :suffix-invalid :valid-attrs valid-attrs :found attr}) 96 | 97 | ;; without any words of a reasonable min length, we consider section blank 98 | (not (re-find #"(?m)[\p{L}]{3,}" content)) 99 | (conj {:error :content-missing}))))) 100 | 101 | (defn- release-checks [] 102 | (let [changelog-findings (reduce (fn [acc n] (assoc acc (:error n) n)) 103 | {} 104 | (analyze-changelog))] 105 | [{:check "not on fork" 106 | :result (if (not-fork?) :pass :fail)} 107 | {:check "on master branch" 108 | :result (if (master-branch?) :pass :fail)} 109 | {:check "no uncommitted code" 110 | :result (if (uncommitted-code?) :fail :pass)} 111 | {:check "no unpushed commits" 112 | :result (if (or (local-branch?) (unpushed-commits?)) :fail :pass)} 113 | {:check "in synch with master HEAD" 114 | :result (if (commit-matches-master?) :pass :fail)} 115 | {:check "changelog has unreleased section" 116 | :result (if (:section-missing changelog-findings) :fail :pass)} 117 | {:check "changelog unreleased section attributes valid" 118 | :result (cond 119 | (:section-missing changelog-findings) :skip 120 | (:suffix-invalid changelog-findings) :fail 121 | :else :pass) 122 | :msg (when-let [{:keys [valid-attrs found]} (:suffix-invalid changelog-findings)] 123 | (format "expected attributes to absent or one of %s, but found: %s" (string/join ", " valid-attrs) found))} 124 | {:check "changelog unreleased section has content" 125 | :result (cond 126 | (:section-missing changelog-findings) :skip 127 | (:content-missing changelog-findings) :fail 128 | :else :pass)}])) 129 | 130 | (defn- bump-version! 131 | "bump version stored in deps.edn" 132 | [] 133 | (t/shell "bb neil version patch --no-tag")) 134 | 135 | (defn- update-file! [fname desc match replacement] 136 | (let [old-content (slurp fname) 137 | new-content (string/replace-first old-content match replacement)] 138 | (if (= old-content new-content) 139 | (status/die 1 "Expected to %s in %s" desc fname) 140 | (spit fname new-content)))) 141 | 142 | (defn- update-user-guide! [version] 143 | (status/line :detail "Applying version %s to user guide" version) 144 | (update-file! user-guide-fname 145 | "update :lib-version: adoc attribute" 146 | #"(?m)^(:lib-version: )(.*)$" 147 | (str "$1" version))) 148 | 149 | (defn- yyyy-mm-dd-now-utc [] 150 | (-> (java.time.Instant/now) str (subs 0 10))) 151 | 152 | (defn- update-changelog! [version release-tag last-release-tag] 153 | (status/line :detail "Applying version %s to changelog" version) 154 | (update-file! changelog-fname 155 | "update unreleased header" 156 | #"(?ims)^== Unreleased(.*?)($.*?)(== v\d|\z)" 157 | (str 158 | ;; add Unreleased section for next released 159 | "== Unreleased\n\n" 160 | ;; replace "Unreleased" with actual version 161 | "== v" version 162 | ;; followed by any attributes 163 | "$1" 164 | ;; followed by datestamp (local time is fine) 165 | " - " (yyyy-mm-dd-now-utc) 166 | ;; followed by an AsciiDoc anchor for easy referencing 167 | " [[v" version "]]" 168 | ;; followed by section content 169 | "$2" 170 | ;; followed by link to commit log 171 | (when last-release-tag 172 | (str 173 | "https://github.com/" (build-shared/lib-github-coords) "/compare/" 174 | last-release-tag 175 | "\\\\..." ;; single backslash is escape for AsciiDoc 176 | release-tag 177 | "[commit log]\n\n")) 178 | ;; followed by next section indicator 179 | "$3"))) 180 | 181 | (defn- commit-changes! [version] 182 | (t/shell "git add deps.edn" changelog-fname user-guide-fname) 183 | (t/shell "git commit -m" (str "publish: apply version " version))) 184 | 185 | (defn- tag! [tag version] 186 | (t/shell "git tag" tag "-m" (str "For release: " version))) 187 | 188 | (defn- push! [] 189 | (t/shell "git push")) 190 | 191 | (defn- push-tag! [tag] 192 | (t/shell "git push origin" tag)) 193 | 194 | ;; task entry points 195 | 196 | (defn pubcheck [] 197 | (status/line :head "Performing publish checks") 198 | (let [check-results (release-checks) 199 | passed? (every? #(= :pass (:result %)) check-results)] 200 | (doseq [{:keys [check result msg]} check-results] 201 | (status/line :detail "%s %s" 202 | (case result 203 | :pass "✓" 204 | :fail "x" 205 | :skip "~") 206 | check) 207 | (when msg 208 | (status/line :detail " > %s" msg))) 209 | (when (not passed?) 210 | (status/die 1 "Release checks failed")))) 211 | 212 | (defn -main [& _args] 213 | (pubcheck) 214 | (status/line :head "Calculating versions") 215 | (bump-version!) 216 | (let [last-release-tag (last-release-tag) 217 | version (build-shared/lib-version) 218 | release-tag (build-shared/version->tag version)] 219 | (status/line :detail "Release version: %s" version) 220 | (status/line :detail "Release tag: %s" release-tag) 221 | (status/line :detail "Last release tag: %s" last-release-tag) 222 | (status/line :head "Updating docs") 223 | (update-user-guide! version) 224 | (update-changelog! version release-tag last-release-tag) 225 | (status/line :head "Committing changes") 226 | (commit-changes! version) 227 | (status/line :head "Tagging & pushing") 228 | (tag! release-tag version) 229 | (push!) 230 | (push-tag! release-tag) 231 | (status/line :detail "\nLocal work done.") 232 | (status/line :head "Remote work") 233 | (status/line :detail "The remainging work will be triggered by the release tag on CI:") 234 | (status/line :detail "- Publish a release jar to clojars") 235 | (status/line :detail "- Create a GitHub release") 236 | (status/line :detail "- Inform cljdoc of release"))) 237 | 238 | ;; default action when executing file directly 239 | (when (= *file* (System/getProperty "babashka.file")) 240 | (apply -main *command-line-args*)) 241 | 242 | (comment 243 | (parse-raw-tag "boo refs/tags/pomegranate-1.8") 244 | ;; => {:tag "pomegranate-1.8", :version "1.8"} 245 | 246 | (parse-raw-tag "boo refs/tags/Release-1.8") 247 | ;; => {:tag "Release-1.8", :version "1.8"} 248 | 249 | (parse-raw-tag "boo refs/tags/v1.8") 250 | ;; => {:tag "v1.8", :version "1.8"} 251 | 252 | (parse-raw-tag "boo refs/tags/1.8") 253 | ;; => nil 254 | 255 | (parse-raw-tag "boo refs/tags/nope") 256 | ;; => nil 257 | 258 | (most-recent-tag [{:tag "a" :version "0.0.2"} 259 | {:tag "b" :version "7.8.9"} 260 | {:tag "c" :version "0.0.4"} 261 | {:tag "d" :version "1.2.3"}]) 262 | ;; => "b" 263 | 264 | (->> (raw-tags) 265 | (keep parse-raw-tag)) 266 | ;; => ({:tag "Release-1.2.0", :version "1.2.0"} 267 | ;; {:tag "Release-1.2.1", :version "1.2.1"} 268 | ;; {:tag "pomegranate-0.0.10", :version "0.0.10"} 269 | ;; {:tag "pomegranate-0.0.11", :version "0.0.11"} 270 | ;; {:tag "pomegranate-0.0.12", :version "0.0.12"} 271 | ;; {:tag "pomegranate-0.0.13", :version "0.0.13"} 272 | ;; {:tag "pomegranate-0.0.2", :version "0.0.2"} 273 | ;; {:tag "pomegranate-0.0.3", :version "0.0.3"} 274 | ;; {:tag "pomegranate-0.0.4", :version "0.0.4"} 275 | ;; {:tag "pomegranate-0.0.5", :version "0.0.5"} 276 | ;; {:tag "pomegranate-0.0.6", :version "0.0.6"} 277 | ;; {:tag "pomegranate-0.0.7", :version "0.0.7"} 278 | ;; {:tag "pomegranate-0.0.8", :version "0.0.8"} 279 | ;; {:tag "pomegranate-0.0.9", :version "0.0.9"} 280 | ;; {:tag "pomegranate-0.2.0", :version "0.2.0"} 281 | ;; {:tag "pomegranate-0.3.0", :version "0.3.0"} 282 | ;; {:tag "pomegranate-0.3.1", :version "0.3.1"} 283 | ;; {:tag "pomegranate-0.4.0", :version "0.4.0"} 284 | ;; {:tag "pomegranate-0.4.0-alpha1", :version "0.4.0-alpha1"} 285 | ;; {:tag "pomegranate-1.0.0", :version "1.0.0"} 286 | ;; {:tag "pomegranate-1.1.0", :version "1.1.0"}) 287 | 288 | (last-release-tag) 289 | ;; => "Release-1.2.1" 290 | ) 291 | -------------------------------------------------------------------------------- /script/test_clj.clj: -------------------------------------------------------------------------------- 1 | (ns test-clj 2 | (:require [babashka.cli :as cli] 3 | [babashka.tasks :as t] 4 | [clojure.string :as string] 5 | [lread.status-line :as status])) 6 | 7 | (defn -main [& args] 8 | (let [old-clojure-versions ["1.4" "1.5" "1.6" "1.7"] 9 | all-clojure-versions (concat old-clojure-versions ["1.8" "1.9" "1.10" "1.11" "1.12"]) 10 | default-version (first all-clojure-versions) 11 | valid-clj-version-opt-values (conj all-clojure-versions ":all") 12 | all-suites [:unit :isolated] 13 | valid-suite-opt-values (conj all-suites :all) 14 | spec {:suite 15 | {:ref "" 16 | :desc (str "The test suite to run, valid values: " (string/join ", " valid-suite-opt-values)) 17 | :coerce :keyword 18 | :default :all 19 | :validate 20 | {:pred (set valid-suite-opt-values) 21 | :ex-msg (fn [_m] 22 | (str "--suite must be one of: " (string/join ", " valid-suite-opt-values)))}} 23 | :clj-version 24 | {:ref "" 25 | :desc (str "The Clojure version to test against, valid values: " (string/join ", " valid-clj-version-opt-values)) 26 | :coerce :string 27 | :booya :foo 28 | :default-desc default-version 29 | ;; don't specify :default, we want to know if the user passed this option in 30 | :validate 31 | {:pred (set valid-clj-version-opt-values) 32 | :ex-msg (fn [_m] 33 | (str "--clj-version must be one of: " (string/join ", " valid-clj-version-opt-values)))}}} 34 | opts (cli/parse-opts args {:spec spec}) 35 | suite (:suite opts) 36 | clj-version (:clj-version opts) 37 | runner-args (if-not (or clj-version suite) 38 | args 39 | (loop [args args 40 | out-args []] 41 | (if-let [a (first args)] 42 | (if (re-matches #"(--|:)(clj-version|suite)" a) 43 | (recur (drop 2 args) out-args) 44 | (recur (rest args) (conj out-args a))) 45 | out-args))) 46 | clj-version (or clj-version default-version)] 47 | 48 | (if (:help opts) 49 | (do 50 | (status/line :head "bb task option help") 51 | (println (cli/format-opts {:spec spec})) 52 | (status/line :head "test-runner option help") 53 | (t/clojure "-M:test:old-runner --test-help")) 54 | (let [suites (if (= :all suite) 55 | all-suites 56 | [suite]) 57 | clj-versions (if (= ":all" clj-version) 58 | all-clojure-versions 59 | [clj-version])] 60 | (doseq [v clj-versions 61 | s suites 62 | :let [test-alias (if (some #{v} old-clojure-versions) 63 | "test:old-runner" 64 | "test") 65 | test-alias (if (= :isolated s) 66 | (str test-alias ":isolated") 67 | test-alias)]] 68 | (status/line :head "Testing %s suite against Clojure version %s" s v) 69 | (apply t/clojure (format "-M:%s:%s" v test-alias) runner-args)))))) 70 | 71 | (when (= *file* (System/getProperty "babashka.file")) 72 | (apply -main *command-line-args*)) 73 | -------------------------------------------------------------------------------- /src/main/clojure/cemerick/pomegranate.clj: -------------------------------------------------------------------------------- 1 | (ns cemerick.pomegranate 2 | "Classpath and class loader tools." 3 | (:require [clojure.java.io :as io] 4 | [cemerick.pomegranate.aether :as aether] 5 | [dynapath.util :as dp]) 6 | (:refer-clojure :exclude (add-classpath))) 7 | 8 | (defn classloader-hierarchy 9 | "Returns a seq of class loaders, with the `tip` of the hierarchy first. 10 | The `tip` defaults to the current thread context class loader." 11 | ([] (classloader-hierarchy (.. Thread currentThread getContextClassLoader))) 12 | ([tip] 13 | (->> tip 14 | (iterate #(.getParent ^ClassLoader %)) 15 | (take-while boolean)))) 16 | 17 | (defn modifiable-classloader? 18 | "Returns `true` iff the given `ClassLoader` is of a type that satisfies 19 | the `dynapath.dynamic-classpath/DynamicClasspath` protocol, and can 20 | be modified." 21 | [cl] 22 | (dp/addable-classpath? cl)) 23 | 24 | (defn add-classpath 25 | "A corollary to the deprecated `add-classpath` in `clojure.core`. 26 | 27 | Attempt to add `jar-or-dir`, a string or `java.io.File`, to `classloader`. 28 | 29 | When `classloader` is not provided, searches for a modifiable classloader rooted at the current thread's context classloader." 30 | ([jar-or-dir ^ClassLoader classloader] 31 | (when-not (dp/add-classpath-url classloader (.toURL (.toURI (io/file jar-or-dir)))) 32 | (throw (IllegalStateException. (str (-> classloader .getClass .getSimpleName) " is not a modifiable classloader"))))) 33 | ([jar-or-dir] 34 | (let [classloaders (classloader-hierarchy)] 35 | (if-let [cl (last (filter modifiable-classloader? classloaders))] 36 | (add-classpath jar-or-dir cl) 37 | (throw (IllegalStateException. (str "Could not find a suitable classloader to modify from " 38 | (mapv (fn [^ClassLoader c] 39 | (-> c .getClass .getSimpleName)) 40 | classloaders)))))))) 41 | 42 | (defn add-dependencies 43 | "Resolves dependency `:coordinates` against Maven `:repositories`, then adds all 44 | the resulting artifacts (jar files) to the current runtime via [[add-classpath]]: 45 | 46 | ```Clojure 47 | (add-dependencies :classloader your-classloader 48 | :coordinates '[[incanter \"1.9.2\"]] 49 | :repositories (merge cemerick.pomegranate.aether/maven-central 50 | {\"clojars\" \"https://clojars.org/repo\"})) 51 | ``` 52 | 53 | kwarg options: 54 | - `:classloader` - (optional, defaults to closest modifiable classloader in current thread's 55 | hierarchy as per [[add-classpath]]) 56 | - Otherwise, kwargs are the same as [[cemerick.pomegranate.aether/resolve-dependencies]] 57 | 58 | Returns the dependency graph from [[cemerick.pomegranate.aether/resolve-dependencies]]." 59 | [& kwargs] 60 | (let [classloader (-> (apply hash-map kwargs) 61 | :classloader 62 | ; replace with some-> when we bump the clojure dep 63 | (#(when % [%]))) 64 | deps (apply aether/resolve-dependencies kwargs)] 65 | (doseq [artifact-file (aether/dependency-files deps)] 66 | (apply add-classpath artifact-file classloader)) 67 | deps)) 68 | 69 | (defn get-classpath 70 | "⚠️ for JDK 9+ returns an empty sequence. 71 | 72 | Returns the effective classpath (i.e. _not_ the value of 73 | `(System/getProperty \"java.class.path\")` as a seq of URL strings. 74 | 75 | Produces the classpath from all classloaders by default, or from a 76 | collection of `classloaders` if provided. This allows you to easily look 77 | at subsets of the current classloader hierarchy, e.g.: 78 | 79 | ```Clojure 80 | (get-classpath (drop 2 (classloader-hierarchy))) 81 | ```" 82 | ([classloaders] 83 | (->> (reverse classloaders) 84 | (mapcat #(dp/classpath-urls %)) 85 | (map str))) 86 | ([] (get-classpath (classloader-hierarchy)))) 87 | 88 | (defn classloader-resources 89 | "Returns a sequence of `[java.lang.ClassLoader [java.net.URL ...]]` pairs 90 | for all found `resource-name`s on the classpath of each classloader. 91 | 92 | If no `classloaders` are given, uses the [[classloader-hierarchy]]. 93 | In this case, the first URL will in most circumstances match what 94 | what `clojure.java.io/resource` returns." 95 | ([classloaders resource-name] 96 | (for [classloader (reverse classloaders)] 97 | [classloader (enumeration-seq 98 | (.getResources ^ClassLoader classloader resource-name))])) 99 | ([resource-name] (classloader-resources (classloader-hierarchy) resource-name))) 100 | 101 | (defn resources 102 | "Returns a sequence of `java.net.URL`s on the effective classpath for specified specified `resource-name`. 103 | This can be useful for finding name collisions among items on the classpath. In most 104 | circumstances, the first of the returned sequence will be the same 105 | as what `clojure.java.io/resource` returns." 106 | ([classloaders resource-name] 107 | (distinct (mapcat second (classloader-resources classloaders resource-name)))) 108 | ([resource-name] (resources (classloader-hierarchy) resource-name))) 109 | 110 | (comment 111 | (io/resource "boo") 112 | ;; => nil 113 | 114 | (-> (classloader-hierarchy) last .getClass .getSimpleName) 115 | ;; jdk8 116 | ;; => "ExtClassLoader" 117 | ;; jdk19 118 | ;; => "PlatformClassLoader" 119 | 120 | (resources [(last (classloader-hierarchy))] "META-INF/MANIFEST.MF") 121 | ;; jdk8 122 | ;; => () 123 | ;; jdk19 124 | ;; => () 125 | ) 126 | -------------------------------------------------------------------------------- /src/main/clojure/cemerick/pomegranate/aether.clj: -------------------------------------------------------------------------------- 1 | (ns cemerick.pomegranate.aether 2 | "An abstraction over the Maven Artifact Resolver." 3 | (:refer-clojure :exclude [type proxy]) 4 | (:require [clojure.java.io :as io] 5 | [clojure.set :as cset] 6 | [clojure.string :as str] 7 | [clojure.stacktrace :as stacktrace]) 8 | (:import (org.eclipse.aether RepositorySystem RepositorySystemSession DefaultRepositorySystemSession) 9 | (org.eclipse.aether.transport.wagon WagonTransporterFactory 10 | WagonProvider) 11 | (org.eclipse.aether.transport.file FileTransporterFactory) 12 | (org.eclipse.aether.transfer TransferListener) 13 | (org.eclipse.aether.artifact Artifact) 14 | (org.eclipse.aether.spi.connector RepositoryConnectorFactory) 15 | (org.eclipse.aether.spi.connector.transport TransporterFactory) 16 | (org.eclipse.aether.repository Proxy 17 | RepositoryPolicy LocalRepository RemoteRepository$Builder 18 | MirrorSelector) 19 | (org.eclipse.aether.util.repository DefaultProxySelector AuthenticationBuilder) 20 | (org.eclipse.aether.graph Dependency Exclusion DependencyNode) 21 | (org.eclipse.aether.collection CollectRequest CollectResult) 22 | (org.eclipse.aether.resolution DependencyRequest DependencyResult ArtifactRequest 23 | ArtifactResult VersionRequest) 24 | (org.eclipse.aether.artifact DefaultArtifact ArtifactProperties) 25 | (org.eclipse.aether.deployment DeployRequest) 26 | (org.eclipse.aether.installation InstallRequest) 27 | (org.eclipse.aether.util.version GenericVersionScheme) 28 | (org.eclipse.aether.connector.basic BasicRepositoryConnectorFactory) 29 | (org.eclipse.aether.impl DefaultServiceLocator$ErrorHandler) 30 | (org.apache.maven.repository.internal MavenRepositorySystemUtils))) 31 | 32 | (def ^{:private true} default-local-repo 33 | (io/file (System/getProperty "user.home") ".m2" "repository")) 34 | 35 | (def maven-central 36 | "Although we call this var `maven-central`, it is used as a map of default maven repositories for resolution." 37 | {"central" "https://repo1.maven.org/maven2/"} ) 38 | 39 | (def ^{:private true} wagon-factories 40 | ;; there were issues with the HTTP lightweight Wagon (now deprecated by Maven), 41 | ;; so we match the Maven tool itself and use HttpWagon. 42 | (atom {"https" #(org.apache.maven.wagon.providers.http.HttpWagon.) 43 | "http" #(throw (Exception. "Tried to use insecure HTTP repository."))})) 44 | 45 | (defn register-wagon-factory! 46 | "⚙️ This is considered a lower level function. 47 | It allows you to add communication channels to talk to maven repositories that aren't over the included https channel. 48 | 49 | Registers a no-arg `factory-fn` function for the given `scheme`. The `factory-fn` 50 | must return an implementation of `org.apache.maven.wagon.Wagon`." 51 | [scheme factory-fn] 52 | (swap! wagon-factories (fn [m] 53 | (when-let [fn (and (not= scheme "http") (m scheme))] 54 | (println (format "Warning: replacing existing support for %s repositories (%s) with %s" scheme fn factory-fn))) 55 | (assoc m scheme factory-fn)))) 56 | 57 | (deftype PomegranateWagonProvider [] 58 | WagonProvider 59 | (release [_ _wagon]) 60 | (lookup [_ role-hint] 61 | (when-let [f (get @wagon-factories role-hint)] 62 | (try 63 | (f) 64 | (catch Exception e 65 | (stacktrace/print-cause-trace e) 66 | (throw e)))))) 67 | 68 | (deftype TransferListenerProxy [listener-fn] 69 | TransferListener 70 | (transferCorrupted [_ e] (listener-fn e)) 71 | (transferFailed [_ e] (listener-fn e)) 72 | (transferInitiated [_ e] (listener-fn e)) 73 | (transferProgressed [_ e] (listener-fn e)) 74 | (transferStarted [_ e] (listener-fn e)) 75 | (transferSucceeded [_ e] (listener-fn e))) 76 | 77 | (defn- transfer-event 78 | [^org.eclipse.aether.transfer.TransferEvent e] 79 | ;; INITIATED, STARTED, PROGRESSED, CORRUPTED, SUCCEEDED, FAILED 80 | {:type (-> e .getType .name str/lower-case keyword) 81 | ;; :get :put 82 | :method (-> e .getRequestType str/lower-case keyword) 83 | :transferred (.getTransferredBytes e) 84 | :error (.getException e) 85 | :data-buffer (.getDataBuffer e) 86 | :data-length (.getDataLength e) 87 | :resource (let [r (.getResource e)] 88 | {:repository (.getRepositoryUrl r) 89 | :name (.getResourceName r) 90 | :file (.getFile r) 91 | :size (.getContentLength r) 92 | :transfer-start-time (.getTransferStartTime r) 93 | :trace (.getTrace r)})}) 94 | 95 | (defn- stdout-listener-fn 96 | [{:keys [type method _transferred resource error] :as _evt}] 97 | (let [{:keys [name size repository _transfer-start-time]} resource] 98 | (case type 99 | :started (do 100 | (print (case method :get "Retrieving" :put "Sending") 101 | name 102 | (if (neg? size) 103 | "" 104 | (format "(%sk)" (Math/round (double (max 1 (/ size 1024))))))) 105 | (when (< 70 (+ 10 (count name) (count repository))) 106 | (println) (print " ")) 107 | (println (case method :get "from" :put "to") repository)) 108 | (:corrupted :failed) (when error (println (.getMessage ^Exception error))) 109 | nil))) 110 | 111 | (defn- repository-system 112 | [] 113 | (let [error-handler (clojure.core/proxy [DefaultServiceLocator$ErrorHandler] [] 114 | (serviceCreationFailed [type-clazz impl-clazz ^Throwable e] 115 | (stacktrace/print-cause-trace e)))] 116 | (.getService 117 | (doto (MavenRepositorySystemUtils/newServiceLocator) 118 | (.setService TransporterFactory WagonTransporterFactory) 119 | (.setService WagonProvider PomegranateWagonProvider) 120 | (.addService RepositoryConnectorFactory BasicRepositoryConnectorFactory) 121 | (.addService TransporterFactory FileTransporterFactory) 122 | (.setErrorHandler error-handler)) 123 | RepositorySystem))) 124 | 125 | (defn- construct-transfer-listener 126 | [transfer-listener] 127 | (cond 128 | (instance? TransferListener transfer-listener) transfer-listener 129 | 130 | (= transfer-listener :stdout) 131 | (TransferListenerProxy. (comp stdout-listener-fn transfer-event)) 132 | 133 | (fn? transfer-listener) 134 | (TransferListenerProxy. (comp transfer-listener transfer-event)) 135 | 136 | :else (TransferListenerProxy. (fn [_])))) 137 | 138 | (defn- generate-checksums-by-default 139 | "Return repository `session` configured with pomegranate checksum defaults. 140 | 141 | By default, maven resolver does not generate checksums for .asc files, but pomegranate prefers to do so. 142 | 143 | We automatically upconvert the legacy (and removed) `aether.checksums.forSignature` 144 | to its replacement `aether.checksums.omitChecksumsForExtensions`. 145 | If both options are specified `aether.checksums.omitChecksumsForExtensions` takes precedence." 146 | [^DefaultRepositorySystemSession session] 147 | (let [config (.getConfigProperties session) 148 | option "aether.checksums.omitChecksumsForExtensions" 149 | value-to-generate-checksums "" ;; as per maven docs 150 | value (get config option) 151 | legacy-option "aether.checksums.forSignature" 152 | legacy-value (get config legacy-option)] 153 | (if (and (nil? value) 154 | (or (nil? legacy-value) (= "true" legacy-value) (true? legacy-value))) 155 | (.setConfigProperty session option value-to-generate-checksums) 156 | session))) 157 | 158 | (defn repository-session 159 | "⚙️ This is considered a lower level function, and used by other aether fns. 160 | Use it if you need to, but there's a good chance you won't need to. 161 | 162 | Returns a repository session created from options map. 163 | 164 | Typically used when overriding via `:repository-session-fn` option available 165 | in various aether fns. In this usage, your `:repository-session-fn` will typically 166 | pass through the options map to `repository-session` but then will tweak the 167 | returned session to your specific needs." 168 | [{:keys [repository-system local-repo offline? transfer-listener mirror-selector]}] 169 | (let [session (org.apache.maven.repository.internal.MavenRepositorySystemUtils/newSession) 170 | session (doto session 171 | (.setLocalRepositoryManager (.newLocalRepositoryManager 172 | ^RepositorySystem repository-system 173 | session 174 | (LocalRepository. 175 | (io/file (or local-repo default-local-repo))))) 176 | (.setMirrorSelector mirror-selector) 177 | (.setOffline (boolean offline?)) 178 | (.setTransferListener (construct-transfer-listener transfer-listener)))] 179 | (generate-checksums-by-default session))) 180 | 181 | (def update-policies {:daily RepositoryPolicy/UPDATE_POLICY_DAILY 182 | :always RepositoryPolicy/UPDATE_POLICY_ALWAYS 183 | :never RepositoryPolicy/UPDATE_POLICY_NEVER}) 184 | 185 | (def checksum-policies {:fail RepositoryPolicy/CHECKSUM_POLICY_FAIL 186 | :ignore RepositoryPolicy/CHECKSUM_POLICY_IGNORE 187 | :warn RepositoryPolicy/CHECKSUM_POLICY_WARN}) 188 | 189 | (defn- policy 190 | [policy-settings enabled?] 191 | (RepositoryPolicy. 192 | (boolean enabled?) 193 | (update-policies (:update policy-settings :daily)) 194 | (checksum-policies (:checksum policy-settings :fail)))) 195 | 196 | (defn- set-policies 197 | [repo-builder settings] 198 | (doto ^org.eclipse.aether.repository.RemoteRepository$Builder repo-builder 199 | (.setSnapshotPolicy (policy settings (:snapshots settings true))) 200 | (.setReleasePolicy (policy settings (:releases settings true))))) 201 | 202 | (defn- authentication 203 | [{:keys [username password passphrase private-key-file] :as _settings}] 204 | (-> (AuthenticationBuilder.) 205 | (.addUsername username) 206 | (.addPassword ^String password) 207 | (.addPrivateKey ^String private-key-file ^String passphrase) 208 | .build)) 209 | 210 | (defn- set-authentication 211 | [repo-builder {:keys [username password passphrase private-key-file] :as settings}] 212 | (if (or username password private-key-file passphrase) 213 | (.setAuthentication ^org.eclipse.aether.repository.RemoteRepository$Builder repo-builder (authentication settings)) 214 | repo-builder)) 215 | 216 | (defn- set-proxy 217 | [repo-builder {:keys [type host port non-proxy-hosts] 218 | :or {type "http"} 219 | :as proxy}] 220 | (if (and host port) 221 | (let [prx-sel (doto (DefaultProxySelector.) 222 | (.add (Proxy. type host port (authentication proxy)) 223 | non-proxy-hosts)) 224 | prx (.getProxy prx-sel (.build ^org.eclipse.aether.repository.RemoteRepository$Builder repo-builder))] ; ugg. 225 | ;; Don't know how to get around "building" the repo for this 226 | (.setProxy ^org.eclipse.aether.repository.RemoteRepository$Builder repo-builder prx)) 227 | repo-builder)) 228 | 229 | (defn make-repository 230 | "⚙️ This is considered a lower level function, and used by other aether fns. 231 | Use it if you need to, but there's a good chance you won't need to. 232 | 233 | Returns an `org.eclipse.aether.repository.RemoteRepository` instance for repository `id` with `settings` using `proxy` 234 | 235 | - `id` - name of maven repository 236 | - `settings` - is either a string representing the URL of the repository or an options map: 237 | - `:url` - string URL of the repository 238 | - `:snapshots` (optional, default `true`) - use snapshots versions? 239 | - `:releases` (optional, default `true`) - use release versions? 240 | - `:username` (as required by repository) - login username for repository 241 | - `:password` (as required by repository) - login password for repository 242 | - `:passphrase` (as required by repository) - login passphrase for repository 243 | - `:private-key-file` - (as required by repository) login private key file for repository 244 | - `:update` - (optional) `:daily` (default) | `:always` | `:never` 245 | - `:checksum` - (optional) `:fail` (default) | `:ignore` | `:warn` 246 | - `proxy` - same as `:proxy` for [[deploy]]" 247 | ^org.eclipse.aether.repository.RemoteRepository 248 | [[id settings] proxy] 249 | (let [settings-map (if (string? settings) 250 | {:url settings} 251 | settings)] 252 | (.build 253 | (doto (RemoteRepository$Builder. (and id (name id)) 254 | (:type settings-map "default") 255 | (str (:url settings-map))) 256 | (set-policies settings-map) 257 | (set-authentication settings-map) 258 | (set-proxy proxy))))) 259 | 260 | (defn- group 261 | [group-artifact] 262 | (or (namespace group-artifact) (name group-artifact))) 263 | 264 | (defn- coordinate-string 265 | "Produces a coordinate string with a format of 266 | :[:[:]]:> 267 | given a lein-style dependency spec. :extension defaults to jar." 268 | [[group-artifact version & {:keys [classifier extension] :or {extension "jar"}}]] 269 | (->> [(group group-artifact) (name group-artifact) extension classifier version] 270 | (remove nil?) 271 | (interpose \:) 272 | (apply str))) 273 | 274 | (defn- exclusion 275 | [[group-artifact & {:as opts}]] 276 | (Exclusion. 277 | (group group-artifact) 278 | (name group-artifact) 279 | (:classifier opts "*") 280 | (:extension opts "*"))) 281 | 282 | (defn- normalize-exclusion-spec [spec] 283 | (if (symbol? spec) 284 | [spec] 285 | spec)) 286 | 287 | (defn- artifact 288 | [[_group-artifact _version & {:keys [_scope _optional _exclusions]} :as dep-spec]] 289 | (DefaultArtifact. (coordinate-string dep-spec))) 290 | 291 | (defn dependency 292 | "⚙️ This is considered a lower level function, and used by other aether fns. 293 | Use it if you need to, but there's a good chance you won't need to. 294 | 295 | Returns an `org.eclipse.aether.graph.Dependency` instance converted from a lein-style dependency vector." 296 | ([dep-spec] (dependency dep-spec "compile")) 297 | ([[_group-artifact _version & {:keys [scope optional exclusions] 298 | :as _opts 299 | :or {optional false}} 300 | :as dep-spec] 301 | default-scope] 302 | (Dependency. (artifact dep-spec) 303 | (or scope default-scope) 304 | optional 305 | (map (comp exclusion normalize-exclusion-spec) exclusions)))) 306 | 307 | (declare dep-spec*) 308 | 309 | (defn- exclusion-spec 310 | "Given an Aether Exclusion, returns a lein-style exclusion vector with the 311 | :exclusion in its metadata." 312 | [^Exclusion ex] 313 | (with-meta (-> ex bean dep-spec*) {:exclusion ex})) 314 | 315 | (defn- dep-spec 316 | "Given an Aether Dependency, returns a lein-style dependency vector with the 317 | :dependency and its corresponding artifact's :file in its metadata." 318 | [^Dependency dep] 319 | (let [artifact (.getArtifact dep)] 320 | (-> (merge (bean dep) (bean artifact)) 321 | dep-spec* 322 | (with-meta {:dependency dep :file (.getFile artifact)})))) 323 | 324 | (defn- dep-spec* 325 | "Base function for producing lein-style dependency spec vectors for dependencies 326 | and exclusions." 327 | [{:keys [groupId artifactId version classifier extension scope optional exclusions] 328 | :or {version nil 329 | scope "compile" 330 | optional false 331 | exclusions nil}}] 332 | (let [group-artifact (apply symbol (if (= groupId artifactId) 333 | [artifactId] 334 | [groupId artifactId]))] 335 | (vec (concat [group-artifact] 336 | (when version [version]) 337 | (when (and (seq classifier) 338 | (not= "*" classifier)) 339 | [:classifier classifier]) 340 | (when (and (seq extension) 341 | (not (#{"*" "jar"} extension))) 342 | [:extension extension]) 343 | (when optional [:optional true]) 344 | (when (not= scope "compile") 345 | [:scope scope]) 346 | (when (seq exclusions) 347 | [:exclusions (vec (map exclusion-spec exclusions))]))))) 348 | 349 | (defn- create-artifact 350 | [files artifact] 351 | (if-let [file (get files artifact)] 352 | (-> (coordinate-string artifact) 353 | DefaultArtifact. 354 | (.setFile (io/file file))) 355 | (throw (IllegalArgumentException. (str "No file provided for artifact " artifact))))) 356 | 357 | (defn deploy-artifacts 358 | "⚙️ This is considered a lower level function, and used by other aether fns. 359 | Use it if you need to, but there's a good chance you won't need to. 360 | Consider instead: [[deploy]]. 361 | 362 | Deploy artifact `:files` to `:repository`. 363 | 364 | kwarg options: 365 | - `:files` - describes files to be deployed. 366 | Map key is artifact vector coordinate and value is associated `java.io.File` file. 367 | All artifacts should have the same version, group and artifact IDs. 368 | Examples of artifact vectors keys: 369 | - `'[group/artifact \"1.0.0\"]` or 370 | - `'[group/artifact \"1.0.0\" :extension \"pom\"]` 371 | - `:repository` - same as [[deploy]] 372 | - `:local-repo` - (optional, default `~/.m2/repository`) - `java.io.File` path to the local repository 373 | - `:transfer-listener` - same as [[deploy]] 374 | - `:proxy` - same as [[deploy]] 375 | - `:repository-session-fn` - (optional, defaults to [[repository-session]])" 376 | 377 | [& {:keys [files repository local-repo transfer-listener proxy repository-session-fn]}] 378 | (when (empty? files) 379 | (throw (IllegalArgumentException. "Must provide valid :files to deploy-artifacts"))) 380 | (when (->> (keys files) 381 | (map (fn [[ga v]] [(if (namespace ga) ga (symbol (str ga) (str ga))) v])) 382 | set 383 | count 384 | (< 1)) 385 | (throw (IllegalArgumentException. 386 | (str "Provided artifacts have varying version, group, or artifact IDs: " (keys files))))) 387 | (let [system (repository-system) 388 | session ((or repository-session-fn 389 | repository-session) 390 | {:repository-system system 391 | :local-repo local-repo 392 | :offline? false 393 | :transfer-listener transfer-listener})] 394 | (.deploy ^RepositorySystem system session 395 | (doto (DeployRequest.) 396 | (.setArtifacts (vec (map (partial create-artifact files) (keys files)))) 397 | (.setRepository (first (map #(make-repository % proxy) repository))))))) 398 | 399 | (defn install-artifacts 400 | "⚙️ This is considered a lower level function, and used by other aether fns. 401 | Use it if you need to, but there's a good chance you won't need to. 402 | Consider instead: [[install]]. 403 | 404 | Deploy the `:files` arg using the coordinates kwarg to the repository kwarg. 405 | 406 | kwarg options: 407 | - `:files` - see [[deploy]] 408 | - `:local-repo` - (optional, default `~/.m2/repository`) - `java.io.File` path to the local repository 409 | - `:transfer-listener` - see [[deploy]] 410 | - `:repository-session-fn` - (optional, defaults to [[repository-session]])" 411 | 412 | [& {:keys [files local-repo transfer-listener repository-session-fn]}] 413 | (let [system (repository-system) 414 | session ((or repository-session-fn 415 | repository-session) 416 | {:repository-system system 417 | :local-repo local-repo 418 | :offline? false 419 | :transfer-listener transfer-listener})] 420 | (.install ^RepositorySystem system session 421 | (doto (InstallRequest.) 422 | (.setArtifacts (vec (map (partial create-artifact files) (keys files)))))))) 423 | 424 | (defn- artifacts-for 425 | "Takes a coordinates map, an a map from partial coordinates to " 426 | [coordinates file-map] 427 | (zipmap (map (partial into coordinates) (keys file-map)) (vals file-map))) 428 | 429 | (defn- optional-artifact 430 | "Takes a coordinates map, an a map from partial coordinates to " 431 | [artifact-coords path] 432 | (when path {artifact-coords path})) 433 | 434 | (defn deploy 435 | "Deploy `:jar-file` and `:pom-file` to `:coordinates` in maven `:repository`. 436 | 437 | For more control use `:artifact-map` in place of, or in addition to, `:jar-file` and `:pom-file`. 438 | 439 | kwarg options: 440 | - `:coordinates` - lein-style `'[group/name \"version\"]` 441 | - `:artifact-map` - (optional) describes artifacts to be deployed, see also `:jar-file` and `:pom-file`. 442 | Map key is a partial artifact vector and value is associated `java.io.File` file. 443 | Coordinates automatically populated from `:coordinates` and should not be respecified. 444 | Examples of partial artifact vectors keys: 445 | - `[]` - for a jar 446 | - `[:extension \"pom\"]` - for a pom 447 | - `:jar-file` - a `java.io.File` pointing to the jar 448 | - `:pom-file` - a `java.io.File` pointing to the pom 449 | - `:repository` - single entry map of `name` to either 450 | - `url` string 451 | - `settings` map of 452 | - `:url` - string URL of the repository 453 | - `:snapshots` (optional, default `true`) - use snapshots versions? 454 | - `:releases` (optional, default `true`) - use release versions? 455 | - `:username` (as required by repository) - login username for repository 456 | - `:password` (as required by repository) - login password for repository 457 | - `:passphrase` (as required by repository) - login passphrase for repository 458 | - `:private-key-file` - (as required by repository) login private key file for repository 459 | - `:update` - (optional) `:daily` (default) | `:always` | `:never` 460 | - `:checksum` - (optional) `:fail` (default) | `:ignore` | `:warn` 461 | - `:local-repo` - (optional, default `~/.m2/repository`) - `java.io.File` path to the local repository 462 | - `:transfer-listener` - (optional, default no listener), can be: 463 | - `:stdout`, writes notifications and progress indicators to stdout, suitable for an 464 | interactive console program 465 | - a function of one argument, which will be called with a map derived from 466 | each event: 467 | - `:type` - `:initiated`, `:started`, `:progressed`, `:corrupted`, `:succeeded`, or `:failed` 468 | - `:method` - `:get` or `:put` 469 | - `:transferred` - number of bytes transferred 470 | - `:error` - the `Exception` that occured, if any, during the transfer 471 | - `:data-buffer` - the `java.nio.ByteBuffer` holding the transferred bytes since the last event 472 | - `:data-length` - the number of bytes transferred since the last event 473 | - `:resource` - a map of: 474 | - `:repository` - string URL of the repository 475 | - `:name` - string path of the resource relative to the repository's base url 476 | - `:file` - the local `File` being uploaded or downloaded 477 | - `:size` - the size of the resource 478 | - `:transfer-start-time` - long epoch 479 | - `:trace` - `org.eclipse.aether.RequestTrace` instance 480 | - an instance of `org.eclipse.aether.transfer.TransferListener` 481 | - `:proxy` - (optional, default no proxy) the `:host` scheme and `:type` must match 482 | - `:host` - proxy hostname 483 | - `:type` - (optional, default `\"http\"`) | `\"https\"` 484 | - `:port` - proxy port 485 | - `:non-proxy-hosts` - (optional) The list of hosts to exclude from proxying 486 | - `:username` - (optional) login username 487 | - `:password` - (optional) login password 488 | - `:passphrase` - (optional) login passphrase 489 | - `:private-key-file` - (optional) login private key file 490 | - `:repository-session-fn` - (optional, defaults to [[repository-session]])" 491 | [& {:keys [coordinates artifact-map jar-file pom-file] :as opts}] 492 | (when (empty? coordinates) 493 | (throw 494 | (IllegalArgumentException. "Must provide valid :coordinates to deploy"))) 495 | (apply deploy-artifacts 496 | (apply concat (assoc opts 497 | :files (artifacts-for 498 | coordinates 499 | (merge 500 | artifact-map 501 | (optional-artifact [:extension "pom"] pom-file) 502 | (optional-artifact [] jar-file))))))) 503 | 504 | (defn install 505 | "Install `:jar-file` and `:pom-file` to `:coordinates` in `:local-repo`. 506 | 507 | For more control use `:artifact-map` in place of, or in addition to, `:jar-file` and `:pom-file`. 508 | 509 | kwarg options: 510 | - `:coordinates` - lein-style `'[group/name \"version\"]` 511 | - `:artifact-map` - same as [[deploy]] 512 | - `:jar-file` - a `java.io.File` pointing to the jar 513 | - `:pom-file` - a `java.io.File` pointing to the pom 514 | - `:local-repo` - (optional, default `~/.m2/repository`) - `java.io.File` path to the local repository 515 | - `:transfer-listener` - same as [[deploy]]" 516 | [& {:keys [coordinates artifact-map jar-file pom-file] :as opts}] 517 | (when (empty? coordinates) 518 | (throw 519 | (IllegalArgumentException. "Must provide valid :coordinates to install"))) 520 | (apply install-artifacts 521 | (apply concat (assoc opts 522 | :files (artifacts-for 523 | coordinates 524 | (merge 525 | artifact-map 526 | (optional-artifact [:extension "pom"] pom-file) 527 | (optional-artifact [] jar-file))))))) 528 | 529 | (defn- dependency-graph 530 | ([node] 531 | (reduce (fn [g ^DependencyNode n] 532 | (if-let [dep (.getDependency n)] 533 | (update-in g [(dep-spec dep)] 534 | cset/union 535 | (->> (.getChildren n) 536 | (map #(.getDependency ^DependencyNode %)) 537 | (map dep-spec) 538 | set)) 539 | g)) 540 | {} 541 | (tree-seq (constantly true) 542 | #(seq (.getChildren ^DependencyNode %)) 543 | node)))) 544 | 545 | (defn- mirror-selector-fn 546 | "Default mirror selection function. The first argument should be a map 547 | like that described as the :mirrors argument in resolve-dependencies. 548 | The second argument should be a repository spec, also as described in 549 | resolve-dependencies. Will return the mirror spec that matches the 550 | provided repository spec." 551 | [mirrors {:keys [name url _snapshots _releases]}] 552 | (let [mirrors (filter (fn [[matcher _mirror-spec]] 553 | (or 554 | (and (string? matcher) (or (= matcher name) (= matcher url))) 555 | (and (instance? java.util.regex.Pattern matcher) 556 | (or (re-matches matcher name) (re-matches matcher url))))) 557 | mirrors)] 558 | (case (count mirrors) 559 | 0 nil 560 | 1 (-> mirrors first second) 561 | (if (some nil? (map second mirrors)) 562 | ;; wildcard override 563 | nil 564 | (throw (IllegalArgumentException. 565 | (str "Multiple mirrors configured to match repository " {name url} ": " 566 | (into {} (map #(update-in % [1] select-keys [:name :url]) mirrors))))))))) 567 | 568 | (defn- mirror-selector 569 | "Returns a MirrorSelector that delegates matching of mirrors to given remote repositories 570 | to the provided function. Any returned repository specifications are turned into 571 | RemoteRepository instances, and configured to use the provided proxy." 572 | [mirror-selector-fn proxy] 573 | (reify MirrorSelector 574 | (getMirror [_ repo] 575 | (let [repo-spec {:name (.getId repo) 576 | :url (.getUrl repo) 577 | :snapshots (-> repo (.getPolicy true) .isEnabled) 578 | :releases (-> repo (.getPolicy false) .isEnabled)} 579 | 580 | {:keys [name repo-manager content-type] :as mirror-spec} 581 | (mirror-selector-fn repo-spec)] 582 | (when-let [mirror (and mirror-spec (make-repository [name mirror-spec] proxy))] 583 | (-> (RemoteRepository$Builder. mirror) 584 | (.setMirroredRepositories [repo]) 585 | (.setRepositoryManager (boolean repo-manager)) 586 | (.setContentType (or content-type "default")) 587 | (.build))))))) 588 | 589 | (defn resolve-artifacts* 590 | "⚙️ This is considered a lower level function, and used by other aether fns. 591 | Use it if you need to, but there's a good chance you won't need to. 592 | Consider instead: [[resolve-artifacts]]. 593 | 594 | Resolves artifacts for `:coordinates` from `:repositories`. 595 | 596 | Returns an sequence of either : 597 | - `org.eclipse.aether.ArtifactResult` when `:retrieve true` (the default) 598 | - `org.eclipse.aether.VersionResult` when `:retrieve false` 599 | 600 | If you don't want to mess with the Aether implementation classes, then use 601 | [[resolve-artifacts]] instead. 602 | 603 | See [[resolve-artifacts]] for kwarg options." 604 | [& {:keys [repositories coordinates files retrieve local-repo 605 | transfer-listener offline? proxy mirrors repository-session-fn] 606 | :or {retrieve true}}] 607 | (when repositories 608 | (assert (seq repositories) 609 | "Empty but truthy `repositories` value found. Please set to nil for using the default, or add a non-empty coll of repositories.")) 610 | (let [repositories (or repositories maven-central) 611 | system (repository-system) 612 | mirror-selector-fn (memoize (partial mirror-selector-fn mirrors)) 613 | mirror-selector (mirror-selector mirror-selector-fn proxy) 614 | session ((or repository-session-fn 615 | repository-session) 616 | {:repository-system system 617 | :local-repo local-repo 618 | :offline? offline? 619 | :transfer-listener transfer-listener 620 | :mirror-selector mirror-selector}) 621 | deps (->> coordinates 622 | (map #(if-let [local-file (get files %)] 623 | (-> ^Artifact (artifact %) 624 | (.setProperties 625 | {ArtifactProperties/LOCAL_PATH 626 | (.getPath (io/file local-file))})) 627 | (artifact %))) 628 | vec) 629 | repositories (vec (map #(let [repo (make-repository % proxy)] 630 | (-> ^RepositorySystemSession session 631 | (.getMirrorSelector) 632 | (.getMirror repo) 633 | (or repo))) 634 | repositories))] 635 | (if retrieve 636 | (.resolveArtifacts 637 | ^RepositorySystem system session (map #(ArtifactRequest. % repositories nil) deps)) 638 | (doall 639 | (for [dep deps] 640 | (.resolveVersion 641 | ^RepositorySystem system session (VersionRequest. dep repositories nil))))))) 642 | 643 | (defn resolve-artifacts 644 | "Resolves artifacts for `:coordinates` in `:repositories`. 645 | 646 | Same as [[resolve-artifacts*]], but returns a sequence of lein-style dependency vectors; each 647 | adorned with metadata: 648 | - `:dependency` - the Aether dependency object 649 | - `:file` - the artifact's `java.io.File` on disk. 650 | 651 | kwarg options: 652 | - `:coordinates` - lein-style `'[[group/name \"version\" & settings] ..]` 653 | where settings is kwargs of: 654 | - `:extension` - (optional) - the maven extension type to require, for example: `\"pom\"` 655 | - `:classifier` - (optional) - the maven classifier to require, for example: `\"sources\"` 656 | - `:scope` - (optional, default `\"compile\"`) - the maven scope for the dependency 657 | - `:optional` - (optional, default `false`) - is the dependency optional? 658 | - `:exclusions` - (optional) which sub-dependencies to skip : lein-style `[group/name & settings]` 659 | where settings is kwargs of: 660 | - `:classifier` (optional, default `\"*\"`) 661 | - `:extension` (optional, default `\"*\"`) 662 | - `:repositories`- (optional, default `{\"central\" \"https://repo1.maven.org/maven2/\"}`) 663 | map of `name` to either 664 | - `url` string 665 | - `settings` map of 666 | - `:url` - string URL of the repository 667 | - `:snapshots` (optional, default `true`) - use snapshots versions? 668 | - `:releases` (optional, defalt `true`) - use release versions? 669 | - `:username` (as required by repository) - login username for repository 670 | - `:password` (as required by repository) - login password for repository 671 | - `:passphrase` (as required by repository) - login passphrase for repository 672 | - `:private-key-file` - (as required by repository) login private key file for repository 673 | - `:update` - (optional) `:daily` (default) | `:always` | `:never` 674 | - `:checksum` - (optional) `:fail` (default) | `:ignore` | `:warn` 675 | - `:retrieve` - (optional, default `true`) - specify `false` to disable downloading of dependencies 676 | - `:local-repo` - (optional, default `~/.m2/repository`) - `java.io.File` path to the local repository 677 | - `:offline?` - if `true`, no remote repositories will be contacted 678 | - `:transfer-listener` - same as [[deploy]] 679 | - `:proxy` - same as [[deploy]] 680 | - `:mirrors` - map of `match` to `settings` where: 681 | - `match` is a string or regex that will be used to match the mirror to 682 | candidate repositories. Attempts will be made to match the 683 | string/regex to repository names and URLs, with exact string 684 | matches preferred. Wildcard mirrors can be specified with 685 | a match-all regex such as `#\".+\"`. Excluding a repository 686 | from mirroring can be done by mapping a string or regex matching 687 | the repository in question to nil. 688 | - `settings` includes the following keys, and all those supported by `:repositories` `:settings`. 689 | - `:name` - name/id of the mirror 690 | - `:repo-manager` - whether the mirror is a repository manager 691 | - `:repository-session-fn` - (optional, defaults to [[repository-session]])" 692 | [& args] 693 | (let [{:keys [coordinates]} (apply hash-map args)] 694 | (->> (apply resolve-artifacts* args) 695 | (map 696 | (fn [coord result] 697 | {:pre [coord result]} 698 | (let [m (when (instance? ArtifactResult result) 699 | {:file (.. ^ArtifactResult result getArtifact getFile)})] 700 | (with-meta coord 701 | (merge {:result result} m)))) 702 | coordinates)))) 703 | 704 | 705 | (defn- add-version-from-managed-coord 706 | "Given an entry from a coordinates vector, and the corresponding entry from the 707 | managed coordinates vector, update the version number in the coordinate with the 708 | value from the managed coordinate." 709 | [coord managed-coord] 710 | (if-let [managed-version (second managed-coord)] 711 | (vec (concat [(first coord) managed-version] 712 | (nthrest coord 2))) 713 | (throw (IllegalArgumentException. (str "Provided artifact is missing a version: " coord))))) 714 | 715 | (defn- coordinates-match? 716 | [[dep _version & opts] [sdep _sversion & sopts]] 717 | (let [om (apply hash-map opts) 718 | som (apply hash-map sopts)] 719 | (and 720 | (= (group dep) 721 | (group sdep)) 722 | (= (name dep) 723 | (name sdep)) 724 | (= (:extension om "jar") 725 | (:extension som "jar")) 726 | (= (:classifier om) 727 | (:classifier som))))) 728 | 729 | (defn- find-managed-coord 730 | "Given an entry from a coordinates vector, and a managed coordinates vector, find 731 | the entry in the managed coordinates vector (if any) that matches the coordinate." 732 | [coord managed-coords] 733 | (first (filter #(coordinates-match? coord %) managed-coords))) 734 | 735 | (defn- add-version-from-managed-coords-if-missing 736 | "Given a managed coordinates vector and an entry from a coordinates vector, check 737 | to see if the coordinate specifies a version string, and if not, update it with 738 | the version string from the managed coordinates (if it is defined)." 739 | [managed-coords coord] 740 | (if (nil? (second coord)) 741 | (add-version-from-managed-coord coord (find-managed-coord coord managed-coords)) 742 | coord)) 743 | 744 | (defn merge-versions-from-managed-coords 745 | "⚙️ This is considered a lower level function, and used by other aether fns. 746 | Use it if you need to, but there's a good chance you won't need to. 747 | 748 | Returns `coordinates` with any `nil` or missing versions merged in from 749 | `managed-coordinates`. 750 | 751 | Coordinates are lein-style, e.g. `'[[group/name \"version\" & settings] ..]`" 752 | [coordinates managed-coordinates] 753 | (vec (map (partial add-version-from-managed-coords-if-missing managed-coordinates) 754 | coordinates))) 755 | 756 | (defn- coords->Dependencies 757 | "Converts a coordinates vector to the maven representation, as Dependency objects." 758 | [files coordinates default-scope] 759 | (->> coordinates 760 | (map #(let [^Dependency dep (dependency % default-scope)] 761 | (if-let [local-file (get files %)] 762 | (.setArtifact dep 763 | (-> dep 764 | .getArtifact 765 | (.setProperties {ArtifactProperties/LOCAL_PATH 766 | (.getPath (io/file local-file))}))) 767 | dep))) 768 | vec)) 769 | 770 | (defn resolve-dependencies* 771 | "⚙️ This is considered a lower level function, and used by other aether fns. 772 | Use it if you need to, but there's a good chance you won't need to. 773 | Consider instead: [[resolve-dependencies]]. 774 | 775 | Returns a graph of dependencies for `:coordinates` in `:repositories`. 776 | 777 | Returns an instance of either: 778 | - `org.eclipse.aether.resolution.DependencyResult` if `:retrieve true` (the default) 779 | - `org.eclipse.aether.collection.CollectResult` if `:retrieve false` 780 | 781 | If you don't want to mess with the Aether implementation classes, then use 782 | [[resolve-dependencies]] instead. 783 | 784 | See [[resolve-dependencies]] for kwarg options." 785 | [& {:keys [repositories coordinates managed-coordinates files retrieve local-repo 786 | transfer-listener offline? proxy mirrors repository-session-fn] 787 | :or {retrieve true}}] 788 | (let [repositories (or repositories maven-central) 789 | system (repository-system) 790 | mirror-selector-fn (memoize (partial mirror-selector-fn mirrors)) 791 | mirror-selector (mirror-selector mirror-selector-fn proxy) 792 | session ((or repository-session-fn 793 | repository-session) 794 | {:repository-system system 795 | :local-repo local-repo 796 | :offline? offline? 797 | :transfer-listener transfer-listener 798 | :mirror-selector mirror-selector}) 799 | coordinates (merge-versions-from-managed-coords coordinates managed-coordinates) 800 | deps (coords->Dependencies files coordinates "compile") 801 | managed-deps (coords->Dependencies files managed-coordinates nil) 802 | collect-request (doto (CollectRequest. ^java.util.List deps 803 | ^java.util.List managed-deps 804 | ^java.util.List 805 | (vec (map #(let [repo (make-repository % proxy)] 806 | (-> ^RepositorySystemSession session 807 | (.getMirrorSelector) 808 | (.getMirror repo) 809 | (or repo))) 810 | repositories))) 811 | (.setRequestContext "runtime"))] 812 | (if retrieve 813 | (.resolveDependencies ^RepositorySystem system session (DependencyRequest. collect-request nil)) 814 | (.collectDependencies ^RepositorySystem system session collect-request)))) 815 | 816 | (defn resolve-dependencies 817 | "Returns a graph of dependencies for `:coordinates` in `:repositories`. 818 | 819 | Same as [[resolve-dependencies*]], but returns a graph of lein-style dependency vectors; each 820 | adorned with metadata: 821 | - `:dependency` - the Aether dependency object 822 | - `:file` - the artifact's `java.io.File` on disk. 823 | 824 | kwarg options: 825 | - `:coordinates` - same as [[resolve-artifacts]] 826 | - `:managed-coordinates` - (optional) lein-style `[['group/name \"version\"] ..]` 827 | Used to determine version numbers for any entries in `:coordinates` that 828 | don't explicitly specify them. 829 | - `:repositories` - same as [[resolve-artifacts]] 830 | - `:retrieve` - (optional, default `true`) - specify `false` to disable downloading of dependencies 831 | - `:local-repo` - (optional, default `~/.m2/repository`) - `java.io.File` path to the local repository 832 | - `:offline?` - if `true`, no remote repositories will be contacted 833 | - `:transfer-listener` - same as [[deploy]] 834 | - `:proxy` - same as [[deploy]] 835 | - `:mirrors` - same as [[resolve-artifacts]] 836 | - `:repository-session-fn` - (optional, defaults to [[repository-session]])" 837 | [& args] 838 | (let [result (apply resolve-dependencies* args)] 839 | (if (instance? DependencyResult result) 840 | (-> ^DependencyResult result 841 | .getRoot 842 | dependency-graph) 843 | (-> ^CollectResult result 844 | .getRoot 845 | dependency-graph)))) 846 | 847 | (defn dependency-files 848 | "Returns a seq of `java.io.Files`s from dependency metadata in `graph` (as returned from [[resolve-dependencies]])" 849 | [graph] 850 | (->> graph keys (map (comp :file meta)) (remove nil?))) 851 | 852 | (defn- exclusion= [spec1 spec2] 853 | (let [[dep & opts] (normalize-exclusion-spec spec1) 854 | [sdep & sopts] (normalize-exclusion-spec spec2) 855 | om (apply hash-map opts) 856 | som (apply hash-map sopts)] 857 | (and (= (group dep) 858 | (group sdep)) 859 | (= (name dep) 860 | (name sdep)) 861 | (= (:extension om "*") 862 | (:extension som "*")) 863 | (= (:classifier om "*") 864 | (:classifier som "*")) 865 | spec2))) 866 | 867 | (defn- exclusions-match? [excs sexcs] 868 | (if-let [ex (first excs)] 869 | (if-let [match (some (partial exclusion= ex) sexcs)] 870 | (recur (next excs) (remove #{match} sexcs)) 871 | false) 872 | (empty? sexcs))) 873 | 874 | (defn within? 875 | "⚙️ This is considered a lower level function, and used by other aether fns. 876 | Use it if you need to, but there's a good chance you won't need to. 877 | 878 | Returns `true` if the first coordinate `coord` is a version within the second 879 | coordinate `scoord`. Only the second coordinate is allowed to contain a 880 | version range." 881 | [[_dep version & opts :as coord] [_sdep sversion & sopts :as scoord]] 882 | (let [om (apply hash-map opts) 883 | som (apply hash-map sopts)] 884 | (and (coordinates-match? coord scoord) 885 | (= (:scope om "compile") 886 | (:scope som "compile")) 887 | (= (:optional om false) 888 | (:optional som false)) 889 | (exclusions-match? (:exclusions om) (:exclusions som)) 890 | (or (= version sversion) 891 | (if-let [[_ ver] (re-find #"^(.*)-SNAPSHOT$" sversion)] 892 | (re-find (re-pattern (str "^" ver "-\\d+\\.\\d+-\\d+$")) 893 | version) 894 | (let [gsv (GenericVersionScheme.) 895 | vc (.parseVersionConstraint gsv sversion) 896 | v (.parseVersion gsv version)] 897 | (.containsVersion vc v))))))) 898 | 899 | (defn dependency-hierarchy 900 | "Returns a dependency hierarchy based on `dep-graph` 901 | (as returned by [[resolve-dependencies]]) and `root-coordinates`. 902 | 903 | `root-coordinates` should be the root(s) of the hierarchy. 904 | 905 | Siblings are sorted alphabetically." 906 | [root-coordinates dep-graph] 907 | (let [root-specs (map (comp dep-spec dependency) root-coordinates) 908 | hierarchy (for [root (filter 909 | #(some (fn [root] (within? % root)) root-specs) 910 | (keys dep-graph))] 911 | [root (dependency-hierarchy (dep-graph root) dep-graph)])] 912 | (when (seq hierarchy) 913 | (into (sorted-map-by #(apply compare (map coordinate-string %&))) hierarchy)))) 914 | -------------------------------------------------------------------------------- /src/test-isolated/clojure/cemerick/pomegranate_isolated_test.clj: -------------------------------------------------------------------------------- 1 | (ns cemerick.pomegranate-isolated-test 2 | "These tests modify the classpath and are to be run in their own isolated process. 3 | They are not REPL friendly and will fail if run multiple times." 4 | (:require [cemerick.pomegranate :as p] 5 | [cemerick.pomegranate.aether :as aether] 6 | [cemerick.pomegranate.test-report] 7 | [clojure.test :refer [deftest is]])) 8 | 9 | ;; Simulate dynamic classloader available by default in a REPL session 10 | (defn bind-dynamic-loader 11 | "Ensures the clojure.lang.Compiler/LOADER var is bound to a DynamicClassLoader, 12 | so that we can add to Clojure's classpath dynamically." 13 | [] 14 | (when-not (bound? Compiler/LOADER) 15 | (.bindRoot Compiler/LOADER (clojure.lang.DynamicClassLoader. (clojure.lang.RT/baseLoader))))) 16 | 17 | (defn simulated-repl-loader [] (deref clojure.lang.Compiler/LOADER)) 18 | 19 | (deftest add-dependency-to-classpath 20 | (is (thrown-with-msg? java.io.FileNotFoundException #"Could not locate" 21 | (require '[clojure.math.numeric-tower :as math]))) 22 | (bind-dynamic-loader) 23 | ;; numeric tower is hosted on maven so default repositories is fine 24 | (p/add-dependencies :classloader (simulated-repl-loader) 25 | :coordinates [['org.clojure/math.numeric-tower "0.0.5"]]) 26 | (require '[clojure.math.numeric-tower :as math]) 27 | ;; need a resolve because require is not top-level (requiring-resolve is Clojure 1.10+ so we avoid it) 28 | (is (= 4 ((resolve 'math/expt) 2 2)))) 29 | 30 | (deftest add-dependencies-to-classpath 31 | (is (thrown-with-msg? java.io.FileNotFoundException #"Could not locate" 32 | ;; not crazy about single segment ns as an example but matches README 33 | (require '[incanter.core :as i]))) 34 | (bind-dynamic-loader) 35 | ;; incanter is hosted on clojars 36 | ;; pick an ancient version for compatibility with Pomegranate supported min clojure version 37 | (p/add-dependencies :classloader (simulated-repl-loader) 38 | :coordinates [['incanter/incanter "1.4.1"]] 39 | :repositories (assoc aether/maven-central "clojars" "https://repo.clojars.org")) 40 | (require '[incanter.core :as i]) 41 | ;; need a resolve because require is not top-level (requiring-resolve is Clojure 1.10+ so we avoid it) 42 | (is (= 3 ((resolve 'i/length) [1 2 3])))) 43 | -------------------------------------------------------------------------------- /src/test/clojure/cemerick/pomegranate/aether_test.clj: -------------------------------------------------------------------------------- 1 | (ns cemerick.pomegranate.aether-test 2 | (:require [cemerick.pomegranate.aether :as aether] 3 | [cemerick.pomegranate.test-report] 4 | [clojure.java.io :as io] 5 | [clojure.string :as str] 6 | [clojure.test :refer [deftest is are testing use-fixtures]]) 7 | (:import (java.io File))) 8 | 9 | (deftest dependency-roundtripping 10 | (are [x] (= x (#'aether/dep-spec (#'aether/dependency x))) 11 | '[ring "1.0.0" :optional true] 12 | '[com.cemerick/pomegranate "0.0.1" :classifier "sources"] 13 | '[demo/demo2 "1.0.0" :exclusions [[demo :classifier "jdk5"]]])) 14 | 15 | (def tmp-dir (io/file (System/getProperty "java.io.tmpdir") "pomegranate-test-tmp")) 16 | (def tmp-remote-repo-dir (.getAbsolutePath (io/file tmp-dir "remote-repo"))) 17 | (def tmp-local-repo-dir (io/file tmp-dir "local-repo")) 18 | (def tmp-local-repo2-dir (io/file tmp-dir "local-repo2")) 19 | 20 | (def test-remote-repo {"central" "https://repo1.maven.org/maven2/"}) 21 | 22 | (def test-repo {:test-repo "file://test-repo"}) 23 | (def tmp-remote-repo {"tmp-remote-repo" (str "file://" tmp-remote-repo-dir)}) 24 | 25 | (defn delete-recursive 26 | [^File file] 27 | (when (.isDirectory file) 28 | (doseq [file (.listFiles file)] 29 | (delete-recursive file))) 30 | (when (not (.delete file)) 31 | (throw (ex-info (str "failed to delete: " file) {})))) 32 | 33 | (defn- clear-tmp 34 | [f] 35 | (when (.exists ^File tmp-dir) 36 | (delete-recursive tmp-dir)) 37 | (f)) 38 | 39 | (use-fixtures :each clear-tmp) 40 | 41 | (defn file-path-eq [^File file1 ^File file2] 42 | (= (.getAbsolutePath file1) 43 | (.getAbsolutePath file2))) 44 | 45 | (deftest live-resolution 46 | (let [deps '[[commons-logging "1.1"]] 47 | graph '{[javax.servlet/servlet-api "2.3"] nil, 48 | [avalon-framework "4.1.3"] nil, 49 | [logkit "1.0.1"] nil, 50 | [log4j "1.2.12"] nil, 51 | [commons-logging "1.1"] 52 | #{[javax.servlet/servlet-api "2.3"] [avalon-framework "4.1.3"] 53 | [logkit "1.0.1"] [log4j "1.2.12"]}} 54 | hierarchy '{[commons-logging "1.1"] 55 | {[avalon-framework "4.1.3"] nil, 56 | [javax.servlet/servlet-api "2.3"] nil, 57 | [log4j "1.2.12"] nil, 58 | [logkit "1.0.1"] nil}}] 59 | (is (= graph (aether/resolve-dependencies :coordinates deps :retrieve false :local-repo tmp-local-repo-dir))) 60 | (is (not (some #(-> ^File % .getName (.endsWith ".jar")) (file-seq tmp-local-repo-dir)))) 61 | 62 | (doseq [[dep _] (aether/resolve-dependencies :coordinates deps :local-repo tmp-local-repo-dir)] 63 | (is (-> dep meta :file)) 64 | (is (-> dep meta ^File (:file) .exists))) 65 | (is (some #(-> ^File % .getName (.endsWith ".jar")) (file-seq tmp-local-repo-dir))) 66 | 67 | (is (= hierarchy (aether/dependency-hierarchy deps graph))))) 68 | 69 | (deftest live-artifact-resolution 70 | (let [deps '[[commons-logging "1.1"]]] 71 | (is (= deps (aether/resolve-artifacts 72 | :coordinates deps :retrieve false 73 | :local-repo tmp-local-repo-dir))) 74 | (is (= 1 (count (aether/resolve-artifacts 75 | :coordinates '[[demo "1.0.0"]] :retrieve false 76 | :files {'[demo "1.0.0"] (io/file "test-repo" "demo" "demo" "1.0.0" "demo-1.0.0.jar")} 77 | :local-repo tmp-local-repo-dir)))) 78 | (is (not (some #(-> ^File % .getName (.endsWith ".jar")) 79 | (file-seq tmp-local-repo-dir)))) 80 | (doseq [dep (aether/resolve-artifacts 81 | :coordinates deps :local-repo tmp-local-repo-dir)] 82 | (is (-> dep meta :file)) 83 | (is (-> dep meta ^File (:file) .exists))) 84 | (is (some #(-> ^File % .getName (.endsWith ".jar")) 85 | (file-seq tmp-local-repo-dir))))) 86 | 87 | (deftest impl-detail-types 88 | (let [args [:coordinates '[[commons-logging "1.1"]] :local-repo tmp-local-repo-dir]] 89 | (is (instance? org.eclipse.aether.resolution.DependencyResult 90 | (apply aether/resolve-dependencies* args))) 91 | (is (instance? org.eclipse.aether.collection.CollectResult 92 | (apply aether/resolve-dependencies* :retrieve false args))))) 93 | 94 | (deftest resolve-deps-with-mirror 95 | (let [deps (aether/resolve-dependencies :repositories {"clojars" "https://clojars.org/repo"} 96 | :coordinates '[[javax.servlet/servlet-api "2.5"]] 97 | :mirrors {"clojars" {:url "https://maven-central.storage-download.googleapis.com/repos/central/data"}} 98 | :local-repo tmp-local-repo-dir)] 99 | (is (= 1 (count deps))) 100 | (is (= (.getAbsolutePath (io/file tmp-dir "local-repo" "javax" "servlet" "servlet-api" "2.5" "servlet-api-2.5.jar")) 101 | (.getAbsolutePath ^File (first (aether/dependency-files deps))))))) 102 | 103 | (deftest resolve-deps-with-wildcard-mirror 104 | (let [deps (aether/resolve-dependencies :repositories {"clojars" "https://clojars.org/repo"} 105 | :coordinates '[[javax.servlet/servlet-api "2.5"]] 106 | :mirrors {#".+" {:url "https://maven-central.storage-download.googleapis.com/repos/central/data"}} 107 | :local-repo tmp-local-repo-dir)] 108 | (is (= 1 (count deps))) 109 | (is (= (.getAbsolutePath (io/file tmp-dir "local-repo" "javax" "servlet" "servlet-api" "2.5" "servlet-api-2.5.jar")) 110 | (.getAbsolutePath ^File (first (aether/dependency-files deps))))))) 111 | 112 | (deftest resolve-deps-with-wildcard-override-mirror 113 | (let [deps (aether/resolve-dependencies :repositories test-remote-repo 114 | :coordinates '[[javax.servlet/servlet-api "2.5"]] 115 | :mirrors {#".+" {:url "https://clojars.org/repo"} 116 | (ffirst test-remote-repo) nil} 117 | :local-repo tmp-local-repo-dir)] 118 | (is (= 1 (count deps))) 119 | (is (= (.getAbsolutePath (io/file tmp-dir "local-repo" "javax" "servlet" "servlet-api" "2.5" "servlet-api-2.5.jar")) 120 | (.getAbsolutePath ^File (first (aether/dependency-files deps))))))) 121 | 122 | (deftest resolve-deps 123 | (let [deps (aether/resolve-dependencies :repositories test-repo 124 | :coordinates '[[demo/demo "1.0.0"]] 125 | :local-repo tmp-local-repo-dir)] 126 | (is (= 1 (count deps))) 127 | (is (= (.getAbsolutePath (io/file tmp-dir "local-repo" "demo" "demo" "1.0.0" "demo-1.0.0.jar")) 128 | (.getAbsolutePath ^File (first (aether/dependency-files deps))))))) 129 | 130 | (deftest resolve-deps-with-deps 131 | (let [deps (aether/resolve-dependencies :repositories test-repo 132 | :coordinates '[[demo/demo2 "1.0.0"]] 133 | :local-repo tmp-local-repo-dir) 134 | files (aether/dependency-files deps)] 135 | (is (= 2 (count files))) 136 | (is (= 1 (count (filter #(file-path-eq % (io/file tmp-dir "local-repo" "demo" "demo" "1.0.0" "demo-1.0.0.jar")) 137 | files)))) 138 | (is (= 1 (count (filter #(file-path-eq % (io/file tmp-dir "local-repo" "demo" "demo2" "1.0.0" "demo2-1.0.0.jar")) 139 | files)))))) 140 | 141 | (deftest resolve-unmanaged-dependencies 142 | (let [deps (aether/resolve-dependencies 143 | :repositories {} 144 | :coordinates '[[demo "1.0.0"]] 145 | :files {'[demo "1.0.0"] (io/file "test-repo" "demo" "demo" "1.0.0" "demo-1.0.0.jar")} 146 | :local-repo tmp-local-repo-dir) 147 | files (aether/dependency-files deps)] 148 | (is (= 1 (count files))) 149 | (is (= nil (:file (meta (first files))))))) 150 | 151 | (deftest resolve-deps-with-exclusions 152 | (let [deps (aether/resolve-dependencies :repositories test-repo 153 | :coordinates 154 | '[[demo/demo2 "1.0.0" :exclusions [demo/demo]]] 155 | :local-repo tmp-local-repo-dir)] 156 | (is (= 1 (count deps))) 157 | (is (= (.getAbsolutePath (io/file tmp-dir "local-repo" "demo" "demo2" "1.0.0" "demo2-1.0.0.jar")) 158 | (.getAbsolutePath ^File (first (aether/dependency-files deps))))))) 159 | 160 | (deftest resolve-deps-with-classifiers 161 | (let [deps (aether/resolve-dependencies :repositories test-repo 162 | :coordinates 163 | '[[demo/demo "1.0.1" :classifier "test"]] 164 | :local-repo tmp-local-repo-dir)] 165 | (is (= 1 (count deps))) 166 | (is (= (.getAbsolutePath (io/file tmp-dir "local-repo" "demo" "demo" "1.0.1" "demo-1.0.1-test.jar")) 167 | (.getAbsolutePath ^File (first (aether/dependency-files deps))))))) 168 | 169 | (deftest resolve-managed-dependencies 170 | (testing "coordinate inherits version from managed coordinate" 171 | (let [deps (aether/resolve-dependencies 172 | :repositories test-repo 173 | :coordinates '[[demo/demo]] 174 | :managed-coordinates '[[demo/demo "1.0.0"]] 175 | :local-repo tmp-local-repo-dir) 176 | files (aether/dependency-files deps)] 177 | (is (= 1 (count files))) 178 | (is (= 1 (count (filter #(file-path-eq % (io/file tmp-dir "local-repo" "demo" "demo" "1.0.0" "demo-1.0.0.jar")) 179 | files)))))) 180 | (testing "coordinate inherits version from managed coordinate using explicit/implicit group ids" 181 | (let [deps (aether/resolve-dependencies 182 | :repositories test-repo 183 | :coordinates '[[demo/demo]] 184 | :managed-coordinates '[[demo "1.0.0"]] 185 | :local-repo tmp-local-repo-dir) 186 | files (aether/dependency-files deps)] 187 | (is (= 1 (count files))) 188 | (is (= 1 (count (filter #(file-path-eq % (io/file tmp-dir "local-repo" "demo" "demo" "1.0.0" "demo-1.0.0.jar")) 189 | files))))) 190 | (let [deps (aether/resolve-dependencies 191 | :repositories test-repo 192 | :coordinates '[[demo]] 193 | :managed-coordinates '[[demo/demo "1.0.0"]] 194 | :local-repo tmp-local-repo-dir) 195 | files (aether/dependency-files deps)] 196 | (is (= 1 (count files))) 197 | (is (= 1 (count (filter #(file-path-eq % (io/file tmp-dir "local-repo" "demo" "demo" "1.0.0" "demo-1.0.0.jar")) 198 | files)))))) 199 | (testing "coordinate with nil version inherits version from managed coordinate" 200 | (let [deps (aether/resolve-dependencies 201 | :repositories test-repo 202 | :coordinates '[[demo/demo nil]] 203 | :managed-coordinates '[[demo/demo "1.0.0"]] 204 | :local-repo tmp-local-repo-dir) 205 | files (aether/dependency-files deps)] 206 | (is (= 1 (count files))) 207 | (is (= 1 (count (filter #(file-path-eq % (io/file tmp-dir "local-repo" "demo" "demo" "1.0.0" "demo-1.0.0.jar")) 208 | files)))))) 209 | (testing "coordinate with nil version and kwargs inherits version from managed coordinate" 210 | (let [deps (aether/resolve-dependencies 211 | :repositories test-repo 212 | :coordinates '[[demo/demo2 nil :exclusions [demo/demo]]] 213 | :managed-coordinates '[[demo/demo2 "1.0.0"]] 214 | :local-repo tmp-local-repo-dir) 215 | files (aether/dependency-files deps)] 216 | (is (= 1 (count files))) 217 | (is (= 1 (count (filter #(file-path-eq % (io/file tmp-dir "local-repo" "demo" "demo2" "1.0.0" "demo2-1.0.0.jar")) 218 | files)))))) 219 | (testing "error if coordinate missing version number without a managed coordinate" 220 | (is (thrown-with-msg? IllegalArgumentException #"Provided artifact is missing a version: \[demo/demo\]" 221 | (aether/resolve-dependencies 222 | :repositories test-repo 223 | :coordinates '[[demo/demo]] 224 | :local-repo tmp-local-repo-dir)))) 225 | (testing "error if coordinate has nil version number without managed coordinates" 226 | (is (thrown-with-msg? IllegalArgumentException #"Provided artifact is missing a version: \[demo/demo nil\]" 227 | (aether/resolve-dependencies 228 | :repositories test-repo 229 | :coordinates '[[demo/demo nil]] 230 | :local-repo tmp-local-repo-dir)))) 231 | (testing "coordinate version number overrides managed coordinates version" 232 | (let [deps (aether/resolve-dependencies 233 | :repositories test-repo 234 | :coordinates '[[demo/demo "1.0.0"]] 235 | :managed-coordinates '[[demo/demo "0.0.1"]] 236 | :local-repo tmp-local-repo-dir) 237 | files (aether/dependency-files deps)] 238 | (is (= 1 (count files))) 239 | (is (= 1 (count (filter #(file-path-eq % (io/file tmp-dir "local-repo" "demo" "demo" "1.0.0" "demo-1.0.0.jar")) 240 | files)))))) 241 | (testing "managed coordinates version is applied to transitive deps" 242 | (let [deps (aether/resolve-dependencies 243 | :repositories test-repo 244 | :coordinates '[[demo/demo2 "1.0.0"]] 245 | :managed-coordinates '[[demo/demo "1.0.1"]] 246 | :local-repo tmp-local-repo-dir) 247 | files (aether/dependency-files deps)] 248 | (is (= 2 (count files))) 249 | (is (= 1 (count (filter #(file-path-eq % (io/file tmp-dir "local-repo" "demo" "demo2" "1.0.0" "demo2-1.0.0.jar")) 250 | files)))) 251 | (is (= 1 (count (filter #(file-path-eq % (io/file tmp-dir "local-repo" "demo" "demo" "1.0.1" "demo-1.0.1.jar")) 252 | files)))))) 253 | (testing "coordinate scope overrides managed coordinate scope" 254 | (testing "coordinate has nil version" 255 | (let [deps (aether/resolve-dependencies 256 | :repositories test-repo 257 | :coordinates '[[demo/demo nil :scope "test"]] 258 | :managed-coordinates '[[demo/demo "1.0.0"]] 259 | :local-repo tmp-local-repo-dir)] 260 | (is (= deps '{[demo "1.0.0" :scope "test"] nil})))) 261 | (testing "coordinate overrides version" 262 | (let [deps (aether/resolve-dependencies 263 | :repositories test-repo 264 | :coordinates '[[demo/demo "1.0.1" :scope "test"]] 265 | :managed-coordinates '[[demo/demo "1.0.0"]] 266 | :local-repo tmp-local-repo-dir)] 267 | (is (= deps '{[demo "1.0.1" :scope "test"] nil})))) 268 | (testing "managed coordinate specifies scope" 269 | (let [deps (aether/resolve-dependencies 270 | :repositories test-repo 271 | :coordinates '[[demo/demo "1.0.1" :scope "test"]] 272 | :managed-coordinates '[[demo/demo "1.0.0" :scope "provided"]] 273 | :local-repo tmp-local-repo-dir)] 274 | (is (= deps '{[demo "1.0.1" :scope "test"] nil}))) 275 | (let [deps (aether/resolve-dependencies 276 | :repositories test-repo 277 | :coordinates '[[demo/demo "1.0.1" :scope "provided"]] 278 | :managed-coordinates '[[demo/demo "1.0.0" :scope "test"]] 279 | :local-repo tmp-local-repo-dir)] 280 | (is (= deps '{[demo "1.0.1" :scope "provided"] nil}))))) 281 | (testing "coordinate transitive dep scope overrides unspecified managed-coordinate scope" 282 | (testing "test overrides unspecified" 283 | (let [deps (aether/resolve-dependencies 284 | :repositories test-repo 285 | :coordinates '[[demo/demo2 "1.0.0" :scope "test"]] 286 | :managed-coordinates '[[demo/demo "1.0.1"]] 287 | :local-repo tmp-local-repo-dir)] 288 | (is (= '{[demo/demo2 "1.0.0" :scope "test"] #{[demo "1.0.1" :scope "test"]} 289 | [demo "1.0.1" :scope "test"] nil} 290 | deps))) 291 | (testing "compile overrides unspecified" 292 | (let [deps (aether/resolve-dependencies 293 | :repositories test-repo 294 | :coordinates '[[demo/demo2 "1.0.0" :scope "compile"]] 295 | :managed-coordinates '[[demo/demo "1.0.1"]] 296 | :local-repo tmp-local-repo-dir)] 297 | (is (= '{[demo/demo2 "1.0.0"] #{[demo "1.0.1"]} 298 | [demo "1.0.1"] nil} 299 | deps)))) 300 | (testing "implicit compile is effectively compile" 301 | (let [deps (aether/resolve-dependencies 302 | :repositories test-repo 303 | :coordinates '[[demo/demo2 "1.0.0"]] 304 | :managed-coordinates '[[demo/demo "1.0.1"]] 305 | :local-repo tmp-local-repo-dir)] 306 | (is (= '{[demo/demo2 "1.0.0"] #{[demo "1.0.1"]} 307 | [demo "1.0.1"] nil} 308 | deps))))) 309 | (testing "compile does not override specified test" 310 | (let [deps (aether/resolve-dependencies 311 | :repositories test-repo 312 | :coordinates '[[demo/demo2 "1.0.0" :scope "compile"]] 313 | :managed-coordinates '[[demo/demo "1.0.1" :scope "test"]] 314 | :local-repo tmp-local-repo-dir)] 315 | (is (= '{[demo/demo2 "1.0.0"] #{[demo "1.0.1" :scope "test"]} 316 | [demo "1.0.1" :scope "test"] nil} 317 | deps)))) 318 | (testing "unspecified (effectively compile) does not override specified test" 319 | (let [deps (aether/resolve-dependencies 320 | :repositories test-repo 321 | :coordinates '[[demo/demo2 "1.0.0"]] 322 | :managed-coordinates '[[demo/demo "1.0.1" :scope "test"]] 323 | :local-repo tmp-local-repo-dir)] 324 | (is (= '{[demo/demo2 "1.0.0"] #{[demo "1.0.1" :scope "test"]} 325 | [demo "1.0.1" :scope "test"] nil} 326 | deps)))) 327 | (testing "provided does not override specified test" 328 | (let [deps (aether/resolve-dependencies 329 | :repositories test-repo 330 | :coordinates '[[demo/demo2 "1.0.0" :scope "provided"]] 331 | :managed-coordinates '[[demo/demo "1.0.1" :scope "test"]] 332 | :local-repo tmp-local-repo-dir)] 333 | (is (= '{[demo/demo2 "1.0.0" :scope "provided"] #{[demo "1.0.1" :scope "test"]} 334 | [demo "1.0.1" :scope "test"] nil} 335 | deps)))) 336 | (testing "test does not override specified provided" 337 | (let [deps (aether/resolve-dependencies 338 | :repositories test-repo 339 | :coordinates '[[demo/demo2 "1.0.0" :scope "test"]] 340 | :managed-coordinates '[[demo/demo "1.0.1" :scope "provided"]] 341 | :local-repo tmp-local-repo-dir)] 342 | (is (= '{[demo/demo2 "1.0.0" :scope "test"] #{[demo "1.0.1" :scope "provided"]} 343 | [demo "1.0.1" :scope "provided"] nil} 344 | deps))))) 345 | (testing "unused entries in managed coordinates are not resolved" 346 | (let [deps (aether/resolve-dependencies 347 | :repositories test-repo 348 | :coordinates '[[demo/demo]] 349 | :managed-coordinates '[[demo/demo "1.0.0"] 350 | [demo/demo2 "1.0.0"]] 351 | :local-repo tmp-local-repo-dir) 352 | files (aether/dependency-files deps)] 353 | (is (= 1 (count files))) 354 | (is (= 1 (count (filter #(file-path-eq % (io/file tmp-dir "local-repo" "demo" "demo" "1.0.0" "demo-1.0.0.jar")) 355 | files)))))) 356 | (testing "exclusions in managed coordinates are inherited" 357 | (let [deps (aether/resolve-dependencies 358 | :repositories test-repo 359 | :coordinates '[[demo/demo2]] 360 | :managed-coordinates '[[demo/demo2 "1.0.0" :exclusions [demo/demo]]] 361 | :local-repo tmp-local-repo-dir) 362 | files (aether/dependency-files deps)] 363 | (is (= 1 (count files))) 364 | (is (= 1 (count (filter #(file-path-eq % (io/file tmp-dir "local-repo" "demo" "demo2" "1.0.0" "demo2-1.0.0.jar")) 365 | files)))))) 366 | (testing "classifiers in managed coordinates are inherited" 367 | (let [deps (aether/resolve-dependencies 368 | :repositories test-repo 369 | :coordinates '[[demo/demo] 370 | [demo/demo nil :classifier "test"]] 371 | :managed-coordinates '[[demo/demo "1.0.0"] 372 | [demo/demo "1.0.1" :classifier "test"]] 373 | :local-repo tmp-local-repo-dir) 374 | files (aether/dependency-files deps)] 375 | (is (= 2 (count files))) 376 | (is (= 1 (count (filter #(file-path-eq % (io/file tmp-dir "local-repo" "demo" "demo" "1.0.0" "demo-1.0.0.jar")) 377 | files)))) 378 | (is (= 1 (count (filter #(file-path-eq % (io/file tmp-dir "local-repo" "demo" "demo" "1.0.1" "demo-1.0.1-test.jar")) 379 | files))))))) 380 | 381 | (deftest deploy-jar 382 | (aether/deploy :coordinates '[group/artifact "1.0.0"] 383 | :jar-file (io/file "test-repo" "demo" "demo" "1.0.0" "demo-1.0.0.jar") 384 | :repository tmp-remote-repo 385 | :local-repo tmp-local-repo-dir) 386 | (is (= 3 (count (.list (io/file tmp-remote-repo-dir "group" "artifact" "1.0.0")))))) 387 | 388 | (deftest deploy-jar-with-pom 389 | (aether/deploy :coordinates '[group/artifact "1.0.0"] 390 | :jar-file (io/file "test-repo" "demo" "demo" "1.0.0" "demo-1.0.0.jar") 391 | :pom-file (io/file "test-repo" "demo" "demo" "1.0.0" "demo-1.0.0.pom") 392 | :repository tmp-remote-repo 393 | :local-repo tmp-local-repo-dir) 394 | (is (= 6 (count (.list (io/file tmp-remote-repo-dir "group" "artifact" "1.0.0")))))) 395 | 396 | (deftest deploy-jar-with-artifact-map 397 | (let [repo-file (partial io/file "test-repo" "demo" "demo" "1.0.0")] 398 | (aether/deploy 399 | :coordinates '[group/artifact "1.0.0"] 400 | ;;TODO was this first entry supposed be a jar file? 401 | :artifact-map {[] (repo-file "demo-1.0.0.pom") 402 | [:extension "pom"] (repo-file "demo-1.0.0.pom")} 403 | :repository tmp-remote-repo 404 | :local-repo tmp-local-repo-dir)) 405 | (is (= 6 (count (.list (io/file tmp-remote-repo-dir "group" "artifact" "1.0.0")))))) 406 | 407 | (deftest install-jar 408 | (aether/install :coordinates '[group/artifact "1.0.0"] 409 | :jar-file (io/file "test-repo" "demo" "demo" "1.0.0" "demo-1.0.0.jar") 410 | :pom-file (io/file "test-repo" "demo" "demo" "1.0.0" "demo-1.0.0.pom") 411 | :local-repo tmp-local-repo-dir) 412 | (is (= 3 (count (.list (io/file tmp-local-repo-dir "group" "artifact" "1.0.0")))))) 413 | 414 | (deftest install-jar-with-artifact-map 415 | (let [repo-file (partial io/file "test-repo" "demo" "demo" "1.0.0")] 416 | (aether/install 417 | :coordinates '[group/artifact "1.0.0"] 418 | :artifact-map {[] (repo-file "demo-1.0.0.jar") 419 | [:extension "pom"] (repo-file "demo-1.0.0.pom")} 420 | :local-repo tmp-local-repo-dir)) 421 | (is (= 3 (count (.list (io/file tmp-local-repo-dir "group" "artifact" "1.0.0")))))) 422 | 423 | (deftest deploy-artifacts 424 | (aether/deploy-artifacts 425 | :artifacts '[[demo "1.0.0"] 426 | [demo "1.0.0" :extension "jar.asc"] 427 | [demo "1.0.0" :extension "pom"] 428 | [demo "1.0.0" :extension "pom.asc"]] 429 | ;; note: the .asc files in the test-repo are dummies, but it doesn't matter for this test 430 | :files {'[demo "1.0.0"] (io/file "test-repo" "demo" "demo" "1.0.0" "demo-1.0.0.jar") 431 | '[demo "1.0.0" :extension "jar.asc"] (io/file "test-repo" "demo" "demo" "1.0.0" "demo-1.0.0.jar.asc") 432 | '[demo "1.0.0" :extension "pom"] (io/file "test-repo" "demo" "demo" "1.0.0" "demo-1.0.0.pom") 433 | '[demo "1.0.0" :extension "pom.asc"] (io/file "test-repo" "demo" "demo" "1.0.0" "demo-1.0.0.pom.asc")} 434 | :repository tmp-remote-repo 435 | :local-repo tmp-local-repo-dir) 436 | (is (= #{"demo-1.0.0.pom.md5" 437 | "demo-1.0.0.pom.sha1" 438 | "demo-1.0.0.pom" 439 | "demo-1.0.0.pom.asc.md5" 440 | "demo-1.0.0.pom.asc.sha1" 441 | "demo-1.0.0.pom.asc" 442 | "demo-1.0.0.jar.md5" 443 | "demo-1.0.0.jar.sha1" 444 | "demo-1.0.0.jar" 445 | "demo-1.0.0.jar.asc.md5" 446 | "demo-1.0.0.jar.asc.sha1" 447 | "demo-1.0.0.jar.asc"} 448 | (set (.list (io/file tmp-remote-repo-dir "demo" "demo" "1.0.0")))) "Should deploy correctly demo \"1.0.0\"") 449 | (is (= '{[demo "1.0.0"] nil} 450 | (aether/resolve-dependencies :repositories tmp-remote-repo 451 | :coordinates 452 | '[[demo "1.0.0"]] 453 | :local-repo tmp-local-repo2-dir))) 454 | (is (= '{[demo "1.0.0" :extension "pom"] nil} 455 | (aether/resolve-dependencies :repositories tmp-remote-repo 456 | :coordinates 457 | '[[demo "1.0.0" :extension "pom"]] 458 | :local-repo tmp-local-repo2-dir))) 459 | (is (= '{[demo "1.0.0" :extension "jar.asc"] nil} 460 | (aether/resolve-dependencies :repositories tmp-remote-repo 461 | :coordinates 462 | '[[demo "1.0.0" :extension "jar.asc"]] 463 | :local-repo tmp-local-repo2-dir))) 464 | (is (= '{[demo "1.0.0" :extension "pom.asc"] nil} 465 | (aether/resolve-dependencies :repositories tmp-remote-repo 466 | :coordinates 467 | '[[demo "1.0.0" :extension "pom.asc"]] 468 | :local-repo tmp-local-repo2-dir)))) 469 | 470 | (deftest install-artifacts 471 | (aether/install-artifacts 472 | :artifacts '[[demo "1.0.0"] 473 | [demo "1.0.0" :extension "jar.asc"] 474 | [demo "1.0.0" :extension "pom"] 475 | [demo "1.0.0" :extension "pom.asc"]] 476 | ;; note: the .asc files in the test-repo are dummies, but it doesn't matter for this test 477 | :files {'[demo "1.0.0"] (io/file "test-repo" "demo" "demo" "1.0.0" "demo-1.0.0.jar") 478 | '[demo "1.0.0" :extension "jar.asc"] (io/file "test-repo" "demo" "demo" "1.0.0" "demo-1.0.0.jar.asc") 479 | '[demo "1.0.0" :extension "pom"] (io/file "test-repo" "demo" "demo" "1.0.0" "demo-1.0.0.pom") 480 | '[demo "1.0.0" :extension "pom.asc"] (io/file "test-repo" "demo" "demo" "1.0.0" "demo-1.0.0.pom.asc")} 481 | :local-repo tmp-local-repo-dir) 482 | (is (= #{"demo-1.0.0.jar" 483 | "demo-1.0.0.pom" 484 | "demo-1.0.0.jar.asc" 485 | "demo-1.0.0.pom.asc" 486 | "_remote.repositories"} 487 | (set (.list (io/file tmp-local-repo-dir "demo" "demo" "1.0.0")))))) 488 | 489 | (deftest deploy-exceptions 490 | (is (thrown-with-msg? IllegalArgumentException #"Provided artifacts have varying" 491 | (aether/deploy-artifacts 492 | :artifacts '[[demo "1.0.0"] 493 | [group/demo "1.0.0" :extension "jar.asc"]] 494 | :files {'[demo "1.0.0"] (io/file "test-repo" "demo" "demo" "1.0.0" "demo-1.0.0.jar") 495 | '[group/demo "1.0.0" :extension "jar.asc"] 496 | (io/file "test-repo" "demo" "demo" "1.0.0" "demo-1.0.0.jar.asc")} 497 | :repository tmp-remote-repo 498 | :local-repo tmp-local-repo-dir))) 499 | (is (thrown-with-msg? IllegalArgumentException #"Provided artifacts have varying version, group, or artifact IDs" 500 | (aether/deploy-artifacts 501 | :artifacts '[[demo "1.0.0"] 502 | [demo/artifact "1.0.0" :extension "jar.asc"]] 503 | :files {'[demo "1.0.0"] (io/file "test-repo" "demo" "demo" "1.0.0" "demo-1.0.0.jar") 504 | '[demo/artifact "1.0.0" :extension "jar.asc"] 505 | (io/file "test-repo" "demo" "demo" "1.0.0" "demo-1.0.0.jar.asc")} 506 | :repository tmp-remote-repo 507 | :local-repo tmp-local-repo-dir))) 508 | (is (thrown-with-msg? IllegalArgumentException #"Provided artifacts have varying version, group, or artifact IDs" 509 | (aether/deploy-artifacts 510 | :artifacts '[[demo "1.0.0"] 511 | [demo "1.1.0" :extension "jar.asc"]] 512 | :files {'[demo "1.0.0"] (io/file "test-repo" "demo" "demo" "1.0.0" "demo-1.0.0.jar") 513 | '[demo "1.1.0" :extension "jar.asc"] 514 | (io/file "test-repo" "demo" "demo" "1.0.0" "demo-1.0.0.jar.asc")} 515 | :repository tmp-remote-repo 516 | :local-repo tmp-local-repo-dir))) 517 | (is (thrown-with-msg? IllegalArgumentException #"Provided artifacts have varying version, group, or artifact IDs" 518 | (aether/deploy-artifacts 519 | :files {'[demo "1.0.0"] 520 | (io/file "test-repo" "demo" "demo" "1.0.0" "demo-1.0.0.jar") 521 | '[demo "1.1.0" :extension "jar.asc"] 522 | (io/file "test-repo" "demo" "demo" "1.0.0" "demo-1.0.0.jar")} 523 | :repository tmp-remote-repo 524 | :local-repo tmp-local-repo-dir)))) 525 | 526 | (deftest within?-comparisons 527 | (is (aether/within? '[demo "0.0.1"] 528 | '[demo "0.0.1"])) 529 | (is (aether/within? '[demo "0.0.1"] 530 | '[demo/demo "0.0.1"])) 531 | (is (aether/within? '[demo/demo "0.0.1"] 532 | '[demo "0.0.1"])) 533 | (is (aether/within? '[demo "0.0.1"] 534 | '[demo "[0.0.1,2.0.0)"])) 535 | (is (not (aether/within? '[demo "2.0.0"] 536 | '[demo "[0.0.1,2.0.0)"]))) 537 | (is (not (aether/within? '[demo "0.0.1"] 538 | '[demo "(0.0.1,2.0.0)"]))) 539 | (is (aether/within? '[demo "0.0.1-SNAPSHOT"] 540 | '[demo/demo "0.0.1-SNAPSHOT"])) 541 | (is (aether/within? '[demo "0.0.1-SNAPSHOT"] 542 | '[demo "0.0.1-SNAPSHOT"])) 543 | (is (aether/within? '[demo "0.0.1-20120403.012847-1"] 544 | '[demo "0.0.1-SNAPSHOT"])) 545 | (is (not (aether/within? '[demo "0.0.1-SNAPSHOT"] 546 | '[demo "0.0.1-20120403.012847-10"]))) 547 | (is (aether/within? '[demo "0.0.1"] 548 | '[demo "0.0.1" :extension "jar"])) 549 | (is (aether/within? '[demo "0.0.1" :extension "jar"] 550 | '[demo "0.0.1"])) 551 | (is (not (aether/within? '[demo "0.0.1" :extension "pom"] 552 | '[demo "0.0.1"]))) 553 | (is (not (aether/within? '[demo "0.0.1"] 554 | '[demo "0.0.1":extension "pom"]))) 555 | (is (aether/within? '[demo "0.0.1" :classifier "sources"] 556 | '[demo "0.0.1" :classifier "sources"])) 557 | (is (not (aether/within? '[demo "0.0.1"] 558 | '[demo "0.0.1" :classifier "sources"]))) 559 | (is (not (aether/within? '[demo "0.0.1" :classifier "sources"] 560 | '[demo "0.0.1"]))) 561 | (is (aether/within? '[demo "0.0.1"] 562 | '[demo "0.0.1" :scope "compile"])) 563 | (is (aether/within? '[demo "0.0.1" :scope "compile"] 564 | '[demo "0.0.1"])) 565 | (is (aether/within? '[demo "0.0.1" :scope "compile"] 566 | '[demo "0.0.1" :scope "compile"])) 567 | (is (not (aether/within? '[demo "0.0.1" :scope "compile"] 568 | '[demo "0.0.1" :scope "test"]))) 569 | (is (not (aether/within? '[demo "0.0.1" :scope "test"] 570 | '[demo "0.0.1" :scope "compile"]))) 571 | (is (aether/within? '[demo "0.0.1"] 572 | '[demo "0.0.1" :optional false])) 573 | (is (aether/within? '[demo "0.0.1" :optional false] 574 | '[demo "0.0.1"])) 575 | (is (aether/within? '[demo "0.0.1" :optional true] 576 | '[demo "0.0.1" :optional true])) 577 | (is (not (aether/within? '[demo "0.0.1" :optional true] 578 | '[demo "0.0.1"]))) 579 | (is (not (aether/within? '[demo "0.0.1"] 580 | '[demo "0.0.1":optional true]))) 581 | (is (aether/within? '[demo "0.0.1" :exclusions []] 582 | '[demo "0.0.1"])) 583 | (is (aether/within? '[demo "0.0.1"] 584 | '[demo "0.0.1" :exclusions []])) 585 | (is (aether/within? '[demo "0.0.1" :exclusions [[demo2]]] 586 | '[demo "0.0.1" :exclusions [[demo2]]])) 587 | (is (not (aether/within? '[demo "0.0.1" :exclusions [[demo2]]] 588 | '[demo "0.0.1"]))) 589 | (is (not (aether/within? '[demo "0.0.1"] 590 | '[demo "0.0.1" :exclusions [[demo2]]]))) 591 | (is (not (aether/within? '[demo "0.0.1" :exclusions [demo2]] 592 | '[demo "0.0.1"]))) 593 | (is (not (aether/within? '[demo "0.0.1"] 594 | '[demo "0.0.1" :exclusions [demo2]]))) 595 | (is (aether/within? '[demo "0.0.1" :exclusions [[demo2] 596 | [demo3]]] 597 | '[demo "0.0.1" :exclusions [[demo2] 598 | [demo3]]])) 599 | (is (aether/within? '[demo "0.0.1" :exclusions [[demo3] 600 | [demo2]]] 601 | '[demo "0.0.1" :exclusions [[demo2] 602 | [demo3]]])) 603 | (is (not (aether/within? '[demo "0.0.1" :exclusions [[demo2]]] 604 | '[demo "0.0.1" :exclusions [[demo2] 605 | [demo3]]]))) 606 | (is (not (aether/within? '[demo "0.0.1" :exclusions [[demo2] 607 | [demo3]]] 608 | '[demo "0.0.1" :exclusions [[demo2]]]))) 609 | (is (aether/within? '[demo "0.0.1" :exclusions [[demo2]]] 610 | '[demo "0.0.1" :exclusions [[demo2 :classifier "*"]]])) 611 | (is (aether/within? '[demo "0.0.1" :exclusions [[demo2 :classifier "*"]]] 612 | '[demo "0.0.1" :exclusions [[demo2]]])) 613 | (is (not (aether/within? 614 | '[demo "0.0.1" :exclusions [[demo2 :classifier "*"]]] 615 | '[demo "0.0.1" :exclusions [[demo2 :classifier "sources"]]]))) 616 | (is (not (aether/within? 617 | '[demo "0.0.1" :exclusions [[demo2 :classifier "sources"]]] 618 | '[demo "0.0.1" :exclusions [[demo2 :classifier "*"]]]))) 619 | (is (aether/within? '[demo "0.0.1" :exclusions [[demo2]]] 620 | '[demo "0.0.1" :exclusions [[demo2 :extension "*"]]])) 621 | (is (aether/within? '[demo "0.0.1" :exclusions [[demo2 :extension "*"]]] 622 | '[demo "0.0.1" :exclusions [[demo2]]])) 623 | (is (not (aether/within? 624 | '[demo "0.0.1" :exclusions [[demo2 :extension "*"]]] 625 | '[demo "0.0.1" :exclusions [[demo2 :extension "jar"]]]))) 626 | (is (not (aether/within? 627 | '[demo "0.0.1" :exclusions [[demo2 :extension "jar"]]] 628 | '[demo "0.0.1" :exclusions [[demo2 :extension "*"]]])))) 629 | 630 | (deftest dependency-hierarchy-matching 631 | (let [coords '[[demo/demo2 "[0.0.1,2.0.0)"] 632 | [tester "0.1.0-SNAPSHOT"]] 633 | deps (aether/resolve-dependencies 634 | :repositories test-repo 635 | :coordinates coords 636 | :local-repo tmp-local-repo-dir)] 637 | (is (= {['demo/demo2 "1.0.0"] {['demo "1.0.0"] nil} 638 | ['tester "0.1.0-20120403.012847-1"] nil} 639 | (aether/dependency-hierarchy coords deps))))) 640 | 641 | (deftest make-repository-proxy-settings 642 | (let [repo (first test-remote-repo) 643 | proxy {:type "https" 644 | :host "squid" 645 | :port 3128}] 646 | (testing "plain proxy" 647 | (let [repo-proxy (->> proxy (aether/make-repository repo) .getProxy bean)] 648 | (is (= proxy (select-keys repo-proxy (keys proxy)))) 649 | (is (not (:authentication repo-proxy))))) 650 | (testing "authentication" 651 | (let [repo-proxy (->> (assoc proxy :username "me" :password "123456") 652 | (aether/make-repository repo) 653 | .getProxy 654 | bean)] 655 | (is (= proxy (select-keys repo-proxy (keys proxy)))) 656 | (is (:authentication repo-proxy)))))) 657 | 658 | ;; taken from the test suite for the pendantic lib by Nelson Morris 659 | 660 | (defn get-versions [name repo] 661 | (let [name (symbol name)] 662 | (map second (filter #(= name (first %)) (keys repo))))) 663 | 664 | (defn make-pom-string [name version deps] 665 | (str " 666 | 667 | 4.0.0 668 | " name " 669 | " name " 670 | jar 671 | " version " 672 | " name "" 673 | (when-not (empty? deps) 674 | (apply str 675 | "" 676 | (str/join "\n" 677 | (for [[n v] deps] 678 | (str " 679 | " n " 680 | "n" 681 | "v" 682 | "))) 683 | "")) 684 | " ")) 685 | 686 | (defn make-metadata [name versions] 687 | (str " 688 | " name " 689 | " name " 690 | 691 | " 692 | (str/join "\n" 693 | (for [v versions] 694 | (str ""v""))) 695 | " 696 | 20120810193549 697 | 698 | ")) 699 | 700 | (def fake-repo 701 | '{[a "1"] [] 702 | [a "2"] [] 703 | [aa "2"] [[a "2"]]}) 704 | 705 | (deftest register-fake-wagon 706 | (aether/register-wagon-factory! 707 | "fake" 708 | #(reify org.apache.maven.wagon.Wagon 709 | (getRepository [_] 710 | (proxy [org.apache.maven.wagon.repository.Repository] [])) 711 | (^void connect [_ 712 | ^org.apache.maven.wagon.repository.Repository _ 713 | ^org.apache.maven.wagon.authentication.AuthenticationInfo _ 714 | ^org.apache.maven.wagon.proxy.ProxyInfoProvider _]) 715 | (disconnect [_]) 716 | (removeTransferListener [_ _]) 717 | (addTransferListener [_ _]) 718 | (setTimeout [_ _]) 719 | (setInteractive [_ _]) 720 | (get [_ name file] 721 | (let [[n _ version] (str/split name #"/")] 722 | (if (= name (str n "/" n "/maven-metadata.xml")) 723 | (if-let [versions (get-versions n fake-repo)] 724 | (spit file (make-metadata n versions)) 725 | (spit file "")) 726 | (if-let [deps (fake-repo [(symbol n) version])] 727 | (if (re-find #".pom$" name) 728 | (spit file (make-pom-string n version deps)) 729 | (spit file "")) 730 | (throw (org.apache.maven.wagon.ResourceDoesNotExistException. "")))))))) 731 | 732 | (let [tmp-local-repo-dir (io/file tmp-dir "local-repo")] 733 | (aether/resolve-dependencies :coordinates '[[a "1"]] 734 | :repositories {"test-repo" 735 | {:url "fake://ss" 736 | :checksum :ignore}} 737 | :local-repo tmp-local-repo-dir) 738 | (is (= #{"local-repo" "a" "1" "a-1.pom" "_remote.repositories" "a-1.jar"} 739 | (set (map (memfn ^File getName) (file-seq tmp-local-repo-dir))))))) 740 | 741 | (comment 742 | "tests needed for: 743 | repository authentication 744 | repository policies 745 | dependency options (optional) 746 | exclusion options (classifier/extension)") 747 | -------------------------------------------------------------------------------- /src/test/clojure/cemerick/pomegranate/test_report.clj: -------------------------------------------------------------------------------- 1 | (ns cemerick.pomegranate.test-report 2 | (:require [clojure.test])) 3 | 4 | (def platform 5 | (str "clj " (clojure-version) " jdk " (System/getProperty "java.version"))) 6 | 7 | (defmethod clojure.test/report :begin-test-var [m] 8 | (let [test-name (-> m :var meta :name)] 9 | (println (format "=== %s [%s]" test-name platform)))) 10 | -------------------------------------------------------------------------------- /src/test/clojure/cemerick/pomegranate_test.clj: -------------------------------------------------------------------------------- 1 | (ns cemerick.pomegranate-test 2 | (:require [cemerick.pomegranate :as p] 3 | [cemerick.pomegranate.test-report] 4 | [clojure.java.io :as io] 5 | [clojure.string :as string] 6 | [clojure.test :refer [deftest is]])) 7 | 8 | (def java-version-major (-> (System/getProperty "java.version") (string/split #"\.") first Integer/parseInt)) 9 | 10 | (deftest resources 11 | (let [r (first (p/resources "META-INF/MANIFEST.MF"))] 12 | (is (not (nil? r)) 13 | "first pomegranate resource match is not nil") 14 | 15 | (is (= r 16 | (io/resource "META-INF/MANIFEST.MF")) 17 | "first pomegranate resources match is the same as java resource match")) 18 | 19 | (let [^ClassLoader platform-classloader (last (p/classloader-hierarchy)) 20 | class-name (-> platform-classloader .getClass .getSimpleName) ] 21 | (if (< java-version-major 9) 22 | (is (= "ExtClassLoader" class-name) 23 | "last class loader is extension classloader (for jdk versions < 9)") 24 | (is (= "PlatformClassLoader" class-name) 25 | "last class loader is platform classloader (for jdk versions >= 9)")) 26 | 27 | (is (->> (p/resources [platform-classloader] "META-INF/MANIFEST.MF") 28 | (map str) 29 | (filter #(.contains ^String % "clojure")) 30 | empty?) 31 | "no clojure manifest resources should be found on platform classloader")) 32 | 33 | (is (->> (p/resources (butlast (p/classloader-hierarchy)) "META-INF/MANIFEST.MF") 34 | (map str) 35 | (filter #(.contains ^String % "clojure")) 36 | seq) 37 | "clojure manifests should be found when searching classloaders")) 38 | 39 | (deftest get-classpath 40 | (if (< java-version-major 9) 41 | (is (seq (p/get-classpath)) 42 | "works for jdk versions < 9") 43 | (is (empty? (p/get-classpath)) 44 | "always empty seq for jdk versions >= 9"))) 45 | -------------------------------------------------------------------------------- /test-repo/demo/demo/1.0.0/demo-1.0.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/pomegranate/12456546e2519158d6ef2d8b65caae4ebbd51885/test-repo/demo/demo/1.0.0/demo-1.0.0.jar -------------------------------------------------------------------------------- /test-repo/demo/demo/1.0.0/demo-1.0.0.jar.asc: -------------------------------------------------------------------------------- 1 | This file here only to test deployment operations; the fact that this isn't a signature file shouldn't cause any issues. 2 | -------------------------------------------------------------------------------- /test-repo/demo/demo/1.0.0/demo-1.0.0.jar.asc.md5: -------------------------------------------------------------------------------- 1 | b5f91153fbd3dc8bc29e98c73d46655f demo-1.0.0.jar.asc 2 | -------------------------------------------------------------------------------- /test-repo/demo/demo/1.0.0/demo-1.0.0.jar.asc.sha1: -------------------------------------------------------------------------------- 1 | d94055dca4e0f2bcc455e278287dcf0e7c4034e1 demo-1.0.0.jar.asc 2 | -------------------------------------------------------------------------------- /test-repo/demo/demo/1.0.0/demo-1.0.0.jar.md5: -------------------------------------------------------------------------------- 1 | d41d8cd98f00b204e9800998ecf8427e -------------------------------------------------------------------------------- /test-repo/demo/demo/1.0.0/demo-1.0.0.jar.sha1: -------------------------------------------------------------------------------- 1 | da39a3ee5e6b4b0d3255bfef95601890afd80709 -------------------------------------------------------------------------------- /test-repo/demo/demo/1.0.0/demo-1.0.0.pom: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | demo 6 | demo 7 | 1.0.0 8 | POM was created from install:install-file 9 | 10 | -------------------------------------------------------------------------------- /test-repo/demo/demo/1.0.0/demo-1.0.0.pom.asc: -------------------------------------------------------------------------------- 1 | This file here only to test deployment operations; the fact that this isn't a signature file shouldn't cause any issues. 2 | -------------------------------------------------------------------------------- /test-repo/demo/demo/1.0.0/demo-1.0.0.pom.asc.md5: -------------------------------------------------------------------------------- 1 | b5f91153fbd3dc8bc29e98c73d46655f demo-1.0.0.pom.asc 2 | -------------------------------------------------------------------------------- /test-repo/demo/demo/1.0.0/demo-1.0.0.pom.asc.sha1: -------------------------------------------------------------------------------- 1 | d94055dca4e0f2bcc455e278287dcf0e7c4034e1 demo-1.0.0.pom.asc 2 | -------------------------------------------------------------------------------- /test-repo/demo/demo/1.0.0/demo-1.0.0.pom.md5: -------------------------------------------------------------------------------- 1 | 5689f1eaf926240885eb9976e5269e5d -------------------------------------------------------------------------------- /test-repo/demo/demo/1.0.0/demo-1.0.0.pom.sha1: -------------------------------------------------------------------------------- 1 | eb4ff44542c69c5271390dad76a96bfd15b4858a -------------------------------------------------------------------------------- /test-repo/demo/demo/1.0.1/demo-1.0.1-test.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/pomegranate/12456546e2519158d6ef2d8b65caae4ebbd51885/test-repo/demo/demo/1.0.1/demo-1.0.1-test.jar -------------------------------------------------------------------------------- /test-repo/demo/demo/1.0.1/demo-1.0.1-test.jar.md5: -------------------------------------------------------------------------------- 1 | 85e554b751c49040a3741cc3d6538135 -------------------------------------------------------------------------------- /test-repo/demo/demo/1.0.1/demo-1.0.1-test.jar.sha1: -------------------------------------------------------------------------------- 1 | 544a87ec520c8e75bad2a9c83d1e133981c13554 -------------------------------------------------------------------------------- /test-repo/demo/demo/1.0.1/demo-1.0.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/pomegranate/12456546e2519158d6ef2d8b65caae4ebbd51885/test-repo/demo/demo/1.0.1/demo-1.0.1.jar -------------------------------------------------------------------------------- /test-repo/demo/demo/1.0.1/demo-1.0.1.jar.md5: -------------------------------------------------------------------------------- 1 | 85e554b751c49040a3741cc3d6538135 -------------------------------------------------------------------------------- /test-repo/demo/demo/1.0.1/demo-1.0.1.jar.sha1: -------------------------------------------------------------------------------- 1 | 544a87ec520c8e75bad2a9c83d1e133981c13554 -------------------------------------------------------------------------------- /test-repo/demo/demo/1.0.1/demo-1.0.1.pom: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | demo 6 | demo 7 | 1.0.1 8 | jar 9 | 10 | 11 | 12 | 13 | org.apache.maven.plugins 14 | maven-jar-plugin 15 | 2.6 16 | 17 | 18 | 19 | test-jar 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /test-repo/demo/demo/1.0.1/demo-1.0.1.pom.md5: -------------------------------------------------------------------------------- 1 | f446abedf7d50932d78ba5c18b0f0af1 -------------------------------------------------------------------------------- /test-repo/demo/demo/1.0.1/demo-1.0.1.pom.sha1: -------------------------------------------------------------------------------- 1 | 1b3cb49c9986afc15b5267979a10fdf50afb54e9 -------------------------------------------------------------------------------- /test-repo/demo/demo/maven-metadata.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | demo 4 | demo 5 | 6 | 1.0.1 7 | 8 | 1.0.0 9 | 1.0.1 10 | 11 | 20160207024909 12 | 13 | 14 | -------------------------------------------------------------------------------- /test-repo/demo/demo/maven-metadata.xml.md5: -------------------------------------------------------------------------------- 1 | e6f14a2cc8e218d3449163ec25257d60 -------------------------------------------------------------------------------- /test-repo/demo/demo/maven-metadata.xml.sha1: -------------------------------------------------------------------------------- 1 | 85569d708319d8377dddcd3f680fc37a391b7c81 -------------------------------------------------------------------------------- /test-repo/demo/demo2/1.0.0/demo2-1.0.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/pomegranate/12456546e2519158d6ef2d8b65caae4ebbd51885/test-repo/demo/demo2/1.0.0/demo2-1.0.0.jar -------------------------------------------------------------------------------- /test-repo/demo/demo2/1.0.0/demo2-1.0.0.jar.md5: -------------------------------------------------------------------------------- 1 | d41d8cd98f00b204e9800998ecf8427e -------------------------------------------------------------------------------- /test-repo/demo/demo2/1.0.0/demo2-1.0.0.jar.sha1: -------------------------------------------------------------------------------- 1 | da39a3ee5e6b4b0d3255bfef95601890afd80709 -------------------------------------------------------------------------------- /test-repo/demo/demo2/1.0.0/demo2-1.0.0.pom: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | demo 6 | demo2 7 | 1.0.0 8 | POM was created from install:install-file 9 | 10 | 11 | 12 | demo 13 | demo 14 | 1.0.0 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /test-repo/demo/demo2/1.0.0/demo2-1.0.0.pom.md5: -------------------------------------------------------------------------------- 1 | 79893caa8e0888232ed9dfe56e396172 -------------------------------------------------------------------------------- /test-repo/demo/demo2/1.0.0/demo2-1.0.0.pom.sha1: -------------------------------------------------------------------------------- 1 | d2a795ccaccf9ade57f7938c390368eeb2e842e7 -------------------------------------------------------------------------------- /test-repo/demo/demo2/maven-metadata.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | demo 4 | demo2 5 | 6 | 1.0.0 7 | 8 | 1.0.0 9 | 10 | 20120403045648 11 | 12 | 13 | -------------------------------------------------------------------------------- /test-repo/demo/demo2/maven-metadata.xml.md5: -------------------------------------------------------------------------------- 1 | 8550a6918ed8fe0cf9ff6e08e61c4d8c -------------------------------------------------------------------------------- /test-repo/demo/demo2/maven-metadata.xml.sha1: -------------------------------------------------------------------------------- 1 | c38f519e44c887aa563201af58779379cbee4b18 -------------------------------------------------------------------------------- /test-repo/tester/tester/0.1.0-SNAPSHOT/maven-metadata.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | tester 4 | tester 5 | 0.1.0-SNAPSHOT 6 | 7 | 8 | 20120403.012847 9 | 1 10 | 11 | 20120403012847 12 | 13 | 14 | jar 15 | 0.1.0-20120403.012847-1 16 | 20120403012847 17 | 18 | 19 | pom 20 | 0.1.0-20120403.012847-1 21 | 20120403012847 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /test-repo/tester/tester/0.1.0-SNAPSHOT/maven-metadata.xml.md5: -------------------------------------------------------------------------------- 1 | 32f77845f3013e9010130dd3c5056d35 -------------------------------------------------------------------------------- /test-repo/tester/tester/0.1.0-SNAPSHOT/maven-metadata.xml.sha1: -------------------------------------------------------------------------------- 1 | 34f980dd6a45ef4ad423c27315015580f5e8c996 -------------------------------------------------------------------------------- /test-repo/tester/tester/0.1.0-SNAPSHOT/tester-0.1.0-20120403.012847-1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clj-commons/pomegranate/12456546e2519158d6ef2d8b65caae4ebbd51885/test-repo/tester/tester/0.1.0-SNAPSHOT/tester-0.1.0-20120403.012847-1.jar -------------------------------------------------------------------------------- /test-repo/tester/tester/0.1.0-SNAPSHOT/tester-0.1.0-20120403.012847-1.jar.md5: -------------------------------------------------------------------------------- 1 | b959415ced8425a03218a51430d8a7e1 -------------------------------------------------------------------------------- /test-repo/tester/tester/0.1.0-SNAPSHOT/tester-0.1.0-20120403.012847-1.jar.sha1: -------------------------------------------------------------------------------- 1 | ecddbf891119b76b09e0f888f8cf5764932c811b -------------------------------------------------------------------------------- /test-repo/tester/tester/0.1.0-SNAPSHOT/tester-0.1.0-20120403.012847-1.pom: -------------------------------------------------------------------------------- 1 | 4.0.0testertester0.1.0-SNAPSHOTtesterFIXME: write descriptionhttp://example.com/FIXMEEclipse Public Licensehttp://www.eclipse.org/legal/epl-v10.html/workspace/tester/src/workspace/tester/test/workspace/tester/resources/workspace/tester/resourcescentralhttp://repo1.maven.org/maven2clojarshttp://clojars.org/repo/snapshotsfile:///tmp/snapshots 2 | 6 | 7 | -------------------------------------------------------------------------------- /test-repo/tester/tester/0.1.0-SNAPSHOT/tester-0.1.0-20120403.012847-1.pom.md5: -------------------------------------------------------------------------------- 1 | f5550306172f01752d6483dd88528271 -------------------------------------------------------------------------------- /test-repo/tester/tester/0.1.0-SNAPSHOT/tester-0.1.0-20120403.012847-1.pom.sha1: -------------------------------------------------------------------------------- 1 | eb42a597bcb2ec429e5ba36ed9b16100a1e9a336 -------------------------------------------------------------------------------- /test-repo/tester/tester/maven-metadata.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | tester 4 | tester 5 | 6 | 7 | 0.1.0-SNAPSHOT 8 | 9 | 20120403012847 10 | 11 | 12 | -------------------------------------------------------------------------------- /test-repo/tester/tester/maven-metadata.xml.md5: -------------------------------------------------------------------------------- 1 | 0748c7d4b7b9b6dc951840d798be8fda -------------------------------------------------------------------------------- /test-repo/tester/tester/maven-metadata.xml.sha1: -------------------------------------------------------------------------------- 1 | 23c0ae6c73dc4c080e18d7664a88e9d6936b9521 --------------------------------------------------------------------------------