Search code examples
gradleartifactgradle-dependencies

Define an artifact to be used as a dependency in another project


TL;DR

I'm trying to configure two Gradle projects in a way that one project uses files built by the other one. The first project is added to the second one by includeBuild and the file is defined in the second project as a dependency.

Project testA

settings.gradle:

rootProject.name = 'testA'

build.gradle:

group = 'org.test'
version = '0.0.0.1_test'

task someZip (type: Zip) {
    from './settings.gradle'
    archiveName = 'xxx.zip'
    destinationDir = file("${buildDir}/test")
}

artifacts {
    //TODO add something here?
}

Project testB

settings.gradle:

rootProject.name = 'testB'

if (System.getenv('LOCAL_COMPILATION') == 'true') {
    includeBuild '../testA'
}

build.gradle:

if (System.getenv('LOCAL_COMPILATION') != 'true') {
    repositories {
        maven { url '192.168.1.100' }
    }
}

configurations {
    magic
}

dependencies {
    magic 'org.test:xxx:0.0.0.+@zip'
}

task ultimateZip (type: Zip) {
    from configurations.magic
    archiveName = 'ultimate.zip'
    destinationDir = file("${buildDir}/ultimate-test")
}

Description

You may noticed that the example has an option use a maven repository. I wanted to highlight that eventually there will be a possibility to do that. Using Maven repository is not the point of this question, though, other than the solution should not interfere with that. (In other words you can assume that System.getenv('LOCAL_COMPILATION') == 'true'.)

The question is how to define the artifact in a way that the other project is able to recognize it.

The preferred solution should be similar to what the Java plugin does because I'm using jar dependencies in my projects and they are working both through includeBuild and through a repository.


Solution

  • The following setup should work (tested with Gradle 5.5.1). It mostly corresponds to your original setup with the exception of the changes indicated by XXX.

    Project testA

    settings.gradle:

    rootProject.name = 'testA'
    

    build.gradle:

    group = 'org.test'
    version = '0.0.0.1_test'
    
    task someZip (type: Zip) {
        from './settings.gradle'
        archiveName = 'xxx.zip'
        destinationDir = file("${buildDir}/test")
    }
    
    // XXX (replaced your empty "artifacts" block)
    configurations.create('default')
    def myArtifact = artifacts.add('default', someZip) {
        name = 'xxx'
    }
    
    // XXX (only added to show that publishing works)
    apply plugin: 'maven-publish'
    publishing {
        repositories {
            maven { url = 'file:///tmp/my-repo' }
        }
        publications {
            myPub(MavenPublication) {
                artifactId myArtifact.name
                artifact myArtifact
            }
        }
    }
    

    Project testB

    settings.gradle:

    rootProject.name = 'testB'
    
    if (System.getenv('LOCAL_COMPILATION') == 'true') {
        // XXX (added a dependency substitution to let Gradle know that
        //      "org.test:xxx" corresponds to the testA project)
        includeBuild('../testA') {
            dependencySubstitution {
                substitute module('org.test:xxx') with project(':')
            }
        }
    }
    

    build.gradle:

    if (System.getenv('LOCAL_COMPILATION') != 'true') {
        repositories {
            // XXX (only changed to show that resolution still works after
            //      publishing)
            maven { url = 'file:///tmp/my-repo' }
        }
    }
    
    configurations {
        magic
    }
    
    dependencies {
        magic 'org.test:xxx:0.0.0.+@zip'
    }
    
    task ultimateZip (type: Zip) {
        from configurations.magic
        archiveName = 'ultimate.zip'
        destinationDir = file("${buildDir}/ultimate-test")
    }
    

    As requested in the comments, here’s some more explanation on the created default configuration and the added artifact in project testA.

    Composite builds in Gradle currently have the limitation that substituted project dependencies “will always point to the default configuration of the target project”. In your example this means that testA needs to publish in the default configuration. We thus first create the default configuration. Note that some plugins (like java) already create this configuration; you don’t need to create it yourself when using such plugins.

    It doesn’t seem to be mentioned explicitly anywhere but as you seem to have found out yourself already, the PublishedArtifacts of a project (as declared with project.artifacts) are important for Gradle to figure out the dependency wiring in composite builds. Hence, we make sure to declare such a PublishedArtifact in testA using this API. The artifact (e.g., its extension) is configured based on the properties of the someZip task. The name seems to not be taken from the someZip task in your example because you manually set archiveName; hence we need to explicitly declare it. If you use archiveBaseName = 'xxx' in someZip instead, then you don’t need the closure when adding the artifact.