From 8c2c49e989663f1405087b468438d5a77a752a85 Mon Sep 17 00:00:00 2001 From: Eloise Pozzi Date: Wed, 22 Jul 2026 18:09:55 +1000 Subject: [PATCH 1/2] fix: keep multiple extras distinct in resolved pins --- pycross/private/lock_resolver.bzl | 7 ++++++- pycross/private/thin_package_repo.bzl | 9 ++++++--- tests/unit/test_lock_resolver.bzl | 23 +++++++++++++++++++++++ 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/pycross/private/lock_resolver.bzl b/pycross/private/lock_resolver.bzl index dd1f755f..23c54485 100644 --- a/pycross/private/lock_resolver.bzl +++ b/pycross/private/lock_resolver.bzl @@ -618,7 +618,12 @@ def resolve( raw_pins = lock_model_data.get("pins", {}) pins = {} for k, v in raw_pins.items(): - name = parse_package_key(k).name + parts = parse_package_key(k) + + if parts.extra: + name = "{}[{}]".format(parts.name, parts.extra) + else: + name = parts.name if type(v) == "string": pins[name] = {"": v} else: diff --git a/pycross/private/thin_package_repo.bzl b/pycross/private/thin_package_repo.bzl index 14bd88a6..81a5e032 100644 --- a/pycross/private/thin_package_repo.bzl +++ b/pycross/private/thin_package_repo.bzl @@ -46,6 +46,7 @@ def _requirements_bzl(rctx, pins, packages): ] for pin in sorted(pins.keys()): pin_target_dict = pins[pin] + pin_parts = parse_package_key(pin) # Check if ANY variant of this pin is platform-specific. is_conditional = False @@ -55,11 +56,13 @@ def _requirements_bzl(rctx, pins, packages): is_conditional = True break - if is_conditional: - us_pin = underscore_name(pin) + us_pin = underscore_name(pin_parts.name) + if pin_parts.extra: + lines.append(' "@@{repo_name}//{pin}:[{extra}]",'.format(repo_name = rctx.name, pin = us_pin, extra = pin_parts.extra)) + elif is_conditional: lines.append(' "@@{repo_name}//{pin}:{maybe}",'.format(repo_name = rctx.name, pin = us_pin, maybe = _safe_name(us_pin, "maybe"))) else: - lines.append(' "@@{repo_name}//{pin}",'.format(repo_name = rctx.name, pin = underscore_name(pin))) + lines.append(' "@@{repo_name}//{pin}",'.format(repo_name = rctx.name, pin = us_pin)) lines.append("]") return "\n".join(lines) + "\n" diff --git a/tests/unit/test_lock_resolver.bzl b/tests/unit/test_lock_resolver.bzl index 2cc1fe70..05e26dda 100644 --- a/tests/unit/test_lock_resolver.bzl +++ b/tests/unit/test_lock_resolver.bzl @@ -223,6 +223,28 @@ def _test_synthesized_deps(name): util.helper_target(native.filegroup, name = name + "_subject", srcs = []) analysis_test(name = name, target = name + "_subject", impl = _test_synthesized_deps_impl) +# buildifier: disable=unused-variable +def _test_multi_extra_pins_impl(env, target): + lock_model_data = { + "packages": { + "foo@1.0": _make_pkg("foo", "1.0", [_make_file("foo-1.0.tar.gz")]), + }, + "pins": { + "foo[bar]": "foo[bar]@1.0", + "foo[baz]": "foo[baz]@1.0", + }, + } + + res = resolve(lock_model_data) + + # Both extras survive as distinct pins rather than collapsing to a single + # "foo" entry, which would silently drop all but the last extra. + env.expect.that_collection(res.pins.keys()).contains_at_least(["foo[bar]", "foo[baz]"]) + +def _test_multi_extra_pins(name): + util.helper_target(native.filegroup, name = name + "_subject", srcs = []) + analysis_test(name = name, target = name + "_subject", impl = _test_multi_extra_pins_impl) + # buildifier: disable=unused-variable def _test_cycle_two_nodes_impl(env, target): lock_model_data = { @@ -2100,6 +2122,7 @@ def lock_resolver_test_suite(name): _test_create_transitive_aliases_with_extras, _test_extra_build_tools_override, _test_synthesized_deps, + _test_multi_extra_pins, _test_cycle_two_nodes, _test_cycle_via_extra, _test_cycle_three_nodes, From 0d68daed225eb577f1e9f9f0ca59399e9332e9d3 Mon Sep 17 00:00:00 2001 From: Jeremy Volkman Date: Thu, 23 Jul 2026 05:10:38 +0000 Subject: [PATCH 2/2] Add test --- tests/unit/test_thin_package_repo.bzl | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/unit/test_thin_package_repo.bzl b/tests/unit/test_thin_package_repo.bzl index 75b70912..a4275b53 100644 --- a/tests/unit/test_thin_package_repo.bzl +++ b/tests/unit/test_thin_package_repo.bzl @@ -432,6 +432,32 @@ def _test_requirements_bzl_all_unconditional(name): util.helper_target(native.filegroup, name = name + "_subject", srcs = []) analysis_test(name = name, target = name + "_subject", impl = _test_requirements_bzl_all_unconditional_impl) +# ── Test: _requirements_bzl with extra-bearing pins ──────────────── + +# buildifier: disable=unused-variable +def _test_requirements_bzl_extra_pins_impl(env, target): + """Extra-bearing pins produce distinct :[extra] entries in all_requirements.""" + mock_rctx = struct(name = "my_repo") + pins = { + "foo[bar]": {"": "foo[bar]@1.0"}, + "foo[baz]": {"": "foo[baz]@1.0"}, + } + packages = { + "foo@1.0": { + "wheel_candidates": [{"filename": "foo-1.0-py3-none-any.whl"}], + "sdist_file": {"key": "foo_sdist"}, + }, + } + res = requirements_bzl_for_testing(mock_rctx, pins, packages) + + # Both extras should appear as separate entries. + env.expect.that_bool("@@my_repo//foo:[bar]" in res).equals(True) + env.expect.that_bool("@@my_repo//foo:[baz]" in res).equals(True) + +def _test_requirements_bzl_extra_pins(name): + util.helper_target(native.filegroup, name = name + "_subject", srcs = []) + analysis_test(name = name, target = name + "_subject", impl = _test_requirements_bzl_extra_pins_impl) + # ── Test suite ───────────────────────────────────────────────────── def thin_package_repo_test_suite(name): @@ -450,5 +476,6 @@ def thin_package_repo_test_suite(name): _test_is_platform_specific, _test_requirements_bzl_maybe_aliases, _test_requirements_bzl_all_unconditional, + _test_requirements_bzl_extra_pins, ], )