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