Skip to content

Document conditional dependencies and installable extras - #100

Merged
arcivanov merged 1 commit into
sourcefrom
dependency-markers-and-extras
Sep 13, 2026
Merged

Document conditional dependencies and installable extras#100
arcivanov merged 1 commit into
sourcefrom
dependency-markers-and-extras

Conversation

@arcivanov

Copy link
Copy Markdown
Member

Documents the conditional-dependency and installable-extras work in pybuilder/pybuilder (branch dependency-markers-and-extras).

  • Release notes (articles/_release-notes/v0.13.x.md): new 0.13.23 section covering the new features, the changed project.dependencies semantics, and the bugs fixed.
  • Manual: new "Conditional Dependencies" and "Extras in Virtual Environments" sections.
  • Plugin reference: rewritten extra and markers rows for depends_on, plus the new install_dependencies_extras property.
  • Tutorial: new "Conditional and Optional Dependencies" section.
  • Project info: the extra key now emitted by pyb -i.
  • Coding agents: guidance updated for the new property.

Release notes for 0.13.23. A manual section on each: how a distribution
may be declared more than once under mutually exclusive markers and what
validation then requires of it, and how install_dependencies_extras
selects groups for installation without changing what is published.

The plugin reference gains the property with its accepted values, and its
extra and markers rows are rewritten - the extra row documented extras as
something only a downstream consumer could ever install, which is no
longer the whole story. The project-info page gains the new extra key and
says how it differs from the extras already there. The tutorial gains a
short section on both shapes, and the coding agents guide the two
declarations an agent is now likely to need to write.
arcivanov added a commit to pybuilder/pybuilder that referenced this pull request Sep 13, 2026
…lease] (#950)

Declaring the same distribution twice under different environment
markers silently lost one of them. Markers took no part in a
dependency's identity, so the two collided in the set that holds them
and a Windows-only dependency would disappear because a Linux-only one
had been declared first. Declaring it correctly did not help either,
since validation counted by name alone and a per-interpreter pin was
therefore a build failure. Separately, extras groups could be declared
but never installed, so the code an extra guards could not be tested.

## Conditional duplicates

Markers now participate in identity, compared in canonical form so that
expressions differing only in quoting or whitespace remain one
declaration rather than becoming two. Validation groups same-named
declarations and reports a duplicate only where two of them apply at
once, or where two carry identical conditions and so can never be told
apart.

Both declarations are published, each with its own marker; only the one
that applies is installed, and only it reaches the constraints file.
That last part matters because pip drops constraint lines whose markers
do not hold but intersects everything else, so two live entries on one
name became a `ResolutionImpossible` naming neither of them. PyBuilder
decides it first, and says which declarations disagree.

Markers are evaluated against the marker environment of the **target**
interpreter rather than of the one running the build.
`PythonEnv.marker_env` carries it, probed from that interpreter with the
vendored `packaging`, whose `default_environment()` is itself the
enumeration of PEP 508 variables and so leaves no list to maintain. The
vendor directory travels to the probe as an argument rather than on
`PYTHONPATH`, because the probe captures `os.environ` verbatim and
PyBuilder reuses that capture for every later command run in that
environment.

## Installable extras

The new `install_dependencies_extras` property selects groups for
installation (a name, a list of names, or `"*"`) and feeds the `build`
and `test` venvs along with the install tasks. Naming a group the
project does not declare fails validation and lists the ones it does.

`project.dependencies` composes the base dependencies with the selected
groups; the new `project.base_dependencies` returns the base ones alone
and is what generates `install_requires` and `dependency_links`, so
selecting an extra in order to test it never makes it a mandatory
requirement of the published distribution.

Asking for an extra was also a no-op on incremental builds. Nothing in
installed metadata records which extras were requested (a `dist-info` is
byte-identical whether or not one was, and `direct_url.json` has no
field for it), so with the distribution already installed at a
satisfying version the extra was skipped and its requirements never
arrived. What an extra requires is now resolved from the installed
distribution's own metadata and checked, which is why
`get_packages_info` carries a per-extra requirement map and evaluates it
against the target environment. Where the installed version no longer
offers the extra at all, that is reported and the dependency handed to
pip.

## Fixed in passing

A marker containing quotes generated a **syntactically invalid
`setup.py`**, because the rendered requirement was interpolated into
single quotes of its own and markers are conventionally written with
single quotes. This has affected every project spelling a marker the
ordinary way (`sys_platform == 'win32'`) since markers shipped in
0.13.19. Values are now rendered as proper Python literals, which also
covers backslashes. Also, `install_requires` dropped a dependency's own
extras, and extras group names went unnormalized, so `extra="Security"`
and `extra="security"` were emitted as two groups.

## Verification

Full `pyb analyze publish` green: 848 unit tests, 60 integration tests,
flake8 clean, coverage 85% → 86%. Two new integration tests cover the
generated `setup.py` and constraints file for conditional declarations,
and extras actually landing in the venvs. Documentation changes are in
pybuilder/pybuilder.github.io#100.
@arcivanov
arcivanov merged commit 83f0bd9 into source Sep 13, 2026
1 check passed
@arcivanov
arcivanov deleted the dependency-markers-and-extras branch September 13, 2026 05:20
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