commit ac35ae5
lung
·
2026-11-03 02:14:47 +0000 UTC
parent 2357bf2
add c.compile_commands(), make buildc.rv idiomatic revo
2 files changed,
+159,
-57
+158,
-57
1@@ -1,110 +1,211 @@
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+const settings = {
9+ # we use zig's C toolchain by default.
10+ cc = {"zig", "cc"},
11+ ar = {"zig", "ar"},
12+ flags = {},
13+ link_flags = {},
14+ include_dirs = {},
15+ defines = {},
16+ deps = {},
17 }
18
19-pub fn configure(opts) do
20+type Flags = table<string>
21+ type Compilation = {file: string, output: string, command: Flags}
22+
23+#* kept for compile_commands.json *#
24+const compilations: table<Compilation> = {}
25+
26+#* override the global c settings *#
27+pub fn configure(opts: table) -> :nil do
28 for key in table.keys(opts) do
29- if (not table.key?(settings, key)) panic("unknown C setting:", key)
30+ if not table.key?(settings, key) do
31+ panic("unknown C setting:", key)
32+ end
33+
34 settings[key] = opts[key]
35 end
36-end
37-
38-fn get(opts, key, fallback) do
39- if (table.key?(opts, key)) return opts[key]
40- return fallback
41-end
42
43-fn append(args, values) do
44- for value in values do
45- args:push(value)
46- end
47+ return :nil
48 end
49
50-fn combined(opts, key) do
51- const values = table.copy(settings[key])
52- append(values, get(opts, key, {}))
53- return values
54+fn combined(opts: table, key: atom) -> Flags do
55+ return settings[key]:merge(table.key(opts, key) orelse {})
56 end
57
58-fn action(path, args) do
59+#*
60+ run a command into a temp path, then move it into place
61+ the returned command may panic, sorry
62+*#
63+fn action(path: string, args: Flags) -> (fn() -> :ok) do
64 return fn() do
65 const parts = path:split("/")
66 parts:pop()
67 const dir = table.join(parts, "/")
68- if (dir != "") fs.mkdir(dir, :true):unwrap()
69+
70+ if dir != "" do
71+ fs.mkdir(dir, :true):unwrap()
72+ end
73+
74 const temporary = path ~ ".tmp"
75- if (fs.exists?(temporary)) fs.remove(temporary):unwrap()
76+
77+ if fs.exists?(temporary) do
78+ fs.remove(temporary):unwrap()
79+ end
80+
81 engine.cmd(args)
82- fs.rename(temporary, path):unwrap()
83+
84+ return fs.rename(temporary, path):unwrap()
85 end
86 end
87
88-fn output_rule(kind, path, opts) do
89- const cc = get(opts, :cc, settings.cc)
90+fn output_rule(kind: atom, path: string, opts: table) -> string do
91+ const cc = table.key(opts, :cc) orelse settings.cc
92 const deps = combined(opts, :deps)
93+
94 # editing the manifest invalidates its outputs too.
95 deps:push(argv[0])
96- const sources = get(opts, :sources, {})
97- const objects = table.copy(get(opts, :objects, {}))
98+
99+ const sources = table.key(opts, :sources) orelse {}
100+ const objects = table.copy(table.key(opts, :objects) orelse {})
101+
102 for source in sources do
103- const stem = if source:ends_with?(".c") do
104- source:sub(0, source:len() - 2)
105- end else do
106- source
107- end
108+ const stem =
109+ if (source:ends_with?(".c"))
110+ source:sub(0, source:len() - 2)
111+ else
112+ source
113+
114 const object = path ~ ".obj/" ~ stem ~ ".o"
115- const args = table.copy(cc)
116- append(args, combined(opts, :flags))
117+ const args = cc:merge(combined(opts, :flags))
118+
119 for dir in combined(opts, :include_dirs) do
120 args:push("-I", dir)
121 end
122+
123 for define in combined(opts, :defines) do
124 args:push("-D" ~ define)
125 end
126- if (kind == :shared) args:push("-fPIC")
127+
128+ if kind == :shared do
129+ args:push("-fPIC")
130+ end
131+
132 const temporary = object ~ ".tmp"
133 args:push("-c", source, "-o", temporary)
134- const inputs = table.copy(deps)
135- inputs:push(source)
136- # construct the rule
137+
138+ # push the real one not temp
139+ const command = table.copy(args)
140+ command[table.len(command) - 1] = object
141+ compilations:push({file = source, output = object, command = command})
142+
143+ const inputs = deps:merge({source})
144 engine.rule(object, inputs, action(object, args))
145+
146 const label = "cc " ~ source
147 engine.describe(object, label)
148 objects:push(object)
149 end
150
151- const libraries = get(opts, :libraries, {})
152- const inputs = table.copy(deps)
153- append(inputs, objects)
154- append(inputs, libraries)
155- let args = table.copy(cc)
156+ const libraries = table.key(opts, :libraries) orelse {}
157+ const inputs = deps
158+ :merge(objects)
159+ :merge(libraries)
160+
161+ let args = cc
162+
163 if kind == :static do
164- if (table.len(libraries) > 0) panic("static libraries take objects, not libraries:", path)
165- args = table.copy(get(opts, :ar, settings.ar))
166+ if not table.empty?(libraries) do
167+ panic("static libraries take objects, not libraries:", path)
168+ end
169+
170 const temporary = path ~ ".tmp"
171- args:push("rcs", temporary)
172- append(args, objects)
173+
174+ args = (table.key(opts, :ar) orelse settings.ar):merge({"rcs", temporary})
175+ args = args:merge(objects)
176 end else do
177- append(args, combined(opts, :flags))
178- if (kind == :shared) args:push("-shared")
179- append(args, objects)
180- append(args, libraries)
181- append(args, combined(opts, :link_flags))
182+ args = args:merge(combined(opts, :flags))
183+
184+ if kind == :shared do
185+ args:push("-shared")
186+ end
187+
188+ args = args
189+ :merge(objects)
190+ :merge(libraries)
191+ :merge(combined(opts, :link_flags))
192+
193 const temporary = path ~ ".tmp"
194 args:push("-o", temporary)
195 end
196+
197 engine.rule(path, inputs, action(path, args))
198+
199 const prefixes = {executable = "ld ", static = "ar ", shared = "shared "}
200 const link_label = prefixes[kind] ~ path
201 engine.describe(path, link_label)
202+
203 return path
204 end
205
206-pub fn executable(path, opts) output_rule(:executable, path, opts)
207-pub fn static_library(path, opts) output_rule(:static, path, opts)
208-pub fn shared_library(path, opts) output_rule(:shared, path, opts)
209+#* build an executable *#
210+pub fn executable(path: string, opts: table) -> string
211+ return output_rule(:executable, path, opts)
212+
213+#* build an static library *#
214+pub fn static_library(path: string, opts: table) -> string
215+ return output_rule(:static, path, opts)
216
217-pub fn build(outputs) engine.run(outputs)
218+#* build an shared library *#
219+pub fn shared_library(path: string, opts: table) -> string
220+ return output_rule(:shared, path, opts)
221+
222+fn quote(arg: string) -> string do
223+ if arg:len() > 0
224+ and not arg:contains?(" ")
225+ and not arg:contains?("\t")
226+ and not arg:contains?("'")
227+ and not arg:contains?("\"") do
228+ return arg
229+ end
230+
231+ return "'" ~ arg:replace("'", "'\\''") ~ "'"
232+end
233+
234+#*
235+ write compile_commands.json into pwd for every source compiled so far
236+ just put this at the end of your script
237+*#
238+pub fn compile_commands() -> :nil do
239+ const directory = cwd()
240+
241+ const entries =
242+ to_iter(compilations)
243+ :map(fn(compilation) do
244+ const quoted =
245+ to_iter(compilation.command)
246+ :map(quote)
247+ :collect()
248+
249+ return {
250+ directory = directory,
251+ file = compilation.file,
252+ output = compilation.output,
253+ command = quoted:join(" "),
254+ }
255+ end)
256+ :collect()
257+
258+ const text = json.encode(entries, :true)?
259+ const file = fs.open("compile_commands.json", "w")?
260+
261+ file:write(text ~ "\n")?
262+ file:close()?
263+
264+ return :nil
265+end
266+
267+pub fn build(outputs: table<string>) -> :ok do
268+ return engine.run(outputs)
269+end
+1,
-0
1@@ -20,3 +20,4 @@ const app = c.executable("out/calculator", {
2 )
3
4 c.build({app})
5+c.compile_commands()