diff options
author | 2017-11-29 23:29:29 -0800 | |
---|---|---|
committer | 2017-11-29 23:31:28 -0800 | |
commit | 2874302e49f3e92bb455dd3ce11f67260b618746 (patch) | |
tree | f8d09546a2f6da81a1b711b2f44281c46de00548 | |
parent | c7633e582e49f8a4c47c8ea0060ac3c9b0b6d71b (diff) |
Add android_local_test base class.
RELNOTES: None
PiperOrigin-RevId: 177414939
6 files changed, 844 insertions, 84 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 c6cd791be9..d8ae98d451 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 @@ -83,6 +83,7 @@ import com.google.devtools.build.lib.rules.android.AndroidBinaryOnlyRule; import com.google.devtools.build.lib.rules.android.AndroidConfiguration; import com.google.devtools.build.lib.rules.android.AndroidDeviceRule; import com.google.devtools.build.lib.rules.android.AndroidLibraryBaseRule; +import com.google.devtools.build.lib.rules.android.AndroidLocalTestBaseRule; import com.google.devtools.build.lib.rules.android.AndroidNeverlinkAspect; import com.google.devtools.build.lib.rules.android.AndroidRuleClasses; import com.google.devtools.build.lib.rules.android.AndroidSkylarkCommon; @@ -506,6 +507,7 @@ public class BazelRuleClassProvider { builder.addRuleDefinition(new AarImportBaseRule()); builder.addRuleDefinition(new BazelAarImportRule()); builder.addRuleDefinition(new AndroidDeviceRule()); + builder.addRuleDefinition(new AndroidLocalTestBaseRule()); builder.addSkylarkAccessibleTopLevels("android_common", new AndroidSkylarkCommon()); builder.addSkylarkAccessibleTopLevels("java_common", diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidLocalTestBase.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidLocalTestBase.java new file mode 100644 index 0000000000..f76c01b928 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidLocalTestBase.java @@ -0,0 +1,554 @@ +// 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.rules.android; +package com.google.devtools.build.lib.rules.android; + +import static com.google.devtools.build.lib.packages.BuildType.LABEL_LIST; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.analysis.ConfiguredTarget; +import com.google.devtools.build.lib.analysis.FileProvider; +import com.google.devtools.build.lib.analysis.OutputGroupProvider; +import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder; +import com.google.devtools.build.lib.analysis.RuleConfiguredTargetFactory; +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.analysis.TransitiveInfoCollection; +import com.google.devtools.build.lib.analysis.actions.CustomCommandLine; +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.analysis.configuredtargets.RuleConfiguredTarget.Mode; +import com.google.devtools.build.lib.collect.nestedset.NestedSet; +import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; +import com.google.devtools.build.lib.collect.nestedset.Order; +import com.google.devtools.build.lib.rules.android.AndroidConfiguration.AndroidAaptVersion; +import com.google.devtools.build.lib.rules.java.ClasspathConfiguredFragment; +import com.google.devtools.build.lib.rules.java.JavaCommon; +import com.google.devtools.build.lib.rules.java.JavaCompilationArgs; +import com.google.devtools.build.lib.rules.java.JavaCompilationArgs.ClasspathType; +import com.google.devtools.build.lib.rules.java.JavaCompilationArtifacts; +import com.google.devtools.build.lib.rules.java.JavaCompilationHelper; +import com.google.devtools.build.lib.rules.java.JavaConfiguration; +import com.google.devtools.build.lib.rules.java.JavaConfiguration.OneVersionEnforcementLevel; +import com.google.devtools.build.lib.rules.java.JavaHelper; +import com.google.devtools.build.lib.rules.java.JavaInfo; +import com.google.devtools.build.lib.rules.java.JavaPrimaryClassProvider; +import com.google.devtools.build.lib.rules.java.JavaRuleOutputJarsProvider; +import com.google.devtools.build.lib.rules.java.JavaRunfilesProvider; +import com.google.devtools.build.lib.rules.java.JavaRuntimeClasspathProvider; +import com.google.devtools.build.lib.rules.java.JavaSemantics; +import com.google.devtools.build.lib.rules.java.JavaSkylarkApiProvider; +import com.google.devtools.build.lib.rules.java.JavaSourceInfoProvider; +import com.google.devtools.build.lib.rules.java.JavaSourceJarsProvider; +import com.google.devtools.build.lib.rules.java.JavaTargetAttributes; +import com.google.devtools.build.lib.rules.java.JavaToolchainProvider; +import com.google.devtools.build.lib.rules.java.OneVersionCheckActionBuilder; +import com.google.devtools.build.lib.rules.java.ProguardHelper; +import com.google.devtools.build.lib.rules.java.proto.GeneratedExtensionRegistryProvider; +import com.google.devtools.build.lib.syntax.Type; +import com.google.devtools.build.lib.util.OS; +import com.google.devtools.build.lib.vfs.PathFragment; +import java.util.ArrayList; +import java.util.List; + +/** A base implementation for the "android_local_test" rule. */ +public abstract class AndroidLocalTestBase implements RuleConfiguredTargetFactory { + + @Override + public ConfiguredTarget create(RuleContext ruleContext) + throws InterruptedException, RuleErrorException { + + ruleContext.checkSrcsSamePackage(true); + + JavaSemantics javaSemantics = createJavaSemantics(); + AndroidSemantics androidSemantics = createAndroidSemantics(); + + final JavaCommon javaCommon = new JavaCommon(ruleContext, javaSemantics); + // Use the regular Java javacopts. Enforcing android-compatible Java + // (-source 7 -target 7 and no TWR) is unnecessary for robolectric tests + // since they run on a JVM, not an android device. + JavaTargetAttributes.Builder attributesBuilder = + getJavaTargetAttributes(ruleContext, javaCommon); + + // Create the final merged manifest + ResourceDependencies resourceDependencies = getResourceDependencies(ruleContext); + ApplicationManifest applicationManifest = + getApplicationManifest(ruleContext, androidSemantics, resourceDependencies); + Artifact manifest = applicationManifest.getManifest(); + String manifestLocation = manifest.getRunfilesPathString(); + + // Create merged resources and assets zip file + Artifact resourcesZip = + ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_RESOURCES_ZIP); + String resourcesLocation = resourcesZip.getRunfilesPathString(); + + // Create the final merged R class + Artifact resourcesClassJar = + ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_RESOURCES_CLASS_JAR); + ResourceApk resourceApk = + applicationManifest.packBinaryWithDataAndResources( + ruleContext, + ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_RESOURCES_APK), + resourceDependencies, + ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_R_TXT), + ResourceFilterFactory.fromRuleContext(ruleContext), + ImmutableList.of(), /* list of uncompressed extensions */ + false, /* crunch png */ + ProguardHelper.getProguardConfigArtifact(ruleContext, ""), + null, /* mainDexProguardCfg */ + false, /* conditionalKeepRules */ + ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_PROCESSED_MANIFEST), + resourcesZip, + DataBinding.isEnabled(ruleContext) ? DataBinding.getLayoutInfoFile(ruleContext) : null, + null, /* featureOfArtifact */ + null /* featureAfterArtifact */); + compileResourceJar(ruleContext, resourceApk, resourcesClassJar); + attributesBuilder.addRuntimeClassPathEntry(resourcesClassJar); + + // Exclude the Rs from the library from the runtime classpath. + NestedSet<Artifact> excludedRuntimeArtifacts = getLibraryResourceJars(ruleContext); + attributesBuilder.addExcludedArtifacts(excludedRuntimeArtifacts); + + // Create robolectric test_config.properties file + String name = "_robolectric/" + ruleContext.getRule().getName() + "_test_config.properties"; + Artifact propertiesFile = ruleContext.getGenfilesArtifact(name); + + Template template = + Template.forResource(AndroidLocalTestBase.class, "robolectric_properties_template.txt"); + List<Substitution> substitutions = new ArrayList<>(); + substitutions.add(Substitution.of("%android_merged_manifest%", manifestLocation)); + substitutions.add( + Substitution.of("%android_merged_resources%", "jar:file:" + resourcesLocation + "!/res")); + substitutions.add( + Substitution.of("%android_merged_assets%", "jar:file:" + resourcesLocation + "!/assets")); + substitutions.add( + Substitution.of( + "%android_custom_package%", resourceApk.getPrimaryResource().getJavaPackage())); + + ruleContext.registerAction( + new TemplateExpansionAction( + ruleContext.getActionOwner(), + propertiesFile, + template, + substitutions, + false /* makeExecutable */)); + // Add the properties file to the test jar as a java resource + attributesBuilder.addResource( + PathFragment.create("com/android/tools/test_config.properties"), propertiesFile); + + String testClass = + getAndCheckTestClass(ruleContext, ImmutableList.copyOf(attributesBuilder.getSourceFiles())); + getAndCheckTestSupport(ruleContext); + javaSemantics.checkForProtoLibraryAndJavaProtoLibraryOnSameProto(ruleContext, javaCommon); + if (ruleContext.hasErrors()) { + return null; + } + + Artifact srcJar = ruleContext.getImplicitOutputArtifact(JavaSemantics.JAVA_BINARY_SOURCE_JAR); + JavaSourceJarsProvider.Builder javaSourceJarsProviderBuilder = + JavaSourceJarsProvider.builder() + .addSourceJar(srcJar) + .addAllTransitiveSourceJars(javaCommon.collectTransitiveSourceJars(srcJar)); + + Artifact classJar = ruleContext.getImplicitOutputArtifact(JavaSemantics.JAVA_BINARY_CLASS_JAR); + JavaRuleOutputJarsProvider.Builder javaRuleOutputJarsProviderBuilder = + JavaRuleOutputJarsProvider.builder() + .addOutputJar( + classJar, + classJar, + srcJar == null ? ImmutableList.<Artifact>of() : ImmutableList.of(srcJar)); + + JavaCompilationArtifacts.Builder javaArtifactsBuilder = new JavaCompilationArtifacts.Builder(); + JavaCompilationHelper helper = + getJavaCompilationHelperWithDependencies( + ruleContext, javaSemantics, javaCommon, attributesBuilder); + Artifact instrumentationMetadata = + helper.createInstrumentationMetadata(classJar, javaArtifactsBuilder); + Artifact executable; // the artifact for the rule itself + if (OS.getCurrent() == OS.WINDOWS + && ruleContext.getConfiguration().enableWindowsExeLauncher()) { + executable = + ruleContext.getImplicitOutputArtifact(ruleContext.getTarget().getName() + ".exe"); + } else { + executable = ruleContext.createOutputArtifact(); + } + NestedSetBuilder<Artifact> filesToBuildBuilder = + NestedSetBuilder.<Artifact>stableOrder().add(classJar).add(executable); + + GeneratedExtensionRegistryProvider generatedExtensionRegistryProvider = + javaSemantics.createGeneratedExtensionRegistry( + ruleContext, + javaCommon, + filesToBuildBuilder, + javaArtifactsBuilder, + javaRuleOutputJarsProviderBuilder, + javaSourceJarsProviderBuilder); + + String mainClass = + getMainClass( + ruleContext, + javaSemantics, + helper, + executable, + instrumentationMetadata, + javaArtifactsBuilder, + attributesBuilder); + + // JavaCompilationHelper.getAttributes() builds the JavaTargetAttributes, after which the + // JavaTargetAttributes becomes immutable. This is an extra safety check to avoid inconsistent + // states (i.e. building the JavaTargetAttributes then modifying it again). + addJavaClassJarToArtifactsBuilder(javaArtifactsBuilder, helper.getAttributes(), classJar); + + // The gensrc jar is created only if the target uses annotation processing. Otherwise, + // it is null, and the source jar action will not depend on the compile action. + Artifact manifestProtoOutput = helper.createManifestProtoOutput(classJar); + + Artifact genClassJar = null; + Artifact genSourceJar = null; + if (helper.usesAnnotationProcessing()) { + genClassJar = helper.createGenJar(classJar); + genSourceJar = helper.createGensrcJar(classJar); + helper.createGenJarAction(classJar, manifestProtoOutput, genClassJar); + } + Artifact outputDepsProtoArtifact = + helper.createOutputDepsProtoArtifact(classJar, javaArtifactsBuilder); + javaRuleOutputJarsProviderBuilder.setJdeps(outputDepsProtoArtifact); + + helper.createCompileAction( + classJar, + manifestProtoOutput, + genSourceJar, + outputDepsProtoArtifact, + instrumentationMetadata); + helper.createSourceJarAction(srcJar, genSourceJar); + + setUpJavaCommon(javaCommon, helper, javaArtifactsBuilder.build()); + + Artifact launcher = JavaHelper.launcherArtifactForTarget(javaSemantics, ruleContext); + + String javaExecutable; + if (javaSemantics.isJavaExecutableSubstitution()) { + javaExecutable = JavaCommon.getJavaBinSubstitution(ruleContext, launcher); + } else { + javaExecutable = JavaCommon.getJavaExecutableForStub(ruleContext, launcher); + } + + javaSemantics.createStubAction( + ruleContext, + javaCommon, + getJvmFlags(ruleContext, testClass), + executable, + mainClass, + javaExecutable); + + Artifact oneVersionOutputArtifact = null; + OneVersionEnforcementLevel oneVersionEnforcementLevel = + ruleContext.getFragment(JavaConfiguration.class).oneVersionEnforcementLevel(); + if (oneVersionEnforcementLevel != OneVersionEnforcementLevel.OFF) { + oneVersionOutputArtifact = + OneVersionCheckActionBuilder.newBuilder() + .withEnforcementLevel(oneVersionEnforcementLevel) + .outputArtifact( + ruleContext.getImplicitOutputArtifact(JavaSemantics.JAVA_ONE_VERSION_ARTIFACT)) + .useToolchain(JavaToolchainProvider.from(ruleContext)) + .checkJars( + NestedSetBuilder.fromNestedSet(helper.getAttributes().getRuntimeClassPath()) + .add(classJar) + .build()) + .build(ruleContext); + } + + NestedSet<Artifact> filesToBuild = filesToBuildBuilder.build(); + + Runfiles defaultRunfiles = + collectDefaultRunfiles( + ruleContext, javaCommon, filesToBuild, manifest, resourcesClassJar, resourcesZip); + + RunfilesSupport runfilesSupport = + RunfilesSupport.withExecutable( + ruleContext, defaultRunfiles, executable, CustomCommandLine.EMPTY); + + JavaSourceJarsProvider sourceJarsProvider = javaSourceJarsProviderBuilder.build(); + NestedSet<Artifact> transitiveSourceJars = sourceJarsProvider.getTransitiveSourceJars(); + + RuleConfiguredTargetBuilder builder = new RuleConfiguredTargetBuilder(ruleContext); + + if (generatedExtensionRegistryProvider != null) { + builder.addProvider( + GeneratedExtensionRegistryProvider.class, generatedExtensionRegistryProvider); + } + + JavaRuleOutputJarsProvider ruleOutputJarsProvider = javaRuleOutputJarsProviderBuilder.build(); + + javaCommon.addTransitiveInfoProviders(builder, filesToBuild, classJar); + javaCommon.addGenJarsProvider(builder, genClassJar, genSourceJar); + + // Just confirming that there are no aliases being used here. + AndroidFeatureFlagSetProvider.getAndValidateFlagMapFromRuleContext(ruleContext); + + if (oneVersionOutputArtifact != null) { + builder.addOutputGroup(OutputGroupProvider.HIDDEN_TOP_LEVEL, oneVersionOutputArtifact); + } + + NestedSet<Artifact> extraFilesToRun = + NestedSetBuilder.create(Order.STABLE_ORDER, runfilesSupport.getRunfilesMiddleman()); + + JavaInfo javaInfo = + JavaInfo.Builder.create() + .addProvider(JavaSourceJarsProvider.class, sourceJarsProvider) + .addProvider(JavaRuleOutputJarsProvider.class, ruleOutputJarsProvider) + .build(); + + return builder + .setFilesToBuild(filesToBuild) + .addSkylarkTransitiveInfo( + JavaSkylarkApiProvider.NAME, JavaSkylarkApiProvider.fromRuleContext()) + .addNativeDeclaredProvider(javaInfo) + .addProvider( + RunfilesProvider.class, + RunfilesProvider.withData( + defaultRunfiles, + new Runfiles.Builder(ruleContext.getWorkspaceName()) + .merge(runfilesSupport) + .build())) + .addFilesToRun(extraFilesToRun) + .setRunfilesSupport(runfilesSupport, executable) + .addProvider( + JavaRuntimeClasspathProvider.class, + new JavaRuntimeClasspathProvider(javaCommon.getRuntimeClasspath())) + .addProvider(JavaPrimaryClassProvider.class, new JavaPrimaryClassProvider(testClass)) + .addProvider( + JavaSourceInfoProvider.class, + JavaSourceInfoProvider.fromJavaTargetAttributes(helper.getAttributes(), javaSemantics)) + .addOutputGroup(JavaSemantics.SOURCE_JARS_OUTPUT_GROUP, transitiveSourceJars) + .build(); + } + + /** Creates the final merged R class with all of the transitive resources. */ + private void compileResourceJar( + RuleContext ruleContext, ResourceApk resourceApk, Artifact resourceClassJar) + throws InterruptedException, RuleErrorException { + + if (resourceApk.getResourceJavaClassJar() == null) { + new RClassGeneratorActionBuilder(ruleContext) + .targetAaptVersion(AndroidAaptVersion.chooseTargetAaptVersion(ruleContext)) + .withPrimary(resourceApk.getPrimaryResource()) + .withDependencies(resourceApk.getResourceDependencies()) + .setClassJarOut(resourceClassJar) + .build(); + } else { + Preconditions.checkArgument(resourceApk.getResourceJavaClassJar().equals(resourceClassJar)); + } + } + + /** + * Returns a merged {@link ApplicationManifest} for the rule. The final merged manifest will be + * merged into the manifest provided on the rule, or into a placeholder manifest if one is not + * provided + * @throws InterruptedException + * @throws RuleErrorException + */ + private ApplicationManifest getApplicationManifest( + RuleContext ruleContext, + AndroidSemantics androidSemantics, + ResourceDependencies resourceDependencies) + throws InterruptedException, RuleErrorException { + ApplicationManifest applicationManifest; + + if (LocalResourceContainer.definesAndroidResources(ruleContext.attributes())) { + LocalResourceContainer.validateRuleContext(ruleContext); + ApplicationManifest ruleManifest = androidSemantics.getManifestForRule(ruleContext); + applicationManifest = ruleManifest.mergeWith(ruleContext, resourceDependencies, false); + } else { + // we don't have a manifest, merge like android_library with a stub manifest + ApplicationManifest dummyManifest = ApplicationManifest.generatedManifest(ruleContext); + applicationManifest = dummyManifest.mergeWith(ruleContext, resourceDependencies, false); + } + return applicationManifest; + } + + /** Returns the transitive closure of resource dependencies, including those specified on the rule + * if present. */ + private ResourceDependencies getResourceDependencies(RuleContext ruleContext) { + return LocalResourceContainer.definesAndroidResources(ruleContext.attributes()) + ? ResourceDependencies.fromRuleDeps(ruleContext, false /* neverlink */) + : ResourceDependencies.fromRuleResourceAndDeps(ruleContext, false /* neverlink */); + } + + private static void setUpJavaCommon( + JavaCommon common, + JavaCompilationHelper helper, + JavaCompilationArtifacts javaCompilationArtifacts) { + common.setJavaCompilationArtifacts(javaCompilationArtifacts); + common.setClassPathFragment( + new ClasspathConfiguredFragment( + common.getJavaCompilationArtifacts(), + helper.getAttributes(), + false, + helper.getBootclasspathOrDefault())); + } + + private void addJavaClassJarToArtifactsBuilder( + JavaCompilationArtifacts.Builder javaArtifactsBuilder, + JavaTargetAttributes attributes, + Artifact classJar) { + if (attributes.hasSources() || attributes.hasResources()) { + // We only want to add a jar to the classpath of a dependent rule if it has content. + javaArtifactsBuilder.addRuntimeJar(classJar); + } + } + + private Runfiles collectDefaultRunfiles( + RuleContext ruleContext, + JavaCommon javaCommon, + NestedSet<Artifact> filesToBuild, + Artifact manifest, + Artifact resourcesClassJar, + Artifact resourcesZip) + throws RuleErrorException { + + Runfiles.Builder builder = new Runfiles.Builder(ruleContext.getWorkspaceName()); + builder.addTransitiveArtifacts(filesToBuild); + builder.addArtifacts(javaCommon.getJavaCompilationArtifacts().getRuntimeJars()); + + builder.addRunfiles(ruleContext, RunfilesProvider.DEFAULT_RUNFILES); + builder.add(ruleContext, JavaRunfilesProvider.TO_RUNFILES); + + List<TransitiveInfoCollection> depsForRunfiles = new ArrayList<>(); + + if (ruleContext.isAttrDefined("$robolectric", LABEL_LIST)) { + depsForRunfiles.addAll(ruleContext.getPrerequisites("$robolectric", Mode.TARGET)); + } + depsForRunfiles.addAll(ruleContext.getPrerequisites("runtime_deps", Mode.TARGET)); + + builder.addArtifacts(getRuntimeJarsForTargets(getAndCheckTestSupport(ruleContext))); + + builder.addTargets(depsForRunfiles, JavaRunfilesProvider.TO_RUNFILES); + builder.addTargets(depsForRunfiles, RunfilesProvider.DEFAULT_RUNFILES); + + if (ruleContext.getConfiguration().isCodeCoverageEnabled()) { + Artifact instrumentedJar = javaCommon.getJavaCompilationArtifacts().getInstrumentedJar(); + if (instrumentedJar != null) { + builder.addArtifact(instrumentedJar); + } + } + + // We assume that the runtime jars will not have conflicting artifacts + // with the same root relative path + builder.addTransitiveArtifactsWrappedInStableOrder(javaCommon.getRuntimeClasspath()); + + // Add the JDK files if it comes from P4 (see java_stub_template.txt). + TransitiveInfoCollection javabaseTarget = ruleContext.getPrerequisite(":jvm", Mode.TARGET); + + if (javabaseTarget != null) { + builder.addTransitiveArtifacts( + javabaseTarget.getProvider(FileProvider.class).getFilesToBuild()); + } + + builder.addArtifact(manifest); + builder.addArtifact(resourcesClassJar); + builder.addArtifact(resourcesZip); + + return builder.build(); + } + + private NestedSet<Artifact> getRuntimeJarsForTargets(TransitiveInfoCollection deps) { + // The dep may be a simple JAR and not a java rule, hence we can't simply do + // dep.getProvider(JavaCompilationArgsProvider.class).getRecursiveJavaCompilationArgs(), + // so we reuse the logic within JavaCompilationArgs to handle both scenarios. + JavaCompilationArgs args = + JavaCompilationArgs.builder() + .addTransitiveTargets( + ImmutableList.of(deps), /*recursive=*/ true, ClasspathType.RUNTIME_ONLY) + .build(); + return args.getRuntimeJars(); + } + + private static String getAndCheckTestClass( + RuleContext ruleContext, ImmutableList<Artifact> sourceFiles) { + String testClass = + ruleContext.getRule().isAttrDefined("test_class", Type.STRING) + ? ruleContext.attributes().get("test_class", Type.STRING) + : ""; + + if (testClass.isEmpty()) { + testClass = JavaCommon.determinePrimaryClass(ruleContext, sourceFiles); + if (testClass == null) { + ruleContext.ruleError( + "cannot determine junit.framework.Test class " + + "(Found no source file '" + + ruleContext.getTarget().getName() + + ".java' and package name doesn't include 'java' or 'javatests'. " + + "You might want to rename the rule or add a 'test_class' " + + "attribute.)"); + } + } + return testClass; + } + + private static NestedSet<Artifact> getLibraryResourceJars(RuleContext ruleContext) { + Iterable<AndroidLibraryResourceClassJarProvider> libraryResourceJarProviders = + AndroidCommon.getTransitivePrerequisites( + ruleContext, Mode.TARGET, AndroidLibraryResourceClassJarProvider.class); + + NestedSetBuilder<Artifact> libraryResourceJarsBuilder = NestedSetBuilder.naiveLinkOrder(); + for (AndroidLibraryResourceClassJarProvider provider : libraryResourceJarProviders) { + libraryResourceJarsBuilder.addTransitive(provider.getResourceClassJars()); + } + return libraryResourceJarsBuilder.build(); + } + + /** Get JavaSemantics */ + protected abstract JavaSemantics createJavaSemantics(); + + /** Get AndroidSemantics */ + protected abstract AndroidSemantics createAndroidSemantics(); + + /** Get JavaTargetAttributes Builder */ + protected abstract JavaTargetAttributes.Builder getJavaTargetAttributes( + RuleContext ruleContext, JavaCommon javaCommon); + + /** Set test and robolectric specific jvm flags */ + protected abstract ImmutableList<String> getJvmFlags(RuleContext ruleContext, String testClass); + + /** Return the testrunner main class */ + protected abstract String getMainClass( + RuleContext ruleContext, + JavaSemantics javaSemantics, + JavaCompilationHelper helper, + Artifact executable, + Artifact instrumentationMetadata, + JavaCompilationArtifacts.Builder javaArtifactsBuilder, + JavaTargetAttributes.Builder attributesBuilder) + throws InterruptedException; + + /** + * Add compilation dependencies to the java compilation helper. + * @throws RuleErrorException + */ + protected abstract JavaCompilationHelper getJavaCompilationHelperWithDependencies( + RuleContext ruleContext, + JavaSemantics javaSemantics, + JavaCommon javaCommon, + JavaTargetAttributes.Builder javaTargetAttributesBuilder) + throws RuleErrorException; + + /** Get the testrunner from the rule */ + protected abstract TransitiveInfoCollection getAndCheckTestSupport(RuleContext ruleContext) + throws RuleErrorException; +} diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidLocalTestBaseRule.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidLocalTestBaseRule.java new file mode 100644 index 0000000000..ef13513cda --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidLocalTestBaseRule.java @@ -0,0 +1,168 @@ +// 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.rules.android; +package com.google.devtools.build.lib.rules.android; + +import static com.google.devtools.build.lib.packages.Attribute.ConfigurationTransition.HOST; +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_KEYED_STRING_DICT; +import static com.google.devtools.build.lib.packages.BuildType.LABEL_LIST; +import static com.google.devtools.build.lib.rules.android.AndroidRuleClasses.getAndroidSdkLabel; +import static com.google.devtools.build.lib.syntax.Type.STRING; +import static com.google.devtools.build.lib.syntax.Type.STRING_DICT; + +import com.google.common.collect.ImmutableList; +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.packages.RuleClass.Builder; +import com.google.devtools.build.lib.packages.RuleClass.Builder.RuleClassType; +import com.google.devtools.build.lib.rules.config.ConfigFeatureFlagProvider; +import com.google.devtools.build.lib.rules.java.JavaConfiguration; +import com.google.devtools.build.lib.rules.java.Jvm; +import com.google.devtools.build.lib.util.FileTypeSet; + +/** Base rule definition for android_local_test */ +public class AndroidLocalTestBaseRule implements RuleDefinition { + + @Override + public RuleClass build(Builder builder, RuleDefinitionEnvironment environment) { + return builder + .requiresConfigurationFragments(JavaConfiguration.class, Jvm.class) + + // Update documentation for inherited attributes + + /* <!-- #BLAZE_RULE($android_local_test_base).ATTRIBUTE(deps) --> + The list of libraries to be tested as well as additional libraries to be linked + in to the target. + All resources, assets and manifest files declared in Android rules in the transitive + closure of this attribute are made available in the test. + <p> + The list of allowed rules in <code>deps</code> are <code>android_library</code>, + <code>aar_import</code>, <code>java_import</code>, <code>java_library</code>, + <code>java_lite_proto_library</code>, and <code>proto_library</code>. + <!-- #END_BLAZE_RULE.ATTRIBUTE --> */ + + /* <!-- #BLAZE_RULE($android_local_test_base).ATTRIBUTE(srcs) --> + The list of source files that are processed to create the target. + Required except in special case described below. + <p><code>srcs</code> files of type <code>.java</code> are compiled. + <em>For readability's sake</em>, it is not good to put the name of a + generated <code>.java</code> source file into the <code>srcs</code>. + Instead, put the depended-on rule name in the <code>srcs</code>, as + described below. + </p> + + <p><code>srcs</code> files of type <code>.srcjar</code> are unpacked and + compiled. (This is useful if you need to generate a set of .java files with + a genrule or build extension.) + </p> + + <p>All other files are ignored, as long as + there is at least one file of a file type described above. Otherwise an + error is raised. + </p> + + <p> + The <code>srcs</code> attribute is required and cannot be empty, unless + <code>runtime_deps</code> is specified. + </p> + <!-- #END_BLAZE_RULE.ATTRIBUTE --> */ + + .add( + attr(AndroidFeatureFlagSetProvider.FEATURE_FLAG_ATTR, LABEL_KEYED_STRING_DICT) + .undocumented("the feature flag feature has not yet been launched") + .allowedRuleClasses("config_feature_flag") + .allowedFileTypes() + .nonconfigurable("defines an aspect of configuration") + .mandatoryProviders(ImmutableList.of(ConfigFeatureFlagProvider.id()))) + .add(AndroidFeatureFlagSetProvider.getWhitelistAttribute(environment)) + // TODO(b/38314524): Move $android_resources_busybox and :android_sdk to a separate + // rule so they're not defined in multiple places + .add( + attr("$android_resources_busybox", LABEL) + .cfg(HOST) + .exec() + .value(environment.getToolsLabel(AndroidRuleClasses.DEFAULT_RESOURCES_BUSYBOX))) + .add( + attr(":android_sdk", LABEL) + .allowedRuleClasses("android_sdk", "filegroup") + .value( + getAndroidSdkLabel(environment.getToolsLabel(AndroidRuleClasses.DEFAULT_SDK)))) + /* <!-- #BLAZE_RULE($android_local_test_base).ATTRIBUTE(test_class) --> + The Java class to be loaded by the test runner.<br/> + <p> + This attribute specifies the name of a Java class to be run by + this test. It is rare to need to set this. If this argument is omitted, the Java class + whose name corresponds to the <code>name</code> of this + <code>android_local_test</code> rule will be used. + The test class needs to be annotated with <code>org.junit.runner.RunWith</code>. + </p> + <!-- #END_BLAZE_RULE.ATTRIBUTE --> */ + .add(attr("test_class", STRING)) // every test class adds this + /* <!-- #BLAZE_RULE($android_local_test_base).ATTRIBUTE(manifest_values) --> + A dictionary of values to be overridden in the manifest. Any instance of ${name} in the + manifest will be replaced with the value corresponding to name in this dictionary. + <code>applicationId</code>, <code>versionCode</code>, <code>versionName</code>, + <code>minSdkVersion</code>, <code>targetSdkVersion</code> and + <code>maxSdkVersion</code> will also override the corresponding attributes + of the manifest and + uses-sdk tags. <code>packageName</code> will be ignored and will be set from either + <code>applicationId</code> if + specified or the package in the manifest. + It is not necessary to have a manifest on the rule in order to use manifest_values. + <!-- #END_BLAZE_RULE.ATTRIBUTE --> */ + .add(attr("manifest_values", STRING_DICT)) + /* <!-- #BLAZE_RULE($android_local_test_base).ATTRIBUTE(manifest) --> + The name of the Android manifest file, normally <code>AndroidManifest.xml</code>. + Must be defined if resource_files or assets are defined or if any of the manifests from + the libraries under test have a <code>minSdkVersion</code> tag in them. + <!-- #END_BLAZE_RULE.ATTRIBUTE --> */ + .add(attr("manifest", LABEL).allowedFileTypes(FileTypeSet.ANY_FILE)) + /* <!-- #BLAZE_RULE($android_local_test_base).ATTRIBUTE(resource_files) --> + The list of test resources to be packaged. This is typically a <code>glob</code> + of all files under the <code>res</code> directory. + <p> + Generated files (from genrules) can be referenced by + <a href="../build-ref.html#labels">Label</a> here as well. The only restriction is that + the generated outputs must be under the same "<code>res</code>" directory as any other + resource files that are included. It is rare to need this. + </p> + <!-- #END_BLAZE_RULE.ATTRIBUTE --> */ + .add(attr("resource_files", LABEL_LIST).allowedFileTypes(FileTypeSet.ANY_FILE)) + /* <!-- #BLAZE_RULE($android_local_test_base).ATTRIBUTE(assets_dir) --> + The string giving the path to the files in <code>assets</code>. + The pair <code>assets</code> and <code>assets_dir</code> describe packaged + assets and either both attributes should be provided or neither of them. + <!-- #END_BLAZE_RULE.ATTRIBUTE --> */ + .add(attr("assets_dir", STRING)) + /* <!-- #BLAZE_RULE($android_local_test_base).ATTRIBUTE(assets) --> + The list of test assets to be packaged. This is typically a <code>glob</code> of all files + under the <code>assets</code> directory. You can also reference other rules (any rule that + produces files) or exported files in the other packages, as long as all those files are + under the <code>assets_dir</code> directory in the corresponding package. It is rare to + need this. + <!-- #END_BLAZE_RULE.ATTRIBUTE --> */ + .add(attr("assets", LABEL_LIST).allowedFileTypes(FileTypeSet.ANY_FILE)) + .build(); + } + + @Override + public Metadata getMetadata() { + return RuleDefinition.Metadata.builder() + .name("$android_local_test_base") + .type(RuleClassType.ABSTRACT) + .build(); + } +}
\ No newline at end of file diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/robolectric_properties_template.txt b/src/main/java/com/google/devtools/build/lib/rules/android/robolectric_properties_template.txt new file mode 100644 index 0000000000..dba03448b2 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/rules/android/robolectric_properties_template.txt @@ -0,0 +1,4 @@ +android_merged_manifest=%android_merged_manifest% +android_merged_resources=%android_merged_resources% +android_merged_assets=%android_merged_assets% +android_custom_package=%android_custom_package%
\ No newline at end of file diff --git a/src/test/java/com/google/devtools/build/lib/rules/android/AbstractAndroidLocalTestTest.java b/src/test/java/com/google/devtools/build/lib/rules/android/AbstractAndroidLocalTestTest.java index 83f72f10d2..0c790c63e0 100644 --- a/src/test/java/com/google/devtools/build/lib/rules/android/AbstractAndroidLocalTestTest.java +++ b/src/test/java/com/google/devtools/build/lib/rules/android/AbstractAndroidLocalTestTest.java @@ -16,27 +16,18 @@ package com.google.devtools.build.lib.rules.android; import static com.google.common.truth.Truth.assertThat; import static com.google.devtools.build.lib.actions.util.ActionsTestUtil.prettyArtifactNames; -import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; -import com.google.common.collect.Iterables; import com.google.devtools.build.lib.actions.Action; 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.FilesToRunProvider; import com.google.devtools.build.lib.analysis.OutputGroupProvider; import com.google.devtools.build.lib.analysis.RunfilesProvider; -import com.google.devtools.build.lib.analysis.RunfilesSupport; import com.google.devtools.build.lib.analysis.actions.FileWriteAction; import com.google.devtools.build.lib.analysis.actions.TemplateExpansionAction; import com.google.devtools.build.lib.analysis.util.BuildViewTestCase; import com.google.devtools.build.lib.rules.java.JavaSemantics; import com.google.devtools.build.lib.util.FileType; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.LinkedHashSet; import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; @@ -174,81 +165,6 @@ public abstract class AbstractAndroidLocalTestTest extends BuildViewTestCase { } @Test - public void testResourcesFromRuntimeDepsAreIncluded() throws Exception { - writeFile( - "java/android/BUILD", - "android_library(name = 'dummyLibraryOne',", - " exports_manifest = 1,", - " manifest = 'AndroidManifest.xml',", - " resource_files = ['res/drawable/dummyResource1.png'],", - " srcs = ['libraryOne.java'])", - "", - "android_library(name = 'dummyLibraryTwo',", - " exports_manifest = 1,", - " manifest = 'AndroidManifest.xml',", - " resource_files = ['res/drawable/dummyResource2.png'],", - " srcs = ['libraryTwo.java'])"); - final String libraryOne = "dummyLibraryOne"; - final String libraryTwo = "dummyLibraryTwo"; - - checkForCorrectLibraries( - "no-runtime", Arrays.asList(libraryOne), Collections.<String>emptyList()); - checkForCorrectLibraries( - "no-runtime-2", Arrays.asList(libraryOne, libraryTwo), Collections.<String>emptyList()); - checkForCorrectLibraries( - "only-runtime", Collections.<String>emptyList(), Arrays.asList(libraryOne)); - checkForCorrectLibraries( - "only-runtime-2", Collections.<String>emptyList(), Arrays.asList(libraryOne, libraryTwo)); - checkForCorrectLibraries( - "runtime-and-dep", Arrays.asList(libraryOne), Arrays.asList(libraryTwo)); - checkForCorrectLibraries( - "runtime-and-dep-2", Arrays.asList(libraryTwo), Arrays.asList(libraryOne)); - } - - private String createDepArrayString(Collection<String> deps) { - if (deps.isEmpty()) { - return ""; - } - ArrayList<String> list = new ArrayList<>(); - for (String dep : deps) { - list.add(String.format("//java/android:%s", dep)); - } - return "'" + Joiner.on("', '").join(list) + "'"; - } - - private void checkForCorrectLibraries( - String name, Collection<String> deps, Collection<String> runtimeDeps) throws Exception { - final String libraryFormat = - "java/android/%s_processed_manifest/AndroidManifest.xml:" + "java/android/%s.aar"; - writeFile( - String.format("javatests/android/%s/BUILD", name), - "android_local_test(name = 'dummyTest',", - " srcs = ['test.java'],", - " runtime_deps = [" + createDepArrayString(runtimeDeps) + "],", - " deps = [" + createDepArrayString(deps) + "])"); - ConfiguredTarget target = - getConfiguredTarget(String.format("//javatests/android/%s:dummyTest", name)); - assertThat(target).isNotNull(); - RunfilesSupport support = target.getProvider(FilesToRunProvider.class).getRunfilesSupport(); - assertThat(support).isNotNull(); - Artifact deployJar = - getFileConfiguredTarget(String.format("//javatests/android/%s:dummyTest_deploy.jar", name)) - .getArtifact(); - List<String> deployJarInputs = - ActionsTestUtil.prettyArtifactNames(getGeneratingAction(deployJar).getInputs()); - - LinkedHashSet<String> uniqueDeps = new LinkedHashSet<>(); - for (String dep : Iterables.concat(runtimeDeps, deps)) { - uniqueDeps.add(String.format(libraryFormat, dep, dep)); - assertThat(deployJarInputs).contains("java/android/" + dep + "_resources.jar"); - } - checkRuntimeSupportInputs(uniqueDeps, support); - } - - protected abstract void checkRuntimeSupportInputs( - LinkedHashSet<String> uniqueDeps, RunfilesSupport support) throws Exception; - - @Test public void testFeatureFlagsAttributeSetsSelectInDependency() throws Exception { useConfiguration("--experimental_dynamic_configs=on"); writeFile( diff --git a/src/test/java/com/google/devtools/build/lib/rules/android/AndroidLocalTestTest.java b/src/test/java/com/google/devtools/build/lib/rules/android/AndroidLocalTestTest.java new file mode 100644 index 0000000000..d100b23808 --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/rules/android/AndroidLocalTestTest.java @@ -0,0 +1,116 @@ +// 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.rules.android; +package com.google.devtools.build.lib.rules.android; + +import static com.google.common.truth.Truth.assertThat; + +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 org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** A test for android_local_test. */ +@RunWith(JUnit4.class) +public class AndroidLocalTestTest extends AbstractAndroidLocalTestTest { + + @Test + public void testSimpleTestNotNull() throws Exception { + scratch.file( + "java/test/BUILD", + "android_local_test(name = 'dummyTest',", + " srcs = ['test.java'])"); + ConfiguredTarget target = getConfiguredTarget("//java/test:dummyTest"); + assertThat(target).isNotNull(); + } + + @Test + public void testResourceFilesZipCalledResourceFilesZip() throws Exception { + scratch.file( + "java/test/BUILD", + "android_local_test(name = 'dummyTest',", + " srcs = ['test.java'])"); + ConfiguredTarget target = getConfiguredTarget("//java/test:dummyTest"); + + Artifact resourcesZip = + getImplicitOutputArtifact(target, AndroidRuleClasses.ANDROID_RESOURCES_ZIP); + assertThat(resourcesZip.getFilename()).isEqualTo("resource_files.zip"); + } + + @Test + public void testManifestInRunfiles() throws Exception { + scratch.file( + "java/test/BUILD", + "android_local_test(name = 'dummyTest',", + " srcs = ['test.java'])"); + ConfiguredTarget target = getConfiguredTarget("//java/test:dummyTest"); + Iterable<Artifact> runfilesArtifacts = collectRunfiles(target); + Artifact manifest = + ActionsTestUtil.getFirstArtifactEndingWith( + runfilesArtifacts, "dummyTest_generated/dummyTest/AndroidManifest.xml"); + assertThat(manifest).isNotNull(); + } + + @Test + public void testResourcesClassJarInRunfiles() throws Exception { + scratch.file( + "java/test/BUILD", + "android_local_test(name = 'dummyTest',", + " srcs = ['test.java'])"); + ConfiguredTarget target = getConfiguredTarget("//java/test:dummyTest"); + Iterable<Artifact> runfilesArtifacts = collectRunfiles(target); + Artifact resourceClassJar = + getImplicitOutputArtifact(target, AndroidRuleClasses.ANDROID_RESOURCES_CLASS_JAR); + assertThat(runfilesArtifacts).contains(resourceClassJar); + } + + @Test + public void testResourcesZipFileInRunfiles() throws Exception { + scratch.file( + "java/test/BUILD", + "android_local_test(name = 'dummyTest',", + " srcs = ['test.java'])"); + ConfiguredTarget target = getConfiguredTarget("//java/test:dummyTest"); + Iterable<Artifact> runfilesArtifacts = collectRunfiles(target); + Artifact resourcesZip = + getImplicitOutputArtifact(target, AndroidRuleClasses.ANDROID_RESOURCES_ZIP); + assertThat(runfilesArtifacts).contains(resourcesZip); + } + + @Test + public void testCanHaveManifestNotNamedAndroidManifestXml() throws Exception { + scratch.file( + "java/test/BUILD", + "android_local_test(name = 'dummyTest',", + " srcs = ['test.java'],", + " manifest = 'NotAndroidManifest.xml')"); + assertNoEvents(); + } + + @Override + protected String getRuleName() { + return "android_local_test"; + } + + @Override + protected void writeFile(String path, String... lines) throws Exception { + scratch.file(path, lines); + } + + @Override + protected void overwriteFile(String path, String... lines) throws Exception { + scratch.overwriteFile(path, lines); + } +} |