Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file added e2e/MODULE.bazel
Empty file.
11 changes: 11 additions & 0 deletions e2e/cases/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,17 @@ write_source_files(
# lower-bound check that the file still gets emitted with no entries.
"snapshots/project_single.private.markers.BUILD.bazel": "@project__single_project_hub//private/markers:BUILD.bazel",

# uv_project: extra activated through a dependency group. The `ci`
# group requests the project's own `build` extra (extra-marker[build]),
# which pulls in colorama gated by `extra == 'build'`. That marker is
# tautological once the extra is active and must be resolved away, or
# colorama gets dropped at runtime. This snapshot pins the resolved
# shape: the sccs file depends on `//:colorama` directly. Regressing the
# fix reintroduces the `extra == 'build'` marker and a `select(... :empty)`
# gate, so it shows up here as a diff. (The markers-file shape — no
# decide_marker emitted — is already pinned by project_single above.)
"snapshots/extra_marker.private.sccs.BUILD.bazel": "@project__extra_marker//private/sccs:BUILD.bazel",

# @whl_install for grpcio with bare linux_* wheels
# ----------------------------------------------------------------
# grpcio 1.80.0 ships bare linux_armv7l wheels alongside manylinux/
Expand Down
1 change: 1 addition & 0 deletions e2e/cases/MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ include("//uv-data-purelib:setup.MODULE.bazel")
include("//uv-dup-lock-records:setup.MODULE.bazel")
include("//uv-duplicate-whl-labels:setup.MODULE.bazel")
include("//uv-exclusions:setup.MODULE.bazel")
include("//uv-extra-marker:setup.MODULE.bazel")
include("//uv-git-source:setup.MODULE.bazel")
include("//uv-include-group:setup.MODULE.bazel")
include("//uv-incompatible-wheel-source:setup.MODULE.bazel")
Expand Down
55 changes: 55 additions & 0 deletions e2e/cases/snapshots/extra_marker.private.sccs.BUILD.bazel

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions e2e/cases/uv-extra-marker/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
load("@aspect_rules_py//py:defs.bzl", "py_test")

py_test(
name = "extra_marker",
srcs = [
"__test__.py",
],
dep_group = "ci",
main = "__test__.py",
python_version = "3.11",
deps = [
"@pypi_uv_extra_marker//colorama",
],
)
12 changes: 12 additions & 0 deletions e2e/cases/uv-extra-marker/__test__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/usr/bin/env python3
"""Reproduce omitted extra-only markers.

The dev dependency group activates the project's `build` extra, which should
make `colorama` available. If rules_py evaluates `extra == 'build'` to false
because the active venv is named `dev`, the dependency is dropped and this
import fails.
"""

import colorama

print(f"colorama: {colorama.__file__}")
15 changes: 15 additions & 0 deletions e2e/cases/uv-extra-marker/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[project]
name = "extra-marker"
version = "0.0.0"
requires-python = ">=3.11"
dependencies = []

[project.optional-dependencies]
build = [
"colorama",
]

[dependency-groups]
ci = [
"extra-marker[build]",
]
8 changes: 8 additions & 0 deletions e2e/cases/uv-extra-marker/setup.MODULE.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
uv = use_extension("@aspect_rules_py//uv:extensions.bzl", "uv")
uv.declare_hub(hub_name = "pypi_uv_extra_marker")
uv.project(
hub_name = "pypi_uv_extra_marker",
lock = "//uv-extra-marker:uv.lock",
pyproject = "//uv-extra-marker:pyproject.toml",
)
use_repo(uv, "project__extra_marker", "pypi_uv_extra_marker")
34 changes: 34 additions & 0 deletions e2e/cases/uv-extra-marker/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 21 additions & 0 deletions uv/private/extension/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ load(":test_defs.bzl", "defs_test_suite")
load(":test_dep_groups.bzl", "dep_groups_test_suite")
load(":test_git_utils.bzl", "git_utils_test_suite")
load(":test_graph_utils.bzl", "graph_utils_test_suite")
load(":test_marker_simplify.bzl", "marker_simplify_test_suite")
load(":test_projectfile.bzl", "projectfile_test_suite")

defs_test_suite()
Expand All @@ -13,6 +14,8 @@ git_utils_test_suite()

graph_utils_test_suite()

marker_simplify_test_suite()

projectfile_test_suite()

bzl_library(
Expand Down Expand Up @@ -57,6 +60,7 @@ bzl_library(
visibility = ["//uv:__subpackages__"],
deps = [
":git_utils",
":marker_simplify",
"//uv/private:normalize_name",
"//uv/private:normalize_version",
"//uv/private:parse_whl_name",
Expand All @@ -67,6 +71,13 @@ bzl_library(
],
)

bzl_library(
name = "marker_simplify",
srcs = ["marker_simplify.bzl"],
visibility = ["//uv:__subpackages__"],
deps = ["//uv/private/markers:pep508_evaluate"],
)

bzl_library(
name = "projectfile",
srcs = ["projectfile.bzl"],
Expand Down Expand Up @@ -119,6 +130,16 @@ bzl_library(
],
)

bzl_library(
name = "test_marker_simplify",
srcs = ["test_marker_simplify.bzl"],
visibility = ["//uv:__subpackages__"],
deps = [
":marker_simplify",
"@bazel_skylib//lib:unittest",
],
)

bzl_library(
name = "test_projectfile",
srcs = ["test_projectfile.bzl"],
Expand Down
7 changes: 6 additions & 1 deletion uv/private/extension/lockfile.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ load("//uv/private/constraints/platform:defs.bzl", "supported_platform")
load("//uv/private/constraints/python:defs.bzl", "supported_python")
load("//uv/private/whl_install:repository.bzl", "compatible_python_tags")
load(":git_utils.bzl", "parse_git_url", "try_git_to_http_archive")
load(":marker_simplify.bzl", "simplify_extra_marker")

def url_basename(url):
"""Returns the trailing file name of a distribution URL.
Expand Down Expand Up @@ -136,14 +137,18 @@ def build_marker_graph(lock_id, lock_data):

graph.setdefault(ek, {})
for dep in optional_deps:
marker = simplify_extra_marker(dep.get("marker", ""), extra_name)
if marker == None:
continue

extras = dep.get("extra", ["__base__"])
if "__base__" not in extras:
extras = ["__base__"] + extras

for e in extras:
eek = (lock_id, dep["name"], dep["version"], e)
graph[ek].setdefault(eek, {})
graph[ek][eek][dep.get("marker", "")] = 1
graph[ek][eek][marker] = 1

return graph

Expand Down
32 changes: 32 additions & 0 deletions uv/private/extension/marker_simplify.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""Partial evaluation of PEP 508 `extra` markers during graph construction.

uv writes every dependency of an extra with a redundant `extra == '<name>'`
marker. An edge out of an extra pseudo-package is only ever traversed once that
extra is active, so once we know which extra an edge belongs to that clause is a
known boolean. We resolve it here rather than emitting an un-evaluable
`decide_marker`, which would later see an empty `extra` environment (the venv
name is not the extra) and wrongly drop the dependency.
"""

load("//uv/private/markers:pep508_evaluate.bzl", "evaluate")

def simplify_extra_marker(marker, extra):
"""Partially evaluate a marker binding the `extra` PEP 508 variable.

Args:
marker: The PEP 508 marker string on the edge.
extra: The name of the active extra the edge belongs to.

Returns:
- `""` if the marker reduces to true.
- `None` if it reduces to false (the clause names a different extra).
- The residual marker string when other (e.g. platform) clauses remain.
"""
if not marker:
return marker
res = evaluate(marker, env = {"extra": extra}, strict = False)
if res == False:
return None
if res == True:
return ""
return res
35 changes: 35 additions & 0 deletions uv/private/extension/test_marker_simplify.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"""Tests for marker_simplify.bzl."""

load("@bazel_skylib//lib:unittest.bzl", "asserts", "unittest")
load(":marker_simplify.bzl", "simplify_extra_marker")

def _simplify_extra_marker_test_impl(ctx):
env = unittest.begin(ctx)

# Markers without `extra` pass through, re-rendered with normalized quoting.
asserts.equals(env, "", simplify_extra_marker("", "build"))
asserts.equals(env, "sys_platform == \"win32\"", simplify_extra_marker("sys_platform == 'win32'", "build"))

# A matching extra collapses the marker to true ("").
asserts.equals(env, "", simplify_extra_marker("extra == 'build'", "build"))

# A non-matching extra collapses to false (None), dropping the edge.
asserts.equals(env, None, simplify_extra_marker("extra == 'build'", "other"))

# Mixed markers reduce to their platform residual, or vanish per the extra.
asserts.equals(env, "sys_platform == \"win32\"", simplify_extra_marker("extra == 'build' and sys_platform == 'win32'", "build"))
asserts.equals(env, None, simplify_extra_marker("extra == 'build' and sys_platform == 'win32'", "other"))
asserts.equals(env, "", simplify_extra_marker("extra == 'build' or sys_platform == 'win32'", "build"))
asserts.equals(env, "sys_platform == \"win32\"", simplify_extra_marker("extra == 'build' or sys_platform == 'win32'", "other"))

return unittest.end(env)

simplify_extra_marker_test = unittest.make(
_simplify_extra_marker_test_impl,
)

def marker_simplify_test_suite():
unittest.suite(
"marker_simplify_tests",
simplify_extra_marker_test,
)
Loading