blob: 859784ff3089d3d1f9129ab604efa3473933eda5 [file] [log] [blame]
// Copyright 2014 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.
package com.google.devtools.build.lib.bazel.rules.python;
import static com.google.devtools.build.lib.packages.Attribute.attr;
import static com.google.devtools.build.lib.packages.BuildType.LABEL;
import static com.google.devtools.build.lib.packages.BuildType.LABEL_LIST;
import static com.google.devtools.build.lib.packages.BuildType.NODEP_LABEL;
import static com.google.devtools.build.lib.packages.BuildType.TRISTATE;
import static com.google.devtools.build.lib.packages.Type.STRING;
import static com.google.devtools.build.lib.packages.Type.STRING_LIST;
import com.google.common.collect.ImmutableList;
import com.google.devtools.build.lib.analysis.BaseRuleClasses;
import com.google.devtools.build.lib.analysis.RuleDefinition;
import com.google.devtools.build.lib.analysis.RuleDefinitionEnvironment;
import com.google.devtools.build.lib.bazel.rules.cpp.BazelCppRuleClasses.CcToolchainRequiringRule;
import com.google.devtools.build.lib.packages.Attribute.AllowedValueSet;
import com.google.devtools.build.lib.packages.Attribute.LabelLateBoundDefault;
import com.google.devtools.build.lib.packages.RuleClass;
import com.google.devtools.build.lib.packages.RuleClass.Builder.RuleClassType;
import com.google.devtools.build.lib.packages.RuleClass.ToolchainTransitionMode;
import com.google.devtools.build.lib.packages.StarlarkProviderIdentifier;
import com.google.devtools.build.lib.packages.TriState;
import com.google.devtools.build.lib.rules.python.PyCommon;
import com.google.devtools.build.lib.rules.python.PyInfo;
import com.google.devtools.build.lib.rules.python.PyRuleClasses;
import com.google.devtools.build.lib.rules.python.PyStructUtils;
import com.google.devtools.build.lib.rules.python.PythonVersion;
/**
* Bazel-specific rule definitions for Python rules.
*/
public final class BazelPyRuleClasses {
private BazelPyRuleClasses() {}
public static final LabelLateBoundDefault<?> PY_INTERPRETER =
LabelLateBoundDefault.fromTargetConfiguration(
BazelPythonConfiguration.class,
null,
(rule, attributes, bazelPythonConfig) -> bazelPythonConfig.getPythonTop());
/**
* Base class for Python rule definitions.
*/
public static final class PyBaseRule implements RuleDefinition {
@Override
public RuleClass build(RuleClass.Builder builder, RuleDefinitionEnvironment env) {
return builder
/* <!-- #BLAZE_RULE($base_py).ATTRIBUTE(deps) -->
The list of other libraries to be linked in to the binary target.
See general comments about <code>deps</code> at
<a href="${link common-definitions#common-attributes}">
Attributes common to all build rules</a>.
These are generally
<a href="${link py_library}"><code>py_library</code></a> rules.
<!-- #END_BLAZE_RULE.ATTRIBUTE --> */
.override(
builder
.copy("deps")
.mandatoryProvidersList(
ImmutableList.of(
// Legacy provider.
// TODO(b/153363654): Remove this legacy set.
ImmutableList.of(
StarlarkProviderIdentifier.forLegacy(PyStructUtils.PROVIDER_NAME)),
// Modern provider.
ImmutableList.of(PyInfo.PROVIDER.id())))
.allowedFileTypes())
/* <!-- #BLAZE_RULE($base_py).ATTRIBUTE(imports) -->
List of import directories to be added to the <code>PYTHONPATH</code>.
<p>
Subject to <a href="${link make-variables}">"Make variable"</a> substitution. These import
directories will be added for this rule and all rules that depend on it (note: not the
rules this rule depends on. Each directory will be added to <code>PYTHONPATH</code> by
<a href="${link py_binary}"><code>py_binary</code></a> rules that depend on this rule.
</p>
<p>
Absolute paths (paths that start with <code>/</code>) and paths that references a path
above the execution root are not allowed and will result in an error.
</p>
<!-- #END_BLAZE_RULE.ATTRIBUTE --> */
.add(attr("imports", STRING_LIST).value(ImmutableList.<String>of()))
/* <!-- #BLAZE_RULE($base_py).ATTRIBUTE(srcs_version) -->
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>Note that only the executable rules (<code>py_binary</code> and <code>py_library
</code>) actually verify the current Python version against the value of this attribute.
(This is a feature; since <code>py_library</code> does not change the current Python
version, if it did the validation, it'd be impossible to build both <code>PY2ONLY</code>
and <code>PY3ONLY</code> libraries in the same invocation.) Furthermore, if there is a
version mismatch, the error is only reported in the execution phase. In particular, the
error will not appear in a <code>bazel build --nobuild</code> invocation.)
<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 &lt;your target&gt; \
--aspects=@rules_python//python:defs.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)
.value(PythonVersion.DEFAULT_SRCS_VALUE.toString())
.allowedValues(new AllowedValueSet(PythonVersion.SRCS_STRINGS)))
.setPreferredDependencyPredicate(PyRuleClasses.PYTHON_SOURCE)
.build();
}
@Override
public Metadata getMetadata() {
return RuleDefinition.Metadata.builder()
.name("$base_py")
.type(RuleClassType.ABSTRACT)
.ancestors(BaseRuleClasses.NativeActionCreatingRule.class)
.build();
}
}
/**
* Base class for Python rule definitions that produce binaries.
*/
public static final class PyBinaryBaseRule implements RuleDefinition {
@Override
public RuleClass build(RuleClass.Builder builder, final RuleDefinitionEnvironment env) {
return builder
/* <!-- #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,
<code>name</code> is used instead (see above). If <code>name</code> does not
match any filename in <code>srcs</code>, <code>main</code> must be specified.
<!-- #END_BLAZE_RULE.ATTRIBUTE --> */
.add(attr("main", LABEL).allowedFileTypes(PyRuleClasses.PYTHON_SOURCE))
/* <!-- #BLAZE_RULE($base_py_binary).ATTRIBUTE(python_version) -->
Whether to build this target (and its transitive <code>deps</code>) for Python 2 or Python
3. Valid values are <code>"PY2"</code> and <code>"PY3"</code> (the default).
<p>The Python version is always reset (possibly by default) to whatever version is
specified by this attribute, regardless of the version specified on the command line or by
other higher 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>@rules_python//python:python_version</code>. See
<a href="https://github.com/bazelbuild/rules_python/blob/120590e2f2b66e5590bf4dc8ebef9c5338984775/python/BUILD#L43">here</a>
for more information.
<p><b>Bug warning:</b> This attribute sets the version for which Bazel builds your target,
but due to <a href="https://github.com/bazelbuild/bazel/issues/4815">#4815</a>, the
resulting stub script may still invoke the wrong interpreter version at runtime. See
<a href="https://github.com/bazelbuild/bazel/issues/4815#issuecomment-460777113">this
workaround</a>, which involves defining a <code>py_runtime</code> target that points to
either Python version as needed, and activating this <code>py_runtime</code> by setting
<code>--python_top</code>.
<!-- #END_BLAZE_RULE.ATTRIBUTE --> */
.add(
attr(PyCommon.PYTHON_VERSION_ATTRIBUTE, STRING)
.value(PythonVersion._INTERNAL_SENTINEL.toString())
.allowedValues(PyRuleClasses.TARGET_PYTHON_ATTR_VALUE_SET)
.nonconfigurable(
"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 (<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(PyRuleClasses.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, excluding the repo root
directory. The default, auto, means true unless
<code>--incompatible_default_to_explicit_init_py</code> is used. 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", TRISTATE).value(TriState.AUTO))
/* <!-- #BLAZE_RULE($base_py_binary).ATTRIBUTE(stamp) -->
Whether to encode build information into the binary. Possible values:
<ul>
<li>
<code>stamp = 1</code>: Always stamp the build information into the binary, even in
<a href="../user-manual.html#flag--stamp"><code>--nostamp</code></a> builds. <b>This
setting should be avoided</b>, since it potentially kills remote caching for the
binary and any downstream actions that depend on it.
</li>
<li>
<code>stamp = 0</code>: Always replace build information by constant values. This
gives good build result caching.
</li>
<li>
<code>stamp = -1</code>: Embedding of build information is controlled by the
<a href="../user-manual.html#flag--stamp"><code>--[no]stamp</code></a> flag.
</li>
</ul>
<p>Stamped binaries are <em>not</em> rebuilt unless their dependencies change.</p>
<!-- #END_BLAZE_RULE.ATTRIBUTE --> */
.add(attr("stamp", TRISTATE).value(TriState.AUTO))
// TODO(brandjon): Consider adding to py_interpreter a .mandatoryBuiltinProviders() of
// PyRuntimeInfoProvider. (Add a test case to PythonConfigurationTest for violations of
// this requirement.) Probably moot now that this is going to be replaced by toolchains.
.add(attr(":py_interpreter", LABEL).value(PY_INTERPRETER))
.add(
attr("$py_toolchain_type", NODEP_LABEL)
.value(env.getToolsLabel("//tools/python:toolchain_type")))
.addRequiredToolchains(env.getToolsLabel("//tools/python:toolchain_type"))
.useToolchainTransition(ToolchainTransitionMode.ENABLED)
.build();
}
@Override
public Metadata getMetadata() {
return RuleDefinition.Metadata.builder()
.name("$base_py_binary")
.type(RuleClassType.ABSTRACT)
.ancestors(PyBaseRule.class, CcToolchainRequiringRule.class)
.build();
}
}
}