I want to optimize my build by running certain things in parallel. Using the combinations of mustRunAfter and others, I have seemed to optimize the build quite significantly, but there is still one spot where I can’t seem to force gradle to run things in parallel.
The last three tasks of my build are all Copy type tasks. All three of them simply need to wait until “assemble” task is complete for every single module (I’m working with a multi-project architecture) and then all three tasks should fire at the same time. But they only run one after another.
First, I have found a solution to try and use @ParallelizableTask, but as I understand, this annotations has been removed in gradle 4. So I am trying to use WorkerExecutor API to extend Copy task so it can run in parallel, but so far I have found no way of actually doing it. Is it even possible to do? Maybe I am looking in the wrong direction altogether? I am still fairly new to gradle, so please excuse me for I am still very much a noob.
Ultimately, there should be a core gradle task which can copy in parallel. Until this time, it’s possibly easier to write a limited ParallelCopy task from scratch rather extending the Copy task.
Eg:
class ParallelCopy extends DefaultTask {
private final WorkerExecutor workerExecutor
@Inject
ParallelCopy(WorkerExecutor workerExecutor) { this.workerExecutor = workerExecutor; }
@InputFiles
private List<FileCollection> froms = []
@OutputDirectory
private File into
public void from(Object from) {
this.froms << project.files(from)
}
public void into(Object into) {
this.into = project.file(into)
}
@TaskAction
public void parallelCopy() {
for (FileCollection fc : froms) {
if (fc instanceof FileTree) {
((FileTree) fc).visit { FileVisitDetails fvd ->
if (!fvd.directory) {
File destination = new File(into, fvd.path)
submitWorker(fvd.file, destination)
}
}
} else {
for (File file : fc.files) {
File destination = new File(into, file.name)
submitWorker(file, destination)
}
}
}
}
private void submitWorker(File source, File destination) {
workerExecutor.submit(ParallelCopyWorker.class, new Action<WorkerConfiguration>() {
@Override
public void execute(WorkerConfiguration config) {
config.setIsolationMode(IsolationMode.NONE);
config.params(source, destination);
}
});
}
public static class ParalellCopyWorker implements Runnable {
private final File source;
private final File destination;
@Inject
public ParalellCopyWorker(File source, File destination) {
this.source = source;
this.destination = destination;
}
public void run() {
destination.parentFile.mkdirs()
destination.bytes = source.bytes // TODO use buffer/stream instead
}
}
}
Usage
task copy1(type:ParallelCopy) {
from fileTree('somePath').matching {
include '**/*.txt'
}
from 'another/path'
into 'some/destination'
}
Thank you very much! This seems to work amazingly well.
Also, do you know how to make sure that this task doesn’t copy unchanged files? Yesterday stubled upon the fact that default “Copy” task sufferes from this issue.
Hmm, from what I understand, Sync task simply deletes everything from the source folder before copying. I don’t think if performs any sort of check whether the fils is unchanged
I don’t think if performs any sort of check whether the fils is unchanged
Ah, that’s possibly the behaviour. You could change my implementation to an incremental task by accepting IncrementalTaskInputs argument in parallelCopy() method and then driving the worker creation and deleting logic based on IncrementalTaskInputs
I’ve just noticed that InputFileDetails does not have a getPath() method which might make it hard to do this via an incremental task if you are copying a FileTree. How annoying, it would be nice if the Gradle team could add this (under the hood it would be the value of FileVisitDetails.getPath() for input files within a FileTree)