MavenPublication cannot find Components on Gradle 6.5.1

Hi there. I am trying to configure a generalized plugin for publishing.
I had a working example from gradle 2.14.1 but this does not carry over too well to gradle 6.5.1

So I read a lot and to my knowledge everything should be working correctly. However executing a build (even without actually executing the publishing task) leads to an error that google finds no answer for.

Specifically, the following Exception is thrown:
groovy.lang.MissingPropertyException: Could not get unknown property 'components' for object of type org.gradle.api.publish.maven.internal.publication.DefaultMavenPublication.

I am at a complete loss why that is. Initially, i thought it might have a connection to the fact that the project throwing this applies the war plugin, but it also uses java which should work, to be sure, I added an extra if checking for presence of war plugin and, if true, use components.web over components.java.
The block in question is currently this:

publications {
  maven(org.gradle.api.publish.maven.MavenPublication) {
    if (project.plugins.hasPlugin('war')) {
      from components.web
    } else if (project.plugins.hasPlugin('java')) {
      from components.java
    }
    afterEvaluate {
      groupId = project.group
      artifactId = project.name
      version = project.version
    }
    if (project.plugins.hasPlugin('spring-boot')) {
      pom {
        parent {
          groupId "org.springframework.boot"
          artifactId "spring-boot-starter-parent"
          version "${project.versions.springBoot}"
        }
      }
    }
  }
}              

the block consistently moves to evaluating components.web, when switching order, components.java is evaluated, and in both cases, components property can not be found.

Any help would be greatly appreciated.

You might need to provide a more complete example that reproduces the problem. The code provided works as expected when added where it belongs in a working Gradle 6.5.1 project, so there’s something impactful that’s not captured here.

Sorry for taking such a long time to reply.
The problem here is, of course, that the project is not open source and I cannot post too much information, so creating a reproducible example is rather difficult here, as is giving exact code samples.

Also not so simple: untangling the plugin code, as it basically is a mashup of several different plugins.

If you (or anyone) can hazard a guess at what might potentially cause this, that would already be a big help.
What I think I can safely show is basically this from the upload-part of the plugin:

        project.plugins.apply('maven-publish')
        project.configurations { deployerJars }
        project.dependencies { deployerJars "org.apache.maven.wagon:wagon-ssh:2.2" }
        def canUpload = project.hasProperty(ARTIFACTORY_USERNAME) && project.hasProperty(ARTIFACTORY_PASSWORD)
        project.publish.onlyIf({canUpload})

basically this part is defined before the block stated in the original post, followed by an evaluation of canUpload to define which repository URL is to be sed, which looks similar to this:

if (canUpload) {
  repositories {
    maven {
      def repositoryReleases
      def repositorySnapshots
      repositoryReleases = ... //ternary to decide on value depending on configured properties
      repositorySnapshots = ... // also ternary depending on properties
      url = project.version.endsWith('SNAPSHOT') ? repositorySnapshots : repositoryReleases
      credentials {
        username ...
        password ...
      }
    }
  }
}

But I don’t see how this could affect the publish-block before.
Furthermore, the plugin manually defines the filename like this:

project.allprojects.each { current ->
  current.afterEvaluate {
    def methods = [...]
    methods.each { method ->
      try {
        current."${method}" {
          doFirst {
            archiveFileName = ...
          }
        }
      }
    }
  }
}

We also force UTF-8 on all projects with a JavaCompile Type Task, set or replace specific properties via gradle, force source and targetCompatibility to 1.8, and define a formatter task.

These actions are among the list of suspects that I have, as other sub-plugins only define task execution dependencies (like “run tests before calling sonar”) or the like.

So, after a lot of trying, I was able to produce the following results

Project structure

test-java-multiproject
|-  test-java-multiproject-api
\- test-java-multiproject-app

declaring build.gradle in test-java-multiproject-api like this

plugins {
    id 'java-library'
}

dependencies {
    compile 'org.apache.commons:commons-lang3:3.11'
}

and in test-java-multiproject-app like this

plugins {
    id 'java'
}
dependencies {
    compile project(':test-java-multiproject-api')
    testCompile ('junit:junit:4.13')
}

working example

using build.gradle in main project like this

buildscript {
	repositories {
		maven { url this.getAt('repositoryDependency') }
	}
}
allprojects { 
	apply plugin:'maven-publish'
	configurations {
		deployerJars
	}
	dependencies {
		deployerJars "org.apache.maven.wagon:wagon-ssh:2.2"
	}
	publishing {
		publications {
			maven(org.gradle.api.publish.maven.MavenPublication) {
				groupId = project.group
				artifactId = project.name
				version = project.version
				if (project.plugins.hasPlugin('java')) {
					from components.java
				}
				if (project.plugins.hasPlugin('spring-boot')) {
					pom {
						parent {
						groupId "org.springframework.boot"
						artifactId "spring-boot-starter-parent"
						version "${project.versions.springBoot}"
						}
					}
				}
			}
		}
		repositories {
			maven {
				def repositoryReleases
				def repositorySnapshots
				
				repositoryReleases = project.hasProperty('repositoryReleases')
					? project.getAt('repositoryReleases')
					: 'http://localhost:8081/artifactory/libs-releases-local'
				repositorySnapshots = project.hasProperty('repositorySnapshots')
					? project.getAt('repositorySnapshots')
					: 'http://localhost:8081/artifactory/libs-snapshots-local'
				url = project.version.endsWith('SNAPSHOT')
					? repositorySnapshots
					: repositoryReleases
				credentials {
					credentials {
						username project.getAt('artifactoryUsername')
						password project.getAt('artifactoryPassword')
					}
				}
			}
		}
	}
	repositories {
		maven {
			url this.getAt('repositoryDependency')
		}
	}
}

subprojects {
    version = '1.0'
}

using any task - inclusing publish - works as intended.

broken example

Changing the main buld.gradle to

apply plugin: 'my-plugin'

buildscript {
	repositories {
		maven { url this.getAt('repositoryDependency') }
	}
	dependencies {
		classpath("my:plugin:0.9.9-SNAPSHOT")
	}
}

allprojects { 
	repositories {
		maven {
			url this.getAt('repositoryDependency')
		}
	}
}

subprojects {
    version = '1.0'
}

and applying my-plugin, which looks llike this:

class UploadPlugin implements Plugin<Project> {
	void apply(Project project) {
		project.allprojects.each { curProject ->
			curProject.afterEvaluate{
				if(curProject.plugins.hasPlugin(JavaPlugin)){
					applyUpload(curProject)
				}
			}
		}
	}

	void applyUpload(Project project){
        project.plugins.apply('maven-publish')
        project.configurations { deployerJars }
        project.dependencies { deployerJars "org.apache.maven.wagon:wagon-ssh:2.2" }
        def canUpload = project.hasProperty('uploadable')
        project.publishing {
            publications {
                maven(org.gradle.api.publish.maven.MavenPublication) {
                    groupId = project.group
                    artifactId = project.name
                    version = project.version
                    if (project.plugins.hasPlugin('java')) {
                        from components.java
                    }
                    if (project.plugins.hasPlugin('spring-boot')) {
                        pom {
                            parent {
                                groupId "org.springframework.boot"
                                artifactId "spring-boot-starter-parent"
                                version "${project.versions.springBoot}"
                            }
                        }
                    }
                }
            }
            if (canUpload) {
                repositories {
                    maven {
                        def repositoryReleases
                        def repositorySnapshots
                        
                        repositoryReleases = project.hasProperty('rel')
                            ? project.getAt(rel)
                            : 'url1'
                        repositorySnapshots = project.hasProperty('sna')
                            ? project.getAt('sna')
                            : 'url2'
                        url = project.version.endsWith('SNAPSHOT')
                            ? repositorySnapshots
                            : repositoryReleases
                    }
                }
            }
        }
    }
}

consistently leads to the following build fail when using
gradle clean assemble test

* What went wrong:
A problem occurred configuring project ':test-java-multiproject-api'.
> Could not get unknown property 'components' for object of type org.gradle.api.publish.maven.internal.publication.DefaultMavenPublication.

As far as I know, both situations should be roughly the same. The plugin was compiled with the same version as the current project is being executed.
If I ever find out how to package the plugin itself with the project and apply it, I will gladly provide it, however right now I’d have to release the entire plugin which I am definitely not allowed to do.

If this helps anyone in reproducing and helping, please tell me. Thanks a lot in advance.

More information:
By removing afterEvaluate from the apply() method in the upload plugin, the projects builld as normal. This I expected to be a good sign.
However, apparently the applyUpload-Method was never invoked at all, as the maven-publish plugin was not applied to any project at all.
I tried modifying the initial condition - a lot - until I found that using

curProject.pluginManager.withPlugin('java') {
  applyUpload(curProject)
}

actually leads to adding the plugin.
This, in turn, leads back to the initial error that there is an inknown Property components on dhe DefaultMavenPublication.However in this case, it is not during the application of the upload-plugin, but rather the java plugin.

However, and this is confusing in my eyes, the maven-publish plugin is apparently still being applied - adding a println("applied maven-publish to $project.name") statement directly after the plugins.apply('maven-publish') liads to the following output (abbreviated) :

BUILD FAILED in 1s
applied maven-publish to test-project-api

And even more information:
removing all plugin logic and adding this to the parent buildscript directly:

subprojects {
	apply plugin: 'maven-publish'
	publishing {
		publications {
			maven(MavenPublication) {
				from components.java
			}
		}
	}
}

leads to this error message:
> Could not get unknown property 'java' for SoftwareComponentInternal set of type org.gradle.api.internal.component.DefaultSoftwareComponentContainer.

Maybe this helps to understand the problem

complete example (reproducible) attached in file,
build scan at https://scans.gradle.com/s/bl6pd4g5so2i2
using OpenJDK Runtime Environment (AdoptOpenJDK)(build 1.8.0_232-b09)
and gradle wrapper vith version 6.5.1test-java-multiproject.zip (6.8 KB)

This is the expected behavior for what you have.

By default, projects are evaluated before their children. That means you’re trying to utilize components.java, added by the java plugin, before you’ve actually applied the java plugin.

If you’re going to have subprojects {} apply and configure maven-publish with code that requires the java plugin to be applied, you need to actually apply the java plugin there instead of the subproject where it’s too late.

Alternatively, if all of your configuration dependencies are depth first, you can flip the order with evaluationsDependsOnChildren(), such that the child projects would evaluated first, before the subprojects {} executes in the parent project.

1 Like

Thank you for the explanation.
So if I understand you correctly, the following applies:

  • if top-level project only adds configurations such as publishing, it is feasible to configure the project with evaluationDependsOnChildren
  • if top-level project also configures children with, for example, dependencies, the required plugin should be added on top-level (e.g. java or war)

Now the problem is, that the project(s) is/are mixed - some blocks are set to evaluate first, some last.
It is possible to have just one configuration block depend on child project evaluations?

Might it already suffice to create a plugin applied to the root-project, which does

subproject.afterEvaluate {
  publications {
    ...

to circumvent the timing problem?

Yes, I think you understood the concepts. However, here’s another example, because I think it’s helpful to separate the buildscript execution order concepts from the multi-project concepts.

This works:

// build.gradle
apply plugin: 'java'
apply plugin: 'maven-publish'

publishing {
    publications {
        maven(MavenPublication) {
            from components.java
        }
    }
}

This does not work because it tries to configure things added by the plugin before the plugin is applied:

// build.gradle
publishing {
    publications {
        maven(MavenPublication) {
            from components.java
        }
    }
}

apply plugin: 'java'
apply plugin: 'maven-publish'

When working with a multi-project build, code from subprojects {...} / allprojects {...} blocks in parent projects are executed earlier, so it’s approximately the same as the second example.


Now, afterEvaluate {...} is not really preferred because you can’t easily control when one afterEvaluate {...} runs vs. another. A better approach would be to react to the application of plugins that you require. For example, if you don’t need to apply or configure publishing for a Java component unless the java plugin is applied, you can write that explicitly:

subprojects {
    plugins.withId('java') {
        apply plugin: 'maven-publish'

        publishing {
            publications {
                mavenJava(MavenPublication) {
                    from components.java
                }
            }
        }
    }
}

Now, this code will only execute in subprojects after the java plugin applied, and it’s fine if you have child projects that don’t need to apply the java plugin.

1 Like

Thank you for the very detailed explanation. It seems I still have a problem, but that seems to stem from my need to try and extract logic to other places for reusability and readability, as I need to handle java, war and ear projects independently, yet simultaneously (meaning, depending on the project, a different SoftwareComponent has to be used, the rest stays the same).

Can you tell me why the same problem exists when creating a plugin class with the following two distinct methods:

void apply(Project project) {
  project.subprojects { current ->
    current.plugins.withId('java') {
      plugins.apply('maven-publish')
      configurePublication(current)
    }
  }
}

void configurePublication(Project project) {
project.publishing {
  publications {
    maven(MavenPublication) {
      from components.java
    }
  }
}

If i move the contents of configurePublication into the original apply method, everything works as expected.

There’s a difference in how the delegation works once you cut things off from the hierarchy in the method. The components.java won’t be referenced on the project unless you specify this directly.

void configurePublication(Project project) {
    project.publishing {
        publications {
            maven(MavenPublication) {
                from project.components.java // <-- add project
            }
        }
    }
}
1 Like

Thank you so much, that seems to have resolved everything =)