Pixi support#
Anaconda Project ships two pieces of tooling aimed at converting
existing anaconda-project.yml projects into a form that can be
managed by pixi (and, by extension,
conda-workspaces,
which consumes the same pixi.toml format).
These are the focus of recent maintenance on this repository:
The
anaconda-project export-pixicommand, which writes apixi.toml(and a siblingap_download.pyhelper when needed) alongside an existinganaconda-project.yml.A
publication_infofunction inanaconda_project.project_infothat can read eitheranaconda-project.ymlorpixi.tomland return a uniform dictionary describing the project’s commands, environments, and variables. Downstream deployment tooling that ingests both formats uses this as its single integration point.The
anaconda-project infocommand, a human-readable view of the same data — modeled onpixi info— that works on either manifest format.
The rest of this page documents the conventions the conversion uses to preserve as much of the original project’s behavior as possible.
Export goals#
The conversion is best-effort but tries to satisfy three contracts:
The resulting
pixi.tomlshould be installable and runnable without further manual editing for the common project shapes (unix:commands,notebook:,bokeh_app:,downloads:,variables:, single or multi-environment).Tasks invoked under pixi should mirror the runtime behavior of
anaconda-project runas closely as the underlying tools allow, including HTTP options, environment selection, and download fetching.The converted manifest should be readable for a maintainer who inspects it later — comments mark anything that couldn’t be translated faithfully, and warnings appear at the top of the file when a downstream consumer needs to act.
Environment layout#
When the source yml has a single
env_specs:entry with a non-default name (for examplesampleproj:), packages live in the top-level[dependencies]table (pixi’s default feature) and the named env inherits viafeatures = ["sampleproj"]. Pixi’s mandatory implicitdefaultenv carries the same packages, sopixi install(without-e) still does something useful.Multi-environment projects emit each
env_specs:entry under[environments]in source order. The first uncommented entry is the project’s intended default; downstream tools can extract it with one line ofawk.If one of multiple env_specs is literally named
default, its[environments]slot is rendered as a comment (# default (pixi creates this implicitly...)) and its packages are folded into top-level[dependencies]. Pixi auto-createsdefaultfrom the default feature, so redeclaring it would be redundant.solve-groupis intentionally not emitted. Anaconda Project does not assume environments solve together, and pixi shouldn’t be told otherwise on import.
The --use-default flag#
Projects that have no env_spec literally named default route every
dependency, task, and prepare body through [feature.{name}.*]
indirection — clean for projects that need explicit fan-out, but verbose
for the common case of a single env (or a default command bound to one
specific env in a multi-env project). Passing --use-default to
export-pixi collapses that:
The exporter picks one env_spec to promote to
default: the env_spec attached to the project’s default command (the one a bareanaconda-project runwould invoke), or — if there are no commands — the firstenv_specs:entry declared.That env_spec’s packages, tasks, and prepare body land in top-level
[dependencies]/[tasks.X]/[tasks.prepare]instead of inside[feature.{name}.*]blocks. Other env_specs (in a multi-env project) keep their[feature.{name}.*]scoping unchanged.The flag is a no-op when an env_spec literally named
defaultalready exists — there’s nothing to rename.Under
--use-defaultthe no-oppreparetask is dropped. When there are nodownloads:to fetch, the unflagged export emits anechoplaceholder in[feature.{name}.tasks.prepare]sopixi run prepareresolves to the project’s intended env even when that env’s name isn’tdefault. Under--use-defaultthe promoted env is the implicit default, so the placeholder loses its purpose;prepareis only emitted when there’s real work to do.
When the user runs export-pixi without the flag and the project would
benefit, the CLI prints a recommendation naming the env that would be
promoted. With the flag on, the CLI confirms which env was renamed.
The renaming applies only to the exported pixi.toml; the source
anaconda-project.yml is not modified.
The --add-current-platform flag#
Pixi rejects an env that doesn’t list the host platform; anaconda-project
is more forgiving, so it’s common to find a ported project with a
platforms: list that worked under conda but breaks at pixi install
time on the developer’s machine. Passing --add-current-platform to
export-pixi widens the converted manifest’s platforms list to
include the host’s conda subdir (e.g. osx-arm64) when it isn’t
already declared.
Behavior mirrors --use-default:
Off by default. The exporter does not silently mutate the user’s platforms list — the user explicitly chose what to support.
When omitted, the CLI prints a recommendation if the host platform is missing.
When passed, the CLI prints a confirmation listing the platform that was added — or stays quiet when the platform was already present.
The change applies only to the exported pixi.toml; the source
anaconda-project.yml is not modified.
Channel handling#
Pixi has no defaults meta-channel and no equivalent of conda’s
default_channels configuration. Two cases are handled explicitly:
If the source yml lists no channels at all, the converted manifest is populated from
conda config --show default_channelsrather than a hard-codedconda-forgefallback. This preserves enterprise users’.condarc-configured mirrors.If the source yml includes
defaultsalongside other channels, only thedefaultsentry is expanded; the rest are preserved in source order with duplicate URLs removed.
If conda is not on PATH (or fails to invoke) and defaults
expansion is required, the conversion fails fast — no partial output
is written.
Tasks and command translation#
unix:command lines are translated to a form compatible with pixi’sdeno_task_shelltask runner.${VAR}and%VAR%references are normalized to$VAR;${PROJECT_DIR}becomes$PIXI_PROJECT_ROOT.When both
unix:andwindows:command lines are present, the converter normalizes the windows form (path separators, env vars) and compares to the unix form. Matching forms collapse to a single task; divergent forms emit the unix variant plus a comment noting what the windows form would have rendered.${CONDA_PREFIX}/bin/<name>,${CONDA_PREFIX}/Scripts/<name>, and similar prefix-rooted paths are stripped to the bare command name. Pixi’s task activation already prepends the env’s executable directories toPATH, so explicit prefix paths are redundant and hurt cross-platform portability.
The prepare task#
Every converted project gets a prepare task. It does double duty:
When the source yml declared
downloads:,prepareruns a helper script (ap_download.py, written next topixi.toml) once per download. The helper is pure stdlib and usespython3, so it works whether or not the env declares its own python.Even when there are no downloads,
prepareis emitted as a no-opecho. Its presence serves two purposes:Acts as a marker that downstream deployment tooling can use to detect that this
pixi.tomlwas converted fromanaconda-project.yml.When scoped to a non-default env’s feature (e.g.
[feature.sampleproj.tasks.prepare]), forcespixi run prepareto resolve to that env automatically — useful when the project’s default env_spec is named something other thandefault.
If a download-needing env doesn’t declare python, the converted
pixi.toml carries a # WARNING: comment block at the top
listing the affected envs, and the same warning is printed to stderr
at conversion time. The conversion does not silently mutate the
user’s package list.
HTTP options#
Anaconda Project’s supports_http_options: true (implicit on
notebook: and bokeh_app: commands) tells the underlying tool
to expect --anaconda-project-X flags for host, port, address,
url-prefix, iframe-hosts, no-browser, and use-xheaders. The exporter
translates this contract into pixi args and templated cmd
strings, dispatching by command type:
notebook:commands becomejupyter notebook <file>plus Jupyter-native flags (--port,--ip,--NotebookApp.base_url=...,--NotebookApp.tornado_settings={...}for iframe hosts,--no-browser,--NotebookApp.trust_xheaders=True).hostis dropped because Jupyter has no host-restrict equivalent.bokeh_app:commands becomebokeh serve <app>plus bokeh’s bare flags (--host,--port,--address,--prefix,--use-xheaders).--showis rendered as the inverse of--no-browser.iframe_hostsis dropped because bokeh has no Content-Security-Policy equivalent.Plain
unix:commands withsupports_http_options: truekeep their--anaconda-project-Xflags verbatim. Generic tools that opt in are expected to recognize the canonical names themselves (panel servedoes).unix:commands withsupports_http_options: falseare scanned for HTTP Jinja vars ({{ port }},{{ host }}, etc.) in their templates; pixiargsare declared only for the vars the cmd actually references.
Each flag is wrapped in a Jinja conditional, so a blank pixi arg omits the flag entirely (rather than passing an empty value the underlying tool might reject).
The {% if var %}...{% endif %} and {{ var }} template syntax
inside task cmd strings is intentional, not accidental — pixi
renders task commands through MiniJinja against the positional values
passed to pixi run <task> <values...> (with args defaults
filling in the rest), and only then hands the rendered command to
deno_task_shell for execution. The syntax is supported but not
prominently documented in pixi’s own docs; the converted pixi.toml
is exercising a real pixi feature, and editing the gates by hand is
fine.
The info command#
anaconda-project info prints a human-readable summary of the
project, modeled on pixi info:
$ anaconda-project info --directory .
Project
------------
Type: pixi
Name: Attractors
Description: A panel dashboard using datashader ...
Directory: /path/to/project
Commands
------------
Command: dashboard [default, http]
: env_spec: sampleproj
: cmd: panel serve attractors.ipynb {% if host %}...
: args: host, port, address, url_prefix, ...
Environments
------------
Environment: default
Channels: https://repo.anaconda.com/pkgs/main, ...
Dependency count: 12
Dependencies: colorcet, datashader, fiona, geoviews, ...
Locked: yes
The command works on either manifest format — pixi.toml is
preferred when both are present.
Flags:
--jsonemits the underlyingpublication_infodict as indented JSON, mirroringpixi info --json.--env-pathsincludes the on-disk prefix path for each environment. For pixi projects this shells out topixi info --json; the full pixi payload is also stashed under an_pixikey in--jsonmode so callers don’t have to repeat the subprocess.--project-type {pixi,anaconda-project}forces a manifest format when both are present in the directory (e.g. mid-conversion).
publication_info#
anaconda_project.project_info.publication_info(project_dir)
returns a uniform dictionary regardless of whether the project is
managed by anaconda-project.yml or by a converted
pixi.toml. The shape mirrors Project.publication_info() from
the anaconda-project side; relevant additions for the pixi side:
commands[name]['args']— ordered list of pixi arg names declared for the task. Empty for tasks without args. Lets downstream tooling drivepixi run <task> *argsto supply positional values.commands[name]['env_spec']— resolves to the name of an env that supports the task. Top-level tasks resolve to the project’s default env (literaldefaultif declared, otherwise the first declared env). Feature-scoped tasks resolve to whichever env actually includes the feature.env_specs[name]['locked']—Truewhenpixi.lockhas anenvironments[<name>]entry. Best-effort: any read or parse failure silently falls back toFalse.
Optional arguments:
project_type='pixi' | 'anaconda-project'forces a specific manifest format. Default behavior is auto-detect, withpixi.tomlwinning when both are present. It is an error to ask for a format whose manifest is not in the directory.env_paths=Truepopulatesenv_specs[name]['path']with the on-disk prefix for each declared environment. For anaconda-project this is derived from eachEnvSpec; for pixi it requires apixi info --jsonsubprocess (so it is opt-in). Failure to invoke pixi or parse its output raisesRuntimeError. When the source is pixi, the fullpixi info --jsonpayload is also stored under the top-level_pixikey so callers that want richer pixi-specific data don’t pay for a second subprocess.
Programmatic export API#
For tools that wrap the conversion (launchers, IDE plugins, custom CLIs), the export pipeline is exposed so downstream code never has to scrape generated TOML or re-implement the conversion rules.
project_ops.export_pixi(project, filename, use_default=False, add_current_platform=False)writespixi.tomlto disk and returns aPixiExportStatus. On success the status carries:default_rename_from— the env_spec promoted todefaultbyuse_default, orNonewhen the flag was off, no-op’d (becausedefaultalready existed), or the export failed.current_platform_added— the platform string added to theplatformslist byadd_current_platform, orNonewhen the flag was off, no-op’d (the platform was already declared), or the export failed.
Callers use these to surface “Renamed env X → default” or “Added platform Y” in their success notification without re-querying the project.
project_ops.preview_pixi_export(project, use_default=False, add_current_platform=False)runs the conversion in memory and returns a dict{pixi_toml, default_rename_from, current_platform_addition_target, warnings}. Thepixi_tomlentry is the would-be file content; the rename / platform-addition fields report the candidates (whether or not their flag was passed), so a confirmation dialog can offer “re-render with –use-default” or “re-render with –add-current-platform” as actions;warningsis the leading# WARNING:block already extracted from the rendered TOML, so callers don’t have to grep for it themselves. RaisesCondaNotAvailableErrorif the conversion needsconda config --show default_channelsand conda isn’t reachable.anaconda_project.internal.pixi_export.default_rename_target(project)returns the env_spec name that--use-defaultwould promote, orNonewhen promotion is a no-op (project already has adefaultenv_spec, or has no env_specs at all). Stable contract; the underlying selection rule (default-command’s env, else first declared env_spec) lives in this one function.anaconda_project.internal.pixi_export.current_platform_addition_target(project)returns the host’s conda subdir if it would be added by--add-current-platform, orNonewhen it’s already in the union of declared platforms. Same shape asdefault_rename_targetso callers can decide both flags symmetrically.
Limitations#
A few aspects of anaconda-project.yml cannot be translated
faithfully. The exporter surfaces them as comments rather than
silently dropping them:
services:(e.g. Redis) — pixi has no equivalent service-launch primitive. The converted manifest carries a comment listing the required services so a maintainer can wire them up out of band.Variables without defaults — anaconda-project would prompt; pixi cannot. The converted manifest emits a comment listing variables that must be set in the environment before running.