conan provider

A conan stage provisions native, non-Python tools (compilers, cmake, ninja, …) via Conan and puts them on PATH.

[my-conan-stage]
provider = "conan"
conanfile = "conanfile.py"

[[my-conan-stage.recipes]]
dirs = ["path/to/recipes"]
catalog = "catalog.yml"  # optional

(provider:/description:/disabled:/ci:/depends-on:/scripts:/env:/env-prepend:/env-append: are generic keys every stage has — see “Generic stage keys” in Configuration. Everything else is specific to conan.)

Requires

conan must be available wherever this stage runs — denver never installs it. In practice an earlier uv stage does, by listing conan in its requirements:, which puts it on PATH via that stage’s venv; a host-wide or in-image install works just as well, and exe: can point at a specific one. A stage with no conan available fails with conan provider needs 'conan' on PATH.

Key reference

  • exe (default: conan on PATH) — the conan executable.

  • deployers (default: just denver’s own bundled symlink deployer) — a list of scripts that deploy installed packages onto PATH, each run as its own conan install --deployer=<script>. The env-wide recipes-exporter (the script that generates/exports recipe references) is not user-configurable at this level — only a recipes: entry’s own export-tool: (below) can override it, for that entry only.

  • base-classes — optional; a list of directories of shared conanfile base classes recipes can inherit from. Each is put on the recipes-exporter’s PYTHONPATH, in list order (earlier entries win), and may itself live in a base env, resolved the normal way (falling back to an imported base env’s own directory). A listed dir must exist (it’s an error if it doesn’t). Appends across import: layers like any other list (see Configuration’s “Merge rules”), so a derived env only needs to list the base-classes dirs it adds itself.

  • conanfile — optional; the single conanfile to install via conan install (a project only ever has one dependency graph, so this is a single path, not a list). Left unset, config/export/prepare still run as normal — this is the only toggle for whether conan install (and the conanbuildenv.sh activation after it) runs at all; there is no separate install: flag to keep in sync with it. Independent of recipes: below — which recipes get exported has nothing to do with what the conanfile itself requires; it installs whatever its own requirements()/build_requirements() reference, whether or not this env exports it itself.

  • recipes — a list of entries, each exported into the local conan cache before conanfile: (if any) is installed. Never guessed from the directory layout (see “Explicit over implicit” in ../concepts/philosophy.md). Appends across import: layers like any other list. Each entry is a mapping with these keys:

    • dirs — optional; directories containing recipes to export. Each must be listed explicitly and must exist. An entry may be a whole recipe tree or a single recipe directory; recipes are found by their conandata.yml, wherever it sits.

    • catalog — optional; a path to write this entry’s catalog to (every one of its recipes pinned as name/version@user/channel#rrev). Unset, the catalog is built in memory, handed straight to the export step and never written — so a run leaves no generated file behind. Set it when the pins should be reviewable/committed, or when the conanfile installed via conanfile: reads them back. Requires dirs: — a catalog with nothing to build it from is rejected.

    • export-tool — optional; overrides the env-wide recipes-exporter for this entry only.

    An entry’s dirs: are resolved as one catalog, so recipes in one dir may require recipes in another dir of the same entry — and an entry’s catalog content follows from that entry’s membership. Put recipes in their own entry to keep their catalog independent of what another entry does.

  • build (default "missing") — passed as --build=<value> (a string or a list) to conan install.

  • install-args — extra literal conan install arguments.

  • authentication (default true) — true, false or "may-fail".

    • true: denver authenticates to every enabled remote, and a remote that fails to authenticate aborts the run.

    • false: denver never logs in to or queries a conan remote. conan install runs with --no-remote, and the prepare/export steps skip remote login and export every recipe missing from the local cache. Use this when a remote is down or you have no credentials for it.

    • "may-fail": like true, but a remote that fails to authenticate (wrong or missing credentials, 403, or unreachable) is warned about and left out of the run instead of aborting it. On a terminal (e.g. inside docker compose run) denver first asks for a username and password, the same as with true; if that login fails too, or the prompt gets an empty input (Ctrl-D), the remote is skipped. Without a terminal (e.g. CI) it is skipped right away. Each remote is asked for at most once per run. The prepare step checks every enabled remote once, including ones a config: directory’s remotes.json set up. Export then resolves recipes against the remotes that authenticated, and conan install gets one -r=<remote> for each of them, or --no-remote if none did. The list of remotes that authenticated is kept in the conan home, as denver-usable-remotes.json.

    A remote that answers HTTP 503 (temporarily unavailable) is not a credentials problem, so in every mode it is handled as if authentication: false was set for that remote alone: denver warns, does not log in, leaves it out of export and of conan install’s -r= list (--no-remote if no other remote is left), and the run continues from the local cache. If the local cache does not have what is needed, the run still fails, and the error names the remote that was unavailable. A 401/403 keeps the behavior above. Any other login failure that stops the run suggests -c conan.authentication=false to work from the local cache only.

    Credentials for a remote, first found wins:

    1. conan’s own: the auth_remote.py plugin, credentials.json, CONAN_LOGIN_USERNAME_<REMOTE>/CONAN_PASSWORD_<REMOTE>.

    2. The machine entry for the remote’s host in $NETRC (default ~/.netrc). host:port matches too, default never.

    3. A prompt, only on a terminal.

    If the remote rejects conan’s own credentials, denver tries netrc, then the prompt. So in CI a netrc entry is enough. The password is never printed. A netrc stage before conan sets NETRC. authentication: false never reads netrc.

  • profiles — host/build, each a list; every entry becomes its own -pr:h=<value> / -pr:b=<value> flag, in list order. Empty by default (no explicit profile flags).

  • config — optional; a list of directories, each installed via conan config install <dir>, in order, before profile detection. Whatever conan’s own config install understands inside them (profiles, remotes.json, credentials.json, source_credentials.json, settings.yml, …) is installed into the conan cache by conan itself — denver never opens or interprets any file inside a config: directory, it only invokes the command.

  • remotes — optional; a project-owned, exhaustive list of the conan remotes this env wants. Each entry: url, verify_ssl (default true), enabled (default true). When the prepare stage runs, the recipes-exporter adds/renames/enables exactly these remotes and disables every other remote already present in the conan home — so remotes: should list every remote the env needs, not just ones being newly added. A remote’s own login (if reachable) runs as part of the same stage. CONAN_REMOTE_ENABLE_<NAME> (an env var, ON/OFF) overrides a given remote’s enabled: at run time.

  • keep-remotes (default false) — set to true to opt out of making remotes: exhaustive: with no remotes: of its own, the env then leaves the conan home’s existing remote configuration alone entirely. Left at the default false, the prepare stage disables every remote already present in the conan home when remotes: is left unset/empty, so each env’s remote configuration is fully self-contained regardless of what an earlier run of a different env left behind. This cleanup is automatically skipped (regardless of keep-remotes:) whenever remotes: is left unset/empty and the env also has a config: — its conan config install <dir> may itself have installed a remotes.json, and reconciling an empty remotes: to “exhaustive” would otherwise silently disable everything config: just set up. An explicit (non-empty) remotes: still reconciles as normal regardless of config:.

  • remove-local-builds (default true) — after the export, remove packages of the recipes: that were built locally, if a remote has the same package. conan install then downloads it. Why: a locally built package is in another folder (<conan home>/p/b/<folder>/p) than a downloaded one (<conan home>/p/<folder>/p). The paths may end up in the compiler commands, which prevents portable caches. Packages that no remote has are kept. Skipped with authentication: false or if no remote is logged in.

  • user (default "denver") / channel (default "snapshot") — become the user/channel half of every reference the recipes-exporter generates while exporting recipes (name/version@user/channel).

Design notes

  • When a custom stage is enough. For a single prebuilt archive with no dependencies, curl + sha256sum -c + tar in a custom stage is an honest twenty lines and needs no conan on PATH — see “Bringing a prebuilt binary in by hand” in custom.md. What conan adds is the per-tool cost: a url and a checksum instead of a script, plus a cache shared across envs and checkouts, and dependencies between tools.

  • Monorepo pattern. Recipes commonly live in the same repository as the project using them, not a separate recipes repo. A recipes: entry’s dirs: just points at wherever they actually are; there’s no requirement they live in any particular place relative to the denver.yml. This simplifies the integration as there is no deployment necessary.

  • A base env with recipes but no conanfile. Since conanfile: and recipes: are independent, a shared base env can ship recipes: with no conanfile: of its own; each derived env that wants to actually install something sets its own conanfile:, and either inherits the base’s recipes: or adds more of its own.

  • Works with or without remotes. An env with no remotes: and no config: at all is a fully offline/local-cache setup — nothing gets reconciled, conan just uses whatever’s already in its local cache/home. Add remotes: (or a config: that installs its own remote config) only once real remote access is actually needed.

  • A broken cache is repaired after a failed conan install. An interrupted install can leave broken recipes or packages in the conan 2.x cache (missing or changed files). Then every subsequent conan install fails. So if conan install fails, recipes.py --repair-cache checks the cache (conan cache check-integrity) and removes only the broken recipes and packages. The check reads every file and is slow, so it only runs after a failure.

  • conan install runs without CCACHE_REMOTE_STORAGE. Conan builds packages with the ccache on PATH. That can be an older ccache, which fails on @layout=local (needs ccache 4.14).

  • Skips conan install when the dependency graph is unchanged. Before wiping and reinstalling, the install step runs a fast conan graph info query (no downloads/builds) and hashes its output; if that hash matches the one stored by the last successful install here, the existing install tree is left alone entirely. A failed query (conan not on PATH yet under --dry-run, a broken recipe, …) is treated as “changed”, so conan install runs and reports the real problem itself. Only the install step is gated this way — config/prepare/export above it always run.

  • --fast activates the already-generated conanbuildenv.sh instead of re-running conan install (or the conan graph info check); dies with a clear message if it doesn’t exist yet.

  • --force recreates the conan/workspace setup steps’ own on-disk state unconditionally, including a fresh conan install regardless of the graph-info check above.

  • --dry-run prints the conan/recipes-exporter commands instead of running them, and leaves the install tree (which a real run wipes first) untouched. conan config home still runs — it is a read-only query whose answer decides whether conan profile detect would be shown; when conan isn’t installed yet (an earlier uv stage would have), that query simply fails, and denver warns and previews the detection anyway rather than stopping there.