gluon/scripts/target_lib.lua
Matthias Schiffer 9e23534ec3
build: rework config generation
So far, we were using a sort operation on the generated .config to
implement precedence of =y packages over =m, and =m over unset.
Unfortunately, this sort not only used for packages, but for all config
lines. This made it impossible to override settings from targets/generic
in a target config when the new setting was sorted before the generic
setting.

To fix this, track configurations by their keys, so we can properly
override config keys that were set before. Value-based precedence is
only preserved for package configuration.

The config() and try_config() calls always take key and value as
separate arguments now. Strings are quoted automatically; the values
true, nil and false map to y, m and unset for tristate options. config()
can take an optional third argument to override the error message to
display when the setting fails to apply.

All existing target configs generate the same .config with the old and the
new code. The new code is also a bit faster on targets with many devices.
2020-05-31 02:20:58 +02:00

332 lines
6.5 KiB
Lua

-- Functions for use in targets/*
local F = {}
-- To be accessed by scripts using target_lib
local M = setmetatable({}, { __index = F })
local funcs = setmetatable({}, {
__index = function(_, k)
return F[k] or _G[k]
end,
})
local env = setmetatable({}, {
__index = function(_, k) return os.getenv(k) end
})
F.env = env
assert(env.GLUON_SITEDIR)
assert(env.GLUON_TARGETSDIR)
assert(env.GLUON_RELEASE)
assert(env.GLUON_DEPRECATED)
M.site_code = assert(assert(dofile('scripts/site_config.lua')('site.conf')).site_code)
M.target_packages = {}
M.configs = {}
M.devices = {}
M.images = {}
M.opkg = true
local default_options = {
profile = false,
factory = '-squashfs-factory',
factory_ext = '.bin',
sysupgrade = '-squashfs-sysupgrade',
sysupgrade_ext = '.bin',
extra_images = {},
aliases = {},
manifest_aliases = {},
packages = {},
class = 'standard',
deprecated = false,
broken = false,
}
local gluon_devices, unknown_devices = {}, {}
for dev in string.gmatch(env.GLUON_DEVICES or '', '%S+') do
gluon_devices[dev] = true
unknown_devices[dev] = true
end
function F.istrue(v)
return (tonumber(v) or 0) > 0
end
local function want_device(dev, options)
if options.broken and not F.istrue(env.BROKEN) then
return false
end
if options.deprecated and env.GLUON_DEPRECATED == '0' then
return false
end
if (env.GLUON_DEVICES or '') == '' then
return true
end
unknown_devices[dev] = nil
return gluon_devices[dev]
end
local full_deprecated = env.GLUON_DEPRECATED == 'full'
local function merge(a, b)
local ret = {}
for k, v in pairs(a) do
ret[k] = v
end
for k, v in pairs(b or {}) do
assert(ret[k] ~= nil, string.format("unknown option '%s'", k))
ret[k] = v
end
return ret
end
-- Escapes a single argument to be used in a shell command
-- The argument is surrounded by single quotes, single quotes inside the
-- argument are replaced by '\''.
-- To allow using shell wildcards, zero bytes in the arguments are replaced
-- by unquoted asterisks.
function F.escape(s)
s = string.gsub(s, "'", "'\\''")
s = string.gsub(s, "%z", "'*'")
return "'" .. s .. "'"
end
local function escape_command(command, raw)
local ret = ''
if not raw then
ret = 'exec'
end
for _, arg in ipairs(command) do
ret = ret .. ' ' .. F.escape(arg)
end
if raw then
ret = ret .. ' ' .. raw
end
return ret
end
function F.exec_raw(command, may_fail)
local ret = os.execute(command)
assert((ret == 0) or may_fail)
return ret
end
function F.exec(command, may_fail, raw)
return F.exec_raw(escape_command(command, raw), may_fail)
end
function F.exec_capture_raw(command)
local f = io.popen(command)
assert(f)
local data = f:read('*a')
f:close()
return data
end
function F.exec_capture(command, raw)
return F.exec_capture_raw(escape_command(command, raw))
end
local image_mt = {
__index = {
dest_name = function(image, name, site, release)
return env.GLUON_IMAGEDIR..'/'..image.subdir,
'gluon-'..(site or M.site_code)..'-'..(release or env.GLUON_RELEASE)..'-'..name..image.out_suffix..image.extension
end,
},
}
local function add_image(image)
local device = image.image
M.images[device] = M.images[device] or {}
table.insert(M.images[device], setmetatable(image, image_mt))
end
local function format_config(k, v)
local format
if type(v) == 'string' then
format = '%s=%q'
elseif v == true then
format = '%s=y'
elseif v == nil then
format = '%s=m'
elseif v == false then
format = '# %s is not set'
else
format = '%s=%d'
end
return string.format(format, k, v)
end
local config_mt = {
__index = {
format = function(config)
return format_config(config.key, config.value)
end,
}
}
local function do_config(k, v, required)
M.configs[k] = setmetatable({
key = k,
value = v,
required = required,
}, config_mt)
end
function F.try_config(k, v)
do_config(k, v)
end
function F.config(k, v, message)
if not message then
message = string.format("unable to set '%s'", format_config(k, v))
end
do_config(k, v, message)
end
function F.packages(pkgs)
for _, pkg in ipairs(pkgs) do
table.insert(M.target_packages, pkg)
end
end
M.packages = F.packages
function F.device(image, name, options)
options = merge(default_options, options)
if not want_device(image, options) then
return
end
table.insert(M.devices, {
image = image,
name = name,
options = options,
})
if options.sysupgrade then
add_image {
image = image,
name = name,
subdir = 'sysupgrade',
in_suffix = options.sysupgrade,
out_suffix = '-sysupgrade',
extension = options.sysupgrade_ext,
aliases = options.aliases,
manifest_aliases = options.manifest_aliases,
}
end
if options.deprecated and not full_deprecated then
return
end
if options.factory then
add_image {
image = image,
name = name,
subdir = 'factory',
in_suffix = options.factory,
out_suffix = '',
extension = options.factory_ext,
aliases = options.aliases,
manifest_aliases = options.manifest_aliases,
}
end
for _, extra_image in ipairs(options.extra_images) do
add_image {
image = image,
name = name,
subdir = 'other',
in_suffix = extra_image[1],
out_suffix = extra_image[2],
extension = extra_image[3],
aliases = options.aliases,
manifest_aliases = options.manifest_aliases,
}
end
end
function F.factory_image(image, name, ext, options)
options = merge(default_options, options)
if not want_device(image, options) then
return
end
if options.deprecated and not full_deprecated then
return
end
add_image {
image = image,
name = name,
subdir = 'factory',
in_suffix = '',
out_suffix = '',
extension = ext,
aliases = options.aliases,
manifest_aliases = options.manifest_aliases,
}
end
function F.sysupgrade_image(image, name, ext, options)
options = merge(default_options, options)
if not want_device(image, options) then
return
end
add_image {
image = image,
name = name,
subdir = 'sysupgrade',
in_suffix = '',
out_suffix = '-sysupgrade',
extension = ext,
aliases = options.aliases,
manifest_aliases = options.manifest_aliases,
}
end
function F.no_opkg()
M.opkg = false
end
function F.defaults(options)
default_options = merge(default_options, options)
end
function F.include(name)
local f = assert(loadfile(env.GLUON_TARGETSDIR .. '/' .. name))
setfenv(f, funcs)
return f()
end
function M.check_devices()
local device_list = {}
for device in pairs(unknown_devices) do
table.insert(device_list, device)
end
if #device_list ~= 0 then
table.sort(device_list)
io.stderr:write('Error: unknown devices given: ', table.concat(device_list, ' '), '\n')
os.exit(1)
end
end
return M