├── .VERSION_PREFIX
├── .dir-locals.el
├── .gitignore
├── CHANGELOG.md
├── LICENSE.txt
├── README.md
├── bb.edn
├── bin
└── proj
├── deps.edn
├── pom.xml
├── repl_session
└── poke.clj
└── src
├── .gitkeep
└── lambdaisland
├── classpath.clj
└── classpath
└── watch_deps.clj
/.VERSION_PREFIX:
--------------------------------------------------------------------------------
1 | 0.6
--------------------------------------------------------------------------------
/.dir-locals.el:
--------------------------------------------------------------------------------
1 | ((nil . ((cider-clojure-cli-global-options . "-A:dev:test"))))
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .cpcache
2 | .nrepl-port
3 | target
4 | repl
5 | scratch.clj
6 | .shadow-cljs
7 | target
8 | yarn.lock
9 | node_modules/
10 | .DS_Store
11 | resources/public/ui
12 | .store
13 | out
14 | .#*
15 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Unreleased
2 |
3 | ## Added
4 |
5 | ## Fixed
6 |
7 | ## Changed
8 |
9 | # 0.6.58 (2024-12-04 / 3edd4ca)
10 |
11 | ## Fixed
12 |
13 | - Fix handling of InnocuousThread (i.e. skip them, not much more we can do)
14 |
15 | ## Changed
16 |
17 | - Upgraded dependencies
18 |
19 | # 0.5.48 (2024-01-10 / 2228a54)
20 |
21 | ## Changed
22 |
23 | - Upgraded dependencies
24 |
25 | # 0.4.44 (2022-09-08 / 0c66dda)
26 |
27 | ## Added
28 |
29 | - [watcher] Add a `:watch-paths` option, to watch additional files. Presumable
30 | in combination with a custom `:basis-fn`
31 |
32 | # 0.3.40 (2022-09-08 / 73c9529)
33 |
34 | ## Added
35 |
36 | - [watcher] Support for custom basis-fn
37 | - [watcher] Check for aliases in `:extra` deps file
38 |
39 | ## Fixed
40 |
41 | - [watcher] Fix watcher stop! function
42 |
43 | # 0.2.37 (2022-08-26 / 34be62f)
44 |
45 | ## Changed
46 |
47 | - Upgrade directory-watcher to the latest version
48 | - Prefix output from watch-deps with `[watch-deps]`
49 | - Print a message when the watcher triggers, but no classpath changes are made
50 |
51 | # 0.1.33 (2022-08-25 / fd51db4)
52 |
53 | - Fix typo in the README's example and one in doc-string
54 |
55 | ## Added
56 |
57 | - Support watching multiple `deps.edn` files referenced via `:local/root`
58 |
59 | ## Fixed
60 |
61 | ## Changed
62 |
63 | # 0.0.27 (2021-10-06 / 719c1f5)
64 |
65 | ## Fixed
66 |
67 | - Watch-deps now triggers on Mac
68 | - Support both `main` and `master` as branch names in `git-pull-lib`
69 | - Speed up resource lookups in priority classloader
70 | - Several extra convenience functions for working with classloaders
71 |
72 | # 0.0.0
73 |
74 | ## First announced version (git only)
75 |
76 | - Classpath inspection utilities
77 | - Priority classloader for overrides
78 | - deps watcher
79 | - git-pull-lib
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Mozilla Public License Version 2.0
2 | ==================================
3 |
4 | 1. Definitions
5 | --------------
6 |
7 | 1.1. "Contributor"
8 | means each individual or legal entity that creates, contributes to
9 | the creation of, or owns Covered Software.
10 |
11 | 1.2. "Contributor Version"
12 | means the combination of the Contributions of others (if any) used
13 | by a Contributor and that particular Contributor's Contribution.
14 |
15 | 1.3. "Contribution"
16 | means Covered Software of a particular Contributor.
17 |
18 | 1.4. "Covered Software"
19 | means Source Code Form to which the initial Contributor has attached
20 | the notice in Exhibit A, the Executable Form of such Source Code
21 | Form, and Modifications of such Source Code Form, in each case
22 | including portions thereof.
23 |
24 | 1.5. "Incompatible With Secondary Licenses"
25 | means
26 |
27 | (a) that the initial Contributor has attached the notice described
28 | in Exhibit B to the Covered Software; or
29 |
30 | (b) that the Covered Software was made available under the terms of
31 | version 1.1 or earlier of the License, but not also under the
32 | terms of a Secondary License.
33 |
34 | 1.6. "Executable Form"
35 | means any form of the work other than Source Code Form.
36 |
37 | 1.7. "Larger Work"
38 | means a work that combines Covered Software with other material, in
39 | a separate file or files, that is not Covered Software.
40 |
41 | 1.8. "License"
42 | means this document.
43 |
44 | 1.9. "Licensable"
45 | means having the right to grant, to the maximum extent possible,
46 | whether at the time of the initial grant or subsequently, any and
47 | all of the rights conveyed by this License.
48 |
49 | 1.10. "Modifications"
50 | means any of the following:
51 |
52 | (a) any file in Source Code Form that results from an addition to,
53 | deletion from, or modification of the contents of Covered
54 | Software; or
55 |
56 | (b) any new file in Source Code Form that contains any Covered
57 | Software.
58 |
59 | 1.11. "Patent Claims" of a Contributor
60 | means any patent claim(s), including without limitation, method,
61 | process, and apparatus claims, in any patent Licensable by such
62 | Contributor that would be infringed, but for the grant of the
63 | License, by the making, using, selling, offering for sale, having
64 | made, import, or transfer of either its Contributions or its
65 | Contributor Version.
66 |
67 | 1.12. "Secondary License"
68 | means either the GNU General Public License, Version 2.0, the GNU
69 | Lesser General Public License, Version 2.1, the GNU Affero General
70 | Public License, Version 3.0, or any later versions of those
71 | licenses.
72 |
73 | 1.13. "Source Code Form"
74 | means the form of the work preferred for making modifications.
75 |
76 | 1.14. "You" (or "Your")
77 | means an individual or a legal entity exercising rights under this
78 | License. For legal entities, "You" includes any entity that
79 | controls, is controlled by, or is under common control with You. For
80 | purposes of this definition, "control" means (a) the power, direct
81 | or indirect, to cause the direction or management of such entity,
82 | whether by contract or otherwise, or (b) ownership of more than
83 | fifty percent (50%) of the outstanding shares or beneficial
84 | ownership of such entity.
85 |
86 | 2. License Grants and Conditions
87 | --------------------------------
88 |
89 | 2.1. Grants
90 |
91 | Each Contributor hereby grants You a world-wide, royalty-free,
92 | non-exclusive license:
93 |
94 | (a) under intellectual property rights (other than patent or trademark)
95 | Licensable by such Contributor to use, reproduce, make available,
96 | modify, display, perform, distribute, and otherwise exploit its
97 | Contributions, either on an unmodified basis, with Modifications, or
98 | as part of a Larger Work; and
99 |
100 | (b) under Patent Claims of such Contributor to make, use, sell, offer
101 | for sale, have made, import, and otherwise transfer either its
102 | Contributions or its Contributor Version.
103 |
104 | 2.2. Effective Date
105 |
106 | The licenses granted in Section 2.1 with respect to any Contribution
107 | become effective for each Contribution on the date the Contributor first
108 | distributes such Contribution.
109 |
110 | 2.3. Limitations on Grant Scope
111 |
112 | The licenses granted in this Section 2 are the only rights granted under
113 | this License. No additional rights or licenses will be implied from the
114 | distribution or licensing of Covered Software under this License.
115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a
116 | Contributor:
117 |
118 | (a) for any code that a Contributor has removed from Covered Software;
119 | or
120 |
121 | (b) for infringements caused by: (i) Your and any other third party's
122 | modifications of Covered Software, or (ii) the combination of its
123 | Contributions with other software (except as part of its Contributor
124 | Version); or
125 |
126 | (c) under Patent Claims infringed by Covered Software in the absence of
127 | its Contributions.
128 |
129 | This License does not grant any rights in the trademarks, service marks,
130 | or logos of any Contributor (except as may be necessary to comply with
131 | the notice requirements in Section 3.4).
132 |
133 | 2.4. Subsequent Licenses
134 |
135 | No Contributor makes additional grants as a result of Your choice to
136 | distribute the Covered Software under a subsequent version of this
137 | License (see Section 10.2) or under the terms of a Secondary License (if
138 | permitted under the terms of Section 3.3).
139 |
140 | 2.5. Representation
141 |
142 | Each Contributor represents that the Contributor believes its
143 | Contributions are its original creation(s) or it has sufficient rights
144 | to grant the rights to its Contributions conveyed by this License.
145 |
146 | 2.6. Fair Use
147 |
148 | This License is not intended to limit any rights You have under
149 | applicable copyright doctrines of fair use, fair dealing, or other
150 | equivalents.
151 |
152 | 2.7. Conditions
153 |
154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
155 | in Section 2.1.
156 |
157 | 3. Responsibilities
158 | -------------------
159 |
160 | 3.1. Distribution of Source Form
161 |
162 | All distribution of Covered Software in Source Code Form, including any
163 | Modifications that You create or to which You contribute, must be under
164 | the terms of this License. You must inform recipients that the Source
165 | Code Form of the Covered Software is governed by the terms of this
166 | License, and how they can obtain a copy of this License. You may not
167 | attempt to alter or restrict the recipients' rights in the Source Code
168 | Form.
169 |
170 | 3.2. Distribution of Executable Form
171 |
172 | If You distribute Covered Software in Executable Form then:
173 |
174 | (a) such Covered Software must also be made available in Source Code
175 | Form, as described in Section 3.1, and You must inform recipients of
176 | the Executable Form how they can obtain a copy of such Source Code
177 | Form by reasonable means in a timely manner, at a charge no more
178 | than the cost of distribution to the recipient; and
179 |
180 | (b) You may distribute such Executable Form under the terms of this
181 | License, or sublicense it under different terms, provided that the
182 | license for the Executable Form does not attempt to limit or alter
183 | the recipients' rights in the Source Code Form under this License.
184 |
185 | 3.3. Distribution of a Larger Work
186 |
187 | You may create and distribute a Larger Work under terms of Your choice,
188 | provided that You also comply with the requirements of this License for
189 | the Covered Software. If the Larger Work is a combination of Covered
190 | Software with a work governed by one or more Secondary Licenses, and the
191 | Covered Software is not Incompatible With Secondary Licenses, this
192 | License permits You to additionally distribute such Covered Software
193 | under the terms of such Secondary License(s), so that the recipient of
194 | the Larger Work may, at their option, further distribute the Covered
195 | Software under the terms of either this License or such Secondary
196 | License(s).
197 |
198 | 3.4. Notices
199 |
200 | You may not remove or alter the substance of any license notices
201 | (including copyright notices, patent notices, disclaimers of warranty,
202 | or limitations of liability) contained within the Source Code Form of
203 | the Covered Software, except that You may alter any license notices to
204 | the extent required to remedy known factual inaccuracies.
205 |
206 | 3.5. Application of Additional Terms
207 |
208 | You may choose to offer, and to charge a fee for, warranty, support,
209 | indemnity or liability obligations to one or more recipients of Covered
210 | Software. However, You may do so only on Your own behalf, and not on
211 | behalf of any Contributor. You must make it absolutely clear that any
212 | such warranty, support, indemnity, or liability obligation is offered by
213 | You alone, and You hereby agree to indemnify every Contributor for any
214 | liability incurred by such Contributor as a result of warranty, support,
215 | indemnity or liability terms You offer. You may include additional
216 | disclaimers of warranty and limitations of liability specific to any
217 | jurisdiction.
218 |
219 | 4. Inability to Comply Due to Statute or Regulation
220 | ---------------------------------------------------
221 |
222 | If it is impossible for You to comply with any of the terms of this
223 | License with respect to some or all of the Covered Software due to
224 | statute, judicial order, or regulation then You must: (a) comply with
225 | the terms of this License to the maximum extent possible; and (b)
226 | describe the limitations and the code they affect. Such description must
227 | be placed in a text file included with all distributions of the Covered
228 | Software under this License. Except to the extent prohibited by statute
229 | or regulation, such description must be sufficiently detailed for a
230 | recipient of ordinary skill to be able to understand it.
231 |
232 | 5. Termination
233 | --------------
234 |
235 | 5.1. The rights granted under this License will terminate automatically
236 | if You fail to comply with any of its terms. However, if You become
237 | compliant, then the rights granted under this License from a particular
238 | Contributor are reinstated (a) provisionally, unless and until such
239 | Contributor explicitly and finally terminates Your grants, and (b) on an
240 | ongoing basis, if such Contributor fails to notify You of the
241 | non-compliance by some reasonable means prior to 60 days after You have
242 | come back into compliance. Moreover, Your grants from a particular
243 | Contributor are reinstated on an ongoing basis if such Contributor
244 | notifies You of the non-compliance by some reasonable means, this is the
245 | first time You have received notice of non-compliance with this License
246 | from such Contributor, and You become compliant prior to 30 days after
247 | Your receipt of the notice.
248 |
249 | 5.2. If You initiate litigation against any entity by asserting a patent
250 | infringement claim (excluding declaratory judgment actions,
251 | counter-claims, and cross-claims) alleging that a Contributor Version
252 | directly or indirectly infringes any patent, then the rights granted to
253 | You by any and all Contributors for the Covered Software under Section
254 | 2.1 of this License shall terminate.
255 |
256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all
257 | end user license agreements (excluding distributors and resellers) which
258 | have been validly granted by You or Your distributors under this License
259 | prior to termination shall survive termination.
260 |
261 | ************************************************************************
262 | * *
263 | * 6. Disclaimer of Warranty *
264 | * ------------------------- *
265 | * *
266 | * Covered Software is provided under this License on an "as is" *
267 | * basis, without warranty of any kind, either expressed, implied, or *
268 | * statutory, including, without limitation, warranties that the *
269 | * Covered Software is free of defects, merchantable, fit for a *
270 | * particular purpose or non-infringing. The entire risk as to the *
271 | * quality and performance of the Covered Software is with You. *
272 | * Should any Covered Software prove defective in any respect, You *
273 | * (not any Contributor) assume the cost of any necessary servicing, *
274 | * repair, or correction. This disclaimer of warranty constitutes an *
275 | * essential part of this License. No use of any Covered Software is *
276 | * authorized under this License except under this disclaimer. *
277 | * *
278 | ************************************************************************
279 |
280 | ************************************************************************
281 | * *
282 | * 7. Limitation of Liability *
283 | * -------------------------- *
284 | * *
285 | * Under no circumstances and under no legal theory, whether tort *
286 | * (including negligence), contract, or otherwise, shall any *
287 | * Contributor, or anyone who distributes Covered Software as *
288 | * permitted above, be liable to You for any direct, indirect, *
289 | * special, incidental, or consequential damages of any character *
290 | * including, without limitation, damages for lost profits, loss of *
291 | * goodwill, work stoppage, computer failure or malfunction, or any *
292 | * and all other commercial damages or losses, even if such party *
293 | * shall have been informed of the possibility of such damages. This *
294 | * limitation of liability shall not apply to liability for death or *
295 | * personal injury resulting from such party's negligence to the *
296 | * extent applicable law prohibits such limitation. Some *
297 | * jurisdictions do not allow the exclusion or limitation of *
298 | * incidental or consequential damages, so this exclusion and *
299 | * limitation may not apply to You. *
300 | * *
301 | ************************************************************************
302 |
303 | 8. Litigation
304 | -------------
305 |
306 | Any litigation relating to this License may be brought only in the
307 | courts of a jurisdiction where the defendant maintains its principal
308 | place of business and such litigation shall be governed by laws of that
309 | jurisdiction, without reference to its conflict-of-law provisions.
310 | Nothing in this Section shall prevent a party's ability to bring
311 | cross-claims or counter-claims.
312 |
313 | 9. Miscellaneous
314 | ----------------
315 |
316 | This License represents the complete agreement concerning the subject
317 | matter hereof. If any provision of this License is held to be
318 | unenforceable, such provision shall be reformed only to the extent
319 | necessary to make it enforceable. Any law or regulation which provides
320 | that the language of a contract shall be construed against the drafter
321 | shall not be used to construe this License against a Contributor.
322 |
323 | 10. Versions of the License
324 | ---------------------------
325 |
326 | 10.1. New Versions
327 |
328 | Mozilla Foundation is the license steward. Except as provided in Section
329 | 10.3, no one other than the license steward has the right to modify or
330 | publish new versions of this License. Each version will be given a
331 | distinguishing version number.
332 |
333 | 10.2. Effect of New Versions
334 |
335 | You may distribute the Covered Software under the terms of the version
336 | of the License under which You originally received the Covered Software,
337 | or under the terms of any subsequent version published by the license
338 | steward.
339 |
340 | 10.3. Modified Versions
341 |
342 | If you create software not governed by this License, and you want to
343 | create a new license for such software, you may create and use a
344 | modified version of this License if you rename the license and remove
345 | any references to the name of the license steward (except to note that
346 | such modified license differs from this License).
347 |
348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary
349 | Licenses
350 |
351 | If You choose to distribute Source Code Form that is Incompatible With
352 | Secondary Licenses under the terms of this version of the License, the
353 | notice described in Exhibit B of this License must be attached.
354 |
355 | Exhibit A - Source Code Form License Notice
356 | -------------------------------------------
357 |
358 | This Source Code Form is subject to the terms of the Mozilla Public
359 | License, v. 2.0. If a copy of the MPL was not distributed with this
360 | file, You can obtain one at http://mozilla.org/MPL/2.0/.
361 |
362 | If it is not possible or desirable to put the notice in a particular
363 | file, then You may include the notice in a location (such as a LICENSE
364 | file in a relevant directory) where a recipient would be likely to look
365 | for such a notice.
366 |
367 | You may add additional accurate notices of copyright ownership.
368 |
369 | Exhibit B - "Incompatible With Secondary Licenses" Notice
370 | ---------------------------------------------------------
371 |
372 | This Source Code Form is "Incompatible With Secondary Licenses", as
373 | defined by the Mozilla Public License, v. 2.0.
374 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # lambdaisland/classpath
2 |
3 |
4 | [](https://cljdoc.org/d/com.lambdaisland/classpath) [](https://clojars.org/com.lambdaisland/classpath)
5 |
6 |
7 | Experimental utilities for dealing with "the classpath", and dynamically loading libraries.
8 |
9 | Blog post: [The Classpath is a Lie](https://lambdaisland.com/blog/2021-08-25-classpath-is-a-lie)
10 |
11 | ## With thanks to Nextjournal!
12 |
13 | [Nextjournal](https://nextjournal.com/) provided the incentive and financial
14 | support to dive into this. Many thanks to them for pushing to move the needle on
15 | dev experience.
16 |
17 | ## Usage
18 |
19 | ### Watch `deps.edn`
20 |
21 | ```clojure
22 | (require '[lambdaisland.classpath.watch-deps :as watch-deps])
23 |
24 | (watch-deps/start! {:aliases [:dev :test]})
25 | ```
26 |
27 | Whenever you change `deps.edn` this will pick up any extra libraries or changed
28 | versions, and add them to the classpath.
29 |
30 | Caveat: we can only *add* to the classpath, any dependencies that were present
31 | when the app started will remain accessible.
32 |
33 | You can pass the option `:include-local-roots? true` to also watch any
34 | `deps.edn` of projects that are referenced via `:local/root` in your project's
35 | `deps.edn`
36 |
37 | ### Classpath inspection and manipulation
38 |
39 | ```clojure
40 | (require '[lambdaisland.classpath :as licp])
41 | ```
42 |
43 | Get the current chain of classloaders
44 |
45 | ```clojure
46 | (licp/classloader-chain)
47 | ```
48 |
49 | Also see which entries each loader searches
50 |
51 | ```clojure
52 | (licp/classpath-chain)
53 | ```
54 |
55 | Update a gitlib in `deps.edn` to the latest `:git/sha` in `main` or in the specified `:git/branch`
56 |
57 | ```clojure
58 | (licp/git-pull-lib 'com.lambdaisland/ornament)
59 | ```
60 |
61 | Add/override the classpath based on the current deps.edn.
62 |
63 | ```clojure
64 | (licp/update-classpath!
65 | '{:aliases [:dev :test :licp]
66 | :extra {:deps {com.lambdaisland/webstuff {:local/root "/home/arne/github/lambdaisland/webstuff"}}}})
67 | ```
68 |
69 | Access specific class loaders
70 |
71 | ```clojure
72 | (licp/context-classloader)
73 | (licp/base-loader)
74 | (licp/root-loader)
75 | (licp/compiler-loader)
76 | ```
77 |
78 |
79 | ## Lambda Island Open Source
80 |
81 |
82 |
83 |
84 |
85 | classpath is part of a growing collection of quality Clojure libraries created and maintained
86 | by the fine folks at [Gaiwan](https://gaiwan.co).
87 |
88 | Pay it forward by [becoming a backer on our Open Collective](http://opencollective.com/lambda-island),
89 | so that we may continue to enjoy a thriving Clojure ecosystem.
90 |
91 | You can find an overview of our projects at [lambdaisland/open-source](https://github.com/lambdaisland/open-source).
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 | ## Contributing
100 |
101 | Everyone has a right to submit patches to classpath, and thus become a contributor.
102 |
103 | Contributors MUST
104 |
105 | - adhere to the [LambdaIsland Clojure Style Guide](https://nextjournal.com/lambdaisland/clojure-style-guide)
106 | - write patches that solve a problem. Start by stating the problem, then supply a minimal solution. `*`
107 | - agree to license their contributions as MPL 2.0.
108 | - not break the contract with downstream consumers. `**`
109 | - not break the tests.
110 |
111 | Contributors SHOULD
112 |
113 | - update the CHANGELOG and README.
114 | - add tests for new functionality.
115 |
116 | If you submit a pull request that adheres to these rules, then it will almost
117 | certainly be merged immediately. However some things may require more
118 | consideration. If you add new dependencies, or significantly increase the API
119 | surface, then we need to decide if these changes are in line with the project's
120 | goals. In this case you can start by [writing a pitch](https://nextjournal.com/lambdaisland/pitch-template),
121 | and collecting feedback on it.
122 |
123 | `*` This goes for features too, a feature needs to solve a problem. State the problem it solves, then supply a minimal solution.
124 |
125 | `**` As long as this project has not seen a public release (i.e. is not on Clojars)
126 | we may still consider making breaking changes, if there is consensus that the
127 | changes are justified.
128 |
129 |
130 |
131 | ## License
132 |
133 | Copyright © 2021 Arne Brasseur and Contributors
134 |
135 | Licensed under the term of the Mozilla Public License 2.0, see LICENSE.
136 |
137 |
--------------------------------------------------------------------------------
/bb.edn:
--------------------------------------------------------------------------------
1 | {:deps
2 | {lambdaisland/open-source {:git/url "https://github.com/lambdaisland/open-source"
3 | :sha "7ce125cbd14888590742da7ab3b6be9bba46fc7a"
4 | #_#_:local/root "../open-source"}}}
5 |
--------------------------------------------------------------------------------
/bin/proj:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bb
2 |
3 | (ns proj
4 | (:require [lioss.main :as lioss]))
5 |
6 | (lioss/main
7 | {:license :mpl
8 | :inception-year 2021
9 | :description "Classpath utilities"
10 | :group-id "com.lambdaisland"})
11 |
12 | ;; Local Variables:
13 | ;; mode:clojure
14 | ;; End:
15 |
--------------------------------------------------------------------------------
/deps.edn:
--------------------------------------------------------------------------------
1 | {:paths ["src"]
2 |
3 | :deps
4 | {rewrite-clj/rewrite-clj {:mvn/version "1.1.49"}
5 | org.clojure/tools.deps.alpha {:mvn/version "0.14.1222"}
6 | com.lambdaisland/shellutils {:mvn/version "0.3.20"}
7 | org.clojure/java.classpath {:mvn/version "1.0.0"}
8 | com.nextjournal/beholder {:mvn/version "1.0.2"}
9 | io.methvin/directory-watcher {:mvn/version "0.18.0"}}}
10 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 | com.lambdaisland
5 | classpath
6 | 0.6.58
7 | classpath
8 | Classpath utilities
9 | https://github.com/lambdaisland/classpath
10 | 2021
11 |
12 | Lambda Island
13 | https://lambdaisland.com
14 |
15 |
16 | UTF-8
17 |
18 |
19 |
20 | MPL-2.0
21 | https://www.mozilla.org/media/MPL/2.0/index.txt
22 |
23 |
24 |
25 | https://github.com/lambdaisland/classpath
26 | scm:git:git://github.com/lambdaisland/classpath.git
27 | scm:git:ssh://git@github.com/lambdaisland/classpath.git
28 | cae7179067faeb0a97b661737034fe86c3b6bcd8
29 |
30 |
31 |
32 | rewrite-clj
33 | rewrite-clj
34 | 1.1.49
35 |
36 |
37 | org.clojure
38 | tools.deps.alpha
39 | 0.14.1222
40 |
41 |
42 | com.lambdaisland
43 | shellutils
44 | 0.3.20
45 |
46 |
47 | org.clojure
48 | java.classpath
49 | 1.0.0
50 |
51 |
52 | com.nextjournal
53 | beholder
54 | 1.0.2
55 |
56 |
57 | io.methvin
58 | directory-watcher
59 | 0.18.0
60 |
61 |
62 |
63 | src
64 |
65 |
66 | src
67 |
68 |
69 |
70 |
71 | org.apache.maven.plugins
72 | maven-compiler-plugin
73 | 3.8.1
74 |
75 | 1.8
76 | 1.8
77 |
78 |
79 |
80 | org.apache.maven.plugins
81 | maven-jar-plugin
82 | 3.2.0
83 |
84 |
85 |
86 | cae7179067faeb0a97b661737034fe86c3b6bcd8
87 |
88 |
89 |
90 |
91 |
92 | org.apache.maven.plugins
93 | maven-gpg-plugin
94 | 1.6
95 |
96 |
97 | sign-artifacts
98 | verify
99 |
100 | sign
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | clojars
110 | https://repo.clojars.org/
111 |
112 |
113 |
114 |
115 | clojars
116 | Clojars repository
117 | https://clojars.org/repo
118 |
119 |
120 |
--------------------------------------------------------------------------------
/repl_session/poke.clj:
--------------------------------------------------------------------------------
1 | (ns poke
2 | (:require [lambdaisland.classpath :as licp]
3 | [clojure.string :as str]
4 | [clojure.java.io :as io])
5 | (:import clojure.lang.DynamicClassLoader))
6 |
7 | (licp/classpath-chain)
8 | (licp/classloader-chain)
9 |
10 | (licp/update-classpath! {:extra {:paths ["repl_session/"]}})
11 |
12 | (licp/debug! false)
13 |
14 | (licp/resources "poke.clj")
15 |
16 | (map (juxt licp/cl-id #(.getResource % "poke.clj"))
17 | (licp/classloader-chain))
18 |
19 | (+ 1 1)
20 |
21 | (defn has-dcl?
22 | "Is this classloader or any of its ancestors a DynamicClassLoader?"
23 | ^DynamicClassLoader
24 | [^ClassLoader cl]
25 | (loop [loader cl]
26 | (when loader
27 | (if (instance? DynamicClassLoader loader)
28 | true
29 | (recur (.getParent loader))))))
30 |
31 | (has-dcl? (licp/parent (licp/root-loader)))
32 |
33 | (keep (fn [{:local/keys [root]}]
34 | (when (and root (.exists (io/file root "shadow-cljs.edn")))
35 | root))
36 | (vals (:libs (licp/read-basis))))
37 |
38 | (io/resource "public/index.html")
39 |
--------------------------------------------------------------------------------
/src/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lambdaisland/classpath/593d09e88af4a38ee328ba37b3e6fcf43cc96a49/src/.gitkeep
--------------------------------------------------------------------------------
/src/lambdaisland/classpath.clj:
--------------------------------------------------------------------------------
1 | (ns lambdaisland.classpath
2 | (:require [rewrite-clj.zip :as z]
3 | [clojure.string :as str]
4 | [clojure.tools.deps.alpha :as deps]
5 | [clojure.tools.gitlibs :as gitlibs]
6 | [clojure.tools.gitlibs.impl :as gitlibs-impl]
7 | [clojure.java.classpath :as cp]
8 | [clojure.java.io :as io]
9 | [lambdaisland.shellutils :as shellutils])
10 | (:import (java.util.jar JarFile JarEntry)
11 | (java.io File)
12 | (java.lang ClassLoader)
13 | (java.net URL URLClassLoader)
14 | (clojure.lang DynamicClassLoader)))
15 |
16 | #_(set! *warn-on-reflection* true)
17 |
18 | (defn read-basis
19 | "Read the basis (extended deps.edn) that Clojure started with, using the
20 | `clojure.basis` system property."
21 | []
22 | (when-let [f (io/file (System/getProperty "clojure.basis"))]
23 | (if (and f (.exists f))
24 | (deps/slurp-deps f)
25 | (throw (IllegalArgumentException. "No basis declared in clojure.basis system property")))))
26 |
27 | (defn git-pull-lib* [loc lib]
28 | (let [coords (z/sexpr loc)
29 | git-url (:git/url coords)
30 | git-dir (gitlibs-impl/git-dir git-url)
31 | _ (when-not (.isDirectory (io/file git-dir))
32 | (gitlibs-impl/git-clone-bare git-url git-dir))
33 | _ (gitlibs-impl/git-fetch git-dir)
34 | sha (some #(and % (gitlibs-impl/git-rev-parse (str git-dir) %))
35 | [(:git/branch coords)
36 | "main"
37 | "master"])]
38 | (z/assoc loc :git/sha sha)))
39 |
40 | (defn git-pull-lib
41 | "Update the :git/sha in deps.edn for a given library to the latest sha in a branch
42 |
43 | Uses `:git/branch` defaulting to `main`"
44 | ([lib]
45 | (git-pull-lib "deps.edn" lib))
46 | ([deps-file lib]
47 | (let [loc (z/of-file deps-file)]
48 | (spit deps-file
49 | (z/root-string
50 | (as-> loc $
51 | (if-let [loc (z/get (z/get $ :deps) lib)]
52 | (z/up (z/up (git-pull-lib* loc lib)))
53 | $)
54 |
55 | (if-let [aliases (some-> $ (z/get :aliases) z/sexpr)]
56 | (reduce (fn [loc alias]
57 | (reduce
58 | (fn [loc dep-type]
59 | (if-let [loc (some-> loc
60 | (z/get alias)
61 | (z/get dep-type)
62 | (z/get lib))]
63 | (z/up (z/up (z/up (git-pull-lib* loc lib))))
64 | loc))
65 | loc
66 | [:extra-deps :override-deps :default-deps]))
67 | $
68 | (keys aliases))
69 | $)))))))
70 |
71 | (defn classpath
72 | "clojure.java.classpath does not play well with the post-Java 9 application
73 | class loader, which is no longer a URLClassLoader, even though ostensibly it
74 | tries to cater for this, but in practice if any URLClassLoader or
75 | DynamicClassLoader higher in the chain contains a non-empty list of URLs, then
76 | this shadows the system classpath."
77 | []
78 | (distinct (concat (cp/classpath) (cp/system-classpath))))
79 |
80 | (defn context-classloader
81 | "Get the context classloader for the current thread"
82 | ^ClassLoader
83 | ([]
84 | (context-classloader (Thread/currentThread)))
85 | ([^Thread thread]
86 | (.getContextClassLoader thread)))
87 |
88 | (defn base-loader
89 | "Get the loader that Clojure uses internally to load files
90 |
91 | This is usually the current context classloader, but can also be
92 | clojure.lang.Compiler/LOADER."
93 | ^ClassLoader []
94 | (clojure.lang.RT/baseLoader))
95 |
96 | (defn cl-name
97 | "Get a classloaders's defined name"
98 | [^ClassLoader cl]
99 | (.getName cl))
100 |
101 | (defn cl-id
102 | "return a symbol identifying the cl, mainly meant for concise printing"
103 | [^ClassLoader cl]
104 | (if cl
105 | (symbol
106 | (or (.getName cl)
107 | (str cl)))
108 | 'boot))
109 |
110 | (defn dynamic-classloader?
111 | "Is the given classloader a [[clojure.lang.DynamicClassLoader]]"
112 | [cl]
113 | (instance? clojure.lang.DynamicClassLoader cl))
114 |
115 | (defn priority-classloader?
116 | "Is the given classloader a [[priority-classloader]]"
117 | [cl]
118 | (when-let [name (and cl (cl-name cl))]
119 | (str/starts-with? name "lambdaisland/priority-classloader")))
120 |
121 | (defn root-loader
122 | "Find the bottom-most DynamicClassLoader in the chain of parent classloaders"
123 | ^DynamicClassLoader
124 | ([]
125 | (root-loader (base-loader)))
126 | ([^ClassLoader cl]
127 | (when cl
128 | (loop [loader cl]
129 | (let [parent (.getParent loader)]
130 | (cond
131 | (or (dynamic-classloader? parent)
132 | (priority-classloader? parent))
133 | (recur parent)
134 |
135 | (or (dynamic-classloader? loader)
136 | (priority-classloader? loader))
137 | loader))))))
138 |
139 | (defn app-loader
140 | "Get the application (aka system) classloader"
141 | ^ClassLoader []
142 | (ClassLoader/getSystemClassLoader))
143 |
144 | (defn platform-loader
145 | "Get the platform classloader"
146 | ^ClassLoader []
147 | (ClassLoader/getPlatformClassLoader))
148 |
149 | (defn compiler-loader
150 | "Get the clojure.lang.Compiler/LOADER, if set
151 |
152 | This is the loader Clojure uses to load code with, if the var is set. If not
153 | it falls back to the context classloader."
154 | []
155 | @clojure.lang.Compiler/LOADER)
156 |
157 | (defn dynamic-classloader
158 | "Construct a new DynamicClassLoader"
159 | ^DynamicClassLoader
160 | [^ClassLoader parent]
161 | (clojure.lang.DynamicClassLoader. parent))
162 |
163 | (defn parent
164 | "Get the parent classloader"
165 | ^ClassLoader [^ClassLoader cl]
166 | (.getParent cl))
167 |
168 | (defn classpath-directories
169 | "Returns a sequence of File objects for the directories on classpath."
170 | []
171 | (filter #(.isDirectory ^File %) (classpath)))
172 |
173 | (defn classpath-jarfiles
174 | "Returns a sequence of JarFile objects for the JAR files on classpath."
175 | []
176 | (map #(JarFile. ^File %) (filter cp/jar-file? (classpath))))
177 |
178 | (defn find-resources
179 | "Scan 'the classpath' for resources that match the given regex."
180 | [regex]
181 | ;; FIXME currently jar entries always come first in the result, this should be
182 | ;; in classpath order.
183 | (concat
184 | (sequence
185 | (comp
186 | (mapcat #(iterator-seq (.entries ^JarFile %)))
187 | (map #(.getName ^JarEntry %))
188 | (filter #(re-find regex %)))
189 | (classpath-jarfiles))
190 |
191 | (sequence
192 | (comp
193 | (mapcat file-seq)
194 | (map str)
195 | (filter #(re-find regex %)))
196 | (classpath-directories))))
197 |
198 | (defn file->ns-name
199 | "Get the ns name for a given clj file name"
200 | [filename]
201 | (-> filename
202 | (str/replace #"\.clj$" "")
203 | (str/replace #"/" ".")
204 | (str/replace #"_" "-")))
205 |
206 | (defn classloader-chain
207 | "Get the chain of parent classloaders, all the way to the system AppClassLoader
208 | and PlatformClassLoader."
209 | ([]
210 | (classloader-chain (base-loader)))
211 | ([cl]
212 | (take-while identity (iterate parent cl))))
213 |
214 | (defn classpath-chain
215 | "Return a list of classloader names, and the URLs they have on their classpath
216 |
217 | Mainly meant for inspecting the current state of things."
218 | ([]
219 | (classpath-chain (context-classloader)))
220 | ([cl]
221 | (for [^ClassLoader cl (classloader-chain cl)]
222 | [(cl-id cl)
223 | (map str (cond
224 | (instance? URLClassLoader cl)
225 | (.getURLs ^URLClassLoader cl)
226 | (= "app" (.getName cl))
227 | (cp/system-classpath)))])))
228 |
229 | (defn resources
230 | "The plural of [[clojure.java.io/resource]], find all resources with the given
231 | name on the classpath.
232 |
233 | Useful for checking shadowing issues, in case a library ended up on the
234 | classpath multiple times."
235 | ([name]
236 | (resources (base-loader) name))
237 | ([^ClassLoader cl name]
238 | (enumeration-seq (.getResources cl name))))
239 |
240 | (defn priority-classloader
241 | "A modified URLClassloader
242 |
243 | It will give precedence to its own URLs over its parents, then to whatever
244 | resources its immediate parent returns, and only then passing the request up
245 | the chain, which will then proceed with the bottom most classloaders (Boot,
246 | then Platform, then App).
247 |
248 | We install this as the child of the bottom most
249 | clojure.lang.DynamicClassloader that we find.
250 |
251 | The logic here relies on the fact that DynamicClassLoader or its parent
252 | URLClassLoader do not implement the `getResource`/`getResources` methods, they
253 | rely on the parent implementation in ClassLoader, which gives precedence to
254 | ancestors, before proceeding to call `findResource`/`findResources`, which
255 | URLClassLoader/DynamicClassloader do implement. This classloader reverses that
256 | logic, so that the system classloader doesn't shadow our own classpath
257 | entries."
258 | [cl urls]
259 | (let [parent-loader (parent cl)]
260 | (proxy [URLClassLoader] [^String (str "lambdaisland/"
261 | (gensym "priority-classloader"))
262 | ^"[Ljava.net.URL;" (into-array URL urls)
263 | ^ClassLoader cl]
264 | (getResource [name]
265 | ;; `cl` is assumed to be the bottom-most DynamcClassLoader, which is
266 | ;; sitting directly above the application classloader
267 | ;;
268 | ;; - priority-classloader
269 | ;; - DynamicClassLoader
270 | ;; - app classloader
271 | ;;
272 | ;; The normal lookup order is bottom to top, we reverse that here by
273 | ;; first checking our own classpath, then the DCL, and only then handing
274 | ;; it to the app cl, which can further traverse down the chain.
275 | (or (.findResource this name)
276 | ;; reflection warning because findResource is protected, but we're a
277 | ;; subclass so it seems to be ok?
278 | (.findResource cl name)
279 | (.getResource parent-loader name)))
280 | (getResources [name]
281 | (java.util.Collections/enumeration
282 | (distinct
283 | (mapcat
284 | enumeration-seq
285 | ;; reflection warning because findResource is protected, but we're a
286 | ;; subclass so it seems to be ok?
287 | [(.findResources this name)
288 | (.findResources cl name)
289 | (.getResources parent-loader name)])))))))
290 |
291 | (def fg-red "\033[0;31m")
292 | (def fg-green "\033[0;32m")
293 | (def fg-yellow "\033[0;33m")
294 | (def fg-blue "\033[0;34m")
295 | (def fg-reset "\033[0m")
296 |
297 | (defn debug-context-classloader* [ns meta-form ^Thread thread cl]
298 | (let [old-cl (context-classloader thread)
299 | chain (classloader-chain cl)
300 | old-chain (classloader-chain old-cl)
301 | merge-base (some (set old-chain) chain)
302 | short-id #(-> (cl-id %)
303 | (str/replace #".*@" "")
304 | (str/replace #".*/" ""))
305 | sym (symbol (str "cl-" (short-id cl)))]
306 | (intern 'user sym (constantly cl))
307 | (println (str "[" (.getName thread) "]")
308 | (str fg-yellow ns ":" (:line meta-form) ":" (:column meta-form)
309 | fg-blue " (user/" sym ")" fg-reset))
310 | (if (= cl old-cl)
311 | (do
312 | (println (str fg-yellow " No-op" fg-reset)))
313 | (do
314 | (run!
315 | #(println fg-red " - " (cl-id %) '-> (short-id (parent %)) fg-reset)
316 | (take-while #(not= merge-base %) old-chain))
317 | (run!
318 | #(println fg-green " + " (cl-id %) '-> (short-id (parent %)) fg-reset)
319 | (take-while #(not= merge-base %) chain))))
320 | (.setContextClassLoader thread cl)))
321 |
322 | (defmacro debug-context-classloader
323 | "Replace calls to `.setContextClassloader` with this to get insights into
324 | who/what/where/when/how is changing the classloader"
325 | [thread cl]
326 | `(debug-context-classloader* '~(ns-name *ns*) ~(meta &form) ~thread ~cl))
327 |
328 | (defn debug? []
329 | (= "true" (System/getProperty "lambdaisland.classpath.debug")))
330 |
331 | (defn debug!
332 | ([]
333 | (debug! true))
334 | ([enable?]
335 | (System/setProperty "lambdaisland.classpath.debug" (str enable?))))
336 |
337 | (defn ensure-trailing-slash
338 | "URLClassPath looks for a trailing slash to determine whether something is a
339 | directory instead of a jar, so add trailing slashes to everything that doesn't
340 | look like a JAR."
341 | [^String path]
342 | (cond
343 | (or (.endsWith path ".jar") (.endsWith path "/"))
344 | path
345 | (.isDirectory (io/file path))
346 | (str path "/")
347 | :else
348 | path))
349 |
350 | (defn install-priority-loader!
351 | "Install the new priority loader as immediate parent of the bottom-most
352 | DynamicClassloader, discarding any further descendants. After this the chain is
353 |
354 | [priority-classloader
355 | DynamicClassLoader
356 | AppClassLoader
357 | PlatformClassLoader]
358 |
359 | Do this for every thread that has a DynamicClassLoader as the context
360 | classloader, or any of its parents.
361 |
362 | We need to do this from a separate thread, hence the `future` call, because
363 | nREPL's interruptible-eval resets the context-classloader at the end of the
364 | evaluation, so this needs to happen after that has happened.
365 |
366 | Start the JVM with `-Dlambdaisland.classpath.debug=true` to get debugging
367 | output.
368 | "
369 | ([]
370 | (install-priority-loader! []))
371 | ([paths]
372 | (when (debug?)
373 | (println "Installing priority-classloader")
374 | (run! #(println "-" %) paths))
375 | (let [urls (map #(URL. (str "file:" (ensure-trailing-slash %))) paths)
376 | current-thread (Thread/currentThread)
377 | dyn-cl (or (root-loader (context-classloader current-thread))
378 | (clojure.lang.DynamicClassLoader. (app-loader)))
379 | new-loader (priority-classloader dyn-cl urls)]
380 | ;; Install a priority-classloader in every thread that currently has a
381 | ;; DynamicClassLoader
382 | (future
383 | (try
384 | (Thread/sleep 100)
385 | (doseq [^Thread thread (.keySet (Thread/getAllStackTraces))
386 | ;; InnocuousThread#setContextClassLoader throws
387 | ;; SecurityException, so we skip those. We can't do an
388 | ;; `instance?` check because the module jdk.internal.misc is
389 | ;; private.
390 | :when (not (#{"class jdk.internal.misc.CarrierThread"
391 | "class jdk.internal.misc.InnocuousThread"} (str (class thread))))
392 | ;; Install the new loader in every thread that has a Clojure
393 | ;; loader, and always in the thread this is invoked in, even if
394 | ;; for some reason it does not yet have a Clojure loader
395 | ;;
396 | ;; We also consider threads that currently have the application
397 | ;; loader set, this includes threads created by
398 | ;; futures/agents (clojure-agent-send-off-pool-*), before
399 | ;; `clojure.main/repl` installed its DynamicClassLoader
400 | :when (or (= thread current-thread)
401 | (root-loader (context-classloader thread))
402 | (= (app-loader) (context-classloader thread)))]
403 | (if (debug?)
404 | (debug-context-classloader thread new-loader)
405 | (.setContextClassLoader thread new-loader)))
406 | (catch Exception e
407 | (println "Error in" `install-priority-loader! e)
408 | (.printStackTrace e))))
409 |
410 | ;; Force orchard to use "our" classloader. This is a bit of nuclear option,
411 | ;; if we can clean up some of nREPLs classloader handling this should not
412 | ;; be necessary.
413 | #_(doseq [filename (find-resources #"orchard.*java/classpath.clj")]
414 | (try
415 | (alter-var-root
416 | (requiring-resolve (symbol (file->ns-name filename) "context-classloader"))
417 | (constantly (constantly new-loader)))
418 | (catch Exception e))))))
419 |
420 | (defn update-classpath!
421 | "Use the given options to construct a basis (see [[deps/create-basis]]), then
422 | add any classpath-roots that aren't part of the system classpath yet to the
423 | classpath, by installing an extra classloader over Clojure's
424 | DynamicClassloader which takes precedence.
425 |
426 | This is the closest we can get to \"replacing\" the classpath. We can't remove
427 | any entries from the system classpath (the classpath the JVM booted with), but
428 | we can make sure any extra entries get precedence."
429 | [basis-opts]
430 | (install-priority-loader!
431 | (remove (set (map str (cp/system-classpath)))
432 | (:classpath-roots (deps/create-basis basis-opts)))))
433 |
434 |
435 |
436 | (comment
437 | (git-pull-lib 'com.lambdaisland/webstuff)
438 |
439 | (update-classpath!
440 | '{:aliases [:dev :test :licp]
441 | :extra {:deps {com.lambdaisland/webstuff {:local/root "/home/arne/github/lambdaisland/webstuff"}}}})
442 |
443 | (classpath-chain)
444 | (resources "lambdaisland/webstuff/http.clj")
445 | (io/resource "lambdaisland/webstuff/http.clj")
446 |
447 | (classloader-chain)
448 | (classpath-chain)
449 |
450 | (io/resource "clojure/main.class")
451 | ;;=> #object[java.net.URL 0x3237dfe5 "jar:file:/home/arne/.m2/repository/org/clojure/clojure/1.10.3/clojure-1.10.3.jar!/clojure/main.class"]
452 |
453 | (.getResource ^ClassLoader loader "clojure/main.class")
454 |
455 | (defn xxx [])
456 |
457 | (.loadClass (clojure.lang.RT/baseLoader) "user$xxx")
458 | ;; user$xxx
459 |
460 | (.loadClass (ClassLoader/getPlatformClassLoader) "lambdaisland.classpath$xxx")
461 | ;; => java.lang.ClassNotFoundException
462 |
463 |
464 | (group-by second
465 | (map (juxt #(.getName %) #(some-> (.getClassLoader %) .getName))
466 | (.modules (java.lang.ModuleLayer/boot))))
467 |
468 |
469 | )
470 |
--------------------------------------------------------------------------------
/src/lambdaisland/classpath/watch_deps.clj:
--------------------------------------------------------------------------------
1 | (ns lambdaisland.classpath.watch-deps
2 | "Watch deps.edn for changes"
3 | (:require [clojure.java.classpath :as cp]
4 | [clojure.string :as str]
5 | [clojure.java.io :as io]
6 | [clojure.tools.deps.alpha :as deps]
7 | [lambdaisland.classpath :as licp]
8 | [nextjournal.beholder :as beholder])
9 | (:import java.util.regex.Pattern
10 | java.nio.file.LinkOption
11 | java.nio.file.Paths
12 | java.nio.file.Path))
13 |
14 | (def watcher (atom nil))
15 |
16 | (defn path ^Path [root & args]
17 | (if (and (instance? Path root) (not (seq args)))
18 | root
19 | (Paths/get (str root) (into-array String args))))
20 |
21 | (defn canonical-path [p]
22 | (.toRealPath (path p) (into-array LinkOption [])))
23 |
24 | (defn parent-path [p]
25 | (.getParent (path p)))
26 |
27 | (def process-root-path (canonical-path "."))
28 |
29 | (defn basis
30 | "Default function for (re-)computing the tools.deps basis, which we then use to
31 | update the classpath. Delegates to [[deps/create-basis]], with one addition:
32 | if you include an `:extra` option which points at a file (string), then we
33 | also look in that file for a `:lambdaisland.classpath/aliases`, which are
34 | additional alias keys to load. This allows having a `deps.local.edn`, where
35 | you can change the aliases in use without restarting."
36 | [opts]
37 | (if-let [f (:basis-fn opts)]
38 | (f opts)
39 | (deps/create-basis
40 | (if (string? (:extra opts))
41 | (update opts :aliases into (:lambdaisland.classpath/aliases
42 | (deps/slurp-deps (io/file (:extra opts)))))
43 | opts))))
44 |
45 | (defn- on-event [deps-paths opts {:keys [type path]}]
46 | (locking watcher
47 | (when (and (= :modify type)
48 | ;; Before we used "." as the watch path, resulting in a
49 | ;; difference between mac, where the path would look like this
50 | ;; `/Users/x/project/./deps.edn`, vs Linux where the path would
51 | ;; look like this `./deps.edn`.
52 | ;;
53 | ;; We now turn `"."` into a canonical path before starting the
54 | ;; watcher, which means we get fully qualified filenames for both
55 | ;; in this equality check.
56 | (some #{path} deps-paths))
57 | (try
58 | (println "[watch-deps] ✨ Reloading"
59 | (str (.relativize process-root-path path))
60 | "✨")
61 | (let [added-paths (remove (set (map str (cp/system-classpath)))
62 | (:classpath-roots (basis opts)))]
63 | (when (not (seq added-paths))
64 | (println "[watch-deps] No new libraries to add."))
65 | (doseq [path added-paths]
66 | (println "[watch-deps] +" (str/replace path #"^.*/\.m2/repository/" "")))
67 | (licp/install-priority-loader! added-paths))
68 | (catch Exception e
69 | (println "[watch-deps] Error while reloading deps.edn")
70 | (println e))))))
71 |
72 | (defn start!
73 | "Start a file system watcher to pick up changes in `deps.edn'
74 |
75 | Options are passed on to tools.deps when creating the basis, you probably want
76 | to at least put the `:aliases` you need in there.
77 |
78 | ```
79 | (start! {:aliases [:dev :test]})
80 | ```
81 | "
82 | [opts]
83 | (swap! watcher
84 | (fn [w]
85 | (when w
86 | (println "Stopping existing `deps.edn' watchers")
87 | (run! beholder/stop w))
88 | (let [basis (basis opts)
89 | deps-paths (cond-> [(path process-root-path "deps.edn")]
90 | (:include-local-roots? opts)
91 | (into (->> (vals (:libs basis))
92 | (keep :local/root)
93 | (map canonical-path)
94 | (map #(path % "deps.edn"))))
95 | (string? (:extra opts))
96 | (conj (canonical-path (:extra opts)))
97 | :always
98 | (concat (:watch-paths opts)))
99 | roots (group-by parent-path deps-paths)]
100 | (doall
101 | (for [[root deps-paths] roots]
102 | (beholder/watch
103 | (partial #'on-event deps-paths opts)
104 | (str root))))))))
105 |
106 | (defn stop!
107 | "Stop a previously started watcher"
108 | [& _]
109 | (swap! watcher
110 | (fn [w]
111 | (run! beholder/stop w)
112 | nil)))
113 |
114 | (comment
115 | (start! {:aliases [:dev]
116 | :extra "deps.local.edn"})
117 |
118 | (stop!)
119 | (deps/create-basis {:aliases [:backend]
120 | :extra '{cider/cider-nrepl #:mvn{:version "0.28.5"}
121 | refactor-nrepl/refactor-nrepl #:mvn{:version "3.5.2"}}})
122 | (remove (set (map str (cp/system-classpath)))
123 | (:classpath-roots (deps/create-basis opts))))
124 |
--------------------------------------------------------------------------------