commit 2357bf2
shrub
·
2026-11-03 00:32:46 +0000 UTC
parent e1178c0
add C builds
12 files changed,
+204,
-18
M
README
+4,
-2
1@@ -7,6 +7,8 @@ checking dependencies, configure-script style stuff, and so on.
2
3 todo
4 ----
5-[ ] extensions for handling specific languages in a more high level way(c, zig)
6+[X] core build engine
7+[X] higher level handling for C
8+[ ] higher level handling for Zig
9 [ ] extensions for doing configure script style stuff easily(checking whats installed on the system)
10-[ ] be good enough to build revo itself(?...)
11+[ ] become part of the revo stdlib!
M
build.rv
+23,
-4
1@@ -11,6 +11,12 @@ pub fn rule(path, deps, action) do
2 rules[path] = {deps = table.unique(deps), action = action, phony = :false}
3 end
4
5+pub fn describe(name, text) do
6+ if (not table.key?(rules, name)) panic("unknown task:", name)
7+ const entry = rules[name]
8+ entry.description = text
9+end
10+
11 # return true when an output is missing or older than an input
12 pub fn timestamp(output, inputs) do
13 if (not fs.exists?(output)) return :true
14@@ -122,14 +128,19 @@ pub fn kahn(plan, jobs) do
15 break
16 end
17 const name = ready[ready_index]
18+ const entry = rules[name]
19+ let description = name
20+ if table.key?(entry, :description) do
21+ description = entry.description
22+ end
23 ready_index += 1
24 reported += 1
25 if dirty(name, rebuilt) do
26- print(fmt("[%v/%v] %v", reported, total, name))
27+ print(fmt("[%v/%v] %v", reported, total, description))
28 handles[name] = spawn execute(name, completed)
29 active += 1
30 end else do
31- print(fmt("[%v/%v] %v (up to date)", reported, total, name))
32+ print(fmt("[%v/%v] %v (up to date)", reported, total, description))
33 rebuilt[name] = :false
34 finished += 1
35 release(name, pending, followers, ready)
36@@ -150,8 +161,16 @@ pub fn kahn(plan, jobs) do
37 return :ok
38 end
39
40-pub fn run(plan) do
41- return kahn(plan, revo.threads())
42+# take a target argument
43+pub fn run(defaults) do
44+ if table.len(argv) > 1 do
45+ const targets = {}
46+ for i in 1..table.len(argv) do
47+ targets:push(argv[i])
48+ end
49+ return kahn(targets, revo.threads())
50+ end
51+ return kahn(defaults, revo.threads())
52 end
53
54 fn showout(text) do
+110,
-0
1@@ -0,0 +1,110 @@
2+const engine = import "./build.rv"
3+#we use zig's C toolchain by default.
4+const settings = {
5+ cc = {"zig", "cc"}, ar = {"zig", "ar"}, flags = {}, link_flags = {},
6+ include_dirs = {}, defines = {}, deps = {},
7+}
8+
9+pub fn configure(opts) do
10+ for key in table.keys(opts) do
11+ if (not table.key?(settings, key)) panic("unknown C setting:", key)
12+ settings[key] = opts[key]
13+ end
14+end
15+
16+fn get(opts, key, fallback) do
17+ if (table.key?(opts, key)) return opts[key]
18+ return fallback
19+end
20+
21+fn append(args, values) do
22+ for value in values do
23+ args:push(value)
24+ end
25+end
26+
27+fn combined(opts, key) do
28+ const values = table.copy(settings[key])
29+ append(values, get(opts, key, {}))
30+ return values
31+end
32+
33+fn action(path, args) do
34+ return fn() do
35+ const parts = path:split("/")
36+ parts:pop()
37+ const dir = table.join(parts, "/")
38+ if (dir != "") fs.mkdir(dir, :true):unwrap()
39+ const temporary = path ~ ".tmp"
40+ if (fs.exists?(temporary)) fs.remove(temporary):unwrap()
41+ engine.cmd(args)
42+ fs.rename(temporary, path):unwrap()
43+ end
44+end
45+
46+fn output_rule(kind, path, opts) do
47+ const cc = get(opts, :cc, settings.cc)
48+ const deps = combined(opts, :deps)
49+ # editing the manifest invalidates its outputs too.
50+ deps:push(argv[0])
51+ const sources = get(opts, :sources, {})
52+ const objects = table.copy(get(opts, :objects, {}))
53+ for source in sources do
54+ const stem = if source:ends_with?(".c") do
55+ source:sub(0, source:len() - 2)
56+ end else do
57+ source
58+ end
59+ const object = path ~ ".obj/" ~ stem ~ ".o"
60+ const args = table.copy(cc)
61+ append(args, combined(opts, :flags))
62+ for dir in combined(opts, :include_dirs) do
63+ args:push("-I", dir)
64+ end
65+ for define in combined(opts, :defines) do
66+ args:push("-D" ~ define)
67+ end
68+ if (kind == :shared) args:push("-fPIC")
69+ const temporary = object ~ ".tmp"
70+ args:push("-c", source, "-o", temporary)
71+ const inputs = table.copy(deps)
72+ inputs:push(source)
73+ # construct the rule
74+ engine.rule(object, inputs, action(object, args))
75+ const label = "cc " ~ source
76+ engine.describe(object, label)
77+ objects:push(object)
78+ end
79+
80+ const libraries = get(opts, :libraries, {})
81+ const inputs = table.copy(deps)
82+ append(inputs, objects)
83+ append(inputs, libraries)
84+ let args = table.copy(cc)
85+ if kind == :static do
86+ if (table.len(libraries) > 0) panic("static libraries take objects, not libraries:", path)
87+ args = table.copy(get(opts, :ar, settings.ar))
88+ const temporary = path ~ ".tmp"
89+ args:push("rcs", temporary)
90+ append(args, objects)
91+ end else do
92+ append(args, combined(opts, :flags))
93+ if (kind == :shared) args:push("-shared")
94+ append(args, objects)
95+ append(args, libraries)
96+ append(args, combined(opts, :link_flags))
97+ const temporary = path ~ ".tmp"
98+ args:push("-o", temporary)
99+ end
100+ engine.rule(path, inputs, action(path, args))
101+ const prefixes = {executable = "ld ", static = "ar ", shared = "shared "}
102+ const link_label = prefixes[kind] ~ path
103+ engine.describe(path, link_label)
104+ return path
105+end
106+
107+pub fn executable(path, opts) output_rule(:executable, path, opts)
108+pub fn static_library(path, opts) output_rule(:static, path, opts)
109+pub fn shared_library(path, opts) output_rule(:shared, path, opts)
110+
111+pub fn build(outputs) engine.run(outputs)
+1,
-0
1@@ -0,0 +1 @@
2+/out/
+6,
-0
1@@ -0,0 +1,6 @@
2+#include "calc.h"
3+
4+int add(int a, int b)
5+{
6+ return a + b;
7+}
+9,
-0
1@@ -0,0 +1,9 @@
2+#ifndef EXAMPLE_CALC_H
3+#define EXAMPLE_CALC_H
4+
5+int add(int a, int b);
6+int subtract(int a, int b);
7+int multiply(int a, int b);
8+int divide(int a, int b);
9+
10+#endif
+6,
-0
1@@ -0,0 +1,6 @@
2+#include "calc.h"
3+
4+int divide(int a, int b)
5+{
6+ return a / b;
7+}
+11,
-0
1@@ -0,0 +1,11 @@
2+#include <stdio.h>
3+#include "calc.h"
4+
5+int main(void)
6+{
7+ printf("6 + 3 = %d\n", add(6, 3));
8+ printf("6 - 3 = %d\n", subtract(6, 3));
9+ printf("6 * 3 = %d\n", multiply(6, 3));
10+ printf("6 / 3 = %d\n", divide(6, 3));
11+ return 0;
12+}
+6,
-0
1@@ -0,0 +1,6 @@
2+#include "calc.h"
3+
4+int multiply(int a, int b)
5+{
6+ return a * b;
7+}
+22,
-0
1@@ -0,0 +1,22 @@
2+const c = import "../../buildc.rv"
3+
4+#we can list the directory and glob for .c to find our sources automatically.
5+const sources = {}
6+for entry in fs.readdir("."):unwrap() do
7+ if (entry.kind == :file and entry.name:ends_with?(".c"))
8+ sources:push(entry.name)
9+end
10+table.sort(sources)
11+
12+# global C settings.
13+c.configure({
14+ flags = {"-std=c99", "-Wall", "-Wextra", "-Werror", "-O2"},
15+ deps = {"calc.h"},
16+})
17+
18+const app = c.executable("out/calculator", {
19+ sources = sources,
20+ cc = {"cc"}} # we can also override settings only for specific outputs
21+)
22+
23+c.build({app})
+6,
-0
1@@ -0,0 +1,6 @@
2+#include "calc.h"
3+
4+int subtract(int a, int b)
5+{
6+ return a - b;
7+}
D
test.rvb
+0,
-12
1@@ -1,12 +0,0 @@
2-const build = import "./build.rv"
3-
4-build.task("prepare", {}, fn() do
5- build.cmd({"ls"})
6-end)
7-
8-build.task("test", {"prepare"}, fn() do
9- build.cmd({"echo", "hi from the build!"})
10-end)
11-
12-const plan = build.mkplan({"test"})
13-build.run(plan)