From 7df96205d6c3a9036d902d2d3fdfcb7649af13c6 Mon Sep 17 00:00:00 2001 From: Justin Larkin Date: Thu, 30 Apr 2026 10:00:56 -0400 Subject: [PATCH] feat(cli): add option for a single output-dir and hide options Add a new -O/--output-dir option that sets the base directory for sdists-repo, wheels-repo, and work-dir. The individual directory options are hidden but still functional for backwards compatibility. When --output-dir is "." (the default), behavior is identical to before. Closes: #721 Co-Authored-By: Claude Opus 4.6 Signed-off-by: Justin Larkin Made-with: Cursor --- src/fromager/__main__.py | 38 +++++++++++++++++++++------- tests/test_cli.py | 54 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 9 deletions(-) diff --git a/src/fromager/__main__.py b/src/fromager/__main__.py index 32f1985b..c219df0e 100644 --- a/src/fromager/__main__.py +++ b/src/fromager/__main__.py @@ -66,19 +66,28 @@ type=clickext.ClickPath(), help="save error messages to a file", ) +@click.option( + "-O", + "--output-dir", + default=pathlib.Path("."), + type=clickext.ClickPath(), + help="base directory for sdists-repo, wheels-repo, and work-dir (default: current directory)", +) @click.option( "-o", "--sdists-repo", - default=pathlib.Path("sdists-repo"), + default=None, type=clickext.ClickPath(), - help="location to manage source distributions", + hidden=True, + help="location to manage source distributions (overrides --output-dir)", ) @click.option( "-w", "--wheels-repo", - default=pathlib.Path("wheels-repo"), + default=None, type=clickext.ClickPath(), - help="location to manage wheel repository", + hidden=True, + help="location to manage wheel repository (overrides --output-dir)", ) @click.option( "--build-wheel-server-url", @@ -87,9 +96,10 @@ @click.option( "-t", "--work-dir", - default=pathlib.Path("work-dir"), + default=None, type=clickext.ClickPath(), - help="location to manage working files, including builds and logs", + hidden=True, + help="location to manage working files, including builds and logs (overrides --output-dir)", ) @click.option( "-p", @@ -158,10 +168,11 @@ def main( log_file: pathlib.Path, log_format: str, error_log_file: pathlib.Path, - sdists_repo: pathlib.Path, - wheels_repo: pathlib.Path, + output_dir: pathlib.Path, + sdists_repo: pathlib.Path | None, + wheels_repo: pathlib.Path | None, build_wheel_server_url: str, - work_dir: pathlib.Path, + work_dir: pathlib.Path | None, patches_dir: pathlib.Path, settings_file: pathlib.Path, settings_dir: pathlib.Path, @@ -176,6 +187,15 @@ def main( global _DEBUG _DEBUG = debug + # Resolve output directories: explicit per-directory flags take + # precedence, otherwise derive from --output-dir. + if not sdists_repo: + sdists_repo = output_dir / "sdists-repo" + if not wheels_repo: + wheels_repo = output_dir / "wheels-repo" + if not work_dir: + work_dir = output_dir / "work-dir" + # Set custom log factory to prepend requirement name. logging.setLogRecordFactory(log.FromagerLogRecord) # Set the overall logger level to debug and allow the handlers to filter diff --git a/tests/test_cli.py b/tests/test_cli.py index 70b365c5..d98845b7 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -42,6 +42,60 @@ def test_fromager_version(cli_runner: CliRunner) -> None: assert result.stdout.startswith("fromager, version") +def test_output_dir_hidden_options(cli_runner: CliRunner) -> None: + """--output-dir is visible in help; old per-directory flags are hidden.""" + result = cli_runner.invoke(fromager, ["--help"]) + assert "-O, --output-dir" in result.output + lines = result.output.splitlines() + option_lines = [line.strip() for line in lines if line.strip().startswith("-")] + option_names = " ".join(option_lines) + assert "--sdists-repo" not in option_names + assert "--wheels-repo" not in option_names + assert "--work-dir" not in option_names + + +def test_output_dir_sets_subdirectories( + tmp_path: pathlib.Path, cli_runner: CliRunner +) -> None: + """Passing -O creates sdists-repo, wheels-repo, work-dir under it.""" + out = tmp_path / "my-output" + out.mkdir() + + result = cli_runner.invoke( + fromager, + ["-O", str(out), "canonicalize", "some-package"], + ) + assert result.exit_code == 0, result.output + assert (out / "sdists-repo").is_dir() + assert (out / "wheels-repo").is_dir() + assert (out / "work-dir").is_dir() + + +def test_output_dir_overridden_by_explicit_flags( + tmp_path: pathlib.Path, cli_runner: CliRunner +) -> None: + """Explicit --sdists-repo takes precedence over --output-dir.""" + out = tmp_path / "base" + out.mkdir() + + result = cli_runner.invoke( + fromager, + [ + "-O", + str(out), + "--sdists-repo", + str(tmp_path / "custom-sdists"), + "canonicalize", + "some-package", + ], + ) + assert result.exit_code == 0, result.output + assert (tmp_path / "custom-sdists").is_dir() + assert (out / "wheels-repo").is_dir() + assert (out / "work-dir").is_dir() + assert not (out / "sdists-repo").exists() + + KNOWN_COMMANDS: set[str] = { "bootstrap", "bootstrap-parallel",