-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathrockspec.lua
More file actions
475 lines (420 loc) · 16.1 KB
/
Copy pathrockspec.lua
File metadata and controls
475 lines (420 loc) · 16.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
local rocked = require("rocked")
local sea = require("sea")
local lde = require("lde-core")
local fs = require("fs")
local env = require("env")
local http = require("http")
local path = require("path")
local process = require("process2")
local util = require("util")
local ansi = require("ansi")
---@param dir string?
---@param rockspecPath string? # Path to the rockspec file; if nil, scanned from dir
---@return lde.Package?, string?
local function openRockspec(dir, rockspecPath)
dir = dir or env.cwd()
local content
if not rockspecPath then -- Search for a rockspec in the directory
if fs.isdir(dir) then
local iter = fs.readdir(dir)
if iter then
for entry in iter do
if entry.type == "file" and entry.name:match("%.rockspec$") then
rockspecPath = path.join(dir, entry.name)
break
end
end
end
end
if not rockspecPath then
return nil, "No rockspec found in directory: " .. dir
end
content = fs.read(rockspecPath)
if not content then
return nil, "Could not read rockspec: " .. rockspecPath
end
elseif rockspecPath:match("^https?://") then -- Looks like a URL
local cacheFile = path.join(lde.global.getRockspecCacheDir(), (rockspecPath:gsub("[^%w]", "_")))
if fs.exists(cacheFile) then
content = fs.read(cacheFile)
else
local err
content, err = http.get(rockspecPath)
if not content then
return nil, "Could not fetch rockspec: " .. rockspecPath .. ": " .. (err or "")
end
fs.write(cacheFile, content)
end
else -- Looks like a path
if not path.isAbsolute(rockspecPath) then
rockspecPath = path.join(dir, rockspecPath)
end
content = fs.read(rockspecPath)
if not content then
return nil, "Could not read rockspec: " .. rockspecPath
end
end ---@cast content -nil
local ok, spec = rocked.parse(content)
if not ok then
return nil, "Failed to parse rockspec: " .. (spec or rockspecPath)
end ---@cast spec rocked.raw.Output
local pkg = setmetatable({ dir = dir }, lde.Package)
local modules = {}
local nativeModules = {}
if spec.build then
for modname, src in pairs(spec.build.modules or {}) do
if type(src) == "string" then
if path.extension(src) == "lua" then
modules[modname] = src
elseif path.extension(src) == "c" then
nativeModules[modname] = { sources = { src } }
else
io.stderr:write("warning: " ..
(spec.package or "?") ..
": unrecognised source type for module '" .. modname .. "': " .. src .. "\n")
end
elseif type(src) == "table" and src.sources then
if type(src.sources) == "string" then src = { sources = { src.sources } } end
nativeModules[modname] = src
elseif type(src) == "table" and src[1] then
nativeModules[modname] = { sources = src }
elseif type(src) == "table" then
io.stderr:write("warning: " ..
(spec.package or "?") .. ": module '" .. modname .. "' has no sources field, skipping\n")
end
end
-- Merge platform-specific modules
local platFallbacks = {
darwin = { "macosx", "unix" },
linux = { "linux", "unix" },
win32 = { "win32", "mingw32" }
}
local jitPlatform = jit.os == "Windows" and "win32" or jit.os == "OSX" and "darwin" or "linux"
local platBuild
for _, key in ipairs(platFallbacks[jitPlatform] or { jitPlatform }) do
platBuild = spec.build.platforms and spec.build.platforms[key]
if platBuild then break end
end
if spec.build.platforms and not platBuild then
io.stderr:write("warning: " ..
(spec.package or "?") .. ": no platform config for '" .. jitPlatform .. "'\n")
end
for modname, src in pairs(platBuild and platBuild.modules or {}) do
if type(src) == "string" then
if path.extension(src) == "lua" then
modules[modname] = src
else
nativeModules[modname] = { sources = { src } }
end
elseif type(src) == "table" and src.sources then
if type(src.sources) == "string" then src = { sources = { src.sources } } end
nativeModules[modname] = src
end
end
end
local binScripts = (spec.build and spec.build.install and spec.build.install.bin) or {}
local installLuaFiles = (spec.build and spec.build.install and spec.build.install.lua) or {}
local binEntry
local bk, bv = next(binScripts)
if bk then binEntry = type(bk) == "number" and path.basename(bv) or bk end
local buildType = spec.build and spec.build.type or "builtin"
local buildStamp = util.fnv1a(content)
local function mkdirp(p)
if fs.isdir(p) then return end
mkdirp(path.dirname(p))
fs.mkdir(p)
end
pkg.buildfn = function(_, outputDir)
if not fs.isdir(outputDir) then mkdirp(outputDir) end
local stampFile = path.join(outputDir, ".lde-built")
if fs.exists(stampFile) and fs.read(stampFile) == buildStamp then
return true
end
local modulesDir = path.dirname(outputDir)
if buildType == "make" then
local makeBin = lde.global.getMakeBin()
if not process.exec(makeBin, { "--version" }) then
return nil,
"Package '" .. (spec.package or "?") .. "' requires 'make' to build, but it was not found." ..
" Install make (e.g. build-essential on Debian/Ubuntu, Xcode Command Line Tools on macOS)."
end
local shellBin = lde.global.getShellBin()
if jit.os == "Windows" and not shellBin then
ansi.printf(
"{yellow}Warning: sh.exe not found. Make builds may fail. Install Git for Windows to fix this.\n")
end
local luajitPath = sea.getLuajitPath()
local luajitInclude = path.join(luajitPath, "include")
local function toShPath(p) return p:gsub("\\", "/") end
local stdVars = {
LUA_INCDIR = toShPath(luajitInclude),
LUA_LIBDIR = toShPath(path.join(luajitPath, "lib")),
LUALIB = "libluajit.a",
CFLAGS = "-fPIC",
LIBFLAG = "-shared",
INST_LIBDIR = toShPath(modulesDir),
INST_LUADIR = toShPath(modulesDir),
LUADIR = toShPath(modulesDir),
LIBDIR = toShPath(modulesDir),
PREFIX = toShPath(modulesDir),
LUA = toShPath(env.execPath())
}
local function subst(s)
return (s:gsub("%$%(([%w_]+)%)", function(k) return stdVars[k] or "" end))
end
local function buildVarList(extraVars)
local args = {}
for k, v in pairs(stdVars) do args[#args + 1] = k .. "=" .. v end
for k, v in pairs(extraVars or {}) do
args[#args + 1] = k .. "=" .. subst(v)
end
return args
end
local buildTarget = spec.build.build_target or ""
local installTarget = spec.build.install_target or "install"
local makeEnv = nil
if shellBin then
local shellDir = path.dirname(shellBin)
makeEnv = { PATH = shellDir .. ";" .. (os.getenv("PATH") or "") }
end
local buildArgs = buildVarList(spec.build.variables)
if shellBin then buildArgs[#buildArgs + 1] = "SHELL=" .. shellBin end
if buildTarget ~= "" then buildArgs[#buildArgs + 1] = buildTarget end
local code, stdout, stderr = process.exec(makeBin, buildArgs, { cwd = dir, env = makeEnv })
if code ~= 0 then
local msg = (stderr ~= "" and stderr) or (stdout ~= "" and stdout) or ("exited with code " .. code)
return nil, "make failed: " .. msg
end
local installArgs = buildVarList(spec.build.install_variables)
if shellBin then installArgs[#installArgs + 1] = "SHELL=" .. shellBin end
installArgs[#installArgs + 1] = installTarget
code, stdout, stderr = process.exec(makeBin, installArgs, { cwd = dir, env = makeEnv })
if code ~= 0 then
local msg = (stderr ~= "" and stderr) or (stdout ~= "" and stdout) or ("exited with code " .. code)
return nil, "make install failed: " .. msg
end
-- Promote any binaries installed to modulesDir/bin/ into outputDir
local binDir = path.join(modulesDir, "bin")
if fs.isdir(binDir) then
local iter = fs.readdir(binDir)
if iter then
for entry in iter do
if entry.type == "file" then
fs.copy(path.join(binDir, entry.name), path.join(outputDir, entry.name))
end
end
end
end
fs.write(stampFile, buildStamp)
return true
elseif buildType == "cmake" then
local luajitPath = sea.getLuajitPath()
local buildDir = path.join(dir, "build.lde")
local installDir = path.join(dir, "install.lde")
if not fs.isdir(buildDir) then fs.mkdir(buildDir) end
if not fs.isdir(installDir) then fs.mkdir(installDir) end
local configureArgs = {
"-H.", "-B" .. buildDir,
"-DLUA_BUILD_TYPE=System",
"-DWITH_LUA_ENGINE=LuaJIT",
"-DLUAJIT_INCLUDE_DIR=" .. path.join(luajitPath, "include"),
"-DLUAJIT_LIBRARIES=" .. path.join(luajitPath, "lib", "libluajit.a"),
"-DCMAKE_INSTALL_PREFIX=" .. installDir
}
for k, v in pairs(spec.build.build_variables or {}) do
configureArgs[#configureArgs + 1] = "-D" .. k .. "=" .. v
end
local code, _, stderr = process.exec("cmake", configureArgs, { cwd = dir })
if code ~= 0 then return nil, "cmake configure failed: " .. (stderr or "") end
code, _, stderr = process.exec("cmake", { "--build", buildDir, "--config", "Release" }, { cwd = dir })
if code ~= 0 then return nil, "cmake build failed: " .. (stderr or "") end
code, _, stderr = process.exec("cmake", { "--build", buildDir, "--target", "install", "--config", "Release" },
{ cwd = dir })
if code ~= 0 then return nil, "cmake install failed: " .. (stderr or "") end
local soExt = jit.os == "OSX" and "**.dylib" or "**.so"
for _, rel in ipairs(fs.scan(installDir, soExt)) do
fs.copy(path.join(installDir, rel), path.join(modulesDir, path.basename(rel)))
end
fs.write(stampFile, buildStamp)
return true
elseif buildType == "builtin" or buildType == "module" or buildType == "none" then
for modname, src in pairs(modules) do
local modPath = modname:gsub("%.", path.separator)
local srcBase = path.basename(src)
local destAbs
if srcBase == "init.lua" then
-- source is an init.lua: install as modPath/init.lua
-- but if modname ends in .init (e.g. "system.init"), strip that segment
local dirPath = modname:match("^(.+)%.init$")
if dirPath then
destAbs = path.join(modulesDir, dirPath:gsub("%.", path.separator), "init.lua")
else
destAbs = path.join(modulesDir, modPath, "init.lua")
end
else
destAbs = path.join(modulesDir, modPath .. ".lua")
end
local destDir = path.dirname(destAbs)
if not fs.isdir(destDir) then mkdirp(destDir) end
fs.copy(path.join(dir, src), destAbs)
end
for modname, src in pairs(nativeModules) do
local ext = jit.os == "Windows" and "dll" or jit.os == "OSX" and "dylib" or "so"
local destAbs = path.join(modulesDir, modname:gsub("%.", path.separator) .. "." .. ext)
local destDir = path.dirname(destAbs)
if not fs.isdir(destDir) then mkdirp(destDir) end
local srcFiles = {}
for _, s in ipairs(src.sources) do
srcFiles[#srcFiles + 1] = path.join(dir, s)
end
lde.global.ensureMingw()
local ljPath = sea.getLuajitPath()
local gccArgs = { "-shared", "-fPIC", "-DLUAJIT_VERSION=LuaJIT 2.1.0-beta3", "-DLUA_VERSION_NUM=501",
"-I" .. path.join(ljPath, "include") }
for _, d in ipairs(src.defines or {}) do gccArgs[#gccArgs + 1] = "-D" .. d end
for _, s in ipairs(srcFiles) do gccArgs[#gccArgs + 1] = s end
gccArgs[#gccArgs + 1] = "-o"
gccArgs[#gccArgs + 1] = destAbs
gccArgs[#gccArgs + 1] = "-L" .. path.join(ljPath, "lib")
for _, d in ipairs(src.libdirs or {}) do gccArgs[#gccArgs + 1] = "-L" .. d end
if jit.os == "Windows" then gccArgs[#gccArgs + 1] = "-lluajit" end
if jit.os == "OSX" then
gccArgs[#gccArgs + 1] = "-undefined"; gccArgs[#gccArgs + 1] = "dynamic_lookup"
end
for _, l in ipairs(src.libraries or {}) do gccArgs[#gccArgs + 1] = "-l" .. l end
local gccEnv
if jit.os == "Windows" then
local mingwBin = path.join(lde.global.getMingwDir(), "bin")
gccEnv = { PATH = mingwBin .. ";" .. (env.var("PATH") or "") }
end
local code, stdout, stderr = process.exec(lde.global.getGCCBin(), gccArgs, { env = gccEnv })
if code ~= 0 then
local msg = (stderr ~= "" and stderr) or (stdout ~= "" and stdout) or ("exited with code " .. code)
return nil, "Failed to compile native module '" .. modname .. "': " .. msg
end
end
for k, v in pairs(binScripts) do
local binName = type(k) == "number" and path.basename(v) or k
local binDest = path.join(outputDir, binName)
local binDestDir = path.dirname(binDest)
if not fs.isdir(binDestDir) then mkdirp(binDestDir) end
fs.copy(path.join(dir, v), binDest)
end
for modname, src in pairs(installLuaFiles) do
if not src:match("%.lua$") then goto continue_install_lua end
if type(modname) == "number" then
modname = src:gsub("%.lua$", ""):gsub("[/\\]", ".")
end
local modPath = modname:gsub("%.", path.separator)
local destAbs = path.join(modulesDir, modPath .. ".lua")
local destDir = path.dirname(destAbs)
if not fs.isdir(destDir) then mkdirp(destDir) end
fs.copy(path.join(dir, src), destAbs)
::continue_install_lua::
end
fs.write(stampFile, buildStamp)
return true
elseif buildType == "command" then
local luajitPath = sea.getLuajitPath()
local ldeBin = env.execPath()
local vars = {
LUA = ldeBin,
LUA_INCDIR = path.join(luajitPath, "include"),
LUA_LIBDIR = path.join(luajitPath, "lib"),
LIBDIR = modulesDir,
LUADIR = modulesDir,
PREFIX = modulesDir,
CC = lde.global.getGCCBin(),
LD = lde.global.getGCCBin(),
CFLAGS = "-fPIC",
LIBFLAG = "-shared",
LIB_EXTENSION = jit.os == "Windows" and "dll" or jit.os == "OSX" and "dylib" or "so",
OBJ_EXTENSION = "o"
}
local function subst(cmd)
return (cmd:gsub("%$%(([%w_]+)%)", function(k) return vars[k] or "" end))
end
local function shellSplit(cmd)
local args = {}
local i = 1
while i <= #cmd do
while i <= #cmd and cmd:sub(i, i) == " " do i = i + 1 end
if i > #cmd then break end
local token = ""
while i <= #cmd and cmd:sub(i, i) ~= " " do
local c = cmd:sub(i, i)
if c == '"' then
i = i + 1
while i <= #cmd and cmd:sub(i, i) ~= '"' do
token = token .. cmd:sub(i, i)
i = i + 1
end
if i <= #cmd then i = i + 1 end -- skip closing quote
else
token = token .. c
i = i + 1
end
end
args[#args + 1] = token
end
return args
end
local function execCmd(cmdStr)
if not cmdStr or cmdStr == "" then return true end
local argv = shellSplit(subst(cmdStr))
local bin = table.remove(argv, 1)
-- If bin is the lde binary, inject --lua so it runs the next arg as a plain script
if bin == ldeBin then
table.insert(argv, 1, "--lua")
end
local code, stdout, stderr = process.exec(bin, argv, { cwd = dir })
if code ~= 0 then
local msg = (stderr ~= "" and stderr) or (stdout ~= "" and stdout) or ("exited with code " .. code)
return nil, msg
end
return true
end
local cmdOk, cmdErr = execCmd(spec.build.build_command)
if not cmdOk then return nil, "build_command failed: " .. (cmdErr or "(no output)") end
cmdOk, cmdErr = execCmd(spec.build.install_command)
if not cmdOk then return nil, "install_command failed: " .. (cmdErr or "(no output)") end
fs.write(stampFile, buildStamp)
return true
else
return nil, "unsupported build type: " .. buildType
end -- builtin
end
pkg.readConfig = function()
local deps = {}
for _, depStr in ipairs(spec.dependencies or {}) do
local name, rest = depStr:match("^([%w%-_]+)%s*(.*)")
if name and name ~= "lua" and name ~= "luajit" then
deps[name] = { luarocks = name, version = rest ~= "" and rest or nil }
end
end
local resolvedBin = binEntry
if not resolvedBin and (buildType == "make" or buildType == "cmake") then
-- Binaries from make/cmake installs are promoted into the package target dir
local targetDir = path.join(dir, "target", spec.package or "")
if fs.isdir(targetDir) then
local iter = fs.readdir(targetDir)
if iter then
for entry in iter do
if entry.type == "file" and entry.name ~= ".lde-built" then
resolvedBin = entry.name
break
end
end
end
end
end
return lde.Package.Config.new({
name = spec.package,
version = spec.version,
bin = resolvedBin,
dependencies = deps
})
end
return pkg, nil
end
return openRockspec