import fnmatch
import functools
import getpass
+import glob
import graphlib
import io
import itertools
unescape: bool = False,
reset: bool = True,
key: Optional[Callable[[T], Any]] = None,
+ expand_globs: bool = False,
) -> ConfigParseCallback[list[T]]:
def config_parse_list(value: Optional[str], old: Optional[list[T]]) -> Optional[list[T]]:
new = old.copy() if old else []
if reset and len(values) == 1 and values[0] == "":
return None
+ if expand_globs:
+ values = flatten(sorted(glob.glob(v)) for v in values)
+
new += [parse(v) for v in values if v]
if key:
long="--configure-script",
metavar="PATH",
section="Config",
- parse=config_make_list_parser(delimiter=",", parse=make_path_parser()),
+ parse=config_make_list_parser(delimiter=",", parse=make_path_parser(), expand_globs=True),
path_suffixes=("configure",),
help="Configure script to run before doing anything",
),
long="--clean-script",
metavar="PATH",
section="Output",
- parse=config_make_list_parser(delimiter=",", parse=make_path_parser()),
+ parse=config_make_list_parser(delimiter=",", parse=make_path_parser(), expand_globs=True),
path_suffixes=("clean",),
recursive_path_suffixes=("clean.d/*",),
help="Clean script to run after cleanup",
long="--sync-script",
metavar="PATH",
section="Content",
- parse=config_make_list_parser(delimiter=",", parse=make_path_parser()),
+ parse=config_make_list_parser(delimiter=",", parse=make_path_parser(), expand_globs=True),
path_suffixes=("sync",),
recursive_path_suffixes=("sync.d/*",),
help="Sync script to run before starting the build",
long="--prepare-script",
metavar="PATH",
section="Content",
- parse=config_make_list_parser(delimiter=",", parse=make_path_parser()),
+ parse=config_make_list_parser(delimiter=",", parse=make_path_parser(), expand_globs=True),
path_suffixes=("prepare", "prepare.chroot"),
recursive_path_suffixes=("prepare.d/*",),
help="Prepare script to run inside the image before it is cached",
long="--build-script",
metavar="PATH",
section="Content",
- parse=config_make_list_parser(delimiter=",", parse=make_path_parser()),
+ parse=config_make_list_parser(delimiter=",", parse=make_path_parser(), expand_globs=True),
path_suffixes=("build", "build.chroot"),
recursive_path_suffixes=("build.d/*",),
help="Build script to run inside image",
metavar="PATH",
name="PostInstallationScripts",
section="Content",
- parse=config_make_list_parser(delimiter=",", parse=make_path_parser()),
+ parse=config_make_list_parser(delimiter=",", parse=make_path_parser(), expand_globs=True),
path_suffixes=("postinst", "postinst.chroot"),
recursive_path_suffixes=("postinst.d/*",),
help="Postinstall script to run inside image",
long="--finalize-script",
metavar="PATH",
section="Content",
- parse=config_make_list_parser(delimiter=",", parse=make_path_parser()),
+ parse=config_make_list_parser(delimiter=",", parse=make_path_parser(), expand_globs=True),
path_suffixes=("finalize", "finalize.chroot"),
recursive_path_suffixes=("finalize.d/*",),
help="Postinstall script to run outside image",
metavar="PATH",
name="PostOutputScripts",
section="Content",
- parse=config_make_list_parser(delimiter=",", parse=make_path_parser()),
+ parse=config_make_list_parser(delimiter=",", parse=make_path_parser(), expand_globs=True),
path_suffixes=("postoutput",),
recursive_path_suffixes=("postoutput.d/*",),
help="Output postprocessing script to run outside image",
`CleanScripts=`, `--clean-script=`
: Takes a comma-separated list of paths to executables that are used as
- the clean scripts for this image. See the **SCRIPTS** section for
- more information.
+ the clean scripts for this image. Glob patterns are supported and
+ matching paths are sorted alphabetically. See the **SCRIPTS** section
+ for more information.
### [Content] Section
`SyncScripts=`, `--sync-script=`
: Takes a comma-separated list of paths to executables that are used as
- the sync scripts for this image. See the **SCRIPTS** section for
- more information.
+ the sync scripts for this image. Glob patterns are supported and
+ matching paths are sorted alphabetically. See the **SCRIPTS** section
+ for more information.
`PrepareScripts=`, `--prepare-script=`
: Takes a comma-separated list of paths to executables that are used as
- the prepare scripts for this image. See the **SCRIPTS** section for
- more information.
+ the prepare scripts for this image. Glob patterns are supported and
+ matching paths are sorted alphabetically. See the **SCRIPTS** section
+ for more information.
`BuildScripts=`, `--build-script=`
: Takes a comma-separated list of paths to executables that are used as
- the build scripts for this image. See the **SCRIPTS** section for more
- information.
+ the build scripts for this image. Glob patterns are supported and
+ matching paths are sorted alphabetically. See the **SCRIPTS** section
+ for more information.
`PostInstallationScripts=`, `--postinst-script=`
: Takes a comma-separated list of paths to executables that are used as
- the post-installation scripts for this image. See the **SCRIPTS** section
+ the post-installation scripts for this image. Glob patterns are supported
+ and matching paths are sorted alphabetically. See the **SCRIPTS** section
for more information.
`FinalizeScripts=`, `--finalize-script=`
: Takes a comma-separated list of paths to executables that are used as
- the finalize scripts for this image. See the **SCRIPTS** section for more
- information.
+ the finalize scripts for this image. Glob patterns are supported and
+ matching paths are sorted alphabetically. See the **SCRIPTS** section
+ for more information.
`PostOutputScripts=`, `--postoutput-script=`
: Takes a comma-separated list of paths to executables that are used as
- the post output scripts for this image. See the **SCRIPTS** section for more
- information.
+ the post output scripts for this image. Glob patterns are supported and
+ matching paths are sorted alphabetically. See the **SCRIPTS** section
+ for more information.
`Bootable=`, `--bootable=`
: Takes a boolean or `auto`. Enables or disables generation of a
`ConfigureScripts=`, `--configure-script=`
: Takes a comma-separated list of paths to executables that are used as
- the configure scripts for this image. See the **SCRIPTS** section for
- more information.
+ the configure scripts for this image. Glob patterns are supported and
+ matching paths are sorted alphabetically. See the **SCRIPTS** section
+ for more information.
`PassEnvironment=`, `--pass-environment=`
: Takes a list of environment variable names separated by spaces. When
]
+def test_glob_expansion(tmp_path: Path) -> None:
+ d = tmp_path
+
+ (d / "script_a.sh").touch()
+ (d / "script_b.sh").touch()
+ (d / "script_c.sh").touch()
+ (d / "other.py").touch()
+
+ # Glob patterns should be expanded and results should be sorted.
+ (d / "mkosi.conf").write_text(
+ f"""\
+ [Content]
+ PrepareScripts={d}/script_*.sh
+ """
+ )
+
+ with chdir(d):
+ _, _, [config] = parse_config()
+
+ assert config.prepare_scripts == [d / "script_a.sh", d / "script_b.sh", d / "script_c.sh"]
+
+ # Glob patterns that match nothing should result in an empty list.
+ (d / "mkosi.conf").write_text(
+ f"""\
+ [Content]
+ PrepareScripts={d}/nonexistent_*.sh
+ """
+ )
+
+ with chdir(d):
+ _, _, [config] = parse_config()
+
+ assert config.prepare_scripts == []
+
+ # Non-glob paths should be ordered before glob results when listed first.
+ (d / "mkosi.conf").write_text(
+ f"""\
+ [Content]
+ PrepareScripts={d}/other.py,{d}/script_*.sh
+ """
+ )
+
+ with chdir(d):
+ _, _, [config] = parse_config()
+
+ assert config.prepare_scripts == [
+ d / "other.py",
+ d / "script_a.sh",
+ d / "script_b.sh",
+ d / "script_c.sh",
+ ]
+
+ # Glob expansion should work with other script options too.
+ (d / "mkosi.conf").write_text(
+ f"""\
+ [Content]
+ BuildScripts={d}/script_*.sh
+ """
+ )
+
+ with chdir(d):
+ _, _, [config] = parse_config()
+
+ assert config.build_scripts == [d / "script_a.sh", d / "script_b.sh", d / "script_c.sh"]
+
+
@pytest.mark.parametrize(
"sections,args,warning_count",
[