Jendrik Johannes
07/01/2025, 11:41 AMFileTree
?
Input is:
src
└── a
├── b
│ └── c
│ └── x.tmp
└── z.txt
Some task using matching
:
val src = layout.projectDirectory.dir("src").asFileTree.matching {
include("**/*.txt")
}
tasks.register<Sync>("sync") {
from(src)
into(layout.buildDirectory.dir("target"))
}
Then I get (I find it surprising that **/*.txt
matches the directories 🤷♀️ )
build/target
└── a
├── b
│ └── c
└── z.txt
How do I get?
build/target
└── a
└── z.txt
(Wrong solutions in Thread)Jendrik Johannes
07/01/2025, 11:42 AM.matching { exclude { it.isDirectory } }
...is wrong as it excludes everything. Also the files that are inside directories (in the example a/z.txt
)Jendrik Johannes
07/01/2025, 11:44 AM.filter { !it.isDirectory }
...is wrong too. Yes, it excludes the directory entries, but filter { }
in general throws away the relative path of the file tree elements (also TIL for me). So a/z.txt
becomes z.txt
.Jendrik Johannes
07/01/2025, 11:46 AMtasks.register<Sync>("sync") {
includeEmptyDirs = false
...
}
Yes this can be done in the context of a Sync
/ Copy
task, but I want to use the FileTree
in another context. This still shows the issue though: it does not exclude the empty directories from the input tracking. If I rename one of the unimportant directories, the task will rerun although the output is the same.Vampire
07/01/2025, 11:50 AMThomas Broyer
07/01/2025, 12:30 PM@IgnoreEmptyDirectories
https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/IgnoreEmptyDirectories.htmlMartin
07/01/2025, 12:32 PMJendrik Johannes
07/01/2025, 12:35 PMJendrik Johannes
07/01/2025, 12:36 PMWhat is the snapshot value of a directory?I think it is only the (relative) path and the fact that it is there. As each file inside would be tracked separately. But I also do not know the details and that (empty) directories are tracked at all was also a surprise to me in this case.
Martin
07/01/2025, 12:39 PMMartin
07/01/2025, 12:41 PMsrc.forEach {}
doesn't show the directories...Martin
07/01/2025, 12:42 PMsrc.visit {}
does, which is probably what the Sync task is using)Jendrik Johannes
07/01/2025, 12:43 PM**/*.txt
matching directories that do not include *.txt
files. I think this is not an intuitive behavior. But I would think that this would be mentioned somewhere already... I mean it must be like this since gradle-0.1
.
(Someone knows about an issue or forum post on this?)Martin
07/01/2025, 12:48 PMJendrik Johannes
07/01/2025, 12:53 PM**
includes (which I never used much)
• The realization that empty directories are tracked as part of a FileTreeJendrik Johannes
07/01/2025, 12:55 PM@IgnoreEmptyDirectories
works in so far that changing something unrelated would no longer lead to the task being out-of-date. What would still happen is that the empty directories are created in the state they are in in the first run. But that's expected (they are ignored, not removed). But then you can combine it with includeEmptyDirs = false
to get it all.Martin
07/01/2025, 12:56 PMJendrik Johannes
07/01/2025, 12:57 PMval src = layout.projectDirectory.dir("src").asFileTree.matching {
include("**/*.txt")
}
tasks.register<CustomSync>("sync") {
inFiles.from(src)
outDir = layout.buildDirectory.dir("target")
}
abstract class CustomSync : DefaultTask() {
@get:Inject
abstract val files: FileSystemOperations
@get:InputFiles
@get:IgnoreEmptyDirectories // !!!
abstract val inFiles: ConfigurableFileCollection
@get:OutputDirectory
abstract val outDir: DirectoryProperty
@TaskAction
fun syncOp() {
files.sync {
includeEmptyDirs = false // !!!
from(inFiles)
into(outDir)
}
}
}
Vampire
07/01/2025, 1:16 PMVampire
07/01/2025, 1:16 PMval src = layout.projectDirectory.dir("src").asFileTree.matching {
include("**/*.txt")
exclude { it.isDirectory && it.file.walk().none { it.isFile && (it.extension == "txt") } }
}
Vampire
07/01/2025, 1:19 PMval src = layout.projectDirectory.dir("src").asFileTree.matching {
include {
(!it.isDirectory && (it.file.extension == "txt"))
|| (it.isDirectory && it.file.walk().any { it.isFile && (it.extension == "txt") })
}
}
Adam
07/01/2025, 1:26 PMVampire
07/01/2025, 1:33 PMFileTree
is not a tree of File
objects, but a tree of files.
Whether the empty directories when using visit
/ Copy
/ Sync
should be there is questionable and might be a bug.
But that if you filter out all files nothing is in the file tree is expected.
Even with the src
from OP, if you use .forEach
or .files
, you only get the actual files.
Only if you use visit
, you get the empty directories.Martin
07/01/2025, 1:37 PMFileTree
to RegularFileTree
😄Vampire
07/01/2025, 1:37 PMRegularFile
, that's probably why 😄Martin
07/01/2025, 1:38 PMMartin
07/01/2025, 1:38 PMMartin
07/01/2025, 1:39 PMA FileTree represents a hierarchy of files
That's not super obvious to me whether the files are "files and directories" or just "regular files"Vampire
07/01/2025, 1:40 PMVampire
07/01/2025, 1:41 PMAdam
07/01/2025, 1:43 PMval srcDir = layout.buildDirectory.asFile.get().resolve("tmp-src")
srcDir.resolve("a/b/c/tmp.x").apply {
parentFile.mkdirs()
writeText("tmp")
}
srcDir.resolve("a/file.txt").apply {
parentFile.mkdirs()
writeText("tmp")
}
val src = fileTree(srcDir).matching {
include("**/*.txt")
}
tasks.register<Sync>("syncDemo") {
from(src.files)
into(layout.buildDirectory.dir("target"))
doLast {
println(destinationDir.walk().joinToString("\n"))
}
}
Martin
07/01/2025, 1:44 PMFileTree
is a FileCollecton
😅Adam
07/01/2025, 1:45 PMVampire
07/01/2025, 1:45 PMsrc.files
does not make it a FileCollection
, but a Set<File>
and looses the relative paths, thus you would flatten all files in target/
Jendrik Johannes
07/01/2025, 1:48 PM.filter { !it.isDirectory }
- it gives you a "FileCollection" that is not a "FileTree" and then also no longer contains the path information. And flattens everything.Martin
07/01/2025, 1:49 PMFileCollection
is a lot more explicit:
A {@code FileCollection} represents a collection of file system locations which you can query in certain ways
I understand this can be either files or directoriesJendrik Johannes
07/01/2025, 1:50 PM**
patterns and the part that excluded the empty dirs is inside my plugins.Vampire
07/01/2025, 1:50 PM.filter { !it.isDirectory }
=> .matching { include { it.isDirectory } }
But as then all directories are excluded, also the files are gone 😄Vampire
07/01/2025, 1:50 PMI understand this can be either files or directoriesExact
Vampire
07/01/2025, 1:51 PMMartin
07/01/2025, 1:53 PMFileCollection
is.Martin
07/01/2025, 1:53 PMVampire
07/01/2025, 1:57 PMVampire
07/01/2025, 1:57 PMAdam
07/01/2025, 1:58 PMMartin
07/01/2025, 1:58 PMou Blog is still categorized as Gaming siteI mean, Gradle can be fun
Martin
07/01/2025, 1:58 PMa directory needs to be a task input.
How else would you give the directory with relative files to some task?I think we've had this discussion already.
FileCollection
contains the normalized pathsMartin
07/01/2025, 1:59 PMMartin
07/01/2025, 1:59 PMVampire
07/01/2025, 1:59 PMMartin
07/01/2025, 1:59 PMVampire
07/01/2025, 2:00 PMoh, is the relative path information based on the input type?Nah, if it is a
FileTree
it has relative paths, if it is a FileCollection
it is just the plain files without relative path information.
What is considered for Gradle input fingerprinting depends on more like PathSensitivity
and so on @AdamMartin
07/01/2025, 2:00 PMif it is aBut ait has relative paths, if it is aFileTree
it is just the plain files without relative path information.FileCollection
FileTree
is a FileCollection
😅Martin
07/01/2025, 2:01 PMFileCollection
do not contain the relative path information I guessVampire
07/01/2025, 2:01 PMAdam
07/01/2025, 2:02 PM// user configurable
@Internal
abstract val fileTree: FileTree
@InputFiles
@PathSensitivity(RELATIVE)
protected val inputFiles: FileCollection
get() = objects.fileCollection()
.from(fileTree.dir)
.from(fileTree.files)
Vampire
07/01/2025, 2:05 PMVampire
07/01/2025, 2:05 PMVampire
07/01/2025, 2:05 PMFor files in the root of the file collection, the file name is used as the normalized path. For directories in the root of the file collection, an empty string is used as normalized path. For files in directories in the root of the file collection, the normalized path is the relative path of the file to the root directory containing it.
Vampire
07/01/2025, 2:06 PM