diff --git a/CHANGELOG.md b/CHANGELOG.md index c4c99206b1..33aecf0ed1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -73,7 +73,8 @@ A brief description of the categories of changes: `TOOL_VERSIONS` for registering patched toolchains please consider setting the `patch_strip` explicitly to `1` if you depend on this value - in the future the value may change to default to `0`. - +* (toolchains) Added `//python:none`, a special target for use with + {obj}`py_exec_tools_toolchain.exec_interpreter` to treat the value as `None`. ### Removed * (toolchains): Removed accidentally exposed `http_archive` symbol from diff --git a/docs/BUILD.bazel b/docs/BUILD.bazel index 2b407db692..149e2c57f3 100644 --- a/docs/BUILD.bazel +++ b/docs/BUILD.bazel @@ -72,7 +72,6 @@ sphinx_docs( deps = [ ":bzl_api_docs", ":py_api_srcs", - ":py_cc_toolchain", ":py_runtime_pair", "//sphinxdocs/docs:docs_lib", ], @@ -86,16 +85,18 @@ sphinx_stardocs( "//python:pip_bzl", "//python:py_binary_bzl", "//python:py_cc_link_params_info_bzl", + "//python:py_exec_tools_info_bzl", + "//python:py_exec_tools_toolchain_bzl", "//python:py_executable_info_bzl", "//python:py_library_bzl", "//python:py_runtime_bzl", "//python:py_runtime_info_bzl", "//python:py_test_bzl", "//python:repositories_bzl", + "//python/cc:py_cc_toolchain_bzl", "//python/cc:py_cc_toolchain_info_bzl", "//python/entry_points:py_console_script_binary_bzl", - "//python/private:py_exec_tools_info_bzl", - "//python/private:py_exec_tools_toolchain_bzl", + "//python/private:py_cc_toolchain_rule_bzl", "//python/private/common:py_binary_rule_bazel_bzl", "//python/private/common:py_library_rule_bazel_bzl", "//python/private/common:py_runtime_rule_bzl", @@ -112,16 +113,6 @@ sphinx_stardocs( target_compatible_with = _TARGET_COMPATIBLE_WITH, ) -sphinx_stardoc( - name = "py_cc_toolchain", - src = "//python/private:py_cc_toolchain_rule.bzl", - prefix = "api/rules_python/", - public_load_path = "//python/cc:py_cc_toolchain.bzl", - tags = ["docs"], - target_compatible_with = _TARGET_COMPATIBLE_WITH, - deps = ["//python/cc:py_cc_toolchain_bzl"], -) - sphinx_stardoc( name = "py_runtime_pair", src = "//python/private:py_runtime_pair_rule_bzl", diff --git a/docs/api/rules_python/python/cc/index.md b/docs/api/rules_python/python/cc/index.md index 233b1308cd..82c59343be 100644 --- a/docs/api/rules_python/python/cc/index.md +++ b/docs/api/rules_python/python/cc/index.md @@ -1,3 +1,5 @@ +:::{default-domain} bzl +::: :::{bzl:currentfile} //python/cc:BUILD.bazel ::: # //python/cc @@ -31,4 +33,9 @@ This target provides: Toolchain type identifier for the Python C toolchain. This toolchain type is typically implemented by {obj}`py_cc_toolchain`. + +::::{seealso} +{any}`Custom Toolchains` for how to define custom toolchains +:::: + ::: diff --git a/docs/api/rules_python/python/index.md b/docs/api/rules_python/python/index.md index 6ce5e7ca8d..bc5a7313c9 100644 --- a/docs/api/rules_python/python/index.md +++ b/docs/api/rules_python/python/index.md @@ -10,7 +10,7 @@ Identifier for the toolchain type for the target platform. This toolchain type gives information about the runtime for the target platform. -It is typically implemented by the {obj}`py_runtime` rule +It is typically implemented by the {obj}`py_runtime` rule. ::::{seealso} {any}`Custom Toolchains` for how to define custom toolchains @@ -21,6 +21,14 @@ It is typically implemented by the {obj}`py_runtime` rule :::{bzl:target} exec_tools_toolchain_type Identifier for the toolchain type for exec tools used to build Python targets. + +This toolchain type gives information about tools needed to build Python targets +at build time. It is typically implemented by the {obj}`py_exec_tools_toolchain` +rule. + +::::{seealso} +{any}`Custom Toolchains` for how to define custom toolchains +:::: ::: :::{bzl:target} current_py_toolchain @@ -28,7 +36,7 @@ Identifier for the toolchain type for exec tools used to build Python targets. Helper target to resolve to the consumer's current Python toolchain. This target provides: -* `PyRuntimeInfo`: The consuming target's target toolchain information +* {obj}`PyRuntimeInfo`: The consuming target's target toolchain information ::: @@ -42,3 +50,16 @@ Use {obj}`@rules_python//python/runtime_env_toolchains:all` instead. ::: :::: +:::{target} none +A special target so that label attributes with default values can be set to +`None`. + +Bazel interprets `None` to mean "use the default value", which +makes it impossible to have a label attribute with a default value that is +optional. To work around this, a target with a special provider is used; +internally rules check for this, then treat the value as `None`. + +::::{versionadded} 0.36.0 +:::: + +::: diff --git a/docs/toolchains.md b/docs/toolchains.md index b5f664fcc3..2ac0099304 100644 --- a/docs/toolchains.md +++ b/docs/toolchains.md @@ -395,7 +395,7 @@ py_cc_toolchain( py_exec_tools_toolchain( name = "exec_tools_toolchain_impl", - exec_interpreter = "@rules_python/python:null_target", + exec_interpreter = "@rules_python/python:none", precompiler = "precompiler-cpython-3.12" ) diff --git a/python/BUILD.bazel b/python/BUILD.bazel index 40880a1495..6fcde3892d 100644 --- a/python/BUILD.bazel +++ b/python/BUILD.bazel @@ -139,6 +139,18 @@ bzl_library( ], ) +bzl_library( + name = "py_exec_tools_info_bzl", + srcs = ["py_exec_tools_info.bzl"], + deps = ["//python/private:py_exec_tools_info_bzl"], +) + +bzl_library( + name = "py_exec_tools_toolchain_bzl", + srcs = ["py_exec_tools_toolchain.bzl"], + deps = ["//python/private:py_exec_tools_toolchain_bzl"], +) + bzl_library( name = "py_executable_info_bzl", srcs = ["py_executable_info.bzl"], @@ -308,6 +320,12 @@ toolchain_type( visibility = ["//visibility:public"], ) +# Special target to indicate `None` for label attributes a default value. +alias( + name = "none", + actual = "//python/private:sentinel", +) + # Definitions for a Python toolchain that, at execution time, attempts to detect # a platform runtime having the appropriate major Python version. Consider this # a toolchain of last resort. diff --git a/python/cc/BUILD.bazel b/python/cc/BUILD.bazel index d384d0538f..f4e4aeb00f 100644 --- a/python/cc/BUILD.bazel +++ b/python/cc/BUILD.bazel @@ -40,7 +40,7 @@ bzl_library( name = "py_cc_toolchain_bzl", srcs = ["py_cc_toolchain.bzl"], visibility = ["//visibility:public"], - deps = ["//python/private:py_cc_toolchain_bzl"], + deps = ["//python/private:py_cc_toolchain_macro_bzl"], ) bzl_library( diff --git a/python/cc/py_cc_toolchain_info.bzl b/python/cc/py_cc_toolchain_info.bzl index 9ea394ad9f..3164f89f10 100644 --- a/python/cc/py_cc_toolchain_info.bzl +++ b/python/cc/py_cc_toolchain_info.bzl @@ -1,4 +1,4 @@ -# Copyright 2023 The Bazel Authors. All rights reserved. +# 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. @@ -11,11 +11,12 @@ # 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. +"""Provider for C/C++ information from the toolchain. -"""Provider for C/C++ information about the Python runtime. - -NOTE: This is a beta-quality feature. APIs subject to change until -https://github.com/bazelbuild/rules_python/issues/824 is considered done. +:::{seealso} +* {any}`Custom toolchains` for how to define custom toolchains. +* {obj}`py_cc_toolchain` rule for defining the toolchain. +::: """ load("//python/private:py_cc_toolchain_info.bzl", _PyCcToolchainInfo = "PyCcToolchainInfo") diff --git a/python/private/BUILD.bazel b/python/private/BUILD.bazel index a35e2f7c2e..e0de7d3b92 100644 --- a/python/private/BUILD.bazel +++ b/python/private/BUILD.bazel @@ -168,15 +168,16 @@ bzl_library( ) bzl_library( - name = "py_cc_toolchain_bzl", - srcs = [ - "py_cc_toolchain_macro.bzl", - "py_cc_toolchain_rule.bzl", - ], - visibility = [ - "//docs:__subpackages__", - "//python/cc:__pkg__", + name = "py_cc_toolchain_macro_bzl", + srcs = ["py_cc_toolchain_macro.bzl"], + deps = [ + ":py_cc_toolchain_rule_bzl", ], +) + +bzl_library( + name = "py_cc_toolchain_rule_bzl", + srcs = ["py_cc_toolchain_rule.bzl"], deps = [ ":py_cc_toolchain_info_bzl", ":rules_cc_srcs_bzl", @@ -188,7 +189,6 @@ bzl_library( bzl_library( name = "py_cc_toolchain_info_bzl", srcs = ["py_cc_toolchain_info.bzl"], - visibility = ["//python/cc:__pkg__"], ) bzl_library( @@ -370,7 +370,6 @@ exports_files( [ "coverage.patch", "repack_whl.py", - "py_cc_toolchain_rule.bzl", "py_package.bzl", "py_wheel.bzl", "py_wheel_normalize_pep440.bzl", diff --git a/python/private/py_cc_toolchain_macro.bzl b/python/private/py_cc_toolchain_macro.bzl index 35276f7401..416caac2ab 100644 --- a/python/private/py_cc_toolchain_macro.bzl +++ b/python/private/py_cc_toolchain_macro.bzl @@ -22,8 +22,10 @@ load(":util.bzl", "add_tag") def py_cc_toolchain(**kwargs): """Creates a py_cc_toolchain target. + This is a macro around the {rule}`py_cc_toolchain` rule. + Args: - **kwargs: Keyword args to pass onto underlying rule. + **kwargs: Keyword args to pass onto underlying {rule}`py_cc_toolchain` rule. """ # This tag is added to easily identify usages through other macros. diff --git a/python/private/py_cc_toolchain_rule.bzl b/python/private/py_cc_toolchain_rule.bzl index 2c52a2ec84..279f86cc14 100644 --- a/python/private/py_cc_toolchain_rule.bzl +++ b/python/private/py_cc_toolchain_rule.bzl @@ -78,5 +78,11 @@ A toolchain for a Python runtime's C/C++ information (e.g. headers) This rule carries information about the C/C++ side of a Python runtime, e.g. headers, shared libraries, etc. + +This provides `ToolchainInfo` with the following attributes: +* `py_cc_toolchain`: {type}`PyCcToolchainInfo` +* `toolchain_label`: {type}`Label` _only present when `--visibile_for_testing=True` + for internal testing_. The rule's label; this allows identifying what toolchain + implmentation was selected for testing purposes. """, ) diff --git a/python/private/py_exec_tools_toolchain.bzl b/python/private/py_exec_tools_toolchain.bzl index 26c09ca5cf..957448f421 100644 --- a/python/private/py_exec_tools_toolchain.bzl +++ b/python/private/py_exec_tools_toolchain.bzl @@ -39,20 +39,42 @@ def _py_exec_tools_toolchain_impl(ctx): py_exec_tools_toolchain = rule( implementation = _py_exec_tools_toolchain_impl, + doc = """ +Provides a toolchain for build time tools. + +This provides `ToolchainInfo` with the following attributes: +* `exec_tools`: {type}`PyExecToolsInfo` +* `toolchain_label`: {type}`Label` _only present when `--visibile_for_testing=True` + for internal testing_. The rule's label; this allows identifying what toolchain + implmentation was selected for testing purposes. +""", attrs = { "exec_interpreter": attr.label( default = "//python/private:current_interpreter_executable", cfg = "exec", doc = """ -The interpreter to use in the exec config. To disable, specify the -special target `//python/private:sentinel`. See PyExecToolsInfo.exec_interpreter -for further docs. +An interpreter that is directly usable in the exec configuration + +If not specified, the interpreter from {obj}`//python:toolchain_type` will +be used. + +To disable, specify the special target {obj}`//python:none`; the raw value `None` +will use the default. + +:::{note} +This is only useful for `ctx.actions.run` calls that _directly_ invoke the +interpreter, which is fairly uncommon and low level. It is better to use a +`cfg="exec"` attribute that points to a `py_binary` rule instead, which will +handle all the necessary transitions and runtime setup to invoke a program. +::: + +See {obj}`PyExecToolsInfo.exec_interpreter` for further docs. """, ), "precompiler": attr.label( allow_files = True, cfg = "exec", - doc = "See PyExecToolsInfo.precompiler", + doc = "See {obj}`PyExecToolsInfo.precompiler`", ), "_visible_for_testing": attr.label( default = "//python/private:visible_for_testing", diff --git a/python/py_exec_tools_info.bzl b/python/py_exec_tools_info.bzl new file mode 100644 index 0000000000..438412376e --- /dev/null +++ b/python/py_exec_tools_info.bzl @@ -0,0 +1,24 @@ +# 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. +"""Provider for the exec tools toolchain. + +:::{seealso} +* {any}`Custom toolchains` for how to define custom toolchains. +* {obj}`py_cc_toolchain` rule for defining the toolchain. +::: +""" + +load("//python/private:py_exec_tools_info.bzl", _PyExecToolsInfo = "PyExecToolsInfo") + +PyExecToolsInfo = _PyExecToolsInfo diff --git a/python/py_exec_tools_toolchain.bzl b/python/py_exec_tools_toolchain.bzl new file mode 100644 index 0000000000..6e0a663c91 --- /dev/null +++ b/python/py_exec_tools_toolchain.bzl @@ -0,0 +1,18 @@ +# 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. +"""Toolchain for build-time tools.""" + +load("//python/private:py_exec_tools_toolchain.bzl", _py_exec_tools_toolchain = "py_exec_tools_toolchain") + +py_exec_tools_toolchain = _py_exec_tools_toolchain diff --git a/sphinxdocs/inventories/bazel_inventory.txt b/sphinxdocs/inventories/bazel_inventory.txt index fd2bac7062..969c772386 100644 --- a/sphinxdocs/inventories/bazel_inventory.txt +++ b/sphinxdocs/inventories/bazel_inventory.txt @@ -59,7 +59,7 @@ ctx.version_file bzl:obj 1 rules/lib/builtins/ctx#version_file - ctx.workspace_name bzl:obj 1 rules/lib/builtins/ctx#workspace_name - depset bzl:type 1 rules/lib/depset - dict bzl:type 1 rules/lib/dict - -exec_compatible_with bzl:attribute 1 reference/be/common-definitions#common.exec_compatible_with - +exec_compatible_with bzl:attr 1 reference/be/common-definitions#common.exec_compatible_with - int bzl:type 1 rules/lib/int - label bzl:type 1 concepts/labels - list bzl:type 1 rules/lib/list - @@ -133,13 +133,13 @@ runfiles.root_symlinks bzl:type 1 rules/lib/builtins/runfiles#root_symlinks - runfiles.symlinks bzl:type 1 rules/lib/builtins/runfiles#symlinks - str bzl:type 1 rules/lib/string - struct bzl:type 1 rules/lib/builtins/struct - -target_compatible_with bzl:attribute 1 reference/be/common-definitions#common.target_compatible_with - +target_compatible_with bzl:attr 1 reference/be/common-definitions#common.target_compatible_with - testing bzl:obj 1 rules/lib/toplevel/testing - testing.ExecutionInfo bzl:function 1 rules/lib/toplevel/testing#ExecutionInfo - testing.TestEnvironment bzl:function 1 rules/lib/toplevel/testing#TestEnvironment - testing.analysis_test bzl:rule 1 rules/lib/toplevel/testing#analysis_test - toolchain bzl:rule 1 reference/be/platforms-and-toolchains#toolchain - toolchain.exec_compatible_with bzl:rule 1 reference/be/platforms-and-toolchains#toolchain.exec_compatible_with - -toolchain.target_settings bzl:attribute 1 reference/be/platforms-and-toolchains#toolchain.target_settings - -toolchain.target_compatible_with bzl:attribute 1 reference/be/platforms-and-toolchains#toolchain.target_compatible_with - +toolchain.target_settings bzl:attr 1 reference/be/platforms-and-toolchains#toolchain.target_settings - +toolchain.target_compatible_with bzl:attr 1 reference/be/platforms-and-toolchains#toolchain.target_compatible_with - toolchain_type bzl:type 1 rules/lib/builtins/toolchain_type.html - diff --git a/sphinxdocs/src/sphinx_bzl/bzl.py b/sphinxdocs/src/sphinx_bzl/bzl.py index cbd35a958b..54b1285a84 100644 --- a/sphinxdocs/src/sphinx_bzl/bzl.py +++ b/sphinxdocs/src/sphinx_bzl/bzl.py @@ -1439,9 +1439,7 @@ class _BzlDomain(domains.Domain): object_types = { "arg": domains.ObjType("arg", "arg", "obj"), # macro/function arg "aspect": domains.ObjType("aspect", "aspect", "obj"), - "attribute": domains.ObjType( - "attribute", "attribute", "attr", "obj" - ), # rule attribute + "attr": domains.ObjType("attr", "attr", "obj"), # rule attribute "function": domains.ObjType("function", "func", "obj"), "method": domains.ObjType("method", "method", "obj"), "module-extension": domains.ObjType( @@ -1460,6 +1458,7 @@ class _BzlDomain(domains.Domain): # types are objects that have a constructor and methods/attrs "type": domains.ObjType("type", "type", "obj"), } + # This controls: # * What is recognized when parsing, e.g. ":bzl:ref:`foo`" requires # "ref" to be in the role dict below. @@ -1508,7 +1507,7 @@ class _BzlDomain(domains.Domain): # dict[str, dict[str, _ObjectEntry]] "doc_names": {}, # Objects by a shorter or alternative name - # dict[str, _ObjectEntry] + # dict[str, dict[str id, _ObjectEntry]] "alt_names": {}, } @@ -1588,8 +1587,14 @@ def _find_entry_for_xref( # Note that the flag value could contain `=` if "=" in target: target = target[: target.find("=")] + if target in self.data["doc_names"].get(fromdocname, {}): - return self.data["doc_names"][fromdocname][target] + entry = self.data["doc_names"][fromdocname][target] + # Prevent a local doc name masking a global alt name when its of + # a different type. e.g. when the macro `foo` refers to the + # rule `foo` in another doc. + if object_type in self.object_types[entry.object_type].roles: + return entry if object_type == "obj": search_space = self.data["objects"] @@ -1600,7 +1605,15 @@ def _find_entry_for_xref( _log_debug("find_entry: alt_names=%s", sorted(self.data["alt_names"].keys())) if target in self.data["alt_names"]: - return self.data["alt_names"][target] + # Give preference to shorter object ids. This is a work around + # to allow e.g. `FooInfo` to refer to the FooInfo type rather than + # the `FooInfo` constructor. + entries = sorted( + self.data["alt_names"][target].items(), key=lambda item: len(item[0]) + ) + for _, entry in entries: + if object_type in self.object_types[entry.object_type].roles: + return entry return None @@ -1633,17 +1646,8 @@ def add_object(self, entry: _ObjectEntry, alt_names=None) -> None: alt_names.append(label + (f"%{symbol}" if symbol else "")) for alt_name in sorted(set(alt_names)): - if alt_name in self.data["alt_names"]: - existing = self.data["alt_names"][alt_name] - # This situation usually occurs for the constructor function - # of a provider, but could occur for e.g. an exported struct - # with an attribute the same name as the struct. For lack - # of a better option, take the shorter entry, on the assumption - # it refers to some container of the longer entry. - if len(entry.full_id) < len(existing.full_id): - self.data["alt_names"][alt_name] = entry - else: - self.data["alt_names"][alt_name] = entry + self.data["alt_names"].setdefault(alt_name, {}) + self.data["alt_names"][alt_name][entry.full_id] = entry docname = entry.index_entry.docname self.data["doc_names"].setdefault(docname, {}) @@ -1653,11 +1657,11 @@ def merge_domaindata( self, docnames: list[str], otherdata: dict[str, typing.Any] ) -> None: # Merge in simple dict[key, value] data - for top_key in ("objects", "alt_names"): + for top_key in ("objects",): self.data[top_key].update(otherdata.get(top_key, {})) # Merge in two-level dict[top_key, dict[sub_key, value]] data - for top_key in ("objects_by_type", "doc_names"): + for top_key in ("objects_by_type", "doc_names", "alt_names"): existing_top_map = self.data[top_key] for sub_key, sub_values in otherdata.get(top_key, {}).items(): if sub_key not in existing_top_map: