diff options
13 files changed, 164 insertions, 14 deletions
diff --git a/examples/py_native/BUILD b/examples/py_native/BUILD index d6fdfa957b..5a82223cf8 100644 --- a/examples/py_native/BUILD +++ b/examples/py_native/BUILD @@ -7,7 +7,10 @@ filegroup( py_binary( name = "bin", srcs = ["bin.py"], - deps = [":lib"], + deps = [ + ":lib", + "//examples/py_native/fibonacci", + ], ) py_library( @@ -18,7 +21,10 @@ py_library( py_test( name = "test", srcs = ["test.py"], - deps = [":lib"], + deps = [ + ":lib", + "//examples/py_native/fibonacci", + ], ) py_test( diff --git a/examples/py_native/bin.py b/examples/py_native/bin.py index f79379a237..7b656278f6 100644 --- a/examples/py_native/bin.py +++ b/examples/py_native/bin.py @@ -1,4 +1,6 @@ """A tiny example binary for the native Python rules of Bazel.""" from examples.py_native.lib import GetNumber +from fib import Fib print "The number is %d" % GetNumber() +print "Fib(5) == %d" % Fib(5) diff --git a/examples/py_native/fibonacci/BUILD b/examples/py_native/fibonacci/BUILD new file mode 100644 index 0000000000..c3ee9b0fea --- /dev/null +++ b/examples/py_native/fibonacci/BUILD @@ -0,0 +1,6 @@ +py_library( + name = "fibonacci", + srcs = ["fib.py"], + imports = ["."], + visibility = ["//examples/py_native:__pkg__"], +) diff --git a/examples/py_native/fibonacci/fib.py b/examples/py_native/fibonacci/fib.py new file mode 100644 index 0000000000..645a937a0e --- /dev/null +++ b/examples/py_native/fibonacci/fib.py @@ -0,0 +1,8 @@ +"""An example binary to test the imports attribute of native Python rules.""" + + +def Fib(n): + if n == 0 or n == 1: + return 1 + else: + return Fib(n-1) + Fib(n-2) diff --git a/examples/py_native/test.py b/examples/py_native/test.py index 811eee144e..f9543aa727 100644 --- a/examples/py_native/test.py +++ b/examples/py_native/test.py @@ -1,6 +1,8 @@ """A tiny example binary for the native Python rules of Bazel.""" + import unittest from examples.py_native.lib import GetNumber +from fib import Fib class TestGetNumber(unittest.TestCase): @@ -8,6 +10,8 @@ class TestGetNumber(unittest.TestCase): def test_ok(self): self.assertEquals(GetNumber(), 42) + def test_fib(self): + self.assertEquals(Fib(5), 8) if __name__ == '__main__': unittest.main() 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 36d3a1ff1a..7c0fc717b4 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 @@ -20,7 +20,9 @@ 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.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; @@ -63,6 +65,20 @@ public final class BazelPyRuleClasses { .override(builder.copy("deps") .allowedRuleClasses(ALLOWED_RULES_IN_DEPS) .allowedFileTypes()) + /* <!-- #BLAZE_RULE($base_py).ATTRIBUTE(imports) --> + List of import directories to be added to the <code>PYTHONPATH</code>. + <p> + Subject to <a href="make-variables.html">"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="#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) --> A string specifying the Python major version(s) that the <code>.py</code> source files listed in the <code>srcs</code> of this rule are compatible with. 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 abff076bb0..7092e5fd5d 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 @@ -14,6 +14,7 @@ package com.google.devtools.build.lib.bazel.rules.python; +import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import com.google.devtools.build.lib.actions.Artifact; import com.google.devtools.build.lib.analysis.RuleContext; @@ -22,13 +23,18 @@ import com.google.devtools.build.lib.analysis.RunfilesSupport; import com.google.devtools.build.lib.analysis.actions.TemplateExpansionAction; import com.google.devtools.build.lib.analysis.actions.TemplateExpansionAction.Substitution; import com.google.devtools.build.lib.analysis.actions.TemplateExpansionAction.Template; +import com.google.devtools.build.lib.collect.nestedset.NestedSet; import com.google.devtools.build.lib.rules.cpp.CcLinkParamsStore; import com.google.devtools.build.lib.rules.python.PyCommon; import com.google.devtools.build.lib.rules.python.PythonSemantics; import com.google.devtools.build.lib.rules.test.InstrumentedFilesCollector.InstrumentationSpec; +import com.google.devtools.build.lib.syntax.Type; import com.google.devtools.build.lib.util.FileTypeSet; +import com.google.devtools.build.lib.vfs.PathFragment; +import java.util.ArrayList; import java.util.Collection; +import java.util.List; /** * Functionality specific to the Python rules in Bazel. @@ -64,8 +70,29 @@ public class BazelPythonSemantics implements PythonSemantics { } @Override + public List<PathFragment> getImports(RuleContext ruleContext) { + List<PathFragment> result = new ArrayList<>(); + PathFragment packageFragment = ruleContext.getLabel().getPackageIdentifier().getPathFragment(); + for (String importsAttr : ruleContext.attributes().get("imports", Type.STRING_LIST)) { + importsAttr = ruleContext.expandMakeVariables("includes", importsAttr); + if (importsAttr.startsWith("/")) { + ruleContext.attributeWarning("imports", + "ignoring invalid absolute path '" + importsAttr + "'"); + continue; + } + PathFragment importsPath = packageFragment.getRelative(importsAttr).normalize(); + if (!importsPath.isNormalized()) { + ruleContext.attributeError("imports", + "Path references a path above the execution root."); + } + result.add(importsPath); + } + return result; + } + + @Override public void createExecutable(RuleContext ruleContext, PyCommon common, - CcLinkParamsStore ccLinkParamsStore) { + CcLinkParamsStore ccLinkParamsStore, NestedSet<PathFragment> imports) { String main = common.determineMainExecutableSource(); BazelPythonConfiguration config = ruleContext.getFragment(BazelPythonConfiguration.class); String pythonBinary; @@ -82,7 +109,8 @@ public class BazelPythonSemantics implements PythonSemantics { STUB_TEMPLATE, ImmutableList.of( Substitution.of("%main%", main), - Substitution.of("%python_binary%", pythonBinary)), + Substitution.of("%python_binary%", pythonBinary), + Substitution.of("%imports%", Joiner.on(":").join(imports))), true)); } diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/python/stub_template.txt b/src/main/java/com/google/devtools/build/lib/bazel/rules/python/stub_template.txt index fdc63cb6b7..2e7677ec6d 100644 --- a/src/main/java/com/google/devtools/build/lib/bazel/rules/python/stub_template.txt +++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/python/stub_template.txt @@ -33,6 +33,10 @@ def FindPythonBinary(): # Case 4: Path has to be looked up in the search path. return SearchPath(PYTHON_BINARY) +def CreatePythonPathEntries(python_imports, module_space): + parts = python_imports.split(':'); + return [module_space] + ["%s/%s" % (module_space, path) for path in parts] + def Main(): args = sys.argv[1:] @@ -61,7 +65,8 @@ def Main(): raise AssertionError('Cannot find .runfiles directory for %s' % sys.argv[0]) - python_path_entries = [module_space] + python_imports = '%imports%' + python_path_entries = CreatePythonPathEntries(python_imports, module_space) external_dir = os.path.join(module_space, 'external') if os.path.isdir(external_dir): diff --git a/src/main/java/com/google/devtools/build/lib/rules/python/PyBinary.java b/src/main/java/com/google/devtools/build/lib/rules/python/PyBinary.java index 01829b670f..ae048a0a03 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/python/PyBinary.java +++ b/src/main/java/com/google/devtools/build/lib/rules/python/PyBinary.java @@ -21,10 +21,12 @@ import com.google.devtools.build.lib.analysis.RuleContext; import com.google.devtools.build.lib.analysis.Runfiles; import com.google.devtools.build.lib.analysis.RunfilesProvider; import com.google.devtools.build.lib.analysis.RunfilesSupport; +import com.google.devtools.build.lib.collect.nestedset.NestedSet; import com.google.devtools.build.lib.rules.RuleConfiguredTargetFactory; import com.google.devtools.build.lib.rules.cpp.CcLinkParams; import com.google.devtools.build.lib.rules.cpp.CcLinkParamsProvider; import com.google.devtools.build.lib.rules.cpp.CcLinkParamsStore; +import com.google.devtools.build.lib.vfs.PathFragment; import java.util.ArrayList; import java.util.List; @@ -65,7 +67,12 @@ public abstract class PyBinary implements RuleConfiguredTargetFactory { return null; } - semantics.createExecutable(ruleContext, common, ccLinkParamsStore); + NestedSet<PathFragment> imports = common.collectImports(ruleContext, semantics); + if (ruleContext.hasErrors()) { + return null; + } + + semantics.createExecutable(ruleContext, common, ccLinkParamsStore, imports); Runfiles commonRunfiles = collectCommonRunfiles(ruleContext, common, semantics); Runfiles.Builder defaultRunfilesBuilder = new Runfiles.Builder(ruleContext.getWorkspaceName()) @@ -99,7 +106,8 @@ public abstract class PyBinary implements RuleConfiguredTargetFactory { .setFilesToBuild(common.getFilesToBuild()) .add(RunfilesProvider.class, runfilesProvider) .setRunfilesSupport(runfilesSupport, common.getExecutable()) - .add(CcLinkParamsProvider.class, new CcLinkParamsProvider(ccLinkParamsStore)); + .add(CcLinkParamsProvider.class, new CcLinkParamsProvider(ccLinkParamsStore)) + .add(PythonImportsProvider.class, new PythonImportsProvider(imports)); } private static Runfiles collectCommonRunfiles(RuleContext ruleContext, PyCommon common, diff --git a/src/main/java/com/google/devtools/build/lib/rules/python/PyCommon.java b/src/main/java/com/google/devtools/build/lib/rules/python/PyCommon.java index 969955f55a..9b6813fad5 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/python/PyCommon.java +++ b/src/main/java/com/google/devtools/build/lib/rules/python/PyCommon.java @@ -276,14 +276,31 @@ public final class PyCommon { } private NestedSet<Artifact> collectTransitivePythonSources() { - NestedSetBuilder<Artifact> builder = - NestedSetBuilder.compileOrder(); + NestedSetBuilder<Artifact> builder = NestedSetBuilder.compileOrder(); collectTransitivePythonSourcesFrom(getTargetDeps(), builder); - addSourceFiles(builder, ruleContext - .getPrerequisiteArtifacts("srcs", Mode.TARGET).filter(PyRuleClasses.PYTHON_SOURCE).list()); + addSourceFiles(builder, + ruleContext.getPrerequisiteArtifacts("srcs", Mode.TARGET) + .filter(PyRuleClasses.PYTHON_SOURCE).list()); return builder.build(); } + public NestedSet<PathFragment> collectImports( + RuleContext ruleContext, PythonSemantics semantics) { + NestedSetBuilder<PathFragment> builder = NestedSetBuilder.compileOrder(); + builder.addAll(semantics.getImports(ruleContext)); + collectTransitivePythonImports(builder); + return builder.build(); + } + + private void collectTransitivePythonImports(NestedSetBuilder<PathFragment> builder) { + for (TransitiveInfoCollection dep : getTargetDeps()) { + if (dep.getProvider(PythonImportsProvider.class) != null) { + PythonImportsProvider provider = dep.getProvider(PythonImportsProvider.class); + builder.addTransitive(provider.getTransitivePythonImports()); + } + } + } + /** * Checks that the source file version is compatible with the Python interpreter. */ diff --git a/src/main/java/com/google/devtools/build/lib/rules/python/PyLibrary.java b/src/main/java/com/google/devtools/build/lib/rules/python/PyLibrary.java index eb2e9314c7..95f4c8364e 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/python/PyLibrary.java +++ b/src/main/java/com/google/devtools/build/lib/rules/python/PyLibrary.java @@ -27,6 +27,7 @@ import com.google.devtools.build.lib.rules.RuleConfiguredTargetFactory; import com.google.devtools.build.lib.rules.cpp.CcLinkParams; import com.google.devtools.build.lib.rules.cpp.CcLinkParamsProvider; import com.google.devtools.build.lib.rules.cpp.CcLinkParamsStore; +import com.google.devtools.build.lib.vfs.PathFragment; import java.util.ArrayList; import java.util.List; @@ -50,7 +51,6 @@ public abstract class PyLibrary implements RuleConfiguredTargetFactory { common.validatePackageName(); semantics.validate(ruleContext, common); - List<Artifact> srcs = common.validateSrcs(); List<Artifact> allOutputs = new ArrayList<>(semantics.precompiledPythonFiles(ruleContext, srcs, common)); @@ -69,6 +69,11 @@ public abstract class PyLibrary implements RuleConfiguredTargetFactory { } }; + NestedSet<PathFragment> imports = common.collectImports(ruleContext, semantics); + if (ruleContext.hasErrors()) { + return null; + } + Runfiles.Builder runfilesBuilder = new Runfiles.Builder(ruleContext.getWorkspaceName()); if (common.getConvertedFiles() != null) { runfilesBuilder.addSymlinks(common.getConvertedFiles()); @@ -85,6 +90,7 @@ public abstract class PyLibrary implements RuleConfiguredTargetFactory { .setFilesToBuild(filesToBuild) .add(RunfilesProvider.class, RunfilesProvider.simple(runfilesBuilder.build())) .add(CcLinkParamsProvider.class, new CcLinkParamsProvider(ccLinkParamsStore)) + .add(PythonImportsProvider.class, new PythonImportsProvider(imports)) .build(); } } diff --git a/src/main/java/com/google/devtools/build/lib/rules/python/PythonImportsProvider.java b/src/main/java/com/google/devtools/build/lib/rules/python/PythonImportsProvider.java new file mode 100644 index 0000000000..03bb52aa70 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/rules/python/PythonImportsProvider.java @@ -0,0 +1,36 @@ +// Copyright 2016 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.rules.python; + +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 com.google.devtools.build.lib.vfs.PathFragment; + +/** + * A {@link TransitiveInfoProvider} that supplies import directories for Python dependencies. + */ +@Immutable +public final class PythonImportsProvider implements TransitiveInfoProvider { + + private final NestedSet<PathFragment> transitivePythonImports; + + public PythonImportsProvider(NestedSet<PathFragment> transitivePythonImports) { + this.transitivePythonImports = transitivePythonImports; + } + + public NestedSet<PathFragment> getTransitivePythonImports() { + return transitivePythonImports; + } +} diff --git a/src/main/java/com/google/devtools/build/lib/rules/python/PythonSemantics.java b/src/main/java/com/google/devtools/build/lib/rules/python/PythonSemantics.java index abf6a88246..2168a879ce 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/python/PythonSemantics.java +++ b/src/main/java/com/google/devtools/build/lib/rules/python/PythonSemantics.java @@ -17,10 +17,13 @@ import com.google.devtools.build.lib.actions.Artifact; import com.google.devtools.build.lib.analysis.RuleContext; import com.google.devtools.build.lib.analysis.Runfiles; import com.google.devtools.build.lib.analysis.RunfilesSupport; +import com.google.devtools.build.lib.collect.nestedset.NestedSet; import com.google.devtools.build.lib.rules.cpp.CcLinkParamsStore; import com.google.devtools.build.lib.rules.test.InstrumentedFilesCollector.InstrumentationSpec; +import com.google.devtools.build.lib.vfs.PathFragment; import java.util.Collection; +import java.util.List; /** * Pluggable semantics for Python rules. @@ -56,16 +59,21 @@ public interface PythonSemantics { RuleContext ruleContext, Collection<Artifact> sources, PyCommon common); /** + * Returns a list of PathFragments for the import paths specified in the imports attribute. + */ + List<PathFragment> getImports(RuleContext ruleContext); + + /** * Create the actual executable artifact. * * <p>This should create a generating action for {@code common.getExecutable()}. */ void createExecutable(RuleContext ruleContext, PyCommon common, - CcLinkParamsStore ccLinkParamsStore); + CcLinkParamsStore ccLinkParamsStore, NestedSet<PathFragment> imports); /** * Called at the end of the analysis of {@code py_binary} rules. - * @throws InterruptedException + * @throws InterruptedException */ void postInitBinary(RuleContext ruleContext, RunfilesSupport runfilesSupport, PyCommon common) throws InterruptedException; |