Skip to content

Commit bd7e4be

Browse files
committed
code moved from tools.deps
1 parent 70f84e1 commit bd7e4be

6 files changed

Lines changed: 258 additions & 52 deletions

File tree

src/main/clojure/clojure/tools/deps/edn.clj

Lines changed: 110 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,17 @@
77
; You must not remove this notice, or any other, from this software.
88

99
(ns clojure.tools.deps.edn
10+
"Functions for reading, validating, and manipulating deps.edn
11+
files and data structures."
1012
(:require
1113
[clojure.edn :as edn]
1214
[clojure.java.io :as jio]
1315
[clojure.string :as str]
1416
[clojure.tools.deps.specs :as specs]
17+
[clojure.tools.deps.util.dir :as dir]
1518
[clojure.walk :as walk])
1619
(:import
17-
[java.io File PushbackReader]
20+
[java.io BufferedReader File InputStreamReader PushbackReader Reader]
1821
[clojure.lang EdnReader$ReaderException]
1922
))
2023

@@ -40,31 +43,34 @@
4043
(let [abs-path (.getAbsolutePath (jio/file path))]
4144
(ex-info (format fmt abs-path) {:path abs-path})))
4245

43-
(defn read-edn
44-
"Read edn from file f, which should contain exactly one edn value.
45-
If f exists but is blank, nil is returned.
46-
Throws if file is unreadable or contains multiple values.
46+
(defn- read-edn
47+
"Read edn from Reader r, which should contain exactly one edn value.
48+
If source exists but is blank, nil is returned.
49+
Throws if source is unreadable or contains multiple values.
50+
4751
Opts:
48-
:path String path to file being read"
49-
[^File f & opts]
50-
(with-open [rdr (PushbackReader. (jio/reader f))]
51-
(let [EOF (Object.)
52-
val (try
53-
(let [val (edn/read {:default tagged-literal :eof EOF} rdr)]
54-
(if (identical? EOF val)
55-
nil ;; empty file
56-
(if (not (identical? EOF (edn/read {:eof EOF} rdr)))
57-
(throw (ex-info "Expected edn to contain a single value." {}))
58-
val)))
59-
(catch EdnReader$ReaderException e
60-
(throw (io-err (str (.getMessage e) " (%s)") opts)))
61-
(catch RuntimeException t
62-
(if (str/starts-with? (.getMessage t) "EOF while reading")
63-
(throw (io-err "Error reading edn, delimiter unmatched (%s)" opts))
64-
(throw (io-err (str "Error reading edn. " (.getMessage t) " (%s)") opts)))))])))
52+
:path String path to file being read, for error reporting"
53+
[^Reader r & opts]
54+
(with-open [rdr (PushbackReader. r)]
55+
(let [EOF (Object.)]
56+
(try
57+
(let [val (edn/read {:default tagged-literal :eof EOF} rdr)]
58+
(if (identical? EOF val)
59+
nil ;; empty file
60+
(if (not (identical? EOF (edn/read {:eof EOF} rdr)))
61+
(throw (ex-info "Expected edn to contain a single value." {}))
62+
val)))
63+
(catch EdnReader$ReaderException e
64+
(throw (io-err (str (.getMessage e) " (%s)") opts)))
65+
(catch RuntimeException t
66+
(if (str/starts-with? (.getMessage t) "EOF while reading")
67+
(throw (io-err "Error reading edn, delimiter unmatched (%s)" opts))
68+
(throw (io-err (str "Error reading edn. " (.getMessage t) " (%s)") opts))))))))
6569

6670
(defn validate
6771
"Validate a deps-edn map according to the specs, throw if invalid.
72+
Returns the deps-edn map.
73+
6874
Opts:
6975
:path String path to file being read"
7076
[deps-edn & opts]
@@ -79,7 +85,7 @@
7985
(if (simple-symbol? s)
8086
(let [cs (as-> (name s) n (symbol n n))]
8187
(printerrln "DEPRECATED: Libs must be qualified, change" s "=>" cs
82-
(if-let [path (:path opts)] (str "(" path ")" "")))
88+
(if-let [path (:path opts)] (str "(" path ")") ""))
8389
cs)
8490
s))
8591

@@ -100,6 +106,8 @@
100106

101107
(defn canonicalize
102108
"Canonicalize a deps.edn map (convert simple lib symbols to qualified lib symbols).
109+
Returns the deps-edn map.
110+
103111
Opts:
104112
:path String path to file being read"
105113
[deps-edn & opts]
@@ -119,13 +127,89 @@
119127
(defn read-deps
120128
"Corece f to a file with jio/file, then read, validate, and canonicalize
121129
the deps.edn. This is the primary entry point for reading.
130+
Use read-edn, validate, or canonicalize for individual steps.
122131
123-
Opts: none"
132+
Opts: none for now"
124133
[f & opts]
125134
(let [file (jio/file f)
126135
opts {:path (.getPath file)}]
127136
(when (.exists file)
128-
(-> file (read-edn opts) (validate opts) (canonicalize opts)))))
137+
(-> file jio/reader (read-edn opts) (validate opts) (canonicalize opts)))))
138+
139+
;;;; Dep chain lookups
140+
141+
(defn root-deps
142+
"Read the root deps.edn resource from the classpath at the path
143+
clojure/tools/deps/deps.edn"
144+
[]
145+
(let [url (jio/resource "clojure/tools/deps/deps.edn")]
146+
;; TODO: separate file reading and resource reading
147+
(read-edn (BufferedReader. (InputStreamReader. (.openStream url))))))
148+
149+
(defn user-deps-path
150+
"Use the same logic as clj to calculate the location of the user deps.edn.
151+
Note that it's possible no file may exist at this location."
152+
[]
153+
(let [config-env (System/getenv "CLJ_CONFIG")
154+
xdg-env (System/getenv "XDG_CONFIG_HOME")
155+
home (System/getProperty "user.home")
156+
config-dir (cond config-env config-env
157+
xdg-env (str xdg-env File/separator "clojure")
158+
:else (str home File/separator ".clojure"))]
159+
(str config-dir File/separator "deps.edn")))
160+
161+
(defn user-deps
162+
"Calculate the user deps.edn per user-deps-path, read and
163+
return the deps.edn data"
164+
[]
165+
(-> (user-deps-path) jio/file dir/canonicalize read-deps))
166+
167+
(defn project-deps-path
168+
"Calculate the project deps.edn location. This
169+
is the deps.edn in the current directory, as defined by
170+
clojure.tools.deps.util.dir/*the-dir* - use with-dir
171+
to push a new local directory context around a call to
172+
project-deps-path."
173+
[]
174+
(str dir/*the-dir* File/separator "deps.edn"))
175+
176+
(defn project-deps
177+
"Calculate the project deps.edn location "
178+
[]
179+
(-> (project-deps-path) jio/file dir/canonicalize read-deps))
180+
181+
(defn- choose-deps
182+
[requested standard-fn]
183+
(cond
184+
(= :standard requested) (standard-fn)
185+
(string? requested) (-> requested jio/file dir/canonicalize read-deps)
186+
(or (nil? requested) (map? requested)) requested
187+
:else (throw (ex-info (format "Unexpected dep source: %s" (pr-str requested))
188+
{:requested requested}))))
189+
190+
(defn create-edn-maps
191+
"Takes optional map of location sources, keys = :root :user :project :extra
192+
where each key may be:
193+
:standard (default) - to get the default source
194+
string - for file path to source
195+
nil - to omit
196+
map - a literal map to use
197+
198+
Returns a set of deps edn maps with the same keys :root :user :project :extra.
199+
Keys may be missing if source was nil or file was missing."
200+
([]
201+
(create-edn-maps nil))
202+
([{:keys [root user project extra] :as params
203+
:or {root :standard, user :standard, project :standard}}]
204+
(let [root-edn (choose-deps root #(root-deps))
205+
user-edn (choose-deps user #(user-deps))
206+
project-edn (choose-deps project #(project-deps))
207+
extra-edn (choose-deps extra (constantly nil))]
208+
(cond-> {}
209+
root-edn (assoc :root root-edn)
210+
user-edn (assoc :user user-edn)
211+
project-edn (assoc :project project-edn)
212+
extra-edn (assoc :extra extra-edn)))))
129213

130214
;;;; deps edn manipulation
131215

@@ -175,7 +259,7 @@
175259
merge
176260
(fn [_v1 v2] v2))))
177261

178-
(defn- merge-alias-maps
262+
(defn merge-alias-maps
179263
"Like merge-with, but using custom per-alias-key merge function"
180264
[& ms]
181265
(reduce
@@ -191,20 +275,3 @@
191275
(->> alias-kws
192276
(map #(get-in edn-map [:aliases %]))
193277
(apply merge-alias-maps)))
194-
195-
196-
197-
198-
(defn- chase-key
199-
"Given an aliases set and a keyword k, return a flattened vector of path
200-
entries for that k, resolving recursively if needed, or nil."
201-
[aliases k]
202-
(let [path-coll (get aliases k)]
203-
(when (seq path-coll)
204-
(into [] (mapcat #(if (string? %) [[% {:path-key k}]] (chase-key aliases %))) path-coll))))
205-
206-
(defn- flatten-paths
207-
[{:keys [paths aliases] :as deps-edn-map} {:keys [extra-paths] :as classpath-args}]
208-
(let [aliases' (assoc aliases :paths paths :extra-paths extra-paths)]
209-
(into [] (comp (mapcat #(chase-key aliases' %)) (remove nil?)) [:extra-paths :paths])))
210-

src/main/clojure/clojure/tools/deps/specs.clj

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,22 @@
139139
(sort-by #(- (count (:in %))))
140140
(sort-by #(- (count (:path %)))))
141141
{:keys [path pred val reason via in]} (first problems)]
142-
(str "Found: " (pr-str val) ", expected: " (if reason reason (s/abbrev pred)))))))
142+
(str "Found: " (pr-str val) ", expected: " (if reason reason (s/abbrev pred)) ", in: " (pr-str in))))))
143+
144+
;; API
145+
146+
(s/fdef clojure.tools.deps/resolve-deps
147+
:args (s/cat :deps ::deps-map :options ::resolve-args)
148+
:ret ::lib-map)
149+
150+
(s/fdef clojure.tools.deps/make-classpath-map
151+
:args (s/cat :deps ::deps-map, :libs ::lib-map, :classpath-args ::classpath-args)
152+
:ret map?)
153+
154+
(s/fdef clojure.tools.deps/make-classpath
155+
:args (s/cat :libs ::lib-map, :paths ::paths, :classpath-args ::classpath-args)
156+
:ret string?)
157+
143158

144159
(comment
145160
;; some scratch code to recursively check every deps.edn under
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
; Copyright (c) Rich Hickey. All rights reserved.
2+
; The use and distribution terms for this software are covered by the
3+
; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
4+
; which can be found in the file epl-v10.html at the root of this distribution.
5+
; By using this software in any fashion, you are agreeing to be bound by
6+
; the terms of this license.
7+
; You must not remove this notice, or any other, from this software.
8+
9+
(ns
10+
clojure.tools.deps.util.dir
11+
"An abstraction for managing a 'current directory' stack.
12+
The dynamic var `*the-dir*` holds the current directory.
13+
Use `with-dir` to push a new absolute (or more usefully)
14+
relative directory onto the stack, which will pop when the
15+
scope exits. Use `canonical` to get the canonical File
16+
of a path in terms of the current directory context."
17+
(:require
18+
[clojure.java.io :as jio])
19+
(:import
20+
[java.io File]
21+
[java.nio.file Files Path]))
22+
23+
(set! *warn-on-reflection* true)
24+
25+
(def ^:dynamic *the-dir*
26+
"Thread-local directory context for resolving relative directories.
27+
Defaults to current directory. Should always hold an absolute directory
28+
java.io.File, never null."
29+
(jio/file (System/getProperty "user.dir")))
30+
31+
(defn canonicalize
32+
"Make canonical File in terms of the current directory context.
33+
f may be either absolute or relative."
34+
^File [^File f]
35+
(.getCanonicalFile
36+
(if (.isAbsolute f)
37+
f
38+
(jio/file *the-dir* f))))
39+
40+
(defmacro with-dir
41+
"Push directory into current directory context for execution of body."
42+
[^File dir & body]
43+
`(binding [*the-dir* (canonicalize ~dir)]
44+
~@body))
45+
46+
(defn- same-file?
47+
"If a file can't be read (most common reason is directory does not exist), then
48+
treat this as not the same file (ie unknown)."
49+
[^Path p1 ^Path p2]
50+
(try
51+
(Files/isSameFile p1 p2)
52+
(catch Exception _ false)))
53+
54+
(defn sub-path?
55+
"True if the path is a sub-path of the current directory context.
56+
path may be either absolute or relative. Will return true if path
57+
has a parent that is the current directory context, false otherwise.
58+
Handles relative paths, .., ., etc. The sub-path does not need to
59+
exist on disk (but the current directory context must)."
60+
[^File path]
61+
(if (nil? path)
62+
false
63+
(let [root-path (.toPath ^File *the-dir*)]
64+
(loop [check-path (.toPath (canonicalize path))]
65+
(cond
66+
(nil? check-path) false
67+
(same-file? root-path check-path) true
68+
:else (recur (.getParent check-path)))))))
69+
70+
;; DEPRECATED
71+
(defn as-canonical
72+
^File [^File dir]
73+
(canonicalize dir))

src/test/clojure/clojure/tools/deps/test_edn.clj

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
(ns clojure.tools.deps.test-edn
22
(:require
3-
[clojure.test :refer [deftest is are testing]]
4-
[clojure.tools.deps.edn :as deps-edn])
3+
[clojure.test :refer [are deftest is]]
4+
[clojure.tools.deps.edn :as depsedn])
55
(:import
66
[java.io File]))
77

8-
(deftest test-slurp-deps-on-nonexistent-file
9-
(is (nil? (deps-edn/read-deps (File. "NONEXISTENT_FILE")))))
8+
(deftest test-read-deps-on-nonexistent-file
9+
(is (nil? (depsedn/read-deps (File. "NONEXISTENT_FILE")))))
1010

1111
(deftest test-merge-or-replace
1212
(are [vals ret]
13-
(= ret (apply #'deps-edn/merge-or-replace vals))
13+
(= ret (apply #'depsedn/merge-or-replace vals))
1414

1515
[nil nil] nil
1616
[nil {:a 1}] {:a 1}
@@ -21,7 +21,7 @@
2121
[1 2] 2))
2222

2323
(deftest test-merge-edns
24-
(is (= (deps-edn/merge-edns
24+
(is (= (depsedn/merge-edns
2525
[{:deps {'a {:v 1}, 'b {:v 1}}
2626
:a/x {:a 1}
2727
:a/y "abc"}
@@ -35,7 +35,7 @@
3535

3636
(deftest merge-alias-maps
3737
(are [m1 m2 out]
38-
(= out (#'deps-edn/merge-alias-maps m1 m2))
38+
(= out (#'depsedn/merge-alias-maps m1 m2))
3939

4040
{} {} {}
4141
{} {:extra-deps {:a 1}} {:extra-deps {:a 1}}
@@ -50,4 +50,3 @@
5050
{:jvm-opts ["-Xms100m" "-Xmx200m"]} {:jvm-opts ["-Dfoo=bar"]} {:jvm-opts ["-Xms100m" "-Xmx200m" "-Dfoo=bar"]}
5151
{} {:main-opts ["foo.bar" "1"]} {:main-opts ["foo.bar" "1"]}
5252
{:main-opts ["foo.bar" "1"]} {:main-opts ["foo.baz" "2"]} {:main-opts ["foo.baz" "2"]}))
53-
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
(ns clojure.tools.deps.test-specs
2+
(:require
3+
[clojure.test :refer [deftest is testing]]
4+
[clojure.tools.deps.specs :as specs]))
5+
6+
7+
(deftest test-explain-deps
8+
(let [deps-map {:mvn/repos {"myrepo" {:url "https://repo1.maven.org/maven2/"
9+
:snapshots true}}}]
10+
(is (= (specs/explain-deps deps-map)
11+
"Found: true, expected: map?, in: [:mvn/repos \"myrepo\" 1 :snapshots]")))
12+
13+
(let [deps-map {:tools/usage {:ns-default "some.ns"}}]
14+
(is (= (specs/explain-deps deps-map)
15+
"Found: \"some.ns\", expected: simple-symbol?, in: [:tools/usage :ns-default]"))))
16+
17+
(deftest empty-nil-deps-is-valid
18+
(testing "file exists but is empty (nil)"
19+
(is (specs/valid-deps? nil))))
20+
21+
(deftest TDEPS-238
22+
(testing "deps are invalid with extra nested vector in :exclusions"
23+
(let [invalid {:deps
24+
{'org.clojure/core.memoize
25+
{:mvn/version "1.0.257"
26+
:exclusions [['org.clojure/data.priority-map]]}}}]
27+
(is (not (specs/valid-deps? invalid))))))
28+
29+
(comment
30+
(test-explain-deps)
31+
)

0 commit comments

Comments
 (0)