4

I'm trying to get a Java programming project set up in a way that it can be built completely offline just from the files in version control, on any machine, with no prerequisites to install. (Sole exception: the JDK)

I don't know much of Maven, but I'm learning Gradle currently. For the build I'm using Gradle with the wrapper, I modified the gradle-wrapper.properties so the distributionUrl doesn't point to a remote URL but instead a file within the project's folder. So Gradle itself is done already.

For dependencies though, this is a pure nightmare. I've already tried managing it with simply directly pointing to single JARs or collections of JARs, but as you sure know this ends up in total confusion if you have interdependencies and/or transitive dependencies.

Basically what I want to have is Gradle/Maven configured (just for this project) in such a way that they use a different directory for their local dependency cache - not one that is shared by anything you do on your user account, in your home directory, but instead a folder within the project folder (so I can check that into version control as well) which is only used for that one project. I would then only once take Gradle/Maven in online mode, run the build once to download all the currently needed dependencies into the local cache directory within the project folder (whose contents then are put into VC), then put it in offline mode for all subsequent builds.

I haven't found a way yet to reconfigure that local cache path just for this one project (has to be something I can just put into my build.gradle). Anyone have a lead for me?

Edit: Another thing would be to have this local repository within the project folder be a "real" Maven repository (instead of just a cache). But that would involve manual work for one way I imagined: You start a local copy of Sonatype Nexus that has a proxying repository set up for any Maven repos you're referring to in your build.gradle; you modify the repo URLs in your build script to point to your local Nexus, run the build once so Nexus downloads the deps and puts them into its work directory in the "usual" Maven repo structure; then you copy that structure to a folder within your project folder and change the buildscript repo URLs to point to the folder you just copied the files to. Quite a lot of work that can barely be automated.

  • Stuffing all those .JARs into a source-code repository bloats the repository and is explicitly not recommended for decentralized repositories like Git. – kevin cline Sep 29 '16 at 0:22
  • I am aware it bloats the repository. Yet I prefer portable builds with minimal dependencies, stuff I can build anywhere, anytime - without having to rely on external services which might not be available for whatever reason, or serve corrupt files. I don't want to rely on volatile, badly portable local caches (which by the way have to be populated once as well). – Julian Sep 29 '16 at 2:24
  • For the projects I set up this way, I don't expect to add too many deps, and those I do add won't be updated often, if at all. So things should stay manageable. Whoever makes a fresh working copy clone of the repo will only download all the big binary files once; and for building the project they'd need to download them either way - doesn't matter much if it's from an external maven server or directly from the source repo. – Julian Sep 29 '16 at 2:24
  • Not exactly what you ask for since the cached dependencies will be stored in default directories (probably in hidden dirs somewhere in your home directory, at least on Linux) rather than inside the project, but you can run ./gradlew --offline to make gradle only use cached dependencies and not access the network when it builds the project. – Michał Kosmulski Sep 29 '16 at 7:17
  • I've tried your solution, but didn't work. Can you take a look here? – MTZ Sep 10 '18 at 9:30
1

Alright, so I found a way which works for me, and it's automatable.

I found this snippet which allows you to create a Gradle task that will copy certain project dependencies to an arbitrary folder in Maven directory structure. I needed to tweak it slightly so it doesn't just do that for one single configuration, but for any and all configurations the project defines.

My build script looks like this now:

apply plugin: 'java'

repositories {
    maven {
        url './dependencies-maven/'
    }
    mavenCentral()
    jcenter()
    // whatever other external repos your deps come from
}

dependencies {
    // ...
}

task offlineRepo (type:OfflineMavenRepository) {
    repoDir = new File(project.projectDir, 'dependencies-maven')
}

You then simply fire the offlineRepo task once: all current project dependencies will be downloaded to Gradle's local cache and then copied into a subfolder in the project directory (in Maven repo structure). The next time you run the build with the project directory in this state, the just-populated local repo will be queried first, and since it contains (should contain) all the deps, nothing should be taken from the Gradle's user folder cache nor from the web.

At this point you can take the whole project folder to any other machine with just Gradle installed and it should be able to do the build without ever going online to fetch some additional data.

Whenever you want to update some project dependency or add new ones, you'll just need to fire the offlineRepo task once again. Be aware that this won't delete the old version of the dependency you update, so if you don't want to end up with a cluttered repo, you need to do some manual cleaning here.

With this, plus putting the Gradle wrapper offline as well (described in my original question), I've now made my project buildable completely offline and with minimal prerequisites: no internet connectivity required, not even a Gradle install required. Merely the JDK.

Note: When I set this up in a completely empty dummy project, I had to create a java source directory and some sample java file, otherwise Gradle would only download/copy some POMs but not the corresponding JARs.

Addition: The tweaked snippet will only take care of project dependencies. But if you use a custom plugin, which is a buildscript dependency, the script will miss it. You'll need to add more code to the build() method:

for(Configuration configuration : project.buildscript.configurations.findAll())
{
    copyJars(configuration)
    copyPoms(configuration)
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.