]> git.ipfire.org Git - thirdparty/mkosi.git/commitdiff
Immediately change directory on -C/--directory
authorGeorges Discry <georges@discry.be>
Fri, 22 Sep 2023 13:10:58 +0000 (15:10 +0200)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Mon, 25 Sep 2023 10:35:11 +0000 (12:35 +0200)
Common utilities (e.g., make and tar) that have a `-C`/`--directory`
option immediately change the working directory when that option is
parsed, with the following properties:

- the option is order sensitive and only applies to the following
  options
- the option can be used multiple times and each is interpreted relative
  to the previous one

In addition, mkosi uses an empty path for `-C`/`--directory` to indicate
that the configuration files in the current directory should be ignored.

At first, mkosi would call `os.chdir()` after parsing the command-line
arguments, so the following options with relative paths would resolve
relative to the initial working directory and not the new one. This
issue was reported in #1879.

The fix in #1880 reversed that approach. A first (and lighter) pass on
the command-line arguments would get the last `-C`/`--directory` and
call `os.chdir()`. Afterwards, all the command-line arguments were fully
parsed so that the relative paths would resolve to that directory.

Neither approach implements the properties above. Only the last value is
used and applies to none/all of the command-line arguments.

By calling `os.chdir()` as the options are parsed, both properties are
respected and the arguments can be parsed in a single-pass. The
resulting directory is still tracked in `args.directory` but as a `Path`
object defaulting to `Path.cwd()`, with `None` indicating that the
configuration files should be ignored.

Furthermore, it's now possible to call `mkosi -C <dir> -C ''` to work in
a given directory (for the output and workspace) while also ignoring the
configuration files present in that directory.

mkosi/config.py

index e91da67398d27daed91eef49fac6a89f2167f262..cdbec18884953d42ce0a93d8fecec7da51036d94 100644 (file)
@@ -556,6 +556,23 @@ class CustomHelpFormatter(argparse.HelpFormatter):
                                      subsequent_indent=subindent) for line in lines)
 
 
+def parse_chdir(path: str) -> Optional[Path]:
+    if not path:
+        # The current directory should be ignored
+        return None
+
+    # Immediately change the current directory so that it's taken into
+    # account when parsing the following options that take a relative path
+    try:
+        os.chdir(path)
+    except (FileNotFoundError, NotADirectoryError):
+        die(f"{path} is not a directory!")
+    except OSError as e:
+        die(f"Cannot change the directory to {path}: {e}")
+    # Keep track of the current directory
+    return Path.cwd()
+
+
 def config_make_action(settings: Sequence[MkosiConfigSetting]) -> type[argparse.Action]:
     lookup = {s.dest: s for s in settings}
 
@@ -1703,7 +1720,7 @@ MATCHES = (
 )
 
 
-def create_argument_parser(*, settings: bool) -> argparse.ArgumentParser:
+def create_argument_parser() -> argparse.ArgumentParser:
     action = config_make_action(SETTINGS)
 
     parser = argparse.ArgumentParser(
@@ -1747,9 +1764,10 @@ def create_argument_parser(*, settings: bool) -> argparse.ArgumentParser:
     )
     parser.add_argument(
         "-C", "--directory",
+        type=parse_chdir,
+        default=Path.cwd(),
         help="Change to specified directory before doing anything",
         metavar="PATH",
-        default=None,
     )
     parser.add_argument(
         "--debug",
@@ -1811,9 +1829,6 @@ def create_argument_parser(*, settings: bool) -> argparse.ArgumentParser:
         help=argparse.SUPPRESS,
     )
 
-    if not settings:
-        return parser
-
     parser.add_argument(
         "verb",
         type=Verb,
@@ -2051,19 +2066,8 @@ def parse_config(argv: Sequence[str] = ()) -> tuple[MkosiArgs, tuple[MkosiConfig
     else:
         argv += ["--", "build"]
 
-    # Don't parse the settings just yet so we can take --directory into account when parsing settings that
-    # take relative paths.
-    argparser = create_argument_parser(settings=False)
-    argparser.parse_known_args(argv, namespace)
-
-    if namespace.directory and not Path(namespace.directory).is_dir():
-        die(f"{namespace.directory} is not a directory!")
-
-    if namespace.directory:
-        os.chdir(namespace.directory)
-
     namespace = argparse.Namespace()
-    argparser = create_argument_parser(settings=True)
+    argparser = create_argument_parser()
     argparser.parse_args(argv, namespace)
 
     args = load_args(namespace)
@@ -2076,7 +2080,7 @@ def parse_config(argv: Sequence[str] = ()) -> tuple[MkosiArgs, tuple[MkosiConfig
 
     include = ()
 
-    if args.directory != "":
+    if args.directory is not None:
         parse_config(Path("."), namespace, defaults)
 
         include = getattr(namespace, "presets", ())
@@ -2126,7 +2130,7 @@ def load_credentials(args: argparse.Namespace) -> dict[str, str]:
     }
 
     d = Path("mkosi.credentials")
-    if args.directory != "" and d.is_dir():
+    if args.directory is not None and d.is_dir():
         for e in d.iterdir():
             if os.access(e, os.X_OK):
                 creds[e.name] = run([e], stdout=subprocess.PIPE, env=os.environ).stdout