Gradle: Include the same SourceSet in Multiple subprojects - gradle

How would I add a single source set to multiple subprojects?
First of all ... yes I know how ridiculous this is. This is just something I have to do.
The setup
The project uses the Groovy DSL.
There are 3 subprojects (A,B,C), each with there own unique main source set.
There are 5 additional sourcesets (1, 2, 3, 4, 5) external to these projects.
Nonce of the external sourcesets can be compiled alone.
All of the source sets depend on an interface that is defined 3 different times in each subproject.
The subproject main source cannot depend on any of the external sources
1 and 2 need to be compiled with A, B, and C.
3 needs to be compiled with A and also B.
4 needs to be compiled with B and also C
5 needs to be compiled with C only.
4 and 5 need depend on a class defined in 2.
5 must be a standalone sourceset so that it can be included as a sourceset inside of any future subprojects that might be added.
None of the external sources are allowed to include sources from any other sourceset
None of the external sources are allowed to be compiled alone.
None of the external sources are allowed to be included as a jar or project dependency; they MUST be included as a source set and they MUST be compiled seperately for each subproject that includes them.
A
sourceSets {
main {
java {
srcDirs = ["src",
"$rootDir/source_sets/1/src",
"$rootDir/source_sets/2/src",
"$rootDir/source_sets/3/src"
]
}
}
}
B
sourceSets {
main {
java {
srcDirs = ["src",
"$rootDir/source_sets/1/src",
"$rootDir/source_sets/2/src",
"$rootDir/source_sets/3/src",
"$rootDir/source_sets/4/src"
]
}
}
}
C
sourceSets {
main {
java {
srcDirs = ["src",
"$rootDir/interfaces/source_sets/1/src",
"$rootDir/interfaces/source_sets/2/src",
"$rootDir/interfaces/source_sets/4/src",
"$rootDir/interfaces/source_sets/5/src"
]
}
}
}
settings.gradle
include(":interfaces/A")
project(":interfaces/A").name = "A"
include(":interfaces/A")
project(":interfaces/A").name = "A"
include(":interfaces/A")
project(":interfaces/A").name = "A"
The problem is that 4 and 5 are not able to find the class in 2, and my IDE (IntelliJ) cannot resolve the correct classpath.
Really what I need is for the external sourcesets to act as if there were 3 separate copies of them without there actually being 3 separate copies, and I need to do it without the use of symbolic/soft links.
The solution needs to only use gradle, but it can use JetBrains "idea" plugin for gradle so long as it doesn't involve committing any files under the ".idea" folder, but it can include inline xml or files in a resource folder outside of the .idea folder.
So yeah ... this is overly complicated and just .. ugh! But that's just how it is.

Ugh indeed.
I don't have an answer, but this is too long to put into a comment. So here goes.
I assume this is a problem with IntelliJ only, and not when compiling with Gradle, right? If that is the case, you should try and model your project in IntelliJ as you want it, and once you have found a way to do it, then figure out how to use customize the Idea plugin to do it for you.
However, I am pretty sure you can't have multiple modules in IntelliJ share the same "content root". So I only see the options left that you don't want - which is either to copy (synchronize) the sources with a new folder only used for IntelliJ (which won't allow for modifications), create symlinks (which aren't always portable) or to restructure your external sources so they can be compiled independently (which may not be easily possible)
:-(

Related

Manage generated resources for project in Gradle

I have a Gradle project which has a lot of resources in the /src/main/resources folder. For performance reasons, some resources have to be generated from other resources at build time. So I split everything up into sub-projects:
/MainProj/src/main/java - The application
/MainProj/src/main/resources
/InternalProj/src/main/java - Code for generating additional resources for MainProj
/InternalProj/src/main/resources
When I run InternalProj using a special Gradle task, it generates some files to /InternalProj/output which I then copy to /MainProj/src/main/resources. Needless to say, this is really ugly and I'd like to know how to do this in a better way. Should I put it somewhere into build or directly into /MainProj/src/main/resources? Maybe use a symlink?
Edit
I now use a relative symlink from /src/main/generated-resources to /build/something and so far it works fine. But now I have a different problem: I have task A that generates some resources and task B that depends on those resources and thus on A. If I run gradle B or gradle A B, B will still fail since the resources generated by A didn't get updated into its build folder. How can I force Gradle to update the resources?
You can add the output file to the main source set of the InternalProj:
Example Groovy Code:
sourceSets {
main {
resources {
srcDirs "src/main/resources", "output"
}
}
}
Related answer: https://stackoverflow.com/a/38982006/3708426

How do I use Gradle to build a special JAR with only a subset of classes?

I have been given a project A that needs access to class files from another project B. More precisely, A only needs classes compiled from the B/ejb/C/src portion of the B/ tree:
B/ejb/C/src/com/company/admin/Foo.java
B/ejb/C/src/com/company/admin/FooHome.java
B/ejb/C/src/com/company/admin/FooBean.java
B/ejb/NOTNEEDED/src/com/company/data/...
The person who had this A project before used JBuilder and included in the source definition pointers to the parallel project's B/ejb/C/src. The A project builds a jar which includes classes compiled from this other tree. I'm trying to figure out how to do this using Gradle. I want to make a B/build.gradle in the B project that will create a B-C-version.jar of .class files compiled from these sources:
B/ejb/C/src/com/company/admin/Foo.java
B/ejb/C/src/com/company/admin/FooHome.java
B/ejb/C/src/com/company/admin/FooBean.java
that I would then publish to Maven and access from the A project.
i.e., the B-C-version.jar would ideally only have these classes:
com/company/admin/Foo.class
com/company/admin/FooHome.class
but if B-C-version.jar had these classes:
com/company/admin/*.class
that would also be OK. How can I make such a thing using a build.gradle in the B project?
You can simply declare a custom Jar task like
task cJar(type: Jar) {
baseName = project.name + '-C'
from sourceSets.main.output
include 'com/company/admin/Foo.class', 'com/company/admin/FooHome.class'
}
or you can make a dedicated sourceset for your api that you then use from your other B code and from your A code, then you don't need to work with includes and update the include if you need to add files, but you just place them in the source folder of the source set and you are done, something like
sourceSets { c }
task cJar(type: Jar) {
baseName = project.name + '-C'
from sourceSets.c.output
}
Then you could also declare dependencies separately and get the correct ones drawn in transitively and so on. But it might be overkill in your situation.

Gradle dependency on module of another project failing

I realize there are a lot of posts online regarding Gradle setup. That being said, I have researched heavily and not found exactly what I'm looking for, or I'm using incorrect terms to do so. I'm using Gradle version 3.3.
So I've got multiple Gradle projects, each of which is maintained separately. There is no master Gradle project. Each projects has its own modules, build, and settings file. The structure of this is as so:
Projects
A
a1
build.gradle
a2
build.gradle
build.gradle
settings.gradle
B
b1
build.gradle
b2
build.gradle
build.gradle
settings.gradle
What I'm attempting to do is make B dependent on A's modules. Let's assume one of the modules in B is dependent on a1. In B's settings, I've done the following:
rootProject.name = 'B'
rootProject.setProjectDir(new File(".")
include 'a1'
project(':a1').setProjectDir(new File(settingsDir.getParentFile(), "/A/a1"))
The way I'm storing version numbers is through each project's build.gradle file in the ext closure. I then access them through the project. Here's how B's build file looks:
ext {
freemarkerVersion = '2.3.19'
}
dependencies {
compile project(':a1')
compile group: 'org.freemarker', name: 'freemarker', version: rootProject.properties.get('freemarkerVersion')
}
What I'm seeing is B is able to resolve its dependencies and is attempting to compile project a1, but it is using B's version numbers instead of A's. I verified this by putting a common dependency in both projects with different version numbers. The dependency showed up using B's version. I also changed the version number in B and further confirmed this. So if I could get any help for using project-appropriate versions in each of their own build.gradle files, that would be great!
EDIT: Updated post, figured out previous problem was from relative path not resolving.
Not to toot my own horn or anything, but I'm posting the solution I came up with, as I had no other answers knocking down my door.
The first thing I did was moved all of my version properties to an external gradle.properties file, instead of in an ext closure in the project's build.gradle file. Doing this, the project will load the properties file by default when compiled from its own context. It will of course be overridden from the user.home gradle.properties file, so keep this in mind. Example gradle.properties:
a_freemarkerVersion = 2.3.19
When using one project's modules from another project, you'll need a way to separately link the two so Gradle can resolve the dependency's properties. I achieved this by defining a method to load in the desired project's properties file. This method looks like so:
def addConfig(String parent, String filename) {
Properties props = new Properties()
props.load(new FileInputStream(new File(project.projectDir.getParent(), "/${parent}/${filename}")))
props.each { prop ->
project.ext.set(prop.key, prop.value)
}
}
addConfig("A", "gradle.properties")
All this method does is goes up one directory, goes into the project specified, and retrieves its gradle.properties file and loads these variables into the current project's properties. With that being said, there's one thing to note here: if you define the same variable in both, one of them will be overridden. To avoid this, I just prefixed all variables with the project name and then an underscore. This will guarantee they'll never conflict with one another.
I accessed the variables in all projects with this syntax:
dependencies {
compile group: 'org.freemarker', name: 'freemarker', version: "${a_freemarkerVersion}"
}
The rest of the setup is the same as I defined in my initial post. Just make sure to include the dependent module, specify its project directory, and compile that project from within the project's dependencies.

Gradle embedded Project

Is it possible to embed multiple projects in a single build.gradle?
Something along the lines of:
project projX {
task a << {
}
}
project projY {
task a << {
}
}
Both within the same build.gradle. Is this possible?
I am asking this because I have multiple projects with equivalent task names which I want to execute from the root project, e.g.
gradle a
However the projects contain only automation tasks, which require no source files or resource files at all. Creating subdirectories just for the build.gradle files to be stored seems very ugly to me.
I could live with a solution with different .gradle files for each project, such as: build.gradle (root)
projA.gradle, projB.gradle within the same directory, however embedding project objects in the root build.gradle seems like the better option, if it is available.
project(":projX") { ... }
project(":projY") { ... }
Note that you still need a settings.gradle.
PS: It's not clear to me why you would want multiple projects in your case.

Gradle - can I include task's output in project dependencies

I have a task that generates java sources and a set of jars from these sources (say, project a). I would like to export these jars to dependent projects (say, project b). So here's roughly what I have right now:
//a.gradle
configurations{
generatedJars
}
task generateJars(type: JavaExec) {
//generate jars ...
outputs.files += //append generated jars here
}
dependencies{
generatedJars generateJars.outputs.files
}
//b.gradle
dependencies{
project(path: ':a', configuration: 'generatedJars')
}
It works OK, except that adding generateJars.outputs.files as a dependency does not tell gradle that it has to run generateJars task when there are no jars generated yet. I have tried adding the task itself as a dependency hoping that it would work in the same way as it does when you add a jar/zip task to an artifact configuration (e.g. artifacts{ myJarTask }), but it throws an error telling me that I cannot do that. Of course I can inject the generateJars task somewhere in the build process before :b starts evaluating, but that's clumsy and brittle, so I would like to avoid it.
I feel like I should be adding the generated jars to artifacts{ ... } of the project, but I am not sure how to make them then visible to dependent projects. Is there a better way of achieving this?
Dependent projects (project b) will need to do setup IntelliJ IDEA module classpath to point to project a's generated jars. Something rather like this (pseudo-code):
//b.gradle
idea{
module{
scopes.COMPILE.plus += project(path: ':a', configuration: 'generatedJars').files
}
}
So far I have tried simply adding a project dependecy on :a's generatedJars in :b, but Idea plugin simply adds module :a as a module-dependency and assumes that it exports its generated jars (which is probably a correct assumption), therefore not adding the generated jars to :b's classpath.
Any help would be greatly appreciated!
First, do you need a separate configuration? That is, do you have clients of a that should not see the generated Jars? If not, you can add the generated Jars to the archives configuration, which will simplify things.
Second, the correct way to add the generated Jars to the configuration is (instead of the dependencies block):
artifacts {
generatedJars generateJars
}
This should make sure that the generateJars task gets run automatically when needed.
Third, I'd omit the += after outputs.files, although it might not make a difference. You should also add the necessary inputs.
Fourth, why do you need a JavaExec task to generate the Jars? Can you instead add the generated sources to some source set and let Gradle build them?
Fifth, IDEA doesn't have a concept corresponding to Gradle's project configuration dependencies. Either an IDEA module fully depends on another module, or not at all. You have two options: either use a module dependency and make the generated sources a source folder of the depended-on module (preferably both in the Gradle and the IDEA build), or pass the generated Jars as external dependencies to IDEA. In either case, you should probably add a task dependency from ideaModule to the appropriate generation task. If this still doesn't lead to a satisfactory IDEA setup, you could think about moving the generation of the Jars into a separate subproject.
For my use case, I had a C++ project which generated some native libraries which my java project needed to load in order to run.
In the project ':native' build.gradle:
task compile(type: Exec, group: 'build') {
dependsOn ...
outputs.files(fileTree('/some/build/directory') {
include 'mylib/libmy.so'
})
...
}
In project java application build.gradle:
configurations {
nativeDep
}
// Add dependency on the task that produces the library
dependencies {
nativeDep files(project(':native').tasks.findByPath('compile'))
}
// Unfortunately, we also have to do this because gradle will only
// run the ':native:compile' task if we needed the tasks inputs for another
// task
tasks.withType(JavaCompile) {
dependsOn ':native:compile'
}
run {
doFirst {
// Use the configuration to add our library to java.library.path
def libDirs = files(configurations.nativeDep.files.collect {it.parentFile})
systemProperty "java.library.path", libDirs.asPath
}
}

Resources