Here is the relevant part of the plugin down to a minimal reproducible set:
public void apply(final Project project) {
Library library = new Library(
/* String */ "myLibrary",
/* File */ project.getLayout().getBuildDirectory().map(it ->
it.dir("sub1").dir("sub2").dir("src")
).get().getAsFile()
);
final TaskProvider<SetupTask> setup1 = project.getTasks().register("setup1", SetupTask.class);
setup1.configure(provider -> {
// This file should go under build/sub1/sub2/src/setup1.txt and it does
provider.getOutputFile().convention(
project.getLayout().getBuildDirectory()
.fileValue(library.getLocation()).file("setup1.txt")
);
});
final TaskProvider<SetupTask> setup2 = project.getTasks().register("setup2", SetupTask.class);
setup2.configure(provider -> {
// This file should go under build/sub3/sub4/setup2.txt
// but it goes to build/sub1/sub2/src/sub3/sub4/setup2.txt
provider.getOutputFile().convention(
project.getLayout().getBuildDirectory().map(it ->
it.dir("sub3").dir("sub4").file("setup2.txt")
));
});
}
It is a complete mystery to me as to why I am seeing the file from the setup2 task go into build/sub1/sub2/src/sub3/sub4/setup2.txt rather than under build/sub2/sub4/setup2.txt.
The SetupTask is almost trivial:
abstract public class SetupTask extends DefaultTask {
@OutputFile
abstract public RegularFileProperty getOutputFile();
@TaskAction
void run() {
final String str = "coucou";
try {
Files.write(getOutputFile().get().getAsFile().toPath(),str.getBytes());
}
catch (IOException ex) {
throw new RuntimeException(ex);
}
}
Well, what else would you expect?
You probably misinterpret what fileValue is doing.
It sets the value of the directory property, so the build directory.
Actually it would probably be better if you made the location in the library aDirectoryProperty. Because it is File, you have to get a property at configuration, which introduced the same race conditions afterEvaluate gives you. You would miss the build directory being changed after your got it’s value.
If it were a DirectoryProperty you could properly lazily write it, and also use zip to properly lazily combine the build directory with the location. You should also not need to create an additional file property.
Now what is coming was NOT in the original question because I wanted to simplify it to a minimum reproducible subset, but the library is actually an input to the SetupTask:
abstract public class SetupTask extends DefaultTask {
@Input
abstract public Property<Library> getLibrary();
@OutputFile
abstract public RegularFileProperty getOutputFile();
// write the library name and location into a file
Since DirectoryProperty is not serializable it fails at runtime. I guess that’s where zip comes into play but I do not know how. The library is actually an input to multiple tasks, which is why I bundled it as an object containing the name and location of the library.
Then the library constructor get hairy (but it “works”)
abstract public class Library implements ExtensionAware {
@Input
abstract public Property<String> getName();
@InputDirectory
@...
abstract public DirectoryProperty getLocation();
// details omitted
[...]
Library lib = objects.newInstance(Library.class)
[...]
abstract public class SetupTask extends DefaultTask {
@Nested
abstract public Property<Library> getLibrary();
Or when not wanting the contents of location as input but only the path, make it internal and add a protected Property<String> that is @Input and is derived from location, just like in a task directly.
I guess that’s where zip comes into play but I do not know how.
No, zip is to combine two providers lazily and build a new combined provider from it, that die example also preserves task dependencies and so on like Provider<C> providerOfC = providerOfA.zip(providerOfB) { a, b -> somehowCalculateC }.