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
)
- a set of dependencies (
- 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.
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
-
Install as a global utility
npm install -g bajel
-
Install as a global utility as root (if above gives
permission denied
error).sudo npm install -g bajel
-
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 withnpx bajel something
.
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
orBUILD.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.
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)"
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)
{
"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)"
}
}
(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(' ')}`
}
}
(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(' ')}`
}
}
# 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 ```
- 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 thedeps
arrays and to insert new dynamically generated targets into the main dictionary.
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
}
}
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
- a file, that may or may not already exist
- or a file pattern with a
%
representing a wildcard. - 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:
- Another target in the build file
- 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 firstdeps
field (after%
expansion)$+
is replaced by the all thedeps
fields (after%
expansion) separated by spaces$(
variable)
is replaced by the corresponding variableValue. (And the variableValue may contain$(
variable)
patterns for other variables.)$i
(wherei
is an integer) is replaced by the path to a tmp file containing the return value of thecall
function thei
th dep (zero-based).
(If you are familiar with makefiles you will note that the semantics are the same, though much simplified.)
The idea of using markdown came from maid.
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.