arch-image-builder/builder/lib/config.py

119 lines
3.2 KiB
Python
Raw Normal View History

import os
import yaml
from logging import getLogger
from builder.lib import json
from builder.lib.cpu import cpu_arch_compatible
from builder.lib.context import ArchBuilderContext
log = getLogger(__name__)
class ArchBuilderConfigError(Exception):
pass
def _dict_merge(dst: dict, src: dict):
"""
Merge two dict with override
"""
for key in src.keys():
st = type(src[key])
if key in dst and st is type(dst[key]):
if st == list:
dst[key].extend(src[key])
continue
if st == dict:
_dict_merge(dst[key], src[key])
continue
dst[key] = src[key]
def load_config_file(ctx: ArchBuilderContext, path: str):
"""
Load one config (yaml/json) to context
"""
log.debug(f"try to open config {path}")
try:
with open(path, "r") as f:
if path.endswith((".yml", ".yaml")):
log.debug(f"load {path} as yaml")
loaded = yaml.safe_load(f)
elif path.endswith((".jsn", ".json")):
log.debug(f"load {path} as json")
loaded = json.load(f)
log.info(f"loaded config {path}")
except BaseException:
log.error(f"failed to load config {path}")
raise
def _proc_include(inc: str | list[str]):
pt = type(inc)
if pt is str: inc = [inc]
elif pt is list: pass
else: raise ArchBuilderConfigError("bad type for also")
load_configs(ctx, inc)
if loaded is None: return
if "+also" in loaded:
_proc_include(loaded["+also"])
loaded.pop("+also")
if ctx.config is None:
log.debug(f"use {path} as current config")
ctx.config = loaded
else:
log.debug(f"merge {path} into current config")
_dict_merge(ctx.config, loaded)
if "+then" in loaded:
_proc_include(loaded["+then"])
loaded.pop("+then")
def populate_config(ctx: ArchBuilderContext):
ctx.finish_config()
ctx.resolve_subscript()
if "target" not in ctx.config:
raise ArchBuilderConfigError("no target set")
if "arch" not in ctx.config:
raise ArchBuilderConfigError("no cpu arch set")
ctx.target = ctx.config["target"]
ctx.tgt_arch = ctx.config["arch"]
if ctx.tgt_arch == "any" or ctx.cur_arch == "any":
raise ArchBuilderConfigError("bad cpu arch value")
if not cpu_arch_compatible(ctx.tgt_arch, ctx.cur_arch):
log.warning(
f"current cpu arch {ctx.cur_arch} is not compatible to {ctx.tgt_arch}, "
"you may need qemu-user-static-binfmt to run incompatible executables",
)
jstr = json.dumps(ctx.config, indent=2)
log.debug(f"populated config:\n {jstr}")
def load_configs(ctx: ArchBuilderContext, configs: list[str]):
"""
Load multiple config to context
"""
loaded = 0
for config in configs:
success = False
for suffix in ["yml", "yaml", "jsn", "json"]:
fn = f"{config}.{suffix}"
path = os.path.join(ctx.dir, "configs", fn)
if os.path.exists(path):
load_config_file(ctx, path)
loaded += 1
success = True
if not success:
raise FileNotFoundError(f"config {config} not found")
if loaded > 0:
if ctx.config is None:
raise ArchBuilderConfigError("no any config loaded")
log.debug(f"loaded {loaded} configs")
def load_preset(ctx: ArchBuilderContext, preset: str):
file = preset
if not preset.startswith("/"):
file = "presets/" + preset
ctx.preset = True
load_configs(ctx, [file])
if "package" not in ctx.config:
raise ArchBuilderConfigError("bad preset config")
log.info(f"loaded preset {preset}")