blob: 48a766e3523e7e7a50b7f94cbb987d51473e0966 [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.TRISTATE;
import static com.google.devtools.build.lib.syntax.Type.BOOLEAN;
import static com.google.devtools.build.lib.syntax.Type.STRING;
import static com.google.devtools.build.lib.syntax.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>Under the old semantics
(<code>--incompatible_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>--incompatible_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 &lt;your target&gt; \
--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)
.value(PythonVersion.DEFAULT_SRCS_VALUE.toString())
.allowedValues(new AllowedValueSet(PythonVersion.SRCS_STRINGS)))
// TODO(brandjon): Consider adding to py_interpreter a .mandatoryNativeProviders() of
// BazelPyRuntimeProvider. (Add a test case to PythonConfigurationTest for violations
// of this requirement.)
.add(attr(":py_interpreter", LABEL).value(PY_INTERPRETER))
// 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.INSTANCE)
.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(default_python_version) -->
A deprecated alias for <code>python_version</code>; use that instead. This attribute is
disabled under <code>--incompatible_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)
.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(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> (the default) and <code>"PY3"</code>.
<p>Under the old semantics
(<code>--incompatible_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>--incompatible_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.
<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 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) -->
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))
.build();
}
@Override
public Metadata getMetadata() {
return RuleDefinition.Metadata.builder()
.name("$base_py_binary")
.type(RuleClassType.ABSTRACT)
.ancestors(PyBaseRule.class, CcToolchainRequiringRule.class)
.build();
}
}
}