Repository: systemd/mkosi

Issue:
Support environment file(s)
Currently mkosi supports specification of environment variables via cli and inside the `mkosi.default` file.
Both variants are tedious to use when variables are generated outside of mkosi.
I propose a new option `EnvironmentFile` (`mkosi.env` as default) which takes multiple file paths that are parsed & translated into nspawn env options when building the container and set inside the `mkosi.finalize` script.
The file format that is used to describe the variables inside these files could be a "plain" shell-like format like systemd uses, which can be generated outside of mkosi.


Discussion:
makes sense I guess.
@nopeslide You can now specify Environment=FOO, and this will propagate $FOO from the mkosi environment. Is this enough for you, or do you still want the env file?
> @nopeslide You can now specify Environment=FOO, and this will propagate $FOO from the mkosi environment. Is this enough for you, or do you still want the env file?

I have a few hundred variables (multiple network configs), a file based approach would be nice.
I also started an implementation, but didn't had the time to finalize it.

Resolving patch:
diff --git a/mkosi/config.py b/mkosi/config.py
--- a/mkosi/config.py
+++ b/mkosi/config.py
@@ -954,6 +954,7 @@ class MkosiConfig:
     build_sources: list[ConfigTree]
     build_sources_ephemeral: bool
     environment: dict[str, str]
+    environment_files: list[Path]
     with_tests: bool
     with_network: bool
 
@@ -1677,6 +1678,16 @@ def parse_ini(path: Path, only_sections: Collection[str] = ()) -> Iterator[tuple
         parse=config_make_list_parser(delimiter=" ", unescape=True),
         help="Set an environment variable when running scripts",
     ),
+    MkosiConfigSetting(
+        dest="environment_files",
+        long="--env-file",
+        metavar="PATH",
+        section="Content",
+        parse=config_make_list_parser(delimiter=",", parse=make_path_parser()),
+        paths=("mkosi.env",),
+        path_default=False,
+        help="Enviroment files to set when running scripts",
+    ),
     MkosiConfigSetting(
         dest="with_tests",
         short="-T",
@@ -2879,8 +2890,10 @@ def load_environment(args: argparse.Namespace) -> dict[str, str]:
     if dnf := os.getenv("MKOSI_DNF"):
         env["MKOSI_DNF"] = dnf
 
-    for s in args.environment:
+    entries = [line for envfile in args.environment_files for line in envfile.read_text().strip().splitlines()]
+    for s in entries + args.environment:
         key, sep, value = s.partition("=")
+        key, value = key.strip(), value.strip()
         value = value if sep else os.getenv(key, "")
         env[key] = value
 
@@ -3077,6 +3090,7 @@ def bold(s: Any) -> str:
                       Build Sources: {line_join_tree_list(config.build_sources)}
             Build Sources Ephemeral: {yes_no(config.build_sources_ephemeral)}
                  Script Environment: {line_join_list(env)}
+                  Environment Files: {line_join_list(config.environment_files)}
          Run Tests in Build Scripts: {yes_no(config.with_tests)}
                Scripts With Network: {yes_no(config.with_network)}
 


Test patch:
diff --git a/tests/test_config.py b/tests/test_config.py
--- a/tests/test_config.py
+++ b/tests/test_config.py
@@ -753,3 +753,45 @@ def test_specifiers(tmp_path: Path) -> None:
 
 def test_deterministic() -> None:
     assert MkosiConfig.default() == MkosiConfig.default()
+
+
+def test_environment(tmp_path: Path) -> None:
+    d = tmp_path
+
+    (d / "mkosi.conf").write_text(
+        """\
+        [Content]
+        Environment=TestValue2=300
+                    TestValue3=400
+        EnvironmentFiles=other.env
+        """
+    )
+
+    (d / "mkosi.env").write_text(
+        """\
+        TestValue1=90
+        TestValue4=99
+        """
+    )
+
+    (d / "other.env").write_text(
+        """\
+        TestValue1=100
+        TestValue2=200
+        """
+    )
+
+    with chdir(d):
+        _, [config] = parse_config()
+
+        expected = {
+            "TestValue1": "100", # from other.env
+            "TestValue2": "300", # from mkosi.conf
+            "TestValue3": "400", # from mkosi.conf
+            "TestValue4": "99", # from mkosi.env
+        }
+
+        # Only check values for keys from expected, as config.environment contains other items as well
+        assert {k: config.environment[k] for k in expected.keys()} == expected
+
+        assert config.environment_files == [Path.cwd() / "mkosi.env", Path.cwd() / "other.env"]
diff --git a/tests/test_json.py b/tests/test_json.py
--- a/tests/test_json.py
+++ b/tests/test_json.py
@@ -117,6 +117,7 @@ def test_config() -> None:
             ],
             "Distribution": "fedora",
             "Environment": {},
+            "EnvironmentFiles": [],
             "Ephemeral": true,
             "ExtraSearchPaths": [],
             "ExtraTrees": [],
@@ -308,6 +309,7 @@ def test_config() -> None:
         dependencies = ("dep1",),
         distribution = Distribution.fedora,
         environment = {},
+        environment_files = [],
         ephemeral = True,
         extra_search_paths = [],
         extra_trees = [],