features: fix handling of logical expressions

The rewrite of the feature handling introduced multiple major bugs. One
of them was caused by the way Lua's logical operators work:

An expression of the form

    _'autoupdater' and _'web-advanced'

would return 'web-advanced' rather than the boolean true when _ returned
both strings unchanged (because the features are enabled).

As entries with more than a single feature name in their expressions did
not set no_default, Gluon would then attempt to add gluon-web-advanced to
the package selection, as web-advanced is a "pure" feature.

To fix this, and get rid of the annoying nodefault, separate handling of
"pure" feature and handling of logical expressions into two separate
functions, called feature() and when(). To simplify the feature
definitions, the package list is now passed directly to these functions
rather than in a table with a single field 'packages'.

Fixes: ee5ec5afe5 ("build: rewrite features.sh in Lua")
main
Matthias Schiffer 2020-08-28 22:04:06 +02:00
parent 097efa9d2d
commit 13b743d51e
4 changed files with 84 additions and 87 deletions

View File

@ -110,5 +110,6 @@ files["package/features"] = {
read_globals = {
"_",
"feature",
"when",
},
}

View File

@ -82,43 +82,43 @@ Each flag *$flag* will include the package the name *gluon-$flag* by default.
The feature definition file can modify the package selection by adding or removing
packages when certain combinations of flags are set.
Feature definitions use Lua syntax. The function *feature* has two arguments:
Feature definitions use Lua syntax. Two basic functions are defined:
* A logical expression composed of feature flag names (each prefixed with an underscore before the opening
quotation mark), logical operators (*and*, *or*, *not*) and parentheses
* A table with settings that are applied when the logical expression is
satisfied:
* *feature(name, pkgs)*: Defines a new feature. *feature()* expects a feature
(flag) name and a list of packages to add or remove when the feature is
enabled.
* Setting *nodefault* to *true* suppresses the default of including the *gluon-$flag* package.
This setting is only applicable when the logical expression is a single,
non-negated flag name.
* The *packages* field adds or removes packages to install. A package is
removed when the package name is prefixed with a ``-`` (after the opening
quotation mark).
* Defining a feature using *feature* replaces the default definition of
just including *gluon-$flag*.
* A package is removed when the package name is prefixed with a ``-`` (after
the opening quotation mark).
* *when(expr, pkgs)*: Adds or removes packages when a given logical expression
of feature flags is satisfied.
* *expr* is a logical expression composed of feature flag names (each prefixed
with an underscore before the opening quotation mark), logical operators
(*and*, *or*, *not*) and parentheses.
* Referencing a feature flag in *expr* has no effect on the default handling
of the flag. When no *feature()* entry for a flag exists, it will still
add *gluon-$flag* by default.
* *pkgs* is handled as for *feature()*.
Example::
feature(_'web-wizard', {
nodefault = true,
packages = {
'gluon-config-mode-hostname',
'gluon-config-mode-geo-location',
'gluon-config-mode-contact-info',
'gluon-config-mode-outdoor',
},
feature('web-wizard', {
'gluon-config-mode-hostname',
'gluon-config-mode-geo-location',
'gluon-config-mode-contact-info',
'gluon-config-mode-outdoor',
})
feature(_'web-wizard' and (_'mesh-vpn-fastd' or _'mesh-vpn-tunneldigger'), {
packages = {
'gluon-config-mode-mesh-vpn',
},
when(_'web-wizard' and (_'mesh-vpn-fastd' or _'mesh-vpn-tunneldigger'), {
'gluon-config-mode-mesh-vpn',
})
feature(_'no-radvd', {
nodefault = true,
packages = {
'-gluon-radvd',
},
feature('no-radvd', {
'-gluon-radvd',
})

View File

@ -5,65 +5,48 @@
-- file format
feature(_'web-wizard', {
nodefault = true,
packages = {
'gluon-config-mode-hostname',
'gluon-config-mode-geo-location',
'gluon-config-mode-contact-info',
'gluon-config-mode-outdoor',
},
feature('web-wizard', {
'gluon-config-mode-hostname',
'gluon-config-mode-geo-location',
'gluon-config-mode-contact-info',
'gluon-config-mode-outdoor',
})
feature(_'web-wizard' and _'autoupdater', {
packages = {
'gluon-config-mode-autoupdater',
},
when(_'web-wizard' and _'autoupdater', {
'gluon-config-mode-autoupdater',
})
feature(_'web-wizard' and (_'mesh-vpn-fastd' or _'mesh-vpn-tunneldigger'), {
packages = {
'gluon-config-mode-mesh-vpn',
},
when(_'web-wizard' and (_'mesh-vpn-fastd' or _'mesh-vpn-tunneldigger'), {
'gluon-config-mode-mesh-vpn',
})
feature(_'web-advanced', {
nodefault = true,
packages = {
'gluon-web-admin',
'gluon-web-network',
'gluon-web-wifi-config',
},
feature('web-advanced', {
'gluon-web-admin',
'gluon-web-network',
'gluon-web-wifi-config',
})
feature(_'web-advanced' and _'autoupdater', {
packages = {
'gluon-web-autoupdater',
},
when(_'web-advanced' and _'autoupdater', {
'gluon-web-autoupdater',
})
feature(_'status-page' and _'mesh-batman-adv-15', {
packages = {
'gluon-status-page-mesh-batman-adv',
},
when(_'mesh-batman-adv-15', {
'gluon-ebtables-limit-arp',
'gluon-radvd',
})
feature(_'mesh-batman-adv-15', {
packages = {
'gluon-ebtables-limit-arp',
'gluon-radvd',
},
when(_'status-page' and _'mesh-batman-adv-15', {
'gluon-status-page-mesh-batman-adv',
})
feature(_'mesh-babel', {
packages = {
'gluon-radvd',
},
when(_'mesh-babel', {
'gluon-radvd',
})
feature(not _'wireless-encryption-wpa3', {
packages = {
'hostapd-mini',
},
when(not _'wireless-encryption-wpa3', {
'hostapd-mini',
})

View File

@ -17,28 +17,41 @@ local function collect_keys(t)
end
function M.get_packages(file, features)
local feature_table = to_keys(features)
local enabled_features = to_keys(features)
local handled_features = {}
local packages = {}
local funcs = {}
function funcs._(feature)
if feature_table[feature] then
return feature
local function add_pkgs(pkgs)
for _, pkg in ipairs(pkgs or {}) do
packages[pkg] = true
end
end
local nodefault = {}
local packages = {}
function funcs.feature(match, options)
if not match then
return
function funcs._(feature)
return enabled_features[feature] ~= nil
end
function funcs.feature(feature, pkgs)
assert(
type(feature) == 'string',
'Incorrect use of feature(): pass a feature name without _ as first argument')
if enabled_features[feature] then
handled_features[feature] = true
add_pkgs(pkgs)
end
if options.nodefault then
nodefault[match] = true
end
for _, package in ipairs(options.packages or {}) do
packages[package] = true
end
function funcs.when(cond, pkgs)
assert(
type(cond) == 'boolean',
'Incorrect use of when(): pass a locical expression of _-prefixed strings as first argument')
if cond then
add_pkgs(pkgs)
end
end
@ -52,7 +65,7 @@ function M.get_packages(file, features)
-- Handle default packages
for _, feature in ipairs(features) do
if not nodefault[feature] then
if not handled_features[feature] then
packages['gluon-' .. feature] = true
end
end