Over past few years, I’ve spent a lot of time thinking about dependency management, usually in the context of NHibernate and Castle Windsor. The truth is that the problems these projects have are the problems any sufficiently complex open source project has. So, having been playing with Clojure for several days I’ve got some thoughts about how the other half lives:
For the most part, the JVM has been phenomenally stable for the last few years, so there’s no analogue of the multiplicity of versions of .NET and Silverlight just isn’t a problem for them. Another way of saying this, of course, is that the JVM is atrophying. Clojure doesn’t support conditional compilation and I don’t think anyone’s come up with a way of compiling the same code base against Clojure and ClojureCLR (yet). It’ll be interesting to see how that’s addressed. Equally, there’s no particularly sensible way of compiling the same code base against different versions of another library.
JARs and Assemblies are roughly analogous, but JARs don’t contain JVM-level versioning information. This means you don’t get any of those “version doesn’t match” compilation errors like you get with .NET. However, that doesn’t mean the problem isn’t there, just that you can’t see it. If the versions really don’t match, your code will break (hopefully visibly, but that’s not guaranteed) and you’ll have no idea what you need to do to fix it.
So, how do they handle compatibility between versions? Well, to be frank, they don’t. Let’s give you the worst case scenario I recently encountered: If you want to use Circumspec from trunk, you’ll need to be using its version of Clojure. That’s not too bad, because of Leiningen’s dependency resolution. However, in order to be able to run “lein repl” you’ll need Leiningen to be running (approximately) the same version of Clojure as Circumspec.
Now for the good news: Leiningen’s version handling (which is Maven and Ivy’s) beats anything in the .NET space. You just specify something like [jline “0.9.94”] to declare a dependency on jline version 0.9.94. If you want to use it, that’s all you do. Leiningen will download it from the internet for you, probably from clojars.org.
So, why don’t we have something similar in .NET? Well, basically, because our thinking is constrained by our tools. MSBuild, and by extension Visual Studio, says that a reference is a file on your local hard drive. Adding assemblies is a pain in the neck
- Find the assembly on the internet
- Download whatever format they’ve chosen to use
- Incorporate it into your project using whatever convention you use
- Now go into Visual Studio and add the reference.
The story is as bad for publishing as assembly. Here’s the process if you’re using Leiningen:
scp $PROJECT.jar firstname.lastname@example.org:
- (Update the version in project.clj)
Anyone who works on a .NET OSS project will recognize that the story is significantly more complex on their side of the fence. This has a huge side effect: we make our assemblies large. Take a look at this project. The source is 17 lines long. I post significantly longer scripts on my blog for people to use via cut and paste. It easy to publish, and it was easy to consume. Equally, you don’t tend to see solutions of large numbers of related projects. Everyone just directly references each others assemblies. (It’s worth noting that typically in Clojure the jar files actually contain the source code as well (possibly) compiled class files.)
In short, there’s a lot we can learn from the Clojure space. The problem is that we’d need to break things in order to make them work again. My ambition of having projects just load from solution files is impossible if you want this kind of flexibility. However, the goal is the same: make code read/write. It’s just that there’s multiple paths to get there.