Skip to content

feat: support inline package definitions in package dependency tables#6523

Draft
Hofer-Julian wants to merge 17 commits into
mainfrom
claude/inline-manifest-dependencies-e4z9w0
Draft

feat: support inline package definitions in package dependency tables#6523
Hofer-Julian wants to merge 17 commits into
mainfrom
claude/inline-manifest-dependencies-e4z9w0

Conversation

@Hofer-Julian

@Hofer-Julian Hofer-Julian commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Description

This PR extends inline package definition support to package dependency tables ([package.run-dependencies], [package.host-dependencies], [package.build-dependencies], and [package.extra-dependencies.*], including their if(...) conditional sub-tables), and adds support for inheriting inline definitions through the [workspace.dependencies] pool via { workspace = true } markers.

Key changes:

  1. Package dependency table support: Inline package = { ... } definitions are now accepted in a package's own dependency tables, allowing manifest-less source dependencies to be described inline at the point of consumption. This works in any package manifest pixi builds — the workspace's own [package] as well as the manifests of path/git/url source dependencies — and definitions now nest, so a chain of manifest-less repositories (A→B→C) can be described from one place.

  2. Workspace pool inheritance: Entries in [workspace.dependencies] can now carry inline definitions. When a package dependency uses { workspace = true }, it inherits both the source spec and the inline definition together from the pool. Combining workspace = true with a use-site package = {...} table is rejected, pointing at the pool.

  3. Solve integration: Backend discovery collects each package's inline definitions (never serialized into the metadata disk cache; discovery runs on cache hits too). They feed the nested build/host environment solves and are attached to transitive source dependencies during the environment walk, matched by name against the backend-reported dependencies.

  4. Conflict rules: A direct dependency of the environment overrides package-level definitions (the existing seed-first behavior, now documented). Two packages disagreeing about the definition for the same (package, source location) fail the solve with a ConflictingInlineDefinitions error naming both parents. Inline definitions stay rejected in [constraints] and [package.run-constraints] (binary-only), and a name may carry at most one definition per target.

  5. Build & verify integration: SourceBuildKey and the environment install re-derive the definition maps via cached discovery so inline-defined packages build at install time, and the mutable-source satisfiability path falls back to the workspace's own [package] definitions (transitive declarers force a re-lock, resolved through the regular walk).

Documentation: docs/build/inline_packages.md now documents where inline definitions are accepted, pool inheritance, nesting, and the conflict rules. The JSON schema already admitted package on these tables (InheritableMatchspecTable inherits it from MatchspecTable, and the pool uses the same spec type), so no schema/model.py change is needed — the parser now matches the schema.

Test data: Added tests/data/pixi-build/inline_package_run_dep/, where package_b's on-disk manifest names a backend that cannot exist and only the inline definition in package_a's [package.run-dependencies] can make the build succeed — a discriminating end-to-end case.

How Has This Been Tested?

  • Unit tests in crates/pixi_manifest (parse-layer validation, pool inheritance, duplicate/conflict rejection, nesting, hash stability); the full pixi_manifest and pixi_command_dispatcher suites pass.
  • Integration tests in tests/integration_python/pixi_build/test_inline_packages.py:
    • fast, offline guards (run against a locally built debug binary): package tables accept the syntax, run-constraints rejects it, pool inheritance resolves, { workspace = true, package = {...} } errors;
    • a slow end-to-end build (test_inline_in_package_run_dependencies_builds) mirroring test_recursive_source_run_dependencies; it needs channel access and runs in CI.
  • pixi run lint-fast jobs verified: cargo fmt, ruff lint/format, taplo, typos, dprint, ty, and cargo-shear all pass.

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 Code

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.

https://claude.ai/code/session_019UVuWiTRdhz5PokX91rcU4

@Hofer-Julian Hofer-Julian changed the title Support inline package definitions in package dependency tables feat: support inline package definitions in package dependency tables Jul 3, 2026
@Hofer-Julian Hofer-Julian marked this pull request as draft July 6, 2026 07:42
Parse `package = {...}` inline definitions in
`[package.run-dependencies]`, `[package.host-dependencies]`,
`[package.build-dependencies]` and `[package.extra-dependencies.*]`
(including their `if(...)` conditional sub-tables), mirroring the
workspace-level dependency tables:

- `InheritablePackageMap` peels the inline definition off each entry
  before spec parsing. The surrounding spec must be a `git`, `path` or
  `url` source, and combining a definition with `workspace = true` is
  rejected pointing at `[workspace.dependencies]`.
- `PackageTarget` gains a name-keyed `inline_packages` map, drained
  from all dependency tables of the target (one definition per name),
  converted through the same `TomlPackage::into_manifest` path as
  workspace-level definitions and fingerprinted the same way (recipe
  now shared via `InlinePackageManifest::from_named_manifest`).
  `run-constraints` rejects inline definitions; constraints are
  binary-only.
- The stable hash of `PackageTarget` folds in `(name, content hash)`
  pairs; empty maps are skipped so existing hashes are unchanged.
- Inline definitions now nest: a definition's own dependency tables are
  parsed by the same code, so a manifest-less source dependency of
  another inline definition can itself be defined inline.

Definitions parsed here are not yet honored by the solve; that plumbing
follows in later commits.
…] pool

Pool entries may attach an inline package definition to a source spec.
A `{ workspace = true }` package dependency inherits the definition
together with the spec — a manifest-less source location without its
definition would fail backend discovery at build time.

- `WorkspaceDependencyMap` peels `package = {...}` off each entry and
  requires a `git`, `path` or `url` source location next to it.
- Definitions convert once at workspace assembly (inheriting the
  workspace's external package properties, like the workspace-level
  dependency-table definitions do) and are stored on
  `Workspace::dependency_inline_packages` /
  `WorkspacePackageProperties::dependency_inline_packages`.
- `into_package_target` adopts the pool definition for inherited
  entries. The same pool entry inherited in several tables is fine
  (identical content); clashing with a direct definition of the same
  name errors as a duplicate.
- Backend inheritance from a definition-carrying pool entry keeps
  erroring via the existing 'build backends cannot be defined as source
  dependencies' rejection (such entries are always source specs).
Inline package definitions declared in a package's dependency tables now
take effect for every source package pixi resolves, uniformly for run-,
host-, build- and extra-dependencies:

- `PackageManifest::combined_inline_packages` merges the definitions
  across the default and conditional targets. Which conditional target
  applies is decided by the backend, so matching is by name alone;
  parsing rejects a name carrying different definitions in different
  targets.
- `DiscoveredBackend` carries the merged map (plus the workspace
  manifest that gives definitions their channel context) off backend
  discovery. The map is not serialized: it feeds only the in-process
  pipeline, never the metadata disk cache. Discovery runs on cache hits
  too, so `BuildBackendMetadata` and `SourceOutputs` expose the map on
  both paths.
- `assemble_source_record` passes the package's map into its nested
  build/host environment solves; the existing seed-attachment logic
  matches definitions by name against the backend-reported deps.
- The transitive walk in `walk_and_resolve` attaches the parent
  package's definition when pushing a child source dependency (run deps
  and extras flow through the same loop). The seen-set now records the
  inline content hash and the pushing parent: a direct dependency of the
  environment keeps overriding package-level definitions (seed-first,
  as before), while two package-level parents disagreeing about the
  same (package, source location) fail the solve with a
  `ConflictingInlineDefinitions` error naming both parents.
- `ResolveSourcePackageKey` now returns `ResolvedSourcePackage`
  (records + the package's inline map) so the walk can do the
  attachment; cache identity of children is unchanged since the child
  key already hashes its inline definition.
The solve resolves records with package-level inline definitions; the
build/install pipeline and lock-file verification need the same
definitions to reconstruct backends without an on-disk manifest:

- `SourceBuildKey` discovers its record's backend once (in-memory
  cached, no backend spawn) and uses the resulting map to (a) attach
  definitions to nested source-dependency builds (previously hardcoded
  `inline: None`) and (b) pass them into the build/host prefix
  installs, which recurse through the same env-install path.
- The env install (`install_pixi_environment`) extends the
  caller-supplied (consumer-level, higher-precedence) definitions with
  package-level ones collected from each source record's manifest.
  Inline-defined records have no on-disk manifest, so collection
  iterates until no new definitions turn up, resolving one link of an
  A→B→C definition chain per round.
- `verify_partial_source_record_against_backend` falls back to the
  workspace's own `[package]` definitions when the environment-level
  lookup misses. Definitions declared by transitive packages' manifests
  are not found there; the fresh metadata query then fails discovery
  and forces a re-lock, which resolves them through the regular walk.
@Hofer-Julian Hofer-Julian force-pushed the claude/inline-manifest-dependencies-e4z9w0 branch from 1a5f954 to 3b689b2 Compare July 6, 2026 20:22
@Hofer-Julian Hofer-Julian force-pushed the claude/inline-manifest-dependencies-e4z9w0 branch from 7915c60 to b305473 Compare July 9, 2026 14:11
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.

1 participant