bajel

    1.1.0 • Public • Published

    Bajel : a Simple Build System

    logo

    Bajel is an easy way of executing commands for building any kind of target files.

    It will only build a target file if is out of date relative to its dependencies.

    You specify the build targets, their dependencies, and the build commands in a build file which has a very simple conceptual structure:

    • A set of targets, each of which has
      • a set of dependencies (deps)
      • a command to execute (exec)
    • Declarations of variables that you can use in the exec

    You can write the build file in your favorite format:

    • TOML if you like things clean and readable
    • YAML if that's your thing
    • JSON if you want just the basics
    • JavaScript if you want more control
    • Markdown if you like literate programming

    The article Bajel, A Simple, Flexible Build and Scripting Tool for NPM describes the motivation for creating Bajel.

    Installation

    First check prerequisites by making sure that npm -v shows you have a working Node.js installation. If not install Node.js (or if you want the flexibilty of switching between multiple versions of Node.js install NVM)

    Choose one of these three options

    1. Install as a global utility

      npm install -g bajel
    2. Install as a global utility as root (if above gives permission denied error).

      sudo npm install -g bajel
    3. Install in the current NPM project (if you only want to use Bajel with Node).

      npm install --save-dev bajel

      Note in this case you will have to replace all the bajel something example commands below with npx bajel something.

    Usage

    The command

    bajel

    will build the default target in build file in the current directory.

    The default target is the first target in the file that is not a file pattern (with a % wildcard).

    bajel foo

    Will build target foo.

    bajel -n

    Will do a dry run, printing out the commands that it would execute, but not actually executing them.

    The build file must be one of the following names:

    • BUILD.toml
    • BUILD.yaml or BUILD.yml
    • BUILD.json
    • BUILD.cjs (JavaScript, as a classic Node-JS module)
    • BUILD.mjs (JavaScript, as a new-style ES6 module -- Node version 13.2.0 or later)
    • BUILD.md

    All these different languages are alternate syntaxes of specifying the same underlying build file structure, as shown by the following examples (based on a makefile example), each of which specify the same thing.

    Examples

    BUILD.toml

    CC = "gcc"
    CFLAGS = "-I."
    OBJ = ["hellomake.o", "hellofunc.o"]
    
    ["%.o"]
    deps = ["%.c", "hellomake.h"]
    exec = "$(CC) -c -o $@ $< $(CFLAGS)"
    
    [hellomake]
    deps = ["hellomake.o", "hellofunc.o"]
    exec = "$(CC) -o $@ $+ $(CFLAGS)"
    
    [clean]
    exec = "rm -f hellomake $(OBJ)"

    BUILD.yaml

    CC: gcc
    CFLAGS: -I.
    OBJ:
    - hellomake.o
    - hellofunc.o
    
    "%.o":
      deps:
        - "%.c"
        - hellomake.h
      exec: $(CC) -c -o $@ $< $(CFLAGS)
    
    hellomake:
      deps:
        - hellomake.o
        - hellofunc.o
      exec: $(CC) -o $@ $+ $(CFLAGS)
    
    clean:
      exec: rm -f hellomake $(OBJ)

    BUILD.json

    {
      "CC": "gcc",
      "CFLAGS": "-I.",
      "OBJ": [
        "hellomake.o",
        "hellofunc.o"
      ],
      "%.o": {
        "deps": [
          "%.c",
          "hellomake.h"
        ],
        "exec": "$(CC) -c -o $@ $< $(CFLAGS)"
      },
      "hellomake": {
        "deps": [
          "hellomake.o",
          "hellofunc.o"
        ],
        "exec": "$(CC) -o $@ $+ $(CFLAGS)"
      },
      "clean": {
        "exec": "rm -f hellomake $(OBJ)"
      }
    }

    BUILD.cjs

    (This JavaScript module format supported with all versions of Node.)

    const CC = 'gcc'
    const CFLAGS = '-I.'
    const DEPS = ['hellomake.h']
    const OBJ = ['hellomake.o', 'hellofunc.o']
    
    module.exports = {
    
      '%.o': {
        deps: ['%.c', ...DEPS],
        exec: `${CC} -c -o $@ $< ${CFLAGS}`
      },
    
      hellomake: {
        deps: OBJ,
        exec: `${CC} -o $@ $+ ${CFLAGS}`
      },
    
      clean: {
        exec: `rm -f hellomake ${OBJ.join(' ')}`
      }
    
    }

    BUILD.mjs

    (This JavaScript module format is only supported if your version of Node is 13.2.0 or later.)

    const CC = 'gcc'
    const CFLAGS = '-I.'
    const DEPS = ['hellomake.h']
    const OBJ = ['hellomake.o', 'hellofunc.o']
    
    export default {
    
      '%.o': {
        deps: ['%.c', ...DEPS],
        exec: `${CC} -c -o $@ $< ${CFLAGS}`
      },
    
      hellomake: {
        deps: OBJ,
        exec: `${CC} -o $@ $+ ${CFLAGS}`
      },
    
      clean: {
        exec: `rm -f hellomake ${OBJ.join(' ')}`
      }
    
    }

    BUILD.md

    # Markdown version of build file
    
    ## %.o
    
    Deps: `%.c` `hellomake.h`
    
    ```sh
    gcc -c -o $@ $< -I.
    ```
    
    ## hellomake
    
    Deps: `hellomake.o` `hellofunc.o`
    
    ```sh
    gcc -o $@ $+ -I.
    ```
    
    ## clean
    
    ```sh
    rm -f hellomake hellomake.o hellofunc.o
    ```
    

    Real world examples

    • Diagmap's BUILD.toml is a fairly straightforward build file in TOML format. The only slightly tricky aspect is that it take advantage of the % wild card to create empty .ok files to indicate that the corresponding .js file has successfully passed the StandardJS linter.
    • Maxichrome's BUILD.toml is another straightforward TOML build file.
    • The BUILD.cjs for a blog uses JavaScript variables, but it is otherwise fairly simple.
    • Mergi's BUILD.mjs is a fairly complex build file which uses the power of JavaScript to remove duplication by refactoring out common elements. For example it extensively uses the ... spread operator to insert sub arrays into the deps arrays and to insert new dynamically generated targets into the main dictionary.

    Advanced feature: JavaScript functions as actions

    As an alternative to using the exec property to specify a shell command to execute, you can use the call property to specify a JavaScript function to be executed.

    The function takes a deps parameter containing the values returned by the dependency functions.

    The example below shows how this can be used to create something that works like a spreadsheet.

    export default {
    
      result: {
        deps: ['B4'],
        exec: 'cat $0'
      },
    
      A1: {
        call: deps => 10
      },
      A2: {
        call: deps => 12
      },
      A3: {
        call: deps => 14
      },
      A4: {
        deps: ['A1', 'A2', 'A3'],
        call: ({ A1, A2, A3 }) => A1 + A2 + A3
      },
    
      'B%': {
        deps: ['A%'],
        call: deps => deps[0] * 100
      }
    }

    Build File Structure

    A build file is an object with the following structure:

    • variable: variableValue
    • variable: variableValue
    • ...
    • target:
      • deps: dependencies
      • exec: shellCommand
      • call: JavaScript function
    • target:
      • deps: dependencies
      • exec: shellCommand
      • call: JavaScript function

    The "call" property can only appear in the JavaScript syntax.

    There can be any number of variables and targets.

    A variableValue is either a string or an array.

    A target can be

    1. a file, that may or may not already exist
    2. or a file pattern with a % representing a wildcard.
    3. otherwise just a label

    The dependencies is an array of strings (or string that is a variable reference to an array variable), where each element of the array can be either:

    1. Another target in the build file
    2. An existing file or file pattern

    The shellCommand is a string that gets executed by the Linux-style shell, but only if the target does not exist as a file, or if the target is older than all the deps.

    The shellCommand may have some special patterns:

    • % expands to whatever matched the % in the target
    • $@ is replaced by the target (after % expansion)
    • $< is replaced by the first deps field (after % expansion)
    • $+ is replaced by the all the deps fields (after % expansion) separated by spaces
    • $(variable) is replaced by the corresponding variableValue. (And the variableValue may contain $(variable) patterns for other variables.)
    • $i (where i is an integer) is replaced by the path to a tmp file containing the return value of the call function the ith dep (zero-based).

    (If you are familiar with makefiles you will note that the semantics are the same, though much simplified.)

    Notes

    The idea of using markdown came from maid.

    Legal

    Copyright (c) 2020 Eamonn O'Brien-Strain All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse Public License v1.0 which accompanies this distribution, and is available at http://www.eclipse.org/legal/epl-v10.html

    This is a purely personal project, not a project of my employer.

    Keywords

    Install

    npm i bajel

    DownloadsWeekly Downloads

    22

    Version

    1.1.0

    License

    EPL-1.0

    Unpacked Size

    9.15 MB

    Total Files

    92

    Last publish

    Collaborators

    • eobrain