diff options
author | 2016-11-09 12:54:51 +0000 | |
---|---|---|
committer | 2016-11-09 13:45:00 +0000 | |
commit | eb0cfc57b58d646ad6a67c655fe0a76b4c22bdbd (patch) | |
tree | 2a79fbb7e02ae988431eb36d1aad6627c3158001 /src | |
parent | 90b5b19927540fc1e1c9e1a7fbd05c816ae0e4e2 (diff) |
Implemented default provider
--
MOS_MIGRATED_REVID=138625702
Diffstat (limited to 'src')
3 files changed, 278 insertions, 81 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleConfiguredTargetBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleConfiguredTargetBuilder.java index 814ae564b8..94c507787d 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleConfiguredTargetBuilder.java +++ b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleConfiguredTargetBuilder.java @@ -13,6 +13,7 @@ // limitations under the License. package com.google.devtools.build.lib.rules; +import com.google.common.base.Optional; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.devtools.build.lib.actions.Artifact; @@ -230,93 +231,141 @@ public final class SkylarkRuleConfiguredTargetBuilder { Map<String, Class<? extends TransitiveInfoProvider>> registeredProviderTypes) throws EvalException { Location loc = null; - Runfiles statelessRunfiles = null; - Runfiles dataRunfiles = null; - Runfiles defaultRunfiles = null; + Boolean isParsed = false; if (target instanceof SkylarkClassObject) { SkylarkClassObject struct = (SkylarkClassObject) target; loc = struct.getCreationLoc(); - for (String key : struct.getKeys()) { - if (key.equals("files")) { - // If we specify files_to_build we don't have the executable in it by default. - builder.setFilesToBuild(cast("files", struct, SkylarkNestedSet.class, Artifact.class, loc) - .getSet(Artifact.class)); - } else if (key.equals("runfiles")) { - statelessRunfiles = cast("runfiles", struct, Runfiles.class, loc); - } else if (key.equals("data_runfiles")) { - dataRunfiles = cast("data_runfiles", struct, Runfiles.class, loc); - } else if (key.equals("default_runfiles")) { - defaultRunfiles = cast("default_runfiles", struct, Runfiles.class, loc); - } else if (key.equals("output_groups")) { - addOutputGroups(struct.getValue(key), loc, builder); - } else if (key.equals("instrumented_files")) { - SkylarkClassObject insStruct = - cast("instrumented_files", struct, SkylarkClassObject.class, loc); - Location insLoc = insStruct.getCreationLoc(); - FileTypeSet fileTypeSet = FileTypeSet.ANY_FILE; - if (insStruct.getKeys().contains("extensions")) { - @SuppressWarnings("unchecked") - List<String> exts = cast( - "extensions", insStruct, SkylarkList.class, String.class, insLoc); - if (exts.isEmpty()) { - fileTypeSet = FileTypeSet.NO_FILE; - } else { - FileType[] fileTypes = new FileType[exts.size()]; - for (int i = 0; i < fileTypes.length; i++) { - fileTypes[i] = FileType.of(exts.get(i)); - } - fileTypeSet = FileTypeSet.of(fileTypes); - } - } - List<String> dependencyAttributes = Collections.emptyList(); - if (insStruct.getKeys().contains("dependency_attributes")) { - dependencyAttributes = - cast("dependency_attributes", insStruct, SkylarkList.class, String.class, insLoc); - } - List<String> sourceAttributes = Collections.emptyList(); - if (insStruct.getKeys().contains("source_attributes")) { - sourceAttributes = - cast("source_attributes", insStruct, SkylarkList.class, String.class, insLoc); - } - InstrumentationSpec instrumentationSpec = - new InstrumentationSpec(fileTypeSet) - .withSourceAttributes(sourceAttributes.toArray(new String[0])) - .withDependencyAttributes(dependencyAttributes.toArray(new String[0])); - InstrumentedFilesProvider instrumentedFilesProvider = - InstrumentedFilesCollector.collect( - ruleContext, - instrumentationSpec, - InstrumentedFilesCollector.NO_METADATA_COLLECTOR, - Collections.<Artifact>emptySet()); - builder.addProvider(InstrumentedFilesProvider.class, instrumentedFilesProvider); - } else if (registeredProviderTypes.containsKey(key)) { - Class<? extends TransitiveInfoProvider> providerType = registeredProviderTypes.get(key); - TransitiveInfoProvider provider = cast(key, struct, providerType, loc); - builder.addProvider(providerType, provider); - } else if (key.equals("providers")) { - Iterable iterable = cast(key, struct, Iterable.class, loc); - for (Object o : iterable) { - SkylarkClassObject declaredProvider = SkylarkType.cast(o, SkylarkClassObject.class, loc, - "The value of 'providers' should be a sequence of declared providers"); - builder.addSkylarkDeclaredProvider(declaredProvider, loc); - } - } else if (!key.equals("executable")) { - // We handled executable already. - builder.addSkylarkTransitiveInfo(key, struct.getValue(key), loc); - } - } + parseProviderKeys(struct, false, ruleContext, loc, executable, registeredProviderTypes, + builder); + isParsed = true; } else if (target instanceof Iterable) { loc = ruleContext.getRule().getRuleClassObject().getConfiguredTargetFunction().getLocation(); for (Object o : (Iterable) target) { SkylarkClassObject declaredProvider = SkylarkType.cast(o, SkylarkClassObject.class, loc, "A return value of rule implementation function should be " + "a sequence of declared providers"); - Location creationLoc = declaredProvider.getCreationLocOrNull(); - builder.addSkylarkDeclaredProvider(declaredProvider, - creationLoc != null ? creationLoc : loc); + if (declaredProvider.getConstructor().getKey().equals( + SkylarkRuleContext.getDefaultProvider().getKey())) { + parseProviderKeys(declaredProvider, true, ruleContext, loc, executable, + registeredProviderTypes, builder); + isParsed = true; + } else { + Location creationLoc = declaredProvider.getCreationLocOrNull(); + builder.addSkylarkDeclaredProvider(declaredProvider, + creationLoc != null ? creationLoc : loc); + } } } + if (!isParsed) { + addSimpleProviders(builder, ruleContext, loc, executable, null, null, null, null); + } + + try { + return builder.build(); + } catch (IllegalArgumentException e) { + throw new EvalException(loc, e.getMessage()); + } + } + + private static void parseProviderKeys( + SkylarkClassObject provider, + Boolean isDefaultProvider, + RuleContext ruleContext, + Location loc, + Artifact executable, + Map<String, Class<? extends TransitiveInfoProvider>> registeredProviderTypes, + RuleConfiguredTargetBuilder builder) throws EvalException { + Runfiles statelessRunfiles = null; + Runfiles dataRunfiles = null; + Runfiles defaultRunfiles = null; + + for (String key : provider.getKeys()) { + if (key.equals("files")) { + // If we specify files_to_build we don't have the executable in it by default. + builder.setFilesToBuild(cast("files", provider, SkylarkNestedSet.class, Artifact.class, loc) + .getSet(Artifact.class)); + } else if (key.equals("runfiles")) { + statelessRunfiles = cast("runfiles", provider, Runfiles.class, loc); + } else if (key.equals("data_runfiles")) { + dataRunfiles = cast("data_runfiles", provider, Runfiles.class, loc); + } else if (key.equals("default_runfiles")) { + defaultRunfiles = cast("default_runfiles", provider, Runfiles.class, loc); + } else if (key.equals("output_groups")) { + addOutputGroups(provider.getValue(key), loc, builder); + } else if (key.equals("instrumented_files")) { + SkylarkClassObject insStruct = + cast("instrumented_files", provider, SkylarkClassObject.class, loc); + Location insLoc = insStruct.getCreationLoc(); + FileTypeSet fileTypeSet = FileTypeSet.ANY_FILE; + if (insStruct.getKeys().contains("extensions")) { + @SuppressWarnings("unchecked") + List<String> exts = cast( + "extensions", insStruct, SkylarkList.class, String.class, insLoc); + if (exts.isEmpty()) { + fileTypeSet = FileTypeSet.NO_FILE; + } else { + FileType[] fileTypes = new FileType[exts.size()]; + for (int i = 0; i < fileTypes.length; i++) { + fileTypes[i] = FileType.of(exts.get(i)); + } + fileTypeSet = FileTypeSet.of(fileTypes); + } + } + List<String> dependencyAttributes = Collections.emptyList(); + if (insStruct.getKeys().contains("dependency_attributes")) { + dependencyAttributes = + cast("dependency_attributes", insStruct, SkylarkList.class, String.class, insLoc); + } + List<String> sourceAttributes = Collections.emptyList(); + if (insStruct.getKeys().contains("source_attributes")) { + sourceAttributes = + cast("source_attributes", insStruct, SkylarkList.class, String.class, insLoc); + } + InstrumentationSpec instrumentationSpec = + new InstrumentationSpec(fileTypeSet) + .withSourceAttributes(sourceAttributes.toArray(new String[0])) + .withDependencyAttributes(dependencyAttributes.toArray(new String[0])); + InstrumentedFilesProvider instrumentedFilesProvider = + InstrumentedFilesCollector.collect( + ruleContext, + instrumentationSpec, + InstrumentedFilesCollector.NO_METADATA_COLLECTOR, + Collections.<Artifact>emptySet()); + builder.addProvider(InstrumentedFilesProvider.class, instrumentedFilesProvider); + } else if (registeredProviderTypes.containsKey(key)) { + Class<? extends TransitiveInfoProvider> providerType = registeredProviderTypes.get(key); + TransitiveInfoProvider providerField = cast(key, provider, providerType, loc); + builder.addProvider(providerType, providerField); + } else if (isDefaultProvider) { + // Custom keys are not allowed for default providers + throw new EvalException(loc, "Invalid key for default provider: " + key); + } else if (key.equals("providers")) { + Iterable iterable = cast(key, provider, Iterable.class, loc); + for (Object o : iterable) { + SkylarkClassObject declaredProvider = SkylarkType.cast(o, SkylarkClassObject.class, loc, + "The value of 'providers' should be a sequence of declared providers"); + builder.addSkylarkDeclaredProvider(declaredProvider, loc); + } + } else if (!key.equals("executable")) { + // We handled executable already. + builder.addSkylarkTransitiveInfo(key, provider.getValue(key), loc); + } + } + + addSimpleProviders(builder, ruleContext, loc, executable, statelessRunfiles, dataRunfiles, + defaultRunfiles, (isDefaultProvider ? provider : null)); + } + + private static void addSimpleProviders(RuleConfiguredTargetBuilder builder, + RuleContext ruleContext, + Location loc, + Artifact executable, + Runfiles statelessRunfiles, + Runfiles dataRunfiles, + Runfiles defaultRunfiles, + SkylarkClassObject defaultProvider) throws EvalException { + if ((statelessRunfiles != null) && (dataRunfiles != null || defaultRunfiles != null)) { throw new EvalException(loc, "Cannot specify the provider 'runfiles' " + "together with 'data_runfiles' or 'default_runfiles'"); @@ -354,11 +403,16 @@ public final class SkylarkRuleConfiguredTargetBuilder { builder.addSkylarkDeclaredProvider(actions, loc); } - try { - return builder.build(); - } catch (IllegalArgumentException e) { - throw new EvalException(loc, e.getMessage()); - } + // Populate default provider fields and build it + ImmutableMap.Builder<String, Object> attrBuilder = new ImmutableMap.Builder<>(); + // TODO: Add actual attributes that users expect to access from default providers + attrBuilder.put("runfiles", runfilesProvider); + SkylarkClassObject statelessDefaultProvider = SkylarkRuleContext.getDefaultProvider().create( + attrBuilder.build(), "Default provider has no attribute '%s'"); + + // Add the default provider + builder.addSkylarkDeclaredProvider(statelessDefaultProvider, (defaultProvider == null) ? loc + : Optional.fromNullable(defaultProvider.getCreationLocOrNull()).or(loc)); } private static <T> T cast(String paramName, ClassObject struct, Class<T> expectedGenericType, diff --git a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleContext.java b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleContext.java index 37a77d4be2..ebd4db681c 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleContext.java +++ b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleContext.java @@ -422,6 +422,14 @@ public final class SkylarkRuleContext { return ruleContext; } + private static final SkylarkClassObjectConstructor DEFAULT_PROVIDER = + SkylarkClassObjectConstructor.createNativeConstructable("default_provider"); + + @SkylarkCallable(name = "default_provider", structField = true) + public static SkylarkClassObjectConstructor getDefaultProvider() { + return DEFAULT_PROVIDER; + } + @SkylarkCallable(name = "created_actions", doc = "For rules marked <code>_skylark_testable=True</code>, this returns an " + "<a href=\"ActionsSkylarkApiProvider.html\">actions</a> provider representing all " diff --git a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleImplementationFunctionsTest.java b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleImplementationFunctionsTest.java index cb22278311..802142f9d9 100644 --- a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleImplementationFunctionsTest.java +++ b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleImplementationFunctionsTest.java @@ -20,6 +20,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import com.google.common.base.Function; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; @@ -28,6 +29,7 @@ import com.google.devtools.build.lib.actions.Artifact; import com.google.devtools.build.lib.actions.util.ActionsTestUtil; import com.google.devtools.build.lib.analysis.ConfiguredTarget; import com.google.devtools.build.lib.analysis.Runfiles; +import com.google.devtools.build.lib.analysis.RunfilesProvider; import com.google.devtools.build.lib.analysis.SkylarkProviders; import com.google.devtools.build.lib.analysis.actions.FileWriteAction; import com.google.devtools.build.lib.analysis.actions.SpawnAction; @@ -171,6 +173,14 @@ public class SkylarkRuleImplementationFunctionsTest extends SkylarkTestCase { } } + private static final Function<Object, String> TO_STRING = + new Function<Object, String>() { + @Override + public String apply(Object input) { + return String.valueOf(input); + } + }; + @Test public void testSkylarkFunctionPosArgs() throws Exception { setupSkylarkFunction("a = mock('a', 'b', mandatory_key='c')"); @@ -831,6 +841,131 @@ public class SkylarkRuleImplementationFunctionsTest extends SkylarkTestCase { } @Test + public void testDefaultProvider() throws Exception { + scratch.file( + "test/foo.bzl", + "foo_provider = provider()", + "def _impl(ctx):", + " default = ctx.default_provider(", + " runfiles=ctx.runfiles(ctx.files.runs),", + " )", + " foo = foo_provider()", + " return [foo, default]", + "foo_rule = rule(", + " implementation = _impl,", + " attrs = {", + " 'runs': attr.label_list(allow_files=True),", + " }", + ")" + ); + scratch.file( + "test/bar.bzl", + "load(':foo.bzl', 'foo_provider')", + "def _impl(ctx):", + " dep = ctx.attr.deps[0]", + " provider = dep[ctx.default_provider]", // The goal is to test this object + " return struct(", // so we return it here + " default = provider,", + " )", + "bar_rule = rule(", + " implementation = _impl,", + " attrs = {", + " 'deps': attr.label_list(allow_files=True),", + " }", + ")" + ); + scratch.file( + "test/BUILD", + "load(':foo.bzl', 'foo_rule')", + "load(':bar.bzl', 'bar_rule')", + "foo_rule(name = 'dep_rule', runs = ['run.file', 'run2.file'])", + "bar_rule(name = 'my_rule', deps = [':dep_rule'])"); + ConfiguredTarget configuredTarget = getConfiguredTarget("//test:my_rule"); + Object provider = configuredTarget.getProvider(SkylarkProviders.class).getValue("default"); + assertThat(provider).isInstanceOf(SkylarkClassObject.class); + SkylarkClassObject defaultProvider = (SkylarkClassObject) provider; + assertThat((defaultProvider).getConstructor().getKey().getExportedName()) + .isEqualTo("default_provider"); + + // Test .runfiles + Object runfilesProvider = defaultProvider.getValue("runfiles"); + assertThat(runfilesProvider).isInstanceOf(RunfilesProvider.class); + assertThat(Iterables.transform( + ((RunfilesProvider) runfilesProvider).getDefaultRunfiles().getAllArtifacts(), TO_STRING) + ).containsExactly("Artifact:[/workspace[source]]test/run.file", + "Artifact:[/workspace[source]]test/run2.file"); + } + + @Test + public void testDefaultProviderProvidedImplicitly() throws Exception { + scratch.file( + "test/foo.bzl", + "foo_provider = provider()", + "def _impl(ctx):", + " foo = foo_provider()", + " return [foo]", + "foo_rule = rule(", + " implementation = _impl,", + ")" + ); + scratch.file( + "test/bar.bzl", + "load(':foo.bzl', 'foo_provider')", + "def _impl(ctx):", + " dep = ctx.attr.deps[0]", + " provider = dep[ctx.default_provider]", // The goal is to test this object + " return struct(", // so we return it here + " default = provider,", + " )", + "bar_rule = rule(", + " implementation = _impl,", + " attrs = {", + " 'deps': attr.label_list(allow_files=True),", + " }", + ")" + ); + scratch.file( + "test/BUILD", + "load(':foo.bzl', 'foo_rule')", + "load(':bar.bzl', 'bar_rule')", + "foo_rule(name = 'dep_rule')", + "bar_rule(name = 'my_rule', deps = [':dep_rule'])"); + ConfiguredTarget configuredTarget = getConfiguredTarget("//test:my_rule"); + Object provider = configuredTarget.getProvider(SkylarkProviders.class).getValue("default"); + assertThat(provider).isInstanceOf(SkylarkClassObject.class); + SkylarkClassObject defaultProvider = (SkylarkClassObject) provider; + assertThat((defaultProvider).getConstructor().getKey().getExportedName()) + .isEqualTo("default_provider"); + } + + @Test + public void testDefaultProviderUnknownFields() throws Exception { + scratch.file( + "test/foo.bzl", + "foo_provider = provider()", + "def _impl(ctx):", + " default = ctx.default_provider(", + " foo=ctx.runfiles(),", + " )", + " return [default]", + "foo_rule = rule(", + " implementation = _impl,", + ")" + ); + scratch.file( + "test/BUILD", + "load(':foo.bzl', 'foo_rule')", + "foo_rule(name = 'my_rule')" + ); + try { + getConfiguredTarget("//test:my_rule"); + fail(); + } catch (AssertionError expected) { + assertThat(expected.getMessage()).contains("Invalid key for default provider: foo"); + } + } + + @Test public void testDeclaredProviders() throws Exception { scratch.file( "test/foo.bzl", @@ -1228,7 +1363,7 @@ public class SkylarkRuleImplementationFunctionsTest extends SkylarkTestCase { }); checkEvalErrorContains( - "There Is No Message: SkylarkRuleImplementationFunctionsTest$2.invoke() in " + "There Is No Message: SkylarkRuleImplementationFunctionsTest$3.invoke() in " + "SkylarkRuleImplementationFunctionsTest.java:", // This test skips the line number since it was not consistent across local tests and TAP. "throw()"); |