Repository: systemd/mkosi

Issue:
Arguments for build are ignored if placed after the command
I *thought* the build command with arguments and the `mkosi.config` file below are the same. However the former ignores `--format` and outputs a `image.raw` file while the latter creates a directory (named `image`) in the current folder.

`mkosi build --distribution=arch --format=directory`

```ini
[Distribution]
Distribution=arch

[Output]
Format=directory
```

(This behavior occurs on the main branch with #1211 fixed.)


Discussion:
I just realized that the correct command is `mkosi --distribution=arch --format=directory build`.
Is it possible to raise an error if the arguments are in the wrong position?

Resolving patch:
diff --git a/mkosi/__init__.py b/mkosi/__init__.py
--- a/mkosi/__init__.py
+++ b/mkosi/__init__.py
@@ -5131,27 +5131,55 @@ def parse_ssh_agent(value: str) -> Optional[Path]:
         die(f"SSH agent socket {sock} is not an AF_UNIX socket")
     return sock
 
+USAGE = """
+       mkosi [options...] {b}summary{e}
+       mkosi [options...] {b}build{e} [script parameters...]
+       mkosi [options...] {b}shell{e} [command line...]
+       mkosi [options...] {b}boot{e} [nspawn settings...]
+       mkosi [options...] {b}qemu{e} [qemu parameters...]
+       mkosi [options...] {b}ssh{e} [command line...]
+       mkosi [options...] {b}clean{e}
+       mkosi [options...] {b}serve{e}
+       mkosi [options...] {b}bump{e}
+       mkosi [options...] {b}genkey{e}
+       mkosi [options...] {b}help{e}
+       mkosi -h | --help
+       mkosi --version
+""".format(b=MkosiPrinter.bold, e=MkosiPrinter.reset)
 
 def create_parser() -> ArgumentParserMkosi:
-    parser = ArgumentParserMkosi(prog="mkosi", description="Build Bespoke OS Images", add_help=False)
+    parser = ArgumentParserMkosi(
+        prog="mkosi",
+        description="Build Bespoke OS Images",
+        usage=USAGE,
+        add_help=False,
+    )
 
-    group = parser.add_argument_group("Commands")
-    group.add_argument(
+    parser.add_argument(
         "verb",
         type=Verb,
         choices=list(Verb),
         default=Verb.build,
-        help="Operation to execute",
+        help=argparse.SUPPRESS,
     )
-    group.add_argument(
+    parser.add_argument(
         "cmdline",
         nargs=argparse.REMAINDER,
-        help=f"The command line to use for {list_to_string(verb.name for verb in MKOSI_COMMANDS_CMDLINE)}",
+        help=argparse.SUPPRESS,
+    )
+    parser.add_argument(
+        "-h", "--help",
+        action="help",
+        help=argparse.SUPPRESS,
+    )
+    parser.add_argument(
+        "--version",
+        action="version",
+        version="%(prog)s " + __version__,
+        help=argparse.SUPPRESS,
     )
-    group.add_argument("-h", "--help", action="help", help="Show this help")
-    group.add_argument("--version", action="version", version="%(prog)s " + __version__)
 
-    group = parser.add_argument_group("Distribution")
+    group = parser.add_argument_group("Distribution options")
     group.add_argument("-d", "--distribution", choices=Distribution.__members__, help="Distribution to install")
     group.add_argument("-r", "--release", help="Distribution release to install")
     group.add_argument("--architecture", help="Override the architecture of installation", default=platform.machine())
@@ -5186,7 +5214,7 @@ def create_parser() -> ArgumentParserMkosi:
         help="Directory container extra distribution specific repository files",
     )
 
-    group = parser.add_argument_group("Output")
+    group = parser.add_argument_group("Output options")
     group.add_argument(
         "-t", "--format",
         dest="output_format",
@@ -5429,7 +5457,7 @@ def create_parser() -> ArgumentParserMkosi:
         help="Generate split out root/verity/kernel images, too",
     )
 
-    group = parser.add_argument_group("Content")
+    group = parser.add_argument_group("Content options")
     group.add_argument(
         "--base-packages",
         type=parse_base_packages,
@@ -5671,7 +5699,7 @@ def create_parser() -> ArgumentParserMkosi:
         metavar="PATH",
     )
 
-    group = parser.add_argument_group("Partitions")
+    group = parser.add_argument_group("Partitions options")
     group.add_argument('--base-image',
                        help='Use the given image as base (e.g. lower sysext layer)',
                        type=Path,
@@ -5716,7 +5744,7 @@ def create_parser() -> ArgumentParserMkosi:
         help="Generate a /usr/ partition instead of a root partition",
     )
 
-    group = parser.add_argument_group("Validation (only gpt_ext4, gpt_xfs, gpt_btrfs, gpt_squashfs, tar, cpio)")
+    group = parser.add_argument_group("Validation options (only gpt_ext4, gpt_xfs, gpt_btrfs, gpt_squashfs, tar, cpio)")
     group.add_argument(
         "--checksum",
         metavar="BOOL",
@@ -5737,7 +5765,7 @@ def create_parser() -> ArgumentParserMkosi:
         help="Write block map file (.bmap) for bmaptool usage (only gpt_ext4, gpt_btrfs)",
     )
 
-    group = parser.add_argument_group("Host configuration")
+    group = parser.add_argument_group("Host configuration options")
     group.add_argument(
         "--extra-search-path",
         dest="extra_search_paths",
@@ -5852,7 +5880,7 @@ def create_parser() -> ArgumentParserMkosi:
         help="If specified, 'mkosi ssh' will use this port to connect",
     )
 
-    group = parser.add_argument_group("Additional Configuration")
+    group = parser.add_argument_group("Additional configuration options")
     group.add_argument(
         "-C", "--directory",
         help="Change to specified directory before doing anything",
@@ -6462,7 +6490,7 @@ def load_args(args: argparse.Namespace) -> MkosiConfig:
     args.extra_search_paths = expand_paths(args.extra_search_paths)
 
     if args.cmdline and args.verb not in MKOSI_COMMANDS_CMDLINE:
-        die(f"Additional parameters only accepted for {list_to_string(verb.name for verb in MKOSI_COMMANDS_CMDLINE)} invocations.")
+        die(f"Parameters after verb are only accepted for {list_to_string(verb.name for verb in MKOSI_COMMANDS_CMDLINE)}.")
 
     if args.output_format is None:
         args.output_format = OutputFormat.gpt_ext4


Test patch: