conan-1.6-utilities/CLAUDE.md
Ole-Morten Duesund be0af8d0f0 CLAUDE.md: fix stale test example, add release procedure and fixture guidance
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EYNdaGDrpaqDyT8QtrTQbM
2026-08-25 16:23:24 +02:00

8.1 KiB
Raw Permalink Blame History

CLAUDE.md

Guidance for working on this repository. It records decisions made when the project was started (August 2026) that are not obvious from the code.

What this is

Small single-file Python utilities for Conan 1.x, targeting 1.66. Not Conan 2. The first (and so far only) tool is conandeps.py; see README.md for what it does and how it is used.

Commands (all inside the container)

./dev.sh build                       # first time, and after editing Containerfile
./dev.sh python -m pytest            # tests (starts a throw-away conan_server)
./dev.sh ruff check . && ./dev.sh ruff format .
shellcheck dev.sh                    # after editing the wrapper (runs on host)
./dev.sh uv lock                     # after changing pyproject.toml

Users install/upgrade on their own machine with uv tool install git+https://kode.naiv.no/olemd/conan-1.6-utilities.git / uv tool upgrade conan-utils; a pushed commit is immediately installable.

Hard constraints

  • Conan 1.66 only. Use the conans.client.conan_api.ConanAPIV1 API and Conan 1 graph internals (Node.binary, Node.dependencies, conanfile.requires). Do not introduce Conan 2 APIs or a Conan 2 code path.
  • Python 3.8+. Conan 1.66 runs on new Pythons too (it falls back to importlib where imp is gone) verified on 3.14. It does emit SyntaxWarnings from its own modules there, which conandeps.py silences before importing conans; keep conans imports deferred so that filter is in place first. requires-python in pyproject.toml and the PEP 723 header must stay in sync.
  • All development runs inside the container. Use ./dev.sh <cmd> for every python/pytest/ruff/uv invocation. Never run conan against the host's ~/.conan; never pip install on the host. Rebuild the image with ./dev.sh build after touching Containerfile.
  • Never depend on the private remote. The user's real remote only exists on the machine where the tool is used and must not be named in the repository use a placeholder such as myremote in docs and help text. Tests must use the local conan_server fixture in tests/test_conandeps.py.

Design decisions (and why)

  • Binary availability is computed via Conan's graph, not conan search. The graph is built once per build type through ConanAPIV1.info() and each node's binary status is read. This honours package_id() overrides, options and default_package_id_mode exactly like conan install. Do not replace this with settings-dict matching against conan search output.
  • "Missing" means missing for the exact profile. Not "no binary with that build_type at all".
  • Dependency table shows the shortest path, not a level number. _paths() does a breadth-first walk from the root; Dep.path is the list of package names on the shortest path (empty = direct, so a package that is both direct and transitive is direct). Dep.paths holds one path per direct parent (shortest path to that parent + parent) bounded, unlike "all paths". The via column renders them one per line as A -> B (/<br> in HTML); _table() supports multi-line cells. Rows sort on (depth, path, host before build, name). The tree section keeps graph order.
  • Tree tags are typed. _tree_rows() yields (head, [(kind, text)], tail) with kinds path/build-require/override/range/missing; _tree_lines() (text, ANSI via a fmt_tag callback) and the HTML renderer (<span class=…>) colour by kind from that one source. Add new tree annotations there, never by string-replacing rendered lines.
  • Only the remote counts, and the source is always shown. Conan says Cache for a locally cached binary without asking the remote, so _RemoteIndex verifies cache hits with search_packages on the remote. Cache-only binaries are reported as not available and count as a problem (exit 1). Every available cell names the remote. The user rejected an opt-in flag for this it is the tool's core question.
  • Overrides are parsed from Conan's WARN output. Conan 1 does not record overrides on the graph; Requirements.update() mutates req.ref in place and only emits "<pkg>: requirement <old> overridden by <who> to <new>". conandeps captures the output stream with a non-coloured ConanOutput and matches _OVERRIDE_RE. test_overrides pins this format if a Conan patch release changes the wording, that test is the alarm.
  • Both explicit and implicit overrides are reported. override=True requirements are collected from conanfile.requires to label an override as explicit; everything else parsed from the WARN lines is implicit.
  • range_ref is not a version range after an override. Conan reuses Requirement.range_ref to hold the pre-override reference, so only treat it as a range when req.version_range is truthy.
  • Single file, stdlib + Conan only. No third-party deps beyond Conan. Packaging is hatchling with only-include = ["conandeps.py"]; the PEP 723 header makes the file usable via uv run conandeps.py without a checkout.
  • Exit codes: 0 fine, 1 missing binaries, 2 Conan error. CI relies on this.
  • Tables and colour, consistently. Dependencies, missing binaries and overrides are all rendered through the same _table() helper (text) and _html_table() (HTML). Colour is an aid only every state is also spelled out in words and _table() aligns on visible width so ANSI codes never break columns. --color auto is the default (terminal + no NO_COLOR).
  • stdout is the report, stderr is progress. Graph resolution against a remote takes a while; progress lines (_progress) and --verbose live Conan output go to stderr only, so stdout can be redirected to a file.

Workflow

  • Lint/format: ./dev.sh ruff check . and ./dev.sh ruff format . (config in pyproject.toml, target py38, line length 100). Run shellcheck dev.sh after editing the wrapper.
  • Tests: ./dev.sh python -m pytest. The fixture spins up conan_server on a free port with a pre-written server.conf (the port cannot be given on the command line), creates fake packages whose build() does nothing, uploads them, then wipes the cache so binaries can only come from the remote. Fake recipes need --build=missing at create time because dependencies' binaries are deliberately incomplete.
  • Assert on table output with regexes, not fixed-width strings. Column widths depend on the longest ref in the fixture and package_ids change whenever a fixture recipe's requirements change (semver_direct_mode). Use re.search(r"libbar/1\.1\s+libfoo\s+host", text) and [0-9a-f]{40} for ids. Also: _conan() in the tests raises with Conan's full output on failure read it before guessing.
  • Use the conan_env fixture only for graph/remote behaviour. Anything about rendering (_via, _table, _tree_rows, HTML) should be a fixture-free unit test on hand-built Dep objects, like test_via_lists_every_parent_path instant, and no conan_server.
  • Dependencies: uv.lock is committed; regenerate with ./dev.sh uv lock after changing pyproject.toml.
  • Commits: atomic, no --amend; run ruff + pytest + shellcheck before committing. Add a memory of anything a future session could not derive from the repository.
  • Releases: bump version in pyproject.toml (must match the tag), commit, then git tag -a vX.Y.Z && git push origin vX.Y.Z, build with ./dev.sh uv build (writes dist/, gitignored), write SHA256SUMS, and fj release create --tag vX.Y.Z --attach dist/<wheel> --attach dist/<sdist> --attach dist/SHA256SUMS --body "..." "vX.Y.Z" from inside the repo fj release does not accept --repo. Then update the "Latest:" line in the README's Releases section and delete dist/.

Adding another tool

Follow the conandeps.py pattern: one executable file with a module docstring explaining how and why, a PEP 723 header, a main(argv) that returns an exit code, a [project.scripts] entry, an only-include entry in pyproject.toml, tests under tests/ using the shared conan_env fixture, and a section in README.md.