Update documentation for Python version mechanisms
This isn't perfect in terms of linkifying names of rules/attrs, but it's a vast improvement and needed to get this change migration-ready. Ideally we'd have a dedicated Python concepts page.
Also did a little drive-by cleanup of unrelated attributes' docs and a few redundancies in attribute declarations.
Work toward #6583, #6442.
RELNOTES: None
PiperOrigin-RevId: 231966448
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/python/BazelPyLibraryRule.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/python/BazelPyLibraryRule.java
index c2a80a4..caea8a9e 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/rules/python/BazelPyLibraryRule.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/python/BazelPyLibraryRule.java
@@ -31,30 +31,15 @@
public RuleClass build(RuleClass.Builder builder, RuleDefinitionEnvironment env) {
return builder
.requiresConfigurationFragments(PythonConfiguration.class)
- /* <!-- #BLAZE_RULE(py_library).ATTRIBUTE(deps) -->
- The list of other libraries to be linked in to the library target.
- See general comments about <code>deps</code> at
- <a href="${link common-definitions#common-attributes}">
- Attributes common to all build rules</a>.
- In practice, these arguments are treated like those in <code>srcs</code>;
- you may move items between these lists willy-nilly. It's probably more
- readable to keep your <code>.py</code> files in your <code>srcs</code>.
- <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
-
- /* <!-- #BLAZE_RULE(py_library).ATTRIBUTE(data) -->
- The list of files needed by this library at runtime.
- See general comments about <code>data</code> at
- <a href="${link common-definitions#common-attributes}">
- Attributes common to all build rules</a>.
- <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
/* <!-- #BLAZE_RULE(py_library).ATTRIBUTE(srcs) -->
- The list of source files that are processed to create the target.
+ The list of source (<code>.py</code>) files that are processed to create the target.
This includes all your checked-in code and any generated source files.
<!-- #END_BLAZE_RULE.ATTRIBUTE --> */
- .add(attr("srcs", LABEL_LIST)
- .direct_compile_time_input()
- .allowedFileTypes(BazelPyRuleClasses.PYTHON_SOURCE))
+ .add(
+ attr("srcs", LABEL_LIST)
+ .direct_compile_time_input()
+ .allowedFileTypes(BazelPyRuleClasses.PYTHON_SOURCE))
.build();
}
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/python/BazelPyRuleClasses.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/python/BazelPyRuleClasses.java
index 0d4a098..e391f53 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/rules/python/BazelPyRuleClasses.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/python/BazelPyRuleClasses.java
@@ -65,8 +65,7 @@
See general comments about <code>deps</code> at
<a href="${link common-definitions#common-attributes}">
Attributes common to all build rules</a>.
- These can be
- <a href="${link py_binary}"><code>py_binary</code></a> rules,
+ These are generally
<a href="${link py_library}"><code>py_library</code></a> rules.
<!-- #END_BLAZE_RULE.ATTRIBUTE --> */
.override(
@@ -80,7 +79,6 @@
SkylarkProviderIdentifier.forLegacy(PyStructUtils.PROVIDER_NAME)),
// Modern provider.
ImmutableList.of(PyInfo.PROVIDER.id())))
- .legacyMandatoryProviders(PyStructUtils.PROVIDER_NAME)
.allowedFileTypes())
/* <!-- #BLAZE_RULE($base_py).ATTRIBUTE(imports) -->
List of import directories to be added to the <code>PYTHONPATH</code>.
@@ -97,26 +95,35 @@
<!-- #END_BLAZE_RULE.ATTRIBUTE --> */
.add(attr("imports", STRING_LIST).value(ImmutableList.<String>of()))
/* <!-- #BLAZE_RULE($base_py).ATTRIBUTE(srcs_version) -->
- The value set here is for documentation purpose, and it will NOT determine which version
- of python interpreter to use. Starting with 0.5.3 this attribute has been deprecated and
- no longer has effect.
- A string specifying the Python major version(s) that the <code>.py</code> source
- files listed in the <code>srcs</code> of this rule are compatible with.
- Please reference to <a href="${link py_runtime}"><code>py_runtime</code></a> rules for
- determining the python version.
- Valid values are:<br/>
- <code>"PY2ONLY"</code> -
- Python 2 code that is <b>not</b> suitable for <code>2to3</code> conversion.<br/>
- <code>"PY2"</code> -
- Python 2 code that is expected to work when run through <code>2to3</code>.<br/>
- <code>"PY2AND3"</code> -
- Code that is compatible with both Python 2 and 3 without
- <code>2to3</code> conversion.<br/>
- <code>"PY3ONLY"</code> -
- Python 3 code that will not run on Python 2.<br/>
- <code>"PY3"</code> -
- A synonym for PY3ONLY.<br/>
- <br/>
+ This attribute declares the target's <code>srcs</code> to be compatible with either Python
+ 2, Python 3, or both. To actually set the Python runtime version, use the
+ <a href="${link py_binary.python_version}"><code>python_version</code></a> attribute of an
+ executable Python rule (<code>py_binary</code> or <code>py_test</code>).
+
+ <p>Allowed values are: <code>"PY2AND3"</code>, <code>"PY2"</code>, and <code>"PY3"</code>.
+ The values <code>"PY2ONLY"</code> and <code>"PY3ONLY"</code> are also allowed for historic
+ reasons, but they are essentially the same as <code>"PY2"</code> and <code>"PY3"</code>
+ and should be avoided.
+
+ <p>Under the old semantics
+ (<code>--experimental_allow_python_version_transitions=false</code>), it is an error to
+ build any Python target for a version disallowed by its <code>srcs_version</code>
+ attribute. Under the new semantics
+ (<code>--experimental_allow_python_version_transitions=true</code>), this check is
+ deferred to the executable rule: You can build a <code>srcs_version = "PY3"</code>
+ <code>py_library</code> target for Python 2, but you cannot actually depend on it via
+ <code>deps</code> from a Python 3 <code>py_binary</code>.
+
+ <p>To get diagnostic information about which dependencies introduce version requirements,
+ you can run the <code>find_requirements</code> aspect on your target:
+ <pre>
+ bazel build <your target> \
+ --aspects=@bazel_tools//tools/python:srcs_version.bzl%find_requirements \
+ --output_groups=pyversioninfo
+ </pre>
+ This will build a file with the suffix <code>-pyversioninfo.txt</code> giving information
+ about why your target requires one Python version or another. Note that it works even if
+ the given target failed to build due to a version conflict.
<!-- #END_BLAZE_RULE.ATTRIBUTE --> */
.add(
attr("srcs_version", STRING)
@@ -154,15 +161,6 @@
@Override
public RuleClass build(RuleClass.Builder builder, final RuleDefinitionEnvironment env) {
return builder
- /* <!-- #BLAZE_RULE($base_py_binary).ATTRIBUTE(data) -->
- The list of files needed by this binary at runtime.
- See general comments about <code>data</code> at
- <a href="${link common-definitions#common-attributes}">
- Attributes common to all build rules</a>.
- Also see the <a href="${link py_library.data}"><code>data</code></a> argument of
- the <a href="${link py_library}"><code>py_library</code></a> rule for details.
- <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
-
/* <!-- #BLAZE_RULE($base_py_binary).ATTRIBUTE(main) -->
The name of the source file that is the main entry point of the application.
This file must also be listed in <code>srcs</code>. If left unspecified,
@@ -171,12 +169,10 @@
<!-- #END_BLAZE_RULE.ATTRIBUTE --> */
.add(attr("main", LABEL).allowedFileTypes(PYTHON_SOURCE))
/* <!-- #BLAZE_RULE($base_py_binary).ATTRIBUTE(default_python_version) -->
- A string specifying the default Python major version to use when building this binary and
- all of its <code>deps</code>.
- Valid values are <code>"PY2"</code> (default) or <code>"PY3"</code>.
- Python 3 support is experimental.
- <p>If both this attribute and <code>python_version</code> are supplied,
- <code>default_python_version</code> will be ignored.
+ A deprecated alias for <code>python_version</code>; use that instead. This attribute is
+ disabled under <code>--experimental_remove_old_python_version_api</code>. For migration
+ purposes, if <code>python_version</code> is given then the value of
+ <code>default_python_version</code> is ignored.
<!-- #END_BLAZE_RULE.ATTRIBUTE --> */
.add(
attr(PyCommon.DEFAULT_PYTHON_VERSION_ATTRIBUTE, STRING)
@@ -186,8 +182,25 @@
"read by PyRuleClasses.PYTHON_VERSION_TRANSITION, which doesn't have access"
+ " to the configuration"))
/* <!-- #BLAZE_RULE($base_py_binary).ATTRIBUTE(python_version) -->
- A replacement for <code>default_python_version</code>. If both this and
- <code>default_python_version</code> are supplied, the latter will be ignored.
+ Whether to build this target (and its transitive <code>deps</code>) for Python 2 or Python
+ 3. Valid values are <code>"PY2"</code> (the default) and <code>"PY3"</code>.
+
+ <p>Under the old semantics
+ (<code>--experimental_allow_python_version_transitions=false</code>), the Python version
+ generally cannot be changed once set. This means that the <code>--python_version</code>
+ flag overrides this attribute, and other Python binaries in the <code>data</code> deps of
+ this target are forced to use the same version as this target.
+
+ <p>Under the new semantics
+ (<code>--experimental_allow_python_version_transitions=true</code>), the Python version
+ is always set (possibly by default) to whatever version is specified by this attribute,
+ regardless of the version specified on the command line or by other targets that depend on
+ this one.
+
+ <p>If you want to <code>select()</code> on the current Python version, you can inspect the
+ value of <code>@bazel_tools//tools/python:python_version</code>. See
+ <a href="https://github.com/bazelbuild/bazel/blob/4b74ea9a3f81b7ed30562f1689827b5488884c86/tools/python/BUILD#L33">here</a>
+ for more information.
<!-- #END_BLAZE_RULE.ATTRIBUTE --> */
.add(
attr(PyCommon.PYTHON_VERSION_ATTRIBUTE, STRING)
@@ -197,26 +210,23 @@
"read by PyRuleClasses.PYTHON_VERSION_TRANSITION, which doesn't have access"
+ " to the configuration"))
/* <!-- #BLAZE_RULE($base_py_binary).ATTRIBUTE(srcs) -->
- The list of source files that are processed to create the target.
- This includes all your checked-in code and any
- generated source files. The line between <code>srcs</code> and
- <code>deps</code> is loose. The <code>.py</code> files
- probably belong in <code>srcs</code> and library targets probably belong
- in <code>deps</code>, but don't worry about it too much.
+ The list of source (<code>.py</code>) files that are processed to create the target.
+ This includes all your checked-in code and any generated source files. Library targets
+ belong in <code>deps</code> instead, while other binary files needed at runtime belong in
+ <code>data</code>.
<!-- #END_BLAZE_RULE.ATTRIBUTE --> */
.add(
attr("srcs", LABEL_LIST)
.mandatory()
.allowedFileTypes(PYTHON_SOURCE)
- .direct_compile_time_input()
- .allowedFileTypes(BazelPyRuleClasses.PYTHON_SOURCE))
+ .direct_compile_time_input())
/* <!-- #BLAZE_RULE($base_py_binary).ATTRIBUTE(legacy_create_init) -->
Whether to implicitly create empty __init__.py files in the runfiles tree.
These are created in every directory containing Python source code or
- shared libraries, and every parent directory of those directories.
- Default is true for backward compatibility. If false, the user is responsible
- for creating __init__.py files (empty or not) and adding them to `srcs` or `deps`
- of Python targets as required.
+ shared libraries, and every parent directory of those directories, excluding the repo root
+ directory. The default is true for backward compatibility. If false, the user is
+ responsible for creating (possibly empty) __init__.py files and adding them to the
+ <code>srcs</code> of Python targets as required.
<!-- #END_BLAZE_RULE.ATTRIBUTE --> */
.add(attr("legacy_create_init", BOOLEAN).value(true))
/* <!-- #BLAZE_RULE($base_py_binary).ATTRIBUTE(stamp) -->
diff --git a/src/main/java/com/google/devtools/build/lib/rules/python/PyCommon.java b/src/main/java/com/google/devtools/build/lib/rules/python/PyCommon.java
index 4a46cc5..6e12259 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/python/PyCommon.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/python/PyCommon.java
@@ -483,14 +483,16 @@
if (!ruleContext.getFragment(PythonConfiguration.class).useNewPyVersionSemantics()) {
return false;
}
- // TODO(brandjon): Add link to documentation explaining the error and use of the aspect.
+ String errorTemplate =
+ "This target is being built for Python %s but (transitively) includes Python %s-only "
+ + "sources. You can get diagnostic information about which dependencies introduce this "
+ + "version requirement by running the `find_requirements` aspect. For more info see "
+ + "the documentation for the `srcs_version` attribute.";
String error = null;
if (version == PythonVersion.PY2 && hasPy3OnlySources) {
- error =
- "target is being built for Python 2 but (transitively) includes Python 3-only sources";
+ error = String.format(errorTemplate, "2", "3");
} else if (version == PythonVersion.PY3 && hasPy2OnlySources) {
- error =
- "target is being built for Python 3 but (transitively) includes Python 2-only sources";
+ error = String.format(errorTemplate, "3", "2");
}
if (error == null) {
return false;
diff --git a/src/main/java/com/google/devtools/build/lib/rules/python/PythonOptions.java b/src/main/java/com/google/devtools/build/lib/rules/python/PythonOptions.java
index 1c8a9e4..0981448 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/python/PythonOptions.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/python/PythonOptions.java
@@ -69,30 +69,28 @@
help = "Build python executable zip; on on Windows, off on other platforms")
public TriState buildPythonZip;
- // TODO(brandjon): For both experimental options below, add documentation and add a link to it
- // from the help text, then change the documentationCategory to SKYLARK_SEMANTICS.
-
@Option(
name = "experimental_remove_old_python_version_api",
- // TODO(brandjon): Do not flip until we have an answer for how to disallow the
- // "default_python_version" attribute without hacking up native.existing_rules(). See
- // #7071 and b/122596733.
defaultValue = "false",
- documentationCategory = OptionDocumentationCategory.UNDOCUMENTED,
+ documentationCategory = OptionDocumentationCategory.SKYLARK_SEMANTICS,
effectTags = {OptionEffectTag.LOADING_AND_ANALYSIS},
metadataTags = {OptionMetadataTag.EXPERIMENTAL},
help =
"If true, disables use of the `--force_python` flag and the `default_python_version` "
- + "attribute for `py_binary` and `py_test`.")
+ + "attribute for `py_binary` and `py_test`. Use the `--python_version` flag and "
+ + "`python_version` attribute instead, which have exactly the same meaning. This "
+ + "flag also disables `select()`-ing over `--host_force_python`.")
public boolean experimentalRemoveOldPythonVersionApi;
@Option(
name = "experimental_allow_python_version_transitions",
defaultValue = "false",
- documentationCategory = OptionDocumentationCategory.UNDOCUMENTED,
+ documentationCategory = OptionDocumentationCategory.SKYLARK_SEMANTICS,
effectTags = {OptionEffectTag.LOADING_AND_ANALYSIS},
metadataTags = {OptionMetadataTag.EXPERIMENTAL},
- help = "If true, Python rules use the new PY2/PY3 version semantics.")
+ help =
+ "If true, Python rules use the new PY2/PY3 version semantics. For more information, see "
+ + "the documentation for `py_binary`'s `python_version` attribute.")
public boolean experimentalAllowPythonVersionTransitions;
/**
@@ -107,17 +105,16 @@
name = "python_version",
defaultValue = "null",
converter = TargetPythonVersionConverter.class,
- // TODO(brandjon): Change to OptionDocumentationCategory.GENERIC_INPUTS when this is
- // sufficiently implemented/documented.
- documentationCategory = OptionDocumentationCategory.UNDOCUMENTED,
+ documentationCategory = OptionDocumentationCategory.GENERIC_INPUTS,
effectTags = {
OptionEffectTag.LOADING_AND_ANALYSIS,
OptionEffectTag.AFFECTS_OUTPUTS // because of "-py3" output root
},
help =
- "The Python major version mode, either `PY2` or `PY3`. Note that this is overridden by "
- + "`py_binary` and `py_test` targets (whether or not they specify their version "
- + "explicitly), so there is usually not much reason to supply this flag.")
+ "The Python major version mode, either `PY2` or `PY3`. Note that under the new version "
+ + "semantics (`--experimental_allow_python_version_transitions`) this is overridden "
+ + "by `py_binary` and `py_test` targets (even if they don't explicitly specify a "
+ + "version) so there is usually not much reason to supply this flag.")
public PythonVersion pythonVersion;
private static final OptionDefinition PYTHON_VERSION_DEFINITION =
@@ -140,7 +137,9 @@
converter = TargetPythonVersionConverter.class,
documentationCategory = OptionDocumentationCategory.OUTPUT_PARAMETERS,
effectTags = {OptionEffectTag.LOADING_AND_ANALYSIS, OptionEffectTag.AFFECTS_OUTPUTS},
- help = "Overrides default_python_version attribute. Can be \"PY2\" or \"PY3\".")
+ help =
+ "Deprecated alias for `--python_version`. Disabled by "
+ + "`--experimental_remove_old_python_version_api`.")
public PythonVersion forcePython;
private static final OptionDefinition FORCE_PYTHON_DEFINITION =
@@ -186,19 +185,25 @@
@Override
public Map<OptionDefinition, SelectRestriction> getSelectRestrictions() {
- // TODO(brandjon): Add an error string that references documentation explaining to use
- // @bazel_tools//tools/python:python_version instead.
+ // TODO(brandjon): Instead of referencing the python_version target, whose path depends on the
+ // tools repo name, reference a standalone documentation page instead.
ImmutableMap.Builder<OptionDefinition, SelectRestriction> restrictions = ImmutableMap.builder();
restrictions.put(
PYTHON_VERSION_DEFINITION,
- new SelectRestriction(/*visibleWithinToolsPackage=*/ true, /*errorMessage=*/ null));
+ new SelectRestriction(
+ /*visibleWithinToolsPackage=*/ true,
+ "Use @bazel_tools//python/tools:python_version instead."));
if (experimentalRemoveOldPythonVersionApi) {
restrictions.put(
FORCE_PYTHON_DEFINITION,
- new SelectRestriction(/*visibleWithinToolsPackage=*/ true, /*errorMessage=*/ null));
+ new SelectRestriction(
+ /*visibleWithinToolsPackage=*/ true,
+ "Use @bazel_tools//python/tools:python_version instead."));
restrictions.put(
HOST_FORCE_PYTHON_DEFINITION,
- new SelectRestriction(/*visibleWithinToolsPackage=*/ false, /*errorMessage=*/ null));
+ new SelectRestriction(
+ /*visibleWithinToolsPackage=*/ false,
+ "Use @bazel_tools//python/tools:python_version instead."));
}
return restrictions.build();
}