Gradle: How to create multiple jar? - java

I'm new to Gradle and Groovy, and I'd hope there would be something to solve my problem.
I have several packages, each of which needs to be compiled into one jar.
One solution I've found is to create multiple tasks that are of Jar type, but I'd like to avoid copy/paste and end up with a giant gradle file whenever I add a new package to my project.
My current implementation is to have multiple jar tasks, like this one :
task jarFoo(type: Jar) {
baseName = "foo"
version = "1.0.0"
String className = baseName.capitalize()
from(sourceSets.main.output) {
include "$baseName/**"
}
from {
configurations.compile.collect {
it.isDirectory() ? it : zipTree(it)
}
}
manifest {
attributes "Implementation-Title": "$className",
"Implementation-Version": "$version",
"Main-Class": "$baseName.$className"
}
}
It works like a charm, however I add packages very often and I will end up with a lot of packages, therefore a lot of tasks and a lot of copied/pasted code.
After fiddling with build.gradle file, I've found that I needed to extend from Jar in order to get a jar created.
So here's the code for the class so far :
class JarTask extends Jar {
String jarName = "default"
String jarVersion = "1.0.0"
#TaskAction
def jar() {
baseName = jarName
version = jarVersion
String className = baseName.capitalize()
// Thanks Opal for reminding that sourceSets
// is defined in project.
from(project.sourceSets.main.output) {
include "$baseName/**"
}
from {
configurations.compile.collect {
it.isDirectory() ? it : zipTree(it)
}
}
manifest {
attributes "Implementation-Title": "$className",
"Implementation-Version": "$version",
"Main-Class": "$baseName.$className"
}
}
}
task jarFoo(type: JarTask) {
jarName = "foo"
}
task jarBar(type: JarTask) {
jarName = "bar"
jarVersion = "1.2.42"
}
The problem is that the jar that is created ignores basically everything in the method: it contains only a MANIFEST.MF containing one line with the manifest version and is given the name of the project, not the name given in task. Nothing else.
If needed, you can find the code online in my GitHub repo (mainly in French).
Any idea would be truly appreciated!

Here is another easier option that allows to pass parameters. I found the inspiration on this topic : https://discuss.gradle.org/t/passing-arguments-to-a-task/8427/20, which sounds exactly like what I was trying to do.
Here we basically define a method that returns a task, given some parameters. The rest is just testing if a version is given, or the code already given in question and adapted with #Opal great help.
It is sufficient to include the new builds in the artifacts block to make tasks available. Then, just run gradle jarqcm to build a single package or gradle assemble to compile everything.
apply plugin: "idea"
apply plugin: "java"
repositories {
mavenCentral()
}
dependencies {
compile "com.intellij:forms_rt:7.0.3"
runtime "com.intellij:forms_rt:7.0.3"
}
def jarPackage(artifactName, artifactVersion) {
if (artifactVersion == "" || artifactVersion == null) {
artifactVersion = "1.0.0"
}
return tasks.create("jar${artifactName}", Jar) {
baseName = artifactName
version = artifactVersion
String className = baseName.capitalize()
from(sourceSets.main.output) {
include "$baseName/**"
}
from {
configurations.compile.collect {
it.isDirectory() ? it : zipTree(it)
}
}
manifest {
attributes "Implementation-Title": "$className",
"Implementation-Version": "$version",
"Main-Class": "$baseName.$className"
}
}
}
artifacts {
archives jarPackage("aleatoire", ""), jarPackage("calculatrice", "1.2.3"), jarPackage("copier", ""),
jarPackage("qcm", "1.0.0")
}

After you edited the question is easy. There's no property sourceSets for the given task (Jar in this case). sourceSets are defined on Project and every task that extends DefaultTask inherits project field from it's parent. So you just need:
from(project.sourceSets.main.output) {
include "$baseName/**"
}
ANSWER
I hope you understand the difference between task configuration and execution phase. This is the problem that occurs here. Basically you extended Jar task which as all tasks of type Copy is not designed to be extended - see here. In task action you configure the artifacts to be copied but.. there's too late for configuration - it's execution phase. To solve the problem task rules may be used. I've modified the script and it's:
apply plugin: "idea"
apply plugin: "java"
repositories {
mavenCentral()
}
dependencies {
compile "com.intellij:forms_rt:7.0.3"
runtime "com.intellij:forms_rt:7.0.3"
}
tasks.addRule('Pattern: build<ID>') { String taskName ->
if (taskName.startsWith('build')) {
task(taskName, type: Jar) {
baseName = taskName - 'build'
version = '1.0.0'
String className = baseName.capitalize()
from(sourceSets.main.output) {
include "$baseName/**"
}
from {
configurations.compile.collect {
it.isDirectory() ? it : zipTree(it)
}
}
manifest {
attributes "Implementation-Title": "$className",
"Implementation-Version": "$version",
"Main-Class": "$baseName.$className"
}
}
}
}
artifacts {
archives project.tasks['buildqcm'], project.tasks['buildlistage'] //etc
}
and should invoked simply with gradle buildlistage buildqcm. You can make additional validation to check if <ID> passed is on the list of packages e.g. Hope that helps and sorry for having to wait so long :/

Related

How to exclude certain packages from API documentation using Dokka

i have two packages "../java/abc"(.java) and "../java/xyz"(.kt)
i need to exclude abc package content in dokka html document
basically i could see both the package are documented in HTML by dokka. after performing
'./gradlew dokkaHtml'
can anybody give me a solution where i can customize/limit the documentation only for 1 package?
i have plugin and dependency in project level build.gradel file
dependencies {
classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.6.21"
}
plugins {
id 'org.jetbrains.dokka' version '1.6.21' apply false
}
my app.gredel file look like below
apply plugin: 'kotlin-parcelize'
apply plugin: 'org.jetbrains.dokka'
tasks.dokkaHtml.configure {
dokkaSourceSets {
configureEach {
perPackageOption {
matchingRegex.set("abc")
suppress.set(true)
}
}
}
}
and I have also tried the below syntax
tasks.dokkaHtml.configure {
dokkaSourceSets {
configureEach {
perPackageOption {
prefix = "abc"
suppress = true
}
}
}
}
Expectation: need to customize the documentation for only 1 package.

How can I use Gradle to configure different environments using a shared source set(main)?

I am trying to use Gradle Source Sets to configure different files for different environments, which is inspired by Gradles integration testing setup documentation.
My goal is to have both fake and prod source sets use interfaces defined in main and provide their own implementations. This is similar if not exactly what you can accomplish with the Android Gradle Plugin's Product Flavors.
For example, here is my build.gradle.kts file:
...
sourceSets {
create("prod") {
compileClasspath += sourceSets.main.get().output
runtimeClasspath += sourceSets.main.get().output
java.srcDirs("src/main/kotlin", "src/prod/kotlin")
}
create("fake") {
compileClasspath += sourceSets.main.get().output
runtimeClasspath += sourceSets.main.get().output
java.srcDirs("src/main/kotlin", "src/fake/kotlin")
}
}
val fakeImplementation by configurations.getting {
extendsFrom(configurations.implementation.get())
}
val prodImplementation by configurations.getting {
extendsFrom(configurations.implementation.get())
}
configurations["fakeRuntimeOnly"].extendsFrom(configurations.runtimeOnly.get())
configurations["prodRuntimeOnly"].extendsFrom(configurations.runtimeOnly.get())
val prodTest = task<Test>("prodTest") {
description = "Runs prod tests."
group = "verification"
testClassesDirs = sourceSets["prod"].output.classesDirs
classpath = sourceSets["prod"].runtimeClasspath
shouldRunAfter("test")
}
val fakeTest = task<Test>("fakeTest") {
description = "Runs fake tests."
group = "verification"
testClassesDirs = sourceSets["fake"].output.classesDirs
classpath = sourceSets["fake"].runtimeClasspath
shouldRunAfter("test")
}
tasks.check { dependsOn(prodTest) }
tasks.check { dependsOn(fakeTest) }
...
compileFakeKotlin doesn't recognize files in the fake source set, particularly on the kaptKotlin task which is defined like so in my dependencies block:
dependencies {
// Dagger
implementation("com.google.dagger:dagger:2.41")
kapt("com.google.dagger:dagger-compiler:2.41")
"kaptProd"("com.google.dagger:dagger-compiler:2.41")
"kaptFake"("com.google.dagger:dagger-compiler:2.41")
}
And here is the error:
> Task :server:compileKotlinCheckIncrementalCompilationAnvil UP-TO-DATE
> Task :server:kaptGenerateStubsKotlin UP-TO-DATE
> Task :server:processResources UP-TO-DATE
> Task :server:compileFakeKotlinCheckIncrementalCompilationAnvil UP-TO-DATE
> Task :server:kaptKotlin FAILED
...[Dagger/MissingBinding] my.package.name.RecipeController cannot be provided without an #Provides-annotated method.
public abstract interface AppComponent { ...
When I put the binding class(FakeRecipeController.kt) in the main source set, it works perfectly. But when it's in src/fake/kotlin, I get this error which leads me to believe the code in that source set isn't being used and I'm unsure how to proceed from here. Thank you.

How can I programmatically add transitive dependencies in Gradle?

I'm pretty (very) new to Gradle and I am evaluating the potential benefits of switching from SBT to Gradle in a Scala project at my current employer. As such I'm not looking to convert an entire build to Gradle right away but it seems it should be possible to dynamically base the Gradle build (atm, primarily compiler flags and dependencies with global versions). This way I don't unnecessarily increase the cognitive load on my colleagues but at the same time I don't run the risk of my build lagging behind or conflicting with the "canonical" configuration in the pom-files.
This is my current build.gradle (so far I have only started to tackle dependencies):
def dependencyVersions = [:]
new XmlSlurper().parse('pom.xml').dependencyManagement.dependencies.dependency.each {
dependencyVersions["${it.groupId}:${it.artifactId}"] = it.version.text()
}
allprojects {
group = 'my.org'
version = 'latest-SNAPSHOT'
}
subprojects {
apply plugin: 'java'
apply plugin: 'scala'
repositories {
mavenLocal()
maven {
url = uri('https://repo.maven.apache.org/maven2')
}
}
dependencies {
implementation 'org.scala-lang:scala-library:2.13.3'
// ... Global deps ...
new XmlSlurper().parse("$projectDir/pom.xml").dependencies.dependency.each {
if(it.groupId.text() == 'my.org') {
add('implementation', project(":${it.artifactId}"))
} else {
def version = it.version.text() ? it.version.text() : dependencyVersions["${it.groupId}:${it.artifactId}"]
def dep = "${it.groupId}:${it.artifactId}:${version}"
def scope = it.scope.text() ? it.scope.text() : 'compile'
if(scope == 'compile')
add('implementation', dep)
else if(scope == 'test') {
add('testImplementation', dep)
} else {
throw new Exception("Unrecognized dependency scope: $scope")
}
}
}
}
sourceCompatibility = '1.8'
tasks.withType(JavaCompile) {
options.encoding = 'UTF-8'
}
}
The above almost works. Direct dependencies are added as they should be but the problem is that transitive dependencies are not available at compile-time. How can I configure the subproject such that any transitive dependencies are resolved and added to the subprojects as well?
If you are intending for a module/subproject to be consumed as a dependency or library from another subproject/project, then you should use the Java Library plugin instead of the Java plugin
With the Java Library plugin, you will have access to the api configuration. You can read more about implementation vs api in API and implementation separation docs.
So your Gradle file could be:
dependencies {
implementation 'org.scala-lang:scala-library:2.13.3'
// ... Global deps ...
new XmlSlurper().parse("$projectDir/pom.xml").dependencies.dependency.each {
if(it.groupId.text() == 'my.org') {
add('api', project(":${it.artifactId}"))
} else {
def version = it.version.text() ? it.version.text() : dependencyVersions["${it.groupId}:${it.artifactId}"]
def dep = "${it.groupId}:${it.artifactId}:${version}"
def scope = it.scope.text() ? it.scope.text() : 'compile'
if(scope == 'compile')
add('api', dep)
else if(scope == 'test') {
add('testImplementation', dep)
} else {
throw new Exception("Unrecognized dependency scope: $scope")
}
}
}
}
The only thing different here is switching from implementation to api.

Omit class file from jar in gradle

I'm writing a gradle plugin that modifies class files. I want the task to run on all the generated class files and replace them. Here's what I have so far:
public class InstrumenterTask extends DefaultTask {
private File outputDir;
private SourceSet sourceSet;
public void setOutputDir(File outputDir) {
this.outputDir = outputDir;
}
public void setSourceSet(SourceSet sourceSet) {
this.sourceSet = sourceSet;
}
#TaskAction
public void processSourceSet() {
File classesDir = sourceSet.getOutput().getClassesDir();
Path classesPath = classesDir.toPath();
Files.walk(classesPath).forEach(this::processClassFile);
}
private void processClassFile(File inputFile) {
// Omitted for brevity. Result is in outputDir
}
}
and my buildscript
apply plugin: 'java'
apply plugin: 'application'
mainClassName = 'user.Main'
jar {
manifest {
attributes 'Main-Class': 'user.Main'
}
}
task(mainInstrumenter, type: InstrumenterTask) {
outputDir = new File(buildDir, 'instrumentedClasses')
sourceSet = sourceSets.main
}
mainInstrumenter.dependsOn classes
jar.dependsOn mainInstrumenter
sourceSets.main.output.dir new File(buildDir, 'instrumentedClasses')
The problem now is that in the compiled jar, the class is there two times. How can I tell gradle not to include the classes in the default classes dir?
Alternatively, can I tell gradle to compile the classes to a different directory and put my classes in the default location?
You can simply, at the end of your task action, call
sourceSet.getOutput().setClassesDir(outputDir);
and store your outputs in outputDir. All the following, dependant tasks will pick up on the newly set classesDir, especially jar and run when the task is a dependency of them.

Fail a Gradle build if inappropriate dependencies are detected?

Using Gradle, I want to ensure appropriate dependency management in a large project such that module A cannot depend on module B (even indirectly). How would I go about doing this?
You can analyze all dependencies and throw an exception if you find a inappropriate one. Here's a code that does it for a specific dependency on classpath:
apply plugin: 'java'
repositories {
mavenCentral()
}
// This is just to have some dependencies on the classpath.
dependencies {
compile 'org.springframework.boot:spring-boot-starter:1.5.1.RELEASE'
compile "org.webjars:webjars-locator:+"
}
// run this task to analyze all deps
task checkDeps {
doLast {
// this closure simply prints a given dependency and throws an exception if slf4j-api is found
def failIfBad = { dep ->
println "CHECKING: $dep.module.id.group:$dep.module.id.name"
if (dep.module.id.name == 'slf4j-api') {
throw new GradleException("$dep.module.id.group:$dep.module.id.name on classpath!")
}
}
// this is a closure with recursion that calls the above failCheck on all children and their children
def checkChildren
checkChildren = { dep ->
if (dep.children.size() != 0) {
dep.children.each { child ->
failIfBad(child)
checkChildren(child)
}
}
}
// this is how you get all dependencies in the compile scope, iterate on the first level deps and then their children
configurations.compile.resolvedConfiguration.firstLevelModuleDependencies.each { firstLevelDep ->
failIfBad(firstLevelDep);
checkChildren(firstLevelDep);
}
}
}
The whole code could probably be optimized and more sophisticated rules will be necessary, but it should give you what you need to get started.

Categories