main
buildzig.rv
1const engine = import "./build.rv"
2
3const settings = {
4 compiler = {"zig"},
5 target = "",
6 cpu = "",
7 optimize = "",
8 code_model = "",
9 red_zone = :nil,
10 single_threaded = :nil,
11 pic = :nil,
12 link_libc = :false,
13 flags = {},
14 module_flags = {},
15 source_dirs = {},
16 deps = {},
17}
18
19type Flags = table<string>
20 let next_module = 0
21
22#* override the global zig settings *#
23pub fn configure(opts: table) -> :nil do
24 for key in table.keys(opts) do
25 if not table.key?(settings, key) do
26 panic("unknown Zig setting:", key)
27 end
28
29 settings[key] = opts[key]
30 end
31
32 return :nil
33end
34
35fn key_name(key: any) -> string do
36 const name = string(key)
37
38 if typeof(key) == :atom do
39 return name:sub(1, name:len() - 1)
40 end
41
42 return name
43end
44
45#* declare a compiler module, imports map local names to other module handles *#
46pub fn module(opts: table) -> table do
47 const options = settings:merge(opts)
48 options.module_flags = settings.module_flags:merge(table.key(opts, :module_flags) orelse {})
49 options.deps = settings.deps:merge(table.key(opts, :deps) orelse {})
50
51 const id = "m" ~ string(next_module)
52 next_module += 1
53 const imports = {}
54 const declared_imports = table.key(opts, :imports) orelse {}
55
56 for key in table.keys(declared_imports) do
57 const name = key_name(key)
58 imports[name] = declared_imports[key]
59 end
60
61 return {
62 id = id,
63 root = opts.root,
64 options = options,
65 imports = imports,
66 }
67end
68
69#* connect modules before declaring their output *#
70pub fn add_import(owner: table, name: string, imported: table) -> :nil do
71 owner.imports[name] = imported
72 return :nil
73end
74
75#* a file for @embedFile *#
76pub fn file(path: string) -> table do
77 return module({root = path, raw = :true})
78end
79
80fn collect(entry: table, seen: table, modules: table) -> :nil do
81 if table.key?(seen, entry.id) do
82 return :nil
83 end
84
85 seen[entry.id] = :true
86 modules:push(entry)
87
88 for key in table.sort(table.keys(entry.imports)) do
89 collect(entry.imports[key], seen, modules)
90 end
91
92 return :nil
93end
94
95# translate settings into something the zig cli can understand
96fn module_args(entry: table) -> Flags do
97 const args: Flags = {}
98 const opts = entry.options
99
100 if not (table.key(opts, :raw) orelse :false) do
101 for pair in {
102 {:target, "-target"}, {:cpu, "-mcpu"},
103 {:optimize, "-O"}, {:code_model, "-mcmodel"},
104 } do
105 const value = opts[pair[0]]
106
107 if value != "" do
108 args:push(pair[1], value)
109 end
110 end
111
112 for pair in {
113 {:red_zone, "-mred-zone", "-mno-red-zone"},
114 {:single_threaded, "-fsingle-threaded", "-fno-single-threaded"},
115 {:pic, "-fPIC", "-fno-PIC"},
116 } do
117 const value = opts[pair[0]]
118
119 if value == :true do
120 args:push(pair[1])
121 end else if value == :false do
122 args:push(pair[2])
123 end
124 end
125
126 if opts.link_libc do
127 args:push("-lc")
128 end
129 end
130
131 for key in table.sort(table.keys(entry.imports)) do
132 const name = key_name(key)
133 const dependency = entry.imports[key]
134 const mapping = name ~ "=" ~ dependency.id
135 args:push("--dep", mapping)
136 end
137
138 return args
139 :merge(opts.module_flags)
140 :merge({"-M" ~ entry.id ~ "=" ~ entry.root})
141end
142
143fn action(path: string, args: Flags) -> (fn() -> :ok) do
144 return fn() do
145 const parts = path:split("/")
146 parts:pop()
147 const dir = table.join(parts, "/")
148
149 if dir != "" do
150 fs.mkdir(dir, :true):unwrap()
151 end
152
153 engine.cmd(args)
154 return fs.rename(path ~ ".tmp", path):unwrap()
155 end
156end
157
158#* track every file in declared source trees *#
159fn source_files(dir: string, inputs: Flags) -> :nil do
160 for entry in fs.readdir(dir):unwrap() do
161 const path = dir ~ "/" ~ entry.name
162
163 if entry.kind == :directory do
164 source_files(path, inputs)
165 end else if entry.kind == :file do
166 inputs:push(path)
167 end
168 end
169
170 return :nil
171end
172
173fn output_rule(kind: atom, path: string, opts: table) -> string do
174 const root = table.key(opts, :root_module) orelse module(opts)
175 const modules = {}
176 collect(root, {}, modules)
177
178 const compiler = table.key(opts, :compiler) orelse settings.compiler
179 const commands = {executable = "build-exe", static = "build-lib", shared = "build-lib", object = "build-obj"}
180 let args = compiler:merge({commands[kind]})
181
182 if kind == :shared do
183 const basename = path:split("/"):last()
184 const soname = "-fsoname=" ~ basename
185 args:push("-dynamic", soname)
186 end
187
188 const entry = table.key(opts, :entry) orelse ""
189
190 if entry != "" do
191 const symbol = "-fentry=" ~ entry
192 args:push(symbol)
193 end
194
195 const pie = table.key(opts, :pie)
196
197 if pie == :true do
198 args:push("-fPIE")
199 end else if pie == :false do
200 args:push("-fno-PIE")
201 end
202
203 const inputs = settings.deps:merge(table.key(opts, :deps) orelse {})
204 const script = table.key(opts, :linker_script) orelse ""
205
206 if script != "" do
207 args:push("--script", script)
208 inputs:push(script)
209 end
210
211 const links = table.key(opts, :link_files) orelse {}
212 args = args
213 :merge(settings.flags)
214 :merge(table.key(opts, :flags) orelse {})
215 :merge(links)
216
217 inputs:push(argv[0])
218
219 for dependency in links do
220 inputs:push(dependency)
221 end
222
223 for entry_module in modules do
224 inputs:push(entry_module.root)
225 const dependencies = entry_module.options.deps
226
227 for dependency in dependencies do
228 inputs:push(dependency)
229 end
230
231 args = args:merge(module_args(entry_module))
232 end
233
234 const emitted = "-femit-bin=" ~ path ~ ".tmp"
235 args:push(emitted)
236
237 const dirs = settings.source_dirs:merge(table.key(opts, :source_dirs) orelse {})
238
239 for dir in table.unique(dirs) do
240 source_files(dir, inputs)
241 end
242
243 if table.empty?(dirs) do
244 engine.task(path, inputs, action(path, args))
245 end else do
246 engine.rule(path, inputs, action(path, args))
247 end
248 engine.track(path, args)
249 const label = "zig " ~ path
250 engine.describe(path, label)
251
252 return path
253end
254
255#* build an executable *#
256pub fn executable(path: string, opts: table) -> string
257 return output_rule(:executable, engine.output(path), opts)
258
259#* build a static library *#
260pub fn static_library(path: string, opts: table) -> string
261 return output_rule(:static, engine.output(path), opts)
262
263#* build a shared library *#
264pub fn shared_library(path: string, opts: table) -> string
265 return output_rule(:shared, engine.output(path), opts)
266
267#* build an object file *#
268pub fn object(path: string, opts: table) -> string
269 return output_rule(:object, engine.output(path), opts)
270
271#* build command-line targets, or the given outputs if none are specified *#
272pub fn build(outputs: Flags) -> :ok do
273 return engine.run(outputs)
274end