Skip to content

Commit

Permalink
Merge pull request #2028 from freakboy3742/cmdline-cleanup
Browse files Browse the repository at this point in the history
Simplify handling of command line arguments.
  • Loading branch information
mhsmith authored Oct 14, 2024
2 parents c11a391 + a7186ce commit 63f7260
Show file tree
Hide file tree
Showing 4 changed files with 47 additions and 46 deletions.
1 change: 1 addition & 0 deletions changes/2026.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Python 3.12.7 introduced an incompatibility with the handling of ``-C``, ``-d`` and other flags that accept values. This incompatibility has been corrected.
71 changes: 32 additions & 39 deletions src/briefcase/cmdline.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,12 @@
from briefcase.console import MAX_TEXT_WIDTH, Console
from briefcase.platforms import get_output_formats, get_platforms

from .exceptions import InvalidFormatError, NoCommandError, UnsupportedCommandError
from .exceptions import (
InvalidFormatError,
InvalidPlatformError,
NoCommandError,
UnsupportedCommandError,
)

COMMANDS = [
NewCommand,
Expand Down Expand Up @@ -103,12 +108,6 @@ def parse_cmdline(args, console: Console | None = None):
help=argparse.SUPPRESS,
)

# To make the UX a little forgiving, we normalize *any* case to the case
# actually used to register the platform. This function maps the lower-case
# version of the registered name to the actual registered name.
def normalize(name):
return {n.lower(): n for n in platforms.keys()}.get(name.lower(), name)

# argparse handles `--` specially, so make the passthrough args bypass the parser.
def parse_known_args(args):
args, passthrough = split_passthrough(args)
Expand Down Expand Up @@ -136,48 +135,42 @@ def parse_known_args(args):
Command = DevCommand
elif options.command == "upgrade":
Command = UpgradeCommand

# Commands dependent on the platform and format
else:
parser.add_argument(
"platform",
choices=list(platforms.keys()),
default={
# Commands dependent on the platform and format. The general form of such a
# command is `briefcase <cmd> <platform> <format>`; but the format will be
# inferred from the platform if one isn't specified, and the platform will be
# inferred from the operating system if it isn't explicitly given.
#
# <platform> and <format> aren't parsed as regular arguments due to ambiguities
# in interpreting those arguments; instead, they're handled directly from the
# argument list, with the expectation that they *must* be the first and second
# arguments (after the command) if provided. There's no other bare arguments, so
# we only need to look for whether the arguments start with "-".
if extra and not extra[0].startswith("-"):
name = extra.pop(0)
# Normalize the platform name to the registered capitalization
platform = {n.lower(): n for n in platforms.keys()}.get(name.lower(), name)
else:
platform = {
"darwin": "macOS",
"linux": "linux",
"win32": "windows",
}[sys.platform],
metavar="platform",
nargs="?",
type=normalize,
help="The platform to target (one of %(choices)s; default: %(default)s",
)

# <format> is also optional, with the default being platform dependent.
# There's no way to encode option-dependent choices, so allow *any*
# input, and we'll manually validate.
parser.add_argument(
"output_format",
metavar="format",
nargs="?",
help="The output format to use (the available output formats are platform dependent)",
)

# Re-parse the arguments, now that we know it is a command that makes use
# of platform/output_format.
options, extra = parse_known_args(args)
}[sys.platform]

# Import the platform module
platform_module = platforms[options.platform]
try:
platform_module = platforms[platform]
except KeyError:
raise InvalidPlatformError(platform, platforms.keys())

# If the output format wasn't explicitly specified, check to see
# Otherwise, extract and use the default output_format for the platform.
if options.output_format is None:
output_format = platform_module.DEFAULT_OUTPUT_FORMAT
if extra and not extra[0].startswith("-") and not extra[0] == "--":
output_format = extra.pop(0)
else:
output_format = options.output_format
output_format = platform_module.DEFAULT_OUTPUT_FORMAT

output_formats = get_output_formats(options.platform)
output_formats = get_output_formats(platform)

# Normalise casing of output_format to be more forgiving.
output_format = {n.lower(): n for n in output_formats}.get(
Expand All @@ -196,7 +189,7 @@ def parse_known_args(args):
)
except AttributeError:
raise UnsupportedCommandError(
platform=options.platform,
platform=platform,
output_format=output_format,
command=options.command,
)
Expand Down
11 changes: 11 additions & 0 deletions src/briefcase/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,17 @@ def __str__(self):
return self.msg


class InvalidPlatformError(BriefcaseError):
def __init__(self, requested, choices):
super().__init__(error_code=-20, skip_logfile=True)
self.requested = requested
self.choices = choices

def __str__(self):
choices = ", ".join(sorted(self.choices, key=str.lower))
return f"Invalid platform {self.requested!r}; (choose from: {choices})"


class InvalidFormatError(BriefcaseError):
def __init__(self, requested, choices):
super().__init__(error_code=-21, skip_logfile=True)
Expand Down
10 changes: 3 additions & 7 deletions tests/test_cmdline.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from briefcase.console import Console, Log, LogLevel
from briefcase.exceptions import (
InvalidFormatError,
InvalidPlatformError,
NoCommandError,
UnsupportedCommandError,
)
Expand Down Expand Up @@ -456,15 +457,10 @@ def test_command_unknown_platform(monkeypatch, logger, console):
# Pretend we're on macOS, regardless of where the tests run.
monkeypatch.setattr(sys, "platform", "darwin")

with pytest.raises(SystemExit) as excinfo:
expected_exc_regex = r"Invalid platform 'foobar'; \(choose from: .*\)"
with pytest.raises(InvalidPlatformError, match=expected_exc_regex):
do_cmdline_parse("create foobar".split(), logger, console)

assert excinfo.value.code == 2
assert excinfo.value.__context__.argument_name == "platform"
assert excinfo.value.__context__.message.startswith(
"invalid choice: 'foobar' (choose from"
)


def test_command_explicit_platform(monkeypatch, logger, console):
"""``briefcase create linux`` returns linux create app command."""
Expand Down

0 comments on commit 63f7260

Please sign in to comment.