# Copyright 2023 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. "" load("//python/private:text_util.bzl", "render") load(":render_pkg_aliases.bzl", "render_multiplatform_pkg_aliases") load(":whl_config_setting.bzl", "whl_config_setting") _BUILD_FILE_CONTENTS = """\ package(default_visibility = ["//visibility:public"]) # Ensure the `requirements.bzl` source can be accessed by stardoc, since users load() from it exports_files(["requirements.bzl"]) """ def _impl(rctx): bzl_packages = rctx.attr.packages or rctx.attr.whl_map.keys() aliases = render_multiplatform_pkg_aliases( aliases = { key: _whl_config_settings_from_json(values) for key, values in rctx.attr.whl_map.items() }, extra_hub_aliases = rctx.attr.extra_hub_aliases, requirement_cycles = rctx.attr.groups, ) for path, contents in aliases.items(): rctx.file(path, contents) # NOTE: we are using the canonical name with the double '@' in order to # always uniquely identify a repository, as the labels are being passed as # a string and the resolution of the label happens at the call-site of the # `requirement`, et al. macros. macro_tmpl = "@@{name}//{{}}:{{}}".format(name = rctx.attr.name) rctx.file("BUILD.bazel", _BUILD_FILE_CONTENTS) rctx.template("requirements.bzl", rctx.attr._template, substitutions = { "%%ALL_DATA_REQUIREMENTS%%": render.list([ macro_tmpl.format(p, "data") for p in bzl_packages ]), "%%ALL_REQUIREMENTS%%": render.list([ macro_tmpl.format(p, "pkg") for p in bzl_packages ]), "%%ALL_WHL_REQUIREMENTS_BY_PACKAGE%%": render.dict({ p: macro_tmpl.format(p, "whl") for p in bzl_packages }), "%%MACRO_TMPL%%": macro_tmpl, }) hub_repository = repository_rule( attrs = { "extra_hub_aliases": attr.string_list_dict( doc = "Extra aliases to make for specific wheels in the hub repo.", mandatory = True, ), "groups": attr.string_list_dict( mandatory = False, ), "packages": attr.string_list( mandatory = False, doc = """\ The list of packages that will be exposed via all_*requirements macros. Defaults to whl_map keys. """, ), "repo_name": attr.string( mandatory = True, doc = "The apparent name of the repo. This is needed because in bzlmod, the name attribute becomes the canonical name.", ), "whl_map": attr.string_dict( mandatory = True, doc = """\ The wheel map where values are json.encoded strings of the whl_map constructed in the pip.parse tag class. """, ), "_template": attr.label( default = ":requirements.bzl.tmpl.bzlmod", ), }, doc = """A rule for bzlmod mulitple pip repository creation. PRIVATE USE ONLY.""", implementation = _impl, ) def _whl_config_settings_from_json(repo_mapping_json): """Deserialize the serialized values with whl_config_settings_to_json. Args: repo_mapping_json: {type}`str` Returns: What `whl_config_settings_to_json` accepts. """ return { whl_config_setting(**v): repo for repo, values in json.decode(repo_mapping_json).items() for v in values } def whl_config_settings_to_json(repo_mapping): """A function to serialize the aliases so that `hub_repository` can accept them. Args: repo_mapping: {type}`dict[str, list[struct]]` repo to {obj}`whl_config_setting` mapping. Returns: A deserializable JSON string """ return json.encode({ repo: [_whl_config_setting_dict(s) for s in settings] for repo, settings in repo_mapping.items() }) def _whl_config_setting_dict(a): ret = {} if a.config_setting: ret["config_setting"] = a.config_setting if a.filename: ret["filename"] = a.filename if a.target_platforms: ret["target_platforms"] = a.target_platforms if a.version: ret["version"] = a.version return ret