Hey I have a simple question (famous last words......
# community-support
j
Hey I have a simple question (famous last words...). I think it is also one of these riddles that could be in a Gradle quiz. And I am hoping this is documented somewhere and I just didn't find it. How do I exclude empty directories from a
FileTree
?
Input is:
Copy code
src
└── a
    ├── b
    │   └── c
    │       └── x.tmp
    └── z.txt
Some task using
matching
:
Copy code
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 🤷‍♀️ )
Copy code
build/target
└── a
    ├── b
    │   └── c
    └── z.txt
How do I get?
Copy code
build/target
└── a
    └── z.txt
(Wrong solutions in Thread)
👀 1
Copy code
.matching { exclude { it.isDirectory } }
...is wrong as it excludes everything. Also the files that are inside directories (in the example
a/z.txt
)
Copy code
.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
.
Copy code
tasks.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.
v
iirc you cannot 😞 But I hope I remember wrongly.
😵 2
t
m
What is the snapshot value of a directory? Its listing?
j
Thank you @Thomas Broyer! That maybe sufficient to solve our use case. I didn't know about that or I forgot. (So hard to keep track of all these annotations and how the interact.)
What 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.
m
TIL as well. What would a task do based on empty directories, that's be interesting...
What's even more interesting is that
src.forEach {}
doesn't show the directories...
(but
src.visit {}
does, which is probably what the Sync task is using)
j
I am wondering if I should create an issue about
**/*.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?)
m
I'd be curious to have the official explanation, this all sounds very suprising to me
j
I was in the same situation. Only that I was in a room with others (who rely on me being the "expert") who showed my this and I was like: How can that be? What did we do wrong in our task configurations? 😄 And it took quite some time for me to realize and accept that this is behavior that seems to exist since ever, but I did not knew about. 😭 And of course as always it has several layers: • The unexpected matching behavior of
**
includes (which I never used much) • The realization that empty directories are tracked as part of a FileTree
🙃 1
FTR:
@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.
m
galaxy brains
j
Copy code
val 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)
        }
    }
}
v
Yay, I was wrong, it's just not trivial. 😄
Copy code
val src = layout.projectDirectory.dir("src").asFileTree.matching {
    include("**/*.txt")
    exclude { it.isDirectory && it.file.walk().none { it.isFile && (it.extension == "txt") } }
}
Or
Copy code
val 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") })
    }
}
a