Gradle: how to make rule-created ZipTask as maven publication artifact - gradle

I want to create a maven publication from inside a RuleSource that will be published via the maven-publish plugin. The artifacts of the publication are the outputs from a series of Zip tasks that are created from rules. When I try to add the artifacts, I get a circular rule exception.
Here is my very simple build.gradle:
buildscript {
repositories {
mavenCentral()
}
dependencies {
}
}
task wrapper(type: Wrapper) {
gradleVersion = '3.3'
}
apply plugin: 'groovy'
apply plugin: 'testpub'
repositories {
mavenCentral()
}
dependencies {
compile 'org.codehaus.groovy:groovy-all:2.4.7'
}
The testpub plugin exists in the buildSrc directory. To be able to apply it as above, it requires the following properties file:
// buildSrc/src/main/resources/META_INF/gradle-plugins/testpub.properties
implementation-class=TestPubPlugin
Here is the very simple plugin file:
import org.gradle.api.Project
import org.gradle.api.Plugin
import org.gradle.model.RuleSource
import org.gradle.api.Task
import org.gradle.model.Mutate
import org.gradle.model.Finalize
import org.gradle.api.tasks.bundling.Zip
import org.gradle.model.ModelMap
import org.gradle.api.publish.PublishingExtension
import org.gradle.api.publish.maven.MavenPublication
class TestPubPlugin implements Plugin<Project> {
void apply(Project project) {
project.configure(project) {
apply plugin: 'maven-publish'
publishing {
repositories {
maven {
url "someUrl"
}
}
}
}
}
static class TestPubPluginRules extends RuleSource {
#Mutate
public void createSomeTasks(final ModelMap<Task> tasks) {
5.times { suffix ->
tasks.create("someTask${suffix}", Zip) {
from "src"
destinationDir(new File("build"))
baseName "someZip${suffix}"
}
}
}
#Mutate
public void configurePublishingPublications(final PublishingExtension publishing, final ModelMap<Task> tasks) {
// Intention is to create a single publication whose artifacts are formed by the `someTaskx` tasks
// where x = [0..4]
publishing {
publications {
mavPub(MavenPublication) {
tasks.matching {it.name.startsWith('someTask')}.each { task ->
artifact(task)
}
}
}
}
}
}
}
The plugin creates a number of tasks called someTaskx where x=[0..4]. They simply zip up the src directory. I want to add the output files as artifacts to the single MavenPublication. However, I get the following exception:
* What went wrong:
A problem occurred configuring root project 'testpub'.
> A cycle has been detected in model rule dependencies. References forming the cycle:
tasks
\- TestPubPlugin.TestPubPluginRules#createSomeTasks(ModelMap<Task>)
\- MavenPublishPlugin.Rules#realizePublishingTasks(ModelMap<Task>, PublishingExtension, File)
\- PublishingPlugin.Rules#tasksDependOnProjectPublicationRegistry(ModelMap<Task>, ProjectPublicationRegistry)
\- projectPublicationRegistry
\- PublishingPlugin.Rules#addConfiguredPublicationsToProjectPublicationRegistry(ProjectPublicationRegistry, PublishingExtension, ProjectIdentifier)
\- publishing
\- TestPubPlugin.TestPubPluginRules#configurePublishingPublications(PublishingExtension, ModelMap<Task>)
\- tasks
What is wrong and how do I fix it?

I don't fully understand why is this a "cycle", but the rule methods always have one mutable part (the subject) and zero or more immutable (the inputs). In your second method, you are passing the publishing as the subject you want to change and the tasks as the input. I thought that would be ok, but obviously it isn't.
You might have tried to switch the method arguments, pass the tasks first and then the PublishingExtension, but you would likely not be able to change it (as gradle docs say it's immutable).
I am not sure what exactly is your use case and there might be an easier solution that doesn't use the rules, or plugin at all. Maybe you could ask another question with the original requirement instead of this specific problem.
But back to your issue. The solution to your problem might be something like this:
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.publish.PublishingExtension
import org.gradle.api.publish.maven.MavenPublication
import org.gradle.api.tasks.bundling.Zip
import org.gradle.model.Defaults
import org.gradle.model.ModelMap
import org.gradle.model.Mutate
import org.gradle.model.RuleSource
class TestPubPlugin implements Plugin<Project> {
void apply(Project project) {
project.configure(project) {
apply plugin: 'maven-publish'
publishing {
publications {
maven(MavenPublication) {
groupId 'com.example'
artifactId 'artifact'
}
}
repositories {
maven {
url "someUrl"
}
}
}
}
}
static class TestPubPluginRules extends RuleSource {
static final def buffer = []
#Defaults
public void createSomeTasks(final ModelMap<Task> tasks) {
5.times { suffix ->
tasks.create("someTask${suffix}", Zip) {
from "src"
destinationDir(new File("build"))
baseName "someZip${suffix}"
}
}
tasks.each { task ->
if (task.name.startsWith('someTask'))
buffer << task
}
}
#Mutate
public void configurePublishingPublications(PublishingExtension extension) {
MavenPublication p = extension.publications[0]
buffer.each { task ->
p.artifact(task)
}
}
}
}
The hack here is to run the mutator of the tasks first (#Defaults phase should run before #Mutate) and save the tasks, so we don't need to ask for them later. Rules can include static final fields, so we use a list here.
Then we run the publication enhancer. The code you have used won't work. It works in the config part, but not in the groovy class. So I have prepared the publication and then just added the artifacts from the buffer.
I ran gradlew publish and got:
Execution failed for task ':publishMavenPublicationToMavenRepository'.
> Failed to publish publication 'maven' to repository 'maven'
> Invalid publication 'maven': artifact file does not exist: 'build\someZip0.zip'
So it seems it's working.

Related

Task 'run' not found in root project

My "build.gradle" file:
apply plugin: 'java'
sourceSets {
main {
java {
srcDir 'src'
}
}
test {
java {
srcDir 'test'
}
}
}
My "Main.java" file in "./src/" directory:
import java.util.*;
import java.io.*;
public class Main {
public static void main(String[] args) {
System.out.println("Hello Gradle");
}
}
I get this error:
Task 'run' not found in root project 'gradleNew'
Sorry for the stupid question... I didn't use Gradle before
The java plugin does not include the run task, which I suppose your gradle build requires somewhere in your app.
Change the java plugin to application and you should be fine.
To verify this, you can go to your project folder inside you terminal and run gradle tasks. You should see the run task listed among other tasks.
I hope that helps.
I just add this code and my app run:
plugins {
id 'application'
}
Try this instead:
group 'com.example'
version '1.0'
apply plugin: 'application' // implicitly includes the java plugin
sourceCompatibility = 1.11 // java version 11
repositories {
mavenCentral()
}
dependencies {
// your dependencies
}
sourceSets {
main {
java {
srcDirs = ['src/main/java']
}
resources {
srcDirs = ['src/main/resources']
}
}
}
I typically use something similar to this for my projects. You can then explicitly create the relevant dirs for src/test code, or sometimes the IDE will do it for you when you point it to the relevant build.gradle file (you can typically restart the IDE and it will pick up the file, or you can open a new project and select the build.gradle file as the project to open if it doesn't identify it right away).

how to use a plugin inside a groovy plugin

I am trying to migrate tasks from build.gradle to a plugin that do it.
In my build.gradle I do this:
plugins {
//id 'java'
id 'war'
//https://plugins.gradle.org/plugin/org.gretty
id 'org.gretty' version '3.0.1'
id "com.github.dkorotych.gradle-maven-exec" version "2.2.1"
}
apply plugin: 'maven'
....
....
prepareFrontEnd (type: MavenExec, dependsOn: build) {
goals 'vaadin:prepare-frontend'
}
task buildFrontEnd (type: MavenExec, dependsOn: prepareFrontEnd) {
goals 'vaadin:build-frontend'
}
Now I am moving this stuff to a plugins:
package com.github.mdre.hgvaadinplugin
import org.gradle.api.Plugin;
import org.gradle.api.Project;
//import com.github.dkorotych.gradle.maven.exec.MavenExec;
class HGVaadinPlugin implements Plugin<Project> {
#Override
void apply(Project project) {
println "Hybrid Gradle Vaaadin plugin."
project.plugins.apply('com.github.dkorotych.gradle-maven-exec')
// project.getPluginManager().apply('gradle-maven-exec-plugin')
project.task('prepareFrontEnd', type: MavenExec){
dependsOn build
doLast {
goal 'vaadin:prepare-frontend'
}
}
}
}
If I try to import the class MavenExec I get this error:
> Task :compileGroovy FAILED
startup failed:
/home/mdre/Proyectos/HGVaadinPlugin/src/main/groovy/com/github/mdre/hgvaadinplugin/HGVaadinPlugin.groovy: 5: unable to resolve class com.github.dkorotych.gradle.maven.exec.MavenExec
# line 5, column 1.
import com.github.dkorotych.gradle.maven.exec.MavenExec;
^
and if I comment the import line, I get this error in the project that use the plugin:
What went wrong:
A problem occurred evaluating root project 'VaadinFlowLab'.
Failed to apply plugin [id 'com.github.mdre.hgvaadinplugin']
No such property: MavenExec for class: com.github.mdre.hgvaadinplugin.HGVaadinPlugin
How could I do this?
Thanks.
Well, I fix it!
I forget to include the dependency class in the final jar. So all of I need was to add this to the build.config:
configurations {
// configuration that holds jars to include in the jar
extraLibs
}
dependencies {
....
....
//necesario para crear un fatJar
extraLibs "gradle.plugin.com.github.dkorotych.gradle.maven.exec:gradle-maven-exec-plugin:2.2.1"
configurations.compile.extendsFrom(configurations.extraLibs)
}
jar {
from {
configurations.extraLibs.collect { it.isDirectory() ? it : zipTree(it) }
}
}
and now it is working!!

How to load Gradle Plugin (with its depenecies) into build.gradle?

I have a project that has two gradle files: build.gradle and myPlugin.gradle
The myPlugin.gradle implemented the Plugin Interface. The plugin also has a dependency on osdetector-gradle-plugin
I added the two gradle files beside each other then I tried to apply myPlugin into build.gradle as follows:
apply from: 'myPlugin.gradle'
However, I have got the following error in myPlugin.gradle file:
Plugin with id 'com.google.osdetector' not found
Here is the code for myPlugin.gradle file:
apply plugin: 'groovy'
apply plugin: 'maven'
repositories {
mavenCentral()
mavenLocal()
}
dependencies {
compile 'com.google.gradle:osdetector-gradle-plugin:1.4.0'
}
import org.gradle.api.tasks.TaskAction
import org.gradle.api.DefaultTask
import org.gradle.api.Plugin
import org.gradle.api.Project
apply plugin: 'com.google.osdetector'
apply plugin: HostingMachineOSPlugin
class HostingMachineOSPlugin implements Plugin<Project>{
void apply(Project project){
project.plugins.apply("com.google.osdetector");
//project.configurations.files('com.google.osdetector')
println project.osdetector.os
/* Extend the project property to have the class HostingMachineOS */
project.ext.HostingMachineOS = HostingMachineOS
}
}
public class HostingMachineOS {
static family = "Unkown"
static def setFamilyName(name){
family = name
}
static def isLinux (){
family == "linux"
}
static def isWindows (){
family == "windows"
}
static def isMacOS(){
family == "osx"
}
}
HostingMachineOS.setFamilyName(osdetector.os)
in build.gradle file: I am just doing something like this:
//define buildScript repositories and dependencies then
apply from: 'myPlugin.gradle'
task dummy{
println HostingMachineOS.isMacOS()
println HostingMachineOS.isLinux()
println HostingMachineOS.isWindows()
}
How can I solve the Plugin with id 'com.google.osdetector' not found?
This is a common pitfall, to add a plugin to build.gradle file you need to add a dependency for the build script itself - not for the project. The following piece of code (added in the file where you apply the plugin) should solve the problem:
buildscript {
repositories {
mavenCentral()
mavenLocal()
}
dependencies {
classpath 'com.google.gradle:osdetector-gradle-plugin:1.4.0'
}
}
EDIT
Please have a look here - it seems that if you need to apply from third-party script you need to use the full class name (with package). So the files should be defined as follows:
build.gradle
apply from: 'myPlugin.gradle'
task dummy{
println HostingMachineOS.isMacOS()
println HostingMachineOS.isLinux()
println HostingMachineOS.isWindows()
}
myPlugin.gradle
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.google.gradle:osdetector-gradle-plugin:1.4.0'
}
}
apply plugin: 'groovy'
apply plugin: 'maven'
apply plugin: com.google.gradle.osdetector.OsDetectorPlugin
apply plugin: HostingMachineOSPlugin
class HostingMachineOSPlugin implements Plugin<Project>{
void apply(Project project){
project.plugins.apply(com.google.gradle.osdetector.OsDetectorPlugin);
//project.configurations.files('com.google.osdetector')
println project.osdetector.os
/* Extend the project property to have the class HostingMachineOS */
project.ext.HostingMachineOS = HostingMachineOS
}
}
public class HostingMachineOS {
static family = "Unkown"
static def setFamilyName(name){
family = name
}
static def isLinux (){
family == "linux"
}
static def isWindows (){
family == "windows"
}
static def isMacOS(){
family == "osx"
}
}
HostingMachineOS.setFamilyName(osdetector.os)

Do gradle configurations merge or override?

Say I define a plugin, like so:
import org.gradle.api.artifacts.dsl.RepositoryHandler
import org.gradle.api.artifacts.repositories.MavenArtifactRepository
import org.gradle.api.Project
import org.gradle.api.Plugin
class MyRepos implements Plugin<Project> {
static final String NEXUS_URL = 'http://nexus.mine.com/nexus/content/repositories/'
static final List<String> NEXUS_REPOS = [
'central',
'build',
'snapshots',
'release-candidates',
'releases',
]
void apply(Project project) {
project.repositories {
NEXUS_REPOS.each {
maven {
url (NEXUS_URL + it)
}
}
mavenLocal()
}
project.buildscript {
repositories {
maven {
url (NEXUS_URL + 'central')
}
mavenLocal()
}
}
}
}
and in a local build.gradle, I write
apply plugin: MyRepos
buildscript {
dependencies {
...
}
}
My desire is for the two buildscript sections to be merged such that the repositories are defined in the plugin and the dependencies in build.gradle, but it appears that the local declaration overrides the plugin and I end up with an error indicating "no repositories defined".
If I do this via configuration injection from the top level build.gradle, is the result the same?
Maybe the right solution is for the plugin to provide an ext closure to define useMyRepos() similar to the way mavenCentral() is used...
Generally configuration items in gradle are merged so you can apply configurations in different locations. You can, for example, configure some of the dependencies in one build script that will be applied to another build script that will add additional dependencies.
However, your case is a bit different since you're using buildScript configuration which is can be used to define the dependencies for the build script itself rather than the root repositories node which is intended for defining the dependencies of the project you build.
In this case, according to Purpose of buildScript in Gradle as these are different configurations you'll have to define your dependency twice.

How to apply a Gradle plugin from another plugin?

I'm trying to encapsulate android plugin in my own plugin, but when I'm trying to apply my plugin build fails with an exception:
A problem occurred evaluating root project 'myproj'.
> Failed to apply plugin [id 'com.mycomp.build']
> Failed to apply plugin [id 'android-library']
> Plugin with id 'android-library' not found.
Here is how I'm applying android plugin inside my own plugin's implementation:
// build.gradle
apply plugin: 'groovy'
version = '1.0'
group = 'com.mycomp'
dependencies {
compile gradleApi()
compile localGroovy()
}
// Build.groovy
package com.mycomp
import org.gradle.api.Plugin
import org.gradle.api.Project
class Build implements Plugin<Project> {
void apply(Project project) {
println 'Hello from com.mycomp.Build'
project.beforeEvaluate {
buildscript.configurations.classpath +=
'com.android.tools.build:gradle:1.0.0-rc1'
}
project.configure(project) {
buildscript.repositories.mavenCentral()
apply plugin: 'android-library'
}
}
}
For some reason a classpath is not being properly loaded, what am I doing wrong?
I guess that at the time you'd like to add the plugin dependencies for the build script have been already resolved, thus it won't work that way. You need to specify the plugin You'd like to apply as a script dependency itself.
It will work that way:
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.0.0-rc1'
}
}
apply plugin: 'groovy'
apply plugin: Build
version = '1.0'
group = 'com.mycomp'
dependencies {
compile gradleApi()
compile localGroovy()
}
import org.gradle.api.Plugin
import org.gradle.api.Project
class Build implements Plugin<Project> {
void apply(Project project) {
project.configure(project) {
apply plugin: 'android-library'
}
}
}
Now, android-plugin is found but it fails because of the fact that groovy plugin had been applied earlier and there's a conflict.
Use the project's PluginManager. For example, the war plugin pulls in the java plugin like this:
public class WarPlugin implements Plugin<Project> {
// ...
public void apply(final Project project) {
project.getPluginManager().apply(org.gradle.api.plugins.JavaPlugin.class);
// ...
}
// ...
}

Resources