@isopodlabs/make
TypeScript icon, indicating that this package has built-in type declarations

0.5.2 • Public • Published

@isopodlabs/make

npm version GitHub stars License

A robust TypeScript parser and executor for GNU Makefiles.
View on npmView 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.

☕ Support My Work

If you use this package, consider buying me a cup of tea to support future updates!

Installation

npm install @isopodlabs/make

Quick start

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

Features

  • Parser and executor:
    • Makefile parsing and execution.
  • Directives:
    • Conditionals: ifeq, ifneq, ifdef, ifndef, with else/endif.
    • include, -include, and sinclude with search over include dirs.
    • export/unexport and .EXPORT_ALL_VARIABLES.
    • undefine
    • vpath
  • 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 and eval.
  • Search paths:
    • VPATH and vpath 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).

API

Makefile

This class encapsulates a makefile. It provides direct access to these builtin variables:

  • CURDIR
  • RECIPEPREFIX
  • VARIABLES
  • FEATURES
  • INCLUDE_DIRS
  • VPATH
  • SUFFIXES
  • DEFAULT_GOAL

Construction

  • 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 and MAKEFILE_LIST.

Methods

  • 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.

Create 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');

Run options

See RunOptions:

  • mode one of normal, 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.

Rules

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

CLI

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})

Limitations

  • 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.

Contributing

Contributions, bug reports, and feature requests are welcome!
Open an issue or pull request on GitHub.

License

MIT © Adrian Stephens

Package Sidebar

Install

npm i @isopodlabs/make

Weekly Downloads

85

Version

0.5.2

License

MIT

Unpacked Size

108 kB

Total Files

13

Last publish

Collaborators

  • adrianstephens