SourceDirectorySet of a single file - gradle

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
}
}
}

Related

Download Gradle dependencies with ".modules" files

I currently have an offline environment where I have all my dependencies (jar, aar & pom files). The thing is that I want to use Coil (image library) as a dependency in my Android project. This library requires kotlinx-coroutines-core to be also downloaded in my environment. I have been able to download all the required files except for .module file (Gradle Module Metadata), which is necessary because there are multiple variants of kotlinx-coroutines-core (versions for the JVM, JS and Native).
In other words, my code downloads kotlinx-coroutines-core-jvm-1.3.9.jar (with its POM) and kotlinx-coroutines-core-1.3.9.pom which is great but kotlinx-coroutines-core-1.3.9.module file is still missing and not sure how can I download it.
Here is my code based on this gist:
task copyDependencies() {
def name = "default"
def configuration = configurations.getByName(name)
copyJars(configuration)
copyPoms(configuration)
}
private void copyJars(Configuration configuration) {
File repoDir = new File(project.buildDir, 'repository')
configuration.resolvedConfiguration.resolvedArtifacts.each { artifact ->
def moduleVersionId = artifact.moduleVersion.id
File moduleDir = new File(repoDir, "${moduleVersionId.group.replace('.', '/')}/${moduleVersionId.name}/${moduleVersionId.version}")
GFileUtils.mkdirs(moduleDir)
GFileUtils.copyFile(artifact.file, new File(moduleDir, artifact.file.name))
}
}
private void copyPoms(Configuration configuration) {
def componentIds = configuration.incoming.resolutionResult.allDependencies.collect { it.selected.id }
def result = project.dependencies.createArtifactResolutionQuery()
.forComponents(componentIds)
.withArtifacts(MavenModule, MavenPomArtifact)
.execute()
for (component in result.resolvedComponents) {
def componentId = component.id
if (componentId instanceof ModuleComponentIdentifier) {
File repoDir = new File(project.buildDir, 'repository')
File moduleDir = new File(repoDir, "${componentId.group.replace('.', '/')}/${componentId.module}/${componentId.version}")
GFileUtils.mkdirs(moduleDir)
File pomFile = component.getArtifacts(MavenPomArtifact)[0].file
GFileUtils.copyFile(pomFile, new File(moduleDir, pomFile.name))
}
}
}
So my question is: How can I download .module files when downloading my dependencies? An example would be downloading this file.
To copy a configuration's files to a directory, you can do this:
task copyLibs(type: Copy) {
from configurations.runtimeClasspath
into file("$buildDir/repository")
}
This will copy everything including transitive dependencies.
Not need to write all that code yourself. Why do you want POMs and all that in your application's classpath? Just let Gradle sort it out for you.

How Do I Add Multiple Directories And Filter Patterns To A SourceSet?

Due to historical reasons I have resources in multiple directories.
If I needed everything in these directories I could just modify the source set like this:
sourceSets {
main {
java {
srcDir = 'src/main/java'
}
resources {
srcDir = 'src/main/resources'
srcDir = 'resources'
srcDir = 'properties'
}
}
}
However, I have different include/exclude filters that I need to apply for each directory and they unfortunately conflict each other and any include/exclude filter in the resources it applies it to all 3 of the directories.
How can I filter each directory individually?
SourceDirectorySet (the class of sourceSets.main.resource) provides a method source(SourceDirectorySet) that allows adding other source sets. New instances of SourceDirectorySet may be created using ObjectFactory.sourceDirectorySet(String, String), so you could try something like the following:
def resourcesSet = objects.sourceDirectorySet("resources", "Resources")
resourcesSet.srcDir 'resources'
// apply filter to resourcesSet
def propertiesSet = objects.sourceDirectorySet("properties", "Properties")
propertiesSet.srcDir 'properties'
// apply filter to propertiesSet
sourceSets {
main {
resources {
source(resourcesSet)
source(propertiesSet)
}
}
}

Programmatically create a file in Gradle build script

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

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.

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.

Resources