aboutsummaryrefslogtreecommitdiff
path: root/bootstrap.tl
diff options
context:
space:
mode:
Diffstat (limited to 'bootstrap.tl')
-rwxr-xr-xbootstrap.tl518
1 files changed, 518 insertions, 0 deletions
diff --git a/bootstrap.tl b/bootstrap.tl
new file mode 100755
index 00000000..5017f117
--- /dev/null
+++ b/bootstrap.tl
@@ -0,0 +1,518 @@
1#!/usr/bin/env -S -- tl run
2
3local PLATFORM = arg[1] or "unix"
4
5local lfs = require("lfs")
6
7local dependencies = {
8 "md5",
9 "lua-zlib",
10 "lua-bz2",
11 "luafilesystem",
12 "luasocket",
13 "luasec",
14 "miniposix",
15}
16
17local c_module_exceptions: {string:{string}} = {
18 ["ssl"] = { "ssl.core", "ssl.context", "ssl.x509", "ssl.config" },
19}
20
21local rockspec_locations: {string:string} = {
22 ["md5"] = "vendor/md5/rockspec/md5-1.2-1.rockspec",
23 ["lua-zlib"] = "vendor/lua-zlib/lua-zlib-1.1-0.rockspec",
24 ["lua-bz2"] = "vendor/lua-bz2/lua-bz2-0.2.1-1.rockspec",
25 ["luafilesystem"] = "vendor/luafilesystem/luafilesystem-scm-1.rockspec",
26 ["luasocket"] = "vendor/luasocket/luasocket-scm-3.rockspec",
27 ["luasec"] = "vendor/luasec/luasec-1.3.2-1.rockspec",
28 ["miniposix"] = "vendor/miniposix/miniposix-dev-1.rockspec",
29}
30
31--------------------------------------------------------------------------------
32-- Utilities
33--------------------------------------------------------------------------------
34
35local hexdump: function(string): string
36do
37 local numtab = {}
38 for i = 0, 255 do
39 numtab[string.char(i)] = ("%-3d,"):format(i)
40 end
41 hexdump = function(str: string): string
42 return (str:gsub(".", numtab):gsub(("."):rep(80), "%0\n"))
43 end
44end
45
46local function apply_template(template: string, variables: {string:string}): string
47 return (template:gsub("$%(([^)]*)%)", variables))
48end
49
50local function reindent_c(input: string): string
51 local out = {}
52 local indent = 0
53 local previous_is_blank = true
54 for line in input:gmatch("([^\n]*)") do
55 line = line:match("^[ \t]*(.-)[ \t]*$")
56
57 local is_blank = (#line == 0)
58 local do_print =
59 (not is_blank) or
60 (not previous_is_blank and indent == 0)
61
62 if line:match("^[})]") then
63 indent = indent - 1
64 if indent < 0 then indent = 0 end
65 end
66 if do_print then
67 table.insert(out, string.rep(" ", indent))
68 table.insert(out, line)
69 table.insert(out, "\n")
70 end
71 if line:match("[{(]$") then
72 indent = indent + 1
73 end
74
75 previous_is_blank = is_blank
76 end
77 return table.concat(out)
78end
79
80local function sortedpairs<K, V>(tbl: {K: V}): function(): (K, V)
81 local keys = {}
82 for k, _ in pairs(tbl) do
83 table.insert(keys, k)
84 end
85 table.sort(keys, function(a: K, b: K): boolean
86 if a is integer and not b is integer then
87 return true
88 elseif not a is integer and b is integer then
89 return false
90 end
91 return (a as integer) < (b as integer)
92 end)
93 local i = 1
94 return function(): (K, V)
95 local key = keys[i]
96 i = i + 1
97 return key, tbl[key]
98 end
99end
100
101local function mkdir_p(dirname: string)
102 local a, b = dirname:match("(.*)/(.*)")
103 if a and b then
104 mkdir_p(a)
105 end
106 lfs.mkdir(dirname)
107end
108
109local function dirname(filename: string): string
110 return (filename:gsub("[^/]*$", ""))
111end
112
113local function write_template(filename: string, template: string, variables: {string:string})
114 local fd = assert(io.open(filename, "wb"))
115 local text = apply_template(template, variables)
116 if filename:match("%.[ch]$") then
117 text = reindent_c(text)
118 end
119 fd:write(text)
120 fd:close()
121end
122
123local function find_files(dir_name: string): (function(): string)
124 local iter_stack: {lfs.DirObj} = {}
125 local dir_stack: {string} = { dir_name }
126 local iter, dd = lfs.dir(dir_name)
127 return function(): string
128 while true do
129 local s = iter(dd)
130 if s ~= "." and s ~= ".." then
131 if not s then
132 if #iter_stack == 0 then
133 return nil
134 end
135 table.remove(dir_stack)
136 dd = table.remove(iter_stack)
137 else
138 local d = dir_stack[#dir_stack]
139 local pathname = d .. "/" .. s, "mode"
140 if lfs.attributes(pathname, "mode") ~= "directory" then
141 return pathname
142 else
143 table.insert(iter_stack, dd)
144 iter, dd = lfs.dir(pathname)
145 table.insert(dir_stack, pathname)
146 end
147 end
148 end
149 end
150 end
151end
152
153--------------------------------------------------------------------------------
154-- Collect data based on rockspecs in vendor/
155--------------------------------------------------------------------------------
156
157local record BuildEntry
158 sources: {string}
159 incdirs: {string}
160 defines: {string}
161end
162
163local record Data
164 record LuaEntry
165 dep: string
166 mod: string
167 source: string
168
169 h_file: string
170 length: integer
171 end
172 record CEntry
173 dep: string
174 mod: string
175 build: BuildEntry
176
177 a_file: string
178 end
179
180 luas: {LuaEntry}
181 cs: {CEntry}
182end
183
184local function add_lua(data: Data, dep: string, mod: string, source: string)
185 table.insert(data.luas, { dep = dep, mod = mod, source = source })
186end
187
188local function add_c(data: Data, dep: string, mod: string, build: BuildEntry)
189 table.insert(data.cs, { dep = dep, mod = mod, build = build })
190end
191
192local record Rockspec
193 record Build
194 record Install
195 lua: {(string | integer): string}
196 end
197
198 type: string
199 modules: {string: (string | BuildEntry)}
200 install: Install
201 platforms: {string: Build}
202 end
203
204 build: Build
205end
206
207local function load_rockspec(dep: string, rockspec_filename: string): Rockspec
208 local rockspec: Rockspec = {}
209 local fd = io.open(rockspec_filename)
210 local source = fd:read("*a")
211 fd:close()
212 local chunk = assert(load(source, dep, "t", rockspec as {any:any}))
213 chunk()
214 return rockspec
215end
216
217local function process_build_entry(data: Data, dep: string, mod: string, modt: string|BuildEntry)
218 if modt is string then
219 if modt:match("lua$") then
220 add_lua(data, dep, mod, "vendor/" .. dep .. "/" .. modt)
221 else
222 add_c(data, dep, mod, { sources = { modt } })
223 end
224 elseif modt is BuildEntry then
225 add_c(data, dep, mod, modt)
226 end
227end
228
229local function process_build_modules(data: Data, dep: string, entries: {string: (string|BuildEntry)})
230 for mod, modt in sortedpairs(entries) do
231 process_build_entry(data, dep, mod, modt)
232 end
233end
234
235local function process_build_install(data: Data, dep: string, entries: {(string|integer): string})
236 for mod, modt in sortedpairs(entries) do
237 if mod is integer then
238 assert(modt is string)
239 mod = modt:gsub("^src/", ""):gsub("^lua/", ""):gsub("%.[^.]*$", ""):gsub("/", ".")
240 end
241 assert(mod is string)
242
243 process_build_entry(data, dep, mod, modt)
244 end
245end
246
247local function process_build(data: Data, dep: string, build: Rockspec.Build)
248 if build.modules then
249 process_build_modules(data, dep, build.modules)
250 end
251 if build.install and build.install.lua then
252 process_build_install(data, dep, build.install.lua)
253 end
254end
255
256local function process(data: Data, dep: string, rockspec_filename: string)
257 local rockspec = load_rockspec(dep, rockspec_filename)
258
259 assert(rockspec.build.type == "builtin")
260
261 if rockspec.build.modules or rockspec.build.install then
262 process_build(data, dep, rockspec.build)
263 end
264
265 if rockspec.build.platforms then
266 if rockspec.build.platforms[PLATFORM] then
267 process_build(data, dep, rockspec.build.platforms[PLATFORM])
268 end
269 end
270end
271
272--------------------------------------------------------------------------------
273-- Generate entries for Makefile-based build
274--------------------------------------------------------------------------------
275
276local function global_name(mod: string): string
277 return "luarocks_gen_" .. mod:gsub("%.", "_")
278end
279
280local function generate(input_filename: string, entry: Data.LuaEntry): string
281 local fd = assert(io.open(input_filename, "rb"))
282 local content = fd:read("*a"):gsub("^#![^\n]+\n", "")
283 fd:close()
284
285 entry.length = #content
286
287 return apply_template([[
288 /* automatically generated by bootstrap.tl */
289
290 static const unsigned char $(global)[] = {
291 $(code)
292 };
293 ]], {
294 global = global_name(entry.mod),
295 code = hexdump(content),
296 })
297end
298
299local function generate_all_luas(luas: {Data.LuaEntry})
300 for _, entry in ipairs(luas) do
301 local filename = "gen/lua/" .. entry.mod:gsub("%.", "/") .. ".h"
302 mkdir_p(dirname(filename))
303
304 local fd = assert(io.open(filename, "wb"))
305 fd:write(reindent_c(generate(entry.source, entry)))
306 entry.h_file = filename
307 fd:close()
308 end
309
310 local includes = {}
311 local array = {}
312 for _, entry in ipairs(luas) do
313 table.insert(includes, ("#include \"%s\""):format(entry.h_file))
314 table.insert(array, apply_template([[
315 {
316 .module_name = "$(mod)",
317 .source_name = "$(source)",
318 .length = $(length),
319 .code = $(global),
320 },
321 ]], {
322 mod = entry.mod,
323 dep = entry.dep,
324 source = entry.source,
325 length = tostring(entry.length),
326 global = global_name(entry.mod),
327 }))
328 end
329
330 table.insert(array, apply_template([[
331 {
332 .module_name = NULL,
333 .source_name = NULL,
334 .length = 0,
335 .code = NULL,
336 },
337 ]], {}))
338
339 write_template("gen/gen.h", [[
340 /* automatically generated by bootstrap.tl */
341
342 $(includes)
343
344 static const Gen GEN[] = {
345 $(array)
346 };
347 ]], {
348 array = table.concat(array, "\n"),
349 includes = table.concat(includes, "\n"),
350 })
351end
352
353local function get_flag_list(flag: string, entries: {string}, parent: string): string
354 local out = {}
355 for _, entry in ipairs(entries) do
356 table.insert(out, flag .. " " .. parent .. entry)
357 end
358 return table.concat(out, " ")
359end
360
361local function generate_makefile_entry(entry: Data.CEntry, seen: {string:boolean}, dirs: {string:boolean}): string
362 local out = {}
363 assert(entry.build.sources)
364
365 local incdirs = ""
366 if entry.build.incdirs then
367 incdirs = get_flag_list("-I", entry.build.incdirs, "vendor/" .. entry.dep .. "/")
368 end
369 local defines = ""
370 if entry.build.defines then
371 defines = get_flag_list("-D", entry.build.defines, "")
372 end
373
374 local objects = {}
375 for _, f in ipairs(entry.build.sources) do
376 local file = "vendor/" .. entry.dep .. "/" .. f
377 local obj_file = "target/objects/" .. entry.dep .. "/" .. file:gsub("%.c$", ".o")
378 table.insert(objects, obj_file)
379
380 if not seen[file] then
381 local d = dirname(obj_file)
382 if not dirs[d] then
383 dirs[d] = true
384 end
385
386 -- the pipe indicates an order-only prerequisite
387 -- (https://www.gnu.org/software/make/manual/make.html#Prerequisite-Types)
388 table.insert(out, ("%s: %s | %s"):format(obj_file, file, d))
389 table.insert(out, ("\t$(CC) -c -o %s %s %s %s"):format(obj_file, file, incdirs, defines))
390 seen[file] = true
391 end
392 end
393
394 local a_file = "target/libraries/" .. entry.mod:gsub("%.", "/") .. ".a"
395 entry.a_file = a_file
396
397 local d = dirname(a_file)
398 if not dirs[d] then
399 dirs[d] = true
400 end
401
402 local object_list = table.concat(objects, " ")
403 table.insert(out, ("%s: %s | %s"):format(a_file, object_list, d))
404 --table.insert(out, ("\t$(MKDIR) -p %s"):format(dirname(a_file)))
405 table.insert(out, ("\t$(AR) rcu %s %s"):format(a_file, object_list))
406 table.insert(out, "")
407
408 return table.concat(out, "\n")
409end
410
411local function process_c_entry(mod: string, externs: {string}, declares: {string})
412 if c_module_exceptions[mod] then
413 for _, m in ipairs(c_module_exceptions[mod]) do
414 process_c_entry(m, externs, declares)
415 end
416 return
417 end
418
419 local cfunc = "luaopen_" .. mod:gsub("%.", "_")
420
421 table.insert(externs, ("extern int %s(lua_State* L);"):format(cfunc))
422
423 table.insert(declares, ("lua_pushcfunction(L, %s);"):format(cfunc))
424 table.insert(declares, ("lua_setfield(L, -2, \"%s\");"):format(mod))
425end
426
427local makefile_vendor_template = [[
428# automatically generated by bootstrap.tl
429
430VENDOR_LIBS = $(a_files)
431
432$(entries)
433
434]]
435
436local function generate_all_cs(cs: {Data.CEntry})
437 local seen = {}
438 local dirs = {}
439
440 local entries = {}
441 for _, entry in ipairs(cs) do
442 table.insert(entries, generate_makefile_entry(entry, seen, dirs))
443 end
444
445 table.insert(entries, "")
446 for d, _ in sortedpairs(dirs) do
447 table.insert(entries, ("%s:"):format(d))
448 table.insert(entries, ("\t$(MKDIR) -p %s"):format(d))
449 end
450
451 local a_files = {}
452 for _, entry in ipairs(cs) do
453 table.insert(a_files, entry.a_file)
454 end
455
456 write_template("Makefile.vendor", makefile_vendor_template, {
457 entries = table.concat(entries, "\n"),
458 a_files = table.concat(a_files, " "),
459 })
460
461 local externs = {}
462 local declares = {}
463 for _, entry in ipairs(cs) do
464 process_c_entry(entry.mod, externs, declares)
465 end
466
467 write_template("gen/libraries.h", [[
468 /* automatically generated by bootstrap.tl */
469
470 $(externs)
471
472 static void declare_libraries(lua_State* L) {
473 lua_getglobal(L, "package"); /* package */
474 lua_getfield(L, -1, "preload"); /* package package.preload */
475 $(declares)
476 lua_settop(L, 0); /* */
477 }
478 ]], {
479 externs = table.concat(externs, "\n"),
480 declares = table.concat(declares, "\n"),
481 })
482end
483
484local function generate_main_h()
485 local fd = assert(io.open("gen/main.h", "wb"))
486 fd:write(reindent_c(generate("src/bin/luarocks", {
487 mod = "main"
488 })))
489 fd:close()
490end
491
492--------------------------------------------------------------------------------
493-- Main operation
494--------------------------------------------------------------------------------
495
496local data: Data = {
497 luas = {},
498 cs = {},
499}
500
501for file in find_files("src") do
502 if file:match("%.lua$") then
503 table.insert(data.luas, {
504 dep = "luarocks",
505 mod = file:gsub("^src/(.*).lua", "%1"):gsub("/", "."),
506 source = file,
507 })
508 end
509end
510
511for _, dep in ipairs(dependencies) do
512 print(dep)
513 process(data, dep, rockspec_locations[dep])
514end
515
516generate_all_luas(data.luas)
517generate_all_cs(data.cs)
518generate_main_h()