Here is the stack trace for the question above ⬆️
Hey all, I’m toying around with Clojure and have some grasp of the syntax and how to build out the project I want to build, but as I transition to getting my project structure in place I’m a little lost. I’m a infrastructure/build tools guy at heart so I always take the time to set up my projects in a way that makes sense for me from a devex perspective have noticed one thing that seem to be missing from Clojure: locked dependencies. Most languages encourage you to lock dependencies, and I can’t help but feel like it’s an obvious best practice - you should know exactly what you’re about to download and use in your project at any given time. Is my google/chatgpt/stackoverflow-fu that bad, or is there really no way to work with locked dependency versions in Clojure?
Invalid versions is cool, don’t know if I need these but I’ll definitely keep them in my back pocket. Thanks!
What does "lock dependencies" mean to you?
Yeah, great question! Maybe I’ll edit, but something that guarantees exactly what all the direct and transitive dependencies that will be included in the build will be.
The majority of clojure dependencies are consumed via maven, which requires specifying a dependency version(there are some pseudo versions for things like LATEST, but use of that is rare, and bad practice)
Depending on which build tool you use there is a specific deterministic algorithm that is used to resolve transient dependencies conflicts
The guarantee that I want is that the transitive dependency that I download and use when I first add a direct dependency is the same that a new person to my project would get in a month
I’m currently using deps.edn for my project.
That is how it is supposed to work
Maybe Clojure projects are different, but when I’ve run Kotlin or Java projects, dependencies tend to just recommend a transitive dependency version, so you can get different versions if a new one is released after you add the dependency but before the next person resolves theirs
Technically, if the maven repo you use allows artifacts to be overwritten that can break that
That doesn't sound correct
Maven is primarily a java build tool, so if you were using it you should only get the version you ask for
Maybe you were using gradle or some other build tool
Oh is Gradle really that different? I thought it used the Maven resolver
The clojure ecosystem is to some degree built around the idea that behavior is distasteful
So you don't have separate notions of lockfiles
You specify what you want, and that is what you get
What happens when 2 direct dependencies disagree on the version of transitive dependency? In Java a very common shared dependency would be Jackson - I think the work project I work on has 5 different suggested versions of Jackson to resolve (but this is Gradle, so potentially caveats)
This would be a cool mindset change if I didn’t have to think about it
It depends, the lein uses mavens resolution strategy to pick a version, but tools-deps uses a slightly different algorithm. They are both deterministic, and both will result the same version chosen every time. The only way to get something new is if you change something, add a new dependency or change a version of a dependency
Oh that’s amazing then, thank you!
And if I recall for both something you explicitly choose always wins over anything from transient dependencies
https://youtu.be/sStlTye-Kjk?si=AZqN5Bks6dz9TnpZ might be an interesting watch, it is tools-deps focused and discusses some other stuff like git dependencies, but even for that tools-deps forces you to depend on a specific sha, you cannot depend on something that could change (tag, branch, etc)
For different levels of paranoia personal comfort, here's some extra things you may be interested in (just to add, not contradict what @hiredman has already pointed out):
• clj -A:with:your:aliases -Stree will give you a nice full dependency list of transitive deps. Feel free to scan it or even better - with some basic text scripting/manipulation, you can automate a check for new "unexpected" deps or potential conflicts
• From https://clojure.org/reference/deps_edn be aware of:
◦ :exclusions
◦ :override-deps
◦ :classpath-overrides
• As was mentioned, deps-tools will use any version of a dependency in your deps.edn (since it's higher in the tree) OVER the one asked for via a transitive dep. So one way to override any transitive dep is to ensure it's directly in your deps (or one of your aliases that is always pulled in).
• For the truly paranoid, you can actually have your main deps.edn define all the deps (use -Stree to ensure you get them all) with an invalid target (eg. {:mvn/version "INVALID"}. Running your build will crash (red test) and you can then explicitly add a proper version e.g to an alias :versions (green test). From this point forward, any transitive dependency pulled in will get the version in :versions (whether the libraries works with that version is now your problem, but at least now it's explicit). Rinse and repeat and you will have essentially built up a "lockfile" of all your transitive deps in your project.
I think everyone would caution this is probably over kill, but I think these are useful tricks to have in your backpocket (and have helped me debug some builds).
I could use help interpreting this stacktrace in *cider-error*. How would I know which such-that predicate messed up in my code? It doesn't seem to say
For future reference, please attach long code listings as snippets. Slack collapses snippets by default, and it makes it much more usable than having to scroll multiple pages even on a large monitor, let alone on a mobile device.
If you search for such-that in #clojure-spec or #test-check you should find some discussion. I don't have a way off the top of my head, but any s/and spec without a custom generator has a good chance of causing this