From f5d428950e835e631e7eb6bbdcc080a60ee7bcfb Mon Sep 17 00:00:00 2001 From: Kevin Veen-Birkenbach Date: Fri, 12 Dec 2025 22:59:46 +0100 Subject: [PATCH] **Replace main.py with module-based entry point and unify CLI execution** * Remove legacy *main.py* and introduce *pkgmgr* module entry via *python -m pkgmgr* * Add ***main**.py* as the canonical entry point delegating to the CLI * Export *PYTHONPATH=src* in Makefile to ensure reliable imports in dev and CI * Update setup scripts (venv & nix) to use module execution * Refactor all E2E tests to execute the real module entry instead of file paths This aligns pkgmgr with standard Python packaging practices and simplifies testing, setup, and execution across environments. https://chatgpt.com/share/693c9056-716c-800f-b583-fc9245eab2b4 --- Makefile | 3 ++- main.py | 14 -------------- scripts/setup/nix.sh | 6 +++--- scripts/setup/venv.sh | 7 ++----- src/pkgmgr/__main__.py | 5 +++++ tests/e2e/test_branch_commands.py | 2 +- tests/e2e/test_branch_help.py | 2 +- tests/e2e/test_changelog_commands.py | 2 +- tests/e2e/test_clone_all.py | 2 +- tests/e2e/test_config_commands.py | 2 +- tests/e2e/test_install_pkgmgr_shallow.py | 2 +- tests/e2e/test_list_commands.py | 2 +- tests/e2e/test_make_commands.py | 2 +- tests/e2e/test_mirror_commands.py | 2 +- tests/e2e/test_path_commands.py | 2 +- tests/e2e/test_proxy_commands.py | 2 +- tests/e2e/test_release_commands.py | 4 ++-- tests/e2e/test_tools_commands.py | 2 +- tests/e2e/test_tools_help.py | 10 +--------- tests/e2e/test_version_commands.py | 2 +- 20 files changed, 28 insertions(+), 47 deletions(-) delete mode 100755 main.py create mode 100755 src/pkgmgr/__main__.py diff --git a/Makefile b/Makefile index 6ae9ab0..90bd8d4 100644 --- a/Makefile +++ b/Makefile @@ -30,6 +30,7 @@ export BASE_IMAGE_CENTOS # PYthon Unittest Pattern TEST_PATTERN := test_*.py export TEST_PATTERN +export PYTHONPATH := src # ------------------------------------------------------------ # System install @@ -45,7 +46,7 @@ install: # Default: keep current auto-detection behavior setup: setup-nix setup-venv -# Explicit: developer setup (Python venv + shell RC + main.py install) +# Explicit: developer setup (Python venv + shell RC + install) setup-venv: setup-nix @bash scripts/setup/venv.sh diff --git a/main.py b/main.py deleted file mode 100755 index 1a682cf..0000000 --- a/main.py +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env python3 -import sys -from pathlib import Path - -# Ensure local src/ overrides installed package -ROOT = Path(__file__).resolve().parent -SRC = ROOT / "src" -if SRC.is_dir(): - sys.path.insert(0, str(SRC)) - -from pkgmgr.cli import main - -if __name__ == "__main__": - main() diff --git a/scripts/setup/nix.sh b/scripts/setup/nix.sh index d3dd5ba..a3dd975 100755 --- a/scripts/setup/nix.sh +++ b/scripts/setup/nix.sh @@ -1,9 +1,9 @@ # ------------------------------------------------------------ -# Nix shell mode: do not touch venv, only run main.py install +# Nix shell mode: do not touch venv, only run install # ------------------------------------------------------------ echo "[setup] Nix mode enabled (NIX_ENABLED=1)." echo "[setup] Skipping virtualenv creation and dependency installation." -echo "[setup] Running main.py install via system python3..." -python3 main.py install +echo "[setup] Running install via system python3..." +python3 -m pkgmgr install echo "[setup] Setup finished (Nix mode)." diff --git a/scripts/setup/venv.sh b/scripts/setup/venv.sh index a1f3a7a..c577f7b 100755 --- a/scripts/setup/venv.sh +++ b/scripts/setup/venv.sh @@ -15,9 +15,6 @@ RC_LINE='if [ -d "${HOME}/.venvs/pkgmgr" ]; then . "${HOME}/.venvs/pkgmgr/bin/ac echo "[setup] Running in normal user mode (developer setup)." -echo "[setup] Ensuring main.py is executable..." -chmod +x main.py || true - echo "[setup] Ensuring global virtualenv root: ${HOME}/.venvs" mkdir -p "${HOME}/.venvs" @@ -90,8 +87,8 @@ for rc in "${HOME}/.bashrc" "${HOME}/.zshrc"; do fi done -echo "[setup] Running main.py install via venv Python..." -"${VENV_DIR}/bin/python" main.py install +echo "[setup] Running install via venv Python..." +"${VENV_DIR}/bin/python" -m pkgmgr install echo echo "[setup] Developer setup complete." diff --git a/src/pkgmgr/__main__.py b/src/pkgmgr/__main__.py new file mode 100755 index 0000000..2eced4f --- /dev/null +++ b/src/pkgmgr/__main__.py @@ -0,0 +1,5 @@ +#!/usr/bin/env python3 +from pkgmgr.cli import main + +if __name__ == "__main__": + main() diff --git a/tests/e2e/test_branch_commands.py b/tests/e2e/test_branch_commands.py index 4d3bcc2..b06711c 100644 --- a/tests/e2e/test_branch_commands.py +++ b/tests/e2e/test_branch_commands.py @@ -27,7 +27,7 @@ class TestIntegrationBranchCommands(unittest.TestCase): try: # argv[0] is the program name; the rest are CLI arguments. sys.argv = ["pkgmgr"] + list(extra_args) - runpy.run_module("main", run_name="__main__") + runpy.run_module("pkgmgr", run_name="__main__") finally: sys.argv = original_argv diff --git a/tests/e2e/test_branch_help.py b/tests/e2e/test_branch_help.py index 910b3ef..1825583 100644 --- a/tests/e2e/test_branch_help.py +++ b/tests/e2e/test_branch_help.py @@ -24,7 +24,7 @@ def _run_pkgmgr_help(argv_tail: list[str]) -> str: try: with redirect_stdout(buffer), redirect_stderr(buffer): - runpy.run_module("main", run_name="__main__") + runpy.run_module("pkgmgr", run_name="__main__") except SystemExit as exc: code = exc.code if isinstance(exc.code, int) else None if code not in (0, None): diff --git a/tests/e2e/test_changelog_commands.py b/tests/e2e/test_changelog_commands.py index 2184901..ac78f84 100644 --- a/tests/e2e/test_changelog_commands.py +++ b/tests/e2e/test_changelog_commands.py @@ -53,7 +53,7 @@ class TestIntegrationChangelogCommands(unittest.TestCase): sys.argv = ["pkgmgr", "changelog"] + list(extra_args) try: - runpy.run_module("main", run_name="__main__") + runpy.run_module("pkgmgr", run_name="__main__") except SystemExit as exc: code = exc.code if isinstance(exc.code, int) else str(exc.code) if code != 0: diff --git a/tests/e2e/test_clone_all.py b/tests/e2e/test_clone_all.py index 9553c87..98cdbb2 100644 --- a/tests/e2e/test_clone_all.py +++ b/tests/e2e/test_clone_all.py @@ -47,7 +47,7 @@ class TestIntegrationCloneAllHttps(unittest.TestCase): try: # Execute main.py as if it was called from CLI. # This will run the full clone pipeline inside the container. - runpy.run_module("main", run_name="__main__") + runpy.run_module("pkgmgr", run_name="__main__") except SystemExit as exc: # Determine the exit code (int or string) exit_code = exc.code diff --git a/tests/e2e/test_config_commands.py b/tests/e2e/test_config_commands.py index 50732a6..1c6d451 100644 --- a/tests/e2e/test_config_commands.py +++ b/tests/e2e/test_config_commands.py @@ -34,7 +34,7 @@ def _run_pkgmgr_config(extra_args: list[str]) -> None: sys.argv = ["pkgmgr"] + extra_args try: - runpy.run_module("main", run_name="__main__") + runpy.run_module("pkgmgr", run_name="__main__") except SystemExit as exc: code = exc.code if isinstance(exc.code, int) else str(exc.code) if code != 0: diff --git a/tests/e2e/test_install_pkgmgr_shallow.py b/tests/e2e/test_install_pkgmgr_shallow.py index c5c9778..ef0c035 100644 --- a/tests/e2e/test_install_pkgmgr_shallow.py +++ b/tests/e2e/test_install_pkgmgr_shallow.py @@ -139,7 +139,7 @@ class TestIntegrationInstalPKGMGRShallow(unittest.TestCase): ] # Execute installation via main.py - runpy.run_module("main", run_name="__main__") + runpy.run_module("pkgmgr", run_name="__main__") # Debug: interactive shell test pkgmgr_help_debug() diff --git a/tests/e2e/test_list_commands.py b/tests/e2e/test_list_commands.py index a0c5312..152db1a 100644 --- a/tests/e2e/test_list_commands.py +++ b/tests/e2e/test_list_commands.py @@ -27,7 +27,7 @@ class TestIntegrationListCommands(unittest.TestCase): sys.argv = ["pkgmgr"] + args try: - runpy.run_module("main", run_name="__main__") + runpy.run_module("pkgmgr", run_name="__main__") except SystemExit as exc: code = exc.code if isinstance(exc.code, int) else str(exc.code) if code != 0: diff --git a/tests/e2e/test_make_commands.py b/tests/e2e/test_make_commands.py index 56a5091..8ca8ef1 100644 --- a/tests/e2e/test_make_commands.py +++ b/tests/e2e/test_make_commands.py @@ -44,7 +44,7 @@ class TestIntegrationMakeCommands(unittest.TestCase): sys.argv = ["pkgmgr"] + extra_args try: - runpy.run_module("main", run_name="__main__") + runpy.run_module("pkgmgr", run_name="__main__") except SystemExit as exc: code = exc.code if isinstance(exc.code, int) else str(exc.code) if code != 0: diff --git a/tests/e2e/test_mirror_commands.py b/tests/e2e/test_mirror_commands.py index a808463..62906a7 100644 --- a/tests/e2e/test_mirror_commands.py +++ b/tests/e2e/test_mirror_commands.py @@ -50,7 +50,7 @@ class TestIntegrationMirrorCommands(unittest.TestCase): try: with redirect_stdout(buffer), redirect_stderr(buffer): - runpy.run_module("main", run_name="__main__") + runpy.run_module("pkgmgr", run_name="__main__") except SystemExit as exc: code = exc.code if isinstance(exc.code, int) else None if code not in (0, None): diff --git a/tests/e2e/test_path_commands.py b/tests/e2e/test_path_commands.py index 2304fd3..0966bb6 100644 --- a/tests/e2e/test_path_commands.py +++ b/tests/e2e/test_path_commands.py @@ -50,7 +50,7 @@ class TestPathCommandsE2E(unittest.TestCase): try: # Capture stdout while running the CLI entry point. with redirect_stdout(buffer): - runpy.run_module("main", run_name="__main__") + runpy.run_module("pkgmgr", run_name="__main__") except SystemExit as exc: # Determine the exit code (int or string) exit_code = exc.code diff --git a/tests/e2e/test_proxy_commands.py b/tests/e2e/test_proxy_commands.py index 4dbbed2..b97fbd2 100644 --- a/tests/e2e/test_proxy_commands.py +++ b/tests/e2e/test_proxy_commands.py @@ -27,7 +27,7 @@ class TestIntegrationProxyCommands(unittest.TestCase): sys.argv = ["pkgmgr"] + args try: - runpy.run_module("main", run_name="__main__") + runpy.run_module("pkgmgr", run_name="__main__") except SystemExit as exc: code = exc.code if isinstance(exc.code, int) else str(exc.code) if code != 0: diff --git a/tests/e2e/test_release_commands.py b/tests/e2e/test_release_commands.py index 8183cc5..564861c 100644 --- a/tests/e2e/test_release_commands.py +++ b/tests/e2e/test_release_commands.py @@ -44,7 +44,7 @@ class TestIntegrationReleaseCommand(unittest.TestCase): try: # argv[0] is the program name; the rest are CLI arguments. sys.argv = ["pkgmgr"] + list(extra_args) - runpy.run_module("main", run_name="__main__") + runpy.run_module("pkgmgr", run_name="__main__") finally: sys.argv = original_argv @@ -152,7 +152,7 @@ class TestIntegrationReleaseCommand(unittest.TestCase): # argparse will call sys.exit(), so we expect a SystemExit here. with contextlib.redirect_stdout(buf), contextlib.redirect_stderr(buf): with self.assertRaises(SystemExit) as cm: - runpy.run_module("main", run_name="__main__") + runpy.run_module("pkgmgr", run_name="__main__") finally: sys.argv = original_argv diff --git a/tests/e2e/test_tools_commands.py b/tests/e2e/test_tools_commands.py index c446511..e134ce5 100644 --- a/tests/e2e/test_tools_commands.py +++ b/tests/e2e/test_tools_commands.py @@ -55,7 +55,7 @@ class TestIntegrationToolsCommands(unittest.TestCase): sys.argv = ["pkgmgr"] + extra_args try: - runpy.run_module("main", run_name="__main__") + runpy.run_module("pkgmgr", run_name="__main__") except SystemExit as exc: code = exc.code if isinstance(exc.code, int) else str(exc.code) if code != 0: diff --git a/tests/e2e/test_tools_help.py b/tests/e2e/test_tools_help.py index 3145b53..ea13308 100644 --- a/tests/e2e/test_tools_help.py +++ b/tests/e2e/test_tools_help.py @@ -18,14 +18,6 @@ import sys import unittest from typing import List - -# Resolve project root (the repo where main.py lives, e.g. /src) -PROJECT_ROOT = os.path.abspath( - os.path.join(os.path.dirname(__file__), "..", "..") -) -MAIN_PATH = os.path.join(PROJECT_ROOT, "main.py") - - def _run_main(argv: List[str]) -> None: """ Helper to run main.py with the given argv. @@ -40,7 +32,7 @@ def _run_main(argv: List[str]) -> None: try: sys.argv = ["pkgmgr"] + argv try: - runpy.run_path(MAIN_PATH, run_name="__main__") + runpy.run_module("pkgmgr", run_name="__main__") except SystemExit as exc: # argparse uses this for --help # SystemExit.code can be int, str or None; for our purposes: code = exc.code diff --git a/tests/e2e/test_version_commands.py b/tests/e2e/test_version_commands.py index 171ca3d..26e1974 100644 --- a/tests/e2e/test_version_commands.py +++ b/tests/e2e/test_version_commands.py @@ -130,7 +130,7 @@ class TestIntegrationVersionCommands(unittest.TestCase): sys.argv = ["pkgmgr", "version"] + extra_args try: - runpy.run_module("main", run_name="__main__") + runpy.run_module("pkgmgr", run_name="__main__") except SystemExit as exc: code = exc.code if isinstance(exc.code, int) else str(exc.code) if code != 0: