| |
| --- Module implementing the LuaRocks "buildroot" command. |
| local buildroot = {} |
| |
| local dir = require("luarocks.dir") |
| local fs = require("luarocks.fs") |
| local util = require("luarocks.util") |
| local queries = require("luarocks.queries") |
| local search = require("luarocks.search") |
| local download = require("luarocks.download") |
| local fetch = require("luarocks.fetch") |
| |
| function buildroot.add_to_parser(parser) |
| local cmd = parser:command("buildroot", [[ |
| This addon generates Buildroot package files of a rock. |
| First argument is the name of a rock, the second argument is optional |
| and needed when Buildroot uses another name (usually prefixed by lua-). |
| Files are generated with the source content of the rock and more |
| especially the rockspec. So, the rock is downloaded and unpacked. |
| ]], util.see_also()) |
| :summary("generate buildroot package files of a rock.") |
| |
| cmd:argument("rockname", "the name of a rock to be fetched and unpacked.") |
| cmd:argument("brname", "the name used by Buildroot.") |
| :args("?") |
| end |
| |
| local function brname (name) |
| return name:upper():gsub('-', '_') |
| end |
| |
| local function brlicense (license) |
| if license:match('MIT/X') then |
| return 'MIT' |
| end |
| return license |
| end |
| |
| local function wrap (txt, max) |
| local lines = {} |
| local line = '' |
| for word in txt:gmatch('(%S+)') do |
| if line:len() + word:len() > max - 1 then |
| lines[#lines+1] = line |
| line = '' |
| end |
| if line == '' then |
| line = word |
| else |
| line = line .. ' ' .. word |
| end |
| end |
| lines[#lines+1] = line |
| return lines |
| end |
| |
| local function has_c_files (rockspec) |
| for _, mod in pairs(rockspec.build.modules or {}) do |
| if type(mod) == 'string' then |
| if mod:match'%.c$' then |
| return true |
| end |
| elseif type(mod) == 'table' then |
| local sources = mod.sources |
| if type(sources) == 'string' and sources:match'%.c$' then |
| return true |
| end |
| for _, src in ipairs(sources or mod) do |
| if src:match'%.c$' then |
| return true |
| end |
| end |
| end |
| end |
| return false |
| end |
| |
| local function get_main_modules (rockspec) |
| local t = {} |
| for name in pairs(rockspec.build.modules or {}) do |
| if not name:match('%.') then |
| t[#t+1] = name |
| end |
| end |
| if #t == 0 then |
| for name in pairs(rockspec.build.modules or {}) do |
| t[#t+1] = name |
| end |
| end |
| if #t == 0 then |
| t[#t+1] = rockspec.package:gsub('%-', '') |
| end |
| table.sort(t) |
| return t |
| end |
| |
| local function get_external_dependencies (rockspec) |
| local t = {} |
| for k in pairs(rockspec.external_dependencies or {}) do |
| k = k:lower() |
| if fs.is_dir('package/' .. k) then |
| t[#t+1] = k |
| else |
| t[#t+1] = 'lib' .. k |
| if not fs.is_dir('package/lib' .. k) then |
| util.printout('unkwown external dependency: ' .. k) |
| end |
| end |
| end |
| table.sort(t) |
| return t |
| end |
| |
| local function get_dependencies (rockspec) |
| local t = {} |
| for i = 1, #rockspec.dependencies do |
| local dep = tostring(rockspec.dependencies[i]):match('^(%S+)') |
| if dep ~= 'lua' then |
| dep = dep:gsub('_', '-') |
| if fs.is_dir('package/lua-' .. dep) then |
| t[#t+1] = 'lua-' .. dep |
| else |
| t[#t+1] = dep |
| if not fs.is_dir('package/' .. dep) then |
| util.printout('unkwown dependency: ' .. dep) |
| end |
| end |
| end |
| end |
| table.sort(t) |
| return t |
| end |
| |
| function get_digest (file) |
| local absname = fs.absolute_name(file) |
| local pipe = io.popen('sha256sum ' .. fs.Q(absname)) |
| local line = pipe:read('*l') |
| pipe:close() |
| local computed = line and line:match('(' .. ('%x'):rep(64) .. ')') |
| if computed then |
| return computed |
| else |
| return nil, "Failed to compute SHA256 hash for file " .. absname |
| end |
| end |
| |
| local function generate_config (rockspec, lcname) |
| local ucname = brname(lcname) |
| local only_luajit = rockspec.package:match('^lj') |
| local summary = rockspec.description.summary |
| if not summary then |
| summary = '???' |
| elseif not summary:match('%.%s*$') then |
| summary = summary:gsub('%s*$', '.') |
| end |
| local homepage = rockspec.description.homepage or '???' |
| local external_dependencies = get_external_dependencies(rockspec) |
| local dependencies = get_dependencies(rockspec) |
| local fname = 'package/' .. lcname .. '/Config.in' |
| local f = assert(io.open(fname, 'w')) |
| util.printout('write ' .. fname) |
| f:write('config BR2_PACKAGE_' .. ucname .. '\n') |
| f:write('\tbool "' .. lcname .. '"\n') |
| if only_luajit then |
| f:write('\tdepends on BR2_PACKAGE_LUAJIT\n') |
| end |
| for i = 1, #external_dependencies do |
| f:write('\tselect BR2_PACKAGE_' .. brname(external_dependencies[i]) .. '\n') |
| end |
| for i = 1, #dependencies do |
| f:write('\tselect BR2_PACKAGE_' .. brname(dependencies[i]) .. ' # runtime\n') |
| end |
| f:write('\thelp\n') |
| f:write('\t ' .. table.concat(wrap(summary, 62), '\n\t ') .. '\n') |
| f:write('\n\t ' .. homepage .. '\n') |
| if only_luajit then |
| f:write('\ncomment "' .. lcname .. ' needs LuaJIT"\n') |
| f:write('\tdepends on !BR2_PACKAGE_LUAJIT\n') |
| end |
| f:close() |
| end |
| |
| local function generate_mk (rockspec, lcname, licenses) |
| local function escape (s) |
| return s:gsub('-', '%%-'):gsub('%.', '%%.') |
| end |
| |
| local ucname = brname(lcname) |
| local need_name_upstream = false |
| local need_version_upstream = false |
| local name_upstream = rockspec.package |
| local version = rockspec.version |
| local version_upstream = version:match('^([^-]+)-') |
| local revision = version:match('-(%d+)$') |
| local license = rockspec.description.license |
| local subdir = rockspec.source.dir |
| if subdir then |
| local root = subdir:match('^(.-)-' .. escape(version) .. '$') |
| if root then |
| subdir = root .. '-$(' .. ucname .. '_VERSION)' |
| end |
| root = subdir:match('^(.--[Vv])' .. escape(version_upstream) .. '$') |
| if root then |
| need_version_upstream = true |
| subdir = root .. '$(' .. ucname .. '_VERSION_UPSTREAM)' |
| end |
| root = subdir:match('^(.-)-' .. escape(version_upstream) .. '$') |
| if root then |
| if root == lcname then |
| subdir = nil |
| elseif root == name_upstream then |
| subdir = nil |
| need_name_upstream = true |
| else |
| need_version_upstream = true |
| subdir = root .. '-$(' .. ucname .. '_VERSION_UPSTREAM)' |
| end |
| end |
| end |
| local external_dependencies = get_external_dependencies(rockspec) |
| local fname = 'package/' .. lcname .. '/' .. lcname .. '.mk' |
| local f = assert(io.open(fname, 'w')) |
| util.printout('write ' .. fname) |
| f:write('################################################################################\n') |
| f:write('#\n') |
| f:write('# ' .. lcname .. '\n') |
| f:write('#\n') |
| f:write('################################################################################\n') |
| f:write('\n') |
| if need_version_upstream then |
| f:write(ucname .. '_VERSION_UPSTREAM = ' .. version_upstream .. '\n') |
| f:write(ucname .. '_VERSION = $(' .. ucname .. '_VERSION_UPSTREAM)-' .. revision .. '\n') |
| else |
| f:write(ucname .. '_VERSION = ' .. version .. '\n') |
| end |
| if lcname ~= name_upstream:lower() or need_name_upstream then |
| f:write(ucname .. '_NAME_UPSTREAM = ' .. name_upstream .. '\n') |
| end |
| if subdir then |
| f:write(ucname .. '_SUBDIR = ' .. subdir .. '\n') |
| end |
| if license then |
| f:write(ucname .. '_LICENSE = ' .. brlicense(license) .. '\n') |
| end |
| if #licenses == 1 then |
| f:write(ucname .. '_LICENSE_FILES = $(' .. ucname .. '_SUBDIR)/' .. licenses[1] .. '\n') |
| elseif #licenses > 1 then |
| f:write(ucname .. '_LICENSE_FILES =') |
| for i = 1, #licenses do |
| local file = licenses[i] |
| f:write(' \\\n\t$(' .. ucname .. '_SUBDIR)/' .. file) |
| end |
| f:write('\n') |
| end |
| if #external_dependencies > 0 then |
| f:write(ucname .. '_DEPENDENCIES = ' .. table.concat(external_dependencies, ' ') .. '\n') |
| end |
| f:write('\n$(eval $(luarocks-package))\n') |
| f:close() |
| end |
| |
| local function generate_hash (rockspec, lcname, rock_file, licenses, digest) |
| local subdir = rockspec.source.dir |
| local fname = 'package/' .. lcname .. '/' .. lcname .. '.hash' |
| local f = assert(io.open(fname, 'w')) |
| util.printout('write ' .. fname) |
| f:write('# computed by luarocks/buildroot\n') |
| f:write('sha256 ' .. digest[rock_file] .. ' ' .. rock_file .. '\n') |
| for i = 1, #licenses do |
| local file = licenses[i] |
| f:write('sha256 ' .. digest[file] .. ' ' .. subdir .. '/' .. file .. '\n') |
| end |
| f:close() |
| end |
| |
| local function generate_test (rockspec, lcname) |
| local ucname = brname(lcname) |
| local classname = rockspec.package:gsub('%-', ''):gsub('%.', '') |
| classname = classname:sub(1, 1):upper() .. classname:sub(2) |
| local modnames = get_main_modules(rockspec) |
| local fname = 'support/testing/tests/package/test_' .. ucname:lower() .. '.py' |
| local f = assert(io.open(fname, 'w')) |
| util.printout('write ' .. fname) |
| f:write('from tests.package.test_lua import TestLuaBase\n') |
| f:write('\n') |
| f:write('\n') |
| f:write('class TestLua' .. classname .. '(TestLuaBase):\n') |
| f:write(' config = TestLuaBase.config + \\\n') |
| f:write(' """\n') |
| f:write(' BR2_PACKAGE_LUA=y\n') |
| f:write(' BR2_PACKAGE_' .. ucname .. '=y\n') |
| f:write(' """\n') |
| f:write('\n') |
| f:write(' def test_run(self):\n') |
| f:write(' self.login()\n') |
| for i = 1, #modnames do |
| f:write(' self.module_test("' .. modnames[i] .. '")\n') |
| end |
| f:write('\n') |
| f:write('\n') |
| f:write('class TestLuajit' .. classname .. '(TestLuaBase):\n') |
| f:write(' config = TestLuaBase.config + \\\n') |
| f:write(' """\n') |
| f:write(' BR2_PACKAGE_LUAJIT=y\n') |
| f:write(' BR2_PACKAGE_' .. ucname .. '=y\n') |
| f:write(' """\n') |
| f:write('\n') |
| f:write(' def test_run(self):\n') |
| f:write(' self.login()\n') |
| for i = 1, #modnames do |
| f:write(' self.module_test("' .. modnames[i] .. '")\n') |
| end |
| f:close() |
| end |
| |
| --- Driver function for the "buildroot" command. |
| -- @return boolean: true if successful |
| function buildroot.command(args) |
| local rockname = assert(args.rockname) |
| local fsname = args.brname or rockname |
| |
| local query = queries.new(rockname:lower(), nil, nil, false, 'src') |
| local url, err = search.find_suitable_rock(query) |
| if not url then |
| return nil, "Could not find a result named " .. tostring(query) .. ": " .. err |
| end |
| local rock_file = dir.base_name(url) |
| |
| local temp_dir, err = fs.make_temp_dir(rockname) |
| if not temp_dir then |
| return nil, "Failed creating temporary dir: " .. err |
| end |
| local ok, err = fs.change_dir(temp_dir) |
| if not ok then return nil, err end |
| |
| ok = fs.download(url, rock_file, true) |
| if not ok then |
| return nil, "Failed downloading " .. url |
| end |
| |
| local digest = {} |
| digest[rock_file], err = get_digest(rock_file) |
| if not digest[rock_file] then return nil, err end |
| ok, err = fs.unzip(rock_file) |
| if not ok then return nil, err end |
| |
| local rockspec_file = rock_file:gsub('%.src%.rock$', '.rockspec') |
| local rockspec, err = fetch.load_rockspec(rockspec_file) |
| if not rockspec then |
| return nil, "Error loading rockspec: " .. err |
| end |
| if rockspec.source.file then |
| ok, err = fs.unpack_archive(rockspec.source.file) |
| if not ok then return nil, err end |
| end |
| |
| if rockspec.source.dir ~= '.' then |
| fs.copy(rockspec.local_abs_filename, rockspec.source.dir, 'read') |
| end |
| |
| local build_type = rockspec.build.type |
| if build_type ~= 'none' and build_type ~= 'builtin' and build_type ~= 'module' then |
| util.printout('[' .. rockspec.package .. "] build_type '" .. build_type .. "' not supported") |
| end |
| |
| local licenses = {} |
| ok, err = fs.change_dir(rockspec.source.dir) |
| if not ok then return nil, err end |
| local files = fs.find() |
| for i = 1, #files do |
| local v = files[i] |
| if v == 'COPYING' |
| or v == 'COPYRIGHT' |
| or v:match('^LICENSE') then |
| licenses[#licenses+1] = v |
| digest[v], err = get_digest(v) |
| if not digest[v] then return nil, err end |
| end |
| end |
| if #licenses == 0 then |
| for i = 1, #files do |
| local v = files[i] |
| if v:match('^doc/LICENSE') |
| or v:match('^doc/license') |
| or v:match('^doc/us/license') then |
| licenses[#licenses+1] = v |
| digest[v], err = get_digest(v) |
| if not digest[v] then return nil, err end |
| end |
| end |
| end |
| fs.pop_dir() |
| table.sort(licenses) |
| |
| fs.pop_dir() |
| ok, err = fs.make_dir('package/' .. fsname:lower()) |
| if not ok then return nil, err end |
| |
| generate_config(rockspec, fsname:lower()) |
| generate_mk(rockspec, fsname:lower(), licenses) |
| generate_hash(rockspec, fsname:lower(), rock_file, licenses, digest) |
| if has_c_files(rockspec) then |
| ok, err = fs.make_dir('support/testing/tests/package') |
| if not ok then return nil, err end |
| generate_test(rockspec, fsname:lower()) |
| end |
| |
| return true |
| end |
| |
| return buildroot |