How do I add a single file to a Java SourceSet?

I’m trying to setup a Gradle build for a source tree that has followed the “big ball of mud” architectural style for many years. One result of this is that the relationship between components and the filesystem is unpredictable, often surprising.

For example, I have defined a “coredb” component that consists of all *.java files from the directories

Databases/Interface/Java

Databases/DatabaseUtilities

Databases/JavaReplication/source

plus one additional file:

DataModels/OrganizationModels/Organization.java

I do not want to pull any other model-related code into the build of coredb. (Eventually I want to fix this weird dependency, but for now I’m trying to create a new build system with the code as it is. Then we can work on fixing our architecture.)

Creating a sourceSet that finds *.java from the specified directories was easy enough. But how do I add one single additional source file from a completely unrelated directory?

The gradle source sets work by pairing set of source directories with a set of include patterns. The ‘main’ source set is an example. However, as the main source set already has include patterns of the type “**/*.java” and as you want these normal include patterns for your other files but not for that one file from a separate location, I think the way to go is to create a separate source set.

Example:

sourceSets {
  external {
    java {
      srcDir "$projectDir/.."
      include "src/main/java/org/thisisatest/Test.java"
    }
  }
  main {
    java {
      compileClasspath += external.classes
    }
  }
}

this creates a second ‘external’ source set and sets the srcDir and include patterns accordingly, in this case including one single Test.java from an external location. No other java files in the “…” tree are included.

Note that the include patterns for the ‘external’ source set are not polluted by the normal **/*.java stuff as we custom created it. Also note that we add the external classes to the compile classpath to force the compiler to pick up that source set.

Hope that helps.

Greg, did the above solution work for you?

No, I cannot get this to work. Here’s how I adapted your suggestion:

sourceSets {

[…]

oddball {

java {

srcDir ‘$projectDir’

include ‘DataModels/OrganizationModels/Organization.java’

}

}

This has no noticeable effect apart from adding various “oddball” tasks. E.g. if I try to force building ‘oddballClasses’, Gradle thinks there is nothing to do:

$ rm -rf .build/classes .gradle && gradle oddballClasses --info

Starting Build

[…]

All projects evaluated.

Starting build for primary task ‘oddballClasses’.

Tasks to be executed: [task ‘:compileOddballJava’, task ‘:processOddballResources’, task ‘:oddballClasses’]

:compileOddballJava

Skipping task ‘:compileOddballJava’ as it has no source files.

:compileOddballJava UP-TO-DATE

:processOddballResources

Skipping task ‘:processOddballResources’ as it has no source files.

:processOddballResources UP-TO-DATE

:oddballClasses

Skipping task ‘:oddballClasses’ as it has no actions.

:oddballClasses UP-TO-DATE

BUILD SUCCESSFUL

And this has no effect on my ‘coredb’ task: it still fails to build because it can’t find the required class that is in the wrong place. (Or the database code has a bad dependency, however you want to look at it.)

did you add the oddball classes to the compile classpath as per my post above? That is what forces them to get dragged into the main compile flow:

main {
    java {
      compileClasspath += oddball.classes
    }
  }

Hi Greg, did you find a solution to yor problem? Did the above work out for you?

Greg, you should change

'$projectDir'

to

"$projectDir"

It would be much more performant to specify the source like: even simpler would be:

from "$projectDir/DataModels/OrganizationModels"
include "Organization.java"

That’s going to require a lot less IO.

1 Like

Detelin – thanks, using double quotes instead of single quotes helped. So quoting in Gradle affects variable expansion like shell or Perl? I didn’t see that in the Gradle docs or in the Groovy tutorial that I skimmed. Did I miss something?

Matias – with Detelin’s tip I was able to get Gradle to see code in my “oddball” component, but this solution does not work. The problem is that we have two top-level components with a circular dependency: our core DB code depends on a model object, and the model object depends on core DB code. I’m trying to implement a decent build system without getting sucked into the endless distraction of fixing our architecture, so I want to trick Gradle into thinking that one model object (Organization.java) is part of our core DB library. No, this is not the right long-term fix, but it lets me concentrate on the build system and let other people worry about the architecture problems.

As I understand it, source sets are top-level components. It’s obviously essential for source sets to be able to depend on each other – eg. “coredb” might depend on “common”, but “models” really should not depend on “coredb”. And dependency cycles between source sets just won’t work – right? So I do not see how your suggestion can work. ;-(

I’m going to try Luke’s idea.

Luke – I’m confused by your suggestion. I don’t understand where I’m supposed to put that “from … include …”. I’ve been through the docs repeatedly, and I don’t see a “from” keyword or method or property or variable. What is it? Where is it documented? And, most importantly, where am I supposed to use it?

I tried using it in a tiny one-file SourceSet:

oddball {

from “$projectDir/DataModels/OrganizationModels”

include “Organization.java”

}

and that doesn’t work:

  • What went wrong: A problem occurred evaluating root project ‘sandbox’. Cause: Could not find method oddball() for arguments [build_nv4ukofdmoqstrhksj0eigimu$_run_closure7_closure10@b98a06] on root project ‘sandbox’.

But I don’t expect it to work, because that file doesn’t compile on its own anyways – dependency cycle with our coredb component. I also tried adding it to the coredb SourceSet at various scopes, but all failed with various mysterious Gradle error messages.

A quick note on the string handling in gradle. Gradle uses groovy as the language for the build files and the double vs single quote handling comes from there.

Groovy strings are described in the Strings and GString section of the groovy user guide. But I have to admit that they don’t do a stellar job of getting to the point…you more or less have to read the whole thing to grok the difference.

An example:

def x = 'World'
  println "Hello $x!"
println 'Hello $x!'

gives:

  $ groovy test.groovy

Hello World!  Hello $x!  

i.e. expressions in double quotes are evaluated whereas expressions in single quotes are not.

Just got back to this problem after a couple of months on other things. None of the suggestions worked, but I found a kludge that did:

sourceSets {
   [...first couple of source sets...]
   coredb {
        output.classesDir = file('.build/classes/coredb')
        java {
            srcDirs = [
                'Databases/Interface/Java',
                'Databases/DatabaseUtilities',
                'Databases/JavaReplication/source',
            ]
        }
        compileClasspath = files([...])
    }
    [...remaining source sets...]
}
// XXX haaaaaaack to avoid fixing architectural problems
compileCoredbJava.options.compilerArgs += [
    'DataModels/OrganizationModels/Organization.java',
]

Obviously this is a total hack, because it means Gradle has no idea that my “coredb” source set is reaching out and grabbing another file to compile behind Gradle’s back. I don’t know what the long-term consequences of that will be, but at least I’m finally able to compile some of our code with Gradle.