mpp: Add support for formating strings

This lets a mpp manifest define some variables and then use
these variables inside python f-strings to expand things in a
flexible way. This allows a single value such as `rootfs_size` to
be expanded in various places, including using computations to
e.g. define the partition offsets/sizes.
This commit is contained in:
Alexander Larsson 2021-07-01 09:55:46 +02:00 committed by Christian Kellner
parent ba3467d747
commit e541c1c196

View file

@ -95,6 +95,30 @@ The parameters for this pre-processor, version "2", look like this:
...
```
String expansion:
You can expand string with using python f-string formatting and variables. The variables
can be set in the mpp-vars toplevel dict (which is removed from the final results) or
overridden by the --var commandline option.
Example:
```
{
"mpp-vars": {
"variable": "some string",
"rootfs_size": 20480
},
...
{
"foo": "a value",
"bar": { "mpp-format-string": "This expands {variable} but can also eval like {variable.upper()}" }
"disk_size": { "mpp-format-int": "{rootfs_size * 512}" }
}
...
```
"""
@ -294,6 +318,21 @@ class ManifestFile:
self.sources = element_enter(self.root, "sources", {})
self.source_urls = {}
self.vars = {}
if "mpp-vars" in root:
self.vars = root["mpp-vars"]
del root["mpp-vars"]
def set_vars(self, args):
for arg in args:
if "=" in arg:
key, value_s = arg.split("=", 1)
value = json.loads(value_s)
else:
key = arg
value = True
self.vars[key] = value
def load_import(self, path, search_dirs):
m = self.find_and_load_manifest(path, search_dirs)
if m.version != self.version:
@ -365,6 +404,40 @@ class ManifestFile:
json.dump(self.root, file, indent=2, sort_keys=sort_keys)
file.write("\n")
def _process_format(self, node):
def _is_format(node):
if not isinstance(node, dict):
return False
return "mpp-format-string" in node or "mpp-format-int" in node
def _eval_format(node, local_vars):
if "mpp-format-string" in node:
is_int = False
format_string = node["mpp-format-string"]
else:
is_int = True
format_string = node["mpp-format-int"]
res = eval(f'f\'\'\'{format_string}\'\'\'', local_vars) # pylint: disable=eval-used # yolo this is fine!
if is_int:
return int(res)
return res
if isinstance(node, dict):
for key in list(node.keys()):
value = node[key]
if _is_format(value):
node[key] = _eval_format(value, self.vars)
else:
self._process_format(value)
if isinstance(node, list):
for i, value in enumerate(node):
if _is_format(value):
node[i] = _eval_format(value, self.vars)
else:
self._process_format(value)
def process_format(self):
self._process_format(self.root)
class ManifestFileV1(ManifestFile):
def __init__(self, path, data):
@ -382,6 +455,8 @@ class ManifestFileV1(ManifestFile):
path = mpp["path"]
imp = self.load_import(path, search_dirs)
self.vars.update(imp.vars)
# We only support importing manifests with URL sources. Other sources are
# not supported, yet. This can be extended in the future, but we should
# maybe rather try to make sources generic (and repeatable?), so we can
@ -463,6 +538,8 @@ class ManifestFileV2(ManifestFile):
path = mpp["path"]
imp = self.load_import(path, search_dirs)
self.vars.update(imp.vars)
for source, desc in imp.sources.items():
target = self.sources.get(source)
if not target:
@ -533,6 +610,12 @@ def main():
action='store_true',
help="Sort keys in generated json",
)
parser.add_argument(
"-D,--define",
dest="vars",
action='append',
help="Set/Override variable, format is key=Json"
)
parser.add_argument(
"src",
metavar="SRCPATH",
@ -551,10 +634,16 @@ def main():
# First resolve all imports
m.process_imports(args.searchdirs)
# Override variables from the main of imported files
if args.vars:
m.set_vars(args.vars)
with tempfile.TemporaryDirectory() as persistdir:
solver = DepSolver(args.dnf_cache, persistdir)
m.process_depsolves(solver)
m.process_format()
with sys.stdout if args.dst == "-" else open(args.dst, "w") as f:
m.write(f, args.sort_keys)