Search code examples
gradlemakefile

Writing custom build tasks to generate build dependencies automatically on each build


I'm trying to figure out how to use Gradle. I'm having this problem, which is trivial to solve in Makefiles, CMake, etc, but I have not idea how to do it in Gradle.

Let's say I have an application that contains some resource files inside. Those resource files are generated from a set of input files, and are put inside the application in compiled form.

I'd like to instruct Gradle to automatically generate those resources when it's needed by pointing it to the resource input files. So, effectively I'd like to do something like this:

all: out1 out2
    @echo done

out1: in1
    cat in1 | xxd > out1

out2: temp1
    cat temp1 | xxd > out2

temp1: in1
    cat in2 | tr '[:lower:]' '[:upper:]' > temp1

(let's skip this unfortunate effect when if xxd will fail then an invalid out1 will be generated)

Let's say out1 and out2 are my resource files that I'd like to embed inside the application. These files are generated by different rules:

  • out1 is generated from in1 by processing it with xxd (it's just an example),
  • out2 is generated from in2 by first uppercasing it, and then piping it through xxd. A temporary file, temp1 is being created in the meantime, but that's just a temporary file and it's not important at all.

So what I'd like to achieve in Gradle is basically an equivalent of the Makefile script pasted above with all its features; I mostly mean that out1 and out2 shouldn't be generated if in1 and in2 didn't change (because the resource generation phase can be expensive and time consuming, so I'd like to avoid having to run it on each build), and the possibility of the build system to automatically figure out how to run the build in parallel, so that out1 and out2 is being generated at the same time.

I'm trying to dig up the docs for Gradle and some examples, but 95% of what I find are some opaque scripts that use some particular plugin, which nobody explains how it works inside. The docs say that Gradle is an "automation tool", so it should be perfectly doable what I'm trying to achieve, but is it really the case? Is there any sense in trying to use Gradle as a tool for the use-case described in this post?


Solution

  • Minimal example code generation Gradle task is being presented here:

    https://github.com/gradle/native-samples/blob/master/cpp/source-generation/build.gradle

    plugins {
        id 'cpp-application'
        id 'xcode'
        id 'visual-studio'
    }
    
    def codeGeneration = tasks.register("codeGeneration", CodeGenerator) {
        cppDir = project.layout.buildDirectory.dir("generated/cpp")
        headersDir = project.layout.buildDirectory.dir("generated/headers")
    }
    
    application {
        source.from codeGeneration.flatMap { it.cppDir }
        privateHeaders.from codeGeneration.flatMap { it.headersDir }
    }
    
    
    // Code generation implementation could be in another plugin or buildSrc
    class CodeGenerator extends DefaultTask {
        @OutputDirectory
        DirectoryProperty cppDir = project.objects.directoryProperty()
        
        @OutputDirectory
        DirectoryProperty headersDir = project.objects.directoryProperty()
    
        @TaskAction
        public void generate() {
            generateSources(cppDir.getAsFile().get())
            generateHeaders(headersDir.getAsFile().get())
        }
    
        private void generateSources(File generatedDir) {
            new File(generatedDir, "main.cpp").text = """
    #include <iostream>
    #include "header.h"
    
    int main() {
        std::cout << MESSAGE << std::endl;
        return 0;
    }
            """
        }
        private void generateHeaders(File generatedDir) {
            new File(generatedDir, "header.h").text = """
    #pragma once
    
    #define MESSAGE "Hello, World!"
            """
        }
    }```