How can plugin programmatically configure maven-publish publishing and allow build.gradle to modify it - gradle

I have project wide settings in a plugin, called parent, that attempts to apply the maven-publish plugin and then programmatically configure the publishing extension. This seems to work but when I apply this plugin in a build.gradle script I can not configure publishing extension to set the project specific publications.
I receive the error:
Cannot configure the 'publishing' extension after it has been accessed.
My intent was to set up the publishing repository in the parent plugin and then let each build.gradle script add the appropriate publications.
Is there a way to do this?
Currently ParentPlugin.groovy looks like:
def void apply(Project project) {
project.getProject().apply plugin: 'maven-publish'
def publishingExtension = project.extensions.findByName('publishing')
publishingExtension.with {
repositories {
maven {
mavenLocal()
credentials {
username getPropertyWithDefault(project.getProject(), 'publishUserName', 'dummy')
password getPropertyWithDefault(project.getProject(), 'publishPassword', 'dummy')
}
}
}
}
}
My client build.gradle fails when it tries to configure the publishing extension.
apply plugin: 'parent'
publishing {
publications {
mavenJava(MavenPublication) {
groupId 'agroup'
artifactId 'anartifactid'
version '1.0.0-SNAPSHOT'
from components.java
}
}
}
Is this possible? Is there another way I should be approaching this?

NOTE regarding repositories{} and publications{} for plugin maven-publish:
Topic: How to workaround this perplexing gradle fatal error message:
Cannot configure the 'publishing' extension after it has been accessed
First thing to try (deep magic):
(note "project." prefix is optional)
-- Configure publications and repositories not like this:
project.publishing {publications {...}}
project.publishing {repositories {...}}
but instead like this recommended style:
project.publishing.publications {...}
project.publishing.repositories {...}
It would be instructive for a gradle guru to explain why this trick works.
Another known workaround is to make sure that each apply of plugin
maven-publish is in the same project code block as
project.publishing.repositories and project.publishing.publications.
But that is more complex and harder to do than the first thing to try,
since by default the CBF applies maven-publish and a second apply of it
may itself cause the same error.
maven-publish is normally applied in pub/scripts/publish-maven.gradle,
unless PUB_PUBLISH_MAVEN is set to override that file location,
in which case the caller should apply plugin maven-publish.
See https://orareview.us.oracle.com/29516818 for how this not-preferred
workaround can be done (for project emcapms) while still using the CBF.
P.S. Someday I'll write this up with minimal code examples. But I'm putting this hard-won knowedge out there now to save other folks from wasting days on this common maven-publish issue.

To deal with this, I wrote another plugin, which can delay modifications to the publication while also avoid a "reading" of the extension, which would put it in the "configured" state. The plugin is called nebula-publishing-plugin, the code for the "lazy" block can be found in the github repo. It looks like this:
/**
* All Maven Publications
*/
def withMavenPublication(Closure withPubClosure) {
// New publish plugin way to specify artifacts in resulting publication
def addArtifactClosure = {
// Wait for our plugin to be applied.
project.plugins.withType(PublishingPlugin) { PublishingPlugin publishingPlugin ->
DefaultPublishingExtension publishingExtension = project.getExtensions().getByType(DefaultPublishingExtension)
publishingExtension.publications.withType(MavenPublication, withPubClosure)
}
}
// It's possible that we're running in someone else's afterEvaluate, which means we need to run this immediately
if (project.getState().executed) {
addArtifactClosure.call()
} else {
project.afterEvaluate addArtifactClosure
}
}
You would then call it like this:
withMavenPublication { MavenPublication t ->
def webComponent = project.components.getByName('web')
// TODO Include deps somehow
t.from(webComponent)
}
The plugin is available in jcenter() as 'com.netflix.nebula:nebula-publishing-plugin:1.9.1'.

A little bit late, but I found a solution that does not require an additional plugin:
(This has been taken from one of my internal plugins, that can work with old and new publishing, thus the ...withType... stuff.
instead of:
project.plugins.withType(MavenPublishPlugin) {
project.publishsing {
publications {
myPub(MavenPublication) {
artifact myJar
}
}
}
}
do this:
project.plugins.withType(MavenPublishPlugin) {
project.extensions.configure PublishingExtension, new ClosureBackedAction( {
publications {
myPub(MavenPublication) {
artifact myJar
}
}
})
}
This will not resolve the Extension immediately, but will apply the configuration at the time when it gets first resolved by someone.
Of course it would perfectly make sense to use this style of configuration in your project-wide plugin to configure the repositories and use the publication extension in the build scripts as usual. This would avoid confusion for buildscript authors.

Related

How can I add/change a Maven publishing repository in a Gradle init script

I'd like an init script that lets me take arbitrary Gradle projects and change the Maven repository location that they publish artifacts to.
Adding a repository is easy enough when you edit the build file directly, just add a maven{} block inside publishing { repositories { } }. However, trying to do this generically leads to frustration and failure. I tried this:
allprojects {
beforeEvaluate {
pluginManager.withPlugin("maven-publish") {
extensions.getByType<PublishingExtension>().publications {
repositories {
maven {
url = uri("file:///my/path")
name = "myrepo"
}
}
}
}
}
}
but, no luck. No such repository appears. I suspect there is a timing issue here: although my code does run, it presumably runs after the publishing plugin has created these tasks. What I want to do is register a callback that is run before the publish plugin gets a chance to do that, but I don't know how.

Gradle cannot find plugin declared in buildscript

I'm trying to factor out common Gradle tasks in a reusable file. Here is an excerpt of a build-root.gradle file:
buildscript {
// Repository declaration
ext {
isSnapshot = version.endsWith("-SNAPSHOT")
repos = {
def mavenRepo = { repoName, repoUrl ->
maven {
credentials {
username System.env.<some env var>
password System.env.<some env var>
}
name repoName
url repoUrl
}
}
mavenLocal()
mavenRepo('repo1', 'https://repo1.url')
mavenRepo('repo2', 'https://repo2.url')
mavenRepo('repo3', 'https://repo3.url')
}
}
// Versions and libraries declaration
ext {
versions = [
... some stuff
// Gradle
gradleRelease : '2.8.1',
... more stuff
]
libs = [
... some stuff
// Gradle
gradleRelease : "net.researchgate:gradle-release:$versions.gradleRelease",
... more stuff
]
}
repositories repos
dependencies {
classpath libs.gradleRelease
}
apply plugin: 'net.researchgate.release'
}
... more common stuff
The idea is for subprojects to apply from that file and get all the goodies from it.
On the "apply plugin" line I get the following error - > Plugin with id 'net.researchgate.release' not found.
I printed the libs.gradleRelease string, it looks fine: net.researchgate:gradle-release:2.8.1
We are currently using Gradle 5.2.1, but I also tried 6.0.1 - same error. Any ideas why it can't find the plugin? BTW, this is not exclusive to this particular plugin, I tried others and still get the same error.
After pulling whatever was left of my hair and banging my head against the wall, I came across this => https://discuss.gradle.org/t/how-do-i-include-buildscript-block-from-external-gradle-script/7016
Relevant comment from #Peter_Niederwieser:
"Secondly, externalizing a build script block into a script plugin isn’t supported. (It’s a tough problem, and can’t think of a good way to implement this.) You may have to live with some duplication, at least for the time being. Remember that dependencies specified in a project’s ‘buildscript’ block are visible to all subprojects. Hence, as long as you don’t need dependencies to be available in a script plugin, you just need to declare them in the root project’s build script."
Which is exactly what I was trying to do. I'm not going to curse here...

Changing configuration with the gretty plugin?

I haven't done anything with Gradle for a while, so it appears I've forgotten how configuration resolution works.
I'm trying to use the gretty plugin (instead of core, deprecated jetty), but I cannot seem to create a custom configuration.
I've boiled it down to a very short, simple script (using Gradle 3.4):
buildscript {
repositories {
maven {
url 'https://plugins.gradle.org/m2/'
}
}
dependencies {
classpath 'org.akhikhl.gretty:gretty:1.4.0'
}
}
plugins {
id 'org.akhikhl.gretty' version '1.4.0'
}
configurations {
fooTest
}
configurations.fooTest.each {
println it.toString()
}
It seems to not like me iterating over the fooTest configuration.
Assuming I need to know the dependencies for that configuration (I stripped that part from the code above)
What am I doing wrong here?
The script above gives me this:
org.gradle.api.InvalidUserDataException: Cannot change strategy of configuration ':fooTest' after it has been resolved.
The key point here was that I needed an unresolved configuration to loop over. Admittedly this information was neglected in the initial description as I didn't know it was critical information. We needed to loop over the files in the dependency and copy/unzip them into certain locations.
However, we cannot do that with a resolved configuration. That said, we can copy the configuration into a unresolved one, and loop over that instead:
configurations.fooTest.copy().each {
println it.toString()
}
This will successfully print out the files involved in the dependency (or unzip them, as my case needs).

How can I make Gradle extensions lazily evaluate properties that are set dynamically by tasks?

I'm pretty new to working with Gradle and I'm trying to develop a plugin that helps manage version numbering. This plugin defines a task that sets the project.version property of the project it's applied to.
What I'm trying to do is make it so that this property is set at the start of every Gradle build. Using Peter's answer to another Gradle question, I've managed to get my task to execute before any other by adding gradle.startParameter.taskNames = [":setProjectVersionNumber"] + gradle.startParameter.taskNames within my plugin's apply method.
However, other plugins (notably 'Maven-publish') rely on the version being specified during the configuration phase:
publishing {
publications {
somePublication(MavenPublication) {
version = project.version
}
}
}
What I'd like to know is if there's a way that I can make the evaluation of properties like version within these extensions as lazy as possible - such that they're not evaluated until a task that depends upon them is called, which in this case might be :publishToMavenLocal.
Below is an SSCCE that demonstrates what I'm hoping to achieve:
// This would be included within the plugin
class SetProjectVersionNumber extends DefaultTask {
#TaskAction
void start() {
// This will set project.version during execution phase
project.version = "1.2.3"
logger.info "Set project version number: $project.version"
}
}
task setProjectVersionNumber(type: SetProjectVersionNumber)
// Imagine this block being replaced by a maven 'publishing' block (or something similar)
ext {
version = project.version
// This will print 'unspecified', as it's evaluated during configuration phase
println "In extension, setting version=$project.version"
}
If you can provide a way to make ext.version equal 1.2.3 in the example above, I believe you've resolved my issue.
If this is asking too much, it may be possible for me to make my plugin generate the version string at configuration-time rather than execution-time. It would be nice to know if I could do it this way, though.
EDIT
In an experimental branch, I tried moving all the version string assignment logic to the configuration-phase (by making it all happen during plugin application rather than during task execution), but I don't believe this will work as the plugin extension has not yet been processed and trying to refer to properties defined in it fail.
EDIT 2
Wrapping the version string assignment logic in a project.afterEvaluate closure seems to have worked:
#Override
public void apply(Project project) {
logger = project.logger
project.extensions.create(EXTENSION_NAME, SemVerPluginExtension)
project.afterEvaluate {
setVersionProjectNumber(project)
addTasks(project)
}
}
In a mock project, I implement build.gradle as follows:
apply plugin: 'semver'
apply plugin: 'maven-publish'
group = 'temp'
buildscript {
repositories {
mavenLocal()
jcenter()
}
dependencies {
classpath 'com.github.tagc:semver-plugin:0.2.2'
}
}
semver {
versionFilePath = 'version.properties'
}
publishing {
publications {
testPublication(MavenPublication) {
version = project.version
assert version
println "Set publication version to $version"
}
}
}
For some reason, this seems to work. Although the version string assignment logic is wrapped in an 'afterEvaluate' closure and the test publication version assignment isn't, the former still occurs before the latter:
Compiling build file '/Users/davidfallah/Documents/semver/TestSemver2/build.gradle' using StatementExtractingScriptTransformer.
Compiling build file '/Users/davidfallah/Documents/semver/TestSemver2/build.gradle' using BuildScriptTransformer.
VERSION FILE PATH=version.properties
Current Git branch: develop
Set project version to 0.2.1-SNAPSHOT
Set publication version to 0.2.1-SNAPSHOT
All projects evaluated.
I'm leaving this question open and unresolved since I'd still like to know if it's possible to do it the way I originally intended. Additionally, I'd appreciate any explanation about why the publication version is assigned after the project version is set, and whether I can depend on that always being the case or whether that's just happening now by accident.
You can use lazy instantiation of GStrings to evaluate properties at run time:
project.tasks.create("example_task", Exec.class, {
commandLine 'echo', "${-> project.someproperty}"
})
Note that you have to use quotation marks and not apostrophes - "${...}" works, but '${...}' does not.

Override a gradle option defined in the master project

I'm adding an upload archive directory for all subprojects via
uploadArchives {
repositories {
flatDir {dirs '../REPO'}
}
}
Now I need to specify a different directory for one subproject.
I've found out that doing it adds to the list, but I'd like to replace the directory. I know I could use subprojects.findAll, but I'll need the possibility to override a setting elsewhere, too.
Disclaimer: My question may sound stupid, but I'm using gradle since a few weeks and must confess, I know hardly anything about it. I like it and it works fine, but reading the manual is not an option (I'm just a BFU and I'd rather switch to makefile before reading it all).
Often, the cleanest solution is to configure things the right way from the start. There are several ways to do this. For example:
rootProject/build.gradle:
configure(subprojects - project(":foo")) {
uploadArchives {
repositories {
flatDir {dirs '../REPO'}
}
}
}
project(":foo") {
uploadArchives {
repositories {
flatDir {dirs '../OTHER'}
}
}
}
Alternatively, you could have two auxiliary scripts gradle/repoA.gradle and gradle/repoB.gradle, and each subproject build script would apply the appropriate script with apply from: "$rootDir/gradle/repoX.gradle".
Finally, overriding the value might work too (untested):
rootProject/subproject/build.gradle:
uploadArchives.repositories[0].dirs = ["../OTHER"]
PS: Now go and learn some Gradle. :-)

Resources