diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f263127..555164a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,17 +15,17 @@ jobs: contents: read steps: - - uses: actions/checkout@v6 - - uses: actions/setup-python@v6 + - uses: actions/checkout@v7 + - uses: actions/setup-python@v7 with: - python-version: "3.13" + python-version: "3.14" - name: Configure git run: | git config --global user.name "Tester" git config --global user.email "test@example.com" - - uses: astral-sh/setup-uv@v7 + - uses: astral-sh/setup-uv@v10.3.2 - run: uvx --with tox-uv tox -e py,docs,style - run: uv build - uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 13c1998..8710ae9 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -18,11 +18,11 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14", "3.15"] steps: - - uses: actions/checkout@v6 - - uses: actions/setup-python@v6 + - uses: actions/checkout@v7 + - uses: actions/setup-python@v7 with: python-version: ${{ matrix.python-version }} @@ -31,35 +31,7 @@ jobs: git config --global user.name "Tester" git config --global user.email "test@example.com" - - uses: astral-sh/setup-uv@v7 - - run: uvx --with tox-uv tox -e py - - - uses: coverallsapp/github-action@v2 - with: - file: .tox/test-reports/coverage.xml - flag-name: python-${{ matrix.python-version }} - parallel: true - - test-eol: - name: Test py${{ matrix.python-version }} (EOL) - runs-on: ubuntu-latest - - strategy: - matrix: - python-version: ["3.7"] # Only one ancient python needed for test coverage... - - steps: - - uses: actions/checkout@v6 - - uses: deadsnakes/action@v3.2.0 - with: - python-version: ${{ matrix.python-version }} - - - name: Configure git - run: | - git config --global user.name "Tester" - git config --global user.email "test@example.com" - - - uses: astral-sh/setup-uv@v7 + - uses: astral-sh/setup-uv@v10.3.2 - run: uvx --with tox-uv tox -e py - uses: coverallsapp/github-action@v2 @@ -70,7 +42,7 @@ jobs: coveralls-finish: name: Finish Coveralls - needs: [test, test-eol] + needs: [test] runs-on: ubuntu-latest steps: - name: Finish parallel build @@ -82,10 +54,10 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 - - uses: actions/setup-python@v6 + - uses: actions/checkout@v7 + - uses: actions/setup-python@v7 with: - python-version: "3.14" + python-version: "3.15" - - uses: astral-sh/setup-uv@v7 + - uses: astral-sh/setup-uv@v10.3.2 - run: uvx --with tox-uv tox -e docs,style diff --git a/AGENTS.md b/AGENTS.md index 604d050..e07cec5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,104 +1,22 @@ -# AGENTS.md +# setupmeta — repo context -## Purpose +setupmeta is a setuptools plugin: git-tag-based versioning and version bumps, plus auto-filled +`setup.py` metadata. It's self-hosted: our own `setup.py` uses it. -This file defines repo-specific guidance for coding agents working on `setupmeta`. -Use it as the first source of truth for how to make changes safely and consistently. +## Docs — read the relevant set before diving into the code -## Project Summary +- **[`docs/`](./docs/index.md)** — how the repo works inside (internal, not published). +- **[`README.rst`](./README.rst)** — the user guide, also published on PyPI as `long_description`. -- `setupmeta` primarily supports git-tag-based versioning and automated version bumps. -- Secondarily, it also: - - Provides custom setup commands: `explain`, `version`, and `check` (this will be phased out - as `setup.py` gets retired in favor of `pyproject.toml`). - - Helps keep `setup.py` files minimal by auto filling metadata and requirements (this will also - be sunset with `pyproject.toml`, as that format is declarative and doesn't lend itself to autofill). -- The project is self-hosted: `setup.py` uses `setupmeta` itself and has bootstrap behavior. +## Working notes -## Core Principles - -- Preserve backward compatibility for users relying on legacy `setup.py` workflows. -- Keep runtime dependencies minimal; prefer stdlib for runtime code. -- Favor explicit, test-covered behavior over cleverness. -- Keep documentation aligned with actual behavior and test scenarios. - -## Repository Layout - -- `setupmeta/`: main library code (`commands`, `model`, `scm`, `versioning`, etc.) -- `tests/`: unit tests + scenario replay tests -- `tests/scenarios/` and `examples/`: behavior fixtures with `expected.txt` snapshots -- `docs/`: user and contributor documentation -- `setup.py`: self-bootstrapping package definition -- `tox.ini`: test/lint/docs/coverage orchestration - -## Environment and Commands - -Use these commands from repo root: - -- Quick tests: `.venv/bin/pytest -q` -- Full test/lint/docs run: `tox` -- Single tox env: `tox -e py314` -- Fast compatibility matrix (old+new + coverage): `tox -e py39,py314,coverage` -- Style only: `tox -e style` -- Docs checks: `tox -e docs` -- Refresh scenario snapshots: `tox -e refreshscenarios` -- Manual command checks: - - `.venv/bin/python setup.py explain` - - `.venv/bin/python setup.py version` - - `.venv/bin/python setup.py check -q` - -Notes: - -- `tox.ini` pins `UV_CACHE_DIR` to `.tox/.uv-cache`, to help runs in sandboxed environments. -- `py37` tox target exists because it is the oldest Python still supported by this library. -- If `py37` is unavailable locally (common on macOS arm64), substitute the oldest available - interpreter and pair it with the newest one. - Example: system Python `3.9` + `3.14` via `tox -e py39,py314,coverage`. -- Intent: keep local runs fast while still exercising one older runtime and one modern runtime, - with `coverage combine` validating cross-env coverage data. - -## Code Change Expectations - -- Keep changes narrow and focused. -- Maintain Python compatibility targeted by this repo (including older supported versions). -- When changing behavior, update both tests and docs in the same change. -- Do not silently alter CLI output formats used by scenario snapshots unless intentional. -- If you touch versioning logic, verify: - - `tests/test_versioning.py` - - `tests/test_setup_py.py` - - scenario outputs that include `version`/`explain`. - -## Scenario Snapshot Rules - -- Scenario tests compare command output against `expected.txt`. -- If behavior changes are intentional: - 1. Regenerate snapshots (`tox -e refreshscenarios`). - 2. Review diffs in `tests/scenarios/*/expected.txt` and `examples/*/expected.txt`. - 3. Ensure docs and release notes explain user-visible changes. -- If behavior changes are not intentional, fix code/tests instead of accepting snapshot churn. - -## Documentation Rules - -- Keep `README.rst`, `docs/*.rst`, and example READMEs consistent with current behavior. -- Avoid documenting deprecated/removed commands as active features. -- Keep versioning docs aligned with current strategy defaults and command examples. -- Record user-visible changes in `HISTORY.rst`. - -## Safety and Review Checklist - -Before finalizing a change, verify: - -1. Tests pass for affected areas. -2. Formatting/lint checks pass. -3. Scenario snapshots are unchanged unless intentionally updated. -4. Docs are updated if behavior or commands changed. -5. No unrelated files were modified. - -## When to Ask for Clarification - -Ask the maintainer before proceeding if: - -- A change could break backward compatibility. -- Expected output changes are broad/noisy and intent is unclear. -- A docs update conflicts with tested behavior. -- A refactor touches bootstrap or version bump internals in `setup.py`, `setupmeta/hook.py`, `setupmeta/versioning.py`, or `setupmeta/scm.py`. +- Quick tests: `.venv/bin/pytest -q`; before finishing, `tox -e style,docs` and the oldest + + newest Python with coverage (see [contributing](./docs/contributing.md)). +- Runtime code is stdlib-only, and must run on the oldest Python `python_requires` allows. +- Preserve backward compatibility for legacy `setup.py` users. +- Behavior changes ship with tests, README updates, and reviewed snapshot diffs in the same change. +- Keep the upcoming version's section at the top of [CHANGELOG.md](./CHANGELOG.md) current with + user-visible changes. +- Ask the maintainer before changing bootstrap or version bump internals (`setup.py`, + `setupmeta/hook.py`, `setupmeta/versioning.py`, `setupmeta/scm.py`), or when a change could + break backward compatibility or produce broad snapshot churn. diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..6c865e8 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,475 @@ +# Changelog + +## 4.0.0 - 2026-10-27 + +### Changed + +- Require Python 3.10+ (dropped support for 3.7, 3.8 and 3.9), test with py3.15, + publish sdist with py3.14 + +- Only `v*.*` git tags are considered as version tags by default, tags without a leading `v` + are not used as a fallback anymore (use `version_tag` in `versioning` to configure this) + +- Docs: the README is now the complete user guide (versioning, auto-fill, requirements, + commands), with corrected versioning examples; `docs/` now holds internal docs only. + +- Release notes moved from `HISTORY.rst` to `CHANGELOG.md`. + +## 3.9.0 - 2026-02-17 + +- Test with py3.14, publish sdist with py3.13 + +- Removed commands: `entrypoints`, `cleanall` + +- Removed post-version-bump hook support (unused, unnecessary, and was not well documented) + +- Removed old `register` hook used with setuptools v50 + +- Removed support for pygradle-style versioning + +- Internal project modernizations: + + - Refactored usage of `subprocess.run()`, removed last left overs from the py2 days + + - use `uv`, `ruff`, enabled more linter rules, etc + +- Use [coveralls](https://coveralls.io/) for test coverage reporting + +## 3.8.0 - 2025-03-25 + +- Stopped using `pkg_resources`. + +- Removed `--deptree` flag from redefined `check` command, this way of visualizing + a dependency tree is not useful anymore, please use `uv pip tree` or [hdeps](https://pypi.org/project/hdeps/) instead. + +## 3.7.2 - 2025-03-20 + +- Adapted `CheckCommand` to setuptools v77.0.3 + +- Dropped support for Python 3.6 + +## 3.6.0 - 2023-12-04 + +- Use `--no-verify` with `git commit` when bumping a version, to support projects with a + `no-commit-to-branch` hook (OK to commit version bump to branch, but not other changes) + +## 3.5.2 - 2023-09-22 + +- Support setuptools 68.2+ + +## 3.5.1 - 2023-09-22 + +- Better support for PEP-517, extract `Requires-Dist:` from `PKG-INFO` if `requires.txt` is missing + +- `.egg` files are not published anymore, see https://blog.pypi.org/posts/2023-06-26-deprecate-egg-uploads/ + +## 3.4.1 - 2023-09-22 + +- Removed support for py2 + +- Look for `v*.*` git tags first, then `*.*` + +- Ignore git tags that do not pass regex `^v?[0-9]+\.[0-9]+`, + default to `0.0.0` in that case, and issue a WARNING + +## 3.4.0 - 2023-01-25 + +- Allow to configure format of git version tag via the advanced dict-form of `versioning`, + example: + + ``` + versioning={ + "main": "{major}.{minor}.{distance}", + "extra": "{dirty}", + "branches": "main", + "version_tag": "v*.*", + }, + ``` + +## 3.3.2 - 2022-06-01 + +- Protect against hook setup call in ancient setuptools versions + +## 3.3.1 - 2022-05-04 + +- Test with 3.10, publish sdist with 3.9 + +## 3.3.0 - 2021-06-14 + +- Do not auto-fill `tests_require` any more, it is deprecated, + see https://setuptools.readthedocs.io/en/latest/userguide/dependency_management.html#optional-dependencies + +## 3.2.0 - 2021-05-17 + +- Do not abstract `requirements.in` files any more, use them as-is (since they're intended to + be a spec for [pip-compile](https://pypi.org/project/pip-tools/)) + +- Next minor version of setupmeta will not auto-fill `tests_require` any more. + +## 3.1.0 - 2021-04-15 + +- Don't try and modify any git url from `requirements.txt`, it is too much of a moving target + and the whole thing is debated: https://github.com/pypa/pip/issues/5898 + +- Do not auto-fill any `dependency_links` at all + +## 3.0.0 - 2021-03-22 + +- Yield [PEP-440](https://www.python.org/dev/peps/pep-0440/#public-version-identifiers) compliant versions + +- Versions yielded by `setupmeta` 3.0 will differ from pre-3.0 versions: + + - Character `"+"` is used exclusively to demarcate the local part of the version + + - Character `"."` is used exclusively to demarcate local segments + (this is not configurable yet, and won't be unless by popular request) + + - The "main" part remains intact, except for `devcommit`, furthermore only known PEP-440 + "main version part" bits can ever be in the main part (anything that is mistakenly + specified to be in the main part gets automatically shifted to the local part) + + - "local" part is always shown now, no need to use `"!"` character (now deprecated), + except for `{devcommit}` and `{dirty}` markers, which will not lead to a local part + being shown when exactly on git tag. + + - `devcommit` yields `.devN` (number of commits since last tag) instead of a lone `.dev` + + - `build-id`, and any strategy showing the commit id, always shows the local part + + - The `separator` advanced argument is retired (deprecation warning if specified), + as is any separator other than `"+"` + +## 2.9.0 - 2021-03-15 + +- Don't auto-complete `classifiers` from `classifiers.txt` any more, + the added value of that is negligible (and potentially confusing) + +## 2.8.3 - 2021-01-07 + +- Don't warn when bumping for the first time (with no git tags yet) + +- Use the most common `setuptools.find_packages()` call (without arguments) + +- Moved repo to https://github.com/codrsquad + +## 2.8.2 - 2020-11-19 + +- Respect `requirements.in` when present + +## 2.8.1 - 2020-11-02 + +- Explicitly state `python_requires` in `setup.py` to satisfy conda-forge linter + +- Bundled tests in sdist, to allow for users wanting to rerun the tests to be able to do so from sdist + +- Using https://pypi.org/project/check-manifest/ + +## 2.8.0 - 2020-10-29 + +- Removed commands `twine` and `uber_egg`, turns out they're not useful + +## 2.7.16 - 2020-10-26 + +- Corrected egg publication for python 2.7 + +## 2.7.15 - 2020-10-26 + +- Publish egg for python 3.9, not publishing for 3.5 any more + +- Corrected support for inlined versions (`__version__ = "..."` in user's code) + +- Stop using deprecated `imp` module with python3 + +- Moved to github actions + +## 2.7.14 - 2020-08-26 + +- Corrected edge case to support git submodules + +## 2.7.13 - 2020-07-27 + +- Verify that HEAD is up-to-date when bumping (#57) + +## 2.7.12 - 2020-07-16 + +- Further refine hooks (#56) + + - Add legacy hook to ease transition in rare cases. + + - Ensure our dist finalization runs first. + +## 2.7.11 - 2020-06-30 + +- Simplify hooks, and fix preprocessing not triggering when not already installed in environment. (#54) + +## 2.7.10 - 2020-06-26 + +- Auto-populate requirements attrs using `@` syntax. (#53) + +## 2.7.9 - 2020-06-17 + +- Always ensure a number is added to version parts such as `rc`, as per [PEP-440](https://www.python.org/dev/peps/pep-0440/#public-version-identifiers) + +## 2.7.8 - 2020-05-21 + +- Don't issue warnings related to versioning on fresh new (still empty) repos + +## 2.7.7 - 2020-05-14 + +- Don't do full auto-fill when invoked with `--name` + +- Added tests, do not try to follow relative paths in `requirements.txt` (pip doesn't accept them anyway) + +## 2.7.6 - 2020-05-14 + +- Avoid infinite recursion with `setup.py --name` + +## 2.7.5 - 2020-05-14 + +- Report package name in `setup_requires` when possible, + for local projects referred to via `-e /some/folder` in a requirements file + +## 2.7.4 - 2020-05-06 + +- Corrected auto-abstract for non-standard version pins such as `foo==1.0-rc1+local-part` + +- Added more debug tracing to help troubleshoot future issues (`SETUPMETA_DEBUG=1 setup.py ...`) + +- Warn if no `packages` or `py_modules` are defined (empty package) + +## 2.7.3 - 2020-05-03 + +- Added support for nested requirements: `-r foo.txt` will be now followed + +## 2.7.2 - 2020-04-30 + +- Corrected bug: parsing `PKG-INFO` files properly + +## 2.7.1 - 2020-04-30 + +- Added `requirements_from_text()` and `requirements_from_file()` + +- Use a regex to determine simple pins of the form `foo==1.0` + +- Consistently apply auto-abstraction to `tests_require` as well + +- Internally use consistent names for `install_requires`, `tests_require` and `extras_require` + +## 2.7.0 - 2020-04-29 + +- Do not use `pip` anymore to parse `requirements.txt` (#49) + +## 2.6.24 - 2020-03-31 + +- Added command `uber_egg`, to support creating [spark](https://spark.apache.org/docs/latest/index.html)-like "uber eggs" + +## 2.6.20 - 2020-03-03 + +- Corrected warning when hardcoded version found does not match git tag + +## 2.6.19 - 2020-03-02 + +- Auto-fill `include_package_data` when `MANIFEST.in` is present + +## 2.6.18 - 2020-02-03 + +- Try and import latest pip first + +## 2.6.17 - 2020-01-24 + +- Adapted `get_pip()` call to pip 20.0 API change + +- Removed support for python 3.4 (not accepted by pypi anymore) + +## 2.6.15 - 2020-01-14 + +- Corrected handling of version tags such as `v0.1.9-rc.1` + +## 2.6.14 - 2020-01-13 + +- Corrected `packages` auto-fill for projects using a direct layout + +- Fix version pinning when `setup_requires` is a list + +## 2.6.13 - 2020-01-09 + +- Support project layout similar to pytest's + +- Warn when `git describe` exits with code != 0 + +- Corrected tests for Windows + +- Removed auto-added `License :: OSI Approved` classifier + +- Corrected `check --deptree` edge cases + +- RFC: include distance with "{devcommit}" + +- Correctly mock absence of twine in tests + +- Bug fix: Correctly initialize .links field in `model.py` + +- version: git: use --first-parent with git-describe + +- Let pip expand req files when not abstracting + +- Allow to override git describe command via env var GIT_DESCRIBE_COMMAND (just in case) + +- Fixed incorrect tag mismatch warning with `post` versioning strategy + +- Publish .egg for python 3.4 and 3.8 + +- Ignore unparseable `requirements.txt` + +- Changed default dirty marker to `.dirty` (instead of `+{commitid}`) + +## 2.5.4 - 2019-05-08 + +- Run only if explicitly required via `setup_requires=["setupmeta"]` + +- Properly handle package name (possible dashes) vs top-level module name (no dashes) + +- Verify that all remote tags are present locally before allowing version bump + +- Show top level deps in `check --deptree`'s "other" section + +- Added `check --deptree`, to show dependency tree of package + +## 2.4.3 - 2019-02-12 + +- Report version as dirty if there staged (but uncommitted changes) + +- Show pending changes (if there are any) in `setup.py check` command + +- Added a test exercising `pip wheel` to expose recent `dirty` determination issue + +- Rolled back `dirty` checkout determination, repo with staged files is considered clean again, + will have to find another fix for that edge case + +- Push version bumps only when `--push` is explicitly specified + +## 2.3.4 - 2019-02-11 + +- Always listify `setup_requires` + +- Better warnings message, should show proper origination + +- Corrected `dirty` determination: repo with staged files is considered dirty until effective commit + +- Extract all relevant info from `PKG-INFO` (not just version) + +- Auto-fill `bugtrack_url` + +- Added support for version determination from PKG-INFO + +## 2.2.1 - 2018-10-23 + +- Added `version --show-next` + +- Added `{devcommit}` versioning strategy token + +- Added `--expand` to command `explain` + +- Better handling of unicode in `README`-s and `setup.py`-s + +- Fixed setupmeta's own download_url + +## 2.0.6 - 2018-09-11 + +- Show how many [requirements](https://github.com/codrsquad/setupmeta#requirements) were abstracted/skipped (if any) in `setup.py check` + +- Warn if current version tag mention patch while versioning strategy doesn't + +- Make sure local tags match remote before pushing a new bumped version tag + +- Auto-fill dependencies accordingly to recommendations in https://packaging.python.org/discussions/install-requires-vs-requirements/ + +- Added support for `.dev` versioning + +- Added `--dependencies` to `explain` command + +## 1.6.2 - 2018-07-16 + +- Auto-fill long_description_content_type when applicable + +## 1.5.1 - 2018-07-13 + +- Test against python 3.7, dropped support for 3.4 as it's not available on travis Xenial + +- Don't pass redundant `bump` cli arg to bump hook + +## 1.4.5 - 2018-04-17 + +- Accept git version tags of the form `M.m.p` (don't require git tags to start with a `v` prefix) + +- Support pip 10.0 + +- Use `g0000000` as commit-id instead of `initial` when no commit took place yet + +- Hook earlier, into `parse_command_line` instead of `get_option_dict` in order for `setup.py --version` (and similar) to work + +- Added pre-defined versioning strategy `post` + +- Renamed pre-defined versioning strategies, to better convey their intent: `changes` -> `distance` and `tag` -> `post` + +## 1.3.6 - 2018-01-14 + +- Env var `SCM_DESCRIBE` used if available and no SCM checkout folder (like `.git`) detected + +- Better support cases where project is in a subfolder of a git checkout + +- Don't consider lack of version tag as dirty checkout (it's confusing otherwise) + +- Parse correctly complex requirements.txt files + +- Support setup.py in a subfolder of a git checkout + +- Renamed command `bump` to `version`, optional bump hook in `./hooks/bump` + +- Added commands: 'twine', 'cleanall' + +- Added `explain --recommend` + +- Added pre-defined versioning strategy `build-id` + +- Test coverage at 100%, added debug info via env var `SETUPMETA_DEBUG=1` + +## 0.8.0 - 2017-12-31 + +- Versioning is more easily customizable, using post-release marker by default (instead of beta) + +- Better defined versioning strategies + +- Fully using setupmeta's own versioning scheme (no more "backup" version stated in `__init__.py`) + +- Versioning compatible with PEP-440 + +- Using `versioning` on setupmeta, which can now bump itself + +- Added support for `versioning` key in setup.py, setupmeta can now compute version from git tags, and bump that version + +- Removed support for Pipfile + +- Testing with pypy as well, produce eggs for 2.7, 3.4, 3.5, 3.6 + +## 0.2.8 - 2017-12-09 + +- Always listify `keywords` + +- Auto-publishing via travis, publish wheels as well + +- Look only at 1st paragraph of docstring for key/value definitions + +- Auto-determine most common license, and associated classifier string + +- Should work with any version of setuptools now, via `setup_requires='setupmeta'` + +- Removed old way, no more "drop setupmeta.py next to your setup.py" mode + +- Fixed bootstrap, so that `PKG-INFO` gets the right metadata (bootstrapping in 2 passes) + +- Use 1st line of README file as short description if no docstrings are found, accept description in project docstrings (not only setup.py) + +- Allow to use portion of README via `.. [[end long_description]]` + +- Allow to use include other files in long description via something like `.. [[include HISTORY.rst]]` diff --git a/HISTORY.rst b/HISTORY.rst deleted file mode 100644 index 44a6bff..0000000 --- a/HISTORY.rst +++ /dev/null @@ -1,581 +0,0 @@ -============= -Release notes -============= - -3.9.0 (2026-02-17) ------------------- - -* Test with py3.14, publish sdist with py3.13 - -* Removed commands: ``entrypoints``, ``cleanall`` - -* Removed post-version-bump hook support (unused, unnecessary, and was not well documented) - -* Removed old ``register`` hook used with setuptools v50 - -* Removed support for pygradle-style versioning - -* Internal project modernizations: - - * Refactored usage of ``subprocess.run()``, removed last left overs from the py2 days - - * use ``uv``, ``ruff``, enabled more linter rules, etc - -* Use coveralls_ for test coverage reporting - - -3.8.0 (2025-03-25) ------------------- - -* Stopped using ``pkg_resources``. - -* Removed ``--deptree`` flag from redefined ``check`` command, this way of visualizing - a dependency tree is not useful anymore, please use ``uv pip tree`` or hdeps_ instead. - - -3.7.2 (2025-03-20) ------------------- - -* Adapted ``CheckCommand`` to setuptools v77.0.3 - -* Dropped support for Python 3.6 - - -3.6.0 (2023-12-04) ------------------- - -* Use ``--no-verify`` with ``git commit`` when bumping a version, to support projects with a - ``no-commit-to-branch`` hook (OK to commit version bump to branch, but not other changes) - - -3.5.2 (2023-09-22) ------------------- - -* Support setuptools 68.2+ - - -3.5.1 (2023-09-22) ------------------- - -* Better support for PEP-517, extract ``Requires-Dist:`` from ``PKG-INFO`` if ``requires.txt`` is missing - -* ``.egg`` files are not published anymore, see https://blog.pypi.org/posts/2023-06-26-deprecate-egg-uploads/ - - -3.4.1 (2023-09-22) ------------------- - -* Removed support for py2 - -* Look for ``v*.*`` git tags first, then ``*.*`` - -* Ignore git tags that do not pass regex ``^v?[0-9]+\.[0-9]+``, - default to ``0.0.0`` in that case, and issue a WARNING - - -3.4.0 (2023-01-25) ------------------- - -* Allow to configure format of git version tag via the advanced dict-form of ``versioning``, - example:: - - versioning={ - "main": "{major}.{minor}.{distance}", - "extra": "{dirty}", - "branches": "main", - "version_tag": "v*.*", - }, - - -3.3.2 (2022-06-01) ------------------- - -* Protect against hook setup call in ancient setuptools versions - - -3.3.1 (2022-05-04) ------------------- - -* Test with 3.10, publish sdist with 3.9 - - -3.3.0 (2021-06-14) ------------------- - -* Do not auto-fill ``tests_require`` any more, it is deprecated, - see https://setuptools.readthedocs.io/en/latest/userguide/dependency_management.html#optional-dependencies - - -3.2.0 (2021-05-17) ------------------- - -* Do not abstract ``requirements.in`` files any more, use them as-is (since they're intended to - be a spec for pip-compile_) - -* Next minor version of setupmeta will not auto-fill ``tests_require`` any more. - - -3.1.0 (2021-04-15) ------------------- - -* Don't try and modify any git url from ``requirements.txt``, it is too much of a moving target - and the whole thing is debated: https://github.com/pypa/pip/issues/5898 - -* Do not auto-fill any ``dependency_links`` at all - - -3.0.0 (2021-03-22) ------------------- - -* Yield PEP-440_ compliant versions - -* Versions yielded by ``setupmeta`` 3.0 will differ from pre-3.0 versions: - - * Character ``"+"`` is used exclusively to demarcate the local part of the version - - * Character ``"."`` is used exclusively to demarcate local segments - (this is not configurable yet, and won't be unless by popular request) - - * The "main" part remains intact, except for ``devcommit``, furthermore only known PEP-440 - "main version part" bits can ever be in the main part (anything that is mistakenly - specified to be in the main part gets automatically shifted to the local part) - - * "local" part is always shown now, no need to use ``"!"`` character (now deprecated), - except for ``{devcommit}`` and ``{dirty}`` markers, which will not lead to a local part - being shown when exactly on git tag. - - * See [pep-440.rst](./docs/pep-440.rst) for more details - - -2.9.0 (2021-03-15) ------------------- - -* Don't auto-complete ``classifiers`` from ``classifiers.txt`` any more, - the added value of that is negligible (and potentially confusing) - - -2.8.3 (2021-01-07) ------------------- - -* Don't warn when bumping for the first time (with no git tags yet) - -* Use the most common ``setuptools.find_packages()`` call (without arguments) - -* Moved repo to https://github.com/codrsquad - - -2.8.2 (2020-11-19) ------------------- - -* Respect ``requirements.in`` when present - - -2.8.1 (2020-11-02) ------------------- - -* Explicitly state ``python_requires`` in ``setup.py`` to satisfy conda-forge linter - -* Bundled tests in sdist, to allow for users wanting to rerun the tests to be able to do so from sdist - -* Using https://pypi.org/project/check-manifest/ - - -2.8.0 (2020-10-29) ------------------- - -* Removed commands ``twine`` and ``uber_egg``, turns out they're not useful - - -2.7.16 (2020-10-26) -------------------- - -* Corrected egg publication for python 2.7 - - -2.7.15 (2020-10-26) -------------------- - -* Publish egg for python 3.9, not publishing for 3.5 any more - -* Corrected support for inlined versions (``__version__ = "..."`` in user's code) - -* Stop using deprecated ``imp`` module with python3 - -* Moved to github actions - - -2.7.14 (2020-08-26) -------------------- - -* Corrected edge case to support git submodules - - -2.7.13 (2020-07-27) -------------------- - -* Verify that HEAD is up-to-date when bumping (#57) - - -2.7.12 (2020-07-16) -------------------- - -* Further refine hooks (#56) - - * Add legacy hook to ease transition in rare cases. - - * Ensure our dist finalization runs first. - - -2.7.11 (2020-06-30) -------------------- - -* Simplify hooks, and fix preprocessing not triggering when not already installed in environment. (#54) - - -2.7.10 (2020-06-26) -------------------- - -* Auto-populate requirements attrs using ``@`` syntax. (#53) - - -2.7.9 (2020-06-17) ------------------- - -* Always ensure a number is added to version parts such as ``rc``, as per PEP-440_ - - -2.7.8 (2020-05-21) ------------------- - -* Don't issue warnings related to versioning on fresh new (still empty) repos - - -2.7.7 (2020-05-14) ------------------- - -* Don't do full auto-fill when invoked with ``--name`` - -* Added tests, do not try to follow relative paths in ``requirements.txt`` (pip doesn't accept them anyway) - - -2.7.6 (2020-05-14) ------------------- - -* Avoid infinite recursion with ``setup.py --name`` - - -2.7.5 (2020-05-14) ------------------- - -* Report package name in ``setup_requires`` when possible, - for local projects referred to via ``-e /some/folder`` in a requirements file - - -2.7.4 (2020-05-06) ------------------- - -* Corrected auto-abstract for non-standard version pins such as ``foo==1.0-rc1+local-part`` - -* Added more debug tracing to help troubleshoot future issues (``SETUPMETA_DEBUG=1 setup.py ...``) - -* Warn if no ``packages`` or ``py_modules`` are defined (empty package) - - -2.7.3 (2020-05-03) ------------------- - -* Added support for nested requirements: ``-r foo.txt`` will be now followed - - -2.7.2 (2020-04-30) ------------------- - -* Corrected bug: parsing ``PKG-INFO`` files properly - - -2.7.1 (2020-04-30) -------------------- - -* Added ``requirements_from_text()`` and ``requirements_from_file()`` - -* Use a regex to determine simple pins of the form ``foo==1.0`` - -* Consistently apply auto-abstraction to ``tests_require`` as well - -* Internally use consistent names for ``install_requires``, ``tests_require`` and ``extras_require`` - - -2.7.0 (2020-04-29) -------------------- - -* Do not use ``pip`` anymore to parse ``requirements.txt`` (#49) - - -2.6.24 (2020-03-31) -------------------- - -* Added command ``uber_egg``, to support creating spark_-like "uber eggs" - - -2.6.20 (2020-03-03) -------------------- - -* Corrected warning when hardcoded version found does not match git tag - - -2.6.19 (2020-03-02) -------------------- - -* Auto-fill ``include_package_data`` when ``MANIFEST.in`` is present - - -2.6.18 (2020-02-03) -------------------- - -* Try and import latest pip first - - -2.6.17 (2020-01-24) -------------------- - -* Adapted ``get_pip()`` call to pip 20.0 API change - -* Removed support for python 3.4 (not accepted by pypi anymore) - - -2.6.15 (2020-01-14) -------------------- - -* Corrected handling of version tags such as ``v0.1.9-rc.1`` - - -2.6.14 (2020-01-13) -------------------- - -* Corrected ``packages`` auto-fill for projects using a direct layout - -* Fix version pinning when ``setup_requires`` is a list - - -2.6.13 (2020-01-09) -------------------- - -* Support project layout similar to pytest's - -* Warn when ``git describe`` exits with code != 0 - -* Corrected tests for Windows - -* Removed auto-added ``License :: OSI Approved`` classifier - -* Corrected ``check --deptree`` edge cases - -* RFC: include distance with "{devcommit}" - -* Correctly mock absence of twine in tests - -* Bug fix: Correctly initialize .links field in ``model.py`` - -* version: git: use --first-parent with git-describe - -* Let pip expand req files when not abstracting - -* Allow to override git describe command via env var GIT_DESCRIBE_COMMAND (just in case) - -* Fixed incorrect tag mismatch warning with ``post`` versioning strategy - -* Publish .egg for python 3.4 and 3.8 - -* Ignore unparseable ``requirements.txt`` - -* Changed default dirty marker to ``.dirty`` (instead of ``+{commitid}``) - - -2.5.4 (2019-05-08) ------------------- - -* Run only if explicitly required via ``setup_requires=["setupmeta"]`` - -* Properly handle package name (possible dashes) vs top-level module name (no dashes) - -* Verify that all remote tags are present locally before allowing version bump - -* Show top level deps in ``check --deptree``'s "other" section - -* Added ``check --deptree``, to show dependency tree of package - - -2.4.3 (2019-02-12) ------------------- - -* Report version as dirty if there staged (but uncommitted changes) - -* Show pending changes (if there are any) in ``setup.py check`` command - -* Added a test exercising ``pip wheel`` to expose recent ``dirty`` determination issue - -* Rolled back ``dirty`` checkout determination, repo with staged files is considered clean again, - will have to find another fix for that edge case - -* Push version bumps only when ``--push`` is explicitly specified - - -2.3.4 (2019-02-11) ------------------- - -* Always listify ``setup_requires`` - -* Better warnings message, should show proper origination - -* Corrected ``dirty`` determination: repo with staged files is considered dirty until effective commit - -* Extract all relevant info from ``PKG-INFO`` (not just version) - -* Auto-fill ``bugtrack_url`` - -* Added support for version determination from PKG-INFO - - -2.2.1 (2018-10-23) ------------------- - -* Added ``version --show-next`` - -* Added ``{devcommit}`` versioning strategy token - -* Added ``--expand`` to command ``explain`` - -* Better handling of unicode in ``README``-s and ``setup.py``-s - -* Fixed setupmeta's own download_url - - -2.0.6 (2018-09-11) ------------------- - -* Show how many requirements_ were abstracted/skipped (if any) in ``setup.py check`` - -* Warn if current version tag mention patch while versioning strategy doesn't - -* Make sure local tags match remote before pushing a new bumped version tag - -* Auto-fill dependencies accordingly to recommendations in https://packaging.python.org/discussions/install-requires-vs-requirements/ - -* Added support for ``.dev`` versioning - -* Added ``--dependencies`` to ``explain`` command - - -1.6.2 (2018-07-16) ------------------- - -* Auto-fill long_description_content_type when applicable - - -1.5.1 (2018-07-13) ------------------- - -* Test against python 3.7, dropped support for 3.4 as it's not available on travis Xenial - -* Don't pass redundant ``bump`` cli arg to bump hook - - -1.4.5 (2018-04-17) ------------------- - -* Accept git version tags of the form ``M.m.p`` (don't require git tags to start with a ``v`` prefix) - -* Support pip 10.0 - -* Use ``g0000000`` as commit-id instead of ``initial`` when no commit took place yet - -* Hook earlier, into ``parse_command_line`` instead of ``get_option_dict`` in order for ``setup.py --version`` (and similar) to work - -* Added pre-defined versioning strategy ``post`` - -* Renamed pre-defined versioning strategies, to better convey their intent: ``changes`` -> ``distance`` and ``tag`` -> ``post`` - - -1.3.6 (2018-01-14) ------------------- - -* Env var ``SCM_DESCRIBE`` used if available and no SCM checkout folder (like ``.git``) detected - -* Better support cases where project is in a subfolder of a git checkout - -* Don't consider lack of version tag as dirty checkout (it's confusing otherwise) - -* Parse correctly complex requirements.txt files - -* Support setup.py in a subfolder of a git checkout - -* Renamed command ``bump`` to ``version``, optional bump hook in ``./hooks/bump`` - -* Added commands: 'twine', 'cleanall' - -* Added ``explain --recommend`` - -* Added pre-defined versioning strategy ``build-id`` - -* Test coverage at 100%, added debug info via env var ``SETUPMETA_DEBUG=1`` - - -0.8.0 (2017-12-31) ------------------- - -* Versioning is more easily customizable, using post-release marker by default (instead of beta) - -* Better defined versioning strategies - -* Fully using setupmeta's own versioning scheme (no more "backup" version stated in ``__init__.py``) - -* Versioning compatible with PEP-440 - -* Using ``versioning`` on setupmeta, which can now bump itself - -* Added support for ``versioning`` key in setup.py, setupmeta can now compute version from git tags, and bump that version - -* Removed support for Pipfile - -* Testing with pypy as well, produce eggs for 2.7, 3.4, 3.5, 3.6 - - -0.2.8 (2017-12-09) ------------------- - -* Always listify ``keywords`` - -* Auto-publishing via travis, publish wheels as well - -* Look only at 1st paragraph of docstring for key/value definitions - -* Auto-determine most common license, and associated classifier string - -* Should work with any version of setuptools now, via ``setup_requires='setupmeta'`` - -* Removed old way, no more "drop setupmeta.py next to your setup.py" mode - -* Fixed bootstrap, so that ``PKG-INFO`` gets the right metadata (bootstrapping in 2 passes) - -* Use 1st line of README file as short description if no docstrings are found, accept description in project docstrings (not only setup.py) - -* Allow to use portion of README via ``.. [[end long_description]]`` - -* Allow to use include other files in long description via something like ``.. [[include HISTORY.rst]]`` - - -.. _requirements: https://github.com/codrsquad/setupmeta/blob/main/docs/requirements.rst - -.. _spark: https://spark.apache.org/docs/latest/index.html - -.. _PEP-440: https://www.python.org/dev/peps/pep-0440/#public-version-identifiers - -.. _PEP-508: https://www.python.org/dev/peps/pep-0508/ - -.. _report: https://github.com/codrsquad/setupmeta/issues - -.. _pip-compile: https://pypi.org/project/pip-tools/ - -.. _hdeps: https://pypi.org/project/hdeps/ - -.. _coveralls: https://coveralls.io/ diff --git a/MANIFEST.in b/MANIFEST.in index 83eaaa1..1ef2e27 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,8 +1,8 @@ include LICENSE -include HISTORY.rst +include CHANGELOG.md include README.rst include tox.ini -recursive-include docs *.rst +recursive-include docs *.md graft examples graft tests global-exclude __pycache__ *.py[cod] diff --git a/README.rst b/README.rst index 73aac34..88cdb0f 100644 --- a/README.rst +++ b/README.rst @@ -23,205 +23,218 @@ Simplify your setup.py ---- -Writing a ``setup.py`` typically involves lots of boilerplate and copy-pasting from project to project. - -This package aims to simplify that and bring some DRY_ principle to python packaging_. -Here's what your (complete, and ready to ship to pypi) ``setup.py`` could look like with setupmeta_:: +setupmeta_ derives your project's version from git tags, bumps it for you, and fills in the +rest of your ``setup.py`` metadata from the files your project already has. +A complete, ready to publish ``setup.py`` can look like this:: from setuptools import setup setup( name="myproject", - versioning="distance", # Optional, would activate tag-based versioning - setup_requires="setupmeta" # This is where setupmeta comes in + versioning="distance", # Optional: activates git-tag-based versioning + setup_requires="setupmeta", # This is what activates setupmeta ) -And that should be it - setupmeta_ will take it from there, extracting everything else from the rest of your project -(following typical conventions commonly used). - -You can use the **explain** command (see commands_) to see what setupmeta_ deduced from your project, -for the above it would look something like this (you can see which file and which line each setting came from, -note that a lot of info is typically extracted from your project, if you follow usual conventions):: - - ~/myproject: python setup.py explain +Run ``python setup.py explain`` to see every setting setupmeta determined, along with the +file and line each value came from. Anything you pass explicitly to ``setup()`` (or state in +``setup.cfg``) is used as-is, so you can always override what setupmeta deduces. - author: (auto-adjust ) Your Name - \_: (myproject.py:7 ) Your Name - author_email: (auto-adjust ) your@email.com - description: (README.rst:1 ) First line of your README - entry_points: (entry_points.ini) [console_scripts] ... - install_requires: (requirements.txt) ["click", ... - license: (auto-fill ) MIT - long_description: (README.rst ) Long description would be your inlined README - name: (explicit ) myproject - py_modules: (auto-fill ) ["myproject"] - setup_requires: (explicit ) ["setupmeta"] - version: (git ) 1.2.3.post2 - versioning: (explicit ) distance -See examples_ for more. +Versioning +========== -**Note**: ``setupmeta``'s versioning is based on:: +Versioning is opt-in: pass a ``versioning=`` strategy to ``setup()`` (or state it like any +other auto-filled value, see below). The version then comes from:: git describe --dirty --tags --long --first-parent --match 'v*.*' -you will need **git version >= 1.8.4** if you wish to use ``setupmeta``'s versioning capabilities. - +which requires git 1.8.4 or later. Only tags starting with ``v`` count (configurable with +``version_tag``, see below). Without any version tag, the version starts from ``0.0.0``. -Goal -==== +The preconfigured strategies yield PEP-440_ compliant versions: -The goal of this project is to: +========================= ===================== ==================== =============== +State of the checkout ``post`` ``dev`` ``distance`` +========================= ===================== ==================== =============== +No commit yet ``0.0.0`` ``0.0.0`` ``0.0.0`` +1 commit, no tag yet ``0.0.0.post1`` ``0.0.1.dev1`` ``0.0.1`` +Same, with pending change ``0.0.0.post1+dirty`` ``0.0.1.dev1+dirty`` ``0.0.1+dirty`` +Exactly on tag ``v0.1.0`` ``0.1.0`` ``0.1.0`` ``0.1.0`` +Same, with pending change ``0.1.0+dirty`` ``0.1.1.dev0+dirty`` ``0.1.0+dirty`` +2 commits after the tag ``0.1.0.post2`` ``0.1.1.dev2`` ``0.1.2`` +========================= ===================== ==================== =============== -* Allow to write very short (yet complete) ``setup.py``-s, without boilerplate, and encourage good common packaging_ practices +- ``post`` suits projects that tag each release and publish occasionally. -* Point out missing important info (example: version) in ``setup.py explain`` +- ``dev`` makes untagged versions pre-releases (``pip install --pre`` to get them), + assuming the next bump will be a ``patch``. -* Support tag-based versioning_ (like setuptools_scm_, but with super simple configuration/defaults and automated ``bump`` capability) +- ``distance`` suits publishing at every commit: the patch number is the count of commits + since the latest ``v{major}.{minor}.0`` tag, so only ``major`` and ``minor`` can be bumped. -* Provide useful Commands_ to see the metadata (**explain**), **version** (including support for bumping versions), - and **check** +- ``devcommit`` is ``dev`` plus the commit id when not exactly on a tag: + ``0.1.1.dev2+g2cff0a6``. +- ``build-id`` is ``distance`` plus a local part that is always shown, made of the first env + var whose name ends with ``BUILD_ID`` (or ``local``) and the commit id: + ``0.1.2+h12.g2cff0a6`` on a CI server, ``0.1.2+hlocal.g2cff0a6`` locally. -How it works? -============= -* Everything that you explicitly provide in your original ``setuptools.setup()`` call is taken as-is (never changed), - and internally labelled as ``explicit``. - So if you don't like something that setupmeta_ deduces, you can always explicitly state it. +Bumping +------- -* ``name`` is auto-filled from your setup.py's ``__title__`` (if there is one, sometimes having a constant is quite handy...) +``python setup.py version`` shows the current version, ``--show-next minor`` the next one. +``--bump major|minor|patch`` does a dry run of a bump; add ``--commit`` to apply the +``v`` git tag, and ``--push`` to also push it. A bump is refused when: -* ``packages`` and ``package_dir`` is auto-filled accordingly if you have a ``/__init__.py`` or ``src//__init__.py`` file +- the current branch isn't allowed (``main`` or ``master`` by default), +- ``--commit`` is given while the checkout has pending changes, +- the remote has tags missing locally, or the local branch is behind its remote. -* ``py_modules`` is auto-filled if you have a ``.py`` file +You don't need a ``__version__`` anywhere, but if one is stated in your ``.py`` files, +setupmeta warns when it disagrees with the git tag, and a bump rewrites and commits it first. -* ``entry_points`` is auto-filled from file ``entry_points.ini`` (bonus: tools like PyCharm have a nice syntax highlighter for those) -* ``install_requires`` is auto-filled from ``requirements.in`` (preferred), then ``requirements.txt`` - (or ``pinned.txt`` for older projects), - pinning is abstracted away by default as per `community recommendation`_, see requirements_ for more info. +Custom formats +-------------- -* ``description`` will be the 1st line of your README (unless that 1st line is too short, or is just the project's name), - or the 1st line of the first docstring found in the scanned files (see list below) +``versioning`` also accepts a format string of the form ``branch():
+``, +for example ``branch(main,release):{major}.{minor}.{patch}{post}+{commitid}{dirty}``. +The ``branch(...):`` prefix lists the branches where bumps are allowed, and is optional. +These can be used: -* ``long_description`` is auto-filled from your README file (looking for ``README.rst``, ``README.md``, - then ``README*``, first one found wins). - Special tokens can be used (notation aimed at them easily being `rst comments`_): +- ``{major}``, ``{minor}``, ``{patch}``: components of the latest version tag - * ``.. [[end long_description]]`` as end marker, so you don't have to use the entire file as long description +- ``{distance}``: number of commits since that tag - * ``.. [[include ]]`` if you want another file included as well (for example, people like to add ``HISTORY.txt`` as well) +- ``{post}``, ``{dev}``: ``.postN`` or ``.devN`` when not exactly on a tag (``N`` being + ``{distance}``, and ``{dev}`` also bumping ``patch``) - * these tokens must appear either at beginning/end of line, or be after/before at least one space character +- ``{devcommit}``, ``{commitid}``, ``{dirty}``: ``.g`` when not exactly on a tag, + ``g`` always, and ``.dirty`` when there are pending changes -* ``version`` can be stated explicitly, or be computed from git tags using ``versioning=...`` (see versioning_ for more info): +- ``{$FOO}``: value of env var ``FOO``; ``{$FOO:default}`` gives a default for when it's + undefined (otherwise ``None`` is shown); ``$*FOO``, ``$FOO*`` and ``$*FOO*`` match the first + (alphabetically) env var whose name ends with, starts with or contains ``FOO`` - * With ``versioning="distance"``, your git tags will be of the form ``v{major}.{minor}.0``, - the number of commits since latest version tag will be used to auto-fill the "patch" part of the version: +- anything else is used as-is - * tag "v1.0.0", no commits since tag -> version is "1.0.0" +Markers other than ``{major}``, ``{minor}``, ``{patch}``, ``{distance}``, ``{post}`` and ``{dev}`` +are moved to the local part if placed in the main part, as PEP-440 requires. +``post+build-id`` and ``dev+devcommit`` are shortcuts for adding the local part of the +``build-id`` or ``devcommit`` strategies to another one. - * tag "v1.0.0", 5 commits since tag -> version is "1.0.5" +For full control, pass a dict: ``main`` and ``extra`` (format strings, or callables receiving a +``setupmeta.scm.Version``; a callable ``main`` disables bumps), ``branches`` (list or +comma-separated string), and ``version_tag`` (glob of git tags to consider, default ``v*.*``). - * if checkout is dirty, a marker is added -> version would be "1.0.5+dirty" - * With ``versioning="post"``, your git tags will be of the form ``v{major}.{minor}.{patch}``, - a "post" addendum will be present if there are commits since latest version tag: +Environment +----------- - * tag "v1.0.0", no commits since tag -> version is "1.0.0" +- ``SETUPMETA_GIT_DESCRIBE_COMMAND`` replaces the ``git describe`` command above (give the + full command, without the leading ``git``). - * tag "v1.0.0", 5 commits since tag -> version is "1.0.0.post5" +- ``SCM_DESCRIBE`` takes a ``git describe``-like output (eg: ``v1.2.0-3-g1234567``) and uses it + instead of asking git, for builds where no git checkout is available. - * if checkout is dirty, a marker is added -> version would be "1.0.0.post5+dirty" +- When ``setup.py`` lives in a subfolder of a git checkout, setupmeta writes the version to a + ``.setupmeta.version`` file next to it, and uses that file when the project is later built + from a copy that has no ``.git``. - * With ``versioning="build-id"``, your git tags will be of the form ``v{major}.{minor}.0``, - the number of commits since latest version tag will be used to auto-fill the "patch" part of the version: - * tag "v1.0.0", no commits since tag, ``BUILD_ID=12`` -> version is "1.0.0+h12.g123" +Auto-filled metadata +==================== - * tag "v1.0.0", no commits since tag, ``BUILD_ID`` not defined -> version is "1.0.0+hlocal.g123" +setupmeta fills in what you didn't state explicitly, following the usual conventions: - * tag "v1.0.0", 5 commits since tag, ``BUILD_ID=12`` -> version is "1.0.5+h12.g456" +- ``name``: from a ``__title__`` stated in ``setup.py``. - * tag "v1.0.0", 5 commits since tag, ``BUILD_ID`` not defined -> version is "1.0.5+hlocal.g456" +- ``packages`` / ``py_modules``: when neither is given, all packages found under ``src/`` + (setting ``package_dir`` accordingly), or else the packages whose name starts with your + project's name, or a ``.py`` module. - * if checkout is dirty, a marker is added -> version would be "1.0.5+hlocal.g456.dirty" +- Simple settings like ``version``, ``versioning``, ``url``, ``download_url``, + ``bugtrack_url``, ``license``, ``keywords``, ``author``, ``maintainer``, ``contact``, or + ``platforms``, from lines like ``__author__ = "..."`` (modules are scanned, never imported), + or ``author: ...`` lines in the first paragraph of a docstring. First found wins, looking in + ``setup.py``, then ``.py``, then ``__about__.py``, ``__version__.py`` and + ``__init__.py`` of your top level packages (also under ``src/``). - * Use the **version** command (see commands_) to easily bump (ie: increment major, minor or patch + apply git tag) +- ``author``, ``maintainer``, ``contact``: a value like ``Bob D bob@example.com`` is split + into name and ``*_email``. - * Version format can be customized, see versioning_ for more info +- ``url``: when pointing to a github org (like ``https://github.com/codrsquad``), your + project's name is appended, and ``bugtrack_url`` defaults to its ``issues`` page. + Relative ``download_url`` and ``bugtrack_url`` are prefixed with ``url``, and all of them can + use ``{name}`` and ``{version}`` markers. -* ``version``, ``versioning``, ``url``, ``download_url``, ``bugtrack_url``, ``license``, ``keywords``, ``author``, ``contact``, ``maintainer``, - and ``platforms`` will be auto-filled from: +- ``description``: the first line of your README, unless it's too short or just your + project's name, in which case the first line of the docstring of a scanned module is used. - * Lines of the form ``__key__ = "value"`` in your modules (simple constants only, - expressions are ignored - the modules are not imported but scanned using regexes) +- ``long_description`` (and its content type): your README (``README.rst``, ``README.md``, + then ``README*``). A line ``.. [[end long_description]]`` ends it early, and + ``.. [[include ]]`` includes another file; both must stand alone on their line. - * Lines of the form ``key: value`` in your docstring +- ``entry_points``: contents of an ``entry_points.ini`` file. - * Files are examined in this order (first find wins): +- ``license``: recognized from the text of a ``LICENSE*`` file. - * ``setup.py`` +- ``include_package_data``: ``True`` when there's a ``MANIFEST.in``. - * ``.py`` (mccabe_ for example) +- ``install_requires``: see below. - * ``/__about__.py`` (cryptography_ for example) +When building from an sdist, values recorded in its ``PKG-INFO`` are used. - * ``/__version__.py`` (requests_ for example) - * ``/__init__.py`` (changes_, arrow_ for example) +Requirements +------------ - * ``src/`` is also examined (for those who like to have their packages under ``src``) +When ``install_requires`` isn't given, it comes from ``requirements.in`` (used as-is), else +``requirements.txt``, where simple ``name==version`` pins are abstracted down to ``name``, as per +this `community recommendation`_ (a library shouldn't impose pins on its users). ``-r other.txt`` references are followed. ``install_requires="@path/to/reqs.txt"`` uses a +given file, and works for ``extras_require`` values too. - * URLs can be simplified: +A comment can mark a line, or start a section applying to all lines below it, with one of: - * if ``url`` points to your general github repo (like: https://github.com/codrsquad), - the ``name`` of your project is auto-appended to it +- ``# abstract`` (the default): ``name==version`` pins become ``name`` +- ``# pinned``: kept as-is +- ``# indirect``: left out of ``install_requires`` altogether - * relative urls are auto-filled by prefixing them with ``url`` +Only plain ``==`` pins are abstracted: ``click>=7.0`` is always kept as-is. For example:: - * urls may use ``{name}`` and/or ``{version}`` markers, it will be expanded appropriately + click==8.1.7 + requests==2.31.0 # pinned + retry>=0.9 - * ``author``, ``maintainer`` and ``contact`` names and emails can be combined into one line - (setupmeta_ will figure out the email part and auto-fill it properly) + # indirect + urllib3==2.2.1 - * i.e.: ``author: Bob D bob@example.com`` will yield the proper ``author`` and ``author_email`` settings +yields ``install_requires=["click", "requests==2.31.0", "retry>=0.9"]``. -This should hopefully work nicely for the vast majority of python projects out there. -If you need advanced stuff, you can still leverage setupmeta_ for all the usual stuff above, and go explicit wherever needed. +Commands +======== +setupmeta adds these ``setup.py`` commands, see ``python setup.py --help``: -.. _DRY: https://en.wikipedia.org/wiki/Don%27t_repeat_yourself +- ``explain``: show all settings and where each came from; ``--dependencies`` shows how + requirements were auto-filled, ``--expand`` prints the equivalent ``setup.py`` without + setupmeta (handy if you ever want to stop using it). -.. _commands: https://github.com/codrsquad/setupmeta/blob/main/docs/commands.rst +- ``version``: show or bump the version, see Bumping_. -.. _requirements: https://github.com/codrsquad/setupmeta/blob/main/docs/requirements.rst +- ``check``: setuptools' ``check``, also reporting how many requirements were abstracted or + ignored, and the pending changes (useful to tell why a CI build saw a dirty checkout). -.. _versioning: https://github.com/codrsquad/setupmeta/blob/main/docs/versioning.rst +Set ``SETUPMETA_DEBUG=1`` to trace what setupmeta does. -.. _community recommendation: https://packaging.python.org/discussions/install-requires-vs-requirements/ - -.. _packaging: https://python-packaging.readthedocs.io/en/latest/ - -.. _setuptools_scm: https://github.com/pypa/setuptools_scm .. _setupmeta: https://github.com/codrsquad/setupmeta -.. _examples: https://github.com/codrsquad/setupmeta/tree/main/examples - -.. _rst comments: http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html#comments +.. _PEP-440: https://peps.python.org/pep-0440/ -.. _requests: https://github.com/psf/requests/tree/main/src/requests - -.. _cryptography: https://github.com/pyca/cryptography/tree/main/src/cryptography - -.. _changes: https://github.com/michaeljoseph/changes/blob/main/changes/__init__.py - -.. _arrow: https://github.com/arrow-py/arrow/blob/master/arrow/__init__.py - -.. _mccabe: https://github.com/PyCQA/mccabe/blob/master/mccabe.py +.. _community recommendation: https://packaging.python.org/discussions/install-requires-vs-requirements/ .. [[end long_description]] @@ -251,18 +264,8 @@ I noticed that most open-source projects out there do the same thing over and ov I didn't want to keep doing this anymore myself, so I decided to try and do something about it with this project. +See examples_ for sample projects, and docs_ for how setupmeta works inside. -Roadmap -======= - -* Support more SCMs, like ``hg`` - - -Test coverage -============= - -Since setupmeta is designed to be used as ``setup_requires``, it is important it doesn't break in any edge case, -as that would be disruptive to all clients. +.. _examples: https://github.com/codrsquad/setupmeta/tree/main/examples -Test coverage is at 100%, and should remain at that at all times. If you are contributing, please craft test cases -that exercise all possible edge cases. +.. _docs: https://github.com/codrsquad/setupmeta/blob/main/docs/index.md diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..0ec094a --- /dev/null +++ b/docs/README.md @@ -0,0 +1,4 @@ +# setupmeta internal docs + +These are internal docs, for working on setupmeta itself: start at [index.md](./index.md). +The user guide is the [README](../README.rst), which is also what PyPI shows. diff --git a/docs/autofill.md b/docs/autofill.md new file mode 100644 index 0000000..61bd1a0 --- /dev/null +++ b/docs/autofill.md @@ -0,0 +1,28 @@ +# Auto-fill + +Every value is a `Definition` (`setupmeta/model.py`) holding its winning value plus every +source it was seen in. `explain` prints that list: the winner on top, the others under `\_`. +A key that isn't a setuptools field is shown with a `*` suffix, and never passed to setuptools. + +Ranking rule, in `Definition.add()`: the first value seen wins, unless added with +`override=True`, which takes over and moves to the top. `SetupMeta.finalize()` relies on order: + +- explicit `setup()` / `setup.cfg` values are added first, tagged `explicit`; +- `PKG-INFO` values next (present when building from an sdist, so the sdist's recorded version + and metadata win over re-computing them); +- scanned modules are merged in scan order, so the first file stating a value wins; +- `auto_fill()` overrides any value, unless the key was explicit. That's how a git-derived + version beats a `__version__` found in code, while an explicit `version=` beats both. + +Module scanning (`SimpleModule`) never imports anything: it matches `__key__ = "value"` lines, +and `key: value` lines in the first paragraph of docstrings, with regexes. Expressions are +silently ignored. The first docstring line that isn't a `key: value` becomes `docstring_lead`, a +fallback for `description`. + +Requirements parsing lives in `setupmeta/__init__.py` (`ReqEntry`, `RequirementsFile`), not in +`model.py`. Abstraction only ever strips simple `name==version` pins; `requirements.in` is +never abstracted, since it's meant as a `pip-compile` input. + +README handling is in `setupmeta/content.py`. The `.. [[end long_description]]` and +`.. [[include ...]]` tokens only count when nothing but whitespace surrounds them on their line: +that's what lets our own README mention them inside RST literals without being cut short. diff --git a/docs/bootstrap.md b/docs/bootstrap.md new file mode 100644 index 0000000..aa5b643 --- /dev/null +++ b/docs/bootstrap.md @@ -0,0 +1,15 @@ +# Bootstrap + +setupmeta uses itself (`setup_requires="setupmeta"`, `versioning="dev"`), which only works once +its own entry points are registered, ie: once `setupmeta.egg-info/` exists. Our +[setup.py](../setup.py) handles that chicken-and-egg problem: + +- With no `setupmeta.egg-info/` yet, it runs `setup.py egg_info` twice as a subprocess, without + self-reference. The second pass is needed so the version gets computed by setupmeta itself. +- A lone `egg_info` command is the bootstrap mode: plain setuptools metadata only. +- Once bootstrapped, `complete_args()` adds the self-reference. + +That's also why `entry_points`, `packages` and `name` are stated explicitly instead of +auto-filled: they must be right before setupmeta can run. + +Changes here can break installing setupmeta itself: get maintainer review. diff --git a/docs/commands.rst b/docs/commands.rst deleted file mode 100644 index 93b9a2e..0000000 --- a/docs/commands.rst +++ /dev/null @@ -1,98 +0,0 @@ -Commands -======== - -``setupmeta`` also introduces a few commands to make your life easier -(more to come in the future). - - -explain -======= - -``python setup.py explain`` will show you what ``setupmeta`` found out about your project, -what definitions came from where. - -For example, this is what setupmeta says about itself (it's self-using):: - - ~/dev/setupmeta: python setup.py explain - - author: (auto-adjust ) Zoran Simic - \_: (setupmeta/__init__.py:6) Zoran Simic zoran@simicweb.com - author_email: (auto-adjust ) zoran@simicweb.com - bugtrack_url: (auto-fill ) https://github.com/codrsquad/setupmeta/issues - classifiers: (explicit ) 23 items: ["Development Status :: 5 - Production/Stable", "Intend... - description: (setupmeta/__init__.py:2) Simplify your setup.py - download_url: (auto-fill ) https://github.com/codrsquad/setupmeta/archive/v3.9.0.tar.gz - \_: (setupmeta/__init__.py:5) archive/v{version}.tar.gz - entry_points: (explicit ) [distutils.commands] check = setupmeta.commands:CheckCommand expla... - include_package_data: (MANIFEST.in ) True - install_requires: (explicit ) ["setuptools>=67"] - license: (auto-fill ) MIT - long_description: (README.rst ) Simplify your setup.py ====================== .. image:: https://... - long_description_content_type: (README.rst ) text/x-rst - name: (explicit ) setupmeta - packages: (explicit ) ["setupmeta"] - python_requires: (explicit ) >=3.7 - setup_requires: (explicit ) ["setupmeta"] - url: (setupmeta/__init__.py:4) https://github.com/codrsquad/setupmeta - version: (git ) 3.9.0 - versioning: (explicit ) dev - zip_safe: (explicit ) True - -In the above output: - -* All the ``explicit`` mentions mean that associated values were seen mentioned explicitly - in setup.py, and were left untouched - -* The ``author`` key was seen in ``setupmeta/__init__.py`` line 6, and the value was name + email, - that got "auto-adjusted" and filled-in as ``author`` + ``author_email`` properly as shown. - -* Note that the ``\_`` indication tries to convey the fact that ``author`` in this example - had a value that came from 2 different sources, final value showing at top, - while all the other values seen showing below with the ``\_`` indicator. - -* ``classifiers`` came from explicit settings in ``setup.py`` - -* ``description`` came from ``setupmeta/__init__.py`` line 2 - -* ``download_url`` was defined in ``setupmeta/__init__.py`` line 5, since it was mentioning - ``{version}`` (and was a relative path), it got auto-expanded and filled in properly - -* ``entry_points`` were explicitly stated (in project's setup.py) - -* ``long_description`` came from ``README.rst`` - -* ``name`` came from an explicit setting in setup.py - -* ``packages`` came from explicit settings in setup.py - -* ``version`` was determined from git (due to ``versioning="dev"`` in setup.py), - in this case ``3.9.0`` means current commit is exactly on a version tag - - -If you'd like to see what your ``setup.py`` would look like without setupmeta -(and/or if you wish to get rid of setupmeta), you can run:: - - python setup.py explain --expand - - -This will show you a copy-pastable ``setup.py`` that is the equivalent of not having setupmeta -at all (obviously without support for versioning etc). - - -version -======= - -If you're using the ``versioning=...`` feature, you can then use the -``python setup.py version --bump=`` command to bump your git-tag driven version. -See ``--help`` for more info. - -Typical usage:: - - python setup.py version --help # What were the options? - python setup.py version # Show current version and exit - python setup.py version --show-next minor # Show next minor version and exit - python setup.py version --bump minor # Dryrun bump: see what would be done - python setup.py version --b minor --commit # Effectively bump - - -.. _PEP-440: https://www.python.org/dev/peps/pep-0440/ diff --git a/docs/contributing.md b/docs/contributing.md new file mode 100644 index 0000000..65bedb0 --- /dev/null +++ b/docs/contributing.md @@ -0,0 +1,36 @@ +# Contributing + +Runtime code must stay stdlib-only (plus setuptools), and keep working on the oldest Python we +support (`python_requires` in [setup.py](../setup.py)). Since projects pull setupmeta in at build +time, a bug breaks other people's builds: test coverage is kept at 100%. + +`tox -e venv` creates `.venv/`. The tox envs (tests, `coverage`, `docs`, `style`, `reformat`, +`refreshscenarios`) are defined in [tox.ini](../tox.ini). The default envlist tests only the +oldest and newest Python, and `coverage` combines both runs: that's the cheap way to get full +coverage locally, CI covers the versions in between. + +`tox -e docs` runs `check-manifest`, and the README render check PyPI relies on. Files added to +the repo must be covered by [MANIFEST.in](../MANIFEST.in) or ignored in `tox.ini`. + +## Scenario snapshots + +`tests/test_scenarios.py` replays every folder with a `setup.py` under `tests/scenarios/` +(edge cases) and `examples/` (vanilla projects), and compares the output to its `expected.txt`. +Replay mechanics are in `tests/scenarios.py`: + +- every scenario runs the commands in `SCENARIO_COMMANDS`; +- without a `.commands` file, it runs in place with a fake `SCM_DESCRIBE` version; +- with one, it runs in a fresh clone of a temp bare origin, tagged `v1.2.3`, and runs the extra + commands listed there too; lines starting with `:` are preparation commands, run before the + initial commit. + +After an intentional behavior change, `tox -e refreshscenarios` regenerates the snapshots: +review the `expected.txt` diffs, they are the user-visible output. Snapshot churn you didn't +intend is a bug to fix, not a snapshot to accept. + +## Releasing + +Record user-visible changes in the top section of [CHANGELOG.md](../CHANGELOG.md) as you go. +Pushing a `v*` tag triggers `.github/workflows/release.yml`, which runs the tests and publishes +to PyPI. setupmeta versions itself, so the tag normally comes from +`python setup.py version --bump --commit --push`. diff --git a/docs/contributing.rst b/docs/contributing.rst deleted file mode 100644 index 850b0c9..0000000 --- a/docs/contributing.rst +++ /dev/null @@ -1,77 +0,0 @@ -Contributions are welcome! - -``setupmeta`` has no dependencies, it should work as-is -(standard libs only + obviously setuptools_). - -tox_ is used for building and testing. - -Development -=========== - -To get going locally, simply do this:: - - git clone https://github.com/codrsquad/setupmeta.git - cd setupmeta - - tox -e venv - - # You have a venv now in ./.venv, use it, open it with pycharm etc - source .venv/bin/activate - which python - python setup.py explain - python setup.py version - - # You can use this local setupmeta now on another project's setup.py - # (provided it has setup_requires='setupmeta') - python .../path/to/some/project/setup.py explain - - deactivate - - - -Running the tests -================= - -To run the tests, simply run ``tox``, this will run tests against all python versions you have -locally installed. You can use pyenv_ for example to get python installations. - -Run: - -* ``tox -e py314`` (for example) to limit test run to only one python version. - -* ``tox -e style`` to run style checks only - -* ``tox -e docs`` to verify that the main README.rst renders properly - - -Test coverage -============= - -A coverage report is generated on all ``tox`` runs -(coverage is combined across all the exercised python versions), - -Run this to see the generated html report:: - - open .tox/test-reports/htmlcov/index.html - - -Refreshing the test scenarios -============================= - -If you've modified the code, and the tests don't pass anymore because you added/removed/changed -something, you can refresh the test scenario replays by running:: - - tox -e refreshscenarios - - -Inspect the diff (``git diff``), verify that the changes to -``tests/scenarios/*/expected.txt`` look good before committing - -Note that the tests replay ``tests/scenarios/*`` as well as ``examples/*`` - - -.. _pyenv: https://github.com/pyenv/pyenv - -.. _setuptools: https://github.com/pypa/setuptools - -.. _tox: https://github.com/tox-dev/tox diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..dbed1b9 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,24 @@ +# setupmeta internals + +setupmeta is a setuptools plugin with no runtime dependencies besides setuptools. A project +activates it with `setup_requires="setupmeta"`; setuptools then calls our hooks, which compute a +version from git tags and fill in whatever `setup()` metadata wasn't given explicitly. +Users see the result through three `setup.py` commands: `explain`, `version` and `check`. +What users can do is documented in the [README](../README.rst); these pages cover the inside. + +Where to start reading: + +- `setupmeta/hook.py`: the entry points setuptools calls, short and the root of every flow. +- `SetupMeta.finalize()` in `setupmeta/model.py`: the auto-fill pipeline, in order. +- `Versioning` in `setupmeta/versioning.py`: computing and bumping versions. + +Pages: + +- [setuptools integration](./setuptools-integration.md): how and when setuptools hands us control. +- [Auto-fill](./autofill.md): how values are found, ranked, and reported by `explain`. +- [Versioning](./versioning.md): strategies, SCM backends, and the bump flow. +- [Bootstrap](./bootstrap.md): why our own `setup.py` looks the way it does. +- [Contributing](./contributing.md): tests, scenario snapshots, coverage, releasing. +- [Known gaps](./known-gaps.md): open TODOs and planned removals. + +Release notes live in [CHANGELOG.md](../CHANGELOG.md). diff --git a/docs/known-gaps.md b/docs/known-gaps.md new file mode 100644 index 0000000..0dc50f5 --- /dev/null +++ b/docs/known-gaps.md @@ -0,0 +1,12 @@ +# Known gaps + +- **`setup.py` is on its way out.** `setup_requires` is deprecated by setuptools, and + `pyproject.toml` is declarative, so it leaves nothing to auto-fill. The plan is to keep + git-tag versioning and bumps as the core, and to phase out the custom commands and metadata + auto-fill. Until then, legacy `setup.py` users must keep working. +- **Only git is supported.** Another SCM would be a new `Scm` subclass in `setupmeta/scm.py`, + picked up by `project_scm()` in `setupmeta/versioning.py`. +- **`versioning` isn't validated** when registered (TODO in `register_keyword()`), only once the + version gets computed. +- **`pinned.txt`** is still read for `install_requires`, as a legacy fallback to phase out. +- **`setupmeta.model.get_pip()`** is dead code kept for external callers that import it. diff --git a/docs/pep-440.rst b/docs/pep-440.rst deleted file mode 100644 index d2220b2..0000000 --- a/docs/pep-440.rst +++ /dev/null @@ -1,26 +0,0 @@ -PEP-440 Compliance changes -========================== - -``setupmeta`` v2.9.1 corrects yielded versions so they are PEP-440 compliant. - -This does change how yielded versions look like, -especially if you use complex ones like ``build-id``. - -What remains unchanged -====================== - -The "main part" of your version will remain unchanged for all strategies except ``devcommit``. - - -What's changed -============== - -* ``devcommit``: ``.dev`` will NOT be alone anymore, but will yield a number indicating - the number of commits since last tag: ``.devN``). - -* ``build-id`` and any strategy that wants to show the "commit id": - the "local" part of your version will now **always** be shown - -* ``separator`` advanced argument is retired (you'll get a deprecation warning if you specify it) - -* Any separator other than ``"+"`` will generate a deprecation warning diff --git a/docs/requirements.rst b/docs/requirements.rst deleted file mode 100644 index f789b54..0000000 --- a/docs/requirements.rst +++ /dev/null @@ -1,124 +0,0 @@ -Auto-fill for dependency requirements -===================================== - -This only applies if you let setupmeta auto-fill your **install_requires**. -If you specify that section explicitly as in:: - - setup( - ... - install_requires=... whatever you want ... - ... - ) - -Then setupmeta will **not** auto-fill anything. - -It's recommended that you let setupmeta auto-fill your requirements -if the following applies to you: - -- you don't have complex dependencies (like 99% of projects out there) - -- you like to `not repeat yourself`_ - -- you agree with this `community recommendation`_, ie: - - - in general your **install_requires** should not be pinning dependencies to specific versions - - - you may want to pin to specific versions when building/packaging (in ``requirements.txt``) - -Note: **tests_require** was auto-filled up to setupmeta 3.2.0, it has been deprecated in -setuptools 41.5.0 (Oct 2019). As of setupmeta 3.3.0, that field is not auto-filled anymore. - - -How it works -============ - -Setupmeta auto-fills dependency/requirements from the contents of the following files: - -- **install_requires** from: - - - ``requirements.in`` (if present, ``.in`` files are NOT abstracted, used as-is) - - - ``requirements.txt`` (recommended) - - - ``pinned.txt`` (deprecated... some old projects used that convention for some reason, will be removed) - - -Your requirement file is parsed and used to auto-fill the **install_requires**: - -- ``.in`` files are used as-is, not abstracted by default - -- comments are stripped, but used as hints as to how to auto-fill - -- there are 3 categories of dependencies that setupmeta will consider: - - - **abstract**: (default) minimal dependency, not bound to any specific version, - example: ``requests`` - - - **pinned**: explicit dependency, example: ``requests==2.19.1`` - - - **indirect**: transitive dependency, not mentioned in **install_requires**, - but will be pinned to a specific version when building/packaging - -- abstracting away applies only to simple ``==`` pinning, and nothing else, ie: - - - ``click==6.7`` will be abstracted as click - - - however ``click>=6.7`` will not be abstracted in any case - -- sections of ``requirements.txt`` (or equivalent) can be marked via a comment line, - all entries below that line (and up to next section) will have the stated category, - for example:: - - # pinned - arrow==0.12.1 - click==6.7 - -- you can also quickly mark any given line explicitly, such as:: - - arrow==0.12.1 - click==6.7 # pinned - - -- by default (if no hint comments are specified), dependencies are considered **abstract** - - - -Example -------- - -``requirements.txt``:: - - arrow==0.12.1 - click==6.7 - requests==2.19.1 # pinned - retry>=0.9 - - # pinned - attrs==18.1.0 - boto3==1.7.48 - - # indirect - botocore==1.10.48 - -In the above example, we have: - -- the first section is considered **abstract** by default, - so ``arrow`` and ``click`` will be auto-filled without their pinned versions - -- ``requests==2.19.1`` will be auto-filled as pinned, - due to the ``# pinned`` explicit comment on that line - -- ``retry>=0.9`` will be auto-filled as-is, since it's not a simple ``==`` pin - (even though it is in the default abstract section) - -- ``attrs==18.1.0`` and ``boto3==1.7.48`` will be auto-filled as pinned - due to them being under the ``# pinned`` section - -- finally, ``botocore`` will not be auto-filled, due it appearing in a **indirect** section - - -.. _not repeat yourself: https://en.wikipedia.org/wiki/Don%27t_repeat_yourself - -.. _community recommendation: https://packaging.python.org/discussions/install-requires-vs-requirements/ - -.. _setuptools documentation: https://setuptools.readthedocs.io/en/latest/setuptools.html diff --git a/docs/setuptools-integration.md b/docs/setuptools-integration.md new file mode 100644 index 0000000..6e1caea --- /dev/null +++ b/docs/setuptools-integration.md @@ -0,0 +1,30 @@ +# setuptools integration + +The entry points are declared in `ENTRY_POINTS` in our [setup.py](../setup.py) (see +[bootstrap](./bootstrap.md) for why they're explicit), and all land in `setupmeta/hook.py`: + +1. `finalize_dist()` runs as a `setuptools.finalize_distribution_options` hook, with + `order = -100` so it runs before other finalizers. It does nothing unless `setup_requires` + mentions `setupmeta`: projects that merely have setupmeta installed are left alone. + It runs *before* `setup.cfg` is read, so it only does `SetupMeta.preprocess()`: resolving + `install_requires="@file"` style values, which must become lists before setuptools + validates them. +2. It then swaps in our `parse_command_line()` on that one `Distribution` instance. That runs + after config files were read and before any command, which is when `SetupMeta.finalize()` + does the real auto-fill. This is why values from `setup.cfg` count as explicit. +3. `register_keyword()` registers the `versioning` keyword, and retries step 1 for contexts + where `setup_requires` gets populated late. + +`MetaDefs.set_field()` writes values to both `dist.metadata` and `dist`: setuptools 68.2+ reads +some fields from one and some from the other. + +The project directory is not the cwd: `SetupMeta.find_project_dir()` finds the `setup.py` +being run from the call stack (or `sys.argv`), and stores it in `MetaDefs.project_dir`, which +every file lookup goes through (`setupmeta.project_path()`). + +Commands are classes decorated with `@MetaCommand` (`setupmeta/commands.py`), which gives them +`self.setupmeta`. It is `None` when the project doesn't use setupmeta; `check` then falls back to +setuptools' own behavior, since we override that command for everyone who has us installed. + +setuptools deprecated `setup_requires` (it warns about `fetch_build_eggs`), so this activation +mechanism is on borrowed time, see [known gaps](./known-gaps.md). diff --git a/docs/versioning.md b/docs/versioning.md new file mode 100644 index 0000000..2d892f0 --- /dev/null +++ b/docs/versioning.md @@ -0,0 +1,37 @@ +# Versioning + +Three layers, all driven by `Versioning` (`setupmeta/versioning.py`): + +- `Strategy` parses the user's `versioning=` value (`_parsed_versioning()`): a preconfigured + name from `PRECONFIGURED` (aliases `default` and `tag` mean `post`), a format string, or a + dict. Each `{...}` becomes a `VersionBit`. Bits not in `MAIN_BITS` get moved from the main to + the local part, to stay PEP-440 compliant. +- `Version` (`setupmeta/scm.py`) is the parsed `git describe` output: tag components, distance, + commit id, dirty flag. Its `post` / `dev` / `devcommit` properties render those bits. +- `Scm` subclasses talk to the source of truth, picked by `project_scm()`: + `Snapshot` when the `SCM_DESCRIBE` env var is set, else `Git` for the nearest `.git` found + walking *up* from the project dir, else `Snapshot` reading `.setupmeta.version`. + +Surprises worth knowing: + +- The nearest parent `.git` wins, even an unrelated one: a test project created inside our + checkout (eg: under `_tmp/`) reports setupmeta's own version (verified). +- `dev` and `devcommit` render a bumped patch (`Strategy.rendered()` auto-bumps the bit before a + trailing `{dev}`), so a dirty checkout exactly on `v0.1.0` gives `0.1.1.dev0+dirty`. +- With `distance` and `build-id`, a tag like `v0.1.3` yields `0.1.0` plus a warning: the + patch component is the distance, so it can't be bumped either. +- A repo with no tag, or no commit yet, yields `0.0.0` rather than an error; `Git.get_version()` + then counts commits itself. +- Only tags matching `v*.*` count by default: a repo tagged `1.2.0` reports `0.0.0`. + +Bumping is `Versioning.bump()`: it checks branch, pending changes and remote tags, rewrites any +`__version__`-style line it found in `.py` sources (`update_sources()`, by recorded file:line), +commits that with `--no-verify`, then `Git.apply_tag()` refuses if the branch is behind its +remote, and creates an annotated `v` tag. Every git call goes through `Git.run_git()`, +which prints instead of running when `--commit` isn't given. + +`.setupmeta.version` is written whenever the git root isn't the project dir, so that a copy of +the project without `.git` (pip builds in a temp dir) still knows its version. + +Tests: `tests/test_versioning.py` mocks git via `conftest.MockGit`; scenario snapshots cover the +end-to-end output, see [contributing](./contributing.md). diff --git a/docs/versioning.rst b/docs/versioning.rst deleted file mode 100644 index e31814e..0000000 --- a/docs/versioning.rst +++ /dev/null @@ -1,470 +0,0 @@ -Support for versioning -====================== - -setupmeta can use git tags to automatically **version** and **bump** the version of your project. - -The functionality is optional and has to be explicitly enabled, note that: - -* SCM tags are used for versioning - -* only git is supported currently - (contributions for other SCMs welcome and should be easy, see `Scm class`_) - -* It can be a drop-in replacement for setuptools_scm_ - -* 5 strategies are pre-configured: post_, dev_, distance_, devcommit_ and build-id_, - they yield versions that play well with PEP-440_ (while remaining very simple). - -* See Advanced_ section for more info - -In order to use setupmeta as a bridge to your git tags as versions, -activate the feature by specifying one of the strategies in your ``setup.py`` like so:: - - setup( - versioning="post", - ... - ) - -You can use then ``python setup.py version --bump`` to bump major/minor/patch -(no need to assign tags manually). - -Note that if you still explicitly mention a ``__version__ = "..."``` in your ``__init__.py`` -or ``__about__.py`` etc, setupmeta will find it and also bump it accordingly. -This is done for convenience only, you don't need ``__version__`` anywhere if you use -setupmeta versioning. - - -How does it work? -================= - -**Note**: ``setupmeta``'s versioning is based on (by default):: - - git describe --dirty --tags --long --first-parent --match 'v*.*' - -you will need **git version >= 1.8.4** if you wish to use ``setupmeta``'s versioning capabilities. - -You can modify the above command via environment variable ``SETUPMETA_GIT_DESCRIBE_COMMAND`` -(give full git command if you do). - ----- - -setupmeta declares a keyword to setuptools called ``versioning``, if you specify that keyword -(and it is valid), setupmeta versioning will be enabled. - -You can specify ``versioning``: - -* explicitly in your ``setup()`` call - -* implicitly in your ``__init__.py``, ``__about__.py``, docstrings etc - (setupmeta will show where ``versioning`` was defined, like any other value it handles) - -Tag-based versioning will take precedence on any other ``version`` specification you may have, -and ``setup.py version --bump`` will modify those over values for ``version`` -that you have (if any) accordingly. - -For example, if you have this: - -* ``setup(versioning="post")`` in your ``setup.py`` - -* ``__version__ = "1.0.0"`` in your ``__init__.py`` line 7 - -* ``git describe`` yields ``v1.0.0-2-g123`` - (ie: latest tag is "v1.0.0", there are 2 commits since that tag, and current commit id is "123") - -Now, running ``setup.py explain`` will show you something like this:: - - version: (git ) 1.0.0.post2+dirty - \_: (__init__.py:7) 1.0.0 - -Which means that determined version of your project (from git tag) is ``1.0.0.post2+dirty``, -while ``__init_.py`` line 7 states it is "1.0.0". - -If you commit your changes, your checkout won't be dirty anymore, -and the number of commits since latest tag will be 3, so ``setup.py explain`` will now show:: - - version: (git ) 1.0.0.post3 - \_: (__init__.py:7) 1.0.0 - -Ie: - -* deduced version is 1.0.0, with 3 commits since latest tag - -* checkout is clean, so the "extra" part is not shown (by default) - -* ``__init__.py`` being static, it does not reflect this yet - (but will if/when you ``setup.py version --bump``) - -* note that this versioning scheme should play well with PEP-440_ - -If you now run ``setup.py version --bump patch --commit``, the following would happen: - -* your ``__init__.py`` line 7 is modified to state ``__version__ = "1.0.1"``, - and committed with description ``"Version 1.0.1"`` - -* tag ``v1.0.1`` is applied at that new commit - -* now ``setup.py explain`` says:: - - version: (git ) 1.0.1 - \_: (__init__.py:7) 1.0.1 - -Note that you do NOT need any ``__version__ = ...`` stated anywhere, we're showing this only -here for illustration purposes. In general, you should simply use ``versioning="post"`` -(or any other format you like). - -You could leverage this ``__version__`` possibility if you have specific use case for that -(like: you'd like to show which version your code is at without having to do any -dynamic query) - - -Preconfigured formats -===================== - -post ----- - -This is well suited if you don't plan to publish often, and have a tag for each release. - -``post`` corresponds to this format: ``branch(main,master):{major}.{minor}.{patch}{post}+{dirty}`` - -State this in your ``setup.py``:: - - setup( - versioning="post", - ... - ) - -Now, every time you commit a change, setupmeta will use the number of commits since last git -tag to determine the ``post`` part of your version. - - -Example: - -======= ====== ================= ============================================================== -Commit Tag Version Note (command ran to add tag) -======= ====== ================= ============================================================== -no .git 0.0.0 Version defaults to 0.0.0 (when no tag yet) -none 0.0.0+dirty No commit yet (but ``git init`` was ran) -g1 0.0.0.post1 Initial commit -g1 0.0.0.post1+dirty Same as above, only checkout was not clean anymore -g2 0.0.0.post2 -g3 0.0.0.post3 -g4 v0.1.0 0.1.0 ``version --bump minor --commit`` -g5 0.1.0.post1 (1 commit since tag) -g6 0.1.0.post2 -g7 v0.1.1 0.1.1 ``version --bump patch --commit`` -g8 0.1.1.post1 -g9 v1.0.0 1.0.0 ``version --bump major --commit`` -g10 1.0.0.post1 -======= ====== ================= ============================================================== - -* Without any tag, version defaults to ``0.0.0`` - -* First tag here is ``v0.1.0``, ``git describe`` will yield ``v0.1.0`` - (no commits since last tag), and setupmeta will consider version to be ``0.1.0`` (tag as-is) - -* A commit occurs and doesn't add a git tag, version for that commit will be ``0.1.0.post1`` - (tag 0.1.0 with 1 change since tag) - -* A 2nd commit doesn't add a git tag, version for that commit will be ``0.1.0.post2`` etc - -* Dirty checkouts will get a version of the form ``0.1.0.post2+dirty`` - -* Use ``python setup.py version --bump [major|minor|patch]`` whenever you want to bump major, - minor or patch revision (this will assign a git tag accordingly) - - * ``python setup.py version --bump patch --commit`` -> tag "v0.1.1" is added, - version is now ``0.1.1`` - - * Next commit after that will be version ``0.1.1.post1`` etc - - -dev ---- - -Similar to post_, with the following differences: - -* ``.dev`` prefix is used instead of ``.post``, this makes untagged versions considered - pre-release (have to use ``pip install --pre`` to get them) - -* Right-most bumpable component (typically **patch**) is assumed to be the next one - that is going to be bumped... (this just means that if your current version is ``0.8.1``, - you would get a ``0.8.2.dev1`` etc; even though you may be planning your next tag to be - ``0.9.0``, and not ``0.8.2``) - -Example: - -======= ====== ================ =============================================================== -Commit Tag Version Note (command ran to add tag) -======= ====== ================ =============================================================== -no .git 0.0.0.dev0 Version defaults to 0.0.0 (when no tag yet) -none 0.0.0.dev0+dirty No commit yet (but ``git init`` was ran) -g1 0.0.0.dev1 Initial commit -g1 0.0.0.dev1+dirty Same as above, only checkout was not clean anymore -g2 0.0.0.dev2 -g3 0.0.0.dev3 -g4 v0.1.0 0.1.0 ``version --bump minor --commit`` -g5 0.1.1.dev1 (1 commit since tag) -g6 0.1.1.dev2 -g7 v0.1.1 0.1.1 ``version --bump patch --commit`` -g8 0.1.2.dev1 -g9 v1.0.0 1.0.0 ``version --bump major --commit`` -g10 1.0.0.dev1 -======= ====== ================ =============================================================== - -devcommit ---------- - -Same as dev_, with commit id added in ``local`` part when not exactly on a version tag. - -Example: - -======= ====== =================== ============================================================ -Commit Tag Version Note (command ran to add tag) -======= ====== =================== ============================================================ -g1 0.0.0.dev1+g1 Initial commit -g1 0.0.0.dev1+g1.dirty Same as above, only checkout was not clean anymore -g2 0.0.0.dev2+g2 -g3 0.0.0.dev3+g3 -g4 v0.1.0 0.1.0 ``version --bump minor --commit`` -g5 0.1.1.dev1+g5 (1 commit since tag) -g6 0.1.1.dev2+g6 -g7 v0.1.1 0.1.1 ``version --bump patch --commit`` -g8 0.1.2.dev1+g7 -g9 v1.0.0 1.0.0 ``version --bump major --commit`` -g10 1.0.0.dev1+g10 -======= ====== =================== ============================================================ - - -distance --------- - -This is well suited if you want to publish a new version at every commit (but don't want to keep -bumping version in code for every commit). - -``distance`` corresponds to this format: ``branch(main,master):{major}.{minor}.{distance}+{dirty}`` - -State this in your ``setup.py``:: - - setup( - versioning="distance", - ... - ) - - -Now, every time you commit a change, setupmeta will use the number of commits since last git tag -to determine the 'patch' part of your version. - - -Example: - -======= ====== ================ =============================================================== -Commit Tag Version Note (command ran to add tag) -======= ====== ================ =============================================================== -no .git 0.0.0 Version defaults to 0.0 (when no tag yet) -none 0.0.0+dirty No commit yet (but ``git init`` was ran) -g1 0.0.1 Initial commit, 0.0.1 means default v0.0 + 1 change -g1 0.0.1+dirty Same as above, only checkout was not clean anymore -g2 0.0.2 -g3 0.0.3 -g4 v0.1.0 0.1.0 ``setup.py version --bump minor --commit`` -g5 0.1.1 (1 commit since tag) -g6 0.1.2 -g7 0.1.3 -g8 v0.2.0 0.2.0 ``setup.py version --bump minor --commit`` - (note: can't bump "patch part" with this format) -g9 0.2.1 -g10 v1.0.0 1.0.0 ``setup.py version --bump major --commit`` -g11 1.0.1 -======= ====== ================ =============================================================== - -* Without any tag, version defaults to ``0.0.*`` - -* First tag here is ``v0.1``, ``git describe`` will yield ``v0.1.0`` (no commits since last tag), - and setupmeta will consider version to be ``0.1.0`` (tag 0.1 with 0 commits) - -* A commit occurs and doesn't add a git tag, version for that commit will be ``0.1.1`` - (tag 0.1 with 1 change since tag) - -* A 2nd commit occurs and doesn't add a git tag, version for that commit will be ``0.1.2`` etc - -* Dirty checkouts will get a version of the form ``0.1.2+dirty`` - -* Use ``python setup.py version --bump [major|minor]`` whenever you want to bump major - or minor version (this will assign a git tag accordingly) - - * ``python setup.py version --bump minor --commit`` -> tag "v0.2" is added, - version is now ``0.2.0`` - - * Next commit after that will be version ``0.2.1`` etc - - -build-id --------- - -This is similar to distance_ (described above), so well suited if you want to publish a new -version at every commit, but also want maximum info in the version identifier. - -``build-id`` corresponds to this format: -``branch(main,master):{major}.{minor}.{distance}+h{$*BUILD_ID:local}.{commitid}{dirty}`` - -State this in your ``setup.py``:: - - setup( - versioning="build-id", - ... - ) - - -Example: - -======= ====== =========================== ==================================================== -Commit Tag Version Note (command ran to add tag) -======= ====== =========================== ==================================================== -no .git 0.0.0+hlocal.g0000000 Version defaults to 0.0 (when no tag yet) -none 0.0.0+hlocal.g0000000.dirty No commit yet (but ``git init`` was ran) -g1 0.0.1+hlocal.g1 Initial commit, built locally - (``$BUILD_ID`` not defined), checkout was clean -g1 0.0.1+hlocal.g1.dirty Same as above, only checkout was not clean anymore -g1 0.0.1+h123.g1 ``$BUILD_ID`` was "123" - (so presumably built on a CI server) -g2 0.0.2+h124.g2 -g3 0.0.3+h125.g3 -g4 v0.1.0 0.1.0+hlocal.g4 ``version --bump minor --commit``, clean, - built locally -g5 0.1.1+h130.g3 (1 commit since tag) -g6 0.1.2+h140.g3 -g7 v0.2.0 0.2.0+h150.g3 ``version --bump minor --commit`` - (note: can't bump "patch" with this format) -g8 0.2.1+h160.g3 -g9 v1.0.0 1.0.0+h200.g3 ``version --bump major --commit`` -g10 1.0.1+h300.g3 -======= ====== =========================== ==================================================== - -* Similar to distance_, except that the ``extra`` part is always shown and will reflect whether - build took locally or on a CI server (which will define an env var ending with ``BUILD_ID``) - - -Advanced -======== - -``versioning`` can be customized beyond the above pre-defined strategies described above, -it can be passed as a **string** describing the version format, -or a **dict** for even more customization: - -* a **string** can be of the form: - - * a version format specified of the form ``branch():
+`` - - * ``branch():`` is optional, and you would use this full form only if you wanted - version bumps to be possible on branches other than ``main`` or ``master``, - if you want bumps to be possible on both ``prod`` and ``test`` branches for example, - you would use ``branch(prod,test):...`` - - * See Formatting_ below to see what's usable for ``
`` and ```` - - * the ``
`` part (before the ``"+"`` character) specifies the format of the - "main version" part (when checkout is clean) - - * the ```` part (after the ``"+"`` character) indicates the ``local`` part of - your version, as per PEP-440_ - -* a **dict** with the following keys: - - * ``main``: a **string** (see Formatting_) or callable - (if callable given, **version --bump** functionality becomes unusable) - - * ``extra``: a **string** (see Formatting_) or callable (custom function yielding - a string from a given ``Version``, see `Scm class`_) - - * ``branches``: list of branch names (or csv) where to allow **bump** - - -This is what ``versioning="post"`` is a shortcut for:: - - setup( - versioning={ - "main": "{major}.{minor}.{patch}{post}", - "extra": "{dirty}", - "branches": ["main"], - "version_tag": "v*.*", - }, - ... - ) - -``version_tag`` is the glob pattern of git tags to consider as version tags. -The default form is ``v*.*`` (ie: git tags that start with ``v`` and have a dot in them). - -Ideally, git would allow to state a full regex, as only tags that match this regex -would ideally be considered as version tags: ``^v?\d+\.\d+(\.\d+)?$``, however this is not -possible with git (if it is possible, setupmeta will be upgraded to simplify things by using -this regex, in which case the ``version_tag`` setting will be sunset). - - -Formatting ----------- - -The following can be used as format specifiers: - -* ``{major}``: Major part of version - -* ``{minor}``: Minor part of version - -* ``{patch}``: Patch part of version - -* ``{distance}``: Number of commits since last version tag from current commit - (0 if current commit is tagged) - -* ``{post}``: Designates a "post" release, empty when current commit - is version-tagged, otherwise ``.postN`` (where ``"N"`` is ``{distance}``) - -* ``{dev}``: Designates a "dev" release, empty when current commit is - version-tagged, otherwise ``[+1].devN`` (where ``"N"`` is ``{distance}``, a ``[+1]`` is - the next revision of the right-most bumpable, usually ``patch``). - Example: ``1.2.dev3``. - -* ``{devcommit}``: Same as ``{dev}``, with commit id added in ``local`` version part - when not exactly on version tag. - Example: ``1.2.dev3+g12345``. - -* ``{commitid}``: short string identifying commit, like ``g3bf9221`` - -* ``{dirty}``: Expands to ``.dirty`` when checkout is dirty (has pending changes), - empty string otherwise - -* Convenience notations: the following shortcuts can be used for the local part of the - version: - - * ``+devcommit``: Use the stated strategy, but add ``{devcommit}`` to the local part - - * ``+build-id``: Use the stated strategy, but add the same info from build-id_ strategy - to the local part - - * Example: ``dev+devcommit``, or ``post+build-id`` etc - - -* ``foo``: constant ``foo`` (used as-is if specified) - -* ``{$FOO}``: value of environment variable ``FOO`` (string ``None`` if not defined) - -* ``{$BUILD_ID:local}``: value of environment variable ``BUILD_ID`` if defined, - constant ``local`` otherwise - -* generalized env var spec is: ``{$*FOO*:default}``: - - * ``$FOO`` will look for env var ``FOO`` exactly - - * ``$*FOO`` will use the first (alphabetically sorted) env var that ends with ``FOO`` - - * ``$FOO*`` will use the first (alphabetically sorted) env var that starts with ``FOO`` - - * ``$*FOO*`` will use the first (alphabetically sorted) env var that contains ``FOO`` - - * ``default`` will be shown if no env var could be found - - -.. _PEP-440: https://www.python.org/dev/peps/pep-0440/ - -.. _setuptools_scm: https://github.com/pypa/setuptools_scm - -.. _Scm class: https://github.com/codrsquad/setupmeta/blob/main/setupmeta/scm.py diff --git a/examples/README.rst b/examples/README.rst index 491b524..032e154 100644 --- a/examples/README.rst +++ b/examples/README.rst @@ -11,16 +11,20 @@ See each subfolder's README for more info on each example: * single_: an example python module laid out as a ``single.py`` file +* via-cfg_: metadata stated in ``setup.cfg``, with a version read from a ``VERSION`` file + Test cases ========== -These examples are also used as test case scenarios, see `test scenarios`_ +These examples are also replayed as test scenarios, see contributing_ -.. _test scenarios: https://github.com/codrsquad/setupmeta/blob/main/tests/scenarios +.. _contributing: https://github.com/codrsquad/setupmeta/blob/main/docs/contributing.md .. _direct: https://github.com/codrsquad/setupmeta/tree/main/examples/direct .. _hierarchical: https://github.com/codrsquad/setupmeta/tree/main/examples/hierarchical .. _single: https://github.com/codrsquad/setupmeta/tree/main/examples/single + +.. _via-cfg: https://github.com/codrsquad/setupmeta/tree/main/examples/via-cfg diff --git a/pyproject.toml b/pyproject.toml index c71a200..56512fc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ line-length = 140 [tool.ruff.lint] ignore = ["RUF005", "RUF012"] -extend-select = [ +select = [ "A", # flake8-builtins "ARG", # flake8-unused-arguments "B", # flake8-bugbear diff --git a/setup.py b/setup.py index 31d711a..32a8786 100644 --- a/setup.py +++ b/setup.py @@ -26,7 +26,7 @@ def run_bootstrap(message): sys.stderr.write(f"--- Bootstrapping {message}\n") - p = subprocess.Popen([sys.executable, "setup.py", "egg_info"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) # noqa: S603 + p = subprocess.Popen([sys.executable, "setup.py", "egg_info"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) output, error = p.communicate() if p.returncode: print(output) @@ -52,7 +52,7 @@ def complete_args(args): "name": "setupmeta", "entry_points": ENTRY_POINTS, "packages": ["setupmeta"], - "python_requires": ">=3.7", + "python_requires": ">=3.10", "zip_safe": True, "classifiers": [ "Development Status :: 5 - Production/Stable", @@ -62,14 +62,12 @@ def complete_args(args): "Operating System :: Unix", "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", + "Programming Language :: Python :: 3.15", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Software Development :: Build Tools", diff --git a/setupmeta/scm.py b/setupmeta/scm.py index 41bda7c..cfc2178 100644 --- a/setupmeta/scm.py +++ b/setupmeta/scm.py @@ -177,19 +177,13 @@ def git_describe_output(self): return self.git_output(*cmd) version_tag = self.version_tag - cmd = ["describe", "--dirty", "--tags", "--long", "--first-parent", "--match"] if version_tag: - # A custom version tag was configured, use it setupmeta.trace("Using configured version_tag: %s" % version_tag) - return self.git_output(*cmd, version_tag) - # No overrides, try v*.* first, then fall back to '*.*' if need be - text = self.git_output(*cmd, "v*.*") - if not text: - # TODO(zsimic): Remove this for setupmeta v4.0 - text = self.git_output(*cmd, "*.*") + else: + version_tag = "v*.*" - return text + return self.git_output("describe", "--dirty", "--tags", "--long", "--first-parent", "--match", version_tag) def get_version(self): text = self.git_describe_output() diff --git a/tests/scenarios/README.rst b/tests/scenarios/README.rst deleted file mode 100644 index ee98387..0000000 --- a/tests/scenarios/README.rst +++ /dev/null @@ -1,22 +0,0 @@ -Test scenarios -============== - -This folder contains test case scenarios. The tests consist of: - -* one scenario folder with a ``setup.py`` and other typical files found in a python project - -* examples_ subfolders are also used as test case scenarios (the cases there are more "vanilla", while the cases here are edge-case oriented) - -* commands ``explain`` and ``version`` are ran on each example, and their output is compared to ``expected.txt`` (has to match) - -* `scenarios.py`_ (or ``tox -e refreshscenarios``) can be used to regenerate ``expected.txt`` (see ``git diff`` to verify changes look good) - -* `test_scenarios.py`_ replays all scenarios and verifies that output matches ``expected.txt`` - - - -.. _examples: https://github.com/codrsquad/setupmeta/tree/main/examples - -.. _test_scenarios.py: https://github.com/codrsquad/setupmeta/blob/main/tests/test_scenarios.py - -.. _scenarios.py: https://github.com/codrsquad/setupmeta/blob/main/tests/scenarios.py diff --git a/tests/test_scm.py b/tests/test_scm.py index b700dfa..6f77c2f 100644 --- a/tests/test_scm.py +++ b/tests/test_scm.py @@ -70,3 +70,22 @@ def test_git_describe_override(monkeypatch): assert v.main_text == "1.2.3" assert v.distance == 4 assert v.commitid == "gabc123" + + +def test_git_version_tag(tmp_path): + conftest.run_git("init", "--initial-branch=main", str(tmp_path)) + conftest.run_git("commit", "--allow-empty", "-m", "Initial commit", cwd=tmp_path) + git = setupmeta.scm.Git(str(tmp_path)) + + # Tags without a leading 'v' are not considered as version tags + conftest.run_git("tag", "-a", "1.2.0", "-m", "Not a version tag", cwd=tmp_path) + assert git.get_version().main_text == "0.0.0" + + conftest.run_git("tag", "-a", "v1.3.0", "-m", "Version 1.3.0", cwd=tmp_path) + assert git.get_version().main_text == "1.3.0" + + # A configured version_tag is used as-is + git.version_tag = "*.*" + conftest.run_git("commit", "--allow-empty", "-m", "Second commit", cwd=tmp_path) + conftest.run_git("tag", "-a", "1.4.0", "-m", "Custom version tag", cwd=tmp_path) + assert git.get_version().main_text == "1.4.0" diff --git a/tox.ini b/tox.ini index 40ab04d..3aee46d 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] # Running tests against min/max only to speed up local iterations (all non-EOL versions tested in GH actions) -envlist = py{37,314}, coverage, docs, style +envlist = py{310,315}, coverage, docs, style # Pointing to pypi.org mirror explicitly to avoid using internal mirrors in place at some companies # This is usually not necessary, but can be useful in some cases (eg: latest setuptools not on internal mirror) indexserver = @@ -11,9 +11,8 @@ setenv = UV_CACHE_DIR={toxworkdir}/.uv-cache COVERAGE_FILE={toxworkdir}/.coverage.{envname} usedevelop = True deps = -rtests/requirements.txt - py37: pip - py37: wheel - py314: pip + py310: pip + py315: pip commands = pytest {posargs:-vv --cov=setupmeta --cov-report=xml tests} [testenv:coverage]