diff --git a/site/docs/skylark/cookbook.md b/site/docs/skylark/cookbook.md
index 4952d91a20c7e8..c4f718faea4a0f 100644
--- a/site/docs/skylark/cookbook.md
+++ b/site/docs/skylark/cookbook.md
@@ -59,6 +59,76 @@ load("/pkg/extension", "macro")
macro(name = "myrule")
```
+## Conditional instantiation.
+
+Macros can look at previously instantiated rules. This is done with
+`native.rule`, which returns information on a single rule defined in the same
+`BUILD` file, eg.,
+
+```python
+native.rule("descriptor_proto")
+```
+
+This is useful to avoid instantiating the same rule twice, which is an
+error. For example, the following rule will simulate a test suite, instantiating
+tests for diverse flavors of the same test.
+
+`extension.bzl`:
+
+```python
+def system_test(test_file, flavor):
+ n = "system_test_%s_%s_test" % (test_file, flavor)
+ if native.rule(n) == None:
+ native.py_test(
+ name = n,
+ srcs = [ "test_driver.py", test_file ],
+ args = [ "--flavor=" + flavor])
+ return n
+
+def system_test_suite(name, flavors=["default"], test_files):
+ ts = []
+ for flavor in flavors:
+ for test in test_files:
+ ts.append(system_test(name, flavor, test))
+ native.test_suite(name = name, tests = ts)
+```
+
+In the following BUILD file, note how `(fast, basic_test.py)` is emitted for
+both the `smoke` test suite and the `thorough` test suite.
+
+```python
+load("/pkg/extension", "system_test_suite")
+
+# Run all files through the 'fast' flavor.
+system_test_suite("smoke", flavors=["fast"], glob(["*_test.py"]))
+
+# Run the basic test through all flavors.
+system_test_suite("thorough", flavors=["fast", "debug", "opt"], ["basic_test.py"])
+```
+
+
+## Aggregating over the BUILD file.
+
+Macros can collect information from the BUILD file as processed so far. We call
+this aggregation. The typical example is collecting data from all rules of a
+certain kind. This is done by calling `native.rules`, which returns a
+dictionary representing all rules defined so far in the current BUILD file. The
+dictionary has entries of the form `name` => `rule`, with the values using the
+same format as `native.rule`.
+
+```python
+def archive_cc_src_files(tag):
+ """Create an archive of all C++ sources that have the given tag."""
+ all_src = []
+ for r in native.rules().values():
+ if tag in r["tags"] and r["kind"] == "cc_library":
+ all_src.append(r["srcs"])
+ native.genrule(cmd = "zip $@ $^", srcs = all_src, outs = ["out.zip"])
+```
+
+Since `native.rules` constructs a potentially large dictionary, you should avoid
+calling it repeatedly within BUILD file.
+
## Empty rule
Minimalist example of a rule that does nothing. If you build it, the target will
diff --git a/src/main/java/com/google/devtools/build/lib/packages/Package.java b/src/main/java/com/google/devtools/build/lib/packages/Package.java
index bc7804475cb349..ce49383f206bc0 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/Package.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/Package.java
@@ -49,6 +49,8 @@
import java.util.Map;
import java.util.Set;
+import javax.annotation.Nullable;
+
/**
* A package, which is a container of {@link Rule}s, each of
* which contains a dictionary of named attributes.
@@ -1045,6 +1047,11 @@ public Collection getTargets() {
return Package.getTargets(targets);
}
+ @Nullable
+ public Target getTarget(String name) {
+ return targets.get(name);
+ }
+
/**
* Returns an (immutable, unordered) view of all the targets belonging to
* this package which are instances of the specified class.
diff --git a/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java b/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java
index 1de065eed8d318..f738a0d252bf73 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java
@@ -66,9 +66,12 @@
import com.google.devtools.build.lib.vfs.UnixGlob;
import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.TreeMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
@@ -828,6 +831,112 @@ public Runtime.NoneType invoke(String name, SkylarkList packages, SkylarkList in
}
};
+ @Nullable
+ static Map callGetRuleFunction(
+ String name, FuncallExpression ast, Environment env)
+ throws EvalException, ConversionException {
+ PackageContext context = getContext(env, ast);
+ Target target = context.pkgBuilder.getTarget(name);
+
+ return targetDict(target);
+ }
+
+ @Nullable
+ private static Map targetDict(Target target) {
+ if (target == null && !(target instanceof Rule)) {
+ return null;
+ }
+ Map values = new TreeMap<>();
+
+ Rule rule = (Rule) target;
+ AttributeContainer cont = rule.getAttributeContainer();
+ for (Attribute attr : rule.getAttributes()) {
+ if (!Character.isAlphabetic(attr.getName().charAt(0))) {
+ continue;
+ }
+
+ Object val = skylarkifyValue(cont.getAttr(attr.getName()), target.getPackage());
+ if (val == null) {
+ continue;
+ }
+ values.put(attr.getName(), val);
+ }
+
+ values.put("name", rule.getName());
+ values.put("kind", rule.getRuleClass());
+ return values;
+ }
+
+ /**
+ * Converts back to type that will work in BUILD and skylark,
+ * such as string instead of label, SkylarkList instead of List,
+ * Returns null if we don't want to export the value.
+ *
+ *
All of the types returned are immutable. If we want, we can change this to
+ * immutable in the future, but this is the safe choice for now.
+ */
+ private static Object skylarkifyValue(Object val, Package pkg) {
+ if (val == null) {
+ return null;
+ }
+ if (val instanceof Integer) {
+ return val;
+ }
+ if (val instanceof String) {
+ return val;
+ }
+ if (val instanceof Label) {
+ Label l = (Label) val;
+ if (l.getPackageName().equals(pkg.getName())) {
+ return ":" + l.getName();
+ }
+ return l.getCanonicalForm();
+ }
+ if (val instanceof List) {
+ List