aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--examples/py_native/BUILD10
-rw-r--r--examples/py_native/bin.py2
-rw-r--r--examples/py_native/fibonacci/BUILD6
-rw-r--r--examples/py_native/fibonacci/fib.py8
-rw-r--r--examples/py_native/test.py4
-rw-r--r--src/main/java/com/google/devtools/build/lib/bazel/rules/python/BazelPyRuleClasses.java16
-rw-r--r--src/main/java/com/google/devtools/build/lib/bazel/rules/python/BazelPythonSemantics.java32
-rw-r--r--src/main/java/com/google/devtools/build/lib/bazel/rules/python/stub_template.txt7
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/python/PyBinary.java12
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/python/PyCommon.java25
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/python/PyLibrary.java8
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/python/PythonImportsProvider.java36
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/python/PythonSemantics.java12
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;