| # Copyright 2024 The Bazel Authors. All rights reserved. |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| """{obj}`pkg_aliases` is a macro to generate aliases for selecting the right wheel for the right target platform. |
| |
| If you see an error where the distribution selection error indicates the config setting names this |
| page may help to describe the naming convention and relationship between various flags and options |
| in `rules_python` and the error message contents. |
| |
| Definitions: |
| :minor_version: Python interpreter minor version that the distributions are compatible with. |
| :suffix: Can be either empty or `_<os>_<suffix>`, which is usually used to distinguish multiple versions used for different target platforms. |
| :os: OS identifier that exists in `@platforms//os:<os>`. |
| :cpu: CPU architecture identifier that exists in `@platforms//cpu:<cpu>`. |
| |
| All of the config settings used by this macro are generated by |
| {obj}`config_settings`, for more detailed documentation on what each config |
| setting maps to and their precedence, refer to documentation on that page. |
| |
| `//_config:is_cp3<minor_version><suffix>` is used to select any target platforms. |
| """ |
| |
| load("@bazel_skylib//lib:selects.bzl", "selects") |
| load("//python/private:common_labels.bzl", "labels") |
| load("//python/private:text_util.bzl", "render") |
| load( |
| ":labels.bzl", |
| "DATA_LABEL", |
| "DIST_INFO_LABEL", |
| "EXTRACTED_WHEEL_FILES", |
| "PY_LIBRARY_IMPL_LABEL", |
| "PY_LIBRARY_PUBLIC_LABEL", |
| "WHEEL_FILE_IMPL_LABEL", |
| "WHEEL_FILE_PUBLIC_LABEL", |
| ) |
| |
| _NO_MATCH_ERROR_TEMPLATE = """\ |
| No matching wheel for current configuration's Python version. |
| |
| The current build configuration's Python version doesn't match any of the Python |
| wheels available for this distribution. This distribution supports the following Python |
| configuration settings: |
| {config_settings} |
| |
| To determine the current configuration's Python version, run: |
| `bazel config <config id>` (shown further below) |
| |
| For the current configuration value see the debug message above that is |
| printing the current flag values. If you can't see the message, then re-run the |
| build to make it a failure instead by running the build with: |
| --{current_flags}=fail |
| |
| However, the command above will hide the `bazel config <config id>` message. |
| """ |
| |
| _LABEL_NONE = labels.NONE |
| _LABEL_CURRENT_CONFIG = Label("//python/config_settings:current_config") |
| _LABEL_CURRENT_CONFIG_NO_MATCH = Label("//python/config_settings:is_not_matching_current_config") |
| _INCOMPATIBLE = "_no_matching_repository" |
| |
| def pkg_aliases( |
| *, |
| name, |
| actual, |
| group_name = None, |
| extra_aliases = None, |
| **kwargs): |
| """Create aliases for an actual package. |
| |
| Exposed only to be used from the hub repositories created by `rules_python`. |
| |
| Args: |
| name: {type}`str` The name of the package. |
| actual: {type}`dict[Label | tuple, str] | str` The name of the repo the |
| aliases point to, or a dict of select conditions to repo names for |
| the aliases to point to mapping to repositories. The keys are passed |
| to bazel skylib's `selects.with_or`, so they can be tuples as well. |
| group_name: {type}`str` The group name that the pkg belongs to. |
| extra_aliases: {type}`list[str]` The extra aliases to be created. |
| **kwargs: extra kwargs to pass to {bzl:obj}`get_config_settings`. |
| """ |
| alias = kwargs.pop("native", native).alias |
| select = kwargs.pop("select", selects.with_or) |
| |
| alias( |
| name = name, |
| actual = ":" + PY_LIBRARY_PUBLIC_LABEL, |
| ) |
| |
| target_names = { |
| PY_LIBRARY_PUBLIC_LABEL: PY_LIBRARY_IMPL_LABEL if group_name else PY_LIBRARY_PUBLIC_LABEL, |
| WHEEL_FILE_PUBLIC_LABEL: WHEEL_FILE_IMPL_LABEL if group_name else WHEEL_FILE_PUBLIC_LABEL, |
| DATA_LABEL: DATA_LABEL, |
| DIST_INFO_LABEL: DIST_INFO_LABEL, |
| EXTRACTED_WHEEL_FILES: EXTRACTED_WHEEL_FILES, |
| } | { |
| x: x |
| for x in extra_aliases or [] |
| } |
| |
| actual = multiplatform_whl_aliases(aliases = actual, **kwargs) |
| if type(actual) == type({}) and "//conditions:default" not in actual: |
| alias( |
| name = _INCOMPATIBLE, |
| actual = select( |
| {_LABEL_CURRENT_CONFIG_NO_MATCH: _LABEL_NONE}, |
| no_match_error = _NO_MATCH_ERROR_TEMPLATE.format( |
| config_settings = render.indent( |
| "\n".join(sorted([ |
| value |
| for key in actual |
| for value in (key if type(key) == "tuple" else [key]) |
| ])), |
| ).lstrip(), |
| current_flags = str(_LABEL_CURRENT_CONFIG), |
| ), |
| ), |
| visibility = ["//visibility:private"], |
| tags = ["manual"], |
| ) |
| actual["//conditions:default"] = _INCOMPATIBLE |
| |
| for name, target_name in target_names.items(): |
| if type(actual) == type(""): |
| _actual = "@{repo}//:{target_name}".format( |
| repo = actual, |
| target_name = name, |
| ) |
| elif type(actual) == type({}): |
| _actual = select( |
| { |
| v: "@{repo}//:{target_name}".format( |
| repo = repo, |
| target_name = name, |
| ) if repo != _INCOMPATIBLE else repo |
| for v, repo in actual.items() |
| }, |
| ) |
| else: |
| fail("The `actual` arg must be a dictionary or a string") |
| |
| kwargs = {} |
| if target_name.startswith("_"): |
| kwargs["visibility"] = ["//_groups:__subpackages__"] |
| |
| alias( |
| name = target_name, |
| actual = _actual, |
| **kwargs |
| ) |
| |
| if group_name: |
| alias( |
| name = PY_LIBRARY_PUBLIC_LABEL, |
| actual = "//_groups:{}_pkg".format(group_name), |
| ) |
| alias( |
| name = WHEEL_FILE_PUBLIC_LABEL, |
| actual = "//_groups:{}_whl".format(group_name), |
| ) |
| |
| def multiplatform_whl_aliases( |
| *, |
| aliases = []): |
| """convert a list of aliases from filename to config_setting ones. |
| |
| Exposed only for unit tests. |
| |
| Args: |
| aliases: {type}`str | dict[struct | str, str]`: The aliases |
| to process. Any aliases that have the filename set will be |
| converted to a dict of config settings to repo names. The |
| struct is created by {func}`whl_config_setting`. |
| |
| Returns: |
| A dict with of config setting labels to repo names or the repo name itself. |
| """ |
| |
| if type(aliases) == type(""): |
| # We don't have any aliases, this is a repo name |
| return aliases |
| |
| ret = {} |
| for alias, repo in aliases.items(): |
| if type(alias) != "struct": |
| ret[alias] = repo |
| continue |
| |
| config_settings = get_config_settings( |
| target_platforms = alias.target_platforms, |
| python_version = alias.version, |
| ) |
| |
| for setting in config_settings: |
| ret["//_config" + setting] = repo |
| |
| return ret |
| |
| def get_config_settings( |
| *, |
| target_platforms, |
| python_version): |
| """Get the filename config settings. |
| |
| Exposed only for unit tests. |
| |
| Args: |
| target_platforms: list[str], target platforms in "{abi}_{os}_{cpu}" format. |
| python_version: the python version to generate the config_settings for. |
| |
| Returns: |
| A tuple: |
| * A list of config settings that are generated by ./pip_config_settings.bzl |
| * The list of default version settings. |
| """ |
| |
| prefixes = [ |
| "cp{}".format(python_version).replace(".", ""), |
| ] |
| |
| if target_platforms: |
| target_platforms = target_platforms or [] |
| suffixes = [_non_versioned_platform(p) for p in target_platforms] |
| return [ |
| ":is_{}_{}".format(prefix, suffix) |
| for prefix in prefixes |
| for suffix in suffixes |
| ] |
| else: |
| return [":is_{}".format(p) for p in prefixes] |
| |
| def _non_versioned_platform(p, *, strict = False): |
| """A small utility function that converts 'cp311_linux_x86_64' to 'linux_x86_64'. |
| |
| This is so that we can tighten the code structure later by using strict = True. |
| """ |
| has_abi = p.startswith("cp") |
| if has_abi: |
| return p.partition("_")[-1] |
| elif not strict: |
| return p |
| else: |
| fail("Expected to always have a platform in the form '{{abi}}_{{os}}_{{arch}}', got: {}".format(p)) |