commit 2357bf2

shrub  ·  2026-11-03 00:32:46 +0000 UTC
parent e1178c0
add C builds
12 files changed,  +204, -18
M README
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!
+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+}
+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)