From 43bdf6c3a9ee0f06aca1f90bef1c18ed70309f49 Mon Sep 17 00:00:00 2001 From: Alex Miller Date: Thu, 2 Jan 2025 05:36:58 -0600 Subject: [PATCH] init, migrated from tools.deps --- .github/PULL_REQUEST_TEMPLATE | 14 ++ .github/workflows/doc-build.yml | 10 + .github/workflows/release.yml | 19 ++ .github/workflows/snapshot.yml | 8 + .github/workflows/test.yml | 22 ++ .gitignore | 13 ++ CHANGELOG.md | 5 + CONTRIBUTING.md | 12 + LICENSE | 205 ++++++++++++++++++ README.md | 67 ++++++ VERSION_TEMPLATE | 1 + pom.xml | 83 +++++++ script/version | 17 ++ src/main/clojure/clojure/tools/deps/edn.clj | 171 +++++++++++++++ src/main/clojure/clojure/tools/deps/specs.clj | 167 ++++++++++++++ .../clojure/clojure/tools/deps/util/io.clj | 61 ++++++ .../clojure/clojure/tools/deps/test_edn.clj | 53 +++++ src/test/clojure/clojure/tools/deps/util.clj | 25 +++ 18 files changed, 953 insertions(+) create mode 100644 .github/PULL_REQUEST_TEMPLATE create mode 100644 .github/workflows/doc-build.yml create mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/snapshot.yml create mode 100644 .github/workflows/test.yml create mode 100644 .gitignore create mode 100644 CHANGELOG.md create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 README.md create mode 100755 VERSION_TEMPLATE create mode 100644 pom.xml create mode 100755 script/version create mode 100644 src/main/clojure/clojure/tools/deps/edn.clj create mode 100644 src/main/clojure/clojure/tools/deps/specs.clj create mode 100644 src/main/clojure/clojure/tools/deps/util/io.clj create mode 100644 src/test/clojure/clojure/tools/deps/test_edn.clj create mode 100644 src/test/clojure/clojure/tools/deps/util.clj diff --git a/.github/PULL_REQUEST_TEMPLATE b/.github/PULL_REQUEST_TEMPLATE new file mode 100644 index 0000000..f1a88e9 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE @@ -0,0 +1,14 @@ +Hi! Thanks for your interest in contributing to this project. + +Clojure contrib projects do not use GitHub issues or pull requests, and +require a signed Contributor Agreement. If you would like to contribute, +please read more about the CA and sign that first (this can be done online). + +Then go to this project's issue tracker in JIRA to create tickets, update +tickets, or submit patches. For help in creating tickets and patches, +please see: + +- Contributing FAQ: https://clojure.org/dev +- Signing the CA: https://clojure.org/dev/contributor_agreement +- Creating Tickets: https://clojure.org/dev/creating_tickets +- Developing Patches: https://clojure.org/dev/developing_patches diff --git a/.github/workflows/doc-build.yml b/.github/workflows/doc-build.yml new file mode 100644 index 0000000..78cc9de --- /dev/null +++ b/.github/workflows/doc-build.yml @@ -0,0 +1,10 @@ +name: Build API Docs + +on: + workflow_dispatch: + +jobs: + call-doc-build-workflow: + uses: clojure/build.ci/.github/workflows/doc-build.yml@master + with: + project: clojure/tools.deps.edn diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..e2718bd --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,19 @@ +name: Release on demand + +on: + workflow_dispatch: + inputs: + releaseVersion: + description: "Version to release" + required: true + snapshotVersion: + description: "Snapshot version after release" + required: true + +jobs: + call-release: + uses: clojure/build.ci/.github/workflows/release.yml@master + with: + releaseVersion: ${{ github.event.inputs.releaseVersion }} + snapshotVersion: ${{ github.event.inputs.snapshotVersion }} + secrets: inherit \ No newline at end of file diff --git a/.github/workflows/snapshot.yml b/.github/workflows/snapshot.yml new file mode 100644 index 0000000..2472957 --- /dev/null +++ b/.github/workflows/snapshot.yml @@ -0,0 +1,8 @@ +name: Snapshot on demand + +on: [workflow_dispatch] + +jobs: + call-snapshot: + uses: clojure/build.ci/.github/workflows/snapshot.yml@master + secrets: inherit diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..06240ed --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,22 @@ +name: Test + +on: [push] + +jobs: + test: + strategy: + matrix: + os: [ubuntu-latest] # macOS-latest, windows-latest] + java-version: ["8", "11", "17", "21"] + clojure-version: ["1.10.3", "1.11.1"] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - name: Set up Java + uses: actions/setup-java@v4 + with: + java-version: ${{ matrix.java-version }} + distribution: 'temurin' + cache: 'maven' + - name: Build with Maven + run: mvn -ntp -B -Dclojure.version=${{ matrix.clojure-version }} clean test diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ad3099d --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +*.iml +.idea/ +.calva/ +.lsp/ +target/ +.nrepl* +.cpcache +.lein* +project.clj +.clj-kondo/.cache +test-out/ +.vscode/ +.clj-watson/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..4afa9fb --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,5 @@ +Changelog +=========== + +* next + * diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..f71fb38 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,12 @@ +This is a [Clojure contrib] project. + +Under the Clojure contrib [guidelines], this project cannot accept +pull requests. All patches must be submitted via [JIRA]. + +See [Contributing] on the Clojure website for +more information on how to contribute. + +[Clojure contrib]: https://clojure.org/community/contrib_libs +[Contributing]: https://clojure.org/community/contributing +[JIRA]: https://clojure.atlassian.net/browse/TDEPS +[guidelines]: https://clojure.org/community/contrib_howto diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e246f6a --- /dev/null +++ b/LICENSE @@ -0,0 +1,205 @@ +Eclipse Public License - v 1.0 + +THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC +LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM +CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + +1. DEFINITIONS + +"Contribution" means: + +a) in the case of the initial Contributor, the initial code and documentation + distributed under this Agreement, and +b) in the case of each subsequent Contributor: + i) changes to the Program, and + ii) additions to the Program; + + where such changes and/or additions to the Program originate from and are + distributed by that particular Contributor. A Contribution 'originates' + from a Contributor if it was added to the Program by such Contributor + itself or anyone acting on such Contributor's behalf. Contributions do not + include additions to the Program which: (i) are separate modules of + software distributed in conjunction with the Program under their own + license agreement, and (ii) are not derivative works of the Program. + +"Contributor" means any person or entity that distributes the Program. + +"Licensed Patents" mean patent claims licensable by a Contributor which are +necessarily infringed by the use or sale of its Contribution alone or when +combined with the Program. + +"Program" means the Contributions distributed in accordance with this +Agreement. + +"Recipient" means anyone who receives the Program under this Agreement, +including all Contributors. + +2. GRANT OF RIGHTS + a) Subject to the terms of this Agreement, each Contributor hereby grants + Recipient a non-exclusive, worldwide, royalty-free copyright license to + reproduce, prepare derivative works of, publicly display, publicly + perform, distribute and sublicense the Contribution of such Contributor, + if any, and such derivative works, in source code and object code form. + b) Subject to the terms of this Agreement, each Contributor hereby grants + Recipient a non-exclusive, worldwide, royalty-free patent license under + Licensed Patents to make, use, sell, offer to sell, import and otherwise + transfer the Contribution of such Contributor, if any, in source code and + object code form. This patent license shall apply to the combination of + the Contribution and the Program if, at the time the Contribution is + added by the Contributor, such addition of the Contribution causes such + combination to be covered by the Licensed Patents. The patent license + shall not apply to any other combinations which include the Contribution. + No hardware per se is licensed hereunder. + c) Recipient understands that although each Contributor grants the licenses + to its Contributions set forth herein, no assurances are provided by any + Contributor that the Program does not infringe the patent or other + intellectual property rights of any other entity. Each Contributor + disclaims any liability to Recipient for claims brought by any other + entity based on infringement of intellectual property rights or + otherwise. As a condition to exercising the rights and licenses granted + hereunder, each Recipient hereby assumes sole responsibility to secure + any other intellectual property rights needed, if any. For example, if a + third party patent license is required to allow Recipient to distribute + the Program, it is Recipient's responsibility to acquire that license + before distributing the Program. + d) Each Contributor represents that to its knowledge it has sufficient + copyright rights in its Contribution, if any, to grant the copyright + license set forth in this Agreement. + +3. REQUIREMENTS + +A Contributor may choose to distribute the Program in object code form under +its own license agreement, provided that: + + a) it complies with the terms and conditions of this Agreement; and + b) its license agreement: + i) effectively disclaims on behalf of all Contributors all warranties + and conditions, express and implied, including warranties or + conditions of title and non-infringement, and implied warranties or + conditions of merchantability and fitness for a particular purpose; + ii) effectively excludes on behalf of all Contributors all liability for + damages, including direct, indirect, special, incidental and + consequential damages, such as lost profits; + iii) states that any provisions which differ from this Agreement are + offered by that Contributor alone and not by any other party; and + iv) states that source code for the Program is available from such + Contributor, and informs licensees how to obtain it in a reasonable + manner on or through a medium customarily used for software exchange. + +When the Program is made available in source code form: + + a) it must be made available under this Agreement; and + b) a copy of this Agreement must be included with each copy of the Program. + Contributors may not remove or alter any copyright notices contained + within the Program. + +Each Contributor must identify itself as the originator of its Contribution, +if +any, in a manner that reasonably allows subsequent Recipients to identify the +originator of the Contribution. + +4. COMMERCIAL DISTRIBUTION + +Commercial distributors of software may accept certain responsibilities with +respect to end users, business partners and the like. While this license is +intended to facilitate the commercial use of the Program, the Contributor who +includes the Program in a commercial product offering should do so in a manner +which does not create potential liability for other Contributors. Therefore, +if a Contributor includes the Program in a commercial product offering, such +Contributor ("Commercial Contributor") hereby agrees to defend and indemnify +every other Contributor ("Indemnified Contributor") against any losses, +damages and costs (collectively "Losses") arising from claims, lawsuits and +other legal actions brought by a third party against the Indemnified +Contributor to the extent caused by the acts or omissions of such Commercial +Contributor in connection with its distribution of the Program in a commercial +product offering. The obligations in this section do not apply to any claims +or Losses relating to any actual or alleged intellectual property +infringement. In order to qualify, an Indemnified Contributor must: +a) promptly notify the Commercial Contributor in writing of such claim, and +b) allow the Commercial Contributor to control, and cooperate with the +Commercial Contributor in, the defense and any related settlement +negotiations. The Indemnified Contributor may participate in any such claim at +its own expense. + +For example, a Contributor might include the Program in a commercial product +offering, Product X. That Contributor is then a Commercial Contributor. If +that Commercial Contributor then makes performance claims, or offers +warranties related to Product X, those performance claims and warranties are +such Commercial Contributor's responsibility alone. Under this section, the +Commercial Contributor would have to defend claims against the other +Contributors related to those performance claims and warranties, and if a +court requires any other Contributor to pay any damages as a result, the +Commercial Contributor must pay those damages. + +5. NO WARRANTY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR +IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, +NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each +Recipient is solely responsible for determining the appropriateness of using +and distributing the Program and assumes all risks associated with its +exercise of rights under this Agreement , including but not limited to the +risks and costs of program errors, compliance with applicable laws, damage to +or loss of data, programs or equipment, and unavailability or interruption of +operations. + +6. DISCLAIMER OF LIABILITY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY +CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION +LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE +EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY +OF SUCH DAMAGES. + +7. GENERAL + +If any provision of this Agreement is invalid or unenforceable under +applicable law, it shall not affect the validity or enforceability of the +remainder of the terms of this Agreement, and without further action by the +parties hereto, such provision shall be reformed to the minimum extent +necessary to make such provision valid and enforceable. + +If Recipient institutes patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Program itself +(excluding combinations of the Program with other software or hardware) +infringes such Recipient's patent(s), then such Recipient's rights granted +under Section 2(b) shall terminate as of the date such litigation is filed. + +All Recipient's rights under this Agreement shall terminate if it fails to +comply with any of the material terms or conditions of this Agreement and does +not cure such failure in a reasonable period of time after becoming aware of +such noncompliance. If all Recipient's rights under this Agreement terminate, +Recipient agrees to cease use and distribution of the Program as soon as +reasonably practicable. However, Recipient's obligations under this Agreement +and any licenses granted by Recipient relating to the Program shall continue +and survive. + +Everyone is permitted to copy and distribute copies of this Agreement, but in +order to avoid inconsistency the Agreement is copyrighted and may only be +modified in the following manner. The Agreement Steward reserves the right to +publish new versions (including revisions) of this Agreement from time to +time. No one other than the Agreement Steward has the right to modify this +Agreement. The Eclipse Foundation is the initial Agreement Steward. The +Eclipse Foundation may assign the responsibility to serve as the Agreement +Steward to a suitable separate entity. Each new version of the Agreement will +be given a distinguishing version number. The Program (including +Contributions) may always be distributed subject to the version of the +Agreement under which it was received. In addition, after a new version of the +Agreement is published, Contributor may elect to distribute the Program +(including its Contributions) under the new version. Except as expressly +stated in Sections 2(a) and 2(b) above, Recipient receives no rights or +licenses to the intellectual property of any Contributor under this Agreement, +whether expressly, by implication, estoppel or otherwise. All rights in the +Program not expressly granted under this Agreement are reserved. + +This Agreement is governed by the laws of the State of New York and the +intellectual property laws of the United States of America. No party to this +Agreement will bring a legal action under this Agreement more than one year +after the cause of action arose. Each party waives its rights to a jury trial in +any resulting litigation. + + diff --git a/README.md b/README.md new file mode 100644 index 0000000..965b9ac --- /dev/null +++ b/README.md @@ -0,0 +1,67 @@ +tools.deps.edn +======================================== + +Reader for deps.edn + +# Rationale + +This library is a small library for reading and understanding deps.edn files. It can +be used in scenarios where the full [tools.deps](https://github.com/clojure/tools.deps) +library is not needed. + +* [deps.edn Reference](https://clojure.org/reference/deps_edn) + +# Release Information + +Latest release: TBD + +* [All released versions](https://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.clojure%22%20AND%20a%3A%22tools.deps.edn%22) + +[deps.edn](https://clojure.org/reference/deps_edn) dependency information: + +``` +org.clojure/tools.deps.edn {:mvn/version "TBD"} +``` + +[Leiningen](https://github.com/technomancy/leiningen/) dependency information: + +``` +[org.clojure/tools.deps.edn "TBD"] +``` + +[Maven](https://maven.apache.org) dependency information: + +``` + + org.clojure + tools.deps.edn + TBD + +``` + +# API + +For info on using tools.deps.edn as a library, see: + +* [API Docs](https://clojure.github.io/tools.deps.edn) + +# Developer Information + +* [GitHub project](https://github.com/clojure/tools.deps.edn) +* [How to contribute](https://clojure.org/community/contributing) +* [Bug Tracker](https://clojure.atlassian.net/browse/TDEPS) +* [Continuous Integration](https://github.com/clojure/tools.deps.edn/actions/workflows/test.yml) + +# Copyright and License + +Copyright © Rich Hickey, Alex Miller, and contributors + +All rights reserved. The use and +distribution terms for this software are covered by the +[Eclipse Public License 1.0] which can be found in the file +LICENSE at the root of this distribution. By using this software +in any fashion, you are agreeing to be bound by the terms of this +license. You must not remove this notice, or any other, from this +software. + +[Eclipse Public License 1.0]: https://opensource.org/license/epl-1-0/ diff --git a/VERSION_TEMPLATE b/VERSION_TEMPLATE new file mode 100755 index 0000000..d1f515d --- /dev/null +++ b/VERSION_TEMPLATE @@ -0,0 +1 @@ +0.1.GENERATED_VERSION diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..439af57 --- /dev/null +++ b/pom.xml @@ -0,0 +1,83 @@ + + 4.0.0 + tools.deps.edn + 0.1.0-SNAPSHOT + tools.deps.edn + + + org.clojure + pom.contrib + 1.2.0 + + + + + puredanger + Alex Miller + + + + + + true + 1.12.0 + + + + + org.clojure + clojure + ${clojure.version} + + + + + + + src/main/resources + true + + + + + org.apache.maven.plugins + maven-resources-plugin + 3.1.0 + + + + com.theoryinpractise + clojure-maven-plugin + 1.7.1 + true + + ${clojure.warnOnReflection} + true + + + + clojure-compile + none + + + clojure-test + test + + test + + + + + + + + + scm:git:git@github.com:clojure/tools.deps.edn.git + scm:git:git@github.com:clojure/tools.deps.edn.git + git@github.com:clojure/tools.deps.edn.git + HEAD + + + diff --git a/script/version b/script/version new file mode 100755 index 0000000..df3ecc6 --- /dev/null +++ b/script/version @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +set -e + +version_template=`cat VERSION_TEMPLATE` +commit_count=`git rev-list HEAD --count` + +if [[ "$version_template" =~ ^[0-9]+\.[0-9]+\.GENERATED_VERSION(-[a-zA-Z0-9]+)?$ ]]; then + + echo ${version_template/GENERATED_VERSION/$commit_count} + +else + echo "Invalid version template string: $version_template" >&2 + exit -1 +fi + + diff --git a/src/main/clojure/clojure/tools/deps/edn.clj b/src/main/clojure/clojure/tools/deps/edn.clj new file mode 100644 index 0000000..ef1b7c0 --- /dev/null +++ b/src/main/clojure/clojure/tools/deps/edn.clj @@ -0,0 +1,171 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns clojure.tools.deps.edn + (:require + [clojure.java.io :as jio] + [clojure.string :as str] + [clojure.tools.deps.util.io :as io] + [clojure.tools.deps.specs :as specs] + [clojure.walk :as walk]) + (:import + [java.io File] + [clojure.lang EdnReader$ReaderException] + )) + +(set! *warn-on-reflection* true) + +;;;; deps.edn reading + +(defn- io-err + ^Throwable [fmt ^File f] + (let [path (.getAbsolutePath f)] + (ex-info (format fmt path) {:path path}))) + +(defn- slurp-edn-map + "Read the file specified by the path-segments, slurp it, and read it as edn." + [^File f] + (let [val (try (io/slurp-edn f) + (catch EdnReader$ReaderException e (throw (io-err (str (.getMessage e) " (%s)") f))) + (catch RuntimeException t + (if (str/starts-with? (.getMessage t) "EOF while reading") + (throw (io-err "Error reading edn, delimiter unmatched (%s)" f)) + (throw (io-err (str "Error reading edn. " (.getMessage t) " (%s)") f)))))] + (if (specs/valid-deps? val) + val + (throw (io-err (str "Error reading deps %s. " (specs/explain-deps val)) f))))) + +;; all this canonicalization is deprecated and will eventually be removed + +(defn- canonicalize-sym + ([s] + (canonicalize-sym s nil)) + ([s file-name] + (if (simple-symbol? s) + (let [cs (as-> (name s) n (symbol n n))] + (io/printerrln "DEPRECATED: Libs must be qualified, change" s "=>" cs + (if file-name (str "(" file-name ")") "")) + cs) + s))) + +(defn- canonicalize-exclusions + [{:keys [exclusions] :as coord} file-name] + (if (seq (filter simple-symbol? exclusions)) + (assoc coord :exclusions (mapv #(canonicalize-sym % file-name) exclusions)) + coord)) + +(defn- canonicalize-dep-map + [deps-map file-name] + (when deps-map + (reduce-kv (fn [acc lib coord] + (let [new-lib (if (simple-symbol? lib) (canonicalize-sym lib file-name) lib) + new-coord (canonicalize-exclusions coord file-name)] + (assoc acc new-lib new-coord))) + {} deps-map))) + +(defn- canonicalize-all-syms + ([deps-edn] + (canonicalize-all-syms deps-edn nil)) + ([deps-edn file-name] + (walk/postwalk + (fn [x] + (if (map? x) + (reduce (fn [xr k] + (if-let [xm (get xr k)] + (assoc xr k (canonicalize-dep-map xm file-name)) + xr)) + x #{:deps :default-deps :override-deps :extra-deps :classpath-overrides}) + x)) + deps-edn))) + +(defn slurp-deps + "Read a single deps.edn file from disk and canonicalize symbols, + return a deps map. If the file doesn't exist, returns nil." + [^File dep-file] + (when (.exists dep-file) + (-> dep-file slurp-edn-map (canonicalize-all-syms (.getPath dep-file))))) + +(defn- merge-or-replace + "If maps, merge, otherwise replace" + [& vals] + (when (some identity vals) + (reduce (fn [ret val] + (if (and (map? ret) (map? val)) + (merge ret val) + (or val ret))) + nil vals))) + +(defn merge-edns + "Merge multiple deps edn maps from left to right into a single deps edn map." + [deps-edn-maps] + (apply merge-with merge-or-replace (remove nil? deps-edn-maps))) + +;;;; Aliases + +;; per-key binary merge-with rules + +(def ^:private last-wins (comp last #(remove nil? %) vector)) +(def ^:private append (comp vec concat)) +(def ^:private append-unique (comp vec distinct concat)) + +(def ^:private merge-alias-rules + {:deps merge ;; FUTURE: remove + :replace-deps merge ;; formerly :deps + :extra-deps merge + :override-deps merge + :default-deps merge + :classpath-overrides merge + :paths append-unique ;; FUTURE: remove + :replace-paths append-unique ;; formerly :paths + :extra-paths append-unique + :jvm-opts append + :main-opts last-wins + :exec-fn last-wins + :exec-args merge-or-replace + :ns-aliases merge + :ns-default last-wins}) + +(defn- choose-rule [alias-key val] + (or (merge-alias-rules alias-key) + (if (map? val) + merge + (fn [_v1 v2] v2)))) + +(defn- merge-alias-maps + "Like merge-with, but using custom per-alias-key merge function" + [& ms] + (reduce + #(reduce + (fn [m [k v]] (update m k (choose-rule k v) v)) + %1 %2) + {} ms)) + +(defn combine-aliases + "Find, read, and combine alias maps identified by alias keywords from + a deps edn map into a single args map." + [edn-map alias-kws] + (->> alias-kws + (map #(get-in edn-map [:aliases %])) + (apply merge-alias-maps))) + + + + +(defn- chase-key + "Given an aliases set and a keyword k, return a flattened vector of path + entries for that k, resolving recursively if needed, or nil." + [aliases k] + (let [path-coll (get aliases k)] + (when (seq path-coll) + (into [] (mapcat #(if (string? %) [[% {:path-key k}]] (chase-key aliases %))) path-coll)))) + +(defn- flatten-paths + [{:keys [paths aliases] :as deps-edn-map} {:keys [extra-paths] :as classpath-args}] + (let [aliases' (assoc aliases :paths paths :extra-paths extra-paths)] + (into [] (comp (mapcat #(chase-key aliases' %)) (remove nil?)) [:extra-paths :paths]))) + diff --git a/src/main/clojure/clojure/tools/deps/specs.clj b/src/main/clojure/clojure/tools/deps/specs.clj new file mode 100644 index 0000000..6eebf90 --- /dev/null +++ b/src/main/clojure/clojure/tools/deps/specs.clj @@ -0,0 +1,167 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns clojure.tools.deps.specs + (:require [clojure.spec.alpha :as s])) + +;;;; generic types + +(s/def ::lib symbol?) +(s/def ::path string?) +(s/def ::alias keyword?) + +;;;; coordinates + +;; generic coord attributes +(s/def :deps/root ::path) +(s/def :deps/manifest keyword?) +(s/def :deps/exclusions (s/coll-of ::lib)) +(s/def :deps/coord (s/keys :opt [:deps/root :deps/manifest] :opt-un [:deps/exclusions])) + +;; maven coords +(s/def :mvn/version string?) +(s/def :mvn/coord (s/merge :deps/coord + (s/keys :req [:mvn/version]))) + +;; local coords +(s/def :local/root string?) +(s/def :local/coord (s/merge :deps/coord + (s/keys :req [:local/root]))) + +;; git coords +(s/def :git/url string?) +(s/def :git/sha string?) +(s/def :git/tag string?) +(s/def :git/coord (s/merge :deps/coord + (s/keys :opt [:git/url :git/sha :git/tag]))) + +;; should this become a multispec? +(s/def ::coord (s/nilable + (s/or :mvn :mvn/coord + :local :local/coord + :git :git/coord))) + +(s/def ::path-ref (s/or :path ::path :alias ::alias)) +(s/def :aliased/paths (s/coll-of ::path-ref :kind vector? :into [])) + +;; tool args +;; ::replace-deps - map of lib to coordinate to replace the +(s/def ::tool-args (s/keys :opt-un [::replace-deps ::replace-paths ::deps ::paths])) +(s/def ::replace-deps (s/map-of ::lib ::coord)) +(s/def ::replace-paths :aliased/paths) + +;; resolve-deps args - used to modify the expanded deps tree +;; ::extra-deps - map of lib to coordinate added to the initial deps collection +;; ::override-deps - map of lib to coordinate to use instead of the coord found during expansion +;; ::default-deps - map of lib to coordinate to use if no coord is specified in extension +(s/def ::resolve-args (s/keys :opt-un [::extra-deps ::override-deps ::default-deps])) +(s/def ::extra-deps (s/map-of ::lib ::coord)) +(s/def ::override-deps (s/map-of ::lib ::coord)) +(s/def ::default-deps (s/map-of ::lib ::coord)) +(s/def ::threads pos-int?) +(s/def ::trace boolean?) + +;; make-classpath args - used when constructing the classpath +;; ::classpath-overrides - map of lib to path to use instead of the artifact found during resolution +;; ::extra-paths - collection of extra paths to add to the classpath in addition to ::paths +(s/def ::classpath-args (s/keys :opt-un [::classpath-overrides ::extra-paths])) +(s/def ::classpath-overrides (s/map-of ::lib ::path)) +(s/def ::extra-paths :aliased/paths) + +;; exec args - used when executing a function with -X or -T +;; ::exec-args - map of default function args +;; ::exec-fn - default function symbol +;; ::ns-default - default namespace to use when resolving functions +;; ::ns-aliases - map of alias to namespace to use when resolving functions +(s/def ::exec-args (s/keys :opt-un [::exec-args ::exec-fn ::ns-default ::ns-aliases])) +(s/def ::exec-args (s/nilable map?)) +(s/def ::ns-default simple-symbol?) +(s/def ::ns-aliases (s/map-of simple-symbol? simple-symbol?)) + +;; deps map (format of the deps.edn file) +(s/def ::paths :aliased/paths) +(s/def ::deps (s/map-of ::lib ::coord)) +(s/def ::aliases (s/map-of ::alias any?)) +(s/def ::deps-map (s/nilable (s/keys + :opt-un [::paths ::deps ::aliases] + :opt [:mvn/repos :mvn/local-repo :tools/usage :deps/prep-lib]))) + +;; lib map +;; a map of lib to resolved coordinate (a coord with a ::path) and dependent info +(s/def ::dependents (s/coll-of ::lib)) +(s/def ::resolved-coord (s/merge ::coord (s/keys :opt-un [:aliased/paths ::dependents]))) +(s/def ::lib-map (s/map-of ::lib ::resolved-coord)) + +;; classpath +(s/def ::classpath string?) + +;; Procurers + +;; Maven +(s/def :mvn/repos (s/map-of ::repo-id ::repo)) +(s/def ::repo-id string?) +(s/def ::repo (s/nilable (s/keys :opt-un [::url :mvn/releases :mvn/snapshots]))) +(s/def ::url string?) +(s/def :mvn-repo/enabled boolean?) +(s/def :mvn-repo/update (s/or :policy #{:daily :always :never} :interval int?)) +(s/def :mvn-repo/checksum #{:warn :fail :ignore}) +(s/def :mvn/repo-policy (s/keys :opt-un [:mvn-repo/enabled :mvn-repo/update :mvn-repo/checksum])) +(s/def :mvn/releases :mvn/repo-policy) +(s/def :mvn/snapshots :mvn/repo-policy) +(s/def :mvn/local-repo string?) + +;; Tool usage +(s/def :tools/usage (s/keys :opt-un [::ns-default ::ns-aliases])) + +;; Prep lib +(s/def :deps/prep-lib (s/keys :req-un [:prep/ensure ::alias :prep/fn])) +(s/def :prep/ensure ::path) +(s/def :prep/fn symbol?) + +(defn valid-deps? + "Determine whether the deps map is valid according to the specs" + [deps-map] + (s/valid? ::deps-map deps-map)) + +(defn explain-deps + "If a spec is invalid, return a message explaining why, suitable + for an error message" + [deps-map] + (let [err-data (s/explain-data ::deps-map deps-map)] + (if (nil? err-data) + "Failed spec, reason unknown" + (let [problems (->> (::s/problems err-data) + (sort-by #(- (count (:in %)))) + (sort-by #(- (count (:path %))))) + {:keys [path pred val reason via in]} (first problems)] + (str "Found: " (pr-str val) ", expected: " (if reason reason (s/abbrev pred))))))) + +(comment + ;; some scratch code to recursively check every deps.edn under + ;; a root directory whether it's valid against the specs + (require + '[clojure.spec.test.alpha :as stest] + '[clojure.tools.deps :as deps]) + (import '[java.nio.file Files Paths FileVisitor FileVisitResult]) + (stest/instrument (stest/enumerate-namespace 'clojure.tools.deps)) + + (Files/walkFileTree + (Paths/get "../" (into-array String [])) + (reify FileVisitor + (postVisitDirectory [_ dir ex] FileVisitResult/CONTINUE) + (preVisitDirectory [_ dir attrs] FileVisitResult/CONTINUE) + (visitFileFailed [_ f ex] FileVisitResult/CONTINUE) + (visitFile [_ f attrs] + (when (.endsWith (str f) "/deps.edn") + (print "Checking" (str f)) + (let [v (s/valid? ::deps-map (#'deps/slurp-edn-map (.toFile f)))] + (println ":" v) + (when-not v + (s/explain ::deps-map (#'deps/slurp-edn-map (.toFile f)))))) + FileVisitResult/CONTINUE))) + ) diff --git a/src/main/clojure/clojure/tools/deps/util/io.clj b/src/main/clojure/clojure/tools/deps/util/io.clj new file mode 100644 index 0000000..4c429c7 --- /dev/null +++ b/src/main/clojure/clojure/tools/deps/util/io.clj @@ -0,0 +1,61 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns ^{:skip-wiki true} + clojure.tools.deps.util.io + (:require + [clojure.edn :as edn] + [clojure.java.io :as jio] + [clojure.string :as str]) + (:import + [java.io Reader FileReader PushbackReader])) + +(defonce ^:private nl (System/getProperty "line.separator")) + +(defn printerrln + "println to *err*" + [& msgs] + (binding [*out* *err* + *print-readably* nil] + (pr (str (str/join " " msgs) nl)) + (flush))) + +(defn read-edn + "Read the edn file from the specified `reader`. + This file should contain a single edn value. Empty files return nil. + The reader will be read to EOF and closed." + [^Reader reader] + (let [EOF (Object.)] + (with-open [rdr (PushbackReader. reader)] + (let [val (edn/read {:default tagged-literal :eof EOF} rdr)] + (if (identical? EOF val) + nil + (if (not (identical? EOF (edn/read {:eof EOF} rdr))) + (throw (ex-info "Invalid file, expected edn to contain a single value." {})) + val)))))) + +(defn slurp-edn + "Read the edn file specified by f, a string or File. + An empty file will return nil." + [f] + (read-edn (FileReader. (jio/file f)))) + +(defn write-file + "Write the string s to file f. Creates parent directories for f if they don't exist." + [f s] + (let [the-file (jio/file f) + parent (.getParentFile the-file)] + (when-not (.exists parent) + (when-not (.mkdirs parent) + (let [parent-name (.getCanonicalPath parent)] + (throw (ex-info (str "Can't create directory: " parent-name) {:dir parent-name}))))) + (spit the-file s))) + +(comment + (slurp-edn "deps.edn") + ) diff --git a/src/test/clojure/clojure/tools/deps/test_edn.clj b/src/test/clojure/clojure/tools/deps/test_edn.clj new file mode 100644 index 0000000..861fcfa --- /dev/null +++ b/src/test/clojure/clojure/tools/deps/test_edn.clj @@ -0,0 +1,53 @@ +(ns clojure.tools.deps.test-edn + (:require + [clojure.test :refer [deftest is are testing]] + [clojure.tools.deps.edn :as deps-edn]) + (:import + [java.io File])) + +(deftest test-slurp-deps-on-nonexistent-file + (is (nil? (deps-edn/slurp-deps (File. "NONEXISTENT_FILE"))))) + +(deftest test-merge-or-replace + (are [vals ret] + (= ret (apply #'deps-edn/merge-or-replace vals)) + + [nil nil] nil + [nil {:a 1}] {:a 1} + [{:a 1} nil] {:a 1} + [{:a 1 :b 1} {:a 2 :c 3} {:c 4 :d 5}] {:a 2 :b 1 :c 4 :d 5} + [nil 1] 1 + [1 nil] 1 + [1 2] 2)) + +(deftest test-merge-edns + (is (= (deps-edn/merge-edns + [{:deps {'a {:v 1}, 'b {:v 1}} + :a/x {:a 1} + :a/y "abc"} + {:deps {'b {:v 2}, 'c {:v 3}} + :a/x {:b 1} + :a/y "def"} + nil]) + {:deps {'a {:v 1}, 'b {:v 2}, 'c {:v 3}} + :a/x {:a 1, :b 1} + :a/y "def"}))) + +(deftest merge-alias-maps + (are [m1 m2 out] + (= out (#'deps-edn/merge-alias-maps m1 m2)) + + {} {} {} + {} {:extra-deps {:a 1}} {:extra-deps {:a 1}} + {:extra-deps {:a 1 :b 1}} {:extra-deps {:b 2}} {:extra-deps {:a 1 :b 2}} + {} {:default-deps {:a 1}} {:default-deps {:a 1}} + {:default-deps {:a 1 :b 1}} {:default-deps {:b 2}} {:default-deps {:a 1 :b 2}} + {} {:override-deps {:a 1}} {:override-deps {:a 1}} + {:override-deps {:a 1 :b 1}} {:override-deps {:b 2}} {:override-deps {:a 1 :b 2}} + {} {:extra-paths ["a" "b"]} {:extra-paths ["a" "b"]} + {:extra-paths ["a" "b"]} {:extra-paths ["c" "d"]} {:extra-paths ["a" "b" "c" "d"]} + {} {:jvm-opts ["-Xms100m" "-Xmx200m"]} {:jvm-opts ["-Xms100m" "-Xmx200m"]} + {:jvm-opts ["-Xms100m" "-Xmx200m"]} {:jvm-opts ["-Dfoo=bar"]} {:jvm-opts ["-Xms100m" "-Xmx200m" "-Dfoo=bar"]} + {} {:main-opts ["foo.bar" "1"]} {:main-opts ["foo.bar" "1"]} + {:main-opts ["foo.bar" "1"]} {:main-opts ["foo.baz" "2"]} {:main-opts ["foo.baz" "2"]})) + diff --git a/src/test/clojure/clojure/tools/deps/util.clj b/src/test/clojure/clojure/tools/deps/util.clj new file mode 100644 index 0000000..7af9b94 --- /dev/null +++ b/src/test/clojure/clojure/tools/deps/util.clj @@ -0,0 +1,25 @@ +(ns clojure.tools.deps.util) + +(defn submap? + "Is m1 a subset of m2?" + [m1 m2] + (if (and (map? m1) (map? m2)) + (every? (fn [[k v]] (and (contains? m2 k) + (submap? v (get m2 k)))) + m1) + (= m1 m2))) + +(defn submap-debug? + "Is m1 a subset of m2? + Print missing keys or mismatched values." + [m1 m2] + (if (and (map? m1) (map? m2)) + (every? (fn [[k v]] + (when (not (contains? m2 k)) (println "m1 has key, m2 does not: " k)) + (and (contains? m2 k) (submap? v (get m2 k)))) + m1) + (if (= m1 m2) + true + (do + (println "Nested values don't match, m1 val=" m1 "m2 val=" m2) + false))))