简体   繁体   中英

Gradle multiple jars from single source folder

As for now we have a project structure with single source folder named src , which contains source code for three modules. What I want to do is:

1) Compile source code. This is easily done with sourceSets definition:

sourceSets {
    main {
        java {
            srcDir 'src'
        }
    }
}

2) Put compilation results into three jars. I am doing this via three 'jar' type tasks:

I am doing this now via three separate tasks:

  • util.jar

     task utilJar(type: Jar) { from(sourceSets.main.output) { include "my/util/package/**" } } 
  • client.jar

     task clientJar(type: Jar) { from(sourceSets.main.output) { include "my/client/package/**" } } 
  • server.jar

     task serverJar(type: Jar) { from(sourceSets.main.output) { include "**" } excludes.addAll(utilJar.includes) excludes.addAll(clientJar.includes) } 

The thing is that server.jar should contain all classes that are not contained within client.jar and util.jar . In ant build script we solve this problem by using difference ant task. How this can be done in gradle (my current approach doesn't work)?

Maybe my approach is completely wrong. Please advice.

PS as for now we CAN NOT change the project source code folder structure.

I will post my working solution here as an answer (I've got a hint on gradle's forum).

The scopes in gradle are very strange thing :) I thought that every task definition creates an object of some 'Task' class, which is something like 'JarTask' in this particular case. Then I can access any property of the class from anywhere in my build.gradle script. However, I found the only place where I can see the patterns, which are included in jar file - inside a from block of a task. So my working solution for now is to:

1) Define a project-level collection to contain patterns to be excluded from server.jar

2) Exclude all patterns in from block of serverJar task.

Please see final version below

sourceSets {  
    main {  
        java {  
            srcDir 'src'  
        }  
    }  
} 

// holds classes included into client.jar and util.jar, so they are to be excluded from server.jar
ext.serverExcludes = []

// util.jar
task utilJar(type: Jar) {  
    from(sourceSets.main.output) {  
        include "my/util/package/**" 
        project.ext.serverExcludes.addAll(includes)
    }  
}

// client.jar
task clientJar(type: Jar) {  
    from(sourceSets.main.output) {  
        include "my/client/package/**"
        project.ext.serverExcludes.addAll(includes)
    }  
}

// server.jar
task serverJar(type: Jar) {  
    from(sourceSets.main.output) {  
        exclude project.ext.serverExcludes
    }  
}

I think the approach is wrong. I recommend making a project with 3 sub projects.

project
- util
- server (depends on util)
- client (depends on util)

If for some reason you cannot change the class structure use this kind of build files:

settings.gradle

include 'util', 'client', 'server'

build.gradle

subprojects {
    apply plugin: 'java'
}

project(':util') {
    sourceSets {
        main {
            java {
                srcDir '../src'
                include 'util/**'
            }
        }
    }
}

project(':server') {
    sourceSets {
        main {
            java {
                srcDir '../src'
                include 'server/**'
            }
        }
    }
    dependencies {
        compile project(':util')
    }
}

project(':client') {
    sourceSets {
        main {
            java {
                srcDir '../src'
                include 'client/**'
            }
        }
    }
    dependencies {
        compile project(':util')
    }
}

You still need directories for subprojects but the sources are in one place as you wanted.

When you run gradle assemble you will have 3 jars with separate set of classes. The advantage of this solution is that we make a proper Gradle multi module project with correct dependencies, not just tasks for building jars.

Please read Multi-Project Builds .

We have the same problem at my company, ie. legacy code that is difficult to migrate into a "good" project structure, and the need to build several jars from the same codebase. We decided to define different sourceSets and build each of the sourceSets using standard Gradle.

We then use iterators to add jar- and javadoc-tasks for each sourceSet:

sourceSets.all { SourceSet sourceSet ->
    Task jarTask = tasks.create("jar" + sourceSet.name, Jar.class)
    jarTask.from(sourceSet.output)
    // Configure other jar task properties: group, description, manifest etc

    Task javadocTask = tasks.create("javadoc" + sourceSet.name, Javadoc.class)
    javadocTask.setClasspath(sourceSet.output + sourceSet.compileClasspath)
    javadocTask.setSource(sourceSet.allJava)
    // Extra config for the javadoc task: group, description etc

    Task javadocJarTask = tasks.create("javadocJar" + sourceSet.name, Jar.class)
    javadocJarTask.setClassifier("javadoc") // adds "-javadoc" to the name of the jar
    javadocJarTask.from(javadocTask.outputs)
    // Add extra config: group, description, manifest etc
}

I agree in principal with the accepted answer too. I found a project where the client requires two JAR essentially of the same file except the Manifest is different only by the Class-Path key.

jar {
    manifest {
        attributes(
                "Main-Class": platformMainClass,
                "Implementation-Title": platformDisplayName,
                "Implementation-Description": platformDescription,
                "Platform-Version": platformVersion,
                "Implementation-Version": version,
                "Build-Assembly-User": System.getProperty("user.name"),
                "Build-Assembly-Date": new java.util.Date().toString(),
                "Class-Path": configurations.compile.collect { "lib/"+it.getName() }.join(' ')
        )
    }

    duplicatesStrategy = DuplicatesStrategy.EXCLUDE

    exclude( [ 'log4j*.properties', 'uk/gov/acme/secret/product/server/**' ])
}

The same manifest and the source code then is:

task applicationClientJar(type: Jar, description: "Creates the Application  Client JAR file.") {
    dependsOn compileJava
    manifest {
        attributes(
                "Main-Class": platformMainClass,
                "Implementation-Title": platformDisplayName,
                "Implementation-Description": platformDescription,
                "Platform-Version": platformVersion,
                "Implementation-Version": version,
                "Assembly-Date": new java.util.Date().toString()
        )
    }
    archiveName = "acme-client-${platformVersion}.jar"
    destinationDir = file("${buildDir}/libs")
    from sourceSets.main.output

    duplicatesStrategy = DuplicatesStrategy.EXCLUDE

    exclude( [ 'log4j*.properties', 'uk/gov/acme/secret/product/server/**'     }

So Grzegorz notation is correct, because the Gradle should know there are two different JAR with GAVs. Multi-module is the preferred option.

compile "uk.gov.acme.secret:acme:1.0"  // CORE
compile "uk.gov.acme.secret:acme-client:1.0"

The only way to configure for this is to use the Multi-Module Gradle project and then add a compile and/or deploy dependency to the core / main project.

project(':common:acme-micro-service-webapp') {
    dependencies {
        compile project(':common:acme-core')
    }
}

Inside the 'acme-micro-service-webapp' project, this ensures that the dependent 'common:acme-core' is compiled first.

PS: I am still trying to figure out a better solution.

PS PS: If you are using Maven as well as, it may be possible to hook on the `install' task.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM