3

When publishing with the use of maven-publish (incubating, I know), compile dependencies are added to the generate POM (in the runtime scope), but testCompile dependencies are ignored.

How to get the testCompile dependencies into the generated POM as test scope?

Markus Pscheidt
  • 6,853
  • 5
  • 55
  • 76
  • Please explain why you would like to publish your test code as artifacts. That's certainly not standard procedure. – Jolta Mar 03 '15 at 00:04
  • There are test libraries such as `spring-test`, `spring-batch-test`, `jsonpath`, which are used in a modular setup by a base and dependent modules. So it would be nice to declare them in the base module in the `test` scope the same way as e.g. `spring-context` is declared in the `runtime` scope. – Markus Pscheidt Mar 03 '15 at 08:48

3 Answers3

2

I used several hours to find a nice way to choose what build configuration an artifact or publication should use, but with no luck. My findings were, that the only way to achieve it is by modifying resulting POM XML as below:

// build.gradle

publishing {
    repositories { /* skipped for brevity */ }

    publications {
        core(MavenPublication) {
            from components.java
            artifactId project.name

            artifact sourcesJar {
                classifier 'sources'
            }
        }

        generators(MavenPublication) {
            from components.java
            artifactId "${project.name}-generators"

            artifacts = [ generatorsJar ]
            artifact generatorsSourcesJar {
                classifier 'sources'
            }

            pom.withXml { pomXml -> replaceDependenciesWith('generatorsBase', pomXml) }
        }
    }
}

void replaceDependenciesWith(String configurationName, XmlProvider pomXml) {
    Node configurationDependencies = new Node(null, 'dependencies')
    project.configurations.getByName(configurationName).allDependencies.each { dep ->
        Node dependency = new Node(null, 'dependency')
        dependency.appendNode('groupId', dep.group)
        dependency.appendNode('artifactId', dep.name)
        dependency.appendNode('version', dep.version)
        dependency.appendNode('scope', 'compile')
        configurationDependencies.append(dependency)
    }
    pomXml.asNode().dependencies*.replaceNode(configurationDependencies)
}

Above worked on Gradle 3.3


Comment on Groovy's XML builder-like syntax

I also tried to use Groovy's XML builder-like syntax, but unfortunately wrong context was attached to closure passed to replaceNode method and hence it did not work. When inlined it was getting the same context as publications {} closure, while when extracted to a method, version dep.version did not work as expected).

// Does not work!
void replaceDependenciesWith(String configurationName, Node pomXmlNode) {
    pomXmlNode.dependencies*.replaceNode {
        dependencies {
            project.configurations.getByName(configurationName).allDependencies.each { dep ->
                dependency {
                    groupId dep.group
                    artifactId dep.name
                    version dep.version
                    scope 'compile'
                }
            }
        }
    }
}
krzychu
  • 3,577
  • 2
  • 27
  • 29
1

The POM is only used when publishing an artifact; it gets uploaded to the Maven repo along with the artifact. Therefore, the POM only needs runtime dependencies.

Gradle executes tests independent of your deployment plugin, so it does not use the POM file.

Assuming you're using the Java plugin, it adds the test source set. This in turn creates the testCompile task.

Now, Gradle assumes that your runtime dependencies will be the same as your compile-time dependencies, if you don't configure otherwise. However, it only considers the main source set. That's why you POM doesn't include test dependencies.

So, in summary, configure your test dependencies similar to the below. Then, just live happy, knowing that the published artifact will not include your test code or its dependencies.

dependencies {
    testCompile 'org.springframework:spring-test:4.+'
}

If you have an exceptional situation, where tests are executed on a machine that doesn't have access to the test source code, please describe in more detail what your requirements are. It should be possible to set up a separate output artifact for the test code, so it can be published, but I still don't think you should release it in the same package (or POM) as the main source set.

Jolta
  • 2,620
  • 1
  • 29
  • 42
  • Ok, I already publish a separate `artifact testJar` with shared test code between modules. It would also be fine to make an additional POM for the test jar declaring external libraries like `spring-test`. Would that be possible? – Markus Pscheidt Mar 03 '15 at 12:04
  • What's your use case for publishing your test code? Sorry for being so insistent but I think it's important to not expend effort on something if it is not needed. =) Yes, you could declare more artifacts to be published. See http://gradle.org/docs/current/userguide/publishing_maven.html#publishing_maven:publications for how to declare your `publications`. – Jolta Mar 03 '15 at 13:33
  • No problem, here's some detail concerning the motivation. Let's take the database init script that initializes the in memory database or the test constants Java class. These are used by unit tests in the main/base module as well as by dependent modules. The alternative would be to copy paste such test resources. – Markus Pscheidt Mar 03 '15 at 13:50
  • The issue of transitive test dependencies in a multi-modular setup is illustrated here for Maven: http://stackoverflow.com/q/15816805/606662 – Markus Pscheidt Mar 03 '15 at 19:11
  • The conclusion seems to be that transitive test dependencies are not seen as the right thing to do. Therefore I'll give in declare test dependencies per project, even if this is duplication of code and violating the DRY principle. – Markus Pscheidt Mar 04 '15 at 15:28
  • Maybe using `testCompile` configuration is not the best practice, but I wanted to generate two publications from one project - one publication with model and alike, while the other with generators, which can be used for tests (when model values need to be generated). Then projects that use this library can include generators separately in their testCompile dependencies. – krzychu Jun 17 '17 at 15:02
0

I wasn't able to use any of the feature of the groovy language when I needed to modify the POM xml. And I had to rely on the API directly just like krzychu's answer.

Otherwise the xml closure wasn't applied as I expected it would, the build was failing with some warning, or the closure wasn't applied correctly resulting in invalid pom.

But recently, after reading carefully the groovy's closure documentation, I noticed one can apply a resolutionStrategy to a closure, to help the runtime find the right context (the implicit this).

The default resolution strategy is Closure.OWNER_FIRST, which explains why I got errors about the closure being applied to publications in some of my trials. From their documentation I tried to set the strategy to Closure.DELEGATE_FIRST and this proved working as expected.

Note however the closure has to apply on a Node, hence the .children() returns a list, .last() returns a Node on which you can add another node either via the .plus(...) method or its alias +.

publishing {
    publications {
        core(MavenPublication) {
            pom.withXml {
                def dependenciesNode = 
                    asNode().dependencyManagement
                            .first()
                            .dependencies
                            .first()

                dependenciesNode.children().last().plus( {
                    resolveStrategy = Closure.DELEGATE_FIRST
                    dependency {
                        'groupId'('org.springframework.boot')
                        'artifactId'('spring-boot-dependencies')
                        'version'(rootProject.'spring-boot.version')
                        'type'('pom')
                        'scope'('import')
                    }
                })
            }
        }
    }
}

Finding the right syntax was like finding a pin in haystack, here's some links (1), (2), (3) that helped me one I found the resolutionStrategy.

bric3
  • 40,072
  • 9
  • 91
  • 111