diff options
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."); + } + } + +} |