A programatic way to set the version of any Gradle project

(Peter Palaga) #1

I am looking for a programatic way to set the version of any random Gradle project to a value that I know in advance.
I could also say I am looking for Gradle’s counterpart of mvn versions:set -DnewVersion=${myVersion}.

Background: I am working on a port of srcdeps [1] to Gradle. Srcdeps is a tool that brings source dependencies to Java projects. This is how it works in Maven:
The GAV requests to the local Maven repository are intercepted by srcdeps. If the requested version ends with -SRC-revision-1234abcd (where 1234abcd is a sha1 of a git commit) and such a version is not available in the local Maven repo, then the commit is checked out to some local directory, mvn versions:set -DnewVersion=${myVersion} is run followed by mvn install and the freshly built artifact is returned to the dependent build.

Now, I am looking for a way to reach something similar with Gradle. Say, that the dependency project is a Gradle project, and I want to be able to build any random commit of it so that the resulting artifacts have the version that the dependent project is requesting. And it is important to note, that in an ideal case, I know nothing about how the *.gradle files of the depedency project are organized.

I first tried to follow the way how I do it with maven where (I) change version and (II) build are two separate steps. Googling has not brought any usable solution. This is what I learned so far, maybe you can correct me somewhere (I am new to Gradle, sorry for listing trivialities):

(1) There is probably no need to have someting like mvn versions:set in Gradle, because the version is typically stored on one single location and manipulating it is easy enough so that that no tool is necessary for that.

(2) Although the location where the version is stored, is only one, it is not the case that the location is the same for all possible Gradle projects.
Moreover, the variety of the possible locations is twofold: (i) the version can live in various files (build.gradle, settings.gradle, others?) and (ii) because those files are scripts rather than any rigidly structured data files, the version can be set nearly anywhere in their syntax tree.

Because of (2), writing a reliable universal tool to set the version does not sound viable. Do you agree with this conclusion?

Can you see other possibilities how to reach what I need? I’d be ready to write a Gradle plugin or any other kind of code.


[1] https://github.com/srcdeps/srcdeps-maven

(Sterling Greene) #2

Hi @ppalaga,

I would suggest a different approach. I think building something on top of composite builds is going to fit better. You’ll need to solve some of the same problems and populating a local repository is going to be troublesome and unnecessary. There isn’t a single task you can run like mvn install which will do the right thing for all Gradle builds.

We’re actually going to be adding something similar to this in Gradle core in the next few months (I’m on vacation this week, but we’re going to start planning it when I get back). I can fill you in once I know more.

In general, our idea is to make it possible to compose builds from other locations (instead of just on the filesystem). Gradle would provide some infrastructure for plugins to provide capabilities (like “fetch/checkout git repository”) to get another build. Then Gradle would add that build as an included build in a composite. Composite builds can already extract the published dependency information from an included build and do the appropriate substitutions without using a local repository.

So if you have a gradle build like this:

apply plugin: 'java'
dependencies {
   compile 'com.example:foo:1.0'

And you decide you need to modify/fix foo, today, you can do something like…

gradle build --include-build ../foo (as a one time thing)

Or in your settings.gradle:
includeBuild '../foo'

And Gradle builds foo as necessary and substitutes it into your build as if it was a subproject. I suppose you could get “source dependencies” with this already using git submodules.

For the new work, I think we want to make it so you are able to eventually do (pseudo-DSL):

includeBuilds {
   foo(GitIncludedBuild) {
     url = "<some url>"
     ref = "abcdef"

Possibly with some way to define actions that happen after checking out the build.

(uklance) #3

I put my version in a version.txt file then add this to build.gradle

version = file('version.txt').text.trim()

I can then update version.txt via command line eg:

echo 1.2 > version.txt && ./gradlew release && echo 1.3-SNAPSHOT > version.txt

I prefer this file based approach rather than requiring a command line arg / sysprop because it’s implicit and committed to source control so I can see the history etc

Example here

(Peter Palaga) #4

Thanks for the tips, @sterling.

Well, you basically recommend to give up my original aim to port srcdeps to Gradle and instead either use Gradle’s included builds or wait for GitIncludedBuilds. This could be a considerable solution if the whole Java universe consisted only of Gradle projects. I suppose neither flavor of included builds is or ever will be able to help in a situation when the included project is a Maven project and vice versa (which is even more common where I work) there will be no support for Maven projects to include Gradle projects. srcdeps aim at offering this kind of portability.

If there is no conventional way to set the version of any random Gradle project, including such ones, that are out of my control, I am going to try to reach this by using byteman. In between, I was able to put some PoC together. It works for simple cases and I need to figure out where are its limits.

Thanks again!

(uklance) #5

You can pass a custom settings file at command line. Eg:

./gradlew build --settings-file /foo/custom-settings.gradle

You then have access to the Settings instance. From there you can access the Gradle instance. You could likely set the version of all projects via a projectsLoaded listener without touching the build.gradle

(Peter Palaga) #6

Thanks @Lance, your suggestion with custom settings file sounds very promising. I’ll definitely try that.

(uklance) #7

You’d likely set the version in Project.afterEvaluate { … } so that your version overrides the one set during normal evaluation

Eg custom-settings.gradle

gradle.projectsLoaded {
    rootProject.allProjects {
        afterEvaluate {
            version = 'x.y.z'