Skip to content

feat(core): Rich Platform support#6178

Open
hunger wants to merge 77 commits into
prefix-dev:mainfrom
hunger:push-swkuqkqmorpx
Open

feat(core): Rich Platform support#6178
hunger wants to merge 77 commits into
prefix-dev:mainfrom
hunger:push-swkuqkqmorpx

Conversation

@hunger
Copy link
Copy Markdown
Contributor

@hunger hunger commented May 21, 2026

Description

Adds support for declaring workspace platforms as inline tables with a conda subdir plus per-virtual-package shortcut keys. The bare-string form still works and remains the default:

[workspace]
platforms = [
  { platform = "linux-64", cuda = "12.0", libc = "2.28" },
  { name = "jetson-nano", platform = "linux-aarch64", cuda = "12.8", archspec = "armv8-a" },
  "linux-64",
  { name = "osx-arm64" },
]

Remove SystemRequirements entirely. They get transparently mapped into features. The pixi.toml file stays unchanged -- till someone uses the command line to add a platform to a rich platorm or "enriches" a existing platform. At this point the file gets updated to the new syntax.

The pixi workspace platform command group is filled in:

  • add <PLATFORM>... learns six friendly virtual-package flags (--cuda, --archspec, --libc, --linux, --macos, --windows), with --libc/--linux/--macos/--windows each restricted to their matching subdir family. Trailing __name=version[=build_string] positionals are the raw escape hatch (mirroring the TOML __name = "..." keys). --feature scopes the addition to a feature.
  • edit <NAME> is new — change --subdir, add/upsert virtual packages (friendly flags + trailing __name=value positionals), remove (--remove-virtual-package) or clear (--clear-virtual-packages).
  • show [NAME] is new — human or --json output for one platform, --all, or --current (auto-detect this machine's subdir).
  • remove and list keep their existing shape.

Lock-file satisfiability now compares both the subdir and the virtual-package set for each workspace platform, and treats the virtual-package list as a set (order-independent). New non-satisfiability fixtures cover changed-subdir and changed-virtual-package; a satisfiability fixture covers virtual-package-permutation invariance.

The workspace JSON schema accepts the new platform-object form and rejects invalid names.

Closes: #4458, #346

How Has This Been Tested?

Unit tests in pixi_manifest and integration tests in tests/integration_python/test_workspace_platform.py, plus manual testing.

AI Disclosure

  • This PR contains AI-generated content.
    • I have tested any AI-generated content in my PR.
    • I take responsibility for any AI-generated content in my PR.

Tools: Claude

Checklist:

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • I have added sufficient tests to cover my changes.
  • I have verified that changes that would impact the JSON schema have been made in schema/model.py.

@hunger hunger requested a review from ruben-arts May 21, 2026 19:49
@ruben-arts
Copy link
Copy Markdown
Contributor

ruben-arts commented May 22, 2026

This is implemented completely differently from the design in #4458, what made you go for virtual-packges and subdir? The difference between platform <> subdir and system-requirements <> virtual-packages is going to be very confusing for our users.


The first thing I tried was:

[workspace]
channels = ["conda-forge"]
platforms = [{ name = "osx-arm64", subdir = "osx-arm64", virtual-packages = ["__osx=14"] }]

# [system-requirements]
# macos = "13.3"

[dependencies]
mlx = "*"

Which only works with the system requirements enabled. So I believe this the virtual packages are not hooked up.


There is very little for me to test right now as not sure what could work. It might be good to start out with making the examples/multi-machine work with this new setup. Note that you would need to implement the target."<platform-name>" to be feature complete with that example.

@hunger
Copy link
Copy Markdown
Contributor Author

hunger commented May 22, 2026

I am aware that this still differs significantly from #4458. You repeatedly asked to see this ASAP, this is the minimum working set I could push to enable you to take a look. Apparently I did fail abysmally at expectation management. Sorry for that.

I went with what we decided to put into the lock file: That is the bare-bones implementation we have to deal with -- at least while we stick with lockfile-v7 and Pixi has to interface with that.

What is here is the plumbing: You can define, edit and delete rich platforms -- which will internally stay with the internals they have I expect. They we wired up to get passed around and target selection works for them. Satisfiability checks fail when you change platforms. The cli is just for me to debug this plumbing. It needs to change, I am fully aware of that.

The Closes: #4458 is a probably overselling this point in time, I wanted to relate that change to that though and it seemed the easiest way to do that -- and I am certain it will close that request soon.

@hunger hunger marked this pull request as draft May 22, 2026 11:56
@hunger
Copy link
Copy Markdown
Contributor Author

hunger commented May 22, 2026

The pixi.toml file seems to trigger a bit too eagerly. I'll take a look next week.

@hunger hunger force-pushed the push-swkuqkqmorpx branch 2 times, most recently from 3d42139 to eb930a7 Compare May 26, 2026 11:35
@hunger
Copy link
Copy Markdown
Contributor Author

hunger commented May 26, 2026

Forgot to update the docs ;-)

@ruben-arts
Copy link
Copy Markdown
Contributor

[workspace]
channels = ["conda-forge"]
platforms = [{ name = "osx-arm64", platform = "osx-arm64", macos="14" }]

[system-requirements]
macos = "13.3"

[dependencies]
mlx = "*"

This creates one platform with a different name:

Environments
------------
        Environment: default
           Features: default
           Channels: conda-forge
   Dependency count: 1
       Dependencies: mlx
   Target platforms: osx-arm64-macos-13-3 (__osx=13.3) << I expect osx=14
    Prefix location: /Users/rubenarts/envs/test-rich-platform/.pixi/envs/default

Which I think is incorrect, the platforms configuration should "win" over the system-requirements.


The platform logic can't deal with the "rich" platforms yet. And I think we shouldn't rename them when we don't rename them.

[workspace]
channels = ["conda-forge"]
platforms = ["osx-arm64", "linux-64"]

[system-requirements]
macos = "13.3"
libc = "2.27"
cuda = "11"

[dependencies]
mlx = "*"
❯ pixi i
Error: unsupported-platform

  × The workspace does not support 'osx-arm64'.
  │ Add it with 'pixi workspace platform add osx-arm64'.
  help: supported platforms are linux-64-cuda-11-libc-2-27, osx-arm64-cuda-11-macos-13-3

I believe the platforms should still be named linux-64 and osx-arm64. And we shouldn't add cuda to osx. There is some existing logic that already deals with that.


[workspace]
channels = ["conda-forge"]
platforms = [
    { name = "osx-64", platform = "osx-64", macos="10.15" },
    { name = "latest-osx-arm64", platform = "osx-arm64", macos="15" },
    { name = "osx-arm64", platform = "osx-arm64", macos="14" },
    "linux-64",
]

[dependencies]
mlx = "*"
❯ pixi i
Error:   × Failed to update PyPI packages for environment 'default'
  ╰─▶ failed to get major and minor version from 'macos' version: '15'

I'm not sure what is going on here, but somehow I got into a state where this error was shown. I can't reproduce it so maybe check the error and if there could be a code path that ends up in that error state. Because that makes no sense to me.

@hunger
Copy link
Copy Markdown
Contributor Author

hunger commented May 26, 2026

@ruben-arts: You are hitting a limitation I do not see any good way around:

We use TargetSelector a lot, which are serialized as simple strings. The rich platforms have to fit into that model (or at least I thought that would be best), so I must make sure they serialize to a string that does not cause contradictions in the TargetSelector. For this reason family names like unix, win, etc. are forbidden for rich platforms. The subdir names are not forbidden, but to not conflict come with the extra requirement that they must not have virtual packages.

Case 1

This should have error-ed out on the platform definition (and indeed does so now;-).

Case 2

This is what happens: The current machine has no CUDA, so it does not match the specification of the platform attached to the workspace and errors out. That is technically correct, but the error message is abysmal and needs improvement. No question about that!

And macos machines should not get the cuda virtual package defined in the first place.

Update: This is what it will look in after the next push:

Error: unsupported-platform

  × The workspace does not support 'linux-64' on this machine:
  │ no declared platform's virtual packages are satisfied here.
  │
  │ Unsatisfied requirements: __cuda >= 11
  help: Mock the missing virtual packages via the environment, e.g.:
          CONDA_OVERRIDE_CUDA=11

Update II: Mac is now ignoring __cuda when generating a platfrom based on system-requirements. It still accepts the flag when you add it manually.

Case 3

I can not test this one: It effects macos only.

Update: It should be fixed in the next push.

@hunger hunger force-pushed the push-swkuqkqmorpx branch from eb930a7 to e899b95 Compare May 26, 2026 15:30
@hunger hunger marked this pull request as ready for review May 27, 2026 08:04
@hunger hunger force-pushed the push-swkuqkqmorpx branch 2 times, most recently from 1fbf69f to 568a026 Compare May 27, 2026 08:55
@ruben-arts
Copy link
Copy Markdown
Contributor

❯ pixi workspace platform show
Error:   × missing platform name; pass a name, `--all`, or `--current` to use the auto-detected subdir

Can you please make this default to --all with (current) behind the current platform?

@ruben-arts
Copy link
Copy Markdown
Contributor

➜ pixi info
System
------------
       Pixi version: 0.69.0
        TLS backend: rustls
           Platform: osx-arm64
   Virtual packages: __unix=0=0
                   : __osx=15.1.1=0
                   : __archspec=1=m4
...
Environments
------------
        Environment: default
           Features: default
           Channels: conda-forge
   Dependency count: 2
       Dependencies: mlx, python
   Target platforms: linux-64, osx-64-macos-10-15 (__osx=10.15), osx-arm64-macos-14 (__osx=14), latest-osx-arm64 (__osx=15)
    Prefix location: /Users/rubenarts/envs/test-rich-platform/.pixi/envs/default

Could you clearify in this info view which platform would be selected as the current platform?

@hunger hunger force-pushed the push-swkuqkqmorpx branch 2 times, most recently from 8543f28 to f2d5455 Compare May 27, 2026 12:41
Copy link
Copy Markdown
Contributor Author

hunger commented May 28, 2026

I am not happy yet with thepixi workspace platforms show vs pixi workspace platform list situation. Maybe we can find a nicer way together…

The list is not listing platforms at all, it lists environments and how those relate to subdirs mostly, that is not what I expect to be shown now that we have platforms as a user-facing context anymore.

How about removing the show variant and merging that into list?

Maybe something like this:

Your computer: linux-64 { subdir = linux-64, unix, glibc=2.42 }

Platform: linux-64 { subdir = linux-64 }
Platform: win-64 { subdir = win-65 }

Then dim out anything not matching your computer?

Then have --environments append all the environment information we show now. I think you need the context of the platforms now to really make sense of the information presented.

I would not want to keep the --current flag show has right now. It is not the currently selected platform or anything, it is just "the platform your current computer was detected as". This information is interesting to have, let's show that always and with a better name. All that information does is limit which platforms you can select without overriding something.

@ruben-arts
Copy link
Copy Markdown
Contributor

ruben-arts commented May 28, 2026

If I rename a platform it doesn't invalidate the lockfile.

@ruben-arts
Copy link
Copy Markdown
Contributor

As discussed, could you rewrite the show command to list with this look:

❯ pixi workspace platform list
Your machine was detected as: osx-arm64( unix, osx=15.1.1, archspec=1=m4 )

cuda-linuxxxx: linux-64 ( unix, linux=5.2, glibc=2.17, cuda=12, cuda_arch=11.7)
  Used by environment:  default
  From feature: default

linux-64: linux-64 ( unix, linux=5.2, glibc=2.17 )
   Used by environment:  default
   From feature: default

osx-arm64: osx-arm64 ( unix, osx=10.13 )
  Used by environment:  default
  From feature: default

@hunger
Copy link
Copy Markdown
Contributor Author

hunger commented May 28, 2026

Workspace-platform UX rework

Five commits, all building on the rich-platform work that's already on the
branch. The theme is: hide subdirs and virtual packages from the user-facing
vocabulary, make PixiPlatform carry everything we need, and migrate the rest
of the CLI to accept platform names where it used to take conda subdirs.

Commits

Change Title
1 feat(manifest): materialise subdir defaults on every PixiPlatform
2 feat(lock-file): rename platforms to match the manifest at load
3 feat(cli): redesign 'pixi workspace platform list'
4 feat(cli): accept PixiPlatformName on platform-filter flows
5 feat(cli): add --platform to 'install' and 'reinstall'

1. feat(manifest): materialise subdir defaults on every PixiPlatform

PixiPlatform::from_subdir now stores the subdir's defaults (__unix,
__linux, __glibc, __win, __osx, __archspec where applicable) in
declared_virtual_packages. PixiPlatform::auto_detected keeps the empty
placeholder for the workspace fallback and the CLI host-detection display.
Subdir platforms are now locked down -- the only legal edit is to transition
into a rich entry by adding a non-default VP.

  • PixiPlatform::new_with_defaults -- one-shot rich-platform constructor that
    merges defaults under user-declared overrides. The TOML deserializer routes
    through it.
  • Synthesised auto-names and TOML serialisation filter out default-matching
    VPs, so the on-disk shape stays terse.
  • get_minimal_virtual_packages drops its inferred-defaults branches and just
    dispatches the platform's declared list to the typed rattler
    VirtualPackage variants.
  • The lock-file satisfiability check and possible_pixi_platforms filter
    strip materialised defaults before comparing, so pre-existing lockfiles
    keep satisfying.

2. feat(lock-file): rename platforms to match the manifest at load

When the workspace renames a platform (e.g. linux-64-cudagpu-linux)
the lock-file still carries the old name. The new platform_rename module:

  • Walks the locked platforms after parse.
  • Matches them against the manifest by identity (subdir + non-default VPs).
  • Rebuilds the LockFile via LockFileBuilder with the manifest's current
    names.
  • Skips colliding renames and ambiguous matches so the rebuilt lockfile stays
    lossless.

Downstream consumers see workspace-current names without any further changes.

3. feat(cli): redesign 'pixi workspace platform list'

Drops pixi workspace platform show. list now prints the auto-detected
host as a header followed by a Platforms: block. Each platform renders on
one line:

Your current machine was detected as:
    subdir=linux-64, libc=2.31

Platforms:
linux-64: subdir=linux-64 (supported by current machine)
    Used in environments: default
gpu-linux: subdir=linux-64, cuda=12.0 (supported by current machine)
    Used in environments: default, gpu
    Used in features    : cuda
osx-arm64: subdir=osx-arm64
    Used in environments: default, unreachable

Format details:

  • <name>: subdir=<subdir>[, key=value, ...] -- uses the same friendly
    shortcuts (cuda, archspec, libc, linux, macos, windows) as
    pixi workspace platform add and the TOML keys; raw __name=value entries
    pass through.
  • Materialised subdir defaults are filtered.
  • Names of platforms supported by the current machine are highlighted;
    the suffix (supported by current machine) is appended.
  • The subdir or VP entries that block a match are dimmed.
  • Used in environments: / Used in features : continuation lines dim
    any env/feature whose platforms are all unreachable on this host.
  • PIXI_OVERRIDE_PLATFORM and CONDA_OVERRIDE_* are honoured, so cross-
    target inspection works without setting flags ad hoc.

4. feat(cli): accept PixiPlatformName on platform-filter flows

Promotes pixi_api::workspace::platforms::resolve_platforms to pub and
routes the dependency, task, and platform-filter CLI flows through it:
workspace lookup with a bare conda-subdir fallback, same UX shape as
pixi add --platform.

  • Migrates update, list, tree, search, exec, import from
    rattler's Platform (subdir) to PixiPlatformName. The resolver runs
    against the workspace when one is in scope and falls back to subdir-parse
    alone for the workspace-less flows (exec, search without a manifest,
    import).
  • Task add / remove / alias drop the strict lookup_platform helper
    and share the dep-flow resolver. task add and task alias now auto-
    declare a subdir-platform on first use, matching pixi add --platform <subdir>. task remove reuses the lookup but leaves the manifest alone.
  • Adds Rust integration tests for the task auto-declare and no-auto-declare
    branches.

5. feat(cli): add --platform to 'install' and 'reinstall'

pixi install --platform <name> and pixi reinstall --platform <name>
materialise the prefix for any workspace platform, including subdirs the
current machine can't run. A warning is printed when the target isn't a
candidate for the host:

warning: installing for platform 'latest-osx-arm64' (subdir 'osx-arm64'),
which this machine ('linux-64') can not run -- packages will be downloaded
and extracted but won't be executable here

Implementation:

  • New Environment::pinned_platform(override) -- the install path uses it
    instead of best_platform so the host-VP satisfaction filter is skipped
    when an override is set.
  • LockFileDerivedData carries the target as a field so the prefix /
    conda_prefix helpers honour it without a fresh parameter at every site.
  • validate_system_meets_environment_requirements is bypassed when the
    override is pinned -- that's the case the override exists for.
  • Shared resolve_install_platform helper handles resolution + warning;
    ReinstallOptions gained a target_platform field for the non-CLI
    surface.

Coverage

Command Status before today Status after today
add, remove, task add/remove/alias mixed (strict on tasks) unified via shared resolver
update, list, tree, search, exec, import Platform (subdir) PixiPlatformName
install, reinstall no flag --platform <PixiPlatformName> with cross-target warning
workspace platform list / show two commands one list with rich output

Still pending (flagged but not in this batch):

  • --platform parity on lock, upgrade.
  • --platform on shell-hook (informational).
  • Migrate build / publish from --target-platform <Platform> to a
    PixiPlatformName-aware resolver.

Tests

  • 1973 workspace tests pass (cargo nextest run --workspace).
  • Clippy clean on all targets.
  • All integration-test additions:
    • platform_rename unit tests (4): identity match, no-rename when target
      name is already taken, no-rename for unmatched entries, default-VP
      tolerance.
    • Task integration tests (2): task add --platform <subdir> auto-declares;
      task remove --platform <subdir> does not.
    • pixi workspace platform list Python integration tests covering the new
      output: detection header, supported marker, dim/highlight behaviour,
      env/feature continuation lines, CONDA_OVERRIDE_CUDA /
      PIXI_OVERRIDE_PLATFORM honoured.
    • resolve_install_platform unit tests (4): unset returns None, declared
      platform resolves, cross-platform subdir resolves with warning, invalid
      name errors.

CLI documentation regenerated via pixi run generate-cli-docs.

@ruben-arts
Copy link
Copy Markdown
Contributor

➜ pixi i
Error:   × Unexpected keys, expected only 'name', 'platform', 'cuda', 'archspec', 'libc', 'linux', 'macos', 'windows'
    ╭─[/Users/rubenarts/dev/pixi/examples/multi-machine/pixi.toml:13:31]
 12 │     { platform = "osx-64", osx = "13.5" },
 13 │     { platform = "osx-arm64", osx = "13.5" },
    ·                               ─┬─
    ·                                ╰── 'osx' was not expected here
 14 │ ]
    ╰────

I think we should still support osx here even if it's just an alias.

@ruben-arts
Copy link
Copy Markdown
Contributor

[workspace]
channels = ["conda-forge"]
description = "A mock project that does ML stuff"
name = "multi-machine"
# The platforms fields can define custom platform features, like `cuda` and `osx` in this example.
# Use the field `name` to easily manage platform specific definitions.
platforms = [
    { name = "cuda-win-64", platform = "win-64", cuda = "12"},
    "win-64",
    { name = "cuda-linux-64", platform = "linux-64", cuda = "12" },
    "linux-64",
    { platform = "osx-arm64", macos = "13.5" },
]

[tasks]
start = { depends-on = ["train", "test"] }
test = "python test.py"
train = "python train.py"

[dependencies]
ipykernel = "*"
matplotlib-base = "*"
polars = "*"
python = "*"
torchvision = "*"
pytorch = "*"

[target.cuda-win-64.tasks]
test = "python test.py --cuda"
train = "python train.py --cuda"
[target.cuda-linux-64.tasks]
test = "python test.py --cuda"
train = "python train.py --cuda"

[target.osx.dependencies]
mlx = ">=0.16.1,<0.17"

[target.osx.tasks]
test = "python test.py --mlx"
train = "python train.py --mlx"

[target.osx-arm64.dependencies]
mlx = "*"

Having this toml, with especially this line:

[target.osx-arm64.dependencies]
mlx = "*"

It will add the following table after pixi upgrade

[target.osx-arm64-macos-13-5.dependencies]
mlx = "*"

@ruben-arts
Copy link
Copy Markdown
Contributor

In the single environment usecase I would leave out the Used in environments part:

Your current machine was detected as:
    subdir=osx-arm64, macos=15.1.1, __archspec=1=m4

Platforms:
cuda-win-64: subdir=win-64, cuda=12
    Used in environments: default
win-64: subdir=win-64
    Used in environments: default
cuda-linux-64: subdir=linux-64, cuda=12
    Used in environments: default
linux-64: subdir=linux-64
    Used in environments: default
osx-arm64-macos-13-5: subdir=osx-arm64, macos=13.5 (supported by current machine)
    Used in environments: default

@ruben-arts
Copy link
Copy Markdown
Contributor

Right now changing the name in the platforms will not rename the platform in the lockfile. With that I questioning whether the reuse of that name in the lockfile makes sense at all.

If we don't sync the lockfile with this name I would use a different identifier for the lockfile platforms.
If we do use the names I would invalidate the lockfile and introduce the same name again in the lockfile.

For example changing this:

platforms = [
    { name = "cuda-win-64", platform = "win-64", cuda = "12"},
]

To this:

platforms = [
    { name = "bla", platform = "win-64", cuda = "12"},
]

Will keep this value in the lockfile right now:

version: 7
platforms:
- name: cuda-win-64
  subdir: win-64
  virtual-packages:
  - __cuda=12
  - __win=10.0
  - __archspec=0=x86_64

@ruben-arts
Copy link
Copy Markdown
Contributor

ruben-arts commented May 29, 2026

Blocking issue, we've decided previously that we don't block the run of an environment based on the system requirements but based on the virtual package requirements of the environment.

e.g.:

[workspace]
channels = ["conda-forge"]
platforms = ["osx-arm64"]

[dependencies]
python = "==3.12.0"

[system-requirements]
macos = "50.0"

Even though 50 obviously wouldn't match my machine, the resulting environment will also not depend on it. So it should block me from running this environment. Same thing still holds with this redesign. It should pick the first platfor m that it "can" run.

The current implementation will give this error:

➜ pixi i
Error: unsupported-platform

  × The workspace does not support 'osx-arm64' on this machine:
  │ no declared platform's virtual packages are satisfied here.
  │ 
  │ Unsatisfied requirements: __osx >= 50.5
  help: Mock the missing virtual packages via the environment, e.g.:
          CONDA_OVERRIDE_OSX=50.5


Edit:
After discussing this it should be changed back to the original behavior introduced in https://github.com/prefix-dev/pixi/pull/2849/changes where we check whether the environment is supported on your machine from the virtual packages in the lockfile instead of the ones you define in the system-requirements/platforms.

@baszalmstra
Copy link
Copy Markdown
Contributor

Here are some more review notes from clanker:

Panic in install --platform --only/--skip skip-summary

The install proceeds via the --platform override (cross-target permitted), but the post-install skip-summary calls environment.best_platform().expect("environment supported on this system") (install.rs:176-177). best_platform() ignores the override and returns None when no env platform is runnable on the host. The best_platform().is_some() filter (install.rs:118-134) only runs under --all, so the single-env path (at_most_one, line 166) with an explicit -e/default env is unprotected. Repro: on linux, an env whose only platform is osx-64, run pixi install --platform osx-64 --only → install succeeds, then line 177 panics. Use the resolved target platform (or pinned_platform) here instead of best_platform().expect.

Logic bug: if !num_skipped > 0 in install skip-summary (always true)

Confirmed at install.rs:208. !num_skipped > 0 on a usize parses as (!num_skipped) > 0 (bitwise-NOT), which is always true (NOT of 0 is usize::MAX). Intended: num_skipped > 0. The block is always entered, so a clean install with valid args can still print "no packages were skipped (check if cli args were correct)". Clippy doesn't catch it because ! on an integer is legal Rust.

pixi workspace platform show is documented but doesn't exist

The CLI Command enum (crates/pixi_cli/src/workspace/platform.rs:354-367) has only Add/Edit/List/Remove — show_to_json is an internal helper for list --json. Three docs still reference show as a command: docs/workspace/multi_platform_configuration.md:129 and docs/workspace/system_requirements.md:103-104. Remove the / show references (the showlist merge from review #7/#9 already happened in code).

Schema accepts platform name values the parser rejects (reserved family names)

schema/model.py:111-116 (name pattern) enforces the character rules but doesn't exclude the reserved family names linux/unix/win/osx/macos that Rust rejects as ReservedName (platform.rs:93-97). { name = "linux", platform = "linux-64", cuda = "12" } passes schema validation but fails the parser. Add a not/enum exclusion to the schema, or document the restriction.

Schema accepts empty platform table {} that the parser rejects

The parser requires at least one of name/platform and rejects platforms = [{}] (toml/platform.rs:390-397), but the schema (WorkspacePlatform, no required fields, extra="allow") accepts it. The schema can't express the "at least one of name/platform" rule. Consider an anyOf/required hint plus an empty-table invalid example.

Misleading error: --platform naming a valid platform not in the chosen environment

resolve_install_platform validates the name against the workspace, not the environment. If the platform exists but isn't in the environment's set, pinned_platform() returns None and the user sees "no platform supported by it matches the current system" — wrong reason. Repro: pixi install --platform osx-64 -e linux-only-env. Give a specific "platform X is not part of environment Y" error; also the cross-target warning prints before this failure.

add on an existing platform no-ops but prints "Added …"

pixi workspace platform add linux-64 when it already exists filters out the existing entry and returns early (workspace.rs:~457-483), yet the reporter prints "Added linux-64" (pixi_api/.../workspace/platform.rs:~105). Report "already present / nothing to do" instead of a false success.

archspec example inconsistent (x86_64_v3 vs x86-64-v3)

schema/model.py:127 and the generated add.md/edit.md use underscores; schema.json:3320, the toml tests, and multi_platform_configuration.md use dashes. Both parse, but unify the example spelling.

New docs use em-dashes/ellipses against repo convention

multi_platform_configuration.md, system_requirements.md, pixi_manifest.md use em-dashes and ; the rest of the docs avoid them. Normalize (commas/parens/periods).

Raw __ VPs that rattler doesn't know are silently ineffective at detection

overrides_from_declared (platform.rs:580-605) maps only known names to rattler override slots; __unix and unrecognized names are dropped (no detection effect) while still round-tripping in TOML. Intentional for forward-compat, but a user declaring __some_new_vp gets no signal it has zero runtime effect. Add a doc note.

add silently collapses duplicate positionals

pixi workspace platform add linux-64 linux-64 collapses silently. Harmless, but a "specified more than once" note (like the VP-dedup path already does) would be consistent.

hunger added 25 commits June 5, 2026 15:37
A `--platform` naming a valid workspace platform that the chosen
environment does not list now fails with "platform X is not part of
environment Y" before any cross-target warning is printed. The warning
moves into the install path so it only fires once membership is
confirmed.
'pixi workspace platform add' printed "Added ..." even when the platform
was already declared. add_platforms now returns the platforms that
actually changed (workspace or feature), and the reporter says "Platform
X is already present; nothing to do" for the rest. As a side effect,
re-adding a platform to a feature no longer appends a duplicate entry to
the TOML array.
…slot

overrides_from_declared mapped every unrecognized `__name` virtual
package to the libc override slot, so a forward-compat escape-hatch entry
like `__future_pkg` silently changed the detected libc version. Restrict
the catch-all to the libc family (`__glibc`/`__musl`/`__eglibc`); any
other raw `__name` now round-trips through TOML but has no detection
effect, as the doc comment and the user docs now state.
…m add'

'pixi workspace platform add linux-64 linux-64' silently collapsed the
duplicate. It now errors with "platform X was specified more than once on
the command line", mirroring the virtual-package dedup path.
Extend the `conda-meta/pixi` marker file with the platform the environment
was resolved with and the minimum platform its installed packages actually
require. Store each as subdir plus declared virtual packages rather than the
platform name, so a synthesised rich-platform name need not be parsed back.

Both fields are optional and default-deserialized so marker files written by
an older pixi still load without forcing a reinstall.
Read the per-environment conda-meta/pixi marker and surface, for each
installed environment, the platform it was resolved for and the minimum
platform its installed packages actually require -- alongside the manifest's
declared 'Target platforms'. Both appear in the text output and as
resolved_platform / minimum_supported_platform in --json, and are omitted
when the environment isn't installed.

Exposes PlatformData and Environment::installed_platforms from pixi_core to
back the new output.
Print a one-line 'Installed for: <platform>' note after the environment
header in 'pixi list' and 'pixi tree', read from the conda-meta/pixi marker,
with an emulation hint when the installed subdir differs from the host. The
note is skipped when the environment isn't installed and is not emitted in
'pixi list --json'.
Accept a '--platform/-p' override on 'pixi run', mirroring 'pixi install':
it pins which declared platform the environment is installed and activated
for, and is threaded into the on-demand prefix install via the lock file's
target_platform. A '--platform' the environment doesn't list reports the
same clear membership error as install, and task resolution uses the pinned
platform.
…ost_platform

Replace the scattered trio of current_platform_with_override(),
detect_system_virtual_packages(), and Environment::host_platform() with a
single Workspace::host_platform(source, overrides) method that is the sole
source of truth for what pixi treats as this machine's host platform.

Two orthogonal enums control the result:
- PlatformSource::{Defaults, AutoDetected} -- pixi's per-subdir defaults
  (deterministic, used at solve time) vs. the virtual packages actually
  detected on the machine.
- PlatformOverrides::{NoOverrides, EnvironmentVariableOverrides} -- whether
  PIXI_OVERRIDE_PLATFORM and CONDA_OVERRIDE_* are honoured.

apply_environment_variable_overrides() layers CONDA_OVERRIDE_* on top of
any base, matching upstream rattler semantics: unset keeps, non-empty
replaces, empty disables.

Also:
- Rename best_platform -> best_declared_platform: it now returns None for
  workspaces with no declared platforms instead of synthesising a fallback,
  removing the fallback_platform OnceCell field from Workspace entirely.
  Callers that need a host fallback call workspace.host_platform() explicitly.
- Rename pinned_platform -> named_or_best_declared_platform and simplify
  its body to use platform_by_name(), correctly handling empty env platform
  sets (meaning 'all platforms') as a no-restriction case.
- Remove the duplicate current_platform_with_override() from pixi_cli.
- Migrate the fallback stanzas in activation.rs and lock_file/resolve/pypi.rs.
- Migrate tool-platform detection in Workspace::to_command_dispatcher.
rebuild_with_renames copied each LockedPackage raw into a fresh builder,
carrying PackageHandle indices that pointed into the old lock file's
package table. Packages only reachable through a source record's
build_packages / host_packages are in no environment list, so they were
never registered in the new table: the serialized lock file referenced
them by URL but lacked their records, and failed its own reparse with
MissingPackage. Both rename passes hit this -- shorten_platform_names
corrupted every save of a rich-platform workspace and
align_platform_names dropped the packages in memory on every load.

Route conda packages through LockFileResolver + into_conda_package_data
instead, mirroring filter_lock_file, so build/host subtrees are
re-registered against the new builder's package table.
…platforms

compute_minimal_required_platforms silently dropped specs without a
version (e.g. tensorflow's bare `__cuda`), so machines lacking the
package entirely looked minimum-compatible while
validate_system_meets_environment_requirements rejected them. Treat a
bare spec as version 0, render it as "(any version)" instead of
">= 0", and suggest a realistic CONDA_OVERRIDE_* value.
apply_environment_variable_overrides only rewrote packages the machine
already provides, so CONDA_OVERRIDE_CUDA on a machine without a GPU was
ignored. Match rattler, which runs every family's detector regardless of
the host OS, by adding overridden packages that detection missed.
'pixi task list' claimed tasks from environments whose resolved
dependencies looked compatible, while 'pixi run' could not even find
them: the task search filtered every environment through the default
environment's platform, and the run/install gates rejected any
environment whose declared platforms don't match the machine before
the lock-based minimum check could apply. A task that was filtered
out fell through to shell execution and reported 'command not found'.

Make the paths agree:

- Search each environment with its own platform (installed marker,
  best declared, first declared); '--platform' still pins strictly.
- Error out, naming the defining environments, instead of falling
  back to a shell command when the name matches a task anywhere.
- Let run/install fall back to a declared platform whose lock-resolved
  minimum requirements the machine meets; verify_run_platform then
  warns once (runs "by accident") or errors from conda-meta/pixi.
- Annotate every task in 'pixi task list' with how this machine runs
  its environment: "(by design)" when the resolved platform's
  requirements are met, "(by accident)" when only the minimum is.
A source build for a foreign '--platform' fails with rattler-build's
bare 'Script failed to execute', leaving the real cause invisible. When
the build target differs from the machine, add a help text naming both
platforms and suggesting to retry without '--platform' or install on a
matching machine.
VirtualPackageNotFoundError and UnsupportedPlatformError each carried
their own virtual-package-name to CONDA_OVERRIDE_* mapping with the
same hardcoded example versions. Share one helper that prefers the
actually-required version and falls back to a realistic example for
version-less requirements. This fixes the old TODO in
VirtualPackageNotFoundError: '__glibc >=2.28' now suggests
CONDA_OVERRIDE_GLIBC=2.28 instead of always 2.17, and gains hints for
__linux, __win, and __archspec.
Lock-file platform names come from rattler's permissive PlatformName but
were converted to the stricter PixiPlatformName with `.expect(...)`, so a
foreign or hand-edited lock file (e.g. a platform named `linux`, or one
containing an underscore) crashed pixi at the boundary.

Handle the mismatch per call site: satisfiability and the update path skip
such names (the platform is re-solved), the lock-file rebuild keeps their
packages verbatim instead of dropping them, and the diff keys on the raw
name so foreign platforms still appear. Add a pixi_diff regression test.
Rename the workspace-platform `libc` friendly key to `glibc` (it always
mapped to `__glibc`; the old name implied a generic libc) across the TOML
model, the `pixi workspace platform` CLI flag, the JSON schema, docs, and
tests.

Make the libc family behave correctly when a user configures musl/eglibc,
which sit beyond CEP-30's glibc-only standard:

- `CONDA_OVERRIDE_GLIBC` now governs glibc alone: unset leaves libc packages
  untouched, an empty value removes `__glibc`, and a version pins
  `__glibc=<version>=0` and displaces `__musl`/`__eglibc`. It no longer
  rewrites or drops non-glibc families through rattler's glibc-only slot.
- Run-time verification accepts `__musl`/`__eglibc` instead of silently
  skipping them, so a configured musl requirement is actually checked.
- The detected-package display restores the declared libc family rather than
  showing the glibc label rattler's override slot forces.

The subdir-default merge already suppresses the default `__glibc` when a libc
family is declared; this adds the missing test coverage for that.
`PixiPlatform`'s `Eq`/`Hash` are by name only, so two platform entries that
resolve to the same name silently collapsed into the name-keyed `IndexSet`,
keeping whichever appeared first. Deserialize the entries with per-entry
spans and emit a `DuplicateKey` error pointing at both occurrences instead.
The derived `Deserialize` (transparent over `String`) accepted any value -
empty, reserved family names, malformed - bypassing the validation every
other construction path enforces via `TryFrom`. `pixi_api` deserializes
`Vec<PixiPlatformName>` from external input, so replace the derive with a
hand-written impl that routes through `TryFrom` and rejects invalid names.
After renaming the platform key to `glibc`, three docs still referenced the
old `libc` friendly key / a nonexistent `__libc` package in prose mappings
and a CLI example; update them to `glibc`/`__glibc`.

Also fix doc-comment merge artifacts: an orphaned first line and a sentence
truncated mid-word on `print_autodetected_host`, and a marker-file paragraph
that documented `installed_platform_data` but sat on `install_platform`.
`store_credentials_from_project` looked up each `env.platforms()` name with
`.expect("Internal error: Unknown platform used")`. A feature whose declared
`platforms` list outlives a workspace platform removal (a dangling reference)
yields a name absent from `workspace.platforms`, panicking the lookup.

Drop the panicking helper and pass the `Option` through to
`combined_dependencies`, matching the sibling
`extract_git_requirements_from_workspace`.
Unify the three divergent virtual-package renderings (pixi info, pixi
workspace platform list text, and its --json) onto a single
`inline_virtual_package_specs(declared, baseline)`: friendly key when one
exists (`cuda`, `glibc`, ...), raw `__name` otherwise, and a bare key for a
version-0 entry. The optional `baseline` filters the subdir defaults -- passed
for declared platforms, omitted for the computed resolved/minimum sets.
`add_task` and `alias_task` duplicated the resolve-platform + auto-declare +
`add_task` block verbatim. Lift it into `declare_platform_and_add_task` so the
platform-resolution rules stay in lock-step by construction; the only
difference (the feature the task lands on) is a parameter.
`get_macos_platform_tags` accepted a single-segment `__osx` version (`15` ->
`15.0`) but `get_pypi_platform_from_virtual_packages` errored on it. Lift the
fallback into a shared `macos_major_minor` helper used by both, so a major-only
macOS version resolves the same on the tag path and the validation path.
Polish pass over the rich-platforms branch following review:

- document: stop creating an empty `[feature.X]` table as a side effect of
  removing `[system-requirements]` from a feature that has no table
- platform CLI: reject a bare `__` as a virtual-package spec
- virtual_packages: rewrite the archspec if/else as an early return
- environment: sort references (not clones) in `virtual_packages_match`, and
  expose `installed_resolved_platform` to avoid a second name-based lookup
- features_ext: replace the per-feature `Box<dyn Iterator>` with `itertools::Either`
- task search: drop the redundant double platform lookup
- use `temp_env::with_var` instead of unsafe `set_var` in the platform test
- drop a speculative TODO and dead test scaffolding

Tests:
- platform axis of the `UnrunnableTask` guard
- host-only (not just build-only) package preservation across platform rename
- `run --platform` membership error fires before any solve
- `VirtualPackageArgs::into_specs` subdir-family validation
@hunger
Copy link
Copy Markdown
Contributor Author

hunger commented Jun 5, 2026

I tried this manifest

[workspace]
name = "legacy-sysreq"
channels = ["conda-forge"]
platforms = ["linux-64"]

[system-requirements]
libc = "2.28"

[dependencies]
python = "3.11.*"

Running it with released Pixi this just works, but with this PR, I get the following:

Error:   × synthesised platform 'linux-64' is invalid: You tried to add a virtual package into a special subdir platform

Embarassing: The most basic thing you could do broke! Fixed now.

@hunger
Copy link
Copy Markdown
Contributor Author

hunger commented Jun 5, 2026

I think it would be good to add a little gradual transition for system-requirements. Instead of outright removing the cli command I think we should either:

  1. Keep them working as, and show a deprecation warning.
  2. Keep them working, update the workspace.platform section instead, and show a deprecation warning.
  3. Not keep them working but just show an error how to translate them to the new command.

I would go for 1.

I want to keep all existing pixi.toml files fully working unchanged. A secondary concern is maintainability and not giving users extra rope to hang themselves with. So I opted for implicit conversion and doing a clean cut as soon as a user opts in to the new way of doing things. Just one code path to worry about -- and the files stay all valid and can be updated when the user explicitly starts using the new feature.

But yes, we could keep the old commands for system requirements working normally, but make them error out when rich platforms are used.

@hunger
Copy link
Copy Markdown
Contributor Author

hunger commented Jun 5, 2026

@Hofer-Julian: The reordering and also the format-change should be fixes next push.

`workspace platform edit` rebuilt the whole [workspace].platforms array from
a sorted view, which reordered the entries and collapsed the multi-line
layout into one line. A single-platform edit now replaces only that entry in
place, preserving its surrounding whitespace and the order of the other
entries; the full rebuild is kept only for the legacy [system-requirements]
migration, where every entry transforms at once.
@hunger hunger force-pushed the push-swkuqkqmorpx branch from 9f10387 to 990a88a Compare June 5, 2026 16:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Proposal to redefine the platforms to include their virtual packages.

4 participants