Gradle wrapper in environments that require controlled builds

I have just joined a new project where my employer is collaborating with external service provider to jointly build some software. One of our critical requirements is that each side should be able to take the codebase and go their own way at any moment in time.

To this end, we are maintaining separate artifact repository, parameterizing the repo URL in build.properties and provide the appropriate details in user-specific gradle.properties. We also got rid of the plugin-portal DSL usage, so things are almost there. It is up to you to figure how to launch the correct jar file.

There is only one catch… as nice as it is, the Gradle wrapper.properties only allow us to have a single download URL and does not provide any mechanisms for customization. I am putting the problem here here as an open question, though a few solutions that come to mind are:

Variant A: have the Gradle wrapper resolve stuff from gradle.properties in the same way the main Gradle does, so we could specify:

distributionUrl = https://${repo.wrapper}/org/gradle/2.5/gradle-2.5-bin.zip

Variant B: allow to provide a list of download URLs that would be tried in order (similar to how builds may specify multiple repos).

The list can be implemented by either allowing coma separated values and stripping spaces, such as:

distributionUrl = https://mycompany.com/repo/public/gradle-2.5-bin.zip, \
                  https://theircompany.com/repo/3rd-party/gradle-2.5-bin.zip, \
                  https://services.gradle.org/distributions/gradle-2.5-bin.zip

or if we don’t want to parse the values, perhaps use numbered properties such as:

distributionUrl = https://mycompany.com/repo/public/gradle-2.5-bin.zip
distributionUrl.fallback.1 = https://theircompany.com/repo/3rd-party/gradle-2.5-bin.zip
distributionUrl.fallback.2 = https://services.gradle.org/distributions/gradle-2.5-bin.zip

Variant C: allow reference to environment variables, such as:

distributionUrl = https://${env:GRADLE_WRAPPER_REPO}/org/gradle/2.5/gradle-2.5-bin.zip

Variant D: allow multiple wrapper.properties files and use filename pattern to try them in alphabetical order. This has the benefit that each client can customize not only the distribution URL, but also the checksum, download location (i.e. in case of disk quotas or roaming profiles), etc.

Edit: Variant D is almost available - if you have multiple copies of wrapper.jar file, each jar would use a corresponding properties file with the same base name.

After some thinking over the weekend, I find Variant D the most appealing for this use case. This means that:

  1. The wrapper will try to find distribution matching wrapper.properties first and if not found, try ~/wrapper-.+.properties/ in alphabetical order. If any of these are found - use them.
  2. If no applicable distributions are found, the wrapper will try to download using the settings wrapper.properties first and if download fails, try ~/wrapper-.+.properties/ in alphabetical order.
  3. If no download succeeds - declare failure.

The advantages are that the property format doesn’t change, the default settings are clearly identified and one can have different wrapper settings for different organizations named wrapper-myorg.properties, allowing for custom URL, unpack location and even checksum (if the enterprise customizes their distro).

An open question is whether the wrapper.properties should be considered a default config for all org-specific settings, or should they use Gradle’s built-in defaults.

Looking further, I realized that my use-case is already accommodated by creating 2 separate gradle wrappers.

While not a typical use-case, Gradle allows you to customize the property file being used to fetch the distribution, albeit in a roundabout way. It turns out that the name of the property is resolved by finding the wrapper’s Jar file as of

GradleWrapperMain.class
   .getProtectionDomain().getCodeSource()
   .getLocation().toURI()` 

and then replacing the .jar extension with .properties

To complete this, we should

  1. clone the gradle-wrapper.jar file (or symlink it) under a new name (i.e. gradle-wrapper-2.jar)
  2. clone the gradle-wrapper.properties in the same way under a name that matches the newly cloned jar file
  3. devise a way to launch the cloned Jar, such as:
  4. clone the shell script/batch file and edit the CLASSPATH=... line to point to the cloned gradle-wrapper-2.jar
  5. or modify the shell scripts to dynamically select the right Jar in the CLASSPATH, based on on some custom criteria (i.e. environment variable, network domain, presence of certain pattern in ~/.gradle/gradle.properties, etc.)

My situation is a bit different from yours, but may still provide some inspiration.

I’m working for a company with multiple offices in different locations separated by more or less fast internet connections. Each office has a local artifact server, that proxies the main one as a cache.

We are using DNS to separate those:

  • Our repository servers are named nexus.location1.example.com, nexus.location2.example.com etc.

  • Our gradle-wrapper.properties only contains the unqualified hostname and the fully qualified name is established via a host-specific DNS suffix.

     distributionUrl=http\://nexus/[...]
    
  • Each developer has to configure this DNS suffix based on where he is located.

One of our requirements is minimum setup on the dev side (we all belong to different companies). Special DNS settings are definitely not an option.

It seems like the current solution (multiple wrappers) is also not an option, as the codebase owner is quite reluctant to have multiple gradle-wrapper.properties even if it means that some devs can not use the wrapper…

I’d really like some feedback from Gradleware on this…

I can see that the dev team has started participating in the forum again, so I will try once again to bump this post…

As I had another stab on this old issue, here is a shell script that somewhat helps with the problem:

#!/usr/bin/env bash
export PROJECT_ROOT=${1:-`pwd`}
export LOCAL_NAME=${2:-gw}
export LOCAL_DISTRIBUTION_URL_PATTERN=${3:-${LOCAL_DISTRIBUTION_URL_PATTERN:-'your-host.com\/artifactory\/pkg\/gradle'}}
export ORIGINAL_DISTRIBUTION_URL_PATTERN=${4:-${ORIGINAL_DISTRIBUTION_URL_PATTERN:-'services.gradle.org\/distributions'}}

echo Creating $LOCAL_NAME under root $PROJECT_ROOT, converting wrapper distributionURLs
echo "Original: $ORIGINAL_DISTRIBUTION_URL_PATTERN"
echo "Local   : $LOCAL_DISTRIBUTION_URL_PATTERN"

cd $PROJECT_ROOT

cat gradlew     | sed "s/gradle-wrapper.jar/gradle-wrapper-$LOCAL_NAME.jar/" > $LOCAL_NAME
cat gradlew.bat | sed "s/gradle-wrapper.jar/gradle-wrapper-$LOCAL_NAME.jar/" > $LOCAL_NAME.bat
cat gradle/wrapper/gradle-wrapper.properties | sed "s/$ORIGINAL_DISTRIBUTION_URL_PATTERN/$LOCAL_DISTRIBUTION_URL_PATTERN/" > gradle/wrapper/gradle-wrapper-$LOCAL_NAME.properties

cp gradle/wrapper/gradle-wrapper.jar gradle/wrapper/gradle-wrapper-$LOCAL_NAME.jar
chmod u+x $LOCAL_NAME

Before you use it set your LOCAL_DISTRIBUTION_URL_PATTERN envvar to a sed-escaped url of your internal distribution mirror. Once you do that, you can checkout a project, go to its root directory and run this script from wherever you put it. It would create new wrapper files that you can use, with config pointing to your distribution Url.

Example:

git clone git@scm-host:foo/bar
cd bar
~/scripts/localize-wrapper.sh    
gw build                   

Tested on Cygwin and Linux

I would prefer Variant A.

I am not a supporter to checking in files that can be generated
gradle/wrapper/gradle-wrapper.jar
gradle/wrapper/gradle-wrapper.properties
gradlew
gradlew.bat

Therefore variant A where we could specify the distributionUrl within the global gradle.properties would work best. Then generating the gradle wrapper files would use this URL.

Edit: 2 years later. This was really an old discussion. Has it become anything of it?

3 years and 3 days later - the 3rd incarnation of my patch is public!