Making Gradle access files outside its project - java

I have a Gradle project which at some point in its code needs to act on a folder one level above the Gradle project's. It needs to write some data in it and create a file if it isn't there. However, the acting is the code's responsibility, all Gradle does is have a task which to run the code.
The problem is that when I run the gradle task, the jvm throws an java.nio.File NoSuchFileException.
Running the same program from Intellij's Run executes perfectly as intended, so it is not the code.
The one big difference I see is that the Intellij Run has a Working directory set at a level above my Kotlin project's, whereas Gradle is pointing to the project as root, as it should be.
I am new to Gradle and I find the documentation hard to read, and it confused me quite a lot. I assume that I need to tell somehow Gradle that the code may need to access folders on the filesystem outside the project. However, I'm not sure if that needs to be in the settings.gradle.kt or on the task level and by using with which function.
Could you please point me the right way?

To create a collection of files in a relative path, This snippet may work for you:
tasks.register('list') {
doLast {
File srcDir
// Create a file collection using a closure
collection = layout.files { srcDir.listFiles() }
srcDir= file("../$rootDir")
println "Contents of $srcDir.name"
collection.collect { relativePath(it) }.sort().each { println it }
}
}
reference website:
https://docs.gradle.org/current/userguide/working_with_files.html#sec:file_collections

As mentioned in my previous comment the problem actually boiled down to having the ability to change the working directory of the custom Gradle task.
Inside a task I ended up doing the following:
run.configure {
workingDir = File("./..")
}
This would make the scope of the working directory of the given task to be the one of its parent directory which solves the issue.

Related

Gradle copy command deprecated, breaks build

In Gradle 3.x I was able to get some xml mapping files to copy into the classes directory prior to build/jar via the following block:
copy{
from 'src/main/java/com/company/mapping'
into 'build/classes/main/java/com/company/mapping'
include '**/*.xml'
}
In Gradle 4.9 this has been deprecated in favor of:
task copyMappings(type: Copy){
from 'src/main/java/com/company/mapping'
into 'build/classes/main/java/com/company/mapping'
include '**/*.xml'
}
The copyMappings task succeeds, but build/jar does not wait for copyMappings to finish. I have tried variations on build.dependsOn and doFirst{ copyMappings } doLast{ build } but nothing seems to get me the desired effect of having the copied files in place in the 'into' path prior to jar.
This is for Windows 10.
This works for me with Gradle 4.9 on Mac OS:
apply plugin: 'java'
task copyMappings(type: Copy) {
from 'src/main/java/com/company/mapping'
into 'build/classes/main/java/com/company/mapping'
include '**/*.xml'
}
jar.dependsOn copyMappings
jar.doFirst {
assert new File("${projectDir}/build/classes/main/java/com/company/mapping/abc.xml").exists()
assert new File("${projectDir}/build/classes/main/java/com/company/mapping/def.xml").exists()
}
command line is gradle clean jar
I like to model things around source sets where appropriate as doing so let's the build work more reliably with a wide range of plugins and use cases. For example, imagine you want to run an application direct from its class files and resources rather than packaging it as a JAR first. You could make sure that the "run" task depends on the copy as well, but you'd have to do that for every instance where this is a requirement.
Source sets are the ideal solution because they have the concept of a runtime classpath, which will work for packaging, instrumentation, running, testing and so on.
With that in mind, I would go for this simple declaration and get rid of the copy task:
sourceSets {
main {
resources {
srcDir "src/main/java"
include "**/*.xml"
}
}
}
The XML files will end up in a different directory from your current approach, but that shouldn't matter unless you have tasks that assume the location rather than using the source set model to get the necessary information.
Note The above include directive applies to all the resources in src/main/resources as well. So if you have properties files or text files or anything else in there, they will be excluded. The simplest solution is to add all required resource file patterns to the include directive.

how to include all subdirectories as gradle modules?

I'm going to have a lot of submodules in my main project directory x, like x/module1, x/module2...
can i avoid manually adding every single module into settings.gradle? can i somehow script it to find all the subdirectories and add them automatically?
As cricket_007 already mentioned, Gradle is based on the Groovy programming language (which is, like Java, executed in the JVM) and the settings.gradle file is nothing more but a Groovy script.
Whenever you use include 'project', the include method of a Settings instance is called, so for your goal, you could simply create a loop which iterates over all folders and calls include for each of them.
A more 'groovyesque' approach would be the usage of a closure for each subdirectory, provided by the Groovy SDK extension for the File class:
file('.').eachDir { sub ->
include sub.name
}
There are multiple ways to solve your problem, e.g. since the include method accepts an array of project path strings, you could also aggregate all required paths first and pass them all together. Simply get familiar with the Gradle docs and decide on your own, what solution suits your case the best.

Gradle application plugin: Modify workingDir property

I simply want to modify the directory where the program is run. Normally, it's run from the project root, which annoys me a little bit, because testing the program out can be quite annoying, since my program generates files and folders where it is being run.
A JavaExec task has a property called JavaExec#workingDir, which would be this exact property I wanted to modify to something different of my choice.
My question is: How do I modify the gradle run task in order to access this property?
You can access a property of a task by using tasks.<TaskToModify>.property = YourValue.
So, in this case, you would have to do this:
File runningDir = new File('build/run/')
runningDir.mkdirs()
tasks.run.workingDir = runningDir
The File#mkdirs() call is neccessary, since if the directories do not exist, the call to your system-dependent java executable will cause a error.

Ant: Replace tokens in source file multiple times

I have a Java configuration file with constants.
I'm using ant to replace the values of the constant according to the build. In a precompile target:
<replace
file="Config.java"
value="default"
propertyFile="${build.env}.properties">
<replacefilter
token="#mytoken#"
property="myprop.x"/>
</replace>
Works well. But after I run this my source file is modified. So if I run it again it will not replace anything because #mytoken# was replaced the first time.
I don't want to put Config.java outside of the project because I want it to work with eclipse and would get lot of compile errors if the file is not where expected.
I was thinking about replacing back in a post build target or something, but not sure if that's secure. If the build fails or the user interrupts the script it will not run and the value will not be set back.
Any help? Thanks in advance.
When I had to deal with this task, I went about it a different way. Instead of editing a real source file, the ant script always makes a file named Version.java. Version.java is never checked into the repository, but the interface Version implements is. This way, you don't have to be statically dependent on the existence of the generated file.
public String getVersionHelper() {
try {
Class versionClass = Class.forName("Version");
IVersion version = (IVersion) versionClass.newInstance()
return version.getVersion();
} catch (ClassNotFoundException ex) {
return "NO VERSION";
}
}
The key point is that official builds are always done with ant, not eclipse. This allows you to run in eclipse for testing and still compile successfully.

Deploy additional files in Gradle Application Plugin

I have a small Java/Gradle project. I'm using the Application plugin to create a zip distribution (using the distZip task). Using the standard configuration I get the following directories in my zip file:
/bin - The scripts to start the application go in here
/lib - Contains my project code in a JAR file and all dependency JAR files.
The trouble is that I would like a third directory: /conf where I can put my configuration files (instead of having them packaged inside my application JAR file.
I imagine that this is a pretty common requirement because things like log4j.xml and hibernate.properties would be better placed outside the JAR file. I just can't figure out how I can customise the behavior of the Application plugin to do this however.
I revisited this problem several months later and I finally have an elegant solution. The following code should be added to the gradle file:
distZip {
into(project.name) {
from '.'
include 'conf/*'
}
}
This adds an additional include to the distZip task. This copies the "conf" directory (including contents) into the Zip distribution.
The generated zip file contains a single directory which is the same as the project name. This is why the "into" part is required.
Actually, create a dist dir under the src dir in your project. Anything in this dir is copied by the application plugin (under applicationDistribution) when installApp or distZip is run.
Or edit applicationDistribution to do other things, if a simple copy is not enough.
For me, a simple
applicationDistribution.from("src/main/config/") {
into "config"
}
did the job. Of course you need to have your properties loaded correctly from within code. Especially if you move them from src/main/resources where they have been usable via classpath, into the new location. I circumvented this by adding a command line parameter which points to the configuration file.
I am not sure whether you can customize the application plugin, I have never used it. There is however other ways to achieve what you want to achieve.
You may create a /conf directory like this:
confDir = new File("$buildDir/conf")
You can then copy the files you need into this directory like this:
task copyConfFiles(type: Copy) {
from _wherever your files reside_
into confDir
include('**/*.properties') // your configuration files
}
You may then hook this copy task into the process like this:
distZip.dependsOn copyConfFiles
And last if you do not want your configurations in the final zip, you can do this:
distZip {
exclude('**/*.properties') // your configuration files
}
Again, there might be a better way. This is a way.
OP's self-answer may be good for his use case, but there are a few things I'd like to improve on:
His answer suggests that he has a directory conf parallel to the build.gradle. There is no such thing in the Maven Standard Directory Layout. The general consensus is to have a src/main/conf as had been hinted to in the docs:
If there are other contributing sources to the artifact build, they
would be under other subdirectories: for example src/main/antlr would
contain Antlr grammar definition files.
The target directory name is NOT project.name as had been pointed out in a comment.
If resource filtering is required, and it often is, then having a separate task is desirable. During local development, this task can be run to generate the filtered files. The distribution would merely use the output of this task (and unlike OP's answer, this also makes conf available to the tar distribution).
def props = new Properties()
file("src/main/filters/application.properties")
.withInputStream { props.load(it) }
import org.apache.tools.ant.filters.ReplaceTokens
task copyConf(type: Copy) {
from("src/main/conf/")
into("$buildDir/conf")
filesMatching("**/*.y*ml") {
filter(tokens: props, ReplaceTokens)
}
}
distributions {
main {
contents {
from(copyConf) {
into("conf")
}
}
}
}

Categories

Resources