Go Make

Makefile parsing and utilities in Go
Usage
Reading
The make.Parser is the primary way to read Makefiles.
f := os.Open("Makefile")
p := make.NewParser(f, nil)
m, err := p.ParseFile()
fmt.Println(m.Rules)
The more primitive make.Scanner and make.ScanTokens used by make.Parser can be used individually.
Using make.ScanTokens with a bufio.Scanner
f := os.Open("Makefile")
s := bufio.NewScanner(f)
s.Split(make.ScanTokens)
for s.Scan() {
s.Bytes() // The current token byte slice i.e. []byte(":=")
s.Text() // The current token as a string i.e. ":="
}
Using make.Scanner
f := os.Open("Makefile")
s := make.NewScanner(f, nil)
for pos, tok, lit := s.Scan(); tok != token.EOF; {
fmt.Println(pos) // The position of tok
fmt.Println(tok) // The current token.Token i.e. token.SIMPLE_ASSIGN
fmt.Println(lit) // Literal tokens as a string i.e. "identifier"
}
if err := s.Err(); err != nil {
fmt.Println(err)
}
Writing
Use make.Fprint to write ast nodes.
var file *ast.File
n, err := make.Fprint(os.Stdout, file)
The make.Writer can be used to incrementally write make syntax to an io.Writer.
buf := &bytes.Buffer{}
w := make.NewWriter(buf)
n, err := w.WriteRule(&ast.Rule{})
Features
Supported Syntax
Makefile syntax that is guaranteed to round-trip (parse and print without modification) is listed in ./testdata/roundtrip.
Additional syntax is supported and may round-trip successfully, but no guarentees are provided until it is listed under ./testdata/roundtrip.
- newline escaping i.e.
\trecipe text\\ncontinued on next line
- newline separated elements i.e.
target:\n\ntarget2:
- comments
- top-level comments i.e.
# comment text
- comment groups i.e.
# comment text\n# more comment text
- rule comments i.e.
target: # comment text
- recipe comments i.e.
target:\n\trecipe # comment text\n
- these are not make comments and are included in the recipe text
- rules
- targets i.e.
target:, target :
- multiple targets i.e.
target1 target2:
- pre-requisites i.e.
target: prereq
- order-only pre-requisites i.e.
target: | prereq
- recipes i.e.
\trecipe text\n
- recipe with a custom
.RECIPEPREFIX
- semimcolon delimited recipes i.e.
target: ;recipe text\n
- variables
- empty declarations i.e.
VAR :=
- simple declarations i.e.
VAR := foo.c bar.c
- all assigment operators i.e.
VAR != foo, VAR ::= bar, etc.
- variable references i.e.
${VAR}
- in targets i.e.
${VAR}:, $(FOO) $(BAR):
- in prereqs i.e.
target: ${FOO}
- in recipes i.e.
target:\n\trecipe $(VAR)\n
- directives
- top-level directives i.e.
ifeq, define, etc.
- conditional directives i.e.
ifeq, ifneq, ifdef, ifndef
- equality directives i.e.
ifeq, ifneq
- parentheses syntax i.e.
ifeq (foo, bar)
- double quotes i.e.
ifeq "foo" "bar"
- single quotes i.e.
ifeq 'foo' 'bar'
- mixed syntax i.e.
ifeq "foo" 'bar'
- definition directives i.e.
ifdef, ifndef
- logging directives i.e.
$(info message)
- expressions i.e.
$(shell script stuff)
- many other things
Will Not Support
Nothing, at this time
Workflow
Pre-Requisites
Go toolchain for the version listed in go.mod
Building
go-make is itself built using make.
| Targets |
Description |
| default goal |
Runs the build target |
build |
Runs go build to verify the code compiles |
test |
Test changed packages |
test_all |
Test all packages |
clean |
Remove .make directory and coverage report |
cover |
Collect coverage for all tests and print report |
tidy |
Runs go mod tidy |
dev |
Setup the developer environment |
Developer Environment
Apart from the Go toolchain, the only main dependency is the ginkgo cli to run tests.
This repo also uses devctl but its use is optional.
Targets will obtain dependencies automatically as needed.
Binaries are stored in a .gitignored bin/ directory at the root of the repository.
An example .envrc file for direnv is provided in hack/example.envrc to add ./bin to your PATH automatically.
To use it, run make .envrc or make dev.
This will copy hack/example.envrc to .envrc at the root of the repository.