This is a broad “hail mary” question , but I’m hoping there are some of you who work on large scale development efforts that could share your experience with me.
My job currently has around 100 Mercurial repositories and we’re on pace to have around 1000 by the time we’re done. We’re just starting the migration from Ant/Ivy to Gradle. As part of this, I’ve been trying to define a strategy for handling repos and 3rd party (open source) dependencies that are constantly evolving. We know the Gradle wrapper & semantic versioning of our custom plugins, production, and test code are key to this strategy, but we’re not sure what the right architecture & policies are.
Gradle gives us several choices: * Each repo could have a build.gradle with its own declaring its own dependencies. This offers the ultimate independence, but makes it very hard to roll out large scale changes, e.g., logging, the JDK, etc.
- I’ve prototyped a DSL that could be used as a wrapper around the Gradle dependencies DSL. My DSL allows developers to express dependencies at a higher more abstract level. For example,
dependenciesSimplified {
junit
}
would result in our custom plugins creating a dependency on our project’s default version of JUnit. If the repo wanted something other than the default, additional information could be passed, e.g. “lastVersionOf junit”. I like this because ** It prevents people from knowing Gradle/Ivy dependency syntax. ** It ensures the same dependencies are expressed correctly across repos (we’ve found we already have a lot of inconsistencies due to wide variety of Gradle/Ivy configurations used by ourselves but especially 3rd parties). ** It allows us to change the defaults and have them picked up by repos the next time they are built OR en masse via something like the Jenkins Ivy plugin (supporting automatic downstream builds) or Gradle multi-project builds (which doesn’t seem realistic on a project of our size).
- The last option is a bit ambitious: Our custom plugins could have a package-to-dependency map which could be used to automatically declare dependencies based on introspecting the source code. This would have all the features of the previous DSL option with the obvious added benefit of minimizing or eliminating dependency declarations in each repo.
Finally, there are some dependencies we don’t manage well: * Dependencies on the JDK, OS, etc. * Actual runtime dependencies - the runtime configuration we use is really a “unit test” configuration. Real runtime is where the transitive dependencies are deployed, installed, and activated in a compatible JVM.
We’re currently using Chef and OBR (OSGi) for this now with some success, but we have big gaps in what this supports. But it is unclear how to merge the deploy/install/activate time dependency management with the compile/test/package time dependencies Gradle excels at.
Sorry for such a long, rambling post. Hopefully these are problems of interest to a wide audience …