https://github.com/unmango/go-make
Makefile parsing library for Go
https://github.com/unmango/go-make
ast go golang make makefile parser printer scanner
Last synced: 2 months ago
JSON representation
Makefile parsing library for Go
- Host: GitHub
- URL: https://github.com/unmango/go-make
- Owner: unmango
- License: mit
- Created: 2025-01-12T21:23:37.000Z (over 1 year ago)
- Default Branch: main
- Last Pushed: 2025-07-01T07:56:47.000Z (12 months ago)
- Last Synced: 2025-07-01T08:39:52.212Z (12 months ago)
- Topics: ast, go, golang, make, makefile, parser, printer, scanner
- Language: Go
- Homepage:
- Size: 153 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Go Make






Makefile parsing and utilities in Go
## Usage
### Reading
The `make.Parser` is the primary way to read Makefiles.
```go
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`
```go
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`
```go
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.
> **Note**
> The AST in this project is a made-up, package-specific representation for Makefiles. It is not an official GNU Make or POSIX AST.
```go
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`.
```go
buf := &bytes.Buffer{}
w := make.NewWriter(buf)
n, err := w.WriteRule(&ast.Rule{})
```
### Builder
The `builder` package contains utilities for building AST nodes.
🚧 This API is not stable yet 🚧
```go
f := builder.NewFile(1,
file.WithRule(expr.Text("target1"),
rule.WithVarRefTarget("FOO")
),
)
make.Fprint(os.Stdout, f)
// target1 ${FOO}:\n
```
## Features
### Syntax Support
Makefile syntax that is guaranteed to round-trip (parse and print without modification) is listed in [./testdata/roundtrip](./testdata/roundtrip/).
Additional syntax is supported and may round-trip successfully, but no guarentees are provided until it is listed under `./testdata/roundtrip`.
| Syntax | Example | Parser | Printer | Builder | Remarks |
| ------------------------------------ | ---------------------------------------- | :----------------: | :----------------: | :----------------: | -------------------------------------------------------------------- |
| newline escaping | `\trecipe text\\ncontinued on next line` | | | | |
| newline separated elements | `target:\n\ntarget2:` | | | | |
| **comments** | | | | | |
| top-level comments | `# comment text` | :white_check_mark: | :white_check_mark: | | |
| comment groups | `# comment text\n# more comment text` | :white_check_mark: | :white_check_mark: | | |
| rule comments | `target: # comment text` | | | | |
| recipe comments | `target:\n\trecipe # comment text\n` | :white_check_mark: | :white_check_mark: | | these are not make comments and are included in the recipe text |
| **rules** | | | | | |
| targets | `target:`, `target :` | :white_check_mark: | :white_check_mark: | :white_check_mark: | |
| multiple targets | `target1 target2:` | :white_check_mark: | :white_check_mark: | :white_check_mark: | |
| pre-requisites | `target: prereq` | :white_check_mark: | :white_check_mark: | | |
| order-only pre-requisites | `target: \| prereq` | :white_check_mark: | :white_check_mark: | | |
| recipes | `\trecipe text\n` | :white_check_mark: | :white_check_mark: | | |
| recipe with a custom `.RECIPEPREFIX` | `\|recipe text\n` | | | | |
| semimcolon delimited recipes | `target: ;recipe text\n` | | | | |
| **variables** | | | | | |
| empty declarations | `VAR :=` | :white_check_mark: | :white_check_mark: | | |
| simple declarations | `VAR := foo.c bar.c` | :white_check_mark: | :white_check_mark: | | |
| all assigment operators | `VAR != foo`, `VAR ::= bar`, etc. | :white_check_mark: | :white_check_mark: | | |
| **variable references** | | | | | |
| in targets | `${VAR}:`, `$(FOO) $(BAR):` | :white_check_mark: | :white_check_mark: | :white_check_mark: | |
| in prereqs | `target: ${FOO}` | :white_check_mark: | :white_check_mark: | | |
| in recipes | `target:\n\trecipe $(VAR)\n` | | | | |
| **directives** | | | | | |
| top-level directives | `ifeq`, `define`, etc. | | | | |
| conditional directives | `ifeq`, `ifneq`, `ifdef`, `ifndef` | :white_check_mark: | :white_check_mark: | | |
| equality directives | `ifeq`, `ifneq` | :white_check_mark: | :white_check_mark: | | |
| parentheses syntax | `ifeq (foo, bar)` | :white_check_mark: | :white_check_mark: | | |
| double quotes | `ifeq "foo" "bar"` | :white_check_mark: | :white_check_mark: | | |
| single quotes | `ifeq 'foo' 'bar'` | :white_check_mark: | :white_check_mark: | | |
| mixed syntax | `ifeq "foo" 'bar'` | :white_check_mark: | :white_check_mark: | | |
| definition directives | `ifdef`, `ifndef` | :white_check_mark: | :white_check_mark: | | |
| logging directives | `$(info message)` | | | | |
| expressions | `$(shell script stuff)` | | | | |
| many other things | | | | | please open an issue if there is anything missing you'd like to see! |
### Will Not Support
Nothing, at this time
## Workflow
### Pre-Requisites
Go toolchain for the version listed in [go.mod](./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) |
### Developer Environment
Apart from the Go toolchain, the only main dependency is the `ginkgo` cli to run tests.
This repo also uses [devctl](https://github.com/unmango/devctl) but its use is optional.
Targets will obtain dependencies automatically as needed.
Binaries are stored in a `.gitignore`d `bin/` directory at the root of the repository.
An example `.envrc` file for [direnv](https://github.com/direnv/direnv) is provided in [hack/example.envrc](./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.
## References
GNU Make Quick Reference: