As of IDEA 2018.2.1, the IDE starts error-highlighting packages "not
in the module graph" from dependencies that have been modularized. I added a module-info.java
file to my project and added the
requisite requires
statements, but I'm now having trouble accessing
resource files in my src/main/resources
directory.
(For a complete example, see this GitHub project.)
When I used ./gradlew run
or ./gradlew installDist
+ the resulting
wrapper script, I was able to read resource files, but when I ran my
app from the IDE, I wasn't.
I filed an issue
with JetBrains, and what I learned was that IDEA was using the module
path, while Gradle, by default, was using the classpath. By adding
the following block to my build.gradle
, I was able to get Gradle
to also … not be able to read any resource files.
run {
inputs.property("moduleName", moduleName)
doFirst {
jvmArgs = [
'--module-path', classpath.asPath,
'--module', "$moduleName/$mainClassName"
]
classpath = files()
}
}
I tried export
-ing the resource directory I was interested in as a
"package", and at compile time got a build failure with:
error: package is empty or does not exist: mydir
Using opens
instead of exports
got the same error, though downgraded
to a warning.
I even tried moving the mydir
resource directory under src/main/java
,
but this produced the same errors / warnings, and also resulted in the
resources not being copied to the build
directory.
Where are resources supposed to go in Java 9, and how do I access them?
Note: I've edited this question considerably after continuing to
research the problem. In the initial question, I was also trying to
figure out how to list the files in a resource directory, but in the
course of the investigation I determined that was a red herring --
first, because reading resource directories only works when the
resource is being read from a file:///
URL (and maybe not even then),
and second because plain files weren't working either, so clearly the
problem was with resource files in general and not specifically with
directories.
Solution:
Per Slaw's answer, I added the following to build.gradle
:
// at compile time, put resources in same directories as classes
sourceSets {
main.output.resourcesDir = main.java.outputDir
}
// at compile time, include resources in module
compileJava {
inputs.property("moduleName", moduleName)
doFirst {
options.compilerArgs = [
'--module-path', classpath.asPath,
'--patch-module', "$moduleName="
+ files(sourceSets.main.resources.srcDirs).asPath,
'--module-version', "$moduleVersion"
]
classpath = files()
}
}
// at run time, make Gradle use the module path
run {
inputs.property("moduleName", moduleName)
doFirst {
jvmArgs = [
'--module-path', classpath.asPath,
'--module', "$moduleName/$mainClassName"
]
classpath = files()
}
}
Side note: Interestingly, if I don't continue on to add Slaw's code that makes the run
task execute against the JAR, attempting to read a resource directory InputStream
in the run task now throws an IOException
instead of providing the list of files. (Against the JAR, it simply gets an empty InputStream
.)
From Gradle's epic regarding Jigsaw support I've learned of a plugin that may ease the process described below: gradle-modules-plugin. The epic also mentions other plugins such as chainsaw (which is a fork of the experimental-jigsaw plugin). Unfortunately, I haven't tried any of them as of yet so I can't comment on how well they handle resources, if at all. I recommend trying gradle-modules-plugin, it seems to handle at least the basic configurations needed to use Jigsaw modules rather painlessly. That said, as far as I can tell, there's still no first-class support for Jigsaw modules as of Gradle 5.5.1.
In your bounty you request official documentation about "the right way" to handle resources with Gradle and Jigsaw modules. The answer, as far as I know, is that there is no "right way" because Gradle still (as of 4.10-rc-2) doesn't have first-class support for Jigsaw modules. The closest you get is the Building Java 9 Modules document.
However, you mention this is about accessing resources from within a module (i.e. not from external modules). This shouldn't be too hard to fix with a simple build.gradle
configuration.
By default, Gradle separates the output directories for classes and resources. It looks something like this:
build/
|--classes/
|--resources/
When using the run
task the classpath
is the value of sourceSets.main.runtimeClasspath
. This value includes both directories and this works because of the way the classpath works. You can think of it like the classpath is just one giant module.
This doesn't work, however, when using the modulepath because technically the files inside resources
do not belong to the module that's inside classes
. We need a way to tell the module system that resources
is part of the module. Luckily, there's --patch-module
. This option will (quoted from java --help-extra
):
override or augment a module with classes and resources in JAR files or directories.
And has the following format (I'm assuming the ;
separator is platform dependent):
--patch-module <module>=<file>(;<file>)*
To allow your module to access it's own resources simply configure your run
task like so:
run {
input.property('moduleName', moduleName)
doFirst {
jvmArgs = [
'--module-path', classpath.asPath,
'--patch-module', "$moduleName=" + files(sourceSets.main.output.resourcesDir).asPath,
'--module', "$moduleName/$mainClassName"
]
classpath = files()
}
}
This is how I've been doing it and it has worked out pretty well so far.
But how do you allow external modules to access resources from your module when launching the application from Gradle? This gets a little more involved.
When you want to allow external modules to access resources your module must opens
(see Eng.Fouad's answer) the resource's package to at least the reading module (this only applies to encapsulated resources). As you've discovered, however, this leads to compilation warnings and runtime errors.
- The compilation warning is because you are trying to
opens
a package that doesn't exist according the the module system.- This is to be expected since the resources directory is not included when compiling by default (assuming a resource-only package).
- The runtime error is because the module system cannot find the package you have declared an
opens
directive for.- Again, assuming a resource-only package.
- This occurs even with the
--patch-module
option mentioned above. I guess that the module system does some integrity checking before applying the patch.
Note: By "resource-only" I mean packages that have no .java
/.class
files.
To fix the compilation warning you just have to use --patch-module
again inside the compileJava
task. This time you'll use the resources' source directories rather than the output directory.
compileJava {
inputs.property('moduleName', moduleName)
doFirst {
options.compilerArgs = [
'--module-path', classpath.asPath,
'--patch-module', "$moduleName=" + files(sourceSets.main.resources.srcDirs).asPath,
'--module-version', "$version"
]
}
}
For the runtime error there are a couple of options. The first option is to "merge" the resources output directory with the output directory for the classes.
sourceSets {
main.output.resourcesDir = main.java.outputDir
}
jar {
// I've had bad experiences when "merging" output directories
// where the jar task ends up creating duplicate entries in the JAR.
// Use this option to combat that.
duplicateStrategy = DuplicatesStrategy.EXCLUDE
}
The second option is to configure the run
task to execute the JAR file rather than the exploded directory(ies). This works because, like the first option, it combines the classes and resources into the same place and thus the resources are part of the module.
run {
dependsOn += jar
inputs.property('moduleName', moduleName)
doFirst {
// add JAR file and runtime classpath. The runtime classpath includes the output of the main source set
// so we remove that to avoid having two of the same module on the modulepath
def modulepath = files(jar.archivePath) + (sourceSets.main.runtimeClasspath - sourceSets.main.output)
jvmArgs = [
'--module-path', modulepath.asPath,
'--module', "$moduleName/$mainClassName"
]
classpath = files()
}
}
Both of these options can be used in place of using --patch-module
in the run
task (explained in the first part of this answer).
As a bonus, this is how I've been adding the --main-class
attribute to my modular JARs:
jar {
inputs.property('mainClassName', mainClassName)
doLast {
exec {
executable = 'jar'
args = [
'--update', '--file', "$archivePath",
'--main-class', "$mainClassName"
]
}
}
}
This allows you to use java -m module.name
rather than java -m module.name/some.package.Main
. Also, if the run
task is configured to execute the JAR you can change:
'--module', "$moduleName/$mainClassName"
To:
'--module', "$moduleName"
P.S. If there's a better way to do this please let me know.
Beside @Slaw's answer (thanks to him), I had to open the package that contains the resources to the caller's module. As follows (moduleone.name module-info.java
):
opens io.fouad.packageone to moduletwo.name;
Otherwise, the following would return null
:
A.class.getResource("/io/fouad/packageone/logging.properties");
considering that class A
is in module moduletwo.name
and the file logging.properties
is inside module moduleone.name
.
Alternatively, moduleone.name
could expose a utility method that returns the resource:
public static URL getLoggingConfigFileAsResource()
{
return A.class.getResource("/io/fouad/packageone/logging.properties");
}
来源:https://stackoverflow.com/questions/51864473/where-do-resource-files-go-in-a-gradle-project-that-builds-a-java-9-module