Skip to content

Commit 39c02aa

Browse files
committed
chore: align engineering configuration and CI entry points
1 parent 3d14ee6 commit 39c02aa

8 files changed

Lines changed: 159 additions & 8 deletions

File tree

‎.gitattributes‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Stable LF checkouts keep generated JSON, source checksums and solution hashes
2+
# identical across contributor platforms, independent of core.autocrlf.
3+
* text=auto eol=lf

‎.github/workflows/validate.yml‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
name: Validate
22

33
on:
4+
workflow_dispatch:
45
pull_request:
56
push:
67
branches:
@@ -11,7 +12,8 @@ permissions:
1112

1213
concurrency:
1314
group: validate-${{ github.workflow }}-${{ github.ref }}
14-
cancel-in-progress: true
15+
# Preserve a complete default-branch validation; superseded PR runs can stop.
16+
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
1517

1618
jobs:
1719
# Lowest and latest supported CPython; the universal lock already resolves

‎AGENTS.md‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,16 @@ reviewed catalog data, and stable public JSON contracts.
7676
- After merge, verify the immutable raw `catalog.json`. Verify flypython.com only
7777
after a separate, deliberate consumer-version update.
7878

79+
## Engineering entry points
80+
81+
- `make help` lists the existing validation and generation tools.
82+
- `make setup` installs the locked dev environment; use
83+
`make check PYTHON=.venv/bin/python` afterwards.
84+
- `make generated-check` is read-only; `make generate` rewrites derived content
85+
in dependency order. Neither publishes or changes the website content pin.
86+
- Keep agent rules here; `CLAUDE.md` imports this file rather than copying it.
87+
- Engineering configuration rationale is in `docs/ENGINEERING_CONFIG.md`.
88+
7989
## Review priorities
8090

8191
1. Content accuracy and source quality.

‎CLAUDE.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
@AGENTS.md

‎CONTRIBUTING.md‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,18 @@ and path folders that ship their own `requirements.txt` must pin exactly the
8484
versions in `requirements-dev.lock.txt` (`tests/test_dependency_pins.py`), so
8585
CI tests the stack learners install.
8686

87+
With `uv` installed, `make setup` is an alternative to the manual setup above:
88+
it uses `.python-version` and synchronizes the exact dev lock into `.venv`.
89+
Then use `make check PYTHON=.venv/bin/python`; Make otherwise defaults to
90+
`python3`. The Make aliases assume a POSIX shell (Windows contributors can use
91+
the direct Python commands below or WSL). Run from this repository or use
92+
`make -C path/to/python`, not `make -f path/to/Makefile` from another directory.
93+
94+
`make generated-check` checks derived content without rewriting it, including
95+
course solution hashes. `make generate` regenerates exports, README indexes,
96+
the content manifest and solution hashes in order; `make all` then runs checks.
97+
Both preserve ordering even when invoked with `make -j`.
98+
8799
After changing catalog sources, regenerate the public export:
88100

89101
```bash
@@ -101,6 +113,7 @@ python tools/validate_catalog.py
101113
python tools/export_catalog.py --check --target both
102114
python tools/render_readmes.py --check
103115
python tools/build_content_manifest.py --check
116+
python tools/build_solution_hashes.py --check
104117
python tools/verify_examples.py
105118
python tools/verify_courses.py
106119
python tools/verify_paths.py

‎Makefile‎

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1-
.PHONY: help check export render manifest test verify courses paths lint lock lock-check all
1+
# Recipes use repository-relative paths. Like Magpie's Makefile, fail closed
2+
# when invoked with `make -f path/to/Makefile` from an unrelated directory.
3+
ifeq ($(and $(wildcard pyproject.toml),$(wildcard tools/validate_catalog.py),$(wildcard catalog/catalog.yml)),)
4+
$(error run make in the Python repository (make -C path/to/python))
5+
endif
6+
7+
.DEFAULT_GOAL := help
8+
.PHONY: help setup check generated-check generate export render manifest solutions test verify courses paths lint lock lock-check all
29

310
PYTHON ?= python3
411
UV ?= uv
@@ -15,10 +22,14 @@ endef
1522

1623
help:
1724
@echo "FlyPython Development Workflow:"
18-
@echo " make check - Run the same checks as CI: lint, tests, catalog, radar, export, readme, manifest, example, course, and path checks"
25+
@echo " make setup - Create .venv using .python-version and install locked dev dependencies (requires uv)"
26+
@echo " make check - Run CI checks (use PYTHON=.venv/bin/python after setup)"
27+
@echo " make generated-check - Check catalog, exports, READMEs, manifest, and solution hashes without rewriting"
28+
@echo " make generate - Regenerate all derived content in dependency order"
1929
@echo " make export - Regenerate catalog.json and radar.json"
2030
@echo " make render - Regenerate root and catalog README indexes plus the Radar table"
2131
@echo " make manifest - Regenerate content-manifest.json"
32+
@echo " make solutions - Regenerate course-solutions.json"
2233
@echo " make lint - Run ruff"
2334
@echo " make test - Run pytest test suite"
2435
@echo " make verify - Verify all runnable examples"
@@ -28,14 +39,29 @@ help:
2839
@echo " make lock-check - Fail if the lock files no longer match pyproject.toml"
2940
@echo " make all - Regenerate all exports and run all checks and tests"
3041

31-
check: lint test
42+
setup:
43+
$(UV) venv --python "$$(cat .python-version)" .venv
44+
$(UV) pip sync --python .venv/bin/python requirements-dev.lock.txt
45+
46+
check: lint test generated-check
47+
$(PYTHON) tools/verify_examples.py
48+
$(PYTHON) tools/verify_courses.py
49+
$(PYTHON) tools/verify_paths.py
50+
51+
generated-check:
3252
$(PYTHON) tools/validate_catalog.py
3353
$(PYTHON) tools/export_catalog.py --check --target both
3454
$(PYTHON) tools/render_readmes.py --check
3555
$(PYTHON) tools/build_content_manifest.py --check
36-
$(PYTHON) tools/verify_examples.py
37-
$(PYTHON) tools/verify_courses.py
38-
$(PYTHON) tools/verify_paths.py
56+
$(PYTHON) tools/build_solution_hashes.py --check
57+
58+
# Keep writers sequential even under make -j: README/manifest checks must not
59+
# race against a producer. No automatic commit, content-pin update or deploy.
60+
generate:
61+
$(MAKE) export
62+
$(MAKE) render
63+
$(MAKE) manifest
64+
$(MAKE) solutions
3965

4066
export:
4167
$(PYTHON) tools/export_catalog.py --target both
@@ -46,6 +72,9 @@ render:
4672
manifest:
4773
$(PYTHON) tools/build_content_manifest.py
4874

75+
solutions:
76+
$(PYTHON) tools/build_solution_hashes.py
77+
4978
lint:
5079
$(PYTHON) -m ruff check .
5180

@@ -72,4 +101,5 @@ lock-check:
72101
@git diff --exit-code requirements.lock.txt requirements-dev.lock.txt || \
73102
{ echo "lock files drifted from pyproject.toml — commit the regenerated files"; exit 1; }
74103

75-
all: export render manifest check
104+
all: generate
105+
$(MAKE) check

‎docs/ENGINEERING_CONFIG.md‎

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Engineering configuration
2+
3+
The Python repository already has a locked Python toolchain, a Makefile,
4+
SHA-pinned GitHub Actions and deterministic bilingual-content gates. These remain
5+
the source of truth. The configuration additions reference
6+
[yetone/magpie](https://github.com/yetone/magpie) (reviewed 2026-10-22), adapting
7+
its engineering practices rather than its Go/desktop stack.
8+
9+
| Reference | FlyPython adaptation |
10+
| --- | --- |
11+
| [Makefile](https://github.com/yetone/magpie/blob/main/Makefile) | Guard repository-relative recipes against the wrong working directory; extend the existing command entry point rather than add another task runner. |
12+
| [.gitattributes](https://github.com/yetone/magpie/blob/main/.gitattributes) | Force LF for text checkouts, keeping source checksums and generated output stable across platforms. Binary files remain auto-detected. |
13+
| [CLAUDE.md](https://github.com/yetone/magpie/blob/main/CLAUDE.md) | Import the existing AGENTS.md; do not maintain two copies of the same agent rules. |
14+
| [test workflow](https://github.com/yetone/magpie/blob/main/.github/workflows/test.yml) | Allow manual validation; do not cancel an in-progress default-branch run on every new push, while superseded PR runs are canceled. |
15+
16+
## Local commands
17+
18+
Use a POSIX shell and `make` from this repository (or `make -C path/to/python`).
19+
`make setup` requires uv, selects `.python-version`, and syncs
20+
`requirements-dev.lock.txt` into `.venv`. It does not update dependency locks.
21+
Use `make check PYTHON=.venv/bin/python` after setup; the Make default remains
22+
`python3` so existing CI and activated environments keep working. Native Windows
23+
contributors can run the direct Python commands in CONTRIBUTING.md or use WSL.
24+
25+
`make generated-check` runs validation and read-only checks of catalog/radar,
26+
README sections, content manifest and course solution hashes. `make generate`
27+
regenerates those artifacts sequentially; `make all` regenerates, then checks.
28+
The ordering holds under `make -j` too. No target publishes content or deploys.
29+
30+
## Intentionally not copied
31+
32+
- Go, Bun, GTK/WebKit, native binary packaging and Docker publishing do not fit
33+
this content-and-Python-tools repository.
34+
- No automatic website pin update, release or production deployment is added.
35+
- Existing exact locks, CI Python version matrix, least-privilege permissions,
36+
SHA-pinned actions and job timeout remain intact.
37+
- No mass formatting or line-ending renormalization is performed. If a future
38+
byte-sensitive fixture deliberately needs CRLF, add a narrow per-file
39+
attributes exception; do not disable LF for ordinary source files.
40+
41+
Configuration regression tests verify the thin agent entry point, wrong-directory
42+
Make guard, locked setup command, read-only generated checks and parallel-safe
43+
regeneration ordering. They run in the existing pytest/CI suite.

‎tests/test_engineering_config.py‎

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
"""Keep developer entry points safe and aligned with generated-content gates."""
2+
3+
import shutil
4+
import subprocess
5+
from pathlib import Path
6+
7+
import pytest
8+
9+
ROOT = Path(__file__).resolve().parents[1]
10+
11+
12+
def _make(*args: str, cwd: Path = ROOT) -> subprocess.CompletedProcess[str]:
13+
if not shutil.which("make"):
14+
pytest.skip("GNU/POSIX make is not installed")
15+
return subprocess.run(
16+
["make", *args], cwd=cwd, capture_output=True, text=True, timeout=15,
17+
check=False,
18+
)
19+
20+
21+
def test_agent_entrypoint_has_single_source() -> None:
22+
assert (ROOT / "CLAUDE.md").read_text().strip() == "@AGENTS.md"
23+
assert "* text=auto eol=lf" in (ROOT / ".gitattributes").read_text()
24+
25+
26+
def test_make_rejects_unrelated_working_directory(tmp_path: Path) -> None:
27+
result = _make("-f", str(ROOT / "Makefile"), "-n", "setup", cwd=tmp_path)
28+
assert result.returncode != 0
29+
assert "run make in the Python repository" in result.stderr
30+
31+
32+
def test_make_generated_checks_are_read_only_and_include_solution_hashes() -> None:
33+
result = _make("-n", "generated-check")
34+
assert result.returncode == 0, result.stderr
35+
for name in ("export_catalog", "render_readmes", "build_content_manifest", "build_solution_hashes"):
36+
assert f"tools/{name}.py --check" in result.stdout
37+
38+
39+
def test_make_setup_uses_pinned_interpreter_and_locked_dependencies() -> None:
40+
result = _make("-n", "setup")
41+
assert result.returncode == 0, result.stderr
42+
assert ".python-version" in result.stdout
43+
assert "pip sync --python .venv/bin/python requirements-dev.lock.txt" in result.stdout
44+
45+
46+
def test_make_all_regenerates_before_checks_even_in_parallel() -> None:
47+
result = _make("-n", "-j4", "all")
48+
assert result.returncode == 0, result.stderr
49+
assert result.stdout.index("tools/build_solution_hashes.py\n") < result.stdout.index("-m ruff check .")

0 commit comments

Comments
 (0)