Skip to main content

TDBuilder: A build system using Deno

Using TDBuilder you can specify rules for building files or directories, or running other tasks.

It is based on https://github.com/TOGoS/NodeBuildUtil.

TDBuilder does not do anything by itself, but can be used by a script, which you might call make.ts, which might look something like this:

import Builder from 'https://deno.land/x/tdbuilder@0.3.4/Builder.ts';

const builder = new Builder({
    rules: {
        "hello-world.txt": {
            description: "An example text file",
            invoke(ctx:BuildContext) {
                Deno.writeTextFile(ctx.targetName, "Hello, world!\n");
                return Promise.resolve();
            }
        },
        // A rule to build foobar.txt is not specified,
        // so it must be present already, e.g. committed to the project repo.
        "concatenation.txt": {
            description: "An example of a rule with prerequisites",
            prereqs: ["hello-world.txt", "foobar.txt"],
            async invoke(ctx:BuildContext) {
                const allContent = await Promise.all(ctx.prereqNames.map(file => Deno.readTextFile(file)))
                return Deno.writeTextFile(ctx.targetName, allContent.join(""));
            }
        },
        "test": {
            description: "Run all unit tests!",
            cmd: [Deno.execPath(),"test", "--allow-read=src", "src/test/deno"]
        }
    },
    logger: console,
    // If the user just runs `deno run make.ts`, we'll build the listed targets:
    defaultTargetNames: ["concatenation.txt","test"]
});
Deno.exit(await builder.processCommandLine(Deno.args));

The build rules and related API look like this:

type BuildResult = { mtime: number };
type BuildFunction = (ctx:BuildContext) => Promise<void>;

export interface MiniBuilder {
    build( targetName:string, stackTrace:string[] ) : Promise<BuildResult>;
}

export interface BuildContext {
    builder: MiniBuilder;
    logger: Logger;
    prereqNames: string[];
    targetName: string;
}

export interface BuildRule {
    description?: string;
    /** a list of names of targets that must be built before this build rule can be invoked */
    prereqs?: string[]|AsyncIterable<string>;

    /** Function to invoke to build the target */
    invoke? : BuildFunction;
    /** An alternative to invoke: a system command to be run */
    cmd?: string[],

    // Metadata to allow Builder to automatically fix existence or mtime:

    /** If false, the target will be removed if the build rule fails */
    keepOnFailure?: boolean;
    /** If true, builder will `touch $targetName` after building it
     *  to ensure that it cannot be mistaken as not having been updated */
    isDirectory?: boolean;
}

There is currently (as of 0.3.4) no way to indicate a build rule that builds multiple targets. As a workaround, define one of the targets and list it as a prerequisite for the others.

Rules are run in parallel as much as possible. Lock a mutex in invoke() if you want to prevent certain build steps from running at the same time.

Normally you shouldn’t need to reference ctx.builder, but you can if you need to dynamically request to build a prerequisite.

As of 0.3.0 there is no equivalent of Make’s .PHONY. A build rule will always be run if its associated target name does not name an existing file.

Run your script with -v to generate some info on the console about targets being built.