From 9639e2ccf22c584154be87a431b1d878af054210 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bern=C3=A1t=20G=C3=A1bor?= Date: Sun, 3 Dec 2023 10:16:01 -0800 Subject: [PATCH] Pull version from VCS to simplify release and build dynamic docs via mkdocs plugin (#1130) Co-authored-by: Jason Lam --- .github/workflows/tests.yml | 97 ++++++++--- .gitignore | 35 ++-- .pre-commit-config.yaml | 18 +- CHANGELOG.md | 1 + CONTRIBUTING.md | 93 ++-------- mkdocs.yml | 8 + noxfile.py | 209 +++++++---------------- pyproject.toml | 11 +- scripts/gen_doc_pages.py | 53 ++++++ scripts/generate_docs.py | 60 ------- scripts/pipx_postrelease.py | 86 ---------- scripts/pipx_prerelease.py | 59 ------- scripts/pipx_release.py | 20 --- {templates => scripts/templates}/docs.md | 0 src/pipx/main.py | 2 +- src/pipx/version.py | 2 - 16 files changed, 242 insertions(+), 512 deletions(-) create mode 100644 scripts/gen_doc_pages.py delete mode 100644 scripts/generate_docs.py delete mode 100644 scripts/pipx_postrelease.py delete mode 100644 scripts/pipx_prerelease.py delete mode 100644 scripts/pipx_release.py rename {templates => scripts/templates}/docs.md (100%) delete mode 100644 src/pipx/version.py diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5e3fcade8a..218094bc24 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,6 +1,7 @@ name: tests on: + workflow_dispatch: push: pull_request: schedule: @@ -41,12 +42,26 @@ jobs: - name: Install nox run: python -m pip install nox - name: Execute Tests - run: nox --non-interactive --session tests-${{ matrix.python-version }} + run: nox --error-on-missing-interpreters --non-interactive --session tests-${{ matrix.python-version }} + man: + name: Build man page + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ env.default-python }} + uses: actions/setup-python@v4 + with: + python-version: ${{ env.default-python }} + cache: "pip" + - name: Install nox + run: python -m pip install nox + - name: Build man page + run: nox --error-on-missing-interpreters --non-interactive --session build_man + - name: Show man page + run: man -l pipx.1 - pypi-publish: - name: Publish pipx to PyPI on release - if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') - needs: [tests] + zipapp: + name: Build zipapp runs-on: ubuntu-latest steps: - name: Checkout ${{ github.ref }} @@ -58,19 +73,26 @@ jobs: cache: "pip" - name: Install nox run: pip install nox - - name: Build sdist and wheel - run: nox --error-on-missing-interpreters --non-interactive --session build - - name: Publish to PyPi - uses: pypa/gh-action-pypi-publish@v1.8.11 + - name: Build zipapp + run: nox --error-on-missing-interpreters --non-interactive --session zipapp + - name: Test zipapp by installing black + run: python ./pipx.pyz install black + - uses: actions/upload-artifact@v3 with: - user: __token__ - password: ${{ secrets.pypi_password }} + name: pipx.pyz + path: pipx.pyz + retention-days: 3 - build-zipapp: - name: Build zipapp + pypi-publish: + name: Publish pipx to PyPI on release if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') - needs: [tests] + needs: [tests, man, zipapp] runs-on: ubuntu-latest + environment: + name: release + url: https://pypi.org/p/pipx + permissions: + id-token: write steps: - name: Checkout ${{ github.ref }} uses: actions/checkout@v4 @@ -79,15 +101,44 @@ jobs: with: python-version: ${{ env.default-python }} cache: "pip" - - name: Install shiv - run: pip install shiv - - name: Build zipapp - run: shiv -c pipx -o ./pipx.pyz git+https://github.com/pypa/pipx@${{ github.ref_name }} - - name: Show zipapp version - run: python ./pipx.pyz --version - - name: Test zipapp by installing black - run: python ./pipx.pyz install black - - name: Upload to releases + - name: Install nox + run: pip install nox + - name: Build sdist and wheel + run: nox --error-on-missing-interpreters --non-interactive --session build + - name: Publish to PyPi + uses: pypa/gh-action-pypi-publish@v1.8.11 + + upload-zipapp: + name: Upload zipapp to GitHub Release on release + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') + needs: [tests, man, zipapp] + runs-on: ubuntu-latest + steps: + - uses: actions/download-artifact@v3 + with: + name: pipx.pyz + - name: Upload to release uses: softprops/action-gh-release@v1 with: files: pipx.pyz + + bump-changelog: + name: Bump changelog on release + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') + needs: [pypi-publish, upload-zipapp] + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout ${{ github.ref }} + uses: actions/checkout@v4 + - name: Extract release tag + run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV + - name: Update changelog + run: echo -e "## dev\n\n## $RELEASE_VERSION\n$(tail -n +2 CHANGELOG.md)" > CHANGELOG.md + - name: Commit and push change + run: | + git config --global user.name 'Github Actions' + git config --global user.email 'gh-action@users.noreply.github.com' + git commit -am "Bump changelog for $RELEASE_VERSION" + git push diff --git a/.gitignore b/.gitignore index fbc7e56f46..3389d5f368 100644 --- a/.gitignore +++ b/.gitignore @@ -1,22 +1,17 @@ -/.nox/ -/.coverage -*egg* -.mypy_cache -.vscode -build -dist -activate -__pypackages__ -venv -.venv -.DS_Store -.tox +/.*_cache +/build +/dist +/src/pipx/version.py +/noxfile.py +/.nox +*.py[co] __pycache__ -site -docs/docs.md -pipx.1 -.pipx_tests +/site /.coverage* -/testdata -!/testdata/empty_project -/.idea +/.pipx_tests +/testdata/tests_packages/*.txt +/pipx.pyz +*.egg-info +build +*.whl +/pipx.1 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 15f7be99b2..9795682113 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,9 +1,3 @@ -# See https://pre-commit.com for more information -# See https://pre-commit.com/#installation for installation instructions -# See https://pre-commit.com/hooks.html for more hooks -# -# use `git commit --no-verify` to disable git hooks for this commit - repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.5.0 @@ -22,15 +16,15 @@ repos: - id: ruff-format - id: ruff args: [ "--fix", "--unsafe-fixes", "--exit-non-zero-on-fix"] -# mypy args: -# must include --ignore-missing-imports for mypy. It is included by default -# if no arguments are supplied, but we must supply it ourselves since we -# specify args -# cannot use --warn-unused-ignores because it conflicts with -# --ignore-missing-imports - repo: https://github.com/pre-commit/mirrors-mypy rev: v1.7.1 hooks: - id: mypy args: ['--warn-unused-ignores', '--strict-equality','--no-implicit-optional', '--check-untyped-defs'] exclude: 'testdata/test_package_specifier/local_extras/setup.py' + additional_dependencies: + - "mkdocs-gen-files" + - "nox" + - "packaging>=20" + - "platformdirs>=2.1" + - "tomli; python_version < '3.11'" diff --git a/CHANGELOG.md b/CHANGELOG.md index 53c0b7e48f..469287b1bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## dev +- The project version number is now dynamic and generated from the VCS at build time - [docs] Add additonal example for --pip-args option, to docs/examples.md ## 1.3.1 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6ec0a5cdc6..d743492915 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,26 +11,6 @@ To run the pipx executable from your source tree during development, run pipx fr python src/pipx --version ``` -## Pre-commit - -The use of [pre-commit](https://pre-commit.com/) is recommended. It can show all and fix some lint errors before commit, -saving you the trouble of finding out later that it failed CI Lint errors, and saving you from having to run -`nox -s lint` separately. - -In the pipx git repository is a `.pre-commit-config.yaml` configuration file tailored just for pipx and its lint -requirements. To use pre-commit in your clone of the pipx repository, you need to do the following **one-time setup -procedure**: - -1. Install pre-commit using `pipx install pre-commit` -2. In the top level directory of your clone of the pipx repository, execute `pre-commit install` - -Afterwards whenever you commit in this repository, it will first run pipx's personalized lint checks. If it makes a fix -to a file (e.g. using `black` or `isort`), you will need to `git add` that file again before committing it again. If it -can't fix your commit itself, it will tell you what's wrong, and you can fix it manually before re-adding the edited -files and committing again. - -If for some reason you want to commit and skip running pre-commit, you can use the switch `git commit --no-verify`. - ## Running Tests ### Setup @@ -88,8 +68,6 @@ At the time of this writing, the output looks like this * build_docs - watch_docs * build_man -- pre_release -- post_release - create_test_package_list-3.12 - create_test_package_list-3.11 - create_test_package_list-3.10 @@ -97,6 +75,11 @@ At the time of this writing, the output looks like this - create_test_package_list-3.8 ``` +### Creating a developer environment + +For developing the tool (and to attach to your IDE) we recommend creating a Python environment via +`nox -s develop-3.12`, afterwards use the Python interpreter available under `.nox/develop-3.12/bin/python`. + ### Unit Tests To run unit tests in Python3.12, you can run @@ -123,13 +106,13 @@ nox -s tests-3.12 Running the unit tests requires a directory `.pipx_tests/package_cache` to be populated from a fixed list of package distribution files (wheels or source files). If you have network access, `nox -s tests` automatically makes sure this -directory is populated (including downloading files if necessary) as a first step. Thus if you are running the tests +directory is populated (including downloading files if necessary) as a first step. Thus, if you are running the tests with network access, you can ignore the rest of this section. If, however, you wish to run tests offline without the need for network access, you can populate `.pipx_tests/package_cache` yourself manually beforehand when you do have network access. -#### Populating the cache directory using nox +### Populating the cache directory using nox To populate `.pipx_tests/package_cache` manually using nox, execute: @@ -140,19 +123,10 @@ nox -s refresh_packages_cache This will sequence through available python executable versions to populate the cache directory for each version of python on your platform. -#### Populating the cache directory without nox - -An alternate method to populate `.pipx_tests/package_cache` without nox is to execute: - -``` -mkdir -p .pipx_tests/package_cache -python3 scripts/update_package_cache.py testdata/tests_packages .pipx_tests/package_cache -``` - -You must do this using every python version that you wish to use to run the tests. - ### Lint Tests +Linting is done via `pre-commit`, setting it up and running it can be done via `nox` by typing: + ``` nox -s lint ``` @@ -181,16 +155,13 @@ Finally, check-in the new or modified list files in the directory `testdata/test ## Testing pipx on Continuous Integration builds -When you push a new git branch, tests will automatically be run against your code as defined in -`.github/workflows/on-push.yml`. +Upon opening pull requests GitHub Actions will automatically trigger. ## Building Documentation `pipx` autogenerate API documentation, and also uses templates. -When updating pipx docs, make sure you are either modifying a file in the `templates` directory, or the `docs` -directory. If in the `docs` directory, make sure the file was not autogenerated from the `templates` directory. -Autogenerated files have a note at the top of the file. +When updating pipx docs, make sure you are modifying the `docs` directory. You can generate the documentation with @@ -207,44 +178,12 @@ To preview changes, including live reloading, open another terminal and run nox -s watch_docs ``` -### Publishing Doc Changes to GitHub pages - -``` -nox -s publish_docs -``` - ## Releasing New `pipx` Versions -### Pre-release - -First, make sure the changelog is complete. Next decide what the next version number will be. Then, from a clone of the -main pypa pipx repo (not a fork) execute: - -``` -nox -s pre_release -``` - -Enter the new version number when asked. When the script is finished, check the diff it produces. If the diff looks -correct, commit the changes as the script instructs, and push the result. - -The script will modify `src/pipx/version.py` to contain the new version, and also update the changelog -(`docs/changelog.md`) to specify the new version. - -### Release - -To publish to PyPI simply create a "published" release on Github. This will trigger Github workflows that both publish -the pipx version to PyPI and publish the pipx documentation to the pipx website. - -### Post-release - -From a clone of the main pypa pipx repo (not a fork) execute: - -``` -nox -s post_release -``` +To publish to PyPI simply create a "published" release on GitHub. This will trigger GitHub workflows that publishes: -When the script is finished, check the diff it produces. If the diff looks correct, commit the changes as the script -instructs, and push the result. +- the pipx version to PyPI, +- the documentation to readthedocs, +- the `zipapp` to the GitHub release created. -This will update pipx's version in `src/pipx/version.py` by adding a suffix `"dev0"` for unreleased development, and -will update the changelog to start a new section at the top entitled `dev`. +No need for any other pre or post publish steps. diff --git a/mkdocs.yml b/mkdocs.yml index c31254acc1..5081e08e3e 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -40,3 +40,11 @@ nav: markdown_extensions: - admonition # note blocks, warning blocks -- https://github.com/mkdocs/mkdocs/issues/1659 + +plugins: + - search: + lang: en + enabled: true + - gen-files: + scripts: + - scripts/gen_doc_pages.py diff --git a/noxfile.py b/noxfile.py index dd39101262..2a09c7b927 100644 --- a/noxfile.py +++ b/noxfile.py @@ -2,18 +2,13 @@ import sys from pathlib import Path -import nox # type: ignore +import nox PYTHON_ALL_VERSIONS = ["3.12", "3.11", "3.10", "3.9", "3.8"] PYTHON_DEFAULT_VERSION = "3.12" -DOC_DEPENDENCIES = [".", "jinja2", "mkdocs", "mkdocs-material"] -MAN_DEPENDENCIES = [".", "argparse-manpage[setuptools]"] -LINT_DEPENDENCIES = [ - "mypy==1.7.1", - "packaging>=20.0", - "ruff==0.1.6", - "types-jinja2", -] +DOC_DEPENDENCIES = ["jinja2", "mkdocs", "mkdocs-material", "mkdocs-gen-files"] +MAN_DEPENDENCIES = ["argparse-manpage[setuptools]"] +TEST_DEPENDENCIES = ["pytest", "pypiserver[passlib]", 'setuptools; python_version>="3.12"', "pytest-cov"] # Packages whose dependencies need an intact system PATH to compile # pytest setup clears PATH. So pre-build some wheels to the pip cache. PREBUILD_PACKAGES = {"all": ["jupyter==1.0.0"], "macos": [], "unix": [], "win": []} @@ -21,23 +16,14 @@ PIPX_TESTS_PACKAGE_LIST_DIR = Path("testdata/tests_packages") # Platform logic -if sys.platform == "darwin": - PLATFORM = "macos" -elif sys.platform == "win32": - PLATFORM = "win" -else: - PLATFORM = "unix" - -# Set nox options -if PLATFORM == "win": - # build_docs fail on Windows, even if `chcp.com 65001` is used - nox.options.sessions = ["tests", "lint", "build_man"] -else: - nox.options.sessions = ["tests", "lint", "build_docs", "build_man"] +PLATFORM = {"darwin": "macos", "win32": "win"}.get(sys.platform, "unix") +nox.options.sessions = ["tests", "lint", "build_docs", "zipapp"] +if PLATFORM != "win": # build_docs fail on Windows, even if `chcp.com 65001` is used + nox.options.sessions.append("build_man") nox.options.reuse_existing_virtualenvs = True -def prebuild_wheels(session, prebuild_dict): +def prebuild_wheels(session: nox.Session, prebuild_dict) -> None: prebuild_list = prebuild_dict.get("all", []) + prebuild_dict.get(PLATFORM, []) session.install("wheel") @@ -48,196 +34,127 @@ def prebuild_wheels(session, prebuild_dict): session.run("pip", "wheel", f"--wheel-dir={wheel_dir}", prebuild, silent=True) -def has_changes(): - status = ( - subprocess.run("git status --porcelain", shell=True, check=True, stdout=subprocess.PIPE).stdout.decode().strip() - ) - return len(status) > 0 - - -def get_branch(): - return ( - subprocess.run( - "git rev-parse --abbrev-ref HEAD", - shell=True, - check=True, - stdout=subprocess.PIPE, - ) - .stdout.decode() - .strip() - ) - - -def on_main_no_changes(session): - if has_changes(): +def on_main_no_changes(session: nox.Session) -> None: + if len(subprocess.check_output(["git", "status", "--porcelain"], text=True).strip()) > 0: # has changes session.error("All changes must be committed or removed before publishing") - branch = get_branch() + branch = subprocess.check_output(["git", "rev-parse", "--abbrev-ref", "HEAD"], text=True).strip() if branch != "main": session.error(f"Must be on 'main' branch. Currently on {branch!r} branch") -@nox.session(python=PYTHON_ALL_VERSIONS) -def refresh_packages_cache(session): - """Populate .pipx_tests/package_cache""" - print("Updating local tests package spec file cache...") - PIPX_TESTS_CACHE_DIR.mkdir(exist_ok=True, parents=True) - session.run( - "python", - "scripts/update_package_cache.py", - str(PIPX_TESTS_PACKAGE_LIST_DIR), - str(PIPX_TESTS_CACHE_DIR), - ) - - -def tests_with_options(session, net_pypiserver): - session.run("python", "-m", "pip", "install", "--upgrade", "pip") +def tests_with_options(session: nox.Session, *, net_pypiserver: bool) -> None: prebuild_wheels(session, PREBUILD_PACKAGES) - session.install("-e", ".", "pytest", "pytest-cov") - tests = session.posargs or ["tests"] - + session.install("-e", ".", *TEST_DEPENDENCIES) + test_dir = session.posargs or ["tests"] if net_pypiserver: pypiserver_option = ["--net-pypiserver"] else: - session.install("pypiserver[passlib]", 'setuptools; python_version>="3.12"') refresh_packages_cache(session) pypiserver_option = [] - - session.run("pytest", *pypiserver_option, "--cov=pipx", "--cov-report=", *tests) + session.run("pytest", *pypiserver_option, "--cov=pipx", "--cov-report=", *test_dir) session.notify("cover") @nox.session(python=PYTHON_ALL_VERSIONS) -def tests_internet(session): - """Tests using internet pypi only""" - tests_with_options(session, net_pypiserver=True) - - -@nox.session(python=PYTHON_ALL_VERSIONS) -def tests(session): +def tests(session: nox.Session) -> None: """Tests using local pypiserver only""" tests_with_options(session, net_pypiserver=False) -@nox.session(python=PYTHON_ALL_VERSIONS) -def test_all_packages(session): - session.run("python", "-m", "pip", "install", "--upgrade", "pip") - session.install("-e", ".", "pytest") - tests = session.posargs or ["tests"] - session.run( - "pytest", - "-v", - "--tb=no", - "--show-capture=no", - "--net-pypiserver", - "--all-packages", - *tests, - ) - - @nox.session -def cover(session): +def cover(session: nox.Session) -> None: """Coverage analysis""" - session.run("python", "-m", "pip", "install", "--upgrade", "pip") session.install("coverage") session.run("coverage", "report", "--show-missing", "--fail-under=70") session.run("coverage", "erase") +@nox.session +def zipapp(session: nox.Session) -> None: + """Build a zipapp with shiv""" + session.install("shiv") + session.run("shiv", "-c", "pipx", "-o", "./pipx.pyz", ".") + session.run("./pipx.pyz", "--version", external=True) + + @nox.session(python=PYTHON_DEFAULT_VERSION) def lint(session): - session.run("python", "-m", "pip", "install", "--upgrade", "pip") session.run("python", "-m", "pip", "install", "pre-commit") session.run("pre-commit", "run", "--all-files") @nox.session(python=PYTHON_ALL_VERSIONS) -def develop(session): +def develop(session: nox.Session) -> None: + """Create a develop environment.""" session.run("python", "-m", "pip", "install", "--upgrade", "pip") - session.install(*DOC_DEPENDENCIES, *LINT_DEPENDENCIES) - session.install("-e", ".", "pytest", "pypiserver[passlib]", 'setuptools; python_version>="3.12"') + session.install(*DOC_DEPENDENCIES, *TEST_DEPENDENCIES, *MAN_DEPENDENCIES, "-e", ".", "nox") @nox.session(python=PYTHON_DEFAULT_VERSION) -def build(session): - session.run("python", "-m", "pip", "install", "--upgrade", "pip") +def build(session: nox.Session) -> None: + """Build the wheel and source distribution for the project.""" session.install("build") session.run("rm", "-rf", "dist", "build", external=True) session.run("python", "-m", "build") @nox.session(python=PYTHON_DEFAULT_VERSION) -def publish(session): +def publish(session: nox.Session) -> None: + """Publish the package to PyPI.""" on_main_no_changes(session) - session.run("python", "-m", "pip", "install", "--upgrade", "pip") session.install("twine") build(session) - print("REMINDER: Has the changelog been updated?") session.run("python", "-m", "twine", "upload", "dist/*") @nox.session(python=PYTHON_DEFAULT_VERSION) -def build_docs(session): +def build_docs(session: nox.Session) -> None: site_dir = session.posargs or ["site/"] - session.run("python", "-m", "pip", "install", "--upgrade", "pip") - session.install(*DOC_DEPENDENCIES) + session.install(*DOC_DEPENDENCIES, ".") session.env["PIPX__DOC_DEFAULT_PYTHON"] = "typically the python used to execute pipx" - session.run("python", "scripts/generate_docs.py") session.run("mkdocs", "build", "--strict", "--site-dir", *site_dir) @nox.session(python=PYTHON_DEFAULT_VERSION) -def watch_docs(session): - session.install(*DOC_DEPENDENCIES) - session.run("python", "scripts/generate_docs.py") +def watch_docs(session: nox.Session) -> None: + session.install(*DOC_DEPENDENCIES, ".") session.run("mkdocs", "serve", "--strict") @nox.session(python=PYTHON_DEFAULT_VERSION) -def build_man(session): - session.run("python", "-m", "pip", "install", "--upgrade", "pip") - session.install(*MAN_DEPENDENCIES) +def build_man(session: nox.Session) -> None: + session.install(*MAN_DEPENDENCIES, ".") session.env["PIPX__DOC_DEFAULT_PYTHON"] = "typically the python used to execute pipx" session.run("python", "scripts/generate_man.py") -@nox.session(python=PYTHON_DEFAULT_VERSION) -def pre_release(session): - on_main_no_changes(session) - session.run("python", "-m", "pip", "install", "--upgrade", "pip") - session.install("mypy") - if session.posargs: - new_version = session.posargs[0] - else: - new_version = input("Enter new version: ") - session.run("python", "scripts/pipx_prerelease.py", new_version) - session.run("git", "--no-pager", "diff", external=True) - print("") - session.log( - "If `git diff` above looks ok, execute the following command:\n\n" - f" git commit -a -m 'Pre-release {new_version}.'\n" - ) +@nox.session(python=PYTHON_ALL_VERSIONS) +def refresh_packages_cache(session: nox.Session) -> None: + """Populate .pipx_tests/package_cache""" + print("Updating local tests package spec file cache...") + PIPX_TESTS_CACHE_DIR.mkdir(exist_ok=True, parents=True) + script = "scripts/update_package_cache.py" + session.run("python", script, str(PIPX_TESTS_PACKAGE_LIST_DIR), str(PIPX_TESTS_CACHE_DIR)) -@nox.session(python=PYTHON_DEFAULT_VERSION) -def post_release(session): - on_main_no_changes(session) +@nox.session(python=PYTHON_ALL_VERSIONS) +def create_test_package_list(session: nox.Session) -> None: + """Update the list of packages needed for running the test suite.""" session.run("python", "-m", "pip", "install", "--upgrade", "pip") - session.install("mypy") - session.run("python", "scripts/pipx_postrelease.py") - session.run("git", "--no-pager", "diff", external=True) - print("") - session.log( - "If `git diff` above looks ok, execute the following command:\n\n" " git commit -a -m 'Post-release.'\n" - ) + output_dir = session.posargs[0] if session.posargs else str(PIPX_TESTS_PACKAGE_LIST_DIR) + primary = str(PIPX_TESTS_PACKAGE_LIST_DIR / "primary_packages.txt") + session.run("python", "scripts/list_test_packages.py", primary, output_dir) @nox.session(python=PYTHON_ALL_VERSIONS) -def create_test_package_list(session): - session.run("python", "-m", "pip", "install", "--upgrade", "pip") - output_dir = session.posargs[0] if session.posargs else str(PIPX_TESTS_PACKAGE_LIST_DIR) - session.run( - "python", - "scripts/list_test_packages.py", - str(PIPX_TESTS_PACKAGE_LIST_DIR / "primary_packages.txt"), - output_dir, - ) +def tests_internet(session: nox.Session) -> None: + """Tests using internet pypi only""" + tests_with_options(session, net_pypiserver=True) + + +@nox.session(python=PYTHON_ALL_VERSIONS) +def test_all_packages(session: nox.Session) -> None: + """A more in depth but slower test suite.""" + session.install("-e", ".", *TEST_DEPENDENCIES) + test_dir = session.posargs or ["tests"] + session.run("pytest", "-v", "--tb=no", "--show-capture=no", "--net-pypiserver", "--all-packages", *test_dir) diff --git a/pyproject.toml b/pyproject.toml index 509c8597d1..f7f8c2b549 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,8 @@ [build-system] build-backend = "hatchling.build" requires = [ - "hatchling>=0.15", + "hatch-vcs>=0.4", + "hatchling>=1.18", ] [project] @@ -48,9 +49,9 @@ urls."Source Code" = "https://github.com/pypa/pipx" scripts.pipx = "pipx.main:cli" [tool.hatch] -version.source = "code" -version.path = "src/pipx/version.py" +build.hooks.vcs.version-file = "src/pipx/version.py" build.targets.sdist.include = ["/src", "/logo.png", "/pipx_demo.gif", "/*.md"] +version.source = "vcs" [tool.ruff] line-length = 121 @@ -81,9 +82,7 @@ markers = ["all_packages: test install with maximum number of packages"] show_error_codes = true overrides = [ { module = [ - "packaging.*", - "platformdirs", + "pipx.version", "pycowsay.*", - "jinja2", ], ignore_missing_imports = true }, ] diff --git a/scripts/gen_doc_pages.py b/scripts/gen_doc_pages.py new file mode 100644 index 0000000000..d8e6f1388c --- /dev/null +++ b/scripts/gen_doc_pages.py @@ -0,0 +1,53 @@ +import os +import sys +from pathlib import Path +from subprocess import check_output +from typing import Optional + +import mkdocs_gen_files +from jinja2 import Environment, FileSystemLoader + +from pipx.main import __version__ + + +def get_help(cmd: Optional[str]) -> str: + base = ["pipx"] + args = base + ([cmd] if cmd else []) + ["--help"] + env_patch = os.environ.copy() + env_patch["PATH"] = os.pathsep.join([str(Path(sys.executable).parent)] + env_patch["PATH"].split(os.pathsep)) + content = check_output(args, text=True, env=env_patch) + content = content.replace(str(Path("~").expanduser()), "~") + return f""" +``` +{" ".join(args[2:])} +{content} +``` +""" + + +params = { + "runpip": get_help("runpip"), + "install": get_help("install"), + "upgrade": get_help("upgrade"), + "upgradeall": get_help("upgrade-all"), + "inject": get_help("inject"), + "uninstall": get_help("uninstall"), + "uninstallall": get_help("uninstall-all"), + "reinstallall": get_help("reinstall-all"), + "list": get_help("list"), + "run": get_help("run"), + "version": __version__, + "usage": get_help(None), +} + + +env = Environment(loader=FileSystemLoader(Path(__file__).parent / "templates")) + +with mkdocs_gen_files.open("docs.md", "wt") as file_handler: + file_handler.write(env.get_template("docs.md").render(**params)) + file_handler.write("\n") + +with mkdocs_gen_files.open("changelog.md", "rt") as file_handler: + text = file_handler.read() +with mkdocs_gen_files.open("changelog.md", "wt") as file_handler: + file_handler.write(f"## {__version__}" + text[len("## dev") :]) diff --git a/scripts/generate_docs.py b/scripts/generate_docs.py deleted file mode 100644 index ef0b9c05b5..0000000000 --- a/scripts/generate_docs.py +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/env python3 - -import os -import subprocess -import textwrap -from typing import Optional - -from jinja2 import Environment, FileSystemLoader - -from pipx.main import __version__ - - -def get_help(pipxcmd: Optional[str]) -> str: - if pipxcmd: - cmd = ["pipx", pipxcmd, "--help"] - else: - cmd = ["pipx", "--help"] - - helptext = ( - subprocess.run(cmd, stdout=subprocess.PIPE, check=True).stdout.decode().replace(os.path.expanduser("~"), "~") - ) - return f""" -``` -{" ".join(cmd)} -{helptext} -``` -""" - - -params = { - "usage": get_help(None), - "runpip": get_help("runpip"), - "install": get_help("install"), - "upgrade": get_help("upgrade"), - "upgradeall": get_help("upgrade-all"), - "inject": get_help("inject"), - "uninstall": get_help("uninstall"), - "uninstallall": get_help("uninstall-all"), - "reinstallall": get_help("reinstall-all"), - "list": get_help("list"), - "run": get_help("run"), - "version": __version__, -} - -warning = textwrap.dedent( - """ - - """ -).lstrip() - -env = Environment(loader=FileSystemLoader("templates")) - -with open("docs/docs.md", "w") as f: - f.write(warning) - f.write(env.get_template("docs.md").render(**params)) - f.write("\n") diff --git a/scripts/pipx_postrelease.py b/scripts/pipx_postrelease.py deleted file mode 100644 index 17c72ef75d..0000000000 --- a/scripts/pipx_postrelease.py +++ /dev/null @@ -1,86 +0,0 @@ -import re -import sys -from pathlib import Path -from typing import List - -from pipx_release import copy_file_replace_line, python_mypy_ok - - -def fix_version_py(current_version_list: List[str]) -> bool: - version_code_file = Path("src/pipx/version.py") - new_version_code_file = Path("src/pipx/version.py.new") - # Version with "dev0" suffix precedes version without "dev0" suffix, - # so to follow previous version we must add to version number before - # appending "dev0". - new_version_list = current_version_list + ["1", '"dev0"'] - - copy_file_replace_line( - version_code_file, - new_version_code_file, - line_re=r"^\s*__version_info__\s*=", - new_line=f'__version_info__ = ({", ".join(new_version_list)})', - ) - if python_mypy_ok(new_version_code_file): - new_version_code_file.rename(version_code_file) - return True - else: - print(f"Aborting: syntax error in {new_version_code_file}") - return False - - -def fix_changelog() -> bool: - changelog_file = Path("docs/changelog.md") - new_changelog_file = Path("docs/changelog.new") - - old_version_fh = changelog_file.open("r") - new_version_fh = new_changelog_file.open("w") - new_version_fh.write("dev\n\n\n") - for line in old_version_fh: - new_version_fh.write(line) - old_version_fh.close() - new_version_fh.close() - - new_changelog_file.rename(changelog_file) - - return True - - -def get_current_version() -> List[str]: - version_code_file = Path("src/pipx/version.py") - version_fh = version_code_file.open("r") - - version = None - for line in version_fh: - version_re = re.search(r"^\s*__version_info__\s*=\s*\(([^)]+)\)", line) - if version_re: - version = version_re.group(1) - - if version is not None: - return version.split(", ") - else: - return [] - - -def post_release() -> int: - current_version_list = get_current_version() - if not current_version_list: - return 1 - - if fix_version_py(current_version_list) and fix_changelog(): - return 0 - else: - return 1 - - -def main(argv: List[str]) -> int: - return post_release() - - -if __name__ == "__main__": - try: - status = main(sys.argv) - except KeyboardInterrupt: - print("Stopped by Keyboard Interrupt", file=sys.stderr) - status = 130 - - sys.exit(status) diff --git a/scripts/pipx_prerelease.py b/scripts/pipx_prerelease.py deleted file mode 100644 index bff251a7a1..0000000000 --- a/scripts/pipx_prerelease.py +++ /dev/null @@ -1,59 +0,0 @@ -import sys -from pathlib import Path -from typing import List - -from pipx_release import copy_file_replace_line, python_mypy_ok - - -def fix_version_py(new_version: str) -> bool: - version_code_file = Path("src/pipx/version.py") - new_version_code_file = Path("src/pipx/version.py.new") - new_version_list = new_version.split(".") - - copy_file_replace_line( - version_code_file, - new_version_code_file, - line_re=r"^\s*__version_info__\s*=", - new_line=f'__version_info__ = ({", ".join(new_version_list)})', - ) - if python_mypy_ok(new_version_code_file): - new_version_code_file.rename(version_code_file) - return True - else: - print(f"Aborting: syntax error in {new_version_code_file}") - return False - - -def fix_changelog(new_version: str) -> bool: - changelog_file = Path("docs/changelog.md") - new_changelog_file = Path("docs/changelog.new") - - copy_file_replace_line(changelog_file, new_changelog_file, line_re=r"^\s*dev\s*$", new_line=new_version) - new_changelog_file.rename(changelog_file) - - return True - - -def pre_release(new_version: str) -> int: - if fix_version_py(new_version) and fix_changelog(new_version): - return 0 - else: - return 1 - - -def main(argv: List[str]) -> int: - if len(argv) > 1: - new_version = argv[1] - else: - new_version = input("Enter new version: ") - return pre_release(new_version) - - -if __name__ == "__main__": - try: - status = main(sys.argv) - except KeyboardInterrupt: - print("Stopped by Keyboard Interrupt", file=sys.stderr) - status = 130 - - sys.exit(status) diff --git a/scripts/pipx_release.py b/scripts/pipx_release.py deleted file mode 100644 index 65352d5525..0000000000 --- a/scripts/pipx_release.py +++ /dev/null @@ -1,20 +0,0 @@ -import re -import subprocess -from pathlib import Path - - -def python_mypy_ok(filepath: Path) -> bool: - mypy_proc = subprocess.run(["mypy", filepath], check=False) - return True if mypy_proc.returncode == 0 else False - - -def copy_file_replace_line(orig_file: Path, new_file: Path, line_re: str, new_line: str) -> None: - old_version_fh = orig_file.open("r") - new_version_fh = new_file.open("w") - for line in old_version_fh: - if re.search(line_re, line): - new_version_fh.write(new_line + "\n") - else: - new_version_fh.write(line) - old_version_fh.close() - new_version_fh.close() diff --git a/templates/docs.md b/scripts/templates/docs.md similarity index 100% rename from templates/docs.md rename to scripts/templates/docs.md diff --git a/src/pipx/main.py b/src/pipx/main.py index 8755fef94e..36211f7f69 100644 --- a/src/pipx/main.py +++ b/src/pipx/main.py @@ -15,7 +15,7 @@ from pathlib import Path from typing import Any, Callable, Dict, List, Optional -import argcomplete # type: ignore +import argcomplete import platformdirs from packaging.utils import canonicalize_name diff --git a/src/pipx/version.py b/src/pipx/version.py deleted file mode 100644 index 60a95db00e..0000000000 --- a/src/pipx/version.py +++ /dev/null @@ -1,2 +0,0 @@ -__version_info__ = (1, 3, 1, "dev1") -__version__ = ".".join(str(i) for i in __version_info__)