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

1

5

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.

Appraise answered 19/7, 2019 at 10:45 Comment(0)
B
3

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.

Bedspread answered 27/7, 2019 at 13:2 Comment(7)
This thing with extension may be a problem when one project generates two files with the same name and different extensions. I think I have one or two such projects.Appraise
I don't like having dependencySubstitution in the settings file of project testB. There are two reasons: 1) I include the same project into multiple other projects so there will be a lot of repeating code. 2) My real code to includeBuild is not just a simple if but a function which checks a few different folders is searching for the right project. If I add even more code to it, it will be quite messy. Would it be possible to move dependencySubstitution to the project testA? build.gradle of project testB would also be acceptable because I could write a plugin to deal with that.Appraise
I have found a way to keep the explicitly declared extension on the dependency in testB; please see my updated answer. As for the dependency substitution, it’s unfortunately required as long as your included project has a different name (testA) than the module it publishes (xxx). I don’t believe there is currently any way to move the dependency substitution out of settings.gradle. That said, would it be an option to rename testA to xxx? Then you wouldn’t need the substitution at all and the setup would also look cleaner (from what I know about your setup from the question).Bedspread
I think in the case of most of the projects the name of artifact is the same as projects. I've shown the most extreme case. So, do I not have to use dependencySubstitution when the names are the same?Appraise
The purpose of most of the code is rather clear but could you add some explanation for these 4 lines after // XXX (replaced your empty "artifacts" block)? I want to know how exactly this works so I can modify this if needed. Especially I would like to know what is the purpose of the configuration default. I'm guess assigning value to myArtifact is not necessary if you don't plan to use it later to publishing?Appraise
Yes, when you use rootProject.name = 'xxx' in testA/settings.gradle, then you don’t need the dependencySubstitution. You indeed only need to define the myArtifact variable when you plan to use it later. I have added some more explanation at the end of my answer on the other points you’ve mentioned.Bedspread
I know now why I could figure it out by myself. There is a problem when you apply plugin java to the project testA. This is probably other problem than I've presented here, so I've decided to create a new question: #57273076Appraise

© 2022 - 2024 — McMap. All rights reserved.