Programmatically create a file in Gradle build script - gradle

I'm sure it is trivial, but I cannot find a way to do it...
In my build.gradle I want processResources task to create (not e.g. copy or fill some tempate) a resource file to be loaded by Java program.
I achieved the following:
processResources {
...
// This is a collection of files I want to copy into resources.
def extra = configurations.extra.filter { file -> file.isFile () }
// This actually copies them to 'classes/extra'. It works.
into ('extra') {
from extra
}
doLast {
// I want to write this string (list of filenames, one per
// line) to 'classes/extra/list.txt'.
println extra.files.collect { file -> file.name }.join ("\n")
}
}
You can see above a println that prints exactly what I need. But how do I write this string to a file instead of the console?

You can use the following code
task writeToFile {
// sample list.(you already have it as extra.files.collect { file -> file.name })
List<String> sample = [ 'line1','line2','line3' ] as String[]
// create the folders if it does not exist.(otherwise it will throw exception)
File extraFolder = new File( "${project.buildDir}/classes/extra")
if( !extraFolder.exists() ) {
extraFolder.mkdirs()
}
// create the file and write text to it.
new File("${project.buildDir}/classes/extra/list.txt").text = sample.join ("\n")
}

One way to implement this would be to define a custom task that will generate this "index" file from the extra configuration, and make the existing processResources task depend on this custom task.
Something like that would work:
// Task that creates the index file which lists all extra libs
task createExtraFilesIndex(){
// destination directory for the index file
def indexFileDir = "$buildDir/resources/main"
// index filename
def indexFileName = "extra-libs.index"
doLast{
file(indexFileDir).mkdirs()
def extraFiles = configurations.extra.filter { file -> file.isFile () }
// Groovy concise syntax for writing into file; maybe you want to delete this file first.
file( "$indexFileDir/$indexFileName") << extraFiles.files.collect { file -> file.name }.join ("\n")
}
}
// make processResources depends on createExtraFilesIndex task
processResources.dependsOn createExtraFilesIndex

def dirA = "${rootDir}/dirA" //creates dirA directory in root project directory
new File("${dirA}/yourFile.txt").append("\n") //creates yourFile.txt file in above created directory

Related

SourceDirectorySet of a single file

I'm trying to create a custom gradle source set consisting of a set of files (no necessarily located in the same directory). Each file may be located in a directory that contains other files that are not supposed to be part of the source directory set.
How to create an instance of a SourceDirectorySet representing a single file?
I'd like to use such instances to configure java sources using the source method:
sourceSets {
custom {
java {
source singleFileSourceDirectorySet
source singleFileSourceDirectorySet2
// ...
}
}
}
Create a SourceDirectorySet for parent directory of each file (line 6). Attach a filter that accepts only the selected file (line 7).
sourceSets {
custom {
java {
final java.nio.file.Path srcPath = rootDir.toPath().resolve('path/to/a/File.java')
final SourceDirectorySet sds = getObjects().sourceDirectorySet("name", "desc")
sds.srcDir(srcPath.getParent().toFile())
sds.filter { java.nio.file.Files.isSameFile(it.toPath(), srcPath) }
source sds
}
}
}

How to copy files into flat directory in Gradle

I'm trying to write a Gradle task to copy specific files from a deep tree into a flat folder.
First Try:
task exportProperties << {
copy {
from "."
into "c:/temp/properties"
include "**/src/main/resources/i18n/*.properties"
}
}
This copies the correct files, but does not flatten the structure, so I end up with every single folder from my original project, and most of them are empty.
Second try, based on answers I saw here and here:
task exportProperties << {
copy {
from fileTree(".").files
into "c:/temp/properties"
include "**/src/main/resources/i18n/*.properties"
}
}
This time, it is not copying anything.
Third Try:
task exportProperties << {
copy {
from fileTree(".").files
into "c:/temp/properties"
include "*.properties"
}
}
Almost works, except it is copying every *.properties file when I only want the files in particular paths.
I solved the issue in a way similar to this:
task exportProperties << {
copy {
into "c:/temp/properties"
include "**/src/main/resources/i18n/*.properties"
// Flatten the hierarchy by setting the path
// of all files to their respective basename
eachFile {
path = name
}
// Flattening the hierarchy leaves empty directories,
// do not copy those
includeEmptyDirs = false
}
}
I got it to work like this:
task exportProperties << {
copy {
from fileTree(".").include("**/src/main/resources/i18n/*.properties").files
into "c:/temp/properties"
}
}
You can modify a number of aspects of copied files on the fly by feeding a closure into the Copy.eachFile method including target file path:
copy {
from 'source_dir'
into 'dest_dir'
eachFile { details ->
details.setRelativePath new RelativePath(true, details.name)
}
}
This copies all files directly into the specified destination directory, though it also replicates the original directory structure without the files.
I was able to solve this in a similar way to Kip but inverted:
distributions {
main {
// other distribution configurations here...
contents {
into('config') {
exclude(['server.crt', 'spotbugs-exclusion-filters.xml'])
from fileTree('src/main/resources').files
}
}
}
}
There are no issues with empty directories when the CopySpec is configured this way.

How can I iterate over CopySpec in gradle, or even get the destination pathname of a file from the copyspec

I have a Zip task which uses the CopySpec like DSL.Like:-
task1 {
from(src/main/pig).exclude(<some file pattern>) {
into "pigscripts"
}
}
What I want to do is iterate over the pig scripts, as in if copySpec is the CopySpec object associated with it,
Map<String,String map> = new HashMap<String,String>()
copySpec.eachfile {
map.put(it.originalpath,it.destinationpath)
}
However in this gradle discussion forum https://discuss.gradle.org/t/iterate-over-a-copyspesc/13030/6 it seems this is not straight forward.
It will also suffice if I can get the destination path of a single file since I can get a FileTree of all the source files as
FileTree filetree = (copySpec as CopySpecInternal).buildRootResolver().allSource
then i would like to do something like
filetree.each{
//map.put(it,copySpec.findDestinationPath(it))
}

Gradle copy and filter task not executed

We are filtering an xml file replacing some tokens with gradle properties.
But the filtering (i.e. copy task) is not executed when we just change the properties in our build.gradle file.
How should we modify our script so that the filtering is performed each time or at least when the template and/or the build.gradle has been modified.
This we have:
war.doFirst {
delete 'src/main/webapp/WEB-INF/appengine-web.xml'
copy {
from 'build.gradle'
from 'src/main/webapp/WEB-INF/'
into 'src/main/webapp/WEB-INF/'
include '*-template.*'
rename { String fileName ->
fileName.replace('-template', '')
}
expand(gaeApp: "$gaeApp", gaeAppVersion: "$gaeAppVersion")
}
}
I just ran some test where the filtering worked. I am confused... I am sure that it sometimes does not!
So after good input from Vampire we tried this
war {
inputs.file "build.gradle"
exclude 'src/main/webapp/WEB-INF/appengine-web.xml'
// filesMatching('src/main/webapp/WEB-INF/**/*-template.*') {
filesMatching('**/*-template.*') {
println "WAR template: $it"
rename { it.replace '-template', '' }
expand gaeApp: gaeApp, gaeAppVersion: gaeAppVersion
}
}
A dollar and a dime to anyone who can explain why the filesMatching('src/main/webapp/WEB-INF/**/*-template.*')does not work!
BUT the biggest problem is that even if the filesMatching locates the template file the appengine-web.xml that is placed inside the WAR is not a processed version of appengine-web-template.xml.
You need to add those properties to the inputs of the task like
war.inputs.property 'gaeApp', gaeApp
war.inputs.property 'gaeAppVersion', gaeAppVersion
so that gradle knows the input changed, otherwise it cannot know when the input is different.
But besides that, you should not (need not) use a copy { } block in there.
The war task itself is an implicit copy spec, so you should be able just doing something like
war {
inputs.property 'gaeApp', gaeApp
inputs.property 'gaeAppVersion', gaeAppVersion
exclude 'src/main/webapp/WEB-INF/appengine-web.xml'
filesMatching('src/main/webapp/WEB-INF/**/*-template.*') {
rename { it.replace '-template', '' }
expand gaeApp: gaeApp, gaeAppVersion: gaeAppVersion
}
}
This is what worked for us in the end.
We moved the template to 'src/template/webapp' and removed the "-template" suffix,
war {
inputs.file "build.gradle"
with copySpec {
from 'src/template/webapp'
expand gaeApp: gaeApp, gaeAppVersion: gaeAppVersion
}
}
Our problem with Vampire's solution must be related to the fact that the template file was in same directory as the file it was to replace.

gradle set a global property for all projects from gradle

I would like to set a property from within gradle (for example from settings.gradle) in a way similar to gradle.properties or -D. Is it possible?
The following code demonstrates what I am trying to do but its not working:
import org.gradle.internal.os.OperatingSystem
def getArchitecture() {
return System.getProperty("os.arch")
}
def getName() {
if(OperatingSystem.current().isMacOsX()) {
return "darwin"
} else if(OperatingSystem.current().isLinux()) {
return "linux"
} else {
throw Exception("The operating system you use is not supported.")
}
}
allprojects {
ext {
// this variable has to be visible from all the projects
// and .gradle files in the same way as if it was set
// from gradle.properties file
buildMachine = getName() + "_" + getArchitecture()
}
}
Edit
I would like this property to be defined in settings.gradle and be visible in all other .gradle file
Edit2
I have a number of build machines and I don't want to specify the value of the variable buildMachine (in settings and in other .gradle files) and I wouldn't like it to be passed with -P or with gradle.properties, because it seems its possible to figure out this property purely within gradle
You could simply set this property for the rootProject and in all other gradle scripts or subprojects, read from it..
For clarity sake..
in your root build.gradle
ext {
buildMachine = getName() + "_" + getArchitecture()
}
and in your subprojects..
ext {
println rootProject.buildMachine
}

Resources