patbuild: a goal-oriented build system
Driven by a manifest file (self_hosting/patbuild.manifest):
# patbuild manifest: name <source or -> [deps...] fib_bench self_hosting/examples/fib_bench.patlang echo_server self_hosting/examples/echo_server.patlang contracts_demo self_hosting/examples/contracts_demo.patlang pos_demo self_hosting/lib/pos.patlang+self_hosting/examples/pos_demo.patlang all - fib_bench echo_server contracts_demo pos_demoTargets are objects, dependencies are walked recursively, finished builds become
facts, progress is reported via events. By default each target is only lowered to IR and checked (no rustc at all, matching the in-browser playground); pass --native to also compile every target to a real executable via rustc โ the transcript below was captured that way. Native transcript only, no "run in browser" button: unlike the demos above, patbuild genuinely needs real filesystem access it can't get in the WASI sandbox โ it reads the manifest file plus every target's own source file from disk by path, and (with --native) invokes a real rustc subprocess per target, neither of which a browser sandbox can provide. This is a capability limit, not a performance one (compare the dynamic-syntax demo below, which CAN run in-browser, just impractically slowly for now).
PatLang source
# =============================================================================
# patbuild: a goal-oriented build system in PatLang (concatenate after the
# compiler libs). Targets are OO objects, the dependency graph is walked
# recursively, completed builds are recorded as facts, and progress is
# reported through events. By default each target is lowered and executed
# via run_ir (no rustc, matching how the in-browser playground works); pass
# --native to compile targets to real .exe artifacts via rustc_build instead.
# =============================================================================
make a function called target takes name, src, deps returns done
new("Target", name)
send(name, "set", "src", src)
send(name, "set", "deps", deps)
send(name, "set", "built", "no")
return true
end
make a function called split_ws takes s returns parts
let parts = []
let cur = sb_new()
let i = 0
while i <= s.length do
let c = char_code(s, i)
if (c == 32) or (c == -1) then
let word = sb_str(cur)
if word != "" then
let parts = list_push(parts, word)
end
let cur = sb_new()
else
sb_push(cur, s[i])
end
let i = i + 1
end
return parts
end
# Splits a source spec on '+' so a target can bundle a library with its
# driver without a module system, e.g. "lib/pos.patlang+examples/pos_demo.patlang"
make a function called split_plus takes s returns parts
let parts = []
let cur = sb_new()
let i = 0
while i <= s.length do
let c = char_code(s, i)
if (c == 43) or (c == -1) then
let piece = sb_str(cur)
let cur = sb_new()
if piece != "" then
let parts = list_push(parts, piece)
end
else
sb_push(cur, s[i])
end
let i = i + 1
end
return parts
end
make a function called read_bundle takes src returns source
let files = split_plus(src)
let b = sb_new()
let i = 0
while i < files.length do
if i > 0 then
sb_push(b, chr(10))
end
sb_push(b, read_file(files[i]))
let i = i + 1
end
return sb_str(b)
end
make a function called build takes name returns ok
if get(name, "built") == "yes" then
return true
end
let deps = split_ws(get(name, "deps"))
let i = 0
while i < deps.length do
build(deps[i])
let i = i + 1
end
let src = get(name, "src")
if src != "" then
let source = read_bundle(src)
let toks = tokenize(source)
let ast = parse_program(toks)
let ir = lower_program(ast)
if get("__vars", "native_mode") == 1 then
let rs = emit_program_rs(ir)
let exe = rustc_build(rs, "portfolio/build/" + name + ".exe")
fact("built", name, exe)
else
# Default: verify the self-hosted front end compiles this target to IR,
# with no rustc involved. Deliberately does not execute the IR here -
# some targets (e.g. echo_server) block on a real network connection
# and are meant to be run directly via `pat --ir-run`, not driven
# unattended by a build tool.
print("[patbuild] compiled " + name + " to IR (no rustc)")
fact("built", name, "(ir-only)")
end
end
send(name, "set", "built", "yes")
emit("built", name)
return true
end
when built do
print("[patbuild] built: " + event_data)
end
# ---- manifest ----
# Format, one target per line: name <source-path or -> [dep ...]
make a function called load_manifest takes path returns count
let count = 0
let text = read_file(path)
let h = str_intern(text)
let n = sc_len(h)
let i = 0
let line = sb_new()
while i <= n do
let c = sc_code(h, i)
if (c == 10) or (c == -1) then
let raw = sb_str(line)
let line = sb_new()
let parts = split_ws(raw)
if parts.length >= 2 then
if char_code(raw, 0) != 35 then
let src = parts[1]
if src == "-" then
let src = ""
end
let deps = sb_new()
let k = 2
while k < parts.length do
if k > 2 then
sb_push(deps, " ")
end
sb_push(deps, parts[k])
let k = k + 1
end
target(parts[0], src, sb_str(deps))
let count = count + 1
end
end
let i = i + 1
else
if c != 13 then
sb_push(line, sc_char(h, i))
end
let i = i + 1
end
end
return count
end
set_var("native_mode", 0)
let raw_args = argv()
let positional = []
let i = 0
while i < raw_args.length do
if raw_args[i] == "--native" then
set_var("native_mode", 1)
else
let positional = list_push(positional, raw_args[i])
end
let i = i + 1
end
let manifest = "self_hosting/patbuild.manifest"
let goal_target = "all"
if positional.length >= 1 then
let manifest = positional[0]
end
if positional.length >= 2 then
let goal_target = positional[1]
end
let loaded = load_manifest(manifest)
goal("ship", goal_target)
if get("__vars", "native_mode") == 1 then
print("[patbuild] manifest: " + manifest + " (" + loaded + " targets), goal: " + goal_target + " [native]")
else
print("[patbuild] manifest: " + manifest + " (" + loaded + " targets), goal: " + goal_target + " [interpreted]")
end
build(goal_target)
print("[patbuild] targets built: " + (query("built", "fib_bench", 0) + query("built", "echo_server", 0) + query("built", "contracts_demo", 0) + query("built", "pos_demo", 0)))
Native run on the build machine:
[patbuild] manifest: self_hosting/patbuild.manifest (5 targets), goal: all [native] [patbuild] built: fib_bench [patbuild] built: echo_server [patbuild] built: contracts_demo [patbuild] built: pos_demo [patbuild] built: all [patbuild] targets built: 4