A robust TypeScript parser and executor for GNU Makefiles.
View on npm • View on GitHub
@isopodlabs/make is a small TypeScript-native library for parsing and executing Makefiles programmatically in Node.js.
- Parses GNU Make–style syntax.
- Executes recipes cross‑platform using your system shell.
- Useful for IDE integrations, build tooling, or testing Makefiles in Node.js.
If you use this package, consider buying me a cup of tea to support future updates!
npm install @isopodlabs/make
import { Makefile } from '@isopodlabs/make';
import { readFile } from 'fs/promises';
async function main() {
// Load and parse a Makefile
const mf = await Makefile.parse(await readFile('Makefile', 'utf8'));
// Run default goal or provide explicit goals
const changed = await mf.run(['all'], {
jobs: 4, // run up to 4 jobs in parallel
mode: 'normal', // 'normal' | 'dry-run' | 'question' | 'touch'
output: s => process.stdout.write(s), // capture stdout/stderr from recipes
});
console.log('did work:', changed);
}
Minimal Makefile example:
.PHONY: all build clean
all: build
build: src/app.c
@echo compiling $<
@echo output -> $@
clean:
-@rm -f build
- Parser and executor:
- Makefile parsing and execution.
- Directives:
- Conditionals:
ifeq
,ifneq
,ifdef
,ifndef
, withelse
/endif
. -
include
,-include
, andsinclude
with search over include dirs. -
export
/unexport
and.EXPORT_ALL_VARIABLES
. undefine
vpath
- Conditionals:
- Targets and rules:
- Static, pattern, and double-colon rules; grouped targets; order-only prerequisites.
- Legacy suffix rules are recognized and converted.
- Variables:
- Recursive (=), simple (:=), conditional (?=), append (+=), shell assignment (!=).
- Target- and pattern-specific variables.
- Automatic variables:
$@
,$<
,$^
(deduped),$+
(with duplicates),$|
,$?
,$*
. -
.EXTRA_PREREQS
supported (added prerequisites not reflected in automatic vars).
- Functions:
- String and list:
subst
,patsubst
,strip
,findstring
,filter
,filter-out
,sort
,word
,words
,wordlist
,firstword
,lastword
,join
. - Filename:
dir
,notdir
,suffix
,basename
,addsuffix
,addprefix
,wildcard
,realpath
,abspath
. - Conditionals/logic:
if
,or
,and
,intcmp
. - Variables/meta:
value
,origin
,flavor
,call
,foreach
,let
,file
,error
,warning
,info
. - Also provides
shell
andeval
.
- String and list:
- Search paths:
-
VPATH
andvpath
pattern-based file resolution.
-
- Special targets (recognized at runtime):
-
.PHONY
,.SUFFIXES
,.DEFAULT
,.PRECIOUS
,.INTERMEDIATE
,.NOTINTERMEDIATE
,.SECONDARY
,.SECONDEXPANSION
,.DELETE_ON_ERROR
,.IGNORE
,.LOW_RESOLUTION_TIME
,.SILENT
,.EXPORT_ALL_VARIABLES
,.NOTPARALLEL
,.ONESHELL
,.POSIX
.
-
- Recipe flags:
-
-
ignore errors,@
silent,+
force execution. -
.ONESHELL
or per-call option to run a whole recipe in a single shell.
-
- Execution modes:
-
normal
,dry-
run
(print commands),question
(returns true if any rebuild is needed),touch
(create missing files and update timestamps).
-
- Parallelism:
-
jobs
option to control concurrency. -
.NOTPARALLEL
serializes prerequisites for affected targets. -
.WAIT
is a pseudo-prerequisite that splits a prerequisite list into serial segments:-
A: p1 p2 .WAIT p3 p4
runs p1/p2 (in parallel), then p3/p4 (in parallel).
-
-
- Classes and types:
This class encapsulates a makefile. It provides direct access to these builtin variables:
CURDIR
RECIPEPREFIX
VARIABLES
FEATURES
INCLUDE_DIRS
VPATH
SUFFIXES
DEFAULT_GOAL
-
new
Makefile
(options?:
CreateOptions
)
Creates an empty makefile with only the provided variables (plus
SHELL
,MAKESHELL
,MAKE_VERSION
,MAKE_HOST
) and rules. -
Makefile
.
parse
(text: string, options?:
CreateOptions
)
As above, but also parses the text into the makefile.
-
Makefile
.load(filePath: string, options?:
CreateOptions
)
Creates a makefile from a file. If options.variables is undefined, the environment variables will be used. Also sets
CURDIR
andMAKEFILE_LIST
.
-
get(name: string)
Lookup a variable.
-
setVariable(name: string, op: string, value: string, origin: VariableOrigin)
Set a variable.
-
setFunction(name: string, fn:
Function
)
Override (or add) a function.
-
addRule(rule:
RuleEntry
)
Add a rule.
-
parse
(text: string, file?: string)
Parse additional text into the makefile (
file
is used to improve error messages). -
run
(goals?: string[], options?:
RunOptions
)
Make goals using provided options.
-
runDirect(goals: string[] = [], options:
RunOptionsDirect
)
Make goals using low-level options.
See CreateOptions
-
variables
:Record<string,
VariableValue
>
initial variables. -
functions
:Record<string,
Function
>
functions to override or augment the standard make functions -
rules
:RuleEntry
[]
initial ruleset; also used to generate.SUFFIXES
. -
includeDirs
:string[]
search paths for include. -
envOverrides
whether environment variables take precedence. -
warnUndef
warn when an undefined variable is accessed.
import { Makefile, environmentVariables } from '@isopodlabs/make';
// Parse from text
const mf = await Makefile.parse(text, {
variables: environmentVariables(),
includeDirs: ['.vscode', 'config/includes'], // search paths for include
});
// Or load directly from a path (sets CURDIR/MAKEFILE_LIST appropriately)
const mf2 = await Makefile.load('path/to/Makefile');
See RunOptions
:
-
mode
one ofnormal
,dry-
run
,question
,touch
-
jobs
number of simultaneous jobs (default 1) -
output
to capture stdout/stderr -
ignoreErrors
,silent
,noSilent
,oneshell
-
keepGoing
,checkSymlink
,printDirectory
: -
always
,assumeOld
,assumeNew
: override timestamp checks - Special targets like
.SILENT
,.ONESHELL
,.IGNORE
,.NOTPARALLEL
influence behavior per target (and globally if declared with no prerequisites).
const changed = await mf.run(['target'], {
jobs: 2,
mode: 'question', // returns true if any rebuild would occur
output: s => process.stdout.write(s),
});
See RunOptionsDirect
for lower-level control over execution.
See RuleEntry
:
-
targets
whitespace-separated list of targets -
prerequisites
whitespace-separated list of prerequisites -
recipe
optional array of strings containing the recipe -
doubleColon
true if it's a doubleColon rule -
grouped
true if the rule is a grouped rule -
builtin
true if the rule is a builtin rule -
file
,lineNo
location of definition
This is an optional sub-module, which:
- Provides a gnumake-compatible command line interface.
- Is automatically invoked if run directly from command line.
- Optionally supplies builtin rules and variables.
- Can be run programmatically, but note that the first two arguments should be the node executable and the path to the make/cli module.
import { cli } from '@isopodlabs/make/cli';
await cli(process.argv);
Using the cli
module's builtinRules
and builtinVariables
:
import { Makefile, environmentVariables } from '@isopodlabs/make';
import { builtinRules, builtinVariables } from '@isopodlabs/make/cli';
const mf = await Makefile.load('path/to/Makefile', {
variables: {...builtinVariables(), ...environmentVariables()},
rules: builtinRules(),
});
mf.run(['all'], {jobs: 6})
- No archive member support (
lib.a(member.o)
,$%
), and no jobserver. - All rules and variables must be passed to the Makefile constructor (or via Makefile.parse or Makefile.load). The typical rules and variables can be obtained from the CLI component. In particular, variables such as MAKE and MAKEFLAGS are only available if manually provided or when run from the CLI.
- Special targets with lifecycle semantics are recognized but not fully implemented:
.PRECIOUS
,.INTERMEDIATE
,.NOTINTERMEDIATE
,.SECONDARY
,.LOW_RESOLUTION_TIME
. - Requires Node.js, however only index.ts (and the optional cli) rely on any external modules (specifically, path, fs, os, and child_process), so parsing and running makefiles is possible without Node.js.
Contributions, bug reports, and feature requests are welcome!
Open an issue or pull request on GitHub.
MIT © Adrian Stephens