Skip to content

Commit

Permalink
Add config inheritance for personality field
Browse files Browse the repository at this point in the history
It is often the case that we want to build an image which just applies a
few minor changes to an existing personality.  For instance, the apps we
want for most Spanish images are the same, but we have a few
country-specific applications, and we often wish to build a sub-variant
for a partner or region of a particular country.

Currently we need to copy the full list of apps & settings from our
es.ini personality config into (for instance) es_MX.ini; and then again
from es_MX.ini into (e.g.) es_MX_aguascalientes.ini to add some
region-specific apps or resources

Implement a form of inheritance where (e.g.) building the es_MX
personality reads es.ini and es_MX.ini, in that order; and (e.g.)
building the es_MX_aguascalientes personality reads es.ini, es_MX.ini
and es_MX_aguascalientes.ini, again in that order.  This matches the
convention that we have of naming personalities for the locale, and then
adding further underscore-separated suffixes for subvariants thereof.

This is only implemented for the personality field. This is where we do
most of our customisation and is the place where inheritance would be
most useful.

https://phabricator.endlessm.com/T34731
  • Loading branch information
wjt committed Nov 22, 2024
1 parent ff6c562 commit 3ebcde2
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 11 deletions.
43 changes: 32 additions & 11 deletions run-build
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,27 @@ class ImageBuilder(object):
datetime.datetime.utcnow().strftime('%y%m%d-%H%M%S')
)

@staticmethod
def _get_prefixes(value):
"""Yields all prefixes of value split by _, including the whole string.
>>> list(ImageBuilder._get_prefixes("en_GB_orkney"))
["en", "en_GB", "en_GB_orkney"]
"""
parts = value.split("_")
for i in range(1, len(parts) + 1):
yield "_".join(parts[:i])

def _get_attr_values(self, attr):
"""Yields one or more values for attr. For most properties this is a single
value, but for "personality" this yields a series of values to implement
inheritance."""
value = getattr(self, attr)
if attr == "personality":
yield from self._get_prefixes(value)
else:
yield value

def _get_config_paths(self, dir_path, dir_ns):
config_files = []

Expand All @@ -398,20 +419,20 @@ class ImageBuilder(object):
config_attrs = ('product', 'branch', 'arch', 'platform',
'personality')
for attr in config_attrs:
path = os.path.join(dir_path, attr,
getattr(self, attr) + '.ini')
namespace = dir_ns + '_'.join((attr, getattr(self, attr)))
config_files.append((path, namespace))
for value in self._get_attr_values(attr):
path = os.path.join(dir_path, attr, value + '.ini')
namespace = dir_ns + '_'.join((attr, value))
config_files.append((path, namespace))

# Add combinations of per-attribute config directories
for attr1, attr2 in itertools.combinations(config_attrs, 2):
ini_name = (getattr(self, attr1) + '-' +
getattr(self, attr2) + '.ini')
path = os.path.join(dir_path, attr1 + '-' + attr2, ini_name)
namespace = (dir_ns +
'_'.join((attr1, attr2, getattr(self, attr1),
getattr(self, attr2))))
config_files.append((path, namespace))
for (value1, value2) in itertools.product(self._get_attr_values(attr1),
self._get_attr_values(attr2)):
ini_name = (value1 + '-' + value2 + '.ini')
path = os.path.join(dir_path, attr1 + '-' + attr2, ini_name)
namespace = (dir_ns +
'_'.join((attr1, attr2, value1, value2)))
config_files.append((path, namespace))

return config_files

Expand Down
48 changes: 48 additions & 0 deletions tests/test_image_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,54 @@ def _run_test():
_run_test()


def test_config_inheritance(make_builder, tmp_path, tmp_builder_paths, caplog):
"""Test that personality en_GB_orkney also loads settings from en and en_GB."""
configdir = tmp_path / 'config'
personalitydir = configdir / 'personality'
personalitydir.mkdir(parents=True, exist_ok=True)

en = personalitydir / 'en.ini'
en.write_text(dedent("""\
[image]
language = en_US.utf8
[flatpak-remote-eos-apps]
apps_add =
com.endlessm.encyclopedia.en
com.endlessm.football.en
"""))

en_GB = personalitydir / 'en_GB.ini'
en_GB.write_text(dedent("""\
[image]
language = en_GB.utf8
[flatpak-remote-eos-apps]
# Football means something else in the UK
apps_del =
com.endlessm.football.en
apps_add =
com.endlessm.football.en_GB
"""))

en_GB_orkney = personalitydir / 'en_GB_orkney.ini'
en_GB_orkney.write_text(dedent("""\
[flatpak-remote-eos-apps]
apps_add =
com.endlessm.orkneyingasaga
"""))

builder = make_builder(configdir=str(configdir), personality="en_GB_orkney")
builder.configure()

assert builder.config['image']['language'] == "en_GB.utf8"
assert builder.config['flatpak-remote-eos-apps']['apps'] == "\n".join([
"com.endlessm.encyclopedia.en",
"com.endlessm.football.en_GB",
"com.endlessm.orkneyingasaga",
])


def test_localdir(make_builder, tmp_path, tmp_builder_paths, caplog):
"""Test use of local settings directory"""
# Build without localdir
Expand Down

0 comments on commit 3ebcde2

Please sign in to comment.