GoAhead → Compile-time Code Generation for Go
GoAhead is a compile-time code generation tool for Go. It replaces placeholder comments with computed values at build time.


Highlights
- Works with Go commands via
-toolexec (build, test, run, generate)
- Placeholder replacement keeps surrounding expressions intact
- Supports parameterised helpers, raw Go expressions, and simple type inference
- Supports standard-library packages directly (strings, strconv, os, http, etc.)
- Hierarchical function inheritance: subdirectories can shadow parent functions
- Supports variadic functions, constants, type definitions, and complex expressions
- Cross-platform compatible (Linux, macOS, Windows)
- Includes examples and an integration test suite
Installation
go install github.com/AeonDave/goahead@latest
Quick Start
1. Declare build-time helpers
Helpers live in files marked with both build tags. They are excluded from normal compilation but executed by GoAhead.
//go:build exclude
//go:ahead functions
package helpers
import "strings"
func welcome(name string) string {
return "Hello, " + strings.ToUpper(name)
}
2. Reference helpers + build
package main
//:welcome:"gopher"
var greeting = ""
go build -toolexec="goahead" ./...
Placeholder Grammar Reference
Basic Syntax
//:functionName[:arg1[:arg2[:argN]]]
The placeholder comment must appear on the line immediately before the target statement or expression.
Argument Types
| Type |
Syntax |
Examples |
| String |
Double quotes |
"hello", "production", "" |
| Raw String |
Backticks |
`raw\nstring` |
| Integer |
Unquoted number |
42, -17, 0xFF, 0o755, 0b1010 |
| Float |
Decimal or scientific |
3.14, -2.5, 1.5e10 |
| Boolean |
Unquoted |
true, false, True, False |
| Raw Expression |
Prefix with = |
=strings.TrimSpace(input) |
GoAhead supports all Go number literal formats:
//:process:42 // Decimal
//:process:0xFF // Hexadecimal
//:process:0o755 // Octal
//:process:0b1010 // Binary
//:process:-3.14 // Negative float
//:process:1.5e10 // Scientific notation
Raw Expressions
Prefix an argument with = to pass a raw Go expression that will be evaluated at build time:
//:hash:=strings.TrimSpace(input)
//:detect:=[]byte{0x89, 0x50, 0x4E, 0x47}
//:process:=map[string]int{"a": 1, "b": 2}
Raw expressions can contain colons (e.g., in map literals, struct literals, slice expressions) - GoAhead correctly parses nested brackets.
Variadic Functions
GoAhead fully supports variadic helper functions:
// Helper definition
func joinAll(sep string, parts ...string) string {
return strings.Join(parts, sep)
}
func sum(nums ...int) int {
total := 0
for _, n := range nums { total += n }
return total
}
// Usage - pass any number of arguments
//:joinAll:"-":"a":"b":"c":"d"
result = "" // → "a-b-c-d"
//:sum:1:2:3:4:5
total = 0 // → 15
Placeholder Targets
The placeholder replaces the first matching literal in the following statement:
| Return Type |
Placeholder |
Example Target |
string |
"" or ` ` |
msg = "" |
int |
0 |
count = 0 |
float64 |
0.0 |
rate = 0.0 |
bool |
false |
enabled = false |
uint |
0xff (hex) |
flags = 0xff |
Helper File Features
Constants and Variables
Helper files can define constants and variables at package level:
//go:build exclude
//go:ahead functions
package helpers
const (
Prefix = "APP_"
Separator = "::"
)
var defaultTimeout = 30
func prefixed(key string) string {
return Prefix + key
}
Custom Types
Define and use custom types within helper files:
type Status int
const (
StatusPending Status = iota
StatusActive
StatusCompleted
)
func getDefaultStatus() Status {
return StatusActive
}
func statusName(s Status) string {
names := []string{"Pending", "Active", "Completed"}
return names[s]
}
Multiple Helper Files
GoAhead scans the entire directory tree and resolves functions using hierarchical inheritance:
- Functions resolve bottom-up from source file's directory to root
- A source file "sees" functions from its own directory + all ancestors
- Local shadows parent: A subdirectory's function overrides the parent's function of the same name
- Siblings are isolated:
pkg1/ cannot see pkg2/'s functions (only common ancestors)
project/
├── helpers.go # version() -> "1.0.0", common() -> "shared"
├── main.go # uses version() → "1.0.0", common() → "shared"
├── pkg1/
│ ├── helpers.go # version() -> "2.0.0-pkg1" ← shadows root
│ └── main.go # uses version() → "2.0.0-pkg1", common() → "shared"
└── pkg2/
└── main.go # uses version() → "1.0.0", common() → "shared"
Console output shows which helper file was used:
[goahead] Replaced in pkg1/main.go: version() -> "2.0.0-pkg1" (from pkg1/helpers.go)
[goahead] WARNING: Function 'version' in pkg1/helpers.go shadows function in helpers.go
⚠️ Duplicate function names in the same directory cause a fatal error - GoAhead will exit with an error showing both file locations.
Standard Library Integration
Auto-detected Packages
GoAhead automatically resolves common standard library packages:
//:strings.ToUpper:"hello"
upper = "" // → "HELLO"
//:os.Getenv:"HOME"
home = "" // → "/home/user"
//:strconv.Itoa:42
str = "" // → "42"
//:http.DetectContentType:=[]byte("PNG data")
mime = "" // → "application/octet-stream"
Function Injection
GoAhead can inject entire functions from helper files into your source code. This is useful for obfuscation scenarios where you need both an encoding function (executed at build time) and a decoding function (included in runtime).
Syntax
Inject markers must appear above an interface declaration and reference methods that exist in that interface:
//:inject:MethodName1
//:inject:MethodName2
type MyInterface interface {
MethodName1(args) returnType
MethodName2(args) returnType
}
GoAhead will:
- Validate that each method exists in the interface
- Find the implementation in helper files
- Inject the function code at the end of the file
- Remove the markers
Example: String Obfuscation
Helper file (helpers.go):
//go:build exclude
//go:ahead functions
package main
const xorKey byte = 0x42
func Shadow(s string) string {
result := ""
for _, c := range s {
result += string(byte(c) ^ xorKey)
}
return result
}
func Unshadow(s string) string {
result := ""
for _, c := range s {
result += string(byte(c) ^ xorKey)
}
return result
}
Source file (main.go):
package main
import "fmt"
//:Shadow:"secret password"
var encrypted = ""
//:inject:Unshadow
type Decoder interface {
Unshadow(s string) string
}
func main() {
fmt.Println(Unshadow(encrypted))
}
After processing:
package main
import "fmt"
var encrypted = "1'!0'6r2#115/0&"
type Decoder interface {
Unshadow(s string) string
}
func main() {
fmt.Println(Unshadow(encrypted))
}
const xorKey byte = 0x42
func Unshadow(s string) string {
result := ""
for _, c := range s {
result += string(byte(c) ^ xorKey)
}
return result
}
What Gets Injected
When you use //:inject:FunctionName above an interface:
- Validates that the method exists in the interface (error if not)
- Removes any existing function with the same name (to allow updates)
- Copies the function from the helper file to the end of the source file
- Adds required imports used by the function
- Includes dependencies (constants, variables, types) that the function uses
- Includes helper-to-helper dependencies (other helper functions called by the injected function)
- Respects hierarchy - uses the function from the nearest helper file
- Preserves the marker - the
//:inject: comment stays in the source file
Subsequent Builds
Unlike placeholder comments which remain static after first replacement, inject markers work differently:
- Marker stays: The
//:inject: comment is never removed
- Function is replaced: On each build, the existing injected code block is removed and re-injected
- Updates propagate: If you change the helper file, the next build will update the injected function
- Generated code marker: Injected code is wrapped in standard Go generated code comments
This makes it easy to iterate on implementations (e.g., changing encryption algorithms) without manual editing.
Error Conditions
- Method not in interface: If
//:inject:Foo is used but Foo is not a method in the interface, GoAhead exits with an error
- Markers not followed by interface: If inject markers are not immediately followed by an interface declaration, GoAhead exits with an error
- Implementation not found: If no helper file contains the function, GoAhead exits with an error
CGO Compatibility
GoAhead works correctly with CGO files:
package main
/*
#include <stdio.h>
#cgo LDFLAGS: -lm
// C comments with colons: like:this are preserved
*/
import "C"
var (
//:getLibVersion
libVersion = ""
)
CGO preambles, directives, and comments are fully preserved.
Command-line Reference
goahead -dir=<path> [-verbose] [-version]
| Flag |
Description |
-dir |
Directory to process (default .) |
-verbose |
Print detailed progress information |
-version |
Print the tool version and exit |
-help |
Show the extended usage banner |
Environment Variables
GOAHEAD_VERBOSE=1 # Force verbose output when invoked via toolexec
- Ensure helper file has both
//go:build exclude and //go:ahead functions
- Check function name matches exactly (case-sensitive)
- Verify argument count matches function signature
Type mismatch
- Ensure the literal placeholder matches the return type
- Use
0 for int, 0.0 for float, "" for string, false for bool
Colon in argument
- Wrap strings in quotes:
"http://localhost:8080"
- For raw expressions, use
=: //:process:=map[string]int{"a": 1}
CGO errors
- GoAhead preserves CGO preambles - check C code syntax separately
- Ensure
import "C" appears alone after the preamble