aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--src/main/java/com/google/devtools/build/lib/bazel/rules/BazelRuleClassProvider.java2
-rw-r--r--src/main/java/com/google/devtools/build/lib/bazel/rules/python/BazelPyRuleClasses.java14
-rw-r--r--src/main/java/com/google/devtools/build/lib/bazel/rules/python/BazelPyRuntime.java79
-rw-r--r--src/main/java/com/google/devtools/build/lib/bazel/rules/python/BazelPyRuntimeProvider.java44
-rw-r--r--src/main/java/com/google/devtools/build/lib/bazel/rules/python/BazelPyRuntimeRule.java95
-rw-r--r--src/main/java/com/google/devtools/build/lib/bazel/rules/python/BazelPythonConfiguration.java54
-rw-r--r--src/main/java/com/google/devtools/build/lib/bazel/rules/python/BazelPythonSemantics.java51
-rw-r--r--src/test/java/com/google/devtools/build/lib/bazel/rules/python/BazelPyRuntimeTest.java163
8 files changed, 490 insertions, 12 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/BazelRuleClassProvider.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/BazelRuleClassProvider.java
index aa4fb60214..e2ebb44dab 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/rules/BazelRuleClassProvider.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/BazelRuleClassProvider.java
@@ -61,6 +61,7 @@ import com.google.devtools.build.lib.bazel.rules.java.proto.BazelJavaProtoLibrar
import com.google.devtools.build.lib.bazel.rules.python.BazelPyBinaryRule;
import com.google.devtools.build.lib.bazel.rules.python.BazelPyLibraryRule;
import com.google.devtools.build.lib.bazel.rules.python.BazelPyRuleClasses;
+import com.google.devtools.build.lib.bazel.rules.python.BazelPyRuntimeRule;
import com.google.devtools.build.lib.bazel.rules.python.BazelPyTestRule;
import com.google.devtools.build.lib.bazel.rules.python.BazelPythonConfiguration;
import com.google.devtools.build.lib.bazel.rules.sh.BazelShBinaryRule;
@@ -541,6 +542,7 @@ public class BazelRuleClassProvider {
builder.addRuleDefinition(new BazelPyLibraryRule());
builder.addRuleDefinition(new BazelPyBinaryRule());
builder.addRuleDefinition(new BazelPyTestRule());
+ builder.addRuleDefinition(new BazelPyRuntimeRule());
}
@Override
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 41ec62c95d..295f0181f1 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
@@ -26,7 +26,12 @@ 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.BuildConfiguration;
import com.google.devtools.build.lib.bazel.rules.cpp.BazelCppRuleClasses;
+import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.packages.Attribute.LateBoundLabel;
+import com.google.devtools.build.lib.packages.AttributeMap;
+import com.google.devtools.build.lib.packages.Rule;
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.TriState;
@@ -41,6 +46,14 @@ import com.google.devtools.build.lib.util.FileType;
public final class BazelPyRuleClasses {
public static final FileType PYTHON_SOURCE = FileType.of(".py");
+ public static final LateBoundLabel<BuildConfiguration> PY_INTERPRETER =
+ new LateBoundLabel<BuildConfiguration>() {
+ @Override
+ public Label resolve(Rule rule, AttributeMap attributes, BuildConfiguration configuration) {
+ return configuration.getFragment(BazelPythonConfiguration.class).getPythonTop();
+ }
+ };
+
/**
* Base class for Python rule definitions.
*/
@@ -92,6 +105,7 @@ public final class BazelPyRuleClasses {
<!-- #END_BLAZE_RULE.ATTRIBUTE --> */
.add(attr("srcs_version", STRING)
.value(PythonVersion.defaultValue().toString()))
+ .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(HOST).exec()
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/python/BazelPyRuntime.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/python/BazelPyRuntime.java
new file mode 100644
index 0000000000..658c680b26
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/python/BazelPyRuntime.java
@@ -0,0 +1,79 @@
+// Copyright 2017 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 com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.ConfiguredTarget;
+import com.google.devtools.build.lib.analysis.PrerequisiteArtifacts;
+import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode;
+import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder;
+import com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.lib.analysis.RunfilesProvider;
+import com.google.devtools.build.lib.collect.nestedset.NestedSet;
+import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
+import com.google.devtools.build.lib.rules.RuleConfiguredTargetFactory;
+import com.google.devtools.build.lib.syntax.Type;
+import com.google.devtools.build.lib.vfs.PathFragment;
+
+/**
+ * Implementation for the {@code py_runtime} rule.
+ */
+public final class BazelPyRuntime implements RuleConfiguredTargetFactory {
+
+ @Override
+ public ConfiguredTarget create(RuleContext ruleContext)
+ throws InterruptedException, RuleErrorException {
+ NestedSet<Artifact> files =
+ PrerequisiteArtifacts.nestedSet(ruleContext, "files", Mode.TARGET);
+ Artifact interpreter =
+ ruleContext.getPrerequisiteArtifact("interpreter", Mode.TARGET);
+ String interpreterPath =
+ ruleContext.attributes().get("interpreter_path", Type.STRING);
+
+ NestedSet<Artifact> all = NestedSetBuilder.<Artifact>stableOrder()
+ .addTransitive(files)
+ .build();
+
+ if (interpreter != null && !interpreterPath.isEmpty()) {
+ ruleContext.ruleError("interpreter and interpreter_path cannot be set at the same time.");
+ }
+
+ if (interpreter == null && interpreterPath.isEmpty()) {
+ ruleContext.ruleError("interpreter and interpreter_path cannot be empty at the same time.");
+ }
+
+ if (!interpreterPath.isEmpty() && !PathFragment.create(interpreterPath).isAbsolute()) {
+ ruleContext.attributeError("interpreter_path", "must be an absolute path.");
+ }
+
+ if (!interpreterPath.isEmpty() && !files.isEmpty()) {
+ ruleContext.ruleError("interpreter with an absolute path requires files to be empty.");
+ }
+
+ if (ruleContext.hasErrors()) {
+ return null;
+ }
+
+ BazelPyRuntimeProvider provider = BazelPyRuntimeProvider
+ .create(files, interpreter, interpreterPath);
+
+ return new RuleConfiguredTargetBuilder(ruleContext)
+ .addProvider(RunfilesProvider.class, RunfilesProvider.EMPTY)
+ .setFilesToBuild(all)
+ .addProvider(BazelPyRuntimeProvider.class, provider)
+ .build();
+ }
+
+}
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/python/BazelPyRuntimeProvider.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/python/BazelPyRuntimeProvider.java
new file mode 100644
index 0000000000..e94afad628
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/python/BazelPyRuntimeProvider.java
@@ -0,0 +1,44 @@
+// Copyright 2017 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 com.google.auto.value.AutoValue;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.TransitiveInfoProvider;
+import com.google.devtools.build.lib.collect.nestedset.NestedSet;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
+import javax.annotation.Nullable;
+
+/** Information about the Python runtime used by the <code>py_*</code> rules. */
+@AutoValue
+@Immutable
+public abstract class BazelPyRuntimeProvider implements TransitiveInfoProvider {
+ public static BazelPyRuntimeProvider create(
+ NestedSet<Artifact> files,
+ @Nullable Artifact interpreter,
+ @Nullable String interpreterPath) {
+
+ return new AutoValue_BazelPyRuntimeProvider(files, interpreter, interpreterPath);
+ }
+
+ public abstract NestedSet<Artifact> files();
+
+ @Nullable
+ public abstract Artifact interpreter();
+
+ @Nullable
+ public abstract String interpreterPath();
+
+}
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/python/BazelPyRuntimeRule.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/python/BazelPyRuntimeRule.java
new file mode 100644
index 0000000000..37cf1f6967
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/python/BazelPyRuntimeRule.java
@@ -0,0 +1,95 @@
+// Copyright 2017 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.LICENSE;
+import static com.google.devtools.build.lib.syntax.Type.STRING;
+
+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.packages.RuleClass;
+import com.google.devtools.build.lib.rules.python.PythonConfiguration;
+import com.google.devtools.build.lib.util.FileTypeSet;
+
+/** Rule definition for {@code python_runtime} */
+public final class BazelPyRuntimeRule implements RuleDefinition {
+
+ @Override
+ public RuleClass build(RuleClass.Builder builder, RuleDefinitionEnvironment env) {
+ return builder
+ .requiresConfigurationFragments(PythonConfiguration.class, BazelPythonConfiguration.class)
+
+ /* <!-- #BLAZE_RULE(py_runtime).ATTRIBUTE(files) -->
+ The set of files comprising this Python runtime.
+ <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
+ .add(attr("files", LABEL_LIST)
+ .allowedFileTypes(FileTypeSet.ANY_FILE)
+ .mandatory())
+
+ /* <!-- #BLAZE_RULE(py_runtime).ATTRIBUTE(interpreter) -->
+ The Python interpreter used in this runtime. Binary rules will be executed using this
+ binary.
+ <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
+ .add(attr("interpreter", LABEL)
+ .allowedFileTypes(FileTypeSet.ANY_FILE)
+ .singleArtifact())
+
+ /* <!-- #BLAZE_RULE(py_runtime).ATTRIBUTE(interpreter_path) -->
+ The absolute path of a Python interpreter. This attribute and interpreter attribute cannot
+ be set at the same time.
+ <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
+ .add(attr("interpreter_path", STRING))
+
+ .add(attr("output_licenses", LICENSE))
+ .build();
+ }
+
+ @Override
+ public Metadata getMetadata() {
+ return Metadata.builder()
+ .name("py_runtime")
+ .ancestors(BaseRuleClasses.BaseRule.class)
+ .factoryClass(BazelPyRuntime.class)
+ .build();
+ }
+}
+/*<!-- #BLAZE_RULE (NAME = py_runtime, TYPE = OTHER, FAMILY = Python) -->
+
+<p>
+Specifies the configuration for a Python runtime. This rule can either describe a Python runtime in
+the source tree or one at a well-known absolute path.
+</p>
+
+<h4 id="py_runtime">Example:</h4>
+
+<pre class="code">
+py_runtime(
+ name = "python-2.7.12",
+ files = glob(["python-2.7.12/**"]),
+ interpreter = "python-2.7.12/bin/python",
+)
+
+py_runtime(
+ name = "python-3.6.0",
+ files = [],
+ interpreter_path = "/opt/pyenv/versions/3.6.0/bin/python",
+)
+</pre>
+
+<!-- #END_BLAZE_RULE -->*/
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/python/BazelPythonConfiguration.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/python/BazelPythonConfiguration.java
index 90a888c3b2..3ca37d79a6 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/rules/python/BazelPythonConfiguration.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/python/BazelPythonConfiguration.java
@@ -17,19 +17,21 @@ package com.google.devtools.build.lib.bazel.rules.python;
import com.google.common.collect.ImmutableSet;
import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
import com.google.devtools.build.lib.analysis.config.BuildConfiguration.Fragment;
+import com.google.devtools.build.lib.analysis.config.BuildConfiguration.LabelConverter;
import com.google.devtools.build.lib.analysis.config.BuildOptions;
import com.google.devtools.build.lib.analysis.config.ConfigurationEnvironment;
import com.google.devtools.build.lib.analysis.config.ConfigurationFragmentFactory;
import com.google.devtools.build.lib.analysis.config.FragmentOptions;
import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException;
+import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
import com.google.devtools.build.lib.util.OS;
+import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.common.options.Converter;
import com.google.devtools.common.options.Option;
import com.google.devtools.common.options.OptionDocumentationCategory;
import com.google.devtools.common.options.OptionsParser.OptionUsageRestrictions;
import com.google.devtools.common.options.proto.OptionFilters.OptionEffectTag;
-import javax.annotation.Nullable;
/**
* Bazel-specific Python configuration.
@@ -65,7 +67,8 @@ public class BazelPythonConfiguration extends BuildConfiguration.Fragment {
category = "version",
documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
effectTags = {OptionEffectTag.UNKNOWN},
- help = "Local path to the Python2 executable."
+ help = "Local path to the Python2 executable. "
+ + "Deprecated, please use python_path or python_top instead."
)
public String python2Path;
@@ -76,11 +79,35 @@ public class BazelPythonConfiguration extends BuildConfiguration.Fragment {
category = "version",
documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
effectTags = {OptionEffectTag.UNKNOWN},
- help = "Local path to the Python3 executable."
+ help = "Local path to the Python3 executable. "
+ + "Deprecated, please use python_path or python_top instead."
)
public String python3Path;
@Option(
+ name = "python_top",
+ converter = LabelConverter.class,
+ defaultValue = "null",
+ category = "version",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ help =
+ "The label of py_runtime rule used for the Python interpreter invoked by Bazel."
+ )
+ public Label pythonTop;
+
+ @Option(
+ name = "python_path",
+ defaultValue = "python",
+ category = "version",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ help =
+ "The absolute path of the Python interpreter invoked by Bazel."
+ )
+ public String pythonPath;
+
+ @Option(
name = "experimental_python_import_all_repositories",
defaultValue = "true",
optionUsageRestrictions = OptionUsageRestrictions.UNDOCUMENTED,
@@ -103,11 +130,19 @@ public class BazelPythonConfiguration extends BuildConfiguration.Fragment {
* Loader for the Bazel-specific Python configuration.
*/
public static final class Loader implements ConfigurationFragmentFactory {
- @Nullable
@Override
public Fragment create(ConfigurationEnvironment env, BuildOptions buildOptions)
throws InvalidConfigurationException {
- return new BazelPythonConfiguration(buildOptions.get(Options.class));
+ BazelPythonConfiguration pythonConfiguration
+ = new BazelPythonConfiguration(buildOptions.get(Options.class));
+
+ String pythonPath = pythonConfiguration.getPythonPath();
+ if (!pythonPath.equals("python") && !PathFragment.create(pythonPath).isAbsolute()) {
+ throw new InvalidConfigurationException(
+ "python_path must be an absolute path when it is set.");
+ }
+
+ return pythonConfiguration;
}
@Override
@@ -135,7 +170,16 @@ public class BazelPythonConfiguration extends BuildConfiguration.Fragment {
return options.python3Path;
}
+ public Label getPythonTop() {
+ return options.pythonTop;
+ }
+
+ public String getPythonPath() {
+ return options.pythonPath;
+ }
+
public boolean getImportAllRepositories() {
return options.experimentalPythonImportAllRepositories;
}
+
}
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/python/BazelPythonSemantics.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/python/BazelPythonSemantics.java
index 135ca865fe..2e3ef240cd 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/rules/python/BazelPythonSemantics.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/python/BazelPythonSemantics.java
@@ -64,6 +64,7 @@ public class BazelPythonSemantics implements PythonSemantics {
@Override
public void collectRunfilesForBinary(RuleContext ruleContext, Builder builder, PyCommon common) {
+ addRuntime(ruleContext, builder);
}
@Override
@@ -131,13 +132,7 @@ public class BazelPythonSemantics implements PythonSemantics {
String main = common.determineMainExecutableSource(/*withWorkspaceName=*/ true);
Artifact executable = common.getExecutable();
BazelPythonConfiguration config = ruleContext.getFragment(BazelPythonConfiguration.class);
- String pythonBinary;
-
- switch (common.getVersion()) {
- case PY2: pythonBinary = config.getPython2Path(); break;
- case PY3: pythonBinary = config.getPython3Path(); break;
- default: throw new IllegalStateException();
- }
+ String pythonBinary = getPythonBinary(ruleContext, config);
if (!ruleContext.getConfiguration().buildPythonZip()) {
ruleContext.registerAction(
@@ -289,4 +284,46 @@ public class BazelPythonSemantics implements PythonSemantics {
.setMnemonic("PythonZipper")
.build(ruleContext));
}
+
+ private static void addRuntime(RuleContext ruleContext, Builder builder) {
+ BazelPyRuntimeProvider provider = ruleContext.getPrerequisite(
+ ":py_interpreter", Mode.TARGET, BazelPyRuntimeProvider.class);
+ if (provider != null && provider.interpreter() != null) {
+ builder.addArtifact(provider.interpreter());
+ // WARNING: we are adding the all Python runtime files here,
+ // and it would fail if the filenames of them contain spaces.
+ // Currently, we need to exclude them in py_runtime rules.
+ // Possible files in Python runtime which contain spaces in filenames:
+ // - https://github.com/pypa/setuptools/blob/master/setuptools/script%20(dev).tmpl
+ // - https://github.com/pypa/setuptools/blob/master/setuptools/command/launcher%20manifest.xml
+ builder.addTransitiveArtifacts(provider.files());
+ }
+ }
+
+ private static String getPythonBinary(
+ RuleContext ruleContext,
+ BazelPythonConfiguration config) {
+
+ String pythonBinary;
+
+ BazelPyRuntimeProvider provider = ruleContext.getPrerequisite(
+ ":py_interpreter", Mode.TARGET, BazelPyRuntimeProvider.class);
+
+ if (provider != null) {
+ // make use of py_runtime defined by --python_top
+ if (!provider.interpreterPath().isEmpty()) {
+ // absolute Python path in py_runtime
+ pythonBinary = provider.interpreterPath();
+ } else {
+ // checked in Python interpreter in py_runtime
+ pythonBinary = provider.interpreter().getExecPathString();
+ }
+ } else {
+ // make use of the Python interpreter in an absolute path
+ pythonBinary = config.getPythonPath();
+ }
+
+ return pythonBinary;
+ }
+
}
diff --git a/src/test/java/com/google/devtools/build/lib/bazel/rules/python/BazelPyRuntimeTest.java b/src/test/java/com/google/devtools/build/lib/bazel/rules/python/BazelPyRuntimeTest.java
new file mode 100644
index 0000000000..1a7d460ed0
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/bazel/rules/python/BazelPyRuntimeTest.java
@@ -0,0 +1,163 @@
+// Copyright 2017 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.common.truth.Truth.assertThat;
+
+import com.google.devtools.build.lib.actions.util.ActionsTestUtil;
+import com.google.devtools.build.lib.analysis.ConfiguredTarget;
+import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Unit tests for {@link BazelPyRuntime}.
+ */
+@RunWith(JUnit4.class)
+public final class BazelPyRuntimeTest extends BuildViewTestCase {
+
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ @Before
+ public final void setup() throws Exception {
+ scratch.file(
+ "py/BUILD",
+ "py_runtime(",
+ " name='py-2.7',",
+ " files = [",
+ " 'py-2.7/bin/python',",
+ " 'py-2.7/lib/libpython2.7.a',",
+ " ],",
+ " interpreter='py-2.7/bin/python',",
+ ")",
+ "",
+ "py_runtime(",
+ " name='py-3.6',",
+ " files = [],",
+ " interpreter_path='/opt/pyenv/versions/3.6.0/bin/python',",
+ ")",
+ "",
+ "py_runtime(",
+ " name='err-interpreter-and-path-both-set',",
+ " files = [],",
+ " interpreter='py-2.7/bin/python',",
+ " interpreter_path='/opt/pyenv/versions/3.6.0/bin/python',",
+ ")",
+ "",
+ "py_runtime(",
+ " name='err-interpreter-and-path-both-unset',",
+ " files = [],",
+ ")",
+ "",
+ "py_runtime(",
+ " name='err-path-not-absolute',",
+ " files = [],",
+ " interpreter_path='py-2.7/bin/python',",
+ ")",
+ "",
+ "py_runtime(",
+ " name='err-non-empty-files-with-path-absolute',",
+ " files = [",
+ " 'py-err/bin/python',",
+ " 'py-err/lib/libpython2.7.a',",
+ " ],",
+ " interpreter_path='/opt/pyenv/versions/3.6.0/bin/python',",
+ ")"
+ );
+
+ }
+
+ @Test
+ public void testCheckedInPyRuntime() throws Exception {
+ useConfiguration("--python_top=//py:py-2.7");
+ ConfiguredTarget target = getConfiguredTarget("//py:py-2.7");
+
+ assertThat(
+ ActionsTestUtil.prettyArtifactNames(
+ target.getProvider(BazelPyRuntimeProvider.class).files()))
+ .containsExactly("py/py-2.7/bin/python", "py/py-2.7/lib/libpython2.7.a");
+ assertThat(
+ target.getProvider(BazelPyRuntimeProvider.class).interpreter().getExecPathString())
+ .isEqualTo("py/py-2.7/bin/python");
+ assertThat(target.getProvider(BazelPyRuntimeProvider.class).interpreterPath())
+ .isEqualTo("");
+ }
+
+ @Test
+ public void testAbsolutePathPyRuntime() throws Exception {
+ useConfiguration("--python_top=//py:py-3.6");
+ ConfiguredTarget target = getConfiguredTarget("//py:py-3.6");
+
+ assertThat(
+ ActionsTestUtil.prettyArtifactNames(
+ target.getProvider(BazelPyRuntimeProvider.class).files()))
+ .isEmpty();
+ assertThat(
+ target.getProvider(BazelPyRuntimeProvider.class).interpreter())
+ .isNull();
+ assertThat(target.getProvider(BazelPyRuntimeProvider.class).interpreterPath())
+ .isEqualTo("/opt/pyenv/versions/3.6.0/bin/python");
+ }
+
+ @Test
+ public void testErrorWithInterpreterAndPathBothSet() throws Exception {
+ useConfiguration("--python_top=//py:err-interpreter-and-path-both-set");
+ try {
+ getConfiguredTarget("//py:err-interpreter-and-path-both-set");
+ } catch (Error e) {
+ assertThat(e.getMessage())
+ .contains("interpreter and interpreter_path cannot be set at the same time.");
+ }
+ }
+
+ @Test
+ public void testErrorWithInterpreterAndPathBothUnset() throws Exception {
+ useConfiguration("--python_top=//py:err-interpreter-and-path-both-unset");
+ try {
+ getConfiguredTarget("//py:err-interpreter-and-path-both-unset");
+ } catch (Error e) {
+ assertThat(e.getMessage())
+ .contains("interpreter and interpreter_path cannot be empty at the same time.");
+ }
+ }
+
+ @Test
+ public void testErrorWithPathNotAbsolute() throws Exception {
+ useConfiguration("--python_top=//py:err-path-not-absolute");
+ try {
+ getConfiguredTarget("//py:err-path-not-absolute");
+ } catch (Error e) {
+ assertThat(e.getMessage())
+ .contains("must be an absolute path.");
+ }
+ }
+
+ @Test
+ public void testPyRuntimeWithError() throws Exception {
+ useConfiguration("--python_top=//py:err-non-empty-files-with-path-absolute");
+ try {
+ getConfiguredTarget("//py:err-non-empty-files-with-path-absolute");
+ } catch (Error e) {
+ assertThat(e.getMessage())
+ .contains("interpreter with an absolute path requires files to be empty.");
+ }
+ }
+
+}