From b081d5c50e25fab8e88ba92c0e61e5ee1cdd7b6c Mon Sep 17 00:00:00 2001 From: Ignas Anikevicius <240938+aignas@users.noreply.github.com> Date: Sat, 6 Sep 2025 14:33:33 +0900 Subject: [PATCH 01/31] seam: add hub_builder --- python/private/pypi/BUILD.bazel | 9 +++++++++ python/private/pypi/extension.bzl | 12 +++++++++--- python/private/pypi/hub_builder.bzl | 16 ++++++++++++++++ 3 files changed, 34 insertions(+), 3 deletions(-) create mode 100644 python/private/pypi/hub_builder.bzl diff --git a/python/private/pypi/BUILD.bazel b/python/private/pypi/BUILD.bazel index cb3408a191..ed268b2474 100644 --- a/python/private/pypi/BUILD.bazel +++ b/python/private/pypi/BUILD.bazel @@ -111,6 +111,7 @@ bzl_library( deps = [ ":attrs_bzl", ":evaluate_markers_bzl", + ":hub_builder_bzl", ":hub_repository_bzl", ":parse_requirements_bzl", ":parse_whl_name_bzl", @@ -167,6 +168,14 @@ bzl_library( ], ) +bzl_library( + name = "hub_builder_bzl", + srcs = ["hub_builder.bzl"], + visibility = ["//:__subpackages__"], + deps = [ + ], +) + bzl_library( name = "hub_repository_bzl", srcs = ["hub_repository.bzl"], diff --git a/python/private/pypi/extension.bzl b/python/private/pypi/extension.bzl index 03af863e1e..580df221aa 100644 --- a/python/private/pypi/extension.bzl +++ b/python/private/pypi/extension.bzl @@ -26,6 +26,7 @@ load("//python/private:version.bzl", "version") load("//python/private:version_label.bzl", "version_label") load(":attrs.bzl", "use_isolated") load(":evaluate_markers.bzl", "evaluate_markers_py", EVALUATE_MARKERS_SRCS = "SRCS", evaluate_markers_star = "evaluate_markers") +load(":hub_builder.bzl", "hub_builder") load(":hub_repository.bzl", "hub_repository", "whl_config_settings_to_json") load(":parse_requirements.bzl", "parse_requirements") load(":parse_whl_name.bzl", "parse_whl_name") @@ -658,10 +659,11 @@ You cannot use both the additive_build_content and additive_build_content_file a for pip_attr in mod.tags.parse: hub_name = pip_attr.hub_name if hub_name not in pip_hub_map: - pip_hub_map[pip_attr.hub_name] = struct( + builder = hub_builder( + name = hub_name, module_name = mod.name, - python_versions = [pip_attr.python_version], ) + pip_hub_map[pip_attr.hub_name] = builder elif pip_hub_map[hub_name].module_name != mod.name: # We cannot have two hubs with the same name in different # modules. @@ -687,7 +689,11 @@ You cannot use both the additive_build_content and additive_build_content_file a version = pip_attr.python_version, )) else: - pip_hub_map[pip_attr.hub_name].python_versions.append(pip_attr.python_version) + builder = pip_hub_map[pip_attr.hub_name] + + builder.add( + python_version = pip_attr.python_version, + ) get_index_urls = None if pip_attr.experimental_index_url: diff --git a/python/private/pypi/hub_builder.bzl b/python/private/pypi/hub_builder.bzl new file mode 100644 index 0000000000..c1af73b6e2 --- /dev/null +++ b/python/private/pypi/hub_builder.bzl @@ -0,0 +1,16 @@ +"""A hub repository builder for incrementally building the hub configuration.""" + +def hub_builder(*, name, module_name): + """Return a hub builder instance""" + self = struct( + name = name, + module_name = module_name, + python_versions = [], + # buildifier: disable=uninitialized + add = lambda *args, **kwargs: _add(self, *args, **kwargs), + # buildifier: enable=uninitialized + ) + return self + +def _add(self, *, python_version): + self.python_versions.append(python_version) From e9e118a1f772528fe20174c895148ee870d39577 Mon Sep 17 00:00:00 2001 From: Ignas Anikevicius <240938+aignas@users.noreply.github.com> Date: Sat, 6 Sep 2025 14:34:49 +0900 Subject: [PATCH 02/31] refactor: move error handling --- python/private/pypi/extension.bzl | 10 ---------- python/private/pypi/hub_builder.bzl | 11 +++++++++++ 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/python/private/pypi/extension.bzl b/python/private/pypi/extension.bzl index 580df221aa..ad38425554 100644 --- a/python/private/pypi/extension.bzl +++ b/python/private/pypi/extension.bzl @@ -678,16 +678,6 @@ You cannot use both the additive_build_content and additive_build_content_file a second_module = mod.name, )) - elif pip_attr.python_version in pip_hub_map[hub_name].python_versions: - fail(( - "Duplicate pip python version '{version}' for hub " + - "'{hub}' in module '{module}': the Python versions " + - "used for a hub must be unique" - ).format( - hub = hub_name, - module = mod.name, - version = pip_attr.python_version, - )) else: builder = pip_hub_map[pip_attr.hub_name] diff --git a/python/private/pypi/hub_builder.bzl b/python/private/pypi/hub_builder.bzl index c1af73b6e2..3b580ebd35 100644 --- a/python/private/pypi/hub_builder.bzl +++ b/python/private/pypi/hub_builder.bzl @@ -13,4 +13,15 @@ def hub_builder(*, name, module_name): return self def _add(self, *, python_version): + if python_version in self.python_versions: + fail(( + "Duplicate pip python version '{version}' for hub " + + "'{hub}' in module '{module}': the Python versions " + + "used for a hub must be unique" + ).format( + hub = self.name, + module = self.module_name, + version = python_version, + )) + self.python_versions.append(python_version) From a2bc6535942595117d0f81d4b7b219c2b5170be6 Mon Sep 17 00:00:00 2001 From: Ignas Anikevicius <240938+aignas@users.noreply.github.com> Date: Sat, 6 Sep 2025 14:40:24 +0900 Subject: [PATCH 03/31] move get_index_url configuration elsewhere --- python/private/pypi/BUILD.bazel | 1 + python/private/pypi/extension.bzl | 34 ++----------------- python/private/pypi/hub_builder.bzl | 52 +++++++++++++++++++++++++++-- 3 files changed, 53 insertions(+), 34 deletions(-) diff --git a/python/private/pypi/BUILD.bazel b/python/private/pypi/BUILD.bazel index ed268b2474..a4d53e56b0 100644 --- a/python/private/pypi/BUILD.bazel +++ b/python/private/pypi/BUILD.bazel @@ -173,6 +173,7 @@ bzl_library( srcs = ["hub_builder.bzl"], visibility = ["//:__subpackages__"], deps = [ + "//python/private:normalize_name_bzl", ], ) diff --git a/python/private/pypi/extension.bzl b/python/private/pypi/extension.bzl index ad38425554..82e8edf5af 100644 --- a/python/private/pypi/extension.bzl +++ b/python/private/pypi/extension.bzl @@ -662,6 +662,8 @@ You cannot use both the additive_build_content and additive_build_content_file a builder = hub_builder( name = hub_name, module_name = mod.name, + simpleapi_download_fn = simpleapi_download, + simpleapi_cache = simpleapi_cache, ) pip_hub_map[pip_attr.hub_name] = builder elif pip_hub_map[hub_name].module_name != mod.name: @@ -685,41 +687,11 @@ You cannot use both the additive_build_content and additive_build_content_file a python_version = pip_attr.python_version, ) - get_index_urls = None - if pip_attr.experimental_index_url: - skip_sources = [ - normalize_name(s) - for s in pip_attr.simpleapi_skip - ] - get_index_urls = lambda ctx, distributions: simpleapi_download( - ctx, - attr = struct( - index_url = pip_attr.experimental_index_url, - extra_index_urls = pip_attr.experimental_extra_index_urls or [], - index_url_overrides = pip_attr.experimental_index_url_overrides or {}, - sources = [ - d - for d in distributions - if normalize_name(d) not in skip_sources - ], - envsubst = pip_attr.envsubst, - # Auth related info - netrc = pip_attr.netrc, - auth_patterns = pip_attr.auth_patterns, - ), - cache = simpleapi_cache, - parallel_download = pip_attr.parallel_download, - ) - elif pip_attr.experimental_extra_index_urls: - fail("'experimental_extra_index_urls' is a no-op unless 'experimental_index_url' is set") - elif pip_attr.experimental_index_url_overrides: - fail("'experimental_index_url_overrides' is a no-op unless 'experimental_index_url' is set") - # TODO @aignas 2025-05-19: express pip.parse as a series of configure calls out = _create_whl_repos( module_ctx, pip_attr = pip_attr, - get_index_urls = get_index_urls, + get_index_urls = builder.get_index_urls(pip_attr), whl_overrides = whl_overrides, config = config, **kwargs diff --git a/python/private/pypi/hub_builder.bzl b/python/private/pypi/hub_builder.bzl index 3b580ebd35..a9e991a395 100644 --- a/python/private/pypi/hub_builder.bzl +++ b/python/private/pypi/hub_builder.bzl @@ -1,15 +1,28 @@ """A hub repository builder for incrementally building the hub configuration.""" -def hub_builder(*, name, module_name): +load("//python/private:normalize_name.bzl", "normalize_name") + +def hub_builder( + *, + name, + module_name, + simpleapi_download_fn, + simpleapi_cache = {}): """Return a hub builder instance""" + + # buildifier: disable=uninitialized self = struct( name = name, module_name = module_name, python_versions = [], - # buildifier: disable=uninitialized + _simpleapi_download_fn = simpleapi_download_fn, + _simpleapi_cache = simpleapi_cache, + # keep sorted add = lambda *args, **kwargs: _add(self, *args, **kwargs), - # buildifier: enable=uninitialized + get_index_urls = lambda *args, **kwargs: _get_index_urls(self, *args, **kwargs), ) + + # buildifier: enable=uninitialized return self def _add(self, *, python_version): @@ -25,3 +38,36 @@ def _add(self, *, python_version): )) self.python_versions.append(python_version) + +def _get_index_urls(self, pip_attr): + get_index_urls = None + if pip_attr.experimental_index_url: + skip_sources = [ + normalize_name(s) + for s in pip_attr.simpleapi_skip + ] + get_index_urls = lambda ctx, distributions: self._simpleapi_download_fn( + ctx, + attr = struct( + index_url = pip_attr.experimental_index_url, + extra_index_urls = pip_attr.experimental_extra_index_urls or [], + index_url_overrides = pip_attr.experimental_index_url_overrides or {}, + sources = [ + d + for d in distributions + if normalize_name(d) not in skip_sources + ], + envsubst = pip_attr.envsubst, + # Auth related info + netrc = pip_attr.netrc, + auth_patterns = pip_attr.auth_patterns, + ), + cache = self._simpleapi_cache, + parallel_download = pip_attr.parallel_download, + ) + elif pip_attr.experimental_extra_index_urls: + fail("'experimental_extra_index_urls' is a no-op unless 'experimental_index_url' is set") + elif pip_attr.experimental_index_url_overrides: + fail("'experimental_index_url_overrides' is a no-op unless 'experimental_index_url' is set") + + return get_index_urls From 8645443aa1655a02ed0982b62f1e5087dbd02684 Mon Sep 17 00:00:00 2001 From: Ignas Anikevicius <240938+aignas@users.noreply.github.com> Date: Sat, 6 Sep 2025 14:53:19 +0900 Subject: [PATCH 04/31] refactor: move interpreter detection to a simple function --- python/private/pypi/extension.bzl | 51 ++++++++--------------------- python/private/pypi/hub_builder.bzl | 48 +++++++++++++++++++++++++-- 2 files changed, 59 insertions(+), 40 deletions(-) diff --git a/python/private/pypi/extension.bzl b/python/private/pypi/extension.bzl index 82e8edf5af..8dcec601e5 100644 --- a/python/private/pypi/extension.bzl +++ b/python/private/pypi/extension.bzl @@ -120,22 +120,17 @@ def _create_whl_repos( pip_attr, whl_overrides, config, - available_interpreters = INTERPRETER_LABELS, + hub, minor_mapping = MINOR_MAPPING, - evaluate_markers = None, - get_index_urls = None): + evaluate_markers = None): """create all of the whl repositories Args: module_ctx: {type}`module_ctx`. pip_attr: {type}`struct` - the struct that comes from the tag class iteration. whl_overrides: {type}`dict[str, struct]` - per-wheel overrides. + hub: TODO. config: The platform configuration. - get_index_urls: A function used to get the index URLs - available_interpreters: {type}`dict[str, Label]` The dictionary of available - interpreters that have been registered using the `python` bzlmod extension. - The keys are in the form `python_{snake_case_version}_host`. This is to be - used during the `repository_rule` and must be always compatible with the host. minor_mapping: {type}`dict[str, str]` The dictionary needed to resolve the full python version used to parse package METADATA files. evaluate_markers: the function used to evaluate the markers. @@ -152,7 +147,8 @@ def _create_whl_repos( rule. """ logger = repo_utils.logger(module_ctx, "pypi:create_whl_repos") - python_interpreter_target = pip_attr.python_interpreter_target + get_index_urls = hub.get_index_urls(pip_attr) + interpreter = hub.detect_interpreter(pip_attr) # containers to aggregate outputs from this function whl_map = {} @@ -162,31 +158,10 @@ def _create_whl_repos( } whl_libraries = {} - # if we do not have the python_interpreter set in the attributes - # we programmatically find it. - hub_name = pip_attr.hub_name - if python_interpreter_target == None and not pip_attr.python_interpreter: - python_name = "python_{}_host".format( - pip_attr.python_version.replace(".", "_"), - ) - if python_name not in available_interpreters: - fail(( - "Unable to find interpreter for pip hub '{hub_name}' for " + - "python_version={version}: Make sure a corresponding " + - '`python.toolchain(python_version="{version}")` call exists.' + - "Expected to find {python_name} among registered versions:\n {labels}" - ).format( - hub_name = hub_name, - version = pip_attr.python_version, - python_name = python_name, - labels = " \n".join(available_interpreters), - )) - python_interpreter_target = available_interpreters[python_name] - # TODO @aignas 2025-06-29: we should not need the version in the pip_name if # we are using pipstar and we are downloading the wheel using the downloader pip_name = "{}_{}".format( - hub_name, + hub.name, version_label(pip_attr.python_version), ) major_minor = _major_minor_version(pip_attr.python_version) @@ -249,8 +224,8 @@ def _create_whl_repos( } for k, plats in requirements.items() }, - python_interpreter = pip_attr.python_interpreter, - python_interpreter_target = python_interpreter_target, + python_interpreter = interpreter.path, + python_interpreter_target = interpreter.target, srcs = pip_attr._evaluate_markers_srcs, logger = logger, ) @@ -293,7 +268,7 @@ def _create_whl_repos( # Construct args separately so that the lock file can be smaller and does not include unused # attrs. whl_library_args = dict( - dep_template = "@{}//{{name}}:{{target}}".format(hub_name), + dep_template = "@{}//{{name}}:{{target}}".format(hub.name), ) maybe_args = dict( # The following values are safe to omit if they have false like values @@ -306,8 +281,8 @@ def _create_whl_repos( group_deps = group_deps, group_name = group_name, pip_data_exclude = pip_attr.pip_data_exclude, - python_interpreter = pip_attr.python_interpreter, - python_interpreter_target = python_interpreter_target, + python_interpreter = interpreter.path, + python_interpreter_target = interpreter.target, whl_patches = { p: json.encode(args) for p, args in whl_overrides.get(whl.name, {}).items() @@ -664,6 +639,8 @@ You cannot use both the additive_build_content and additive_build_content_file a module_name = mod.name, simpleapi_download_fn = simpleapi_download, simpleapi_cache = simpleapi_cache, + minor_mapping = kwargs.get("minor_mapping", MINOR_MAPPING), + available_interpreters = kwargs.pop("available_interpreters", INTERPRETER_LABELS), ) pip_hub_map[pip_attr.hub_name] = builder elif pip_hub_map[hub_name].module_name != mod.name: @@ -690,8 +667,8 @@ You cannot use both the additive_build_content and additive_build_content_file a # TODO @aignas 2025-05-19: express pip.parse as a series of configure calls out = _create_whl_repos( module_ctx, + hub = builder, pip_attr = pip_attr, - get_index_urls = builder.get_index_urls(pip_attr), whl_overrides = whl_overrides, config = config, **kwargs diff --git a/python/private/pypi/hub_builder.bzl b/python/private/pypi/hub_builder.bzl index a9e991a395..1bd67ef353 100644 --- a/python/private/pypi/hub_builder.bzl +++ b/python/private/pypi/hub_builder.bzl @@ -6,20 +6,37 @@ def hub_builder( *, name, module_name, + minor_mapping, + available_interpreters, simpleapi_download_fn, simpleapi_cache = {}): - """Return a hub builder instance""" + """Return a hub builder instance + + Args: + name: TODO + module_name: TODO + minor_mapping: TODO + available_interpreters: {type}`dict[str, Label]` The dictionary of available + interpreters that have been registered using the `python` bzlmod extension. + The keys are in the form `python_{snake_case_version}_host`. This is to be + used during the `repository_rule` and must be always compatible with the host. + simpleapi_download_fn: TODO + simpleapi_cache: TODO + """ # buildifier: disable=uninitialized self = struct( name = name, module_name = module_name, python_versions = [], + _minor_mapping = minor_mapping, + _available_interpreters = available_interpreters, _simpleapi_download_fn = simpleapi_download_fn, _simpleapi_cache = simpleapi_cache, # keep sorted - add = lambda *args, **kwargs: _add(self, *args, **kwargs), - get_index_urls = lambda *args, **kwargs: _get_index_urls(self, *args, **kwargs), + add = lambda *a, **k: _add(self, *a, **k), + get_index_urls = lambda *a, **k: _get_index_urls(self, *a, **k), + detect_interpreter = lambda *a, **k: _detect_interpreter(self, *a, **k), ) # buildifier: enable=uninitialized @@ -71,3 +88,28 @@ def _get_index_urls(self, pip_attr): fail("'experimental_index_url_overrides' is a no-op unless 'experimental_index_url' is set") return get_index_urls + +def _detect_interpreter(self, pip_attr): + python_interpreter_target = pip_attr.python_interpreter_target + if python_interpreter_target == None and not pip_attr.python_interpreter: + python_name = "python_{}_host".format( + pip_attr.python_version.replace(".", "_"), + ) + if python_name not in self._available_interpreters: + fail(( + "Unable to find interpreter for pip hub '{hub_name}' for " + + "python_version={version}: Make sure a corresponding " + + '`python.toolchain(python_version="{version}")` call exists.' + + "Expected to find {python_name} among registered versions:\n {labels}" + ).format( + hub_name = self.name, + version = pip_attr.python_version, + python_name = python_name, + labels = " \n".join(self._available_interpreters), + )) + python_interpreter_target = self._available_interpreters[python_name] + + return struct( + target = python_interpreter_target, + path = pip_attr.python_interpreter, + ) From df8d2be38401e69092a2ebddfd1c2264a77b84ad Mon Sep 17 00:00:00 2001 From: Ignas Anikevicius <240938+aignas@users.noreply.github.com> Date: Sat, 6 Sep 2025 14:57:35 +0900 Subject: [PATCH 05/31] minor: move declaration closer to use --- python/private/pypi/extension.bzl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/python/private/pypi/extension.bzl b/python/private/pypi/extension.bzl index 8dcec601e5..d8704eda24 100644 --- a/python/private/pypi/extension.bzl +++ b/python/private/pypi/extension.bzl @@ -164,7 +164,6 @@ def _create_whl_repos( hub.name, version_label(pip_attr.python_version), ) - major_minor = _major_minor_version(pip_attr.python_version) whl_modifications = {} if pip_attr.whl_modifications != None: @@ -315,7 +314,7 @@ def _create_whl_repos( get_index_urls != None, # defaults to True if the get_index_urls is defined ), auth_patterns = config.auth_patterns or pip_attr.auth_patterns, - python_version = major_minor, + python_version = _major_minor_version(pip_attr.python_version), is_multiple_versions = whl.is_multiple_versions, enable_pipstar = config.enable_pipstar, ) From 863243b8b58588786e1a43a300130354bf4e0d5a Mon Sep 17 00:00:00 2001 From: Ignas Anikevicius <240938+aignas@users.noreply.github.com> Date: Sat, 6 Sep 2025 15:10:25 +0900 Subject: [PATCH 06/31] refactor: move platform building for the hub to the builder --- python/private/pypi/BUILD.bazel | 6 ++- python/private/pypi/extension.bzl | 70 ++++------------------------- python/private/pypi/hub_builder.bzl | 66 ++++++++++++++++++++++++++- 3 files changed, 78 insertions(+), 64 deletions(-) diff --git a/python/private/pypi/BUILD.bazel b/python/private/pypi/BUILD.bazel index a4d53e56b0..73451b9784 100644 --- a/python/private/pypi/BUILD.bazel +++ b/python/private/pypi/BUILD.bazel @@ -117,7 +117,6 @@ bzl_library( ":parse_whl_name_bzl", ":pep508_env_bzl", ":pip_repository_attrs_bzl", - ":python_tag_bzl", ":simpleapi_download_bzl", ":whl_config_setting_bzl", ":whl_library_bzl", @@ -173,7 +172,12 @@ bzl_library( srcs = ["hub_builder.bzl"], visibility = ["//:__subpackages__"], deps = [ + ":pep508_env_bzl", + ":pep508_evaluate_bzl", + ":python_tag_bzl", + "//python/private:full_version_bzl", "//python/private:normalize_name_bzl", + "//python/private:version_bzl", ], ) diff --git a/python/private/pypi/extension.bzl b/python/private/pypi/extension.bzl index d8704eda24..d77e1df881 100644 --- a/python/private/pypi/extension.bzl +++ b/python/private/pypi/extension.bzl @@ -31,9 +31,7 @@ load(":hub_repository.bzl", "hub_repository", "whl_config_settings_to_json") load(":parse_requirements.bzl", "parse_requirements") load(":parse_whl_name.bzl", "parse_whl_name") load(":pep508_env.bzl", "env") -load(":pep508_evaluate.bzl", "evaluate") load(":pip_repository_attrs.bzl", "ATTRS") -load(":python_tag.bzl", "python_tag") load(":requirements_files_by_platform.bzl", "requirements_files_by_platform") load(":simpleapi_download.bzl", "simpleapi_download") load(":whl_config_setting.bzl", "whl_config_setting") @@ -69,57 +67,11 @@ def _whl_mods_impl(whl_mods_dict): whl_mods = whl_mods, ) -def _platforms(*, python_version, minor_mapping, config): - platforms = {} - python_version = version.parse( - full_version( - version = python_version, - minor_mapping = minor_mapping, - ), - strict = True, - ) - - for platform, values in config.platforms.items(): - # TODO @aignas 2025-07-07: this is probably doing the parsing of the version too - # many times. - abi = "{}{}{}.{}".format( - python_tag(values.env["implementation_name"]), - python_version.release[0], - python_version.release[1], - python_version.release[2], - ) - key = "{}_{}".format(abi, platform) - - env_ = env( - env = values.env, - os = values.os_name, - arch = values.arch_name, - python_version = python_version.string, - ) - - if values.marker and not evaluate(values.marker, env = env_): - continue - - platforms[key] = struct( - env = env_, - triple = "{}_{}_{}".format(abi, values.os_name, values.arch_name), - whl_abi_tags = [ - v.format( - major = python_version.release[0], - minor = python_version.release[1], - ) - for v in values.whl_abi_tags - ], - whl_platform_tags = values.whl_platform_tags, - ) - return platforms - def _create_whl_repos( module_ctx, *, pip_attr, whl_overrides, - config, hub, minor_mapping = MINOR_MAPPING, evaluate_markers = None): @@ -130,7 +82,6 @@ def _create_whl_repos( pip_attr: {type}`struct` - the struct that comes from the tag class iteration. whl_overrides: {type}`dict[str, struct]` - per-wheel overrides. hub: TODO. - config: The platform configuration. minor_mapping: {type}`dict[str, str]` The dictionary needed to resolve the full python version used to parse package METADATA files. evaluate_markers: the function used to evaluate the markers. @@ -185,16 +136,12 @@ def _create_whl_repos( whl_group_mapping = {} requirement_cycles = {} - platforms = _platforms( - python_version = pip_attr.python_version, - minor_mapping = minor_mapping, - config = config, - ) + platforms = hub.platforms(pip_attr.python_version) if evaluate_markers: # This is most likely unit tests pass - elif config.enable_pipstar: + elif hub.config.enable_pipstar: evaluate_markers = lambda _, requirements: evaluate_markers_star( requirements = requirements, platforms = platforms, @@ -287,7 +234,7 @@ def _create_whl_repos( for p, args in whl_overrides.get(whl.name, {}).items() }, ) - if not config.enable_pipstar: + if not hub.config.enable_pipstar: maybe_args["experimental_target_platforms"] = pip_attr.experimental_target_platforms whl_library_args.update({k: v for k, v in maybe_args.items() if v}) @@ -308,15 +255,15 @@ def _create_whl_repos( src = src, whl_library_args = whl_library_args, download_only = pip_attr.download_only, - netrc = config.netrc or pip_attr.netrc, + netrc = hub.config.netrc or pip_attr.netrc, use_downloader = use_downloader.get( whl.name, get_index_urls != None, # defaults to True if the get_index_urls is defined ), - auth_patterns = config.auth_patterns or pip_attr.auth_patterns, + auth_patterns = hub.config.auth_patterns or pip_attr.auth_patterns, python_version = _major_minor_version(pip_attr.python_version), is_multiple_versions = whl.is_multiple_versions, - enable_pipstar = config.enable_pipstar, + enable_pipstar = hub.config.enable_pipstar, ) if repo == None: # NOTE @aignas 2025-07-07: we guard against an edge-case where there @@ -332,7 +279,7 @@ def _create_whl_repos( )) whl_libraries[repo_name] = repo.args - if not config.enable_pipstar and "experimental_target_platforms" in repo.args: + if not hub.config.enable_pipstar and "experimental_target_platforms" in repo.args: whl_libraries[repo_name] |= { "experimental_target_platforms": sorted({ # TODO @aignas 2025-07-07: this should be solved in a better way @@ -636,6 +583,7 @@ You cannot use both the additive_build_content and additive_build_content_file a builder = hub_builder( name = hub_name, module_name = mod.name, + config = config, simpleapi_download_fn = simpleapi_download, simpleapi_cache = simpleapi_cache, minor_mapping = kwargs.get("minor_mapping", MINOR_MAPPING), @@ -669,9 +617,9 @@ You cannot use both the additive_build_content and additive_build_content_file a hub = builder, pip_attr = pip_attr, whl_overrides = whl_overrides, - config = config, **kwargs ) + hub_whl_map.setdefault(hub_name, {}) for key, settings in out.whl_map.items(): for setting, repo in settings.items(): diff --git a/python/private/pypi/hub_builder.bzl b/python/private/pypi/hub_builder.bzl index 1bd67ef353..e74e5072c0 100644 --- a/python/private/pypi/hub_builder.bzl +++ b/python/private/pypi/hub_builder.bzl @@ -1,11 +1,17 @@ """A hub repository builder for incrementally building the hub configuration.""" +load("//python/private:full_version.bzl", "full_version") load("//python/private:normalize_name.bzl", "normalize_name") +load("//python/private:version.bzl", "version") +load(":pep508_env.bzl", "env") +load(":pep508_evaluate.bzl", "evaluate") +load(":python_tag.bzl", "python_tag") def hub_builder( *, name, module_name, + config, minor_mapping, available_interpreters, simpleapi_download_fn, @@ -15,6 +21,7 @@ def hub_builder( Args: name: TODO module_name: TODO + config: The platform configuration. minor_mapping: TODO available_interpreters: {type}`dict[str, Label]` The dictionary of available interpreters that have been registered using the `python` bzlmod extension. @@ -28,7 +35,12 @@ def hub_builder( self = struct( name = name, module_name = module_name, - python_versions = [], + python_versions = {}, + config = config, + whl_map = {}, + exposed_packages = {}, + extra_aliases = {}, + whl_libraries = {}, _minor_mapping = minor_mapping, _available_interpreters = available_interpreters, _simpleapi_download_fn = simpleapi_download_fn, @@ -37,6 +49,7 @@ def hub_builder( add = lambda *a, **k: _add(self, *a, **k), get_index_urls = lambda *a, **k: _get_index_urls(self, *a, **k), detect_interpreter = lambda *a, **k: _detect_interpreter(self, *a, **k), + platforms = lambda version: self.python_versions[version], ) # buildifier: enable=uninitialized @@ -54,7 +67,11 @@ def _add(self, *, python_version): version = python_version, )) - self.python_versions.append(python_version) + self.python_versions[python_version] = _platforms( + python_version = python_version, + minor_mapping = self._minor_mapping, + config = self.config, + ) def _get_index_urls(self, pip_attr): get_index_urls = None @@ -113,3 +130,48 @@ def _detect_interpreter(self, pip_attr): target = python_interpreter_target, path = pip_attr.python_interpreter, ) + +def _platforms(*, python_version, minor_mapping, config): + platforms = {} + python_version = version.parse( + full_version( + version = python_version, + minor_mapping = minor_mapping, + ), + strict = True, + ) + + for platform, values in config.platforms.items(): + # TODO @aignas 2025-07-07: this is probably doing the parsing of the version too + # many times. + abi = "{}{}{}.{}".format( + python_tag(values.env["implementation_name"]), + python_version.release[0], + python_version.release[1], + python_version.release[2], + ) + key = "{}_{}".format(abi, platform) + + env_ = env( + env = values.env, + os = values.os_name, + arch = values.arch_name, + python_version = python_version.string, + ) + + if values.marker and not evaluate(values.marker, env = env_): + continue + + platforms[key] = struct( + env = env_, + triple = "{}_{}_{}".format(abi, values.os_name, values.arch_name), + whl_abi_tags = [ + v.format( + major = python_version.release[0], + minor = python_version.release[1], + ) + for v in values.whl_abi_tags + ], + whl_platform_tags = values.whl_platform_tags, + ) + return platforms From 7ee62e7963d1590df503b5333c090e3b25524520 Mon Sep 17 00:00:00 2001 From: Ignas Anikevicius <240938+aignas@users.noreply.github.com> Date: Sat, 6 Sep 2025 15:14:55 +0900 Subject: [PATCH 07/31] refactor: store the whl_map on the builder itself --- python/private/pypi/extension.bzl | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/python/private/pypi/extension.bzl b/python/private/pypi/extension.bzl index d77e1df881..6a77c13868 100644 --- a/python/private/pypi/extension.bzl +++ b/python/private/pypi/extension.bzl @@ -102,7 +102,6 @@ def _create_whl_repos( interpreter = hub.detect_interpreter(pip_attr) # containers to aggregate outputs from this function - whl_map = {} extra_aliases = { whl_name: {alias: True for alias in aliases} for whl_name, aliases in pip_attr.extra_hub_aliases.items() @@ -290,7 +289,7 @@ def _create_whl_repos( }), } - mapping = whl_map.setdefault(whl.name, {}) + mapping = hub.whl_map.setdefault(whl.name, {}) if repo.config_setting in mapping and mapping[repo.config_setting] != repo_name: fail( "attempting to override an existing repo '{}' for config setting '{}' with a new repo '{}'".format( @@ -303,7 +302,6 @@ def _create_whl_repos( mapping[repo.config_setting] = repo_name return struct( - whl_map = whl_map, exposed_packages = exposed_packages, extra_aliases = extra_aliases, whl_libraries = whl_libraries, @@ -620,10 +618,6 @@ You cannot use both the additive_build_content and additive_build_content_file a **kwargs ) - hub_whl_map.setdefault(hub_name, {}) - for key, settings in out.whl_map.items(): - for setting, repo in settings.items(): - hub_whl_map[hub_name].setdefault(key, {}).setdefault(repo, []).append(setting) extra_aliases.setdefault(hub_name, {}) for whl_name, aliases in out.extra_aliases.items(): extra_aliases[hub_name].setdefault(whl_name, {}).update(aliases) @@ -653,6 +647,12 @@ You cannot use both the additive_build_content and additive_build_content_file a # using an alternative cycle resolution strategy. hub_group_map[hub_name] = pip_attr.experimental_requirement_cycles + for hub in pip_hub_map.values(): + hub_whl_map.setdefault(hub.name, {}) + for key, settings in hub.whl_map.items(): + for setting, repo in settings.items(): + hub_whl_map[hub.name].setdefault(key, {}).setdefault(repo, []).append(setting) + return struct( # We sort so that the lock-file remains the same no matter the order of how the # args are manipulated in the code going before. From 106789f6561f0d0192efb50ac05c7918223caae1 Mon Sep 17 00:00:00 2001 From: Ignas Anikevicius <240938+aignas@users.noreply.github.com> Date: Sat, 6 Sep 2025 15:24:34 +0900 Subject: [PATCH 08/31] refactor: store the whl_libraries for each hub in the builder --- python/private/pypi/extension.bzl | 68 ++++++----------------------- python/private/pypi/hub_builder.bzl | 45 +++++++++++++++++++ 2 files changed, 58 insertions(+), 55 deletions(-) diff --git a/python/private/pypi/extension.bzl b/python/private/pypi/extension.bzl index 6a77c13868..20a3f2ef18 100644 --- a/python/private/pypi/extension.bzl +++ b/python/private/pypi/extension.bzl @@ -23,7 +23,6 @@ load("//python/private:full_version.bzl", "full_version") load("//python/private:normalize_name.bzl", "normalize_name") load("//python/private:repo_utils.bzl", "repo_utils") load("//python/private:version.bzl", "version") -load("//python/private:version_label.bzl", "version_label") load(":attrs.bzl", "use_isolated") load(":evaluate_markers.bzl", "evaluate_markers_py", EVALUATE_MARKERS_SRCS = "SRCS", evaluate_markers_star = "evaluate_markers") load(":hub_builder.bzl", "hub_builder") @@ -106,14 +105,6 @@ def _create_whl_repos( whl_name: {alias: True for alias in aliases} for whl_name, aliases in pip_attr.extra_hub_aliases.items() } - whl_libraries = {} - - # TODO @aignas 2025-06-29: we should not need the version in the pip_name if - # we are using pipstar and we are downloading the wheel using the downloader - pip_name = "{}_{}".format( - hub.name, - version_label(pip_attr.python_version), - ) whl_modifications = {} if pip_attr.whl_modifications != None: @@ -264,47 +255,15 @@ def _create_whl_repos( is_multiple_versions = whl.is_multiple_versions, enable_pipstar = hub.config.enable_pipstar, ) - if repo == None: - # NOTE @aignas 2025-07-07: we guard against an edge-case where there - # are more platforms defined than there are wheels for and users - # disallow building from sdist. - continue - - repo_name = "{}_{}".format(pip_name, repo.repo_name) - if repo_name in whl_libraries: - fail("attempting to create a duplicate library {} for {}".format( - repo_name, - whl.name, - )) - whl_libraries[repo_name] = repo.args - - if not hub.config.enable_pipstar and "experimental_target_platforms" in repo.args: - whl_libraries[repo_name] |= { - "experimental_target_platforms": sorted({ - # TODO @aignas 2025-07-07: this should be solved in a better way - platforms[candidate].triple.partition("_")[-1]: None - for p in repo.args["experimental_target_platforms"] - for candidate in platforms - if candidate.endswith(p) - }), - } - - mapping = hub.whl_map.setdefault(whl.name, {}) - if repo.config_setting in mapping and mapping[repo.config_setting] != repo_name: - fail( - "attempting to override an existing repo '{}' for config setting '{}' with a new repo '{}'".format( - mapping[repo.config_setting], - repo.config_setting, - repo_name, - ), - ) - else: - mapping[repo.config_setting] = repo_name + hub.add_whl_library( + python_version = pip_attr.python_version, + whl = whl, + repo = repo, + ) return struct( exposed_packages = exposed_packages, extra_aliases = extra_aliases, - whl_libraries = whl_libraries, ) def _whl_repo( @@ -631,15 +590,6 @@ You cannot use both the additive_build_content and additive_build_content_file a continue intersection[pkg] = None exposed_packages[hub_name] = intersection - whl_libraries.update(out.whl_libraries) - for whl_name, lib in out.whl_libraries.items(): - if enable_pipstar: - whl_libraries.setdefault(whl_name, lib) - elif whl_name in lib: - fail("'{}' already in created".format(whl_name)) - else: - # replicate whl_libraries.update(out.whl_libraries) - whl_libraries[whl_name] = lib # TODO @aignas 2024-04-05: how do we support different requirement # cycles for different abis/oses? For now we will need the users to @@ -653,6 +603,14 @@ You cannot use both the additive_build_content and additive_build_content_file a for setting, repo in settings.items(): hub_whl_map[hub.name].setdefault(key, {}).setdefault(repo, []).append(setting) + whl_libraries.update(hub.whl_libraries) + for whl_name, lib in hub.whl_libraries.items(): + if whl_name in lib: + fail("'{}' already in created".format(whl_name)) + else: + # replicate whl_libraries.update(out.whl_libraries) + whl_libraries[whl_name] = lib + return struct( # We sort so that the lock-file remains the same no matter the order of how the # args are manipulated in the code going before. diff --git a/python/private/pypi/hub_builder.bzl b/python/private/pypi/hub_builder.bzl index e74e5072c0..b3b8aa06b6 100644 --- a/python/private/pypi/hub_builder.bzl +++ b/python/private/pypi/hub_builder.bzl @@ -3,6 +3,7 @@ load("//python/private:full_version.bzl", "full_version") load("//python/private:normalize_name.bzl", "normalize_name") load("//python/private:version.bzl", "version") +load("//python/private:version_label.bzl", "version_label") load(":pep508_env.bzl", "env") load(":pep508_evaluate.bzl", "evaluate") load(":python_tag.bzl", "python_tag") @@ -50,6 +51,7 @@ def hub_builder( get_index_urls = lambda *a, **k: _get_index_urls(self, *a, **k), detect_interpreter = lambda *a, **k: _detect_interpreter(self, *a, **k), platforms = lambda version: self.python_versions[version], + add_whl_library = lambda *a, **k: _add_whl_library(self, *a, **k), ) # buildifier: enable=uninitialized @@ -175,3 +177,46 @@ def _platforms(*, python_version, minor_mapping, config): whl_platform_tags = values.whl_platform_tags, ) return platforms + +def _add_whl_library(self, *, python_version, whl, repo): + if repo == None: + # NOTE @aignas 2025-07-07: we guard against an edge-case where there + # are more platforms defined than there are wheels for and users + # disallow building from sdist. + return + + platforms = self.python_versions[python_version] + + # TODO @aignas 2025-06-29: we should not need the version in the repo_name if + # we are using pipstar and we are downloading the wheel using the downloader + repo_name = "{}_{}_{}".format(self.name, version_label(python_version), repo.repo_name) + + if repo_name in self.whl_libraries: + fail("attempting to create a duplicate library {} for {}".format( + repo_name, + whl.name, + )) + self.whl_libraries[repo_name] = repo.args + + if not self.config.enable_pipstar and "experimental_target_platforms" in repo.args: + self.whl_libraries[repo_name] |= { + "experimental_target_platforms": sorted({ + # TODO @aignas 2025-07-07: this should be solved in a better way + platforms[candidate].triple.partition("_")[-1]: None + for p in repo.args["experimental_target_platforms"] + for candidate in platforms + if candidate.endswith(p) + }), + } + + mapping = self.whl_map.setdefault(whl.name, {}) + if repo.config_setting in mapping and mapping[repo.config_setting] != repo_name: + fail( + "attempting to override an existing repo '{}' for config setting '{}' with a new repo '{}'".format( + mapping[repo.config_setting], + repo.config_setting, + repo_name, + ), + ) + else: + mapping[repo.config_setting] = repo_name From 76d082eb36fcd9dcf449e0af501ef6bbb86fb984 Mon Sep 17 00:00:00 2001 From: Ignas Anikevicius <240938+aignas@users.noreply.github.com> Date: Sat, 6 Sep 2025 15:40:01 +0900 Subject: [PATCH 09/31] refactor: get_index_urls early return --- python/private/pypi/extension.bzl | 6 +-- python/private/pypi/hub_builder.bzl | 69 +++++++++++++++-------------- 2 files changed, 38 insertions(+), 37 deletions(-) diff --git a/python/private/pypi/extension.bzl b/python/private/pypi/extension.bzl index 20a3f2ef18..21608b5f6a 100644 --- a/python/private/pypi/extension.bzl +++ b/python/private/pypi/extension.bzl @@ -97,7 +97,7 @@ def _create_whl_repos( rule. """ logger = repo_utils.logger(module_ctx, "pypi:create_whl_repos") - get_index_urls = hub.get_index_urls(pip_attr) + get_index_urls = hub.get_index_urls(pip_attr.python_version) interpreter = hub.detect_interpreter(pip_attr) # containers to aggregate outputs from this function @@ -564,9 +564,7 @@ You cannot use both the additive_build_content and additive_build_content_file a else: builder = pip_hub_map[pip_attr.hub_name] - builder.add( - python_version = pip_attr.python_version, - ) + builder.add(pip_attr = pip_attr) # TODO @aignas 2025-05-19: express pip.parse as a series of configure calls out = _create_whl_repos( diff --git a/python/private/pypi/hub_builder.bzl b/python/private/pypi/hub_builder.bzl index b3b8aa06b6..595dc70a37 100644 --- a/python/private/pypi/hub_builder.bzl +++ b/python/private/pypi/hub_builder.bzl @@ -46,10 +46,11 @@ def hub_builder( _available_interpreters = available_interpreters, _simpleapi_download_fn = simpleapi_download_fn, _simpleapi_cache = simpleapi_cache, + _get_index_urls = {}, # keep sorted add = lambda *a, **k: _add(self, *a, **k), - get_index_urls = lambda *a, **k: _get_index_urls(self, *a, **k), detect_interpreter = lambda *a, **k: _detect_interpreter(self, *a, **k), + get_index_urls = lambda version: self._get_index_urls[version], platforms = lambda version: self.python_versions[version], add_whl_library = lambda *a, **k: _add_whl_library(self, *a, **k), ) @@ -57,7 +58,8 @@ def hub_builder( # buildifier: enable=uninitialized return self -def _add(self, *, python_version): +def _add(self, *, pip_attr): + python_version = pip_attr.python_version if python_version in self.python_versions: fail(( "Duplicate pip python version '{version}' for hub " + @@ -74,39 +76,40 @@ def _add(self, *, python_version): minor_mapping = self._minor_mapping, config = self.config, ) + self._get_index_urls[python_version] = _get_index_urls(self, pip_attr) def _get_index_urls(self, pip_attr): - get_index_urls = None - if pip_attr.experimental_index_url: - skip_sources = [ - normalize_name(s) - for s in pip_attr.simpleapi_skip - ] - get_index_urls = lambda ctx, distributions: self._simpleapi_download_fn( - ctx, - attr = struct( - index_url = pip_attr.experimental_index_url, - extra_index_urls = pip_attr.experimental_extra_index_urls or [], - index_url_overrides = pip_attr.experimental_index_url_overrides or {}, - sources = [ - d - for d in distributions - if normalize_name(d) not in skip_sources - ], - envsubst = pip_attr.envsubst, - # Auth related info - netrc = pip_attr.netrc, - auth_patterns = pip_attr.auth_patterns, - ), - cache = self._simpleapi_cache, - parallel_download = pip_attr.parallel_download, - ) - elif pip_attr.experimental_extra_index_urls: - fail("'experimental_extra_index_urls' is a no-op unless 'experimental_index_url' is set") - elif pip_attr.experimental_index_url_overrides: - fail("'experimental_index_url_overrides' is a no-op unless 'experimental_index_url' is set") - - return get_index_urls + if not pip_attr.experimental_index_url: + if pip_attr.experimental_extra_index_urls: + fail("'experimental_extra_index_urls' is a no-op unless 'experimental_index_url' is set") + elif pip_attr.experimental_index_url_overrides: + fail("'experimental_index_url_overrides' is a no-op unless 'experimental_index_url' is set") + + return None + + skip_sources = [ + normalize_name(s) + for s in pip_attr.simpleapi_skip + ] + return lambda ctx, distributions: self._simpleapi_download_fn( + ctx, + attr = struct( + index_url = pip_attr.experimental_index_url, + extra_index_urls = pip_attr.experimental_extra_index_urls or [], + index_url_overrides = pip_attr.experimental_index_url_overrides or {}, + sources = [ + d + for d in distributions + if normalize_name(d) not in skip_sources + ], + envsubst = pip_attr.envsubst, + # Auth related info + netrc = pip_attr.netrc, + auth_patterns = pip_attr.auth_patterns, + ), + cache = self._simpleapi_cache, + parallel_download = pip_attr.parallel_download, + ) def _detect_interpreter(self, pip_attr): python_interpreter_target = pip_attr.python_interpreter_target From 044b004510ff31d42666ac734965d4b4ba30c5bc Mon Sep 17 00:00:00 2001 From: Ignas Anikevicius <240938+aignas@users.noreply.github.com> Date: Sat, 6 Sep 2025 15:51:41 +0900 Subject: [PATCH 10/31] refactor: add a method for checking if we should use the downloader --- python/private/pypi/extension.bzl | 12 ++-------- python/private/pypi/hub_builder.bzl | 36 +++++++++++++++++++++-------- 2 files changed, 28 insertions(+), 20 deletions(-) diff --git a/python/private/pypi/extension.bzl b/python/private/pypi/extension.bzl index 21608b5f6a..bd3e3903a6 100644 --- a/python/private/pypi/extension.bzl +++ b/python/private/pypi/extension.bzl @@ -97,7 +97,6 @@ def _create_whl_repos( rule. """ logger = repo_utils.logger(module_ctx, "pypi:create_whl_repos") - get_index_urls = hub.get_index_urls(pip_attr.python_version) interpreter = hub.detect_interpreter(pip_attr) # containers to aggregate outputs from this function @@ -184,15 +183,11 @@ def _create_whl_repos( ), platforms = platforms, extra_pip_args = pip_attr.extra_pip_args, - get_index_urls = get_index_urls, + get_index_urls = hub.get_index_urls(pip_attr.python_version), evaluate_markers = evaluate_markers, logger = logger, ) - use_downloader = { - normalize_name(s): False - for s in pip_attr.simpleapi_skip - } exposed_packages = {} for whl in requirements_by_platform: if whl.is_exposed: @@ -246,10 +241,7 @@ def _create_whl_repos( whl_library_args = whl_library_args, download_only = pip_attr.download_only, netrc = hub.config.netrc or pip_attr.netrc, - use_downloader = use_downloader.get( - whl.name, - get_index_urls != None, # defaults to True if the get_index_urls is defined - ), + use_downloader = hub.use_downloader(pip_attr.python_version, whl.name), auth_patterns = hub.config.auth_patterns or pip_attr.auth_patterns, python_version = _major_minor_version(pip_attr.python_version), is_multiple_versions = whl.is_multiple_versions, diff --git a/python/private/pypi/hub_builder.bzl b/python/private/pypi/hub_builder.bzl index 595dc70a37..3e42b6864f 100644 --- a/python/private/pypi/hub_builder.bzl +++ b/python/private/pypi/hub_builder.bzl @@ -47,12 +47,18 @@ def hub_builder( _simpleapi_download_fn = simpleapi_download_fn, _simpleapi_cache = simpleapi_cache, _get_index_urls = {}, + _pip_attrs = {}, + _use_downloader = {}, # keep sorted add = lambda *a, **k: _add(self, *a, **k), detect_interpreter = lambda *a, **k: _detect_interpreter(self, *a, **k), - get_index_urls = lambda version: self._get_index_urls[version], + get_index_urls = lambda version: self._get_index_urls.get(version), platforms = lambda version: self.python_versions[version], add_whl_library = lambda *a, **k: _add_whl_library(self, *a, **k), + use_downloader = lambda python_version, whl_name: self._use_downloader.get(python_version, {}).get( + normalize_name(whl_name), + python_version in self._get_index_urls, + ), ) # buildifier: enable=uninitialized @@ -76,22 +82,32 @@ def _add(self, *, pip_attr): minor_mapping = self._minor_mapping, config = self.config, ) - self._get_index_urls[python_version] = _get_index_urls(self, pip_attr) + _set_index_urls(self, pip_attr) + self._pip_attrs[python_version] = pip_attr -def _get_index_urls(self, pip_attr): +def _set_index_urls(self, pip_attr): if not pip_attr.experimental_index_url: if pip_attr.experimental_extra_index_urls: fail("'experimental_extra_index_urls' is a no-op unless 'experimental_index_url' is set") elif pip_attr.experimental_index_url_overrides: fail("'experimental_index_url_overrides' is a no-op unless 'experimental_index_url' is set") + elif pip_attr.simpleapi_skip: + fail("'simpleapi_skip' is a no-op unless 'experimental_index_url' is set") + elif pip_attr.netrc: + fail("'netrc' is a no-op unless 'experimental_index_url' is set") + elif pip_attr.auth_patterns: + fail("'auth_patterns' is a no-op unless 'experimental_index_url' is set") + + # parallel_download is set to True by default, so we are not checking/validating it + # here + return - return None - - skip_sources = [ - normalize_name(s) + python_version = pip_attr.python_version + self._use_downloader.setdefault(python_version, {}).update({ + normalize_name(s): False for s in pip_attr.simpleapi_skip - ] - return lambda ctx, distributions: self._simpleapi_download_fn( + }) + self._get_index_urls[python_version] = lambda ctx, distributions: self._simpleapi_download_fn( ctx, attr = struct( index_url = pip_attr.experimental_index_url, @@ -100,7 +116,7 @@ def _get_index_urls(self, pip_attr): sources = [ d for d in distributions - if normalize_name(d) not in skip_sources + if self.use_downloader(python_version, d) ], envsubst = pip_attr.envsubst, # Auth related info From 0f83603e3ef9a393aeb784c9e3f5970f60ce7fca Mon Sep 17 00:00:00 2001 From: Ignas Anikevicius <240938+aignas@users.noreply.github.com> Date: Sat, 6 Sep 2025 16:05:34 +0900 Subject: [PATCH 11/31] refactor: move evaluate_markers fn creation to the builder --- python/private/pypi/BUILD.bazel | 1 + python/private/pypi/extension.bzl | 52 ++++------------------------- python/private/pypi/hub_builder.bzl | 49 +++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 45 deletions(-) diff --git a/python/private/pypi/BUILD.bazel b/python/private/pypi/BUILD.bazel index 73451b9784..ea41a3cf24 100644 --- a/python/private/pypi/BUILD.bazel +++ b/python/private/pypi/BUILD.bazel @@ -172,6 +172,7 @@ bzl_library( srcs = ["hub_builder.bzl"], visibility = ["//:__subpackages__"], deps = [ + ":evaluate_markers_bzl", ":pep508_env_bzl", ":pep508_evaluate_bzl", ":python_tag_bzl", diff --git a/python/private/pypi/extension.bzl b/python/private/pypi/extension.bzl index bd3e3903a6..084e009d3c 100644 --- a/python/private/pypi/extension.bzl +++ b/python/private/pypi/extension.bzl @@ -24,7 +24,7 @@ load("//python/private:normalize_name.bzl", "normalize_name") load("//python/private:repo_utils.bzl", "repo_utils") load("//python/private:version.bzl", "version") load(":attrs.bzl", "use_isolated") -load(":evaluate_markers.bzl", "evaluate_markers_py", EVALUATE_MARKERS_SRCS = "SRCS", evaluate_markers_star = "evaluate_markers") +load(":evaluate_markers.bzl", EVALUATE_MARKERS_SRCS = "SRCS") load(":hub_builder.bzl", "hub_builder") load(":hub_repository.bzl", "hub_repository", "whl_config_settings_to_json") load(":parse_requirements.bzl", "parse_requirements") @@ -72,8 +72,7 @@ def _create_whl_repos( pip_attr, whl_overrides, hub, - minor_mapping = MINOR_MAPPING, - evaluate_markers = None): + minor_mapping = MINOR_MAPPING): """create all of the whl repositories Args: @@ -83,7 +82,6 @@ def _create_whl_repos( hub: TODO. minor_mapping: {type}`dict[str, str]` The dictionary needed to resolve the full python version used to parse package METADATA files. - evaluate_markers: the function used to evaluate the markers. Returns a {type}`struct` with the following attributes: whl_map: {type}`dict[str, list[struct]]` the output is keyed by the @@ -127,44 +125,6 @@ def _create_whl_repos( platforms = hub.platforms(pip_attr.python_version) - if evaluate_markers: - # This is most likely unit tests - pass - elif hub.config.enable_pipstar: - evaluate_markers = lambda _, requirements: evaluate_markers_star( - requirements = requirements, - platforms = platforms, - ) - else: - # NOTE @aignas 2024-08-02: , we will execute any interpreter that we find either - # in the PATH or if specified as a label. We will configure the env - # markers when evaluating the requirement lines based on the output - # from the `requirements_files_by_platform` which should have something - # similar to: - # { - # "//:requirements.txt": ["cp311_linux_x86_64", ...] - # } - # - # We know the target python versions that we need to evaluate the - # markers for and thus we don't need to use multiple python interpreter - # instances to perform this manipulation. This function should be executed - # only once by the underlying code to minimize the overhead needed to - # spin up a Python interpreter. - evaluate_markers = lambda module_ctx, requirements: evaluate_markers_py( - module_ctx, - requirements = { - k: { - p: platforms[p].triple - for p in plats - } - for k, plats in requirements.items() - }, - python_interpreter = interpreter.path, - python_interpreter_target = interpreter.target, - srcs = pip_attr._evaluate_markers_srcs, - logger = logger, - ) - requirements_by_platform = parse_requirements( module_ctx, requirements_by_platform = requirements_files_by_platform( @@ -184,7 +144,7 @@ def _create_whl_repos( platforms = platforms, extra_pip_args = pip_attr.extra_pip_args, get_index_urls = hub.get_index_urls(pip_attr.python_version), - evaluate_markers = evaluate_markers, + evaluate_markers = hub.evaluate_markers(pip_attr), logger = logger, ) @@ -536,7 +496,9 @@ You cannot use both the additive_build_content and additive_build_content_file a simpleapi_download_fn = simpleapi_download, simpleapi_cache = simpleapi_cache, minor_mapping = kwargs.get("minor_mapping", MINOR_MAPPING), - available_interpreters = kwargs.pop("available_interpreters", INTERPRETER_LABELS), + evaluate_markers_fn = kwargs.get("evaluate_markers", None), + available_interpreters = kwargs.get("available_interpreters", INTERPRETER_LABELS), + logger = repo_utils.logger(module_ctx, "pypi:hub:" + hub_name), ) pip_hub_map[pip_attr.hub_name] = builder elif pip_hub_map[hub_name].module_name != mod.name: @@ -564,7 +526,7 @@ You cannot use both the additive_build_content and additive_build_content_file a hub = builder, pip_attr = pip_attr, whl_overrides = whl_overrides, - **kwargs + minor_mapping = kwargs.get("minor_mapping", MINOR_MAPPING), ) extra_aliases.setdefault(hub_name, {}) diff --git a/python/private/pypi/hub_builder.bzl b/python/private/pypi/hub_builder.bzl index 3e42b6864f..cf69cd5689 100644 --- a/python/private/pypi/hub_builder.bzl +++ b/python/private/pypi/hub_builder.bzl @@ -4,6 +4,7 @@ load("//python/private:full_version.bzl", "full_version") load("//python/private:normalize_name.bzl", "normalize_name") load("//python/private:version.bzl", "version") load("//python/private:version_label.bzl", "version_label") +load(":evaluate_markers.bzl", "evaluate_markers_py", evaluate_markers_star = "evaluate_markers") load(":pep508_env.bzl", "env") load(":pep508_evaluate.bzl", "evaluate") load(":python_tag.bzl", "python_tag") @@ -16,6 +17,8 @@ def hub_builder( minor_mapping, available_interpreters, simpleapi_download_fn, + evaluate_markers_fn, + logger, simpleapi_cache = {}): """Return a hub builder instance @@ -24,12 +27,14 @@ def hub_builder( module_name: TODO config: The platform configuration. minor_mapping: TODO + evaluate_markers_fn: the function used to evaluate the markers. available_interpreters: {type}`dict[str, Label]` The dictionary of available interpreters that have been registered using the `python` bzlmod extension. The keys are in the form `python_{snake_case_version}_host`. This is to be used during the `repository_rule` and must be always compatible with the host. simpleapi_download_fn: TODO simpleapi_cache: TODO + logger: TODO """ # buildifier: disable=uninitialized @@ -42,6 +47,8 @@ def hub_builder( exposed_packages = {}, extra_aliases = {}, whl_libraries = {}, + _evaluate_markers_fn = evaluate_markers_fn, + _logger = logger, _minor_mapping = minor_mapping, _available_interpreters = available_interpreters, _simpleapi_download_fn = simpleapi_download_fn, @@ -59,6 +66,7 @@ def hub_builder( normalize_name(whl_name), python_version in self._get_index_urls, ), + evaluate_markers = lambda *a, **k: _evaluate_markers(self, *a, **k), ) # buildifier: enable=uninitialized @@ -239,3 +247,44 @@ def _add_whl_library(self, *, python_version, whl, repo): ) else: mapping[repo.config_setting] = repo_name + +def _evaluate_markers(self, pip_attr): + if self._evaluate_markers_fn: + return self._evaluate_markers_fn + + if self.config.enable_pipstar: + return lambda _, requirements: evaluate_markers_star( + requirements = requirements, + platforms = self.python_versions[pip_attr.python_version], + ) + + interpreter = self.detect_interpreter(pip_attr) + + # NOTE @aignas 2024-08-02: , we will execute any interpreter that we find either + # in the PATH or if specified as a label. We will configure the env + # markers when evaluating the requirement lines based on the output + # from the `requirements_files_by_platform` which should have something + # similar to: + # { + # "//:requirements.txt": ["cp311_linux_x86_64", ...] + # } + # + # We know the target python versions that we need to evaluate the + # markers for and thus we don't need to use multiple python interpreter + # instances to perform this manipulation. This function should be executed + # only once by the underlying code to minimize the overhead needed to + # spin up a Python interpreter. + return lambda module_ctx, requirements: evaluate_markers_py( + module_ctx, + requirements = { + k: { + p: self.python_versions[pip_attr.python_version][p].triple + for p in plats + } + for k, plats in requirements.items() + }, + python_interpreter = interpreter.path, + python_interpreter_target = interpreter.target, + srcs = pip_attr._evaluate_markers_srcs, + logger = self._logger, + ) From 7988d4470a71c6d6c244fc25e456b926ab8b4299 Mon Sep 17 00:00:00 2001 From: Ignas Anikevicius <240938+aignas@users.noreply.github.com> Date: Sat, 6 Sep 2025 16:07:55 +0900 Subject: [PATCH 12/31] minor: code reshuffling --- python/private/pypi/extension.bzl | 41 ++++++++++++++----------------- 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/python/private/pypi/extension.bzl b/python/private/pypi/extension.bzl index 084e009d3c..3851199667 100644 --- a/python/private/pypi/extension.bzl +++ b/python/private/pypi/extension.bzl @@ -97,34 +97,12 @@ def _create_whl_repos( logger = repo_utils.logger(module_ctx, "pypi:create_whl_repos") interpreter = hub.detect_interpreter(pip_attr) - # containers to aggregate outputs from this function - extra_aliases = { - whl_name: {alias: True for alias in aliases} - for whl_name, aliases in pip_attr.extra_hub_aliases.items() - } - whl_modifications = {} if pip_attr.whl_modifications != None: for mod, whl_name in pip_attr.whl_modifications.items(): whl_modifications[normalize_name(whl_name)] = mod - if pip_attr.experimental_requirement_cycles: - requirement_cycles = { - name: [normalize_name(whl_name) for whl_name in whls] - for name, whls in pip_attr.experimental_requirement_cycles.items() - } - - whl_group_mapping = { - whl_name: group_name - for group_name, group_whls in requirement_cycles.items() - for whl_name in group_whls - } - else: - whl_group_mapping = {} - requirement_cycles = {} - platforms = hub.platforms(pip_attr.python_version) - requirements_by_platform = parse_requirements( module_ctx, requirements_by_platform = requirements_files_by_platform( @@ -148,6 +126,20 @@ def _create_whl_repos( logger = logger, ) + if pip_attr.experimental_requirement_cycles: + requirement_cycles = { + name: [normalize_name(whl_name) for whl_name in whls] + for name, whls in pip_attr.experimental_requirement_cycles.items() + } + + whl_group_mapping = { + whl_name: group_name + for group_name, group_whls in requirement_cycles.items() + for whl_name in group_whls + } + else: + whl_group_mapping = {} + requirement_cycles = {} exposed_packages = {} for whl in requirements_by_platform: if whl.is_exposed: @@ -215,7 +207,10 @@ def _create_whl_repos( return struct( exposed_packages = exposed_packages, - extra_aliases = extra_aliases, + extra_aliases = { + whl_name: {alias: True for alias in aliases} + for whl_name, aliases in pip_attr.extra_hub_aliases.items() + }, ) def _whl_repo( From 4e754f7606a053109d9df948740588ea53d7d86e Mon Sep 17 00:00:00 2001 From: Ignas Anikevicius <240938+aignas@users.noreply.github.com> Date: Sat, 6 Sep 2025 16:14:30 +0900 Subject: [PATCH 13/31] minor: use hub builder for all hub output storage --- python/private/pypi/extension.bzl | 61 ++++++++++------------------- python/private/pypi/hub_builder.bzl | 23 +++++++++++ 2 files changed, 43 insertions(+), 41 deletions(-) diff --git a/python/private/pypi/extension.bzl b/python/private/pypi/extension.bzl index 3851199667..45968bf47c 100644 --- a/python/private/pypi/extension.bzl +++ b/python/private/pypi/extension.bzl @@ -66,7 +66,7 @@ def _whl_mods_impl(whl_mods_dict): whl_mods = whl_mods, ) -def _create_whl_repos( +def _add_whl_repos( module_ctx, *, pip_attr, @@ -82,17 +82,6 @@ def _create_whl_repos( hub: TODO. minor_mapping: {type}`dict[str, str]` The dictionary needed to resolve the full python version used to parse package METADATA files. - - Returns a {type}`struct` with the following attributes: - whl_map: {type}`dict[str, list[struct]]` the output is keyed by the - normalized package name and the values are the instances of the - {bzl:obj}`whl_config_setting` return values. - exposed_packages: {type}`dict[str, Any]` this is just a way to - represent a set of string values. - whl_libraries: {type}`dict[str, dict[str, Any]]` the keys are the - aparent repository names for the hub repo and the values are the - arguments that will be passed to {bzl:obj}`whl_library` repository - rule. """ logger = repo_utils.logger(module_ctx, "pypi:create_whl_repos") interpreter = hub.detect_interpreter(pip_attr) @@ -126,6 +115,7 @@ def _create_whl_repos( logger = logger, ) + exposed_packages = {} if pip_attr.experimental_requirement_cycles: requirement_cycles = { name: [normalize_name(whl_name) for whl_name in whls] @@ -140,7 +130,6 @@ def _create_whl_repos( else: whl_group_mapping = {} requirement_cycles = {} - exposed_packages = {} for whl in requirements_by_platform: if whl.is_exposed: exposed_packages[whl.name] = None @@ -205,13 +194,17 @@ def _create_whl_repos( repo = repo, ) - return struct( - exposed_packages = exposed_packages, - extra_aliases = { - whl_name: {alias: True for alias in aliases} - for whl_name, aliases in pip_attr.extra_hub_aliases.items() - }, - ) + if not hub.exposed_packages: + hub.exposed_packages.update(exposed_packages) + else: + intersection = {} + for pkg in exposed_packages: + if pkg not in hub.exposed_packages: + continue + intersection[pkg] = None + + hub.exposed_packages.clear() + hub.exposed_packages.update(intersection) def _whl_repo( *, @@ -516,7 +509,7 @@ You cannot use both the additive_build_content and additive_build_content_file a builder.add(pip_attr = pip_attr) # TODO @aignas 2025-05-19: express pip.parse as a series of configure calls - out = _create_whl_repos( + _add_whl_repos( module_ctx, hub = builder, pip_attr = pip_attr, @@ -524,32 +517,15 @@ You cannot use both the additive_build_content and additive_build_content_file a minor_mapping = kwargs.get("minor_mapping", MINOR_MAPPING), ) - extra_aliases.setdefault(hub_name, {}) - for whl_name, aliases in out.extra_aliases.items(): - extra_aliases[hub_name].setdefault(whl_name, {}).update(aliases) - - if hub_name not in exposed_packages: - exposed_packages[hub_name] = out.exposed_packages - else: - intersection = {} - for pkg in out.exposed_packages: - if pkg not in exposed_packages[hub_name]: - continue - intersection[pkg] = None - exposed_packages[hub_name] = intersection - - # TODO @aignas 2024-04-05: how do we support different requirement - # cycles for different abis/oses? For now we will need the users to - # assume the same groups across all versions/platforms until we start - # using an alternative cycle resolution strategy. - hub_group_map[hub_name] = pip_attr.experimental_requirement_cycles - for hub in pip_hub_map.values(): hub_whl_map.setdefault(hub.name, {}) for key, settings in hub.whl_map.items(): for setting, repo in settings.items(): hub_whl_map[hub.name].setdefault(key, {}).setdefault(repo, []).append(setting) + for whl_name, aliases in hub.extra_aliases.items(): + extra_aliases[hub.name].setdefault(whl_name, {}).update(aliases) + whl_libraries.update(hub.whl_libraries) for whl_name, lib in hub.whl_libraries.items(): if whl_name in lib: @@ -558,6 +534,9 @@ You cannot use both the additive_build_content and additive_build_content_file a # replicate whl_libraries.update(out.whl_libraries) whl_libraries[whl_name] = lib + hub_group_map[hub.name] = hub.group_map + exposed_packages[hub.name] = hub.exposed_packages + return struct( # We sort so that the lock-file remains the same no matter the order of how the # args are manipulated in the code going before. diff --git a/python/private/pypi/hub_builder.bzl b/python/private/pypi/hub_builder.bzl index cf69cd5689..0dac160eb7 100644 --- a/python/private/pypi/hub_builder.bzl +++ b/python/private/pypi/hub_builder.bzl @@ -35,6 +35,18 @@ def hub_builder( simpleapi_download_fn: TODO simpleapi_cache: TODO logger: TODO + + TODO + Returns a {type}`struct` with the following attributes: + whl_map: {type}`dict[str, list[struct]]` the output is keyed by the + normalized package name and the values are the instances of the + {bzl:obj}`whl_config_setting` return values. + exposed_packages: {type}`dict[str, Any]` this is just a way to + represent a set of string values. + whl_libraries: {type}`dict[str, dict[str, Any]]` the keys are the + aparent repository names for the hub repo and the values are the + arguments that will be passed to {bzl:obj}`whl_library` repository + rule. """ # buildifier: disable=uninitialized @@ -93,6 +105,17 @@ def _add(self, *, pip_attr): _set_index_urls(self, pip_attr) self._pip_attrs[python_version] = pip_attr + self.extra_aliases.update({ + whl_name: {alias: True for alias in aliases} + for whl_name, aliases in pip_attr.extra_hub_aliases.items() + }) + + # TODO @aignas 2024-04-05: how do we support different requirement + # cycles for different abis/oses? For now we will need the users to + # assume the same groups across all versions/platforms until we start + # using an alternative cycle resolution strategy. + self.group_map = pip_attr.experimental_requirement_cycles + def _set_index_urls(self, pip_attr): if not pip_attr.experimental_index_url: if pip_attr.experimental_extra_index_urls: From 6064ab7287c5d3231655d892ae88236a8358ad30 Mon Sep 17 00:00:00 2001 From: Ignas Anikevicius <240938+aignas@users.noreply.github.com> Date: Sat, 6 Sep 2025 16:22:11 +0900 Subject: [PATCH 14/31] wip --- python/private/pypi/extension.bzl | 15 +++++++-------- python/private/pypi/hub_builder.bzl | 14 +++++++++++++- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/python/private/pypi/extension.bzl b/python/private/pypi/extension.bzl index 45968bf47c..653673f28a 100644 --- a/python/private/pypi/extension.bzl +++ b/python/private/pypi/extension.bzl @@ -518,24 +518,23 @@ You cannot use both the additive_build_content and additive_build_content_file a ) for hub in pip_hub_map.values(): + out = hub.build() hub_whl_map.setdefault(hub.name, {}) - for key, settings in hub.whl_map.items(): + for key, settings in out.whl_map.items(): for setting, repo in settings.items(): hub_whl_map[hub.name].setdefault(key, {}).setdefault(repo, []).append(setting) - for whl_name, aliases in hub.extra_aliases.items(): - extra_aliases[hub.name].setdefault(whl_name, {}).update(aliases) - - whl_libraries.update(hub.whl_libraries) - for whl_name, lib in hub.whl_libraries.items(): + for whl_name, lib in out.whl_libraries.items(): if whl_name in lib: fail("'{}' already in created".format(whl_name)) else: # replicate whl_libraries.update(out.whl_libraries) whl_libraries[whl_name] = lib - hub_group_map[hub.name] = hub.group_map - exposed_packages[hub.name] = hub.exposed_packages + hub_whl_map[hub.name] = out.whl_map + extra_aliases[hub.name] = out.extra_aliases + hub_group_map[hub.name] = out.group_map + exposed_packages[hub.name] = out.exposed_packages return struct( # We sort so that the lock-file remains the same no matter the order of how the diff --git a/python/private/pypi/hub_builder.bzl b/python/private/pypi/hub_builder.bzl index 0dac160eb7..8172f2bf47 100644 --- a/python/private/pypi/hub_builder.bzl +++ b/python/private/pypi/hub_builder.bzl @@ -59,6 +59,7 @@ def hub_builder( exposed_packages = {}, extra_aliases = {}, whl_libraries = {}, + group_map = {}, _evaluate_markers_fn = evaluate_markers_fn, _logger = logger, _minor_mapping = minor_mapping, @@ -79,11 +80,21 @@ def hub_builder( python_version in self._get_index_urls, ), evaluate_markers = lambda *a, **k: _evaluate_markers(self, *a, **k), + build = lambda: _build(self), ) # buildifier: enable=uninitialized return self +def _build(self): + return struct( + whl_map = self.whl_map, + exposed_packages = self.exposed_packages, + extra_aliases = self.extra_aliases, + whl_libraries = self.whl_libraries, + group_map = self.group_map, + ) + def _add(self, *, pip_attr): python_version = pip_attr.python_version if python_version in self.python_versions: @@ -114,7 +125,8 @@ def _add(self, *, pip_attr): # cycles for different abis/oses? For now we will need the users to # assume the same groups across all versions/platforms until we start # using an alternative cycle resolution strategy. - self.group_map = pip_attr.experimental_requirement_cycles + self.group_map.clear() + self.group_map.update(pip_attr.experimental_requirement_cycles) def _set_index_urls(self, pip_attr): if not pip_attr.experimental_index_url: From b9bf362ab3b43c1078371dbbee3c78bb022cd1b5 Mon Sep 17 00:00:00 2001 From: Ignas Anikevicius <240938+aignas@users.noreply.github.com> Date: Sat, 6 Sep 2025 21:50:37 +0900 Subject: [PATCH 15/31] Revert "wip" This reverts commit 6064ab7287c5d3231655d892ae88236a8358ad30. --- python/private/pypi/extension.bzl | 15 ++++++++------- python/private/pypi/hub_builder.bzl | 14 +------------- 2 files changed, 9 insertions(+), 20 deletions(-) diff --git a/python/private/pypi/extension.bzl b/python/private/pypi/extension.bzl index 653673f28a..45968bf47c 100644 --- a/python/private/pypi/extension.bzl +++ b/python/private/pypi/extension.bzl @@ -518,23 +518,24 @@ You cannot use both the additive_build_content and additive_build_content_file a ) for hub in pip_hub_map.values(): - out = hub.build() hub_whl_map.setdefault(hub.name, {}) - for key, settings in out.whl_map.items(): + for key, settings in hub.whl_map.items(): for setting, repo in settings.items(): hub_whl_map[hub.name].setdefault(key, {}).setdefault(repo, []).append(setting) - for whl_name, lib in out.whl_libraries.items(): + for whl_name, aliases in hub.extra_aliases.items(): + extra_aliases[hub.name].setdefault(whl_name, {}).update(aliases) + + whl_libraries.update(hub.whl_libraries) + for whl_name, lib in hub.whl_libraries.items(): if whl_name in lib: fail("'{}' already in created".format(whl_name)) else: # replicate whl_libraries.update(out.whl_libraries) whl_libraries[whl_name] = lib - hub_whl_map[hub.name] = out.whl_map - extra_aliases[hub.name] = out.extra_aliases - hub_group_map[hub.name] = out.group_map - exposed_packages[hub.name] = out.exposed_packages + hub_group_map[hub.name] = hub.group_map + exposed_packages[hub.name] = hub.exposed_packages return struct( # We sort so that the lock-file remains the same no matter the order of how the diff --git a/python/private/pypi/hub_builder.bzl b/python/private/pypi/hub_builder.bzl index 8172f2bf47..0dac160eb7 100644 --- a/python/private/pypi/hub_builder.bzl +++ b/python/private/pypi/hub_builder.bzl @@ -59,7 +59,6 @@ def hub_builder( exposed_packages = {}, extra_aliases = {}, whl_libraries = {}, - group_map = {}, _evaluate_markers_fn = evaluate_markers_fn, _logger = logger, _minor_mapping = minor_mapping, @@ -80,21 +79,11 @@ def hub_builder( python_version in self._get_index_urls, ), evaluate_markers = lambda *a, **k: _evaluate_markers(self, *a, **k), - build = lambda: _build(self), ) # buildifier: enable=uninitialized return self -def _build(self): - return struct( - whl_map = self.whl_map, - exposed_packages = self.exposed_packages, - extra_aliases = self.extra_aliases, - whl_libraries = self.whl_libraries, - group_map = self.group_map, - ) - def _add(self, *, pip_attr): python_version = pip_attr.python_version if python_version in self.python_versions: @@ -125,8 +114,7 @@ def _add(self, *, pip_attr): # cycles for different abis/oses? For now we will need the users to # assume the same groups across all versions/platforms until we start # using an alternative cycle resolution strategy. - self.group_map.clear() - self.group_map.update(pip_attr.experimental_requirement_cycles) + self.group_map = pip_attr.experimental_requirement_cycles def _set_index_urls(self, pip_attr): if not pip_attr.experimental_index_url: From c8e5304bd6993becd59efff3aef8746cf769b634 Mon Sep 17 00:00:00 2001 From: Ignas Anikevicius <240938+aignas@users.noreply.github.com> Date: Sat, 6 Sep 2025 21:50:48 +0900 Subject: [PATCH 16/31] Revert "minor: use hub builder for all hub output storage" This reverts commit 4e754f7606a053109d9df948740588ea53d7d86e. --- python/private/pypi/extension.bzl | 61 +++++++++++++++++++---------- python/private/pypi/hub_builder.bzl | 23 ----------- 2 files changed, 41 insertions(+), 43 deletions(-) diff --git a/python/private/pypi/extension.bzl b/python/private/pypi/extension.bzl index 45968bf47c..3851199667 100644 --- a/python/private/pypi/extension.bzl +++ b/python/private/pypi/extension.bzl @@ -66,7 +66,7 @@ def _whl_mods_impl(whl_mods_dict): whl_mods = whl_mods, ) -def _add_whl_repos( +def _create_whl_repos( module_ctx, *, pip_attr, @@ -82,6 +82,17 @@ def _add_whl_repos( hub: TODO. minor_mapping: {type}`dict[str, str]` The dictionary needed to resolve the full python version used to parse package METADATA files. + + Returns a {type}`struct` with the following attributes: + whl_map: {type}`dict[str, list[struct]]` the output is keyed by the + normalized package name and the values are the instances of the + {bzl:obj}`whl_config_setting` return values. + exposed_packages: {type}`dict[str, Any]` this is just a way to + represent a set of string values. + whl_libraries: {type}`dict[str, dict[str, Any]]` the keys are the + aparent repository names for the hub repo and the values are the + arguments that will be passed to {bzl:obj}`whl_library` repository + rule. """ logger = repo_utils.logger(module_ctx, "pypi:create_whl_repos") interpreter = hub.detect_interpreter(pip_attr) @@ -115,7 +126,6 @@ def _add_whl_repos( logger = logger, ) - exposed_packages = {} if pip_attr.experimental_requirement_cycles: requirement_cycles = { name: [normalize_name(whl_name) for whl_name in whls] @@ -130,6 +140,7 @@ def _add_whl_repos( else: whl_group_mapping = {} requirement_cycles = {} + exposed_packages = {} for whl in requirements_by_platform: if whl.is_exposed: exposed_packages[whl.name] = None @@ -194,17 +205,13 @@ def _add_whl_repos( repo = repo, ) - if not hub.exposed_packages: - hub.exposed_packages.update(exposed_packages) - else: - intersection = {} - for pkg in exposed_packages: - if pkg not in hub.exposed_packages: - continue - intersection[pkg] = None - - hub.exposed_packages.clear() - hub.exposed_packages.update(intersection) + return struct( + exposed_packages = exposed_packages, + extra_aliases = { + whl_name: {alias: True for alias in aliases} + for whl_name, aliases in pip_attr.extra_hub_aliases.items() + }, + ) def _whl_repo( *, @@ -509,7 +516,7 @@ You cannot use both the additive_build_content and additive_build_content_file a builder.add(pip_attr = pip_attr) # TODO @aignas 2025-05-19: express pip.parse as a series of configure calls - _add_whl_repos( + out = _create_whl_repos( module_ctx, hub = builder, pip_attr = pip_attr, @@ -517,15 +524,32 @@ You cannot use both the additive_build_content and additive_build_content_file a minor_mapping = kwargs.get("minor_mapping", MINOR_MAPPING), ) + extra_aliases.setdefault(hub_name, {}) + for whl_name, aliases in out.extra_aliases.items(): + extra_aliases[hub_name].setdefault(whl_name, {}).update(aliases) + + if hub_name not in exposed_packages: + exposed_packages[hub_name] = out.exposed_packages + else: + intersection = {} + for pkg in out.exposed_packages: + if pkg not in exposed_packages[hub_name]: + continue + intersection[pkg] = None + exposed_packages[hub_name] = intersection + + # TODO @aignas 2024-04-05: how do we support different requirement + # cycles for different abis/oses? For now we will need the users to + # assume the same groups across all versions/platforms until we start + # using an alternative cycle resolution strategy. + hub_group_map[hub_name] = pip_attr.experimental_requirement_cycles + for hub in pip_hub_map.values(): hub_whl_map.setdefault(hub.name, {}) for key, settings in hub.whl_map.items(): for setting, repo in settings.items(): hub_whl_map[hub.name].setdefault(key, {}).setdefault(repo, []).append(setting) - for whl_name, aliases in hub.extra_aliases.items(): - extra_aliases[hub.name].setdefault(whl_name, {}).update(aliases) - whl_libraries.update(hub.whl_libraries) for whl_name, lib in hub.whl_libraries.items(): if whl_name in lib: @@ -534,9 +558,6 @@ You cannot use both the additive_build_content and additive_build_content_file a # replicate whl_libraries.update(out.whl_libraries) whl_libraries[whl_name] = lib - hub_group_map[hub.name] = hub.group_map - exposed_packages[hub.name] = hub.exposed_packages - return struct( # We sort so that the lock-file remains the same no matter the order of how the # args are manipulated in the code going before. diff --git a/python/private/pypi/hub_builder.bzl b/python/private/pypi/hub_builder.bzl index 0dac160eb7..cf69cd5689 100644 --- a/python/private/pypi/hub_builder.bzl +++ b/python/private/pypi/hub_builder.bzl @@ -35,18 +35,6 @@ def hub_builder( simpleapi_download_fn: TODO simpleapi_cache: TODO logger: TODO - - TODO - Returns a {type}`struct` with the following attributes: - whl_map: {type}`dict[str, list[struct]]` the output is keyed by the - normalized package name and the values are the instances of the - {bzl:obj}`whl_config_setting` return values. - exposed_packages: {type}`dict[str, Any]` this is just a way to - represent a set of string values. - whl_libraries: {type}`dict[str, dict[str, Any]]` the keys are the - aparent repository names for the hub repo and the values are the - arguments that will be passed to {bzl:obj}`whl_library` repository - rule. """ # buildifier: disable=uninitialized @@ -105,17 +93,6 @@ def _add(self, *, pip_attr): _set_index_urls(self, pip_attr) self._pip_attrs[python_version] = pip_attr - self.extra_aliases.update({ - whl_name: {alias: True for alias in aliases} - for whl_name, aliases in pip_attr.extra_hub_aliases.items() - }) - - # TODO @aignas 2024-04-05: how do we support different requirement - # cycles for different abis/oses? For now we will need the users to - # assume the same groups across all versions/platforms until we start - # using an alternative cycle resolution strategy. - self.group_map = pip_attr.experimental_requirement_cycles - def _set_index_urls(self, pip_attr): if not pip_attr.experimental_index_url: if pip_attr.experimental_extra_index_urls: From f902694e6d7d3a64114c7240a1ca97ffd47ec19d Mon Sep 17 00:00:00 2001 From: Ignas Anikevicius <240938+aignas@users.noreply.github.com> Date: Sat, 6 Sep 2025 21:53:02 +0900 Subject: [PATCH 17/31] refactor: move group_map to builder --- python/private/pypi/extension.bzl | 8 ++------ python/private/pypi/hub_builder.bzl | 8 ++++++++ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/python/private/pypi/extension.bzl b/python/private/pypi/extension.bzl index 3851199667..a161740cb4 100644 --- a/python/private/pypi/extension.bzl +++ b/python/private/pypi/extension.bzl @@ -538,12 +538,6 @@ You cannot use both the additive_build_content and additive_build_content_file a intersection[pkg] = None exposed_packages[hub_name] = intersection - # TODO @aignas 2024-04-05: how do we support different requirement - # cycles for different abis/oses? For now we will need the users to - # assume the same groups across all versions/platforms until we start - # using an alternative cycle resolution strategy. - hub_group_map[hub_name] = pip_attr.experimental_requirement_cycles - for hub in pip_hub_map.values(): hub_whl_map.setdefault(hub.name, {}) for key, settings in hub.whl_map.items(): @@ -558,6 +552,8 @@ You cannot use both the additive_build_content and additive_build_content_file a # replicate whl_libraries.update(out.whl_libraries) whl_libraries[whl_name] = lib + hub_group_map[hub.name] = hub.group_map + return struct( # We sort so that the lock-file remains the same no matter the order of how the # args are manipulated in the code going before. diff --git a/python/private/pypi/hub_builder.bzl b/python/private/pypi/hub_builder.bzl index cf69cd5689..1195f3346f 100644 --- a/python/private/pypi/hub_builder.bzl +++ b/python/private/pypi/hub_builder.bzl @@ -47,6 +47,7 @@ def hub_builder( exposed_packages = {}, extra_aliases = {}, whl_libraries = {}, + group_map = {}, _evaluate_markers_fn = evaluate_markers_fn, _logger = logger, _minor_mapping = minor_mapping, @@ -93,6 +94,13 @@ def _add(self, *, pip_attr): _set_index_urls(self, pip_attr) self._pip_attrs[python_version] = pip_attr + # TODO @aignas 2024-04-05: how do we support different requirement + # cycles for different abis/oses? For now we will need the users to + # assume the same groups across all versions/platforms until we start + # using an alternative cycle resolution strategy. + self.group_map.clear() + self.group_map.update(pip_attr.experimental_requirement_cycles) + def _set_index_urls(self, pip_attr): if not pip_attr.experimental_index_url: if pip_attr.experimental_extra_index_urls: From 7f7f635930cd064c7190e2e71a0fa8bd92f8afa5 Mon Sep 17 00:00:00 2001 From: Ignas Anikevicius <240938+aignas@users.noreply.github.com> Date: Sat, 6 Sep 2025 21:56:00 +0900 Subject: [PATCH 18/31] refactor: move extra aliases to the hub builder --- python/private/pypi/extension.bzl | 9 +-------- python/private/pypi/hub_builder.bzl | 5 +++++ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/python/private/pypi/extension.bzl b/python/private/pypi/extension.bzl index a161740cb4..fd28eba467 100644 --- a/python/private/pypi/extension.bzl +++ b/python/private/pypi/extension.bzl @@ -207,10 +207,6 @@ def _create_whl_repos( return struct( exposed_packages = exposed_packages, - extra_aliases = { - whl_name: {alias: True for alias in aliases} - for whl_name, aliases in pip_attr.extra_hub_aliases.items() - }, ) def _whl_repo( @@ -524,10 +520,6 @@ You cannot use both the additive_build_content and additive_build_content_file a minor_mapping = kwargs.get("minor_mapping", MINOR_MAPPING), ) - extra_aliases.setdefault(hub_name, {}) - for whl_name, aliases in out.extra_aliases.items(): - extra_aliases[hub_name].setdefault(whl_name, {}).update(aliases) - if hub_name not in exposed_packages: exposed_packages[hub_name] = out.exposed_packages else: @@ -553,6 +545,7 @@ You cannot use both the additive_build_content and additive_build_content_file a whl_libraries[whl_name] = lib hub_group_map[hub.name] = hub.group_map + extra_aliases[hub.name] = hub.extra_aliases return struct( # We sort so that the lock-file remains the same no matter the order of how the diff --git a/python/private/pypi/hub_builder.bzl b/python/private/pypi/hub_builder.bzl index 1195f3346f..df050250d2 100644 --- a/python/private/pypi/hub_builder.bzl +++ b/python/private/pypi/hub_builder.bzl @@ -101,6 +101,11 @@ def _add(self, *, pip_attr): self.group_map.clear() self.group_map.update(pip_attr.experimental_requirement_cycles) + for whl_name, aliases in pip_attr.extra_hub_aliases.items(): + self.extra_aliases.setdefault(whl_name, {}).update( + {alias: True for alias in aliases}, + ) + def _set_index_urls(self, pip_attr): if not pip_attr.experimental_index_url: if pip_attr.experimental_extra_index_urls: From a636ca6b9573c062959c45a40d04e2c03da07cb1 Mon Sep 17 00:00:00 2001 From: Ignas Anikevicius <240938+aignas@users.noreply.github.com> Date: Sat, 6 Sep 2025 21:59:34 +0900 Subject: [PATCH 19/31] refactor: move exposed_packages to the hub builder --- python/private/pypi/extension.bzl | 37 ++++++++++--------------------- 1 file changed, 12 insertions(+), 25 deletions(-) diff --git a/python/private/pypi/extension.bzl b/python/private/pypi/extension.bzl index fd28eba467..705063c34b 100644 --- a/python/private/pypi/extension.bzl +++ b/python/private/pypi/extension.bzl @@ -82,17 +82,6 @@ def _create_whl_repos( hub: TODO. minor_mapping: {type}`dict[str, str]` The dictionary needed to resolve the full python version used to parse package METADATA files. - - Returns a {type}`struct` with the following attributes: - whl_map: {type}`dict[str, list[struct]]` the output is keyed by the - normalized package name and the values are the instances of the - {bzl:obj}`whl_config_setting` return values. - exposed_packages: {type}`dict[str, Any]` this is just a way to - represent a set of string values. - whl_libraries: {type}`dict[str, dict[str, Any]]` the keys are the - aparent repository names for the hub repo and the values are the - arguments that will be passed to {bzl:obj}`whl_library` repository - rule. """ logger = repo_utils.logger(module_ctx, "pypi:create_whl_repos") interpreter = hub.detect_interpreter(pip_attr) @@ -205,9 +194,16 @@ def _create_whl_repos( repo = repo, ) - return struct( - exposed_packages = exposed_packages, - ) + if hub.exposed_packages: + intersection = {} + for pkg in exposed_packages: + if pkg not in hub.exposed_packages: + continue + intersection[pkg] = None + hub.exposed_packages.clear() + exposed_packages = intersection + + hub.exposed_packages.update(exposed_packages) def _whl_repo( *, @@ -512,7 +508,7 @@ You cannot use both the additive_build_content and additive_build_content_file a builder.add(pip_attr = pip_attr) # TODO @aignas 2025-05-19: express pip.parse as a series of configure calls - out = _create_whl_repos( + _create_whl_repos( module_ctx, hub = builder, pip_attr = pip_attr, @@ -520,16 +516,6 @@ You cannot use both the additive_build_content and additive_build_content_file a minor_mapping = kwargs.get("minor_mapping", MINOR_MAPPING), ) - if hub_name not in exposed_packages: - exposed_packages[hub_name] = out.exposed_packages - else: - intersection = {} - for pkg in out.exposed_packages: - if pkg not in exposed_packages[hub_name]: - continue - intersection[pkg] = None - exposed_packages[hub_name] = intersection - for hub in pip_hub_map.values(): hub_whl_map.setdefault(hub.name, {}) for key, settings in hub.whl_map.items(): @@ -546,6 +532,7 @@ You cannot use both the additive_build_content and additive_build_content_file a hub_group_map[hub.name] = hub.group_map extra_aliases[hub.name] = hub.extra_aliases + exposed_packages[hub.name] = hub.exposed_packages return struct( # We sort so that the lock-file remains the same no matter the order of how the From 62dccd5dbdfb14f9dde8c9772533204059e66ad4 Mon Sep 17 00:00:00 2001 From: Ignas Anikevicius <240938+aignas@users.noreply.github.com> Date: Sat, 6 Sep 2025 22:06:33 +0900 Subject: [PATCH 20/31] seam: add a build function --- python/private/pypi/extension.bzl | 19 ++++++++----------- python/private/pypi/hub_builder.bzl | 15 +++++++++++++++ 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/python/private/pypi/extension.bzl b/python/private/pypi/extension.bzl index 705063c34b..d30b8f215b 100644 --- a/python/private/pypi/extension.bzl +++ b/python/private/pypi/extension.bzl @@ -517,22 +517,19 @@ You cannot use both the additive_build_content and additive_build_content_file a ) for hub in pip_hub_map.values(): - hub_whl_map.setdefault(hub.name, {}) - for key, settings in hub.whl_map.items(): - for setting, repo in settings.items(): - hub_whl_map[hub.name].setdefault(key, {}).setdefault(repo, []).append(setting) - - whl_libraries.update(hub.whl_libraries) - for whl_name, lib in hub.whl_libraries.items(): - if whl_name in lib: + out = hub.build() + + for whl_name, lib in out.whl_libraries.items(): + if whl_name in whl_libraries: fail("'{}' already in created".format(whl_name)) else: # replicate whl_libraries.update(out.whl_libraries) whl_libraries[whl_name] = lib - hub_group_map[hub.name] = hub.group_map - extra_aliases[hub.name] = hub.extra_aliases - exposed_packages[hub.name] = hub.exposed_packages + hub_whl_map[hub.name] = out.whl_map + hub_group_map[hub.name] = out.group_map + extra_aliases[hub.name] = out.extra_aliases + exposed_packages[hub.name] = out.exposed_packages return struct( # We sort so that the lock-file remains the same no matter the order of how the diff --git a/python/private/pypi/hub_builder.bzl b/python/private/pypi/hub_builder.bzl index df050250d2..d3e26d850e 100644 --- a/python/private/pypi/hub_builder.bzl +++ b/python/private/pypi/hub_builder.bzl @@ -68,11 +68,26 @@ def hub_builder( python_version in self._get_index_urls, ), evaluate_markers = lambda *a, **k: _evaluate_markers(self, *a, **k), + build = lambda: _build(self), ) # buildifier: enable=uninitialized return self +def _build(self): + whl_map = {} + for key, settings in self.whl_map.items(): + for setting, repo in settings.items(): + whl_map.setdefault(key, {}).setdefault(repo, []).append(setting) + + return struct( + whl_map = whl_map, + group_map = self.group_map, + extra_aliases = self.extra_aliases, + exposed_packages = self.exposed_packages, + whl_libraries = self.whl_libraries, + ) + def _add(self, *, pip_attr): python_version = pip_attr.python_version if python_version in self.python_versions: From 5b39b2e11f8b4d5675bfd080d162c1e71090d4ea Mon Sep 17 00:00:00 2001 From: Ignas Anikevicius <240938+aignas@users.noreply.github.com> Date: Sat, 6 Sep 2025 22:17:57 +0900 Subject: [PATCH 21/31] move the create_whl_repos to the builder --- python/private/pypi/extension.bzl | 226 +--------------------------- python/private/pypi/hub_builder.bzl | 217 ++++++++++++++++++++++++++ 2 files changed, 219 insertions(+), 224 deletions(-) diff --git a/python/private/pypi/extension.bzl b/python/private/pypi/extension.bzl index d30b8f215b..839c36b4df 100644 --- a/python/private/pypi/extension.bzl +++ b/python/private/pypi/extension.bzl @@ -19,27 +19,16 @@ load("@pythons_hub//:interpreters.bzl", "INTERPRETER_LABELS") load("@pythons_hub//:versions.bzl", "MINOR_MAPPING") load("@rules_python_internal//:rules_python_config.bzl", rp_config = "config") load("//python/private:auth.bzl", "AUTH_ATTRS") -load("//python/private:full_version.bzl", "full_version") load("//python/private:normalize_name.bzl", "normalize_name") load("//python/private:repo_utils.bzl", "repo_utils") -load("//python/private:version.bzl", "version") -load(":attrs.bzl", "use_isolated") load(":evaluate_markers.bzl", EVALUATE_MARKERS_SRCS = "SRCS") load(":hub_builder.bzl", "hub_builder") load(":hub_repository.bzl", "hub_repository", "whl_config_settings_to_json") -load(":parse_requirements.bzl", "parse_requirements") load(":parse_whl_name.bzl", "parse_whl_name") load(":pep508_env.bzl", "env") load(":pip_repository_attrs.bzl", "ATTRS") -load(":requirements_files_by_platform.bzl", "requirements_files_by_platform") load(":simpleapi_download.bzl", "simpleapi_download") -load(":whl_config_setting.bzl", "whl_config_setting") load(":whl_library.bzl", "whl_library") -load(":whl_repo_name.bzl", "pypi_repo_name", "whl_repo_name") - -def _major_minor_version(version_str): - ver = version.parse(version_str) - return "{}.{}".format(ver.release[0], ver.release[1]) def _whl_mods_impl(whl_mods_dict): """Implementation of the pip.whl_mods tag class. @@ -66,216 +55,6 @@ def _whl_mods_impl(whl_mods_dict): whl_mods = whl_mods, ) -def _create_whl_repos( - module_ctx, - *, - pip_attr, - whl_overrides, - hub, - minor_mapping = MINOR_MAPPING): - """create all of the whl repositories - - Args: - module_ctx: {type}`module_ctx`. - pip_attr: {type}`struct` - the struct that comes from the tag class iteration. - whl_overrides: {type}`dict[str, struct]` - per-wheel overrides. - hub: TODO. - minor_mapping: {type}`dict[str, str]` The dictionary needed to resolve the full - python version used to parse package METADATA files. - """ - logger = repo_utils.logger(module_ctx, "pypi:create_whl_repos") - interpreter = hub.detect_interpreter(pip_attr) - - whl_modifications = {} - if pip_attr.whl_modifications != None: - for mod, whl_name in pip_attr.whl_modifications.items(): - whl_modifications[normalize_name(whl_name)] = mod - - platforms = hub.platforms(pip_attr.python_version) - requirements_by_platform = parse_requirements( - module_ctx, - requirements_by_platform = requirements_files_by_platform( - requirements_by_platform = pip_attr.requirements_by_platform, - requirements_linux = pip_attr.requirements_linux, - requirements_lock = pip_attr.requirements_lock, - requirements_osx = pip_attr.requirements_darwin, - requirements_windows = pip_attr.requirements_windows, - extra_pip_args = pip_attr.extra_pip_args, - platforms = sorted(platforms), # here we only need keys - python_version = full_version( - version = pip_attr.python_version, - minor_mapping = minor_mapping, - ), - logger = logger, - ), - platforms = platforms, - extra_pip_args = pip_attr.extra_pip_args, - get_index_urls = hub.get_index_urls(pip_attr.python_version), - evaluate_markers = hub.evaluate_markers(pip_attr), - logger = logger, - ) - - if pip_attr.experimental_requirement_cycles: - requirement_cycles = { - name: [normalize_name(whl_name) for whl_name in whls] - for name, whls in pip_attr.experimental_requirement_cycles.items() - } - - whl_group_mapping = { - whl_name: group_name - for group_name, group_whls in requirement_cycles.items() - for whl_name in group_whls - } - else: - whl_group_mapping = {} - requirement_cycles = {} - exposed_packages = {} - for whl in requirements_by_platform: - if whl.is_exposed: - exposed_packages[whl.name] = None - - group_name = whl_group_mapping.get(whl.name) - group_deps = requirement_cycles.get(group_name, []) - - # Construct args separately so that the lock file can be smaller and does not include unused - # attrs. - whl_library_args = dict( - dep_template = "@{}//{{name}}:{{target}}".format(hub.name), - ) - maybe_args = dict( - # The following values are safe to omit if they have false like values - add_libdir_to_library_search_path = pip_attr.add_libdir_to_library_search_path, - annotation = whl_modifications.get(whl.name), - download_only = pip_attr.download_only, - enable_implicit_namespace_pkgs = pip_attr.enable_implicit_namespace_pkgs, - environment = pip_attr.environment, - envsubst = pip_attr.envsubst, - group_deps = group_deps, - group_name = group_name, - pip_data_exclude = pip_attr.pip_data_exclude, - python_interpreter = interpreter.path, - python_interpreter_target = interpreter.target, - whl_patches = { - p: json.encode(args) - for p, args in whl_overrides.get(whl.name, {}).items() - }, - ) - if not hub.config.enable_pipstar: - maybe_args["experimental_target_platforms"] = pip_attr.experimental_target_platforms - - whl_library_args.update({k: v for k, v in maybe_args.items() if v}) - maybe_args_with_default = dict( - # The following values have defaults next to them - isolated = (use_isolated(module_ctx, pip_attr), True), - quiet = (pip_attr.quiet, True), - timeout = (pip_attr.timeout, 600), - ) - whl_library_args.update({ - k: v - for k, (v, default) in maybe_args_with_default.items() - if v != default - }) - - for src in whl.srcs: - repo = _whl_repo( - src = src, - whl_library_args = whl_library_args, - download_only = pip_attr.download_only, - netrc = hub.config.netrc or pip_attr.netrc, - use_downloader = hub.use_downloader(pip_attr.python_version, whl.name), - auth_patterns = hub.config.auth_patterns or pip_attr.auth_patterns, - python_version = _major_minor_version(pip_attr.python_version), - is_multiple_versions = whl.is_multiple_versions, - enable_pipstar = hub.config.enable_pipstar, - ) - hub.add_whl_library( - python_version = pip_attr.python_version, - whl = whl, - repo = repo, - ) - - if hub.exposed_packages: - intersection = {} - for pkg in exposed_packages: - if pkg not in hub.exposed_packages: - continue - intersection[pkg] = None - hub.exposed_packages.clear() - exposed_packages = intersection - - hub.exposed_packages.update(exposed_packages) - -def _whl_repo( - *, - src, - whl_library_args, - is_multiple_versions, - download_only, - netrc, - auth_patterns, - python_version, - use_downloader, - enable_pipstar = False): - args = dict(whl_library_args) - args["requirement"] = src.requirement_line - is_whl = src.filename.endswith(".whl") - - if src.extra_pip_args and not is_whl: - # pip is not used to download wheels and the python - # `whl_library` helpers are only extracting things, however - # for sdists, they will be built by `pip`, so we still - # need to pass the extra args there, so only pop this for whls - args["extra_pip_args"] = src.extra_pip_args - - if not src.url or (not is_whl and download_only): - if download_only and use_downloader: - # If the user did not allow using sdists and we are using the downloader - # and we are not using simpleapi_skip for this - return None - else: - # Fallback to a pip-installed wheel - target_platforms = src.target_platforms if is_multiple_versions else [] - return struct( - repo_name = pypi_repo_name( - normalize_name(src.distribution), - *target_platforms - ), - args = args, - config_setting = whl_config_setting( - version = python_version, - target_platforms = target_platforms or None, - ), - ) - - # This is no-op because pip is not used to download the wheel. - args.pop("download_only", None) - - if netrc: - args["netrc"] = netrc - if auth_patterns: - args["auth_patterns"] = auth_patterns - - args["urls"] = [src.url] - args["sha256"] = src.sha256 - args["filename"] = src.filename - if not enable_pipstar: - args["experimental_target_platforms"] = [ - # Get rid of the version for the target platforms because we are - # passing the interpreter any way. Ideally we should search of ways - # how to pass the target platforms through the hub repo. - p.partition("_")[2] - for p in src.target_platforms - ] - - return struct( - repo_name = whl_repo_name(src.filename, src.sha256), - args = args, - config_setting = whl_config_setting( - version = python_version, - target_platforms = src.target_platforms, - ), - ) - def _plat(*, name, arch_name, os_name, config_settings = [], env = {}, marker = "", whl_abi_tags = [], whl_platform_tags = []): # NOTE @aignas 2025-07-08: the least preferred is the first item in the list if "any" not in whl_platform_tags: @@ -482,6 +261,7 @@ You cannot use both the additive_build_content and additive_build_content_file a config = config, simpleapi_download_fn = simpleapi_download, simpleapi_cache = simpleapi_cache, + # TODO @aignas 2025-09-06: do not use kwargs minor_mapping = kwargs.get("minor_mapping", MINOR_MAPPING), evaluate_markers_fn = kwargs.get("evaluate_markers", None), available_interpreters = kwargs.get("available_interpreters", INTERPRETER_LABELS), @@ -508,12 +288,10 @@ You cannot use both the additive_build_content and additive_build_content_file a builder.add(pip_attr = pip_attr) # TODO @aignas 2025-05-19: express pip.parse as a series of configure calls - _create_whl_repos( + builder.create_whl_repos( module_ctx, - hub = builder, pip_attr = pip_attr, whl_overrides = whl_overrides, - minor_mapping = kwargs.get("minor_mapping", MINOR_MAPPING), ) for hub in pip_hub_map.values(): diff --git a/python/private/pypi/hub_builder.bzl b/python/private/pypi/hub_builder.bzl index d3e26d850e..4865501f9f 100644 --- a/python/private/pypi/hub_builder.bzl +++ b/python/private/pypi/hub_builder.bzl @@ -4,10 +4,19 @@ load("//python/private:full_version.bzl", "full_version") load("//python/private:normalize_name.bzl", "normalize_name") load("//python/private:version.bzl", "version") load("//python/private:version_label.bzl", "version_label") +load(":attrs.bzl", "use_isolated") load(":evaluate_markers.bzl", "evaluate_markers_py", evaluate_markers_star = "evaluate_markers") +load(":parse_requirements.bzl", "parse_requirements") load(":pep508_env.bzl", "env") load(":pep508_evaluate.bzl", "evaluate") load(":python_tag.bzl", "python_tag") +load(":requirements_files_by_platform.bzl", "requirements_files_by_platform") +load(":whl_config_setting.bzl", "whl_config_setting") +load(":whl_repo_name.bzl", "pypi_repo_name", "whl_repo_name") + +def _major_minor_version(version_str): + ver = version.parse(version_str) + return "{}.{}".format(ver.release[0], ver.release[1]) def hub_builder( *, @@ -69,6 +78,7 @@ def hub_builder( ), evaluate_markers = lambda *a, **k: _evaluate_markers(self, *a, **k), build = lambda: _build(self), + create_whl_repos = lambda *a, **k: _create_whl_repos(self, *a, **k), ) # buildifier: enable=uninitialized @@ -316,3 +326,210 @@ def _evaluate_markers(self, pip_attr): srcs = pip_attr._evaluate_markers_srcs, logger = self._logger, ) + +def _create_whl_repos( + self, + module_ctx, + *, + pip_attr, + whl_overrides): + """create all of the whl repositories + + Args: + self: the builder. + module_ctx: {type}`module_ctx`. + pip_attr: {type}`struct` - the struct that comes from the tag class iteration. + whl_overrides: {type}`dict[str, struct]` - per-wheel overrides. + """ + logger = self._logger + platforms = self.platforms(pip_attr.python_version) + requirements_by_platform = parse_requirements( + module_ctx, + requirements_by_platform = requirements_files_by_platform( + requirements_by_platform = pip_attr.requirements_by_platform, + requirements_linux = pip_attr.requirements_linux, + requirements_lock = pip_attr.requirements_lock, + requirements_osx = pip_attr.requirements_darwin, + requirements_windows = pip_attr.requirements_windows, + extra_pip_args = pip_attr.extra_pip_args, + platforms = sorted(platforms), # here we only need keys + python_version = full_version( + version = pip_attr.python_version, + minor_mapping = self._minor_mapping, + ), + logger = logger, + ), + platforms = platforms, + extra_pip_args = pip_attr.extra_pip_args, + get_index_urls = self.get_index_urls(pip_attr.python_version), + evaluate_markers = self.evaluate_markers(pip_attr), + logger = logger, + ) + + whl_modifications = {} + if pip_attr.whl_modifications != None: + for mod, whl_name in pip_attr.whl_modifications.items(): + whl_modifications[normalize_name(whl_name)] = mod + + if pip_attr.experimental_requirement_cycles: + requirement_cycles = { + name: [normalize_name(whl_name) for whl_name in whls] + for name, whls in pip_attr.experimental_requirement_cycles.items() + } + + whl_group_mapping = { + whl_name: group_name + for group_name, group_whls in requirement_cycles.items() + for whl_name in group_whls + } + else: + whl_group_mapping = {} + requirement_cycles = {} + + interpreter = self.detect_interpreter(pip_attr) + exposed_packages = {} + for whl in requirements_by_platform: + if whl.is_exposed: + exposed_packages[whl.name] = None + + group_name = whl_group_mapping.get(whl.name) + group_deps = requirement_cycles.get(group_name, []) + + # Construct args separately so that the lock file can be smaller and does not include unused + # attrs. + whl_library_args = dict( + dep_template = "@{}//{{name}}:{{target}}".format(self.name), + ) + maybe_args = dict( + # The following values are safe to omit if they have false like values + add_libdir_to_library_search_path = pip_attr.add_libdir_to_library_search_path, + annotation = whl_modifications.get(whl.name), + download_only = pip_attr.download_only, + enable_implicit_namespace_pkgs = pip_attr.enable_implicit_namespace_pkgs, + environment = pip_attr.environment, + envsubst = pip_attr.envsubst, + group_deps = group_deps, + group_name = group_name, + pip_data_exclude = pip_attr.pip_data_exclude, + python_interpreter = interpreter.path, + python_interpreter_target = interpreter.target, + whl_patches = { + p: json.encode(args) + for p, args in whl_overrides.get(whl.name, {}).items() + }, + ) + if not self.config.enable_pipstar: + maybe_args["experimental_target_platforms"] = pip_attr.experimental_target_platforms + + whl_library_args.update({k: v for k, v in maybe_args.items() if v}) + maybe_args_with_default = dict( + # The following values have defaults next to them + isolated = (use_isolated(module_ctx, pip_attr), True), + quiet = (pip_attr.quiet, True), + timeout = (pip_attr.timeout, 600), + ) + whl_library_args.update({ + k: v + for k, (v, default) in maybe_args_with_default.items() + if v != default + }) + + for src in whl.srcs: + repo = _whl_repo( + src = src, + whl_library_args = whl_library_args, + download_only = pip_attr.download_only, + netrc = self.config.netrc or pip_attr.netrc, + use_downloader = self.use_downloader(pip_attr.python_version, whl.name), + auth_patterns = self.config.auth_patterns or pip_attr.auth_patterns, + python_version = _major_minor_version(pip_attr.python_version), + is_multiple_versions = whl.is_multiple_versions, + enable_pipstar = self.config.enable_pipstar, + ) + self.add_whl_library( + python_version = pip_attr.python_version, + whl = whl, + repo = repo, + ) + + if self.exposed_packages: + intersection = {} + for pkg in exposed_packages: + if pkg not in self.exposed_packages: + continue + intersection[pkg] = None + self.exposed_packages.clear() + exposed_packages = intersection + + self.exposed_packages.update(exposed_packages) + +def _whl_repo( + *, + src, + whl_library_args, + is_multiple_versions, + download_only, + netrc, + auth_patterns, + python_version, + use_downloader, + enable_pipstar = False): + args = dict(whl_library_args) + args["requirement"] = src.requirement_line + is_whl = src.filename.endswith(".whl") + + if src.extra_pip_args and not is_whl: + # pip is not used to download wheels and the python + # `whl_library` helpers are only extracting things, however + # for sdists, they will be built by `pip`, so we still + # need to pass the extra args there, so only pop this for whls + args["extra_pip_args"] = src.extra_pip_args + + if not src.url or (not is_whl and download_only): + if download_only and use_downloader: + # If the user did not allow using sdists and we are using the downloader + # and we are not using simpleapi_skip for this + return None + else: + # Fallback to a pip-installed wheel + target_platforms = src.target_platforms if is_multiple_versions else [] + return struct( + repo_name = pypi_repo_name( + normalize_name(src.distribution), + *target_platforms + ), + args = args, + config_setting = whl_config_setting( + version = python_version, + target_platforms = target_platforms or None, + ), + ) + + # This is no-op because pip is not used to download the wheel. + args.pop("download_only", None) + + if netrc: + args["netrc"] = netrc + if auth_patterns: + args["auth_patterns"] = auth_patterns + + args["urls"] = [src.url] + args["sha256"] = src.sha256 + args["filename"] = src.filename + if not enable_pipstar: + args["experimental_target_platforms"] = [ + # Get rid of the version for the target platforms because we are + # passing the interpreter any way. Ideally we should search of ways + # how to pass the target platforms through the hub repo. + p.partition("_")[2] + for p in src.target_platforms + ] + + return struct( + repo_name = whl_repo_name(src.filename, src.sha256), + args = args, + config_setting = whl_config_setting( + version = python_version, + target_platforms = src.target_platforms, + ), + ) From 872d0c472e74459b5a743c79f1a8fd4bd20640d5 Mon Sep 17 00:00:00 2001 From: Ignas Anikevicius <240938+aignas@users.noreply.github.com> Date: Sat, 6 Sep 2025 22:20:35 +0900 Subject: [PATCH 22/31] create a pip_parse method in the builder --- python/private/pypi/extension.bzl | 5 +---- python/private/pypi/hub_builder.bzl | 10 ++++++++++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/python/private/pypi/extension.bzl b/python/private/pypi/extension.bzl index 839c36b4df..25dba86137 100644 --- a/python/private/pypi/extension.bzl +++ b/python/private/pypi/extension.bzl @@ -285,10 +285,7 @@ You cannot use both the additive_build_content and additive_build_content_file a else: builder = pip_hub_map[pip_attr.hub_name] - builder.add(pip_attr = pip_attr) - - # TODO @aignas 2025-05-19: express pip.parse as a series of configure calls - builder.create_whl_repos( + builder.pip_parse( module_ctx, pip_attr = pip_attr, whl_overrides = whl_overrides, diff --git a/python/private/pypi/hub_builder.bzl b/python/private/pypi/hub_builder.bzl index 4865501f9f..1f1dfea9a2 100644 --- a/python/private/pypi/hub_builder.bzl +++ b/python/private/pypi/hub_builder.bzl @@ -79,6 +79,7 @@ def hub_builder( evaluate_markers = lambda *a, **k: _evaluate_markers(self, *a, **k), build = lambda: _build(self), create_whl_repos = lambda *a, **k: _create_whl_repos(self, *a, **k), + pip_parse = lambda *a, **k: _pip_parse(self, *a, **k), ) # buildifier: enable=uninitialized @@ -533,3 +534,12 @@ def _whl_repo( target_platforms = src.target_platforms, ), ) + +def _pip_parse(self, module_ctx, pip_attr, whl_overrides): + self.add(pip_attr = pip_attr) + + self.create_whl_repos( + module_ctx, + pip_attr = pip_attr, + whl_overrides = whl_overrides, + ) From 2bd0b42cf7da2d02015ba4c57a985d667eaa4611 Mon Sep 17 00:00:00 2001 From: Ignas Anikevicius <240938+aignas@users.noreply.github.com> Date: Sat, 6 Sep 2025 22:33:13 +0900 Subject: [PATCH 23/31] simplify the code base a little --- python/private/pypi/extension.bzl | 66 +++++++++-------------------- python/private/pypi/hub_builder.bzl | 7 ++- 2 files changed, 24 insertions(+), 49 deletions(-) diff --git a/python/private/pypi/extension.bzl b/python/private/pypi/extension.bzl index 25dba86137..66dfc21062 100644 --- a/python/private/pypi/extension.bzl +++ b/python/private/pypi/extension.bzl @@ -168,7 +168,7 @@ def parse_modules( enable_pipstar: {type}`bool` a flag to enable dropping Python dependency for evaluation of the extension. _fail: {type}`function` the failure function, mainly for testing. - **kwargs: Extra arguments passed to the layers below. + **kwargs: Extra arguments passed to the hub_builder. Returns: A struct with the following attributes: @@ -242,15 +242,6 @@ You cannot use both the additive_build_content and additive_build_content_file a pip_hub_map = {} simpleapi_cache = {} - # Keeps track of all the hub's whl repos across the different versions. - # dict[hub, dict[whl, dict[version, str pip]]] - # Where hub, whl, and pip are the repo names - hub_whl_map = {} - hub_group_map = {} - exposed_packages = {} - extra_aliases = {} - whl_libraries = {} - for mod in module_ctx.modules: for pip_attr in mod.tags.parse: hub_name = pip_attr.hub_name @@ -291,6 +282,14 @@ You cannot use both the additive_build_content and additive_build_content_file a whl_overrides = whl_overrides, ) + # Keeps track of all the hub's whl repos across the different versions. + # dict[hub, dict[whl, dict[version, str pip]]] + # Where hub, whl, and pip are the repo names + hub_whl_map = {} + hub_group_map = {} + exposed_packages = {} + extra_aliases = {} + whl_libraries = {} for hub in pip_hub_map.values(): out = hub.build() @@ -298,43 +297,21 @@ You cannot use both the additive_build_content and additive_build_content_file a if whl_name in whl_libraries: fail("'{}' already in created".format(whl_name)) else: - # replicate whl_libraries.update(out.whl_libraries) whl_libraries[whl_name] = lib - hub_whl_map[hub.name] = out.whl_map - hub_group_map[hub.name] = out.group_map - extra_aliases[hub.name] = out.extra_aliases exposed_packages[hub.name] = out.exposed_packages + extra_aliases[hub.name] = out.extra_aliases + hub_group_map[hub.name] = out.group_map + hub_whl_map[hub.name] = out.whl_map return struct( - # We sort so that the lock-file remains the same no matter the order of how the - # args are manipulated in the code going before. - whl_mods = dict(sorted(whl_mods.items())), - hub_whl_map = { - hub_name: { - whl_name: dict(settings) - for whl_name, settings in sorted(whl_map.items()) - } - for hub_name, whl_map in sorted(hub_whl_map.items()) - }, - hub_group_map = { - hub_name: { - key: sorted(values) - for key, values in sorted(group_map.items()) - } - for hub_name, group_map in sorted(hub_group_map.items()) - }, - exposed_packages = { - k: sorted(v) - for k, v in sorted(exposed_packages.items()) - }, - extra_aliases = { - hub_name: { - whl_name: sorted(aliases) - for whl_name, aliases in extra_whl_aliases.items() - } - for hub_name, extra_whl_aliases in extra_aliases.items() - }, + config = config, + exposed_packages = exposed_packages, + extra_aliases = extra_aliases, + hub_group_map = hub_group_map, + hub_whl_map = hub_whl_map, + whl_libraries = whl_libraries, + whl_mods = whl_mods, platform_config_settings = { hub_name: { platform_name: sorted([str(Label(cv)) for cv in p.config_settings]) @@ -342,11 +319,6 @@ You cannot use both the additive_build_content and additive_build_content_file a } for hub_name in hub_whl_map }, - whl_libraries = { - k: dict(sorted(args.items())) - for k, args in sorted(whl_libraries.items()) - }, - config = config, ) def _pip_impl(module_ctx): diff --git a/python/private/pypi/hub_builder.bzl b/python/private/pypi/hub_builder.bzl index 1f1dfea9a2..ae753fd90f 100644 --- a/python/private/pypi/hub_builder.bzl +++ b/python/private/pypi/hub_builder.bzl @@ -94,8 +94,11 @@ def _build(self): return struct( whl_map = whl_map, group_map = self.group_map, - extra_aliases = self.extra_aliases, - exposed_packages = self.exposed_packages, + extra_aliases = { + whl: sorted(aliases) + for whl, aliases in self.extra_aliases.items() + }, + exposed_packages = sorted(self.exposed_packages), whl_libraries = self.whl_libraries, ) From 462868869c51483fb98b79024085173a6839fb7d Mon Sep 17 00:00:00 2001 From: Ignas Anikevicius <240938+aignas@users.noreply.github.com> Date: Sat, 6 Sep 2025 22:44:47 +0900 Subject: [PATCH 24/31] eliminate some unnecessary methods --- python/private/pypi/hub_builder.bzl | 94 +++++++++++++---------------- 1 file changed, 42 insertions(+), 52 deletions(-) diff --git a/python/private/pypi/hub_builder.bzl b/python/private/pypi/hub_builder.bzl index ae753fd90f..6fa4c2c59d 100644 --- a/python/private/pypi/hub_builder.bzl +++ b/python/private/pypi/hub_builder.bzl @@ -50,7 +50,7 @@ def hub_builder( self = struct( name = name, module_name = module_name, - python_versions = {}, + _platforms = {}, config = config, whl_map = {}, exposed_packages = {}, @@ -64,21 +64,14 @@ def hub_builder( _simpleapi_download_fn = simpleapi_download_fn, _simpleapi_cache = simpleapi_cache, _get_index_urls = {}, - _pip_attrs = {}, _use_downloader = {}, # keep sorted - add = lambda *a, **k: _add(self, *a, **k), - detect_interpreter = lambda *a, **k: _detect_interpreter(self, *a, **k), get_index_urls = lambda version: self._get_index_urls.get(version), - platforms = lambda version: self.python_versions[version], - add_whl_library = lambda *a, **k: _add_whl_library(self, *a, **k), use_downloader = lambda python_version, whl_name: self._use_downloader.get(python_version, {}).get( normalize_name(whl_name), - python_version in self._get_index_urls, + self.get_index_urls(python_version) != None, ), - evaluate_markers = lambda *a, **k: _evaluate_markers(self, *a, **k), build = lambda: _build(self), - create_whl_repos = lambda *a, **k: _create_whl_repos(self, *a, **k), pip_parse = lambda *a, **k: _pip_parse(self, *a, **k), ) @@ -102,39 +95,6 @@ def _build(self): whl_libraries = self.whl_libraries, ) -def _add(self, *, pip_attr): - python_version = pip_attr.python_version - if python_version in self.python_versions: - fail(( - "Duplicate pip python version '{version}' for hub " + - "'{hub}' in module '{module}': the Python versions " + - "used for a hub must be unique" - ).format( - hub = self.name, - module = self.module_name, - version = python_version, - )) - - self.python_versions[python_version] = _platforms( - python_version = python_version, - minor_mapping = self._minor_mapping, - config = self.config, - ) - _set_index_urls(self, pip_attr) - self._pip_attrs[python_version] = pip_attr - - # TODO @aignas 2024-04-05: how do we support different requirement - # cycles for different abis/oses? For now we will need the users to - # assume the same groups across all versions/platforms until we start - # using an alternative cycle resolution strategy. - self.group_map.clear() - self.group_map.update(pip_attr.experimental_requirement_cycles) - - for whl_name, aliases in pip_attr.extra_hub_aliases.items(): - self.extra_aliases.setdefault(whl_name, {}).update( - {alias: True for alias in aliases}, - ) - def _set_index_urls(self, pip_attr): if not pip_attr.experimental_index_url: if pip_attr.experimental_extra_index_urls: @@ -254,7 +214,7 @@ def _add_whl_library(self, *, python_version, whl, repo): # disallow building from sdist. return - platforms = self.python_versions[python_version] + platforms = self._platforms[python_version] # TODO @aignas 2025-06-29: we should not need the version in the repo_name if # we are using pipstar and we are downloading the wheel using the downloader @@ -297,10 +257,10 @@ def _evaluate_markers(self, pip_attr): if self.config.enable_pipstar: return lambda _, requirements: evaluate_markers_star( requirements = requirements, - platforms = self.python_versions[pip_attr.python_version], + platforms = self._platforms[pip_attr.python_version], ) - interpreter = self.detect_interpreter(pip_attr) + interpreter = _detect_interpreter(self, pip_attr) # NOTE @aignas 2024-08-02: , we will execute any interpreter that we find either # in the PATH or if specified as a label. We will configure the env @@ -320,7 +280,7 @@ def _evaluate_markers(self, pip_attr): module_ctx, requirements = { k: { - p: self.python_versions[pip_attr.python_version][p].triple + p: self._platforms[pip_attr.python_version][p].triple for p in plats } for k, plats in requirements.items() @@ -346,7 +306,7 @@ def _create_whl_repos( whl_overrides: {type}`dict[str, struct]` - per-wheel overrides. """ logger = self._logger - platforms = self.platforms(pip_attr.python_version) + platforms = self._platforms[pip_attr.python_version] requirements_by_platform = parse_requirements( module_ctx, requirements_by_platform = requirements_files_by_platform( @@ -366,7 +326,7 @@ def _create_whl_repos( platforms = platforms, extra_pip_args = pip_attr.extra_pip_args, get_index_urls = self.get_index_urls(pip_attr.python_version), - evaluate_markers = self.evaluate_markers(pip_attr), + evaluate_markers = _evaluate_markers(self, pip_attr), logger = logger, ) @@ -390,7 +350,7 @@ def _create_whl_repos( whl_group_mapping = {} requirement_cycles = {} - interpreter = self.detect_interpreter(pip_attr) + interpreter = _detect_interpreter(self, pip_attr) exposed_packages = {} for whl in requirements_by_platform: if whl.is_exposed: @@ -450,7 +410,8 @@ def _create_whl_repos( is_multiple_versions = whl.is_multiple_versions, enable_pipstar = self.config.enable_pipstar, ) - self.add_whl_library( + _add_whl_library( + self, python_version = pip_attr.python_version, whl = whl, repo = repo, @@ -539,9 +500,38 @@ def _whl_repo( ) def _pip_parse(self, module_ctx, pip_attr, whl_overrides): - self.add(pip_attr = pip_attr) + python_version = pip_attr.python_version + if python_version in self._platforms: + fail(( + "Duplicate pip python version '{version}' for hub " + + "'{hub}' in module '{module}': the Python versions " + + "used for a hub must be unique" + ).format( + hub = self.name, + module = self.module_name, + version = python_version, + )) + + self._platforms[python_version] = _platforms( + python_version = python_version, + minor_mapping = self._minor_mapping, + config = self.config, + ) + _set_index_urls(self, pip_attr) + + # TODO @aignas 2024-04-05: how do we support different requirement + # cycles for different abis/oses? For now we will need the users to + # assume the same groups across all versions/platforms until we start + # using an alternative cycle resolution strategy. + self.group_map.clear() + self.group_map.update(pip_attr.experimental_requirement_cycles) - self.create_whl_repos( + for whl_name, aliases in pip_attr.extra_hub_aliases.items(): + self.extra_aliases.setdefault(whl_name, {}).update( + {alias: True for alias in aliases}, + ) + _create_whl_repos( + self, module_ctx, pip_attr = pip_attr, whl_overrides = whl_overrides, From c283aa3c59979030729318a851435aa471fea3cd Mon Sep 17 00:00:00 2001 From: Ignas Anikevicius <240938+aignas@users.noreply.github.com> Date: Sat, 6 Sep 2025 22:56:59 +0900 Subject: [PATCH 25/31] documnet methods on the builder --- python/private/pypi/hub_builder.bzl | 117 +++++++++++++++------------- 1 file changed, 65 insertions(+), 52 deletions(-) diff --git a/python/private/pypi/hub_builder.bzl b/python/private/pypi/hub_builder.bzl index 6fa4c2c59d..d489ecaf0a 100644 --- a/python/private/pypi/hub_builder.bzl +++ b/python/private/pypi/hub_builder.bzl @@ -50,49 +50,55 @@ def hub_builder( self = struct( name = name, module_name = module_name, + + # public methods, keep sorted and to minimum + build = lambda: _build(self), + pip_parse = lambda *a, **k: _pip_parse(self, *a, **k), + + # build output + _exposed_packages = {}, # modified by _add_exposed_packages + _extra_aliases = {}, # modified by _add_extra_aliases + _group_map = {}, # modified by _add_group_map + _whl_libraries = {}, # modified by _add_whl_library + _whl_map = {}, # modified by _add_whl_library + # internal _platforms = {}, - config = config, - whl_map = {}, - exposed_packages = {}, - extra_aliases = {}, - whl_libraries = {}, - group_map = {}, + _get_index_urls = {}, + _use_downloader = {}, + _simpleapi_cache = simpleapi_cache, + # instance constants + _config = config, _evaluate_markers_fn = evaluate_markers_fn, _logger = logger, _minor_mapping = minor_mapping, _available_interpreters = available_interpreters, _simpleapi_download_fn = simpleapi_download_fn, - _simpleapi_cache = simpleapi_cache, - _get_index_urls = {}, - _use_downloader = {}, - # keep sorted - get_index_urls = lambda version: self._get_index_urls.get(version), - use_downloader = lambda python_version, whl_name: self._use_downloader.get(python_version, {}).get( - normalize_name(whl_name), - self.get_index_urls(python_version) != None, - ), - build = lambda: _build(self), - pip_parse = lambda *a, **k: _pip_parse(self, *a, **k), ) # buildifier: enable=uninitialized return self +def _use_downloader(self, python_version, whl_name): + return self._use_downloader.get(python_version, {}).get( + normalize_name(whl_name), + self._get_index_urls.get(python_version) != None, + ) + def _build(self): whl_map = {} - for key, settings in self.whl_map.items(): + for key, settings in self._whl_map.items(): for setting, repo in settings.items(): whl_map.setdefault(key, {}).setdefault(repo, []).append(setting) return struct( whl_map = whl_map, - group_map = self.group_map, + group_map = self._group_map, extra_aliases = { whl: sorted(aliases) - for whl, aliases in self.extra_aliases.items() + for whl, aliases in self._extra_aliases.items() }, - exposed_packages = sorted(self.exposed_packages), - whl_libraries = self.whl_libraries, + exposed_packages = sorted(self._exposed_packages), + whl_libraries = self._whl_libraries, ) def _set_index_urls(self, pip_attr): @@ -126,7 +132,7 @@ def _set_index_urls(self, pip_attr): sources = [ d for d in distributions - if self.use_downloader(python_version, d) + if _use_downloader(self, python_version, d) ], envsubst = pip_attr.envsubst, # Auth related info @@ -220,15 +226,15 @@ def _add_whl_library(self, *, python_version, whl, repo): # we are using pipstar and we are downloading the wheel using the downloader repo_name = "{}_{}_{}".format(self.name, version_label(python_version), repo.repo_name) - if repo_name in self.whl_libraries: + if repo_name in self._whl_libraries: fail("attempting to create a duplicate library {} for {}".format( repo_name, whl.name, )) - self.whl_libraries[repo_name] = repo.args + self._whl_libraries[repo_name] = repo.args - if not self.config.enable_pipstar and "experimental_target_platforms" in repo.args: - self.whl_libraries[repo_name] |= { + if not self._config.enable_pipstar and "experimental_target_platforms" in repo.args: + self._whl_libraries[repo_name] |= { "experimental_target_platforms": sorted({ # TODO @aignas 2025-07-07: this should be solved in a better way platforms[candidate].triple.partition("_")[-1]: None @@ -238,7 +244,7 @@ def _add_whl_library(self, *, python_version, whl, repo): }), } - mapping = self.whl_map.setdefault(whl.name, {}) + mapping = self._whl_map.setdefault(whl.name, {}) if repo.config_setting in mapping and mapping[repo.config_setting] != repo_name: fail( "attempting to override an existing repo '{}' for config setting '{}' with a new repo '{}'".format( @@ -254,7 +260,7 @@ def _evaluate_markers(self, pip_attr): if self._evaluate_markers_fn: return self._evaluate_markers_fn - if self.config.enable_pipstar: + if self._config.enable_pipstar: return lambda _, requirements: evaluate_markers_star( requirements = requirements, platforms = self._platforms[pip_attr.python_version], @@ -325,7 +331,7 @@ def _create_whl_repos( ), platforms = platforms, extra_pip_args = pip_attr.extra_pip_args, - get_index_urls = self.get_index_urls(pip_attr.python_version), + get_index_urls = self._get_index_urls.get(pip_attr.python_version), evaluate_markers = _evaluate_markers(self, pip_attr), logger = logger, ) @@ -382,7 +388,7 @@ def _create_whl_repos( for p, args in whl_overrides.get(whl.name, {}).items() }, ) - if not self.config.enable_pipstar: + if not self._config.enable_pipstar: maybe_args["experimental_target_platforms"] = pip_attr.experimental_target_platforms whl_library_args.update({k: v for k, v in maybe_args.items() if v}) @@ -403,12 +409,12 @@ def _create_whl_repos( src = src, whl_library_args = whl_library_args, download_only = pip_attr.download_only, - netrc = self.config.netrc or pip_attr.netrc, - use_downloader = self.use_downloader(pip_attr.python_version, whl.name), - auth_patterns = self.config.auth_patterns or pip_attr.auth_patterns, + netrc = self._config.netrc or pip_attr.netrc, + use_downloader = _use_downloader(self, pip_attr.python_version, whl.name), + auth_patterns = self._config.auth_patterns or pip_attr.auth_patterns, python_version = _major_minor_version(pip_attr.python_version), is_multiple_versions = whl.is_multiple_versions, - enable_pipstar = self.config.enable_pipstar, + enable_pipstar = self._config.enable_pipstar, ) _add_whl_library( self, @@ -417,16 +423,19 @@ def _create_whl_repos( repo = repo, ) - if self.exposed_packages: + _add_exposed_packages(self, exposed_packages) + +def _add_exposed_packages(self, exposed_packages): + if self._exposed_packages: intersection = {} for pkg in exposed_packages: - if pkg not in self.exposed_packages: + if pkg not in self._exposed_packages: continue intersection[pkg] = None - self.exposed_packages.clear() + self._exposed_packages.clear() exposed_packages = intersection - self.exposed_packages.update(exposed_packages) + self._exposed_packages.update(exposed_packages) def _whl_repo( *, @@ -499,6 +508,20 @@ def _whl_repo( ), ) +def _add_group_map(self, group_map): + # TODO @aignas 2024-04-05: how do we support different requirement + # cycles for different abis/oses? For now we will need the users to + # assume the same groups across all versions/platforms until we start + # using an alternative cycle resolution strategy. + self._group_map.clear() + self._group_map.update(group_map) + +def _add_extra_aliases(self, extra_hub_aliases): + for whl_name, aliases in extra_hub_aliases.items(): + self._extra_aliases.setdefault(whl_name, {}).update( + {alias: True for alias in aliases}, + ) + def _pip_parse(self, module_ctx, pip_attr, whl_overrides): python_version = pip_attr.python_version if python_version in self._platforms: @@ -515,21 +538,11 @@ def _pip_parse(self, module_ctx, pip_attr, whl_overrides): self._platforms[python_version] = _platforms( python_version = python_version, minor_mapping = self._minor_mapping, - config = self.config, + config = self._config, ) _set_index_urls(self, pip_attr) - - # TODO @aignas 2024-04-05: how do we support different requirement - # cycles for different abis/oses? For now we will need the users to - # assume the same groups across all versions/platforms until we start - # using an alternative cycle resolution strategy. - self.group_map.clear() - self.group_map.update(pip_attr.experimental_requirement_cycles) - - for whl_name, aliases in pip_attr.extra_hub_aliases.items(): - self.extra_aliases.setdefault(whl_name, {}).update( - {alias: True for alias in aliases}, - ) + _add_group_map(self, pip_attr.experimental_requirement_cycles) + _add_extra_aliases(self, pip_attr.extra_hub_aliases) _create_whl_repos( self, module_ctx, From 5764570db577704e5baf8da63cd413ac24b1c7c9 Mon Sep 17 00:00:00 2001 From: Ignas Anikevicius <240938+aignas@users.noreply.github.com> Date: Sat, 6 Sep 2025 22:58:56 +0900 Subject: [PATCH 26/31] documnet args on the builder --- python/private/pypi/hub_builder.bzl | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/python/private/pypi/hub_builder.bzl b/python/private/pypi/hub_builder.bzl index d489ecaf0a..43e7bce3e0 100644 --- a/python/private/pypi/hub_builder.bzl +++ b/python/private/pypi/hub_builder.bzl @@ -32,18 +32,18 @@ def hub_builder( """Return a hub builder instance Args: - name: TODO - module_name: TODO + name: {type}`str`, the name of the hub. + module_name: {type}`str`, the module name that has created the hub. config: The platform configuration. - minor_mapping: TODO - evaluate_markers_fn: the function used to evaluate the markers. + minor_mapping: {type}`dict[str, str]` the mapping between minor and full versions. + evaluate_markers_fn: the override function used to evaluate the markers. available_interpreters: {type}`dict[str, Label]` The dictionary of available interpreters that have been registered using the `python` bzlmod extension. The keys are in the form `python_{snake_case_version}_host`. This is to be used during the `repository_rule` and must be always compatible with the host. - simpleapi_download_fn: TODO - simpleapi_cache: TODO - logger: TODO + simpleapi_download_fn: the function used to download from SimpleAPI. + simpleapi_cache: the cache for the download results. + logger: the logger for this builder. """ # buildifier: disable=uninitialized From 46d65b9ba6a497a4e53909409a83a1c4e1a26e20 Mon Sep 17 00:00:00 2001 From: Ignas Anikevicius <240938+aignas@users.noreply.github.com> Date: Sat, 6 Sep 2025 23:05:35 +0900 Subject: [PATCH 27/31] fix bzl_library deps --- python/private/pypi/BUILD.bazel | 15 +- python/private/pypi/hub_builder.bzl | 207 ++++++++++++++-------------- 2 files changed, 115 insertions(+), 107 deletions(-) diff --git a/python/private/pypi/BUILD.bazel b/python/private/pypi/BUILD.bazel index ea41a3cf24..fd850857e9 100644 --- a/python/private/pypi/BUILD.bazel +++ b/python/private/pypi/BUILD.bazel @@ -109,22 +109,17 @@ bzl_library( name = "extension_bzl", srcs = ["extension.bzl"], deps = [ - ":attrs_bzl", ":evaluate_markers_bzl", ":hub_builder_bzl", ":hub_repository_bzl", - ":parse_requirements_bzl", ":parse_whl_name_bzl", ":pep508_env_bzl", ":pip_repository_attrs_bzl", ":simpleapi_download_bzl", - ":whl_config_setting_bzl", ":whl_library_bzl", - ":whl_repo_name_bzl", - "//python/private:full_version_bzl", + "//python/private:auth_bzl", "//python/private:normalize_name_bzl", - "//python/private:version_bzl", - "//python/private:version_label_bzl", + "//python/private:repo_utils_bzl", "@bazel_features//:features", "@pythons_hub//:interpreters_bzl", "@pythons_hub//:versions_bzl", @@ -172,13 +167,19 @@ bzl_library( srcs = ["hub_builder.bzl"], visibility = ["//:__subpackages__"], deps = [ + ":attrs_bzl", ":evaluate_markers_bzl", + ":parse_requirements_bzl", ":pep508_env_bzl", ":pep508_evaluate_bzl", ":python_tag_bzl", + ":requirements_files_by_platform_bzl", + ":whl_config_setting_bzl", + ":whl_repo_name_bzl", "//python/private:full_version_bzl", "//python/private:normalize_name_bzl", "//python/private:version_bzl", + "//python/private:version_label_bzl", ], ) diff --git a/python/private/pypi/hub_builder.bzl b/python/private/pypi/hub_builder.bzl index 43e7bce3e0..32eeba08b2 100644 --- a/python/private/pypi/hub_builder.bzl +++ b/python/private/pypi/hub_builder.bzl @@ -78,11 +78,7 @@ def hub_builder( # buildifier: enable=uninitialized return self -def _use_downloader(self, python_version, whl_name): - return self._use_downloader.get(python_version, {}).get( - normalize_name(whl_name), - self._get_index_urls.get(python_version) != None, - ) +### PUBLIC methods def _build(self): whl_map = {} @@ -101,6 +97,108 @@ def _build(self): whl_libraries = self._whl_libraries, ) +def _pip_parse(self, module_ctx, pip_attr, whl_overrides): + python_version = pip_attr.python_version + if python_version in self._platforms: + fail(( + "Duplicate pip python version '{version}' for hub " + + "'{hub}' in module '{module}': the Python versions " + + "used for a hub must be unique" + ).format( + hub = self.name, + module = self.module_name, + version = python_version, + )) + + self._platforms[python_version] = _platforms( + python_version = python_version, + minor_mapping = self._minor_mapping, + config = self._config, + ) + _set_index_urls(self, pip_attr) + _add_group_map(self, pip_attr.experimental_requirement_cycles) + _add_extra_aliases(self, pip_attr.extra_hub_aliases) + _create_whl_repos( + self, + module_ctx, + pip_attr = pip_attr, + whl_overrides = whl_overrides, + ) + +### end of PUBLIC methods +### setters for build outputs + +def _add_exposed_packages(self, exposed_packages): + if self._exposed_packages: + intersection = {} + for pkg in exposed_packages: + if pkg not in self._exposed_packages: + continue + intersection[pkg] = None + self._exposed_packages.clear() + exposed_packages = intersection + + self._exposed_packages.update(exposed_packages) + +def _add_group_map(self, group_map): + # TODO @aignas 2024-04-05: how do we support different requirement + # cycles for different abis/oses? For now we will need the users to + # assume the same groups across all versions/platforms until we start + # using an alternative cycle resolution strategy. + self._group_map.clear() + self._group_map.update(group_map) + +def _add_extra_aliases(self, extra_hub_aliases): + for whl_name, aliases in extra_hub_aliases.items(): + self._extra_aliases.setdefault(whl_name, {}).update( + {alias: True for alias in aliases}, + ) + +def _add_whl_library(self, *, python_version, whl, repo): + if repo == None: + # NOTE @aignas 2025-07-07: we guard against an edge-case where there + # are more platforms defined than there are wheels for and users + # disallow building from sdist. + return + + platforms = self._platforms[python_version] + + # TODO @aignas 2025-06-29: we should not need the version in the repo_name if + # we are using pipstar and we are downloading the wheel using the downloader + repo_name = "{}_{}_{}".format(self.name, version_label(python_version), repo.repo_name) + + if repo_name in self._whl_libraries: + fail("attempting to create a duplicate library {} for {}".format( + repo_name, + whl.name, + )) + self._whl_libraries[repo_name] = repo.args + + if not self._config.enable_pipstar and "experimental_target_platforms" in repo.args: + self._whl_libraries[repo_name] |= { + "experimental_target_platforms": sorted({ + # TODO @aignas 2025-07-07: this should be solved in a better way + platforms[candidate].triple.partition("_")[-1]: None + for p in repo.args["experimental_target_platforms"] + for candidate in platforms + if candidate.endswith(p) + }), + } + + mapping = self._whl_map.setdefault(whl.name, {}) + if repo.config_setting in mapping and mapping[repo.config_setting] != repo_name: + fail( + "attempting to override an existing repo '{}' for config setting '{}' with a new repo '{}'".format( + mapping[repo.config_setting], + repo.config_setting, + repo_name, + ), + ) + else: + mapping[repo.config_setting] = repo_name + +### end of setters, below we have various functions to implement the public methods + def _set_index_urls(self, pip_attr): if not pip_attr.experimental_index_url: if pip_attr.experimental_extra_index_urls: @@ -213,49 +311,6 @@ def _platforms(*, python_version, minor_mapping, config): ) return platforms -def _add_whl_library(self, *, python_version, whl, repo): - if repo == None: - # NOTE @aignas 2025-07-07: we guard against an edge-case where there - # are more platforms defined than there are wheels for and users - # disallow building from sdist. - return - - platforms = self._platforms[python_version] - - # TODO @aignas 2025-06-29: we should not need the version in the repo_name if - # we are using pipstar and we are downloading the wheel using the downloader - repo_name = "{}_{}_{}".format(self.name, version_label(python_version), repo.repo_name) - - if repo_name in self._whl_libraries: - fail("attempting to create a duplicate library {} for {}".format( - repo_name, - whl.name, - )) - self._whl_libraries[repo_name] = repo.args - - if not self._config.enable_pipstar and "experimental_target_platforms" in repo.args: - self._whl_libraries[repo_name] |= { - "experimental_target_platforms": sorted({ - # TODO @aignas 2025-07-07: this should be solved in a better way - platforms[candidate].triple.partition("_")[-1]: None - for p in repo.args["experimental_target_platforms"] - for candidate in platforms - if candidate.endswith(p) - }), - } - - mapping = self._whl_map.setdefault(whl.name, {}) - if repo.config_setting in mapping and mapping[repo.config_setting] != repo_name: - fail( - "attempting to override an existing repo '{}' for config setting '{}' with a new repo '{}'".format( - mapping[repo.config_setting], - repo.config_setting, - repo_name, - ), - ) - else: - mapping[repo.config_setting] = repo_name - def _evaluate_markers(self, pip_attr): if self._evaluate_markers_fn: return self._evaluate_markers_fn @@ -425,18 +480,6 @@ def _create_whl_repos( _add_exposed_packages(self, exposed_packages) -def _add_exposed_packages(self, exposed_packages): - if self._exposed_packages: - intersection = {} - for pkg in exposed_packages: - if pkg not in self._exposed_packages: - continue - intersection[pkg] = None - self._exposed_packages.clear() - exposed_packages = intersection - - self._exposed_packages.update(exposed_packages) - def _whl_repo( *, src, @@ -508,44 +551,8 @@ def _whl_repo( ), ) -def _add_group_map(self, group_map): - # TODO @aignas 2024-04-05: how do we support different requirement - # cycles for different abis/oses? For now we will need the users to - # assume the same groups across all versions/platforms until we start - # using an alternative cycle resolution strategy. - self._group_map.clear() - self._group_map.update(group_map) - -def _add_extra_aliases(self, extra_hub_aliases): - for whl_name, aliases in extra_hub_aliases.items(): - self._extra_aliases.setdefault(whl_name, {}).update( - {alias: True for alias in aliases}, - ) - -def _pip_parse(self, module_ctx, pip_attr, whl_overrides): - python_version = pip_attr.python_version - if python_version in self._platforms: - fail(( - "Duplicate pip python version '{version}' for hub " + - "'{hub}' in module '{module}': the Python versions " + - "used for a hub must be unique" - ).format( - hub = self.name, - module = self.module_name, - version = python_version, - )) - - self._platforms[python_version] = _platforms( - python_version = python_version, - minor_mapping = self._minor_mapping, - config = self._config, - ) - _set_index_urls(self, pip_attr) - _add_group_map(self, pip_attr.experimental_requirement_cycles) - _add_extra_aliases(self, pip_attr.extra_hub_aliases) - _create_whl_repos( - self, - module_ctx, - pip_attr = pip_attr, - whl_overrides = whl_overrides, +def _use_downloader(self, python_version, whl_name): + return self._use_downloader.get(python_version, {}).get( + normalize_name(whl_name), + self._get_index_urls.get(python_version) != None, ) From c69f1a13e421b00876ab74c98e1f973385279ae1 Mon Sep 17 00:00:00 2001 From: Ignas Anikevicius <240938+aignas@users.noreply.github.com> Date: Sun, 7 Sep 2025 00:20:25 +0900 Subject: [PATCH 28/31] refactor: clarify name --- python/private/pypi/hub_builder.bzl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/private/pypi/hub_builder.bzl b/python/private/pypi/hub_builder.bzl index 32eeba08b2..85d172bf47 100644 --- a/python/private/pypi/hub_builder.bzl +++ b/python/private/pypi/hub_builder.bzl @@ -115,7 +115,7 @@ def _pip_parse(self, module_ctx, pip_attr, whl_overrides): minor_mapping = self._minor_mapping, config = self._config, ) - _set_index_urls(self, pip_attr) + _set_get_index_urls(self, pip_attr) _add_group_map(self, pip_attr.experimental_requirement_cycles) _add_extra_aliases(self, pip_attr.extra_hub_aliases) _create_whl_repos( @@ -199,7 +199,7 @@ def _add_whl_library(self, *, python_version, whl, repo): ### end of setters, below we have various functions to implement the public methods -def _set_index_urls(self, pip_attr): +def _set_get_index_urls(self, pip_attr): if not pip_attr.experimental_index_url: if pip_attr.experimental_extra_index_urls: fail("'experimental_extra_index_urls' is a no-op unless 'experimental_index_url' is set") From c06b99c68e8c9563ee4ebbfa2ec9749c78808c21 Mon Sep 17 00:00:00 2001 From: Ignas Anikevicius <240938+aignas@users.noreply.github.com> Date: Sun, 7 Sep 2025 00:28:18 +0900 Subject: [PATCH 29/31] move the construction of the requirement cycles --- python/private/pypi/hub_builder.bzl | 31 +++++++++++++---------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/python/private/pypi/hub_builder.bzl b/python/private/pypi/hub_builder.bzl index 85d172bf47..90005f9eb5 100644 --- a/python/private/pypi/hub_builder.bzl +++ b/python/private/pypi/hub_builder.bzl @@ -63,6 +63,7 @@ def hub_builder( _whl_map = {}, # modified by _add_whl_library # internal _platforms = {}, + _group_name_by_whl = {}, _get_index_urls = {}, _use_downloader = {}, _simpleapi_cache = simpleapi_cache, @@ -145,8 +146,19 @@ def _add_group_map(self, group_map): # cycles for different abis/oses? For now we will need the users to # assume the same groups across all versions/platforms until we start # using an alternative cycle resolution strategy. + group_map = { + name: [normalize_name(whl_name) for whl_name in whls] + for name, whls in group_map.items() + } self._group_map.clear() + self._group_name_by_whl.clear() + self._group_map.update(group_map) + self._group_name_by_whl.update({ + whl_name: group_name + for group_name, group_whls in self._group_map.items() + for whl_name in group_whls + }) def _add_extra_aliases(self, extra_hub_aliases): for whl_name, aliases in extra_hub_aliases.items(): @@ -396,29 +408,14 @@ def _create_whl_repos( for mod, whl_name in pip_attr.whl_modifications.items(): whl_modifications[normalize_name(whl_name)] = mod - if pip_attr.experimental_requirement_cycles: - requirement_cycles = { - name: [normalize_name(whl_name) for whl_name in whls] - for name, whls in pip_attr.experimental_requirement_cycles.items() - } - - whl_group_mapping = { - whl_name: group_name - for group_name, group_whls in requirement_cycles.items() - for whl_name in group_whls - } - else: - whl_group_mapping = {} - requirement_cycles = {} - interpreter = _detect_interpreter(self, pip_attr) exposed_packages = {} for whl in requirements_by_platform: if whl.is_exposed: exposed_packages[whl.name] = None - group_name = whl_group_mapping.get(whl.name) - group_deps = requirement_cycles.get(group_name, []) + group_name = self._group_name_by_whl.get(whl.name) + group_deps = self._group_map.get(group_name, []) # Construct args separately so that the lock file can be smaller and does not include unused # attrs. From b00aff0da5d7454d7ba682beb500ea9b6a83619e Mon Sep 17 00:00:00 2001 From: Ignas Anikevicius <240938+aignas@users.noreply.github.com> Date: Sun, 7 Sep 2025 00:52:17 +0900 Subject: [PATCH 30/31] refactor: pass whl_overrides differently --- python/private/pypi/extension.bzl | 2 +- python/private/pypi/hub_builder.bzl | 112 +++++++++++++++------------- 2 files changed, 62 insertions(+), 52 deletions(-) diff --git a/python/private/pypi/extension.bzl b/python/private/pypi/extension.bzl index 66dfc21062..c73e88ac0d 100644 --- a/python/private/pypi/extension.bzl +++ b/python/private/pypi/extension.bzl @@ -250,6 +250,7 @@ You cannot use both the additive_build_content and additive_build_content_file a name = hub_name, module_name = mod.name, config = config, + whl_overrides = whl_overrides, simpleapi_download_fn = simpleapi_download, simpleapi_cache = simpleapi_cache, # TODO @aignas 2025-09-06: do not use kwargs @@ -279,7 +280,6 @@ You cannot use both the additive_build_content and additive_build_content_file a builder.pip_parse( module_ctx, pip_attr = pip_attr, - whl_overrides = whl_overrides, ) # Keeps track of all the hub's whl repos across the different versions. diff --git a/python/private/pypi/hub_builder.bzl b/python/private/pypi/hub_builder.bzl index 90005f9eb5..b5b0de7d2d 100644 --- a/python/private/pypi/hub_builder.bzl +++ b/python/private/pypi/hub_builder.bzl @@ -23,6 +23,7 @@ def hub_builder( name, module_name, config, + whl_overrides, minor_mapping, available_interpreters, simpleapi_download_fn, @@ -35,6 +36,7 @@ def hub_builder( name: {type}`str`, the name of the hub. module_name: {type}`str`, the module name that has created the hub. config: The platform configuration. + whl_overrides: {type}`dict[str, struct]` - per-wheel overrides. minor_mapping: {type}`dict[str, str]` the mapping between minor and full versions. evaluate_markers_fn: the override function used to evaluate the markers. available_interpreters: {type}`dict[str, Label]` The dictionary of available @@ -69,6 +71,7 @@ def hub_builder( _simpleapi_cache = simpleapi_cache, # instance constants _config = config, + _whl_overrides = whl_overrides, _evaluate_markers_fn = evaluate_markers_fn, _logger = logger, _minor_mapping = minor_mapping, @@ -98,7 +101,7 @@ def _build(self): whl_libraries = self._whl_libraries, ) -def _pip_parse(self, module_ctx, pip_attr, whl_overrides): +def _pip_parse(self, module_ctx, pip_attr): python_version = pip_attr.python_version if python_version in self._platforms: fail(( @@ -123,7 +126,6 @@ def _pip_parse(self, module_ctx, pip_attr, whl_overrides): self, module_ctx, pip_attr = pip_attr, - whl_overrides = whl_overrides, ) ### end of PUBLIC methods @@ -368,15 +370,13 @@ def _create_whl_repos( self, module_ctx, *, - pip_attr, - whl_overrides): + pip_attr): """create all of the whl repositories Args: self: the builder. module_ctx: {type}`module_ctx`. pip_attr: {type}`struct` - the struct that comes from the tag class iteration. - whl_overrides: {type}`dict[str, struct]` - per-wheel overrides. """ logger = self._logger platforms = self._platforms[pip_attr.python_version] @@ -403,58 +403,25 @@ def _create_whl_repos( logger = logger, ) + _add_exposed_packages(self, { + whl.name: None + for whl in requirements_by_platform + if whl.is_exposed + }) + whl_modifications = {} if pip_attr.whl_modifications != None: for mod, whl_name in pip_attr.whl_modifications.items(): whl_modifications[normalize_name(whl_name)] = mod - interpreter = _detect_interpreter(self, pip_attr) - exposed_packages = {} for whl in requirements_by_platform: - if whl.is_exposed: - exposed_packages[whl.name] = None - - group_name = self._group_name_by_whl.get(whl.name) - group_deps = self._group_map.get(group_name, []) - - # Construct args separately so that the lock file can be smaller and does not include unused - # attrs. - whl_library_args = dict( - dep_template = "@{}//{{name}}:{{target}}".format(self.name), - ) - maybe_args = dict( - # The following values are safe to omit if they have false like values - add_libdir_to_library_search_path = pip_attr.add_libdir_to_library_search_path, - annotation = whl_modifications.get(whl.name), - download_only = pip_attr.download_only, - enable_implicit_namespace_pkgs = pip_attr.enable_implicit_namespace_pkgs, - environment = pip_attr.environment, - envsubst = pip_attr.envsubst, - group_deps = group_deps, - group_name = group_name, - pip_data_exclude = pip_attr.pip_data_exclude, - python_interpreter = interpreter.path, - python_interpreter_target = interpreter.target, - whl_patches = { - p: json.encode(args) - for p, args in whl_overrides.get(whl.name, {}).items() - }, - ) - if not self._config.enable_pipstar: - maybe_args["experimental_target_platforms"] = pip_attr.experimental_target_platforms - - whl_library_args.update({k: v for k, v in maybe_args.items() if v}) - maybe_args_with_default = dict( - # The following values have defaults next to them - isolated = (use_isolated(module_ctx, pip_attr), True), - quiet = (pip_attr.quiet, True), - timeout = (pip_attr.timeout, 600), + whl_library_args = _whl_library_args( + self, + module_ctx, + pip_attr = pip_attr, + whl = whl, + whl_modifications = whl_modifications, ) - whl_library_args.update({ - k: v - for k, (v, default) in maybe_args_with_default.items() - if v != default - }) for src in whl.srcs: repo = _whl_repo( @@ -475,7 +442,50 @@ def _create_whl_repos( repo = repo, ) - _add_exposed_packages(self, exposed_packages) +def _whl_library_args(self, module_ctx, *, pip_attr, whl, whl_modifications): + interpreter = _detect_interpreter(self, pip_attr) + group_name = self._group_name_by_whl.get(whl.name) + group_deps = self._group_map.get(group_name, []) + + # Construct args separately so that the lock file can be smaller and does not include unused + # attrs. + whl_library_args = dict( + dep_template = "@{}//{{name}}:{{target}}".format(self.name), + ) + maybe_args = dict( + # The following values are safe to omit if they have false like values + add_libdir_to_library_search_path = pip_attr.add_libdir_to_library_search_path, + annotation = whl_modifications.get(whl.name), + download_only = pip_attr.download_only, + enable_implicit_namespace_pkgs = pip_attr.enable_implicit_namespace_pkgs, + environment = pip_attr.environment, + envsubst = pip_attr.envsubst, + group_deps = group_deps, + group_name = group_name, + pip_data_exclude = pip_attr.pip_data_exclude, + python_interpreter = interpreter.path, + python_interpreter_target = interpreter.target, + whl_patches = { + p: json.encode(args) + for p, args in self._whl_overrides.get(whl.name, {}).items() + }, + ) + if not self._config.enable_pipstar: + maybe_args["experimental_target_platforms"] = pip_attr.experimental_target_platforms + + whl_library_args.update({k: v for k, v in maybe_args.items() if v}) + maybe_args_with_default = dict( + # The following values have defaults next to them + isolated = (use_isolated(module_ctx, pip_attr), True), + quiet = (pip_attr.quiet, True), + timeout = (pip_attr.timeout, 600), + ) + whl_library_args.update({ + k: v + for k, (v, default) in maybe_args_with_default.items() + if v != default + }) + return whl_library_args def _whl_repo( *, From cc2b35370776c651ade768baa48a279d0adeee92 Mon Sep 17 00:00:00 2001 From: Ignas Anikevicius <240938+aignas@users.noreply.github.com> Date: Sun, 7 Sep 2025 00:56:02 +0900 Subject: [PATCH 31/31] split the whl_library_arg creation --- python/private/pypi/hub_builder.bzl | 44 ++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/python/private/pypi/hub_builder.bzl b/python/private/pypi/hub_builder.bzl index b5b0de7d2d..b6088e4ded 100644 --- a/python/private/pypi/hub_builder.bzl +++ b/python/private/pypi/hub_builder.bzl @@ -414,15 +414,17 @@ def _create_whl_repos( for mod, whl_name in pip_attr.whl_modifications.items(): whl_modifications[normalize_name(whl_name)] = mod + common_args = _common_args( + self, + module_ctx, + pip_attr = pip_attr, + ) for whl in requirements_by_platform: - whl_library_args = _whl_library_args( + whl_library_args = common_args | _whl_library_args( self, - module_ctx, - pip_attr = pip_attr, whl = whl, whl_modifications = whl_modifications, ) - for src in whl.srcs: repo = _whl_repo( src = src, @@ -442,10 +444,8 @@ def _create_whl_repos( repo = repo, ) -def _whl_library_args(self, module_ctx, *, pip_attr, whl, whl_modifications): +def _common_args(self, module_ctx, *, pip_attr): interpreter = _detect_interpreter(self, pip_attr) - group_name = self._group_name_by_whl.get(whl.name) - group_deps = self._group_map.get(group_name, []) # Construct args separately so that the lock file can be smaller and does not include unused # attrs. @@ -455,20 +455,13 @@ def _whl_library_args(self, module_ctx, *, pip_attr, whl, whl_modifications): maybe_args = dict( # The following values are safe to omit if they have false like values add_libdir_to_library_search_path = pip_attr.add_libdir_to_library_search_path, - annotation = whl_modifications.get(whl.name), download_only = pip_attr.download_only, enable_implicit_namespace_pkgs = pip_attr.enable_implicit_namespace_pkgs, environment = pip_attr.environment, envsubst = pip_attr.envsubst, - group_deps = group_deps, - group_name = group_name, pip_data_exclude = pip_attr.pip_data_exclude, python_interpreter = interpreter.path, python_interpreter_target = interpreter.target, - whl_patches = { - p: json.encode(args) - for p, args in self._whl_overrides.get(whl.name, {}).items() - }, ) if not self._config.enable_pipstar: maybe_args["experimental_target_platforms"] = pip_attr.experimental_target_platforms @@ -487,6 +480,29 @@ def _whl_library_args(self, module_ctx, *, pip_attr, whl, whl_modifications): }) return whl_library_args +def _whl_library_args(self, *, whl, whl_modifications): + group_name = self._group_name_by_whl.get(whl.name) + group_deps = self._group_map.get(group_name, []) + + # Construct args separately so that the lock file can be smaller and does not include unused + # attrs. + whl_library_args = dict( + dep_template = "@{}//{{name}}:{{target}}".format(self.name), + ) + maybe_args = dict( + # The following values are safe to omit if they have false like values + annotation = whl_modifications.get(whl.name), + group_deps = group_deps, + group_name = group_name, + whl_patches = { + p: json.encode(args) + for p, args in self._whl_overrides.get(whl.name, {}).items() + }, + ) + + whl_library_args.update({k: v for k, v in maybe_args.items() if v}) + return whl_library_args + def _whl_repo( *, src,