Multi-project reusing tasks etc

So, I’ve really struggled with the way Gradle defines and uses multi-projects.

Most likely this is partly me being a total newb on Groovy/Gradle - but I really have a hard time getting stuff that should be simple to work.

Simple task: Define an anttask in the parent-project, so it can be used by all the children.

Project Structure:

include :a, :a:b, :a:b:c, :a:z

a.gradle

configurations { xmlbean }
dependencies { xmlbean 'org.apache.xmlbeans:xmlbeans:2.6.0' }
ant.taskdef(name: 'xmlbean', classname: 'org.apache.xmlbeans.impl.tool.XMLBean', classpath: configurations.xmlbean.asPath)

Can I then use ant.xmlbean() {} in b, c and z? No.

Can I iterate over all the projects, and define the task everywhere - yes - but, that is not truly reusing it, that is writing code that duplicates it everywhere.

What am I missing?

I run into stuff like this, almost when-ever I try to follow any of the guides from the user-guide, when having to “share some task or definition” between projects.

Any help will be dearly appreciated.

this is done by using Gradle Custom plugins.

It is not specified that task in a parent project shall be accessible in subprojects directly.

Either I misunderstand your answer - or it’s not an answer at all.

In a multi-project build, I would require, that I’m able to define common behaviour, tasks, macros, functions etc. in a hiearchical way, and hopefully reuse a lot.

One of the main selling points of Gradle is, that you don’t have to “code in XML”, but if I can’t express the most basic “shared state” in a feasible way - I’m in trouble.

Even defining which repositories to use, have to be done in each project specifically - no “inheritance” there, unless I again, iterate over all the projects, and setup the repository.

I think I’ve might have found a way to “move on”, but the pit in my stomach growls, and my love for Gradle fades.

I really hope it’s just me not understanding Gradle - and not Gradle that suck big time.

Leveraging a “high-level-programming language” as Groovy, code-reuse instead of code-copy should be a very simple task.

Read the [organising build logic] (https://docs.gradle.org/current/userguide/organizing_build_logic.html) chapter in the gradle documentation. It discusses a few approaches to sharing build logic

Well, having a multi project build does not imply at all that the configuration of the parent project shall be shared with the children projects. As pointed by @Lance_Java, properties and method are shared though. The multi-project build section can help you understand exactly what is (and what is not) a multi-project in the Gradle world.

That being said, you can use the allprojects or subprojects set to centralize configuration common to all projects.
Or you can develop a custom plugin, being applied to all your projects.
Or you can put your stuff in buildSrc directory ,instead of writing and publishing a plugin.

Maybe with a little more insight on what you’re trying to achieve, we can help you do it. Don’t give up on Gradle yet !

Thanks for the reference @Lance_Java, it gave me some more options at least, though it sort of feels like I need to read too much, to do something simple.

And writing my own task-class to share an ant.taskdef, seems a bit “heavy” - but it is probably the “best option” atm.

The whole frustration originated from this example: https://docs.gradle.org/current/userguide/ant.html#N113CF

But I simply wanted to have the ant.taskdef in one place, and not in all projects.

The taskdef as mentioned in the original post, is to invoke the xmlbeans anttask from org.apache.xmlbeans.

To “inject” the correct classpath in the taskdef using the gradle dependency resolution seemed something that should be simple, but to me wasn’t.

I now see, that the “best approach”, most likely is, to define a custom task, that encapsulates the common/shared configuration, and then use it in the various projects.

Is there a reason why you can’t use an allprojects or subprojects closure as suggested by @FrançoisGuillot?

Defining a closure as a variable is another way to share the logic. Defining it in an allprojects block is essentially like making a method:

allProjects {
    project.ext.someCommonLogic = { String arg1, String arg2 ->
        ant.taskdef{}
    }
}

//Inside Subproject:
project.someCommonLogic("1", "2")

Kinda quick and dirty, but sometimes quick and dirty works…
If you have logic that has to be invoked in a particular scope (as opposed to say, just defining a task), you might need to ‘invoke’ it against the right context, but should be workable

Thanks for all the replies.

They made me understand the different closures a bit better (I think).

I ended up doing this:
parent-project.gradle

configurations {
    xmlbean
}
dependencies {
    xmlbean 'org.apache.xmlbeans:xmlbeans:2.5.0'
}

def createXmlBeanTask(def proj, def name, def schema) {
    proj.ant.taskdef(name: 'xmlbean', classname: 'org.apache.xmlbeans.impl.tool.XMLBean', classpath: rootProject.configurations.xmlbean.asPath)
    def buildDir = proj.buildDir
    def jar = "$buildDir/libs/${name}.jar"
    def jarFile = file(jar)
    def t = proj.tasks.create("generateBeans" + name.capitalize()) {
        outputs.files files(jarFile)
        inputs.files schema
        doLast {
            printf "Generating beans for ${name}\n"
            printf "- input: $inputs.files.asPath\n"
            printf "- output: $outputs.files.asPath\n"
            ant.xmlbean(
                    destfile: jar,
                    srcgendir: "$buildDir/generated/${name}",
                    classpath: rootProject.configurations.xmlbean.asPath,
                    classgendir: "$buildDir/xmlbeans/${name}"
            ) {
                schema.each { f ->
                    fileset(dir: f.parentFile) {
                        include(name: f.name)
                    }
                }
            }
        }
    }
    proj.compileJava.dependsOn += t
    proj.sourceSets.main.runtimeClasspath += t.outputs.files
    proj.sourceSets.main.compileClasspath += t.outputs.files
    proj.artifacts.archives(jarFile)
    return t
}

child-project.gradle:

createXmlBeanTask(project, 'processor', files("src/resources/Processor.xsd"))
def t = createXmlBeanTask(project, 'oioxml-pie-pcm', files(
        "$buildDir/generated/oioxml-schemas/pieStrict.xsd",
        "$buildDir/generated/oioxml-schemas/pcmStrict.xsd"))
t.dependsOn += unzipOioXmlSchemas

I’d rather have defined a task-type in the parent-project, but since sharing that between subprojects meant either making it into a stand-alone gradle plugin (seemed overkill), or placing it in a specific buildpath (buildSrc) - which would “clutter” our project-structure, I ended up with the above approach.

I dislike having to pass the project into the method, I dislike the way the method injects stuff directly into the child-project, and I dislike some of the file-passing - but hey, it works, and I get the added value from gradle compared to ant reg. “UP-TO-DATE”.

If any of you have a cleaner/nicer solution, I’d be glad to hear it.

Do you realise that the subprojects and allprojects closures can accept the current project as an argument?

eg:

subprojects { Project subProject ->
    println "Doing stuff with $subProject.name"
    doStuff(subProject)
}

API Documentation here

Yes, but the multi-project I’m trying to port from ant, make, bash, etc. is quite large, with many sub-modules, so using subprojects in the parent-project will not help divide and conquer the configuration/building process.

I’m aiming at simplifying the configuration for each project, not gathering it all in one monolithic gradle file.

So, what I need, is more like adding “general capabilities” to our environment (I will most likely use configurations a lot, and some added functionality to the taskGraph).

But I’d like the sub-projects to be as clear as possible reg. dependencies, build-process and outputs/artifacts.

Sounds like you want a plugin + pluginConvention

Perhaps.

But all I wanted was, to invoke an anttask during the generateSource “phase”, and having to write a new gradle-plugin to accomplish that, seemed bloated to me.

It’s not that heavyweight to write a plugin… example plugin & convention here