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:conanonPATH) — the conan executable.deployers(default: just denver’s own bundled symlink deployer) — a list of scripts that deploy installed packages ontoPATH, each run as its ownconan install --deployer=<script>. The env-wide recipes-exporter (the script that generates/exports recipe references) is not user-configurable at this level — only arecipes:entry’s ownexport-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’sPYTHONPATH, 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 acrossimport: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 viaconan 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 whetherconan install(and theconanbuildenv.shactivation after it) runs at all; there is no separateinstall:flag to keep in sync with it. Independent ofrecipes:below — which recipes get exported has nothing to do with what the conanfile itself requires; it installs whatever its ownrequirements()/build_requirements()reference, whether or not this env exports it itself.recipes— a list of entries, each exported into the local conan cache beforeconanfile:(if any) is installed. Never guessed from the directory layout (see “Explicit over implicit” in../concepts/philosophy.md). Appends acrossimport: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 theirconandata.yml, wherever it sits.catalog— optional; a path to write this entry’s catalog to (every one of its recipes pinned asname/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 viaconanfile:reads them back. Requiresdirs:— 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) toconan install.install-args— extra literalconan installarguments.authentication(defaulttrue) —true,falseor"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 installruns 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": liketrue, 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. insidedocker compose run) denver first asks for a username and password, the same as withtrue; 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 aconfig:directory’sremotes.jsonset up. Export then resolves recipes against the remotes that authenticated, andconan installgets one-r=<remote>for each of them, or--no-remoteif none did. The list of remotes that authenticated is kept in the conan home, asdenver-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: falsewas set for that remote alone: denver warns, does not log in, leaves it out of export and ofconan install’s-r=list (--no-remoteif 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=falseto work from the local cache only.Credentials for a remote, first found wins:
conan’s own: the
auth_remote.pyplugin,credentials.json,CONAN_LOGIN_USERNAME_<REMOTE>/CONAN_PASSWORD_<REMOTE>.The
machineentry for the remote’s host in$NETRC(default~/.netrc).host:portmatches too,defaultnever.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
netrcstage beforeconansetsNETRC.authentication: falsenever 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 viaconan config install <dir>, in order, before profile detection. Whatever conan’s ownconfig installunderstands 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 aconfig: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(defaulttrue),enabled(defaulttrue). 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 — soremotes: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’senabled:at run time.keep-remotes(defaultfalse) — set totrueto opt out of makingremotes:exhaustive: with noremotes:of its own, the env then leaves the conan home’s existing remote configuration alone entirely. Left at the defaultfalse, the prepare stage disables every remote already present in the conan home whenremotes: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 ofkeep-remotes:) wheneverremotes:is left unset/empty and the env also has aconfig:— itsconan config install <dir>may itself have installed aremotes.json, and reconciling an emptyremotes:to “exhaustive” would otherwise silently disable everythingconfig:just set up. An explicit (non-empty)remotes:still reconciles as normal regardless ofconfig:.remove-local-builds(defaulttrue) — after the export, remove packages of therecipes:that were built locally, if a remote has the same package.conan installthen 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 withauthentication: falseor 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
customstage is enough. For a single prebuilt archive with no dependencies,curl+sha256sum -c+tarin acustomstage is an honest twenty lines and needs no conan onPATH— see “Bringing a prebuilt binary in by hand” incustom.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’sdirs:just points at wherever they actually are; there’s no requirement they live in any particular place relative to thedenver.yml. This simplifies the integration as there is no deployment necessary.A base env with recipes but no conanfile. Since
conanfile:andrecipes:are independent, a shared base env can shiprecipes:with noconanfile:of its own; each derived env that wants to actually install something sets its ownconanfile:, and either inherits the base’srecipes:or adds more of its own.Works with or without remotes. An env with no
remotes:and noconfig:at all is a fully offline/local-cache setup — nothing gets reconciled, conan just uses whatever’s already in its local cache/home. Addremotes:(or aconfig: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 subsequentconan installfails. So ifconan installfails,recipes.py --repair-cachechecks 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 installruns withoutCCACHE_REMOTE_STORAGE. Conan builds packages with the ccache onPATH. That can be an older ccache, which fails on@layout=local(needs ccache 4.14).Skips
conan installwhen the dependency graph is unchanged. Before wiping and reinstalling, the install step runs a fastconan graph infoquery (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 onPATHyet under--dry-run, a broken recipe, …) is treated as “changed”, soconan installruns and reports the real problem itself. Only the install step is gated this way —config/prepare/exportabove it always run.--fastactivates the already-generatedconanbuildenv.shinstead of re-runningconan install(or theconan graph infocheck); dies with a clear message if it doesn’t exist yet.--forcerecreates the conan/workspace setup steps’ own on-disk state unconditionally, including a freshconan installregardless of the graph-info check above.--dry-runprints theconan/recipes-exporter commands instead of running them, and leaves the install tree (which a real run wipes first) untouched.conan config homestill runs — it is a read-only query whose answer decides whetherconan profile detectwould be shown; when conan isn’t installed yet (an earlieruvstage would have), that query simply fails, and denver warns and previews the detection anyway rather than stopping there.