Skip to content

Commit 8c6d467

Browse files
zsimicZoran SimicCopilot
authored
Refactor run_program() function (#95)
* Refactor run_program() function * Reviewed docs * Update tests/scenarios.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Added AGENTS.md * Corrected `clear()` Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Record args correctly Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * AGENTS.md not needed in sdist * Corrected tests * Fine-tunes agent recs --------- Co-authored-by: Zoran Simic <zsimic@netflix.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 4449053 commit 8c6d467

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+677
-849
lines changed

.github/workflows/release.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ jobs:
2222

2323
- name: Configure git
2424
run: |
25-
git config --global user.name "GH-actions-bot"
26-
git config --global user.email "gh-actions-bot@noreply.github.com"
25+
git config --global user.name "Tester"
26+
git config --global user.email "test@example.com"
2727
2828
- uses: astral-sh/setup-uv@v7
2929
- run: uvx --with tox-uv tox -e py,docs,style

.github/workflows/tests.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ jobs:
2828

2929
- name: Configure git
3030
run: |
31-
git config --global user.name "GH-actions-bot"
32-
git config --global user.email "gh-actions-bot@noreply.github.com"
31+
git config --global user.name "Tester"
32+
git config --global user.email "test@example.com"
3333
3434
- uses: astral-sh/setup-uv@v7
3535
- run: uvx --with tox-uv tox -e py
@@ -56,8 +56,8 @@ jobs:
5656

5757
- name: Configure git
5858
run: |
59-
git config --global user.name "GH-actions-bot"
60-
git config --global user.email "gh-actions-bot@noreply.github.com"
59+
git config --global user.name "Tester"
60+
git config --global user.email "test@example.com"
6161
6262
- uses: astral-sh/setup-uv@v7
6363
- run: uvx --with tox-uv tox -e py

AGENTS.md

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# AGENTS.md
2+
3+
## Purpose
4+
5+
This file defines repo-specific guidance for coding agents working on `setupmeta`.
6+
Use it as the first source of truth for how to make changes safely and consistently.
7+
8+
## Project Summary
9+
10+
- `setupmeta` primarily supports git-tag-based versioning and automated version bumps.
11+
- Secondarily, it also:
12+
- Provides custom setup commands: `explain`, `version`, and `check` (this will be phased out
13+
as `setup.py` gets retired in favor of `pyproject.toml`).
14+
- Helps keep `setup.py` files minimal by auto filling metadata and requirements (this will also
15+
be sunset with `pyproject.toml`, as that format is declarative and doesn't lend itself to autofill).
16+
- The project is self-hosted: `setup.py` uses `setupmeta` itself and has bootstrap behavior.
17+
18+
## Core Principles
19+
20+
- Preserve backward compatibility for users relying on legacy `setup.py` workflows.
21+
- Keep runtime dependencies minimal; prefer stdlib for runtime code.
22+
- Favor explicit, test-covered behavior over cleverness.
23+
- Keep documentation aligned with actual behavior and test scenarios.
24+
25+
## Repository Layout
26+
27+
- `setupmeta/`: main library code (`commands`, `model`, `scm`, `versioning`, etc.)
28+
- `tests/`: unit tests + scenario replay tests
29+
- `tests/scenarios/` and `examples/`: behavior fixtures with `expected.txt` snapshots
30+
- `docs/`: user and contributor documentation
31+
- `setup.py`: self-bootstrapping package definition
32+
- `tox.ini`: test/lint/docs/coverage orchestration
33+
34+
## Environment and Commands
35+
36+
Use these commands from repo root:
37+
38+
- Quick tests: `.venv/bin/pytest -q`
39+
- Full test/lint/docs run: `tox`
40+
- Single tox env: `tox -e py314`
41+
- Fast compatibility matrix (old+new + coverage): `tox -e py39,py314,coverage`
42+
- Style only: `tox -e style`
43+
- Docs checks: `tox -e docs`
44+
- Refresh scenario snapshots: `tox -e refreshscenarios`
45+
- Manual command checks:
46+
- `.venv/bin/python setup.py explain`
47+
- `.venv/bin/python setup.py version`
48+
- `.venv/bin/python setup.py check -q`
49+
50+
Notes:
51+
52+
- `tox.ini` pins `UV_CACHE_DIR` to `.tox/.uv-cache`, to help runs in sandboxed environments.
53+
- `py37` tox target exists because it is the oldest Python still supported by this library.
54+
- If `py37` is unavailable locally (common on macOS arm64), substitute the oldest available
55+
interpreter and pair it with the newest one.
56+
Example: system Python `3.9` + `3.14` via `tox -e py39,py314,coverage`.
57+
- Intent: keep local runs fast while still exercising one older runtime and one modern runtime,
58+
with `coverage combine` validating cross-env coverage data.
59+
60+
## Code Change Expectations
61+
62+
- Keep changes narrow and focused.
63+
- Maintain Python compatibility targeted by this repo (including older supported versions).
64+
- When changing behavior, update both tests and docs in the same change.
65+
- Do not silently alter CLI output formats used by scenario snapshots unless intentional.
66+
- If you touch versioning logic, verify:
67+
- `tests/test_versioning.py`
68+
- `tests/test_setup_py.py`
69+
- scenario outputs that include `version`/`explain`.
70+
71+
## Scenario Snapshot Rules
72+
73+
- Scenario tests compare command output against `expected.txt`.
74+
- If behavior changes are intentional:
75+
1. Regenerate snapshots (`tox -e refreshscenarios`).
76+
2. Review diffs in `tests/scenarios/*/expected.txt` and `examples/*/expected.txt`.
77+
3. Ensure docs and release notes explain user-visible changes.
78+
- If behavior changes are not intentional, fix code/tests instead of accepting snapshot churn.
79+
80+
## Documentation Rules
81+
82+
- Keep `README.rst`, `docs/*.rst`, and example READMEs consistent with current behavior.
83+
- Avoid documenting deprecated/removed commands as active features.
84+
- Keep versioning docs aligned with current strategy defaults and command examples.
85+
- Record user-visible changes in `HISTORY.rst`.
86+
87+
## Safety and Review Checklist
88+
89+
Before finalizing a change, verify:
90+
91+
1. Tests pass for affected areas.
92+
2. Formatting/lint checks pass.
93+
3. Scenario snapshots are unchanged unless intentionally updated.
94+
4. Docs are updated if behavior or commands changed.
95+
5. No unrelated files were modified.
96+
97+
## When to Ask for Clarification
98+
99+
Ask the maintainer before proceeding if:
100+
101+
- A change could break backward compatibility.
102+
- Expected output changes are broad/noisy and intent is unclear.
103+
- A docs update conflicts with tested behavior.
104+
- A refactor touches bootstrap or version bump internals in `setup.py`, `setupmeta/hook.py`, `setupmeta/versioning.py`, or `setupmeta/scm.py`.

HISTORY.rst

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,24 @@
22
Release notes
33
=============
44

5-
3.9.0 (2026-02-09)
5+
3.9.0 (2026-02-17)
66
------------------
77

88
* Test with py3.14, publish sdist with py3.13
99

10+
* Removed commands: ``entrypoints``, ``cleanall``
11+
1012
* Removed post-version-bump hook support (unused, unnecessary, and was not well documented)
1113

12-
* Internal project modernizations (use ``uv``, ``ruff``, enabled more linter rules, etc)
14+
* Removed old ``register`` hook used with setuptools v50
15+
16+
* Removed support for pygradle-style versioning
17+
18+
* Internal project modernizations:
19+
20+
* Refactored usage of ``subprocess.run()``, removed last left overs from the py2 days
21+
22+
* use ``uv``, ``ruff``, enabled more linter rules, etc
1323

1424
* Use coveralls_ for test coverage reporting
1525

README.rst

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,6 @@ See examples_ for more.
6565

6666
git describe --dirty --tags --long --first-parent --match 'v*.*'
6767

68-
# Then, if above yields nothing, we try the more vague '*.*'
69-
70-
git describe --dirty --tags --long --first-parent --match '*.*'
71-
7268
you will need **git version >= 1.8.4** if you wish to use ``setupmeta``'s versioning capabilities.
7369

7470

@@ -84,7 +80,7 @@ The goal of this project is to:
8480
* Support tag-based versioning_ (like setuptools_scm_, but with super simple configuration/defaults and automated ``bump`` capability)
8581

8682
* Provide useful Commands_ to see the metadata (**explain**), **version** (including support for bumping versions),
87-
**cleanall**, etc
83+
and **check**
8884

8985

9086
How it works?
@@ -102,12 +98,10 @@ How it works?
10298

10399
* ``entry_points`` is auto-filled from file ``entry_points.ini`` (bonus: tools like PyCharm have a nice syntax highlighter for those)
104100

105-
* ``install_requires`` is auto-filled if you have a ``requirements.txt`` (or ``pinned.txt``) file,
101+
* ``install_requires`` is auto-filled from ``requirements.in`` (preferred), then ``requirements.txt``
102+
(or ``pinned.txt`` for older projects),
106103
pinning is abstracted away by default as per `community recommendation`_, see requirements_ for more info.
107104

108-
* ``tests_require`` is auto-filled if you have a ``tests/requirements.txt``, or ``requirements-dev.txt``,
109-
or ``dev-requirements.txt``, or ``test-requirements.txt`` file
110-
111105
* ``description`` will be the 1st line of your README (unless that 1st line is too short, or is just the project's name),
112106
or the 1st line of the first docstring found in the scanned files (see list below)
113107

@@ -130,7 +124,7 @@ How it works?
130124

131125
* tag "v1.0.0", 5 commits since tag -> version is "1.0.5"
132126

133-
* if checkout is dirty, a marker is added -> version would be "1.0.5.post5.dirty"
127+
* if checkout is dirty, a marker is added -> version would be "1.0.5+dirty"
134128

135129
* With ``versioning="post"``, your git tags will be of the form ``v{major}.{minor}.{patch}``,
136130
a "post" addendum will be present if there are commits since latest version tag:
@@ -139,7 +133,7 @@ How it works?
139133

140134
* tag "v1.0.0", 5 commits since tag -> version is "1.0.0.post5"
141135

142-
* if checkout is dirty, a marker is added -> version would be "1.0.0.post5.dirty"
136+
* if checkout is dirty, a marker is added -> version would be "1.0.0.post5+dirty"
143137

144138
* With ``versioning="build-id"``, your git tags will be of the form ``v{major}.{minor}.0``,
145139
the number of commits since latest version tag will be used to auto-fill the "patch" part of the version:
@@ -154,7 +148,7 @@ How it works?
154148

155149
* if checkout is dirty, a marker is added -> version would be "1.0.5+hlocal.g456.dirty"
156150

157-
* Use the **bump** command (see commands_) to easily bump (ie: increment major, minor or patch + apply git tag)
151+
* Use the **version** command (see commands_) to easily bump (ie: increment major, minor or patch + apply git tag)
158152

159153
* Version format can be customized, see versioning_ for more info
160154

docs/commands.rst

Lines changed: 16 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -18,21 +18,23 @@ For example, this is what setupmeta says about itself (it's self-using)::
1818
author: (auto-adjust ) Zoran Simic
1919
\_: (setupmeta/__init__.py:6) Zoran Simic zoran@simicweb.com
2020
author_email: (auto-adjust ) zoran@simicweb.com
21-
classifiers: (classifiers.txt ) 21 items: ["Development Status :: 5 - Production/Stable", "Intend...
21+
bugtrack_url: (auto-fill ) https://github.com/codrsquad/setupmeta/issues
22+
classifiers: (explicit ) 23 items: ["Development Status :: 5 - Production/Stable", "Intend...
2223
description: (setupmeta/__init__.py:2) Simplify your setup.py
23-
download_url: (auto-fill ) https://github.com/codrsquad/setupmeta/archive/v2.1.1.tar.gz
24+
download_url: (auto-fill ) https://github.com/codrsquad/setupmeta/archive/v3.9.0.tar.gz
2425
\_: (setupmeta/__init__.py:5) archive/v{version}.tar.gz
25-
entry_points: (explicit ) [distutils.commands] check = setupmeta.commands:CheckCommand clea...
26-
keywords: (setup.py:4 ) ["simple", "DRY", "setup.py"]
26+
entry_points: (explicit ) [distutils.commands] check = setupmeta.commands:CheckCommand expla...
27+
include_package_data: (MANIFEST.in ) True
28+
install_requires: (explicit ) ["setuptools>=67"]
2729
license: (auto-fill ) MIT
2830
long_description: (README.rst ) Simplify your setup.py ====================== .. image:: https://...
2931
long_description_content_type: (README.rst ) text/x-rst
30-
name: (setup.py:16 ) setupmeta
31-
packages: (auto-fill ) ["setupmeta"]
32+
name: (explicit ) setupmeta
33+
packages: (explicit ) ["setupmeta"]
34+
python_requires: (explicit ) >=3.7
3235
setup_requires: (explicit ) ["setupmeta"]
33-
title*: (setup.py:16 ) setupmeta
3436
url: (setupmeta/__init__.py:4) https://github.com/codrsquad/setupmeta
35-
version: (git ) 2.1.1
37+
version: (git ) 3.9.0
3638
versioning: (explicit ) dev
3739
zip_safe: (explicit ) True
3840

@@ -48,9 +50,9 @@ In the above output:
4850
had a value that came from 2 different sources, final value showing at top,
4951
while all the other values seen showing below with the ``\_`` indicator.
5052

51-
* ``classifiers`` came from file ``classifiers.txt``
53+
* ``classifiers`` came from explicit settings in ``setup.py``
5254

53-
* ``description`` came from ``setup.py`` line 2
55+
* ``description`` came from ``setupmeta/__init__.py`` line 2
5456

5557
* ``download_url`` was defined in ``setupmeta/__init__.py`` line 5, since it was mentioning
5658
``{version}`` (and was a relative path), it got auto-expanded and filled in properly
@@ -59,25 +61,12 @@ In the above output:
5961

6062
* ``long_description`` came from ``README.rst``
6163

62-
* ``name`` came from line 16 of setup.py, note that ``title`` also came from that line -
63-
this simply means the constant ``__title__`` was used as ``name``
64+
* ``name`` came from an explicit setting in setup.py
6465

65-
* Note that ``title*`` is shown with an asterisk, the asterisk means that setupmeta sees
66-
the value and can use it, but doesn't transfer it to setuptools
66+
* ``packages`` came from explicit settings in setup.py
6767

68-
* ``packages`` was auto-filled to ``["setupmeta"]``
69-
70-
* ``version`` was determined from git tag (due to ``versioning="post"`` in setup.py),
71-
in this case ``1.1.2.post1+g816252c`` means:
72-
73-
* latest tag was 1.1.2
74-
75-
* there was 1 commit since that tag (``.post1`` means 1 change since tag,
76-
``".post"`` denotes this would be a "post-release" version,
77-
and should play nicely with PEP-440_)
78-
79-
* the ``+g816252c`` suffix means that the checkout wasn't clean when ``explain`` command
80-
was ran, local checkout was dirty at short git commit id "816252c"
68+
* ``version`` was determined from git (due to ``versioning="dev"`` in setup.py),
69+
in this case ``3.9.0`` means current commit is exactly on a version tag
8170

8271

8372
If you'd like to see what your ``setup.py`` would look like without setupmeta
@@ -106,40 +95,4 @@ Typical usage::
10695
python setup.py version --b minor --commit # Effectively bump
10796

10897

109-
cleanall
110-
========
111-
112-
Handily clean build artifacts. Cleans the usual suspects:
113-
``.cache/ .tox/ build/ dist/ venv/ __pycache__/ *.egg-info *.py[cod]``.
114-
115-
Example::
116-
117-
🦎 3.9 ~/dev/github/setupmeta: ./setup.py cleanall
118-
running cleanall
119-
deleted .tox
120-
deleted setupmeta.egg-info
121-
deleted examples/direct/__pycache__
122-
deleted examples/hierarchical/__pycache__
123-
deleted examples/single/__pycache__
124-
deleted setupmeta/__pycache__
125-
deleted tests/__pycache__
126-
deleted tests/scenarios/complex/tests/__pycache__
127-
deleted tests/scenarios/readmes/__pycache__
128-
deleted 14 .pyc files
129-
130-
131-
entrypoints
132-
===========
133-
134-
This will simply show you your ``entry_points/console_scripts``.
135-
Can be handy for pygradle_ users.
136-
137-
Example::
138-
139-
🦎 3.9 ~/github/pickley: python setup.py entrypoints
140-
141-
pickley = pickley.cli:protected_main
142-
14398
.. _PEP-440: https://www.python.org/dev/peps/pep-0440/
144-
145-
.. _pygradle: https://github.com/linkedin/pygradle/

docs/contributing.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ A coverage report is generated on all ``tox`` runs
5252

5353
Run this to see the generated html report::
5454

55-
open .tox/coverage/index.html
55+
open .tox/test-reports/htmlcov/index.html
5656

5757

5858
Refreshing the test scenarios

docs/versioning.rst

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ How does it work?
3939

4040
**Note**: ``setupmeta``'s versioning is based on (by default)::
4141

42-
git describe --dirty --tags --long --match *.* --first-parent
42+
git describe --dirty --tags --long --first-parent --match 'v*.*'
4343

4444
you will need **git version >= 1.8.4** if you wish to use ``setupmeta``'s versioning capabilities.
4545

@@ -144,7 +144,7 @@ Example:
144144
Commit Tag Version Note (command ran to add tag)
145145
======= ====== ================= ==============================================================
146146
no .git 0.0.0 Version defaults to 0.0.0 (when no tag yet)
147-
none 0.0.0.dirty No commit yet (but ``git init`` was ran)
147+
none 0.0.0+dirty No commit yet (but ``git init`` was ran)
148148
g1 0.0.0.post1 Initial commit
149149
g1 0.0.0.post1+dirty Same as above, only checkout was not clean anymore
150150
g2 0.0.0.post2
@@ -242,7 +242,7 @@ distance
242242
This is well suited if you want to publish a new version at every commit (but don't want to keep
243243
bumping version in code for every commit).
244244

245-
``distance`` corresponds to this format: ``branch(main,master):{major}.{minor}.{distance}{dirty}``
245+
``distance`` corresponds to this format: ``branch(main,master):{major}.{minor}.{distance}+{dirty}``
246246

247247
State this in your ``setup.py``::
248248

@@ -387,15 +387,13 @@ This is what ``versioning="post"`` is a shortcut for::
387387
"main": "{major}.{minor}.{patch}{post}",
388388
"extra": "{dirty}",
389389
"branches": ["main"],
390-
"version_tag": "*.*",
390+
"version_tag": "v*.*",
391391
},
392392
...
393393
)
394394

395395
``version_tag`` is the glob pattern of git tags to consider as version tags.
396-
Unfortunately (for historical reasons), the default form is ``*.*`` (ie: any git tag
397-
with a dot in it), and arguably should have been ``v*.*`` (ie: git tags that start with ``v``
398-
and have dot in them...)
396+
The default form is ``v*.*`` (ie: git tags that start with ``v`` and have a dot in them).
399397

400398
Ideally, git would allow to state a full regex, as only tags that match this regex
401399
would ideally be considered as version tags: ``^v?\d+\.\d+(\.\d+)?$``, however this is not

0 commit comments

Comments
 (0)