Custom plugin task chaining

Hi,

This is the first time I’m trying to build a custom plugin for gradle so I’m a bit lost with the apis and the correct way of implementing things. We currently have a java application that is distributed through an Inno Setup based installer that is manually built for every new release. In trying to simplify this process I’m making a plugin to create all the necessary tasks required to build this installer automatically according to some required params.

Currently these are the required steps for building the installer:

  • Download the necessary jre distribution for the target architecture (we support both x86 and x64)
  • Write a build.properties file inside the project resources containing the current version and arch (used by the self update system) and white label names
  • Run inno setup compiler with the required parameters (jre distribution path, target arch, …) to build the installer

My initial design is to have the following 3 tasks:

  • propertiesFile
  • downloadJre
  • buildInstaller

All 3 tasks require knowlege of the target arch, “makePropertiesFile” and “buildInstaller” requires knowlege of the applied White Label, “buildInstaller” is dependent on the files downloaded by “downloadJre” and finally the “build” requires the files generated by “propertiesFile”.

I’m building everything using the java api, however I’m struggling to find concrete examples of piping the inputs/outputs from task to task. My objetive is to be able to run a simple “./gradlew buildInstaller --arch=x64 --branding=someBrandName” and get everything done automatically.

All this boils to down to the following classes

public abstract class PropertiesFileTask extends DefaultTask {

    @TaskAction
    public void run() {
        // create build.properties in $buildDir/resource/main
    }

    @Option(option = "arch", description = "target arch")
    @Input
    public abstract Property<String> getArch();

    @Option(option = "branding", description = "target branding")
    @Input
    public abstract Property<String> getBranding();

    @OutputDirectory
    public abstract RegularFileProperty getPropertiesFile();
}
public abstract class DownloadJreTask extends DefaultTask {

    @TaskAction
    public void run() {
        // download required files
    }

    @Option(option = "arch", description = "target arch")
    @Input
    public abstract Property<String> getArch();

    @OutputDirectory
    public abstract RegularFileProperty getJreDirectory();
}

public abstract class BuildInstallerTask extends DefaultTask {

    @TaskAction
    public void run() {
        // run inno setup compiler
    }

    @Option(option = "arch", description = "target arch")
    @Input
    public abstract Property<String> getArch();

    @Option(option = "branding", description = "target branding")
    @Input
    public abstract Property<String> getBranding();

    @InputDirectory
    public abstract RegularFileProperty getJreDirectory();
    
    @OutputDirectory
    public abstract RegularFileProperty getInstallerDirectory();
}

How can I connect the inputs and outputs of these tasks? My current pluging initialization code is the following

public class InstallerPlugin implements Plugin<Project> {
    @Override
    public void apply(Project project) {
        var propertiesFile = project.getTasks().register("propertiesFile", PropertiesFileTask.class);
        propertiesFile.configure(task -> {
            task.setGroup("build");
            task.getArch().convention("x64");
            task.getBranding().convention("avanteweb");
        });

        var downloadJre = project.getTasks().register("downloadJre", JreDownloadTask.class);
        downloadJre.configure(task -> {
            task.setGroup("installer");
            task.getArch().convention("x64");
            task.getOutputJre().convention(project.getLayout().getBuildDirectory().file("jre"));
        });

       // todo: buildInstaller task
    }
}

Thanks in advance.

Well, to connect the inputs and outputs, set the input properties to the output properties.

But actually you should start to get your types and annotations correct.
A RegularFileProperty is a “regular file”, hence its name.
So it does not make much sense to annotate it as input or output directory.
Instead you should have DirectoryProperty as type.
And similar, if you have an output file, RegularFileProperty is ok, but then it should be @OutputFile, not @OutputDirectory.

Sorry for the trouble, but I’m having a hard time figuring out how to share these values. Let’s take for example downloadJre and buildInstaller tasks.

public abstract class DownloadJreTask extends DefaultTask {
    @Option(option = "arch", description = "target arch")
    @Input
    public abstract Property<String> getArch();
}
public abstract class BuildInstallerTask extends DefaultTask {
    @Option(option = "arch", description = "target arch")
    @Input
    public abstract Property<String> getArch();
}

Registering these with

var downloadJre = project.getTasks().register("downloadJre", DownloadJreTask.class);
downloadJre.configure(task -> {
    task.getArch().convention("x64");
});

var buildInstaller = project.getTasks().register("buildInstaller", InstallerTask.class);
buildInstaller.configure(task -> {
    task.dependsOn(downloadJre);
    task.getArch().set(downloadJre.get().getArch());
});

do not propagate the arch option from buildInstaller to downloadJre when running “gradle buildInstaller --arch=x86”. I’m thinking that task.getArch().set(downloadJre.get().getArch()); is not the correct way of passing these values, however I’m unable to find a concrete example. Can you please share with me a small code snippet of how to connect both of these options?

I don’t think you can do a bidirectional connection like you intend to.
What you do in your shown code ist to configure the buildInstaller task to take the value from the one configured in downloadJre task.
So if you now give the --arch value to downloadJre like ./gradlew downloadJre --arch foo buildInstaller, both use the same value as you configure it in downloadJre and buildInstaller takes it from there.
But if you configure it in buildInstaller like ./gradlew buildInstaller --arch foo, then the value for buildInstaller is overwritten, but downloadJre still takes its own convention.
If you swap the configuration, you have the same sitatuation, just swapped around.
And you configure each to take the value from the other, you will have a circular dependency if you don’t set it on either and it blows up.