The tree builder now emits typed tags (path, override, missing, ...) and both renderers colour by kind: <span class> in the HTML <pre> block, ANSI in the terminal (path dim, override yellow, missing red). Previously the HTML tree was one escaped string and only MISSING was coloured in text. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EYNdaGDrpaqDyT8QtrTQbM
7.3 KiB
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.ConanAPIV1API 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
importlibwhereimpis gone) – verified on 3.14. It does emit SyntaxWarnings from its own modules there, whichconandeps.pysilences before importingconans; keepconansimports deferred so that filter is in place first.requires-pythoninpyproject.tomland 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 runconanagainst the host's~/.conan; neverpip installon the host. Rebuild the image with./dev.sh buildafter touchingContainerfile. - 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
myremotein docs and help text. Tests must use the localconan_serverfixture intests/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 throughConanAPIV1.info()and each node'sbinarystatus is read. This honourspackage_id()overrides, options anddefault_package_id_modeexactly likeconan install. Do not replace this with settings-dict matching againstconan searchoutput. - "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.pathis the list of package names on the shortest path (empty = direct, so a package that is both direct and transitive is direct).Dep.pathsholds one path per direct parent (shortest path to that parent + parent) – bounded, unlike "all paths". Theviacolumn renders them one per line asA -> 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 afmt_tagcallback) 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
Cachefor a locally cached binary without asking the remote, so_RemoteIndexverifies cache hits withsearch_packageson the remote. Cache-only binaries are reported asnot availableand 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()mutatesreq.refin place and only emits"<pkg>: requirement <old> overridden by <who> to <new>".conandepscaptures the output stream with a non-colouredConanOutputand matches_OVERRIDE_RE.test_overridespins this format – if a Conan patch release changes the wording, that test is the alarm. - Both explicit and implicit overrides are reported.
override=Truerequirements are collected fromconanfile.requiresto label an override as explicit; everything else parsed from the WARN lines is implicit. range_refis not a version range after an override. Conan reusesRequirement.range_refto hold the pre-override reference, so only treat it as a range whenreq.version_rangeis 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 viauv run conandeps.pywithout a checkout. - Exit codes:
0fine,1missing binaries,2Conan 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 autois the default (terminal + noNO_COLOR). - stdout is the report, stderr is progress. Graph resolution against a
remote takes a while; progress lines (
_progress) and--verboselive 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 inpyproject.toml, target py38, line length 100). Runshellcheck dev.shafter editing the wrapper. - Tests:
./dev.sh python -m pytest. The fixture spins upconan_serveron a free port with a pre-writtenserver.conf(the port cannot be given on the command line), creates fake packages whosebuild()does nothing, uploads them, then wipes the cache so binaries can only come from the remote. Fake recipes need--build=missingat 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). Usere.search(r"libbar/1\.1\s+indirect\s+2", 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. - Dependencies:
uv.lockis committed; regenerate with./dev.sh uv lockafter changingpyproject.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.
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.