blob: e9c357d269cc02c510922bc233874483a22b563d [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.analysis.config.HostTransition;
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.SkylarkProviderIdentifier;
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;
import com.google.devtools.build.lib.util.FileType;
/**
* Bazel-specific rule definitions for Python rules.
*/
public final class BazelPyRuleClasses {
public static final FileType PYTHON_SOURCE = FileType.of(".py");
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(#7010): Remove this legacy set.
ImmutableList.of(
SkylarkProviderIdentifier.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} and {@code py_library}) actually
verify the current Python version against the value of this attribute. (This is a feature;
since {@code py_library} does not change the current Python version, if it did the
validation, it'd be impossible to build both {@code PY2ONLY} and {@code PY3ONLY} 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} 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)))
// do not depend on lib2to3:2to3 rule, because it creates circular dependencies
// 2to3 is itself written in Python and depends on many libraries.
.add(
attr("$python2to3", LABEL)
.cfg(HostTransition.createFactory())
.exec()
.value(env.getToolsLabel("//tools/python:2to3")))
.setPreferredDependencyPredicate(PyRuleClasses.PYTHON_SOURCE)
.build();
}
@Override
public Metadata getMetadata() {
return RuleDefinition.Metadata.builder()
.name("$base_py")
.type(RuleClassType.ABSTRACT)
.ancestors(BaseRuleClasses.RuleBase.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(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(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) -->
Enable link stamping.
Whether to encode build information into the binary. Possible values:
<ul>
<li><code>stamp = 1</code>: Stamp the build information into the
binary. Stamped binaries are only rebuilt when their dependencies
change. Use this if there are tests that depend on the build
information.</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">--[no]stamp</a> Blaze
flag.</li>
</ul>
<!-- #END_BLAZE_RULE.ATTRIBUTE --> */
.add(attr("stamp", TRISTATE).value(TriState.AUTO))
// TODO(brandjon): Consider adding to py_interpreter a .mandatoryNativeProviders() 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"))
.build();
}
@Override
public Metadata getMetadata() {
return RuleDefinition.Metadata.builder()
.name("$base_py_binary")
.type(RuleClassType.ABSTRACT)
.ancestors(PyBaseRule.class, CcToolchainRequiringRule.class)
.build();
}
}
}