diff options
author | 2015-09-26 20:35:57 +0000 | |
---|---|---|
committer | 2015-09-28 11:39:56 +0000 | |
commit | 22616ae8ea53df3909fd16b74e0d9210138dc2c2 (patch) | |
tree | 42740e6f6eba481ff858d7e48cc1605ece0ddb92 /src | |
parent | a72be0f1d68ec449d463f825afa0aaabc4f05246 (diff) |
Add Android IDL jar outputs.
These outputs are a jar and source jar for the results of aidl processing.
This is used to add aidl output to IDEs separate from the source code,
similar to annotation output (gen jars).
--
MOS_MIGRATED_REVID=104024453
Diffstat (limited to 'src')
19 files changed, 972 insertions, 250 deletions
diff --git a/src/java_tools/buildjar/BUILD b/src/java_tools/buildjar/BUILD index a9f6276cf0..1edd5828cc 100644 --- a/src/java_tools/buildjar/BUILD +++ b/src/java_tools/buildjar/BUILD @@ -160,6 +160,7 @@ java_library( ], visibility = [ "//src/java_tools/buildjar/java/com/google/devtools/build/buildjar/genclass:__pkg__", + "//src/tools/android/java/com/google/devtools/build/android/idlclass:__pkg__", ], deps = [ "//third_party:guava", diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/android/AndroidRepositoryRules.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/android/AndroidRepositoryRules.java index 45ecc0301d..d57d1f6c82 100644 --- a/src/main/java/com/google/devtools/build/lib/bazel/rules/android/AndroidRepositoryRules.java +++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/android/AndroidRepositoryRules.java @@ -51,7 +51,8 @@ public class AndroidRepositoryRules { "aar_generator", "shuffle_jars", "merge_dexzips", - "debug_keystore"); + "debug_keystore", + "IdlClass"); private static final Function<? super Rule, Map<String, Label>> BINDINGS_FUNCTION = new Function< Rule, Map<String, Label>>() { diff --git a/src/main/java/com/google/devtools/build/lib/ideinfo/AndroidStudioInfoAspect.java b/src/main/java/com/google/devtools/build/lib/ideinfo/AndroidStudioInfoAspect.java index a88f4a8646..2db796563d 100644 --- a/src/main/java/com/google/devtools/build/lib/ideinfo/AndroidStudioInfoAspect.java +++ b/src/main/java/com/google/devtools/build/lib/ideinfo/AndroidStudioInfoAspect.java @@ -260,6 +260,21 @@ public class AndroidStudioInfoAspect implements ConfiguredAspectFactory { builder.addTransitiveResources(makeArtifactLocation(transitiveResource)); } + boolean hasIdlSources = !provider.getIdlSrcs().isEmpty(); + builder.setHasIdlSources(hasIdlSources); + if (hasIdlSources) { + LibraryArtifact.Builder jarBuilder = LibraryArtifact.newBuilder(); + Artifact idlClassJar = provider.getIdlClassJar(); + if (idlClassJar != null) { + jarBuilder.setJar(makeArtifactLocation(idlClassJar)); + } + Artifact idlSourceJar = provider.getIdlSourceJar(); + if (idlSourceJar != null) { + jarBuilder.setSourceJar(makeArtifactLocation(idlSourceJar)); + } + builder.setIdlJar(jarBuilder.build()); + } + return builder.build(); } diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinary.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinary.java index d28420cfad..e5bac926f7 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinary.java +++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinary.java @@ -264,7 +264,6 @@ public abstract class AndroidBinary implements RuleConfiguredTargetFactory { javaSemantics, androidSemantics, resourceApk, - AndroidIdlProvider.EMPTY, ruleContext.getConfiguration().isCodeCoverageEnabled(), true /* collectJavaCompilationArgs */); if (resourceClasses == null) { diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidCommon.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidCommon.java index b2bf36fbcb..f4e5d67d2b 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidCommon.java +++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidCommon.java @@ -21,7 +21,6 @@ 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; -import com.google.devtools.build.lib.actions.MiddlemanFactory; import com.google.devtools.build.lib.actions.ResourceSet; import com.google.devtools.build.lib.analysis.AnalysisUtils; import com.google.devtools.build.lib.analysis.FilesToRunProvider; @@ -35,7 +34,6 @@ import com.google.devtools.build.lib.analysis.TransitiveInfoCollection; import com.google.devtools.build.lib.analysis.actions.FileWriteAction; import com.google.devtools.build.lib.analysis.actions.SpawnAction; import com.google.devtools.build.lib.analysis.config.BuildConfiguration.StrictDepsMode; -import com.google.devtools.build.lib.cmdline.Label; 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; @@ -65,16 +63,13 @@ 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.JavaUtil; import com.google.devtools.build.lib.syntax.Type; -import com.google.devtools.build.lib.vfs.FileSystemUtils; import com.google.devtools.build.lib.vfs.PathFragment; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashMap; -import java.util.LinkedHashSet; import java.util.List; import java.util.Map; -import java.util.Set; import javax.annotation.Nullable; @@ -102,12 +97,11 @@ public class AndroidCommon { private Artifact genClassJar; private Artifact genSourceJar; - private Collection<Artifact> idls; - private AndroidIdlProvider transitiveIdlImportData; private NestedSet<ResourceContainer> transitiveResources; - private Map<Artifact, Artifact> translatedIdlSources = ImmutableMap.of(); private boolean asNeverLink; private boolean exportDeps; + private Artifact manifestProtoOutput; + private AndroidIdlHelper idlHelper; public AndroidCommon(RuleContext ruleContext, JavaCommon javaCommon) { this.ruleContext = ruleContext; @@ -212,13 +206,16 @@ public class AndroidCommon { public static AndroidIdeInfoProvider createAndroidIdeInfoProvider( RuleContext ruleContext, AndroidSemantics semantics, + AndroidIdlHelper idlHelper, ResourceApk resourceApk, Artifact zipAlignedApk, Iterable<Artifact> apksUnderTest) { AndroidIdeInfoProvider.Builder ideInfoProviderBuilder = new AndroidIdeInfoProvider.Builder() - .addIdlParcelables(getIdlParcelables(ruleContext)) - .addIdlSrcs(getIdlSrcs(ruleContext)) + .setIdlClassJar(idlHelper.getIdlClassJar()) + .setIdlSourceJar(idlHelper.getIdlSourceJar()) + .addIdlParcelables(idlHelper.getIdlParcelables()) + .addIdlSrcs(idlHelper.getIdlSources()) .addAllApksUnderTest(apksUnderTest); if (zipAlignedApk != null) { @@ -385,17 +382,22 @@ public class AndroidCommon { public JavaTargetAttributes init( JavaSemantics javaSemantics, AndroidSemantics androidSemantics, - ResourceApk resourceApk, AndroidIdlProvider transitiveIdlImportData, + ResourceApk resourceApk, boolean addCoverageSupport, boolean collectJavaCompilationArgs) throws InterruptedException { - ImmutableList<Artifact> extraSources = - resourceApk.isLegacy() || resourceApk.getResourceJavaSrcJar() == null + + classJar = ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_LIBRARY_CLASS_JAR); + idlHelper = new AndroidIdlHelper(ruleContext, classJar); + + ImmutableList.Builder<Artifact> extraSourcesBuilder = ImmutableList.<Artifact>builder() + .addAll(resourceApk.isLegacy() || resourceApk.getResourceJavaSrcJar() == null ? ImmutableList.<Artifact>of() - : ImmutableList.of(resourceApk.getResourceJavaSrcJar()); + : ImmutableList.of(resourceApk.getResourceJavaSrcJar())) + .addAll(idlHelper.getIdlGeneratedJavaSources()); + JavaTargetAttributes.Builder attributes = init( androidSemantics, - transitiveIdlImportData, resourceApk.getTransitiveResources(), - extraSources); + extraSourcesBuilder.build()); JavaCompilationArtifacts.Builder artifactsBuilder = new JavaCompilationArtifacts.Builder(); if (resourceApk.isLegacy()) { compileResources(javaSemantics, artifactsBuilder, attributes, @@ -430,23 +432,11 @@ public class AndroidCommon { private JavaTargetAttributes.Builder init( AndroidSemantics androidSemantics, - AndroidIdlProvider transitiveIdlImportData, NestedSet<AndroidResourcesProvider.ResourceContainer> transitiveResources, Collection<Artifact> extraArtifacts) { - this.transitiveIdlImportData = transitiveIdlImportData; this.transitiveResources = transitiveResources; - - ImmutableList.Builder<Artifact> extraSrcsBuilder = - new ImmutableList.Builder<Artifact>().addAll(extraArtifacts); - - idls = getIdlSrcs(ruleContext); - if (!idls.isEmpty() && !ruleContext.hasErrors()) { - translatedIdlSources = generateTranslatedIdlArtifacts(ruleContext, idls); - } - javaCommon.initializeJavacOpts(androidSemantics.getJavacArguments()); - JavaTargetAttributes.Builder attributes = javaCommon.initCommon( - extraSrcsBuilder.addAll(translatedIdlSources.values()).build()); + JavaTargetAttributes.Builder attributes = javaCommon.initCommon(extraArtifacts); attributes.setBootClassPath(ImmutableList.of( AndroidSdkProvider.fromRuleContext(ruleContext).getAndroidJar())); @@ -533,7 +523,6 @@ public class AndroidCommon { } Artifact jar = null; - classJar = ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_LIBRARY_CLASS_JAR); if (attributes.hasSourceFiles() || attributes.hasSourceJars() || attributes.hasResources()) { // We only want to add a jar to the classpath of a dependent rule if it has content. javaArtifactsBuilder.addRuntimeJar(classJar); @@ -542,7 +531,7 @@ public class AndroidCommon { filesBuilder.add(classJar); - Artifact manifestProtoOutput = helper.createManifestProtoOutput(classJar); + manifestProtoOutput = helper.createManifestProtoOutput(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. @@ -597,17 +586,13 @@ public class AndroidCommon { ResourceApk resourceApk, Artifact zipAlignedApk, Iterable<Artifact> apksUnderTest) { - if (!idls.isEmpty()) { - generateAndroidIdlActions( - ruleContext, idls, transitiveIdlImportData, translatedIdlSources); - } - Runfiles runfiles = new Runfiles.Builder(ruleContext.getWorkspaceName()) .addRunfiles(ruleContext, RunfilesProvider.DEFAULT_RUNFILES) .build(); javaCommon.addTransitiveInfoProviders(builder, filesToBuild, classJar); javaCommon.addGenJarsProvider(builder, genClassJar, genSourceJar); + idlHelper.addTransitiveInfoProviders(builder, classJar, manifestProtoOutput); return builder .setFilesToBuild(filesToBuild) @@ -621,9 +606,8 @@ public class AndroidCommon { new AndroidResourcesProvider(ruleContext.getLabel(), transitiveResources)) .add( AndroidIdeInfoProvider.class, - createAndroidIdeInfoProvider( - ruleContext, androidSemantics, resourceApk, zipAlignedApk, apksUnderTest)) - .add(AndroidIdlProvider.class, transitiveIdlImportData) + createAndroidIdeInfoProvider(ruleContext, androidSemantics, idlHelper, + resourceApk, zipAlignedApk, apksUnderTest)) .add( JavaCompilationArgsProvider.class, new JavaCompilationArgsProvider( @@ -647,38 +631,6 @@ public class AndroidCommon { Type.STRING)); } - public static ImmutableList<Artifact> getIdlParcelables(RuleContext ruleContext) { - return ruleContext.getRule().isAttrDefined("idl_parcelables", BuildType.LABEL_LIST) - ? ImmutableList.copyOf(ruleContext.getPrerequisiteArtifacts( - "idl_parcelables", Mode.TARGET).filter(AndroidRuleClasses.ANDROID_IDL).list()) - : ImmutableList.<Artifact>of(); - } - - public static Collection<Artifact> getIdlSrcs(RuleContext ruleContext) { - if (!ruleContext.getRule().isAttrDefined("idl_srcs", BuildType.LABEL_LIST)) { - return ImmutableList.of(); - } - checkIdlSrcsSamePackage(ruleContext); - return ruleContext.getPrerequisiteArtifacts( - "idl_srcs", Mode.TARGET).filter(AndroidRuleClasses.ANDROID_IDL).list(); - } - - public static void checkIdlSrcsSamePackage(RuleContext ruleContext) { - PathFragment packageName = ruleContext.getLabel().getPackageFragment(); - Collection<Artifact> idls = ruleContext - .getPrerequisiteArtifacts("idl_srcs", Mode.TARGET) - .filter(AndroidRuleClasses.ANDROID_IDL) - .list(); - for (Artifact idl : idls) { - Label idlLabel = idl.getOwner(); - if (!packageName.equals(idlLabel.getPackageFragment())) { - ruleContext.attributeError("idl_srcs", "do not import '" + idlLabel + "' directly. " - + "You should either move the file to this package or depend on " - + "an appropriate rule there"); - } - } - } - public static NestedSet<LinkerInput> collectTransitiveNativeLibraries( Iterable<? extends TransitiveInfoCollection> deps) { NestedSetBuilder<LinkerInput> builder = NestedSetBuilder.stableOrder(); @@ -733,24 +685,6 @@ public class AndroidCommon { return applicationApksBuilder.build(); } - private ImmutableMap<Artifact, Artifact> generateTranslatedIdlArtifacts( - RuleContext ruleContext, Collection<Artifact> idls) { - ImmutableMap.Builder<Artifact, Artifact> outputJavaSources = ImmutableMap.builder(); - String ruleName = ruleContext.getRule().getName(); - // for each aidl file use aggregated preprocessed files to generate Java code - for (Artifact idl : idls) { - // Reconstruct the package tree under <rule>_aidl to avoid a name conflict - // if the same AIDL files are used in multiple targets. - PathFragment javaOutputPath = FileSystemUtils.replaceExtension( - new PathFragment(ruleName + "_aidl").getRelative(idl.getRootRelativePath()), - ".java"); - Artifact output = ruleContext.getPackageRelativeArtifact( - javaOutputPath, ruleContext.getConfiguration().getGenfilesDirectory()); - outputJavaSources.put(idl, output); - } - return outputJavaSources.build(); - } - private JavaCompilationArgs collectJavaCompilationArgs(RuleContext ruleContext, boolean recursive, boolean neverLink, boolean hasSrcs) { JavaCompilationArgs.Builder builder = JavaCompilationArgs.builder() @@ -762,85 +696,6 @@ public class AndroidCommon { return builder.build(); } - private void generateAndroidIdlActions(RuleContext ruleContext, - Collection<Artifact> idls, AndroidIdlProvider transitiveIdlImportData, - Map<Artifact, Artifact> translatedIdlSources) { - AndroidSdkProvider sdk = AndroidSdkProvider.fromRuleContext(ruleContext); - Set<Artifact> preprocessedIdls = new LinkedHashSet<>(); - List<String> preprocessedArgs = new ArrayList<>(); - - // add imports - for (String idlImport : transitiveIdlImportData.getTransitiveIdlImportRoots()) { - preprocessedArgs.add("-I" + idlImport); - } - - // preprocess each aidl file - preprocessedArgs.add("-p" + sdk.getFrameworkAidl().getExecPathString()); - String ruleName = ruleContext.getRule().getName(); - for (Artifact idl : idls) { - // Reconstruct the package tree under <rule>_aidl to avoid a name conflict - // if the source AIDL files are also generated. - PathFragment preprocessedPath = new PathFragment(ruleName + "_aidl") - .getRelative(idl.getRootRelativePath()); - Artifact preprocessed = ruleContext.getPackageRelativeArtifact( - preprocessedPath, ruleContext.getConfiguration().getGenfilesDirectory()); - preprocessedIdls.add(preprocessed); - preprocessedArgs.add("-p" + preprocessed.getExecPathString()); - - createAndroidIdlPreprocessAction(ruleContext, idl, preprocessed); - } - - // aggregate all preprocessed aidl files - MiddlemanFactory middlemanFactory = ruleContext.getAnalysisEnvironment().getMiddlemanFactory(); - Artifact preprocessedIdlsMiddleman = middlemanFactory.createAggregatingMiddleman( - ruleContext.getActionOwner(), "AndroidIDLMiddleman", preprocessedIdls, - ruleContext.getConfiguration().getMiddlemanDirectory()); - - for (Artifact idl : translatedIdlSources.keySet()) { - createAndroidIdlAction(ruleContext, idl, - transitiveIdlImportData.getTransitiveIdlImports(), - preprocessedIdlsMiddleman, translatedIdlSources.get(idl), preprocessedArgs); - } - } - - private void createAndroidIdlPreprocessAction(RuleContext ruleContext, - Artifact idl, Artifact preprocessed) { - AndroidSdkProvider sdk = AndroidSdkProvider.fromRuleContext(ruleContext); - ruleContext.registerAction(new SpawnAction.Builder() - .setExecutable(sdk.getAidl()) - // Note the below may be an overapproximation of the actual runfiles, due to "conditional - // artifacts" (see Runfiles.PruningManifest). - // TODO(bazel-team): When using getFilesToRun(), the middleman is - // not expanded. Fix by providing code to expand and use getFilesToRun here. - .addInput(idl) - .addOutput(preprocessed) - .addArgument("--preprocess") - .addArgument(preprocessed.getExecPathString()) - .addArgument(idl.getExecPathString()) - .setProgressMessage("Android IDL preprocessing") - .setMnemonic("AndroidIDLPreprocess") - .build(ruleContext)); - } - - private void createAndroidIdlAction(RuleContext ruleContext, - Artifact idl, Iterable<Artifact> idlImports, Artifact preprocessedIdls, - Artifact output, List<String> preprocessedArgs) { - AndroidSdkProvider sdk = AndroidSdkProvider.fromRuleContext(ruleContext); - ruleContext.registerAction(new SpawnAction.Builder() - .setExecutable(sdk.getAidl()) - .addInput(idl) - .addInputs(idlImports) - .addInput(preprocessedIdls) - .addInput(sdk.getFrameworkAidl()) - .addOutput(output) - .addArgument("-b") // Fail if trying to compile a parcelable. - .addArguments(preprocessedArgs) - .addArgument(idl.getExecPathString()) - .addArgument(output.getExecPathString()) - .setProgressMessage("Android IDL generation") - .setMnemonic("AndroidIDLGnerate") - .build(ruleContext)); - } public ImmutableList<String> getJavacOpts() { return javaCommon.getJavacOpts(); diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidIdeInfoProvider.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidIdeInfoProvider.java index 5d3ce1c7db..9fe1faf096 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidIdeInfoProvider.java +++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidIdeInfoProvider.java @@ -97,6 +97,8 @@ public final class AndroidIdeInfoProvider implements TransitiveInfoProvider { private Artifact manifest = null; private Artifact generatedManifest = null; private Artifact apk = null; + private Artifact idlClassJar = null; + private Artifact idlSourceJar = null; private final Set<SourceDirectory> resourceDirs = new LinkedHashSet<>(); private final Set<SourceDirectory> assetDirs = new LinkedHashSet<>(); private final Set<SourceDirectory> idlDirs = new LinkedHashSet<>(); @@ -108,6 +110,8 @@ public final class AndroidIdeInfoProvider implements TransitiveInfoProvider { manifest, generatedManifest, apk, + idlClassJar, + idlSourceJar, ImmutableList.copyOf(assetDirs), ImmutableList.copyOf(resourceDirs), ImmutableList.copyOf(idlDirs), @@ -133,6 +137,18 @@ public final class AndroidIdeInfoProvider implements TransitiveInfoProvider { return this; } + public Builder setIdlClassJar(@Nullable Artifact idlClassJar) { + Preconditions.checkState(this.idlClassJar == null); + this.idlClassJar = idlClassJar; + return this; + } + + public Builder setIdlSourceJar(@Nullable Artifact idlSourceJar) { + Preconditions.checkState(this.idlSourceJar == null); + this.idlSourceJar = idlSourceJar; + return this; + } + /** * Add "idl_srcs" contents. */ @@ -268,6 +284,8 @@ public final class AndroidIdeInfoProvider implements TransitiveInfoProvider { private final Artifact manifest; private final Artifact generatedManifest; private final Artifact signedApk; + @Nullable private final Artifact idlClassJar; + @Nullable private final Artifact idlSourceJar; private final ImmutableCollection<SourceDirectory> resourceDirs; private final ImmutableCollection<SourceDirectory> assetDirs; private final ImmutableCollection<SourceDirectory> idlImports; @@ -277,6 +295,8 @@ public final class AndroidIdeInfoProvider implements TransitiveInfoProvider { AndroidIdeInfoProvider(@Nullable Artifact manifest, @Nullable Artifact generatedManifest, @Nullable Artifact signedApk, + @Nullable Artifact idlClassJar, + @Nullable Artifact idlSourceJar, ImmutableCollection<SourceDirectory> assetDirs, ImmutableCollection<SourceDirectory> resourceDirs, ImmutableCollection<SourceDirectory> idlImports, @@ -285,6 +305,8 @@ public final class AndroidIdeInfoProvider implements TransitiveInfoProvider { this.manifest = manifest; this.generatedManifest = generatedManifest; this.signedApk = signedApk; + this.idlClassJar = idlClassJar; + this.idlSourceJar = idlSourceJar; this.assetDirs = assetDirs; this.resourceDirs = resourceDirs; this.idlImports = idlImports; @@ -311,6 +333,16 @@ public final class AndroidIdeInfoProvider implements TransitiveInfoProvider { return signedApk; } + @Nullable + public Artifact getIdlClassJar() { + return idlClassJar; + } + + @Nullable + public Artifact getIdlSourceJar() { + return idlSourceJar; + } + /** A list of the direct Resource directories. */ public ImmutableCollection<SourceDirectory> getResourceDirs() { return resourceDirs; diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidIdlHelper.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidIdlHelper.java new file mode 100644 index 0000000000..ff4e5f85b4 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidIdlHelper.java @@ -0,0 +1,373 @@ +// Copyright 2015 Google Inc. 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; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.actions.MiddlemanFactory; +import com.google.devtools.build.lib.actions.ParameterFile.ParameterFileType; +import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode; +import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder; +import com.google.devtools.build.lib.analysis.RuleContext; +import com.google.devtools.build.lib.analysis.actions.CustomCommandLine; +import com.google.devtools.build.lib.analysis.actions.SpawnAction; +import com.google.devtools.build.lib.cmdline.Label; +import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; +import com.google.devtools.build.lib.packages.BuildType; +import com.google.devtools.build.lib.rules.java.JavaUtil; +import com.google.devtools.build.lib.syntax.Type; +import com.google.devtools.build.lib.vfs.FileSystemUtils; +import com.google.devtools.build.lib.vfs.PathFragment; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.annotation.Nullable; + +/** + * Helper class for Android IDL processing. + */ +public class AndroidIdlHelper { + + private final RuleContext ruleContext; + private final AndroidIdlProvider androidIdlProvider; + private final Collection<Artifact> idls; + private final Map<Artifact, Artifact> translatedIdlSources; + private final Artifact idlClassJar; + private final Artifact idlSourceJar; + + public AndroidIdlHelper(RuleContext ruleContext, Artifact classJar) { + this.ruleContext = ruleContext; + + checkIdlRootImport(ruleContext); + + idls = getIdlSrcs(ruleContext); + + if (!idls.isEmpty() && !ruleContext.hasErrors()) { + translatedIdlSources = generateTranslatedIdlArtifacts(ruleContext, idls); + idlClassJar = createIdlJar(classJar, "-idl.jar"); + idlSourceJar = createIdlJar(classJar, "-idl.srcjar"); + } else { + translatedIdlSources = ImmutableMap.of(); + idlClassJar = null; + idlSourceJar = null; + } + + androidIdlProvider = createAndroidIdlProvider( + ruleContext, idlClassJar, idlSourceJar); + } + + public void addTransitiveInfoProviders(RuleConfiguredTargetBuilder builder, + Artifact classJar, Artifact manifestProtoOutput) { + if (!idls.isEmpty()) { + generateAndroidIdlCompilationActions( + ruleContext, idls, androidIdlProvider, translatedIdlSources); + createIdlClassJarAction(ruleContext, classJar, translatedIdlSources.values(), + manifestProtoOutput, idlClassJar, idlSourceJar); + } + builder + .add(AndroidIdlProvider.class, androidIdlProvider) + .addOutputGroup( + AndroidSemantics.IDL_JARS_OUTPUT_GROUP, androidIdlProvider.getTransitiveIdlJars()); + } + + public Collection<Artifact> getIdlSources() { + return idls; + } + + public Collection<Artifact> getIdlParcelables() { + return getIdlParcelables(ruleContext); + } + + public Collection<Artifact> getIdlGeneratedJavaSources() { + return translatedIdlSources.values(); + } + + @Nullable + public Artifact getIdlClassJar() { + return idlClassJar; + } + + @Nullable + public Artifact getIdlSourceJar() { + return idlSourceJar; + } + + /** + * Returns the artifact for a jar file containing class files that were generated by + * annotation processors. + */ + private Artifact createIdlJar(Artifact outputJar, String suffix) { + return ruleContext.getDerivedArtifact( + FileSystemUtils.replaceExtension(outputJar.getRootRelativePath(), suffix), + outputJar.getRoot()); + } + + private static ImmutableList<Artifact> getIdlParcelables(RuleContext ruleContext) { + return ruleContext.getRule().isAttrDefined("idl_parcelables", BuildType.LABEL_LIST) + ? ImmutableList.copyOf(ruleContext.getPrerequisiteArtifacts( + "idl_parcelables", Mode.TARGET).filter(AndroidRuleClasses.ANDROID_IDL).list()) + : ImmutableList.<Artifact>of(); + } + + private static Collection<Artifact> getIdlSrcs(RuleContext ruleContext) { + if (!ruleContext.getRule().isAttrDefined("idl_srcs", BuildType.LABEL_LIST)) { + return ImmutableList.of(); + } + checkIdlSrcsSamePackage(ruleContext); + return ruleContext.getPrerequisiteArtifacts( + "idl_srcs", Mode.TARGET).filter(AndroidRuleClasses.ANDROID_IDL).list(); + } + + private static void checkIdlSrcsSamePackage(RuleContext ruleContext) { + PathFragment packageName = ruleContext.getLabel().getPackageFragment(); + Collection<Artifact> idls = ruleContext + .getPrerequisiteArtifacts("idl_srcs", Mode.TARGET) + .filter(AndroidRuleClasses.ANDROID_IDL) + .list(); + for (Artifact idl : idls) { + Label idlLabel = idl.getOwner(); + if (!packageName.equals(idlLabel.getPackageFragment())) { + ruleContext.attributeError("idl_srcs", "do not import '" + idlLabel + "' directly. " + + "You should either move the file to this package or depend on " + + "an appropriate rule there"); + } + } + } + + private static ImmutableMap<Artifact, Artifact> generateTranslatedIdlArtifacts( + RuleContext ruleContext, Collection<Artifact> idls) { + ImmutableMap.Builder<Artifact, Artifact> outputJavaSources = ImmutableMap.builder(); + String ruleName = ruleContext.getRule().getName(); + // for each aidl file use aggregated preprocessed files to generate Java code + for (Artifact idl : idls) { + // Reconstruct the package tree under <rule>_aidl to avoid a name conflict + // if the same AIDL files are used in multiple targets. + PathFragment javaOutputPath = FileSystemUtils.replaceExtension( + new PathFragment(ruleName + "_aidl").getRelative(idl.getRootRelativePath()), + ".java"); + Artifact output = ruleContext.getPackageRelativeArtifact( + javaOutputPath, ruleContext.getConfiguration().getGenfilesDirectory()); + outputJavaSources.put(idl, output); + } + return outputJavaSources.build(); + } + + private static void generateAndroidIdlCompilationActions( + RuleContext ruleContext, + Collection<Artifact> idls, + AndroidIdlProvider transitiveIdlImportData, + Map<Artifact, Artifact> translatedIdlSources) { + AndroidSdkProvider sdk = AndroidSdkProvider.fromRuleContext(ruleContext); + Set<Artifact> preprocessedIdls = new LinkedHashSet<>(); + List<String> preprocessedArgs = new ArrayList<>(); + + // add imports + for (String idlImport : transitiveIdlImportData.getTransitiveIdlImportRoots()) { + preprocessedArgs.add("-I" + idlImport); + } + + // preprocess each aidl file + preprocessedArgs.add("-p" + sdk.getFrameworkAidl().getExecPathString()); + String ruleName = ruleContext.getRule().getName(); + for (Artifact idl : idls) { + // Reconstruct the package tree under <rule>_aidl to avoid a name conflict + // if the source AIDL files are also generated. + PathFragment preprocessedPath = new PathFragment(ruleName + "_aidl") + .getRelative(idl.getRootRelativePath()); + Artifact preprocessed = ruleContext.getPackageRelativeArtifact( + preprocessedPath, ruleContext.getConfiguration().getGenfilesDirectory()); + preprocessedIdls.add(preprocessed); + preprocessedArgs.add("-p" + preprocessed.getExecPathString()); + + createAndroidIdlPreprocessAction(ruleContext, idl, preprocessed); + } + + // aggregate all preprocessed aidl files + MiddlemanFactory middlemanFactory = ruleContext.getAnalysisEnvironment().getMiddlemanFactory(); + Artifact preprocessedIdlsMiddleman = middlemanFactory.createAggregatingMiddleman( + ruleContext.getActionOwner(), "AndroidIDLMiddleman", preprocessedIdls, + ruleContext.getConfiguration().getMiddlemanDirectory()); + + for (Artifact idl : translatedIdlSources.keySet()) { + createAndroidIdlAction(ruleContext, idl, + transitiveIdlImportData.getTransitiveIdlImports(), + preprocessedIdlsMiddleman, translatedIdlSources.get(idl), preprocessedArgs); + } + } + + /** + * Creates the idl class jar action. + */ + private static void createIdlClassJarAction( + RuleContext ruleContext, + Artifact classJar, + Iterable<Artifact> generatedIdlJavaFiles, + Artifact manifestProtoOutput, + Artifact idlClassJar, + Artifact idlSourceJar) { + ruleContext.registerAction(new SpawnAction.Builder() + .addInput(manifestProtoOutput) + .addInput(classJar) + .addInputs(generatedIdlJavaFiles) + .addOutput(idlClassJar) + .addOutput(idlSourceJar) + .setExecutable(ruleContext.getExecutablePrerequisite("$idlclass", Mode.HOST)) + .setCommandLine(CustomCommandLine.builder() + .addExecPath("--manifest_proto", manifestProtoOutput) + .addExecPath("--class_jar", classJar) + .addExecPath("--output_class_jar", idlClassJar) + .addExecPath("--output_source_jar", idlSourceJar) + .add("--temp_dir").addPath(idlTempDir(ruleContext, idlClassJar)) + .addExecPaths(generatedIdlJavaFiles) + .build()) + .useParameterFile(ParameterFileType.SHELL_QUOTED) + .setProgressMessage("Building idl jars " + idlClassJar.prettyPrint()) + .setMnemonic("AndroidIdlJars") + .build(ruleContext)); + } + + private static PathFragment idlTempDir(RuleContext ruleContext, Artifact outputJar) { + String basename = FileSystemUtils.removeExtension(outputJar.getExecPath().getBaseName()); + return ruleContext.getConfiguration().getBinDirectory().getExecPath() + .getRelative(ruleContext.getUniqueDirectory("_idl")) + .getRelative(basename + "_temp"); + } + + private static void createAndroidIdlPreprocessAction(RuleContext ruleContext, + Artifact idl, Artifact preprocessed) { + AndroidSdkProvider sdk = AndroidSdkProvider.fromRuleContext(ruleContext); + ruleContext.registerAction(new SpawnAction.Builder() + .setExecutable(sdk.getAidl()) + // Note the below may be an overapproximation of the actual runfiles, due to "conditional + // artifacts" (see Runfiles.PruningManifest). + // TODO(bazel-team): When using getFilesToRun(), the middleman is + // not expanded. Fix by providing code to expand and use getFilesToRun here. + .addInput(idl) + .addOutput(preprocessed) + .addArgument("--preprocess") + .addArgument(preprocessed.getExecPathString()) + .addArgument(idl.getExecPathString()) + .setProgressMessage("Android IDL preprocessing") + .setMnemonic("AndroidIDLPreprocess") + .build(ruleContext)); + } + + private static void createAndroidIdlAction(RuleContext ruleContext, + Artifact idl, Iterable<Artifact> idlImports, Artifact preprocessedIdls, + Artifact output, List<String> preprocessedArgs) { + AndroidSdkProvider sdk = AndroidSdkProvider.fromRuleContext(ruleContext); + ruleContext.registerAction(new SpawnAction.Builder() + .setExecutable(sdk.getAidl()) + .addInput(idl) + .addInputs(idlImports) + .addInput(preprocessedIdls) + .addInput(sdk.getFrameworkAidl()) + .addOutput(output) + .addArgument("-b") // Fail if trying to compile a parcelable. + .addArguments(preprocessedArgs) + .addArgument(idl.getExecPathString()) + .addArgument(output.getExecPathString()) + .setProgressMessage("Android IDL generation") + .setMnemonic("AndroidIDLGnerate") + .build(ruleContext)); + } + + /** + * Returns the union of "idl_srcs" and "idl_parcelables", i.e. all .aidl files + * provided by this library that contribute to .aidl --> .java compilation. + */ + private static Collection<Artifact> getIdlImports(RuleContext ruleContext) { + return ImmutableList.<Artifact>builder() + .addAll(getIdlParcelables(ruleContext)) + .addAll(getIdlSrcs(ruleContext)) + .build(); + } + + private static AndroidIdlProvider createAndroidIdlProvider(RuleContext ruleContext, + @Nullable Artifact idlClassJar, @Nullable Artifact idlSourceJar) { + NestedSetBuilder<String> rootsBuilder = NestedSetBuilder.naiveLinkOrder(); + NestedSetBuilder<Artifact> importsBuilder = NestedSetBuilder.naiveLinkOrder(); + NestedSetBuilder<Artifact> jarsBuilder = NestedSetBuilder.stableOrder(); + if (idlClassJar != null) { + jarsBuilder.add(idlClassJar); + } + if (idlSourceJar != null) { + jarsBuilder.add(idlSourceJar); + } + + for (AndroidIdlProvider dep : ruleContext.getPrerequisites( + "deps", Mode.TARGET, AndroidIdlProvider.class)) { + rootsBuilder.addTransitive(dep.getTransitiveIdlImportRoots()); + importsBuilder.addTransitive(dep.getTransitiveIdlImports()); + jarsBuilder.addTransitive(dep.getTransitiveIdlJars()); + } + + Collection<Artifact> idlImports = getIdlImports(ruleContext); + if (!hasExplicitlySpecifiedIdlImportRoot(ruleContext)) { + for (Artifact idlImport : idlImports) { + PathFragment javaRoot = JavaUtil.getJavaRoot(idlImport.getExecPath()); + if (javaRoot == null) { + ruleContext.ruleError("Cannot determine java/javatests root for import " + + idlImport.getExecPathString()); + } else { + rootsBuilder.add(javaRoot.toString()); + } + } + } else { + PathFragment pkgFragment = ruleContext.getLabel().getPackageFragment(); + Set<PathFragment> idlImportRoots = new HashSet<>(); + for (Artifact idlImport : idlImports) { + idlImportRoots.add(idlImport.getRoot().getExecPath() + .getRelative(pkgFragment) + .getRelative(getIdlImportRoot(ruleContext))); + } + for (PathFragment idlImportRoot : idlImportRoots) { + rootsBuilder.add(idlImportRoot.toString()); + } + } + importsBuilder.addAll(idlImports); + + return new AndroidIdlProvider(rootsBuilder.build(), + importsBuilder.build(), jarsBuilder.build()); + } + + private static void checkIdlRootImport(RuleContext ruleContext) { + if (hasExplicitlySpecifiedIdlImportRoot(ruleContext) + && !hasExplicitlySpecifiedIdlSrcsOrParcelables(ruleContext)) { + ruleContext.attributeError("idl_import_root", + "Neither idl_srcs nor idl_parcelables were specified, " + + "but 'idl_import_root' attribute was set"); + } + } + + private static boolean hasExplicitlySpecifiedIdlImportRoot(RuleContext ruleContext) { + return ruleContext.getRule().isAttributeValueExplicitlySpecified("idl_import_root"); + } + + private static boolean hasExplicitlySpecifiedIdlSrcsOrParcelables(RuleContext ruleContext) { + return ruleContext.getRule().isAttributeValueExplicitlySpecified("idl_srcs") + || ruleContext.getRule().isAttributeValueExplicitlySpecified("idl_parcelables"); + } + + private static String getIdlImportRoot(RuleContext ruleContext) { + return ruleContext.attributes().get("idl_import_root", Type.STRING); + } +} diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidIdlProvider.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidIdlProvider.java index 2c98727aa9..c7365db8d2 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidIdlProvider.java +++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidIdlProvider.java @@ -29,15 +29,20 @@ public final class AndroidIdlProvider implements TransitiveInfoProvider { public static final AndroidIdlProvider EMPTY = new AndroidIdlProvider( NestedSetBuilder.<String>emptySet(Order.STABLE_ORDER), + NestedSetBuilder.<Artifact>emptySet(Order.STABLE_ORDER), NestedSetBuilder.<Artifact>emptySet(Order.STABLE_ORDER)); private final NestedSet<String> transitiveIdlImportRoots; private final NestedSet<Artifact> transitiveIdlImports; + private final NestedSet<Artifact> transitiveIdlJars; - public AndroidIdlProvider(NestedSet<String> transitiveIdlImportRoots, - NestedSet<Artifact> transitiveIdlImports) { + public AndroidIdlProvider( + NestedSet<String> transitiveIdlImportRoots, + NestedSet<Artifact> transitiveIdlImports, + NestedSet<Artifact> transitiveIdlJars) { this.transitiveIdlImportRoots = transitiveIdlImportRoots; this.transitiveIdlImports = transitiveIdlImports; + this.transitiveIdlJars = transitiveIdlJars; } /** @@ -53,4 +58,11 @@ public final class AndroidIdlProvider implements TransitiveInfoProvider { public NestedSet<Artifact> getTransitiveIdlImports() { return transitiveIdlImports; } + + /** + * The IDL jars in the transitive closure, both class and source jars. + */ + public NestedSet<Artifact> getTransitiveIdlJars() { + return transitiveIdlJars; + } } diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidLibrary.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidLibrary.java index f83553b5ed..67156b0ba8 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidLibrary.java +++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidLibrary.java @@ -39,14 +39,11 @@ 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.JavaUtil; import com.google.devtools.build.lib.syntax.Type; import com.google.devtools.build.lib.vfs.PathFragment; import java.util.Collection; -import java.util.HashSet; import java.util.List; -import java.util.Set; /** * An implementation for the "android_library" rule. @@ -66,7 +63,6 @@ public abstract class AndroidLibrary implements RuleConfiguredTargetFactory { List<? extends TransitiveInfoCollection> deps = ruleContext.getPrerequisites("deps", Mode.TARGET); checkResourceInlining(ruleContext); - checkIdlRootImport(ruleContext); NestedSet<AndroidResourcesProvider.ResourceContainer> transitiveResources = AndroidCommon.getTransitiveResourceContainers(ruleContext, true); NestedSetBuilder<Aar> transitiveAars = collectTransitiveAars(ruleContext); @@ -74,11 +70,9 @@ public abstract class AndroidLibrary implements RuleConfiguredTargetFactory { AndroidCommon.collectTransitiveNativeLibraries(deps); NestedSet<Artifact> transitiveProguardConfigs = collectTransitiveProguardConfigs(ruleContext); - AndroidIdlProvider transitiveIdlImportData = collectTransitiveIdlImports(ruleContext); JavaCommon javaCommon = new JavaCommon(ruleContext, javaSemantics); AndroidCommon androidCommon = new AndroidCommon(ruleContext, javaCommon); - boolean definesLocalResources = LocalResourceContainer.definesAndroidResources(ruleContext.attributes()); if (definesLocalResources && !LocalResourceContainer.validateRuleContext(ruleContext)) { @@ -116,7 +110,6 @@ public abstract class AndroidLibrary implements RuleConfiguredTargetFactory { javaSemantics, androidSemantics, resourceApk, - transitiveIdlImportData, false /* addCoverageSupport */, true /* collectJavaCompilationArgs */); if (javaTargetAttributes == null) { @@ -221,53 +214,6 @@ public abstract class AndroidLibrary implements RuleConfiguredTargetFactory { return mergedJar; } - private AndroidIdlProvider collectTransitiveIdlImports(RuleContext ruleContext) { - NestedSetBuilder<String> rootsBuilder = NestedSetBuilder.naiveLinkOrder(); - NestedSetBuilder<Artifact> importsBuilder = NestedSetBuilder.naiveLinkOrder(); - - for (AndroidIdlProvider dep : ruleContext.getPrerequisites( - "deps", Mode.TARGET, AndroidIdlProvider.class)) { - rootsBuilder.addTransitive(dep.getTransitiveIdlImportRoots()); - importsBuilder.addTransitive(dep.getTransitiveIdlImports()); - } - - Collection<Artifact> idlImports = getIdlImports(ruleContext); - if (!hasExplicitlySpecifiedIdlImportRoot(ruleContext)) { - for (Artifact idlImport : idlImports) { - PathFragment javaRoot = JavaUtil.getJavaRoot(idlImport.getExecPath()); - if (javaRoot == null) { - ruleContext.ruleError("Cannot determine java/javatests root for import " - + idlImport.getExecPathString()); - } else { - rootsBuilder.add(javaRoot.toString()); - } - } - } else { - PathFragment pkgFragment = ruleContext.getLabel().getPackageFragment(); - Set<PathFragment> idlImportRoots = new HashSet<>(); - for (Artifact idlImport : idlImports) { - idlImportRoots.add(idlImport.getRoot().getExecPath() - .getRelative(pkgFragment) - .getRelative(getIdlImportRoot(ruleContext))); - } - for (PathFragment idlImportRoot : idlImportRoots) { - rootsBuilder.add(idlImportRoot.toString()); - } - } - importsBuilder.addAll(idlImports); - - return new AndroidIdlProvider(rootsBuilder.build(), importsBuilder.build()); - } - - private void checkIdlRootImport(RuleContext ruleContext) { - if (hasExplicitlySpecifiedIdlImportRoot(ruleContext) - && !hasExplicitlySpecifiedIdlSrcsOrParcelables(ruleContext)) { - ruleContext.attributeError("idl_import_root", - "Neither idl_srcs nor idl_parcelables were specified, " - + "but 'idl_import_root' attribute was set"); - } - } - private void checkResourceInlining(RuleContext ruleContext) { AndroidResourcesProvider resources = AndroidCommon.getAndroidResources(ruleContext); if (resources == null) { @@ -293,30 +239,6 @@ public abstract class AndroidLibrary implements RuleConfiguredTargetFactory { return builder; } - private boolean hasExplicitlySpecifiedIdlImportRoot(RuleContext ruleContext) { - return ruleContext.getRule().isAttributeValueExplicitlySpecified("idl_import_root"); - } - - private boolean hasExplicitlySpecifiedIdlSrcsOrParcelables(RuleContext ruleContext) { - return ruleContext.getRule().isAttributeValueExplicitlySpecified("idl_srcs") - || ruleContext.getRule().isAttributeValueExplicitlySpecified("idl_parcelables"); - } - - private String getIdlImportRoot(RuleContext ruleContext) { - return ruleContext.attributes().get("idl_import_root", Type.STRING); - } - - /** - * Returns the union of "idl_srcs" and "idl_parcelables", i.e. all .aidl files - * provided by this library that contribute to .aidl --> .java compilation. - */ - private static Collection<Artifact> getIdlImports(RuleContext ruleContext) { - return ImmutableList.<Artifact>builder() - .addAll(AndroidCommon.getIdlParcelables(ruleContext)) - .addAll(AndroidCommon.getIdlSrcs(ruleContext)) - .build(); - } - private NestedSet<Artifact> collectTransitiveProguardConfigs(RuleContext ruleContext) { NestedSetBuilder<Artifact> specsBuilder = NestedSetBuilder.naiveLinkOrder(); diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidRuleClasses.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidRuleClasses.java index 5372fc10b0..c1e14989a3 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidRuleClasses.java +++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidRuleClasses.java @@ -531,6 +531,8 @@ public final class AndroidRuleClasses { // like all the rest of android tools. .add(attr("$jarjar_bin", LABEL).cfg(HOST).exec() .value(env.getLabel("//third_party/java/jarjar:jarjar_bin"))) + .add(attr("$idlclass", LABEL).cfg(HOST).exec() + .value(env.getLabel(Constants.ANDROID_DEP_PREFIX + "IdlClass"))) .build(); } diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidSemantics.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidSemantics.java index ea6a4588b2..e11c46038c 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidSemantics.java +++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidSemantics.java @@ -15,6 +15,7 @@ package com.google.devtools.build.lib.rules.android; import com.google.common.collect.ImmutableList; import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.analysis.OutputGroupProvider; import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder; import com.google.devtools.build.lib.analysis.RuleContext; import com.google.devtools.build.lib.analysis.actions.SpawnAction; @@ -33,6 +34,13 @@ import javax.annotation.Nullable; */ public interface AndroidSemantics { /** + * Name of the output group used for idl jars (the jars containing the class files for sources + * generated from annotation processors). + */ + String IDL_JARS_OUTPUT_GROUP = + OutputGroupProvider.HIDDEN_OUTPUT_GROUP_PREFIX + "idl_jars"; + + /** * Adds transitive info providers for {@code android_binary} and {@code android_library} rules. * @throws InterruptedException */ diff --git a/src/main/protobuf/android_studio_ide_info.proto b/src/main/protobuf/android_studio_ide_info.proto index 36ec0feba8..ee0d327e87 100644 --- a/src/main/protobuf/android_studio_ide_info.proto +++ b/src/main/protobuf/android_studio_ide_info.proto @@ -46,6 +46,7 @@ message AndroidRuleIdeInfo { ArtifactLocation generated_manifest = 6; string java_package = 7; bool has_idl_sources = 8; + LibraryArtifact idl_jar = 9; } message AndroidSdkRuleInfo { diff --git a/src/test/java/com/google/devtools/build/android/idlclass/BUILD b/src/test/java/com/google/devtools/build/android/idlclass/BUILD new file mode 100644 index 0000000000..f9255e4083 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/idlclass/BUILD @@ -0,0 +1,12 @@ +java_test( + name = "IdlClassTest", + size = "medium", + srcs = glob(["*.java"]), + deps = [ + "//src/main/protobuf:proto_java_compilation", + "//src/tools/android/java/com/google/devtools/build/android/idlclass:idlclass_lib", + "//third_party:guava", + "//third_party:junit4", + "//third_party:truth", + ], +) diff --git a/src/test/java/com/google/devtools/build/android/idlclass/IdlClassTest.java b/src/test/java/com/google/devtools/build/android/idlclass/IdlClassTest.java new file mode 100644 index 0000000000..740f753b49 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/idlclass/IdlClassTest.java @@ -0,0 +1,170 @@ +// Copyright 2015 Google Inc. 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.android.idlclass; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.collect.Sets; +import com.google.devtools.build.buildjar.proto.JavaCompilation.CompilationUnit; +import com.google.devtools.build.buildjar.proto.JavaCompilation.Manifest; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Enumeration; +import java.util.List; +import java.util.Set; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipOutputStream; + +/** {@link IdlClass} test */ +@RunWith(JUnit4.class) +public class IdlClassTest { + + static final Manifest MANIFEST = + Manifest.newBuilder() + .addCompilationUnit( + CompilationUnit.newBuilder() + .setPath("c/g/Foo.java") + .setPkg("c.g") + .addTopLevel("Foo") + .addTopLevel("Bar")) + .addCompilationUnit( + CompilationUnit.newBuilder() + .setPath("c/g/Bar.java") + .setPkg("c.g") + .addTopLevel("Bar") + .addTopLevel("Bar2")) + .addCompilationUnit( + CompilationUnit.newBuilder() + // default package + .setPath("wrong/source/dir/Baz.java") + .addTopLevel("Baz")) + .build(); + + @Rule public TemporaryFolder tempFolder = new TemporaryFolder(); + + @Test + public void generatedPrefixes() { + Set<String> idlSources = Sets.newHashSet( + "c/g/Bar.java", + "wrong/source/dir/Baz.java" + ); + assertThat(IdlClass.getIdlPrefixes(MANIFEST, idlSources)) + .containsExactly("c/g/Bar", "c/g/Bar2", "Baz"); + } + + @Test + public void idlClass() throws IOException { + File classJar = tempFolder.newFile("lib.jar"); + File manifestProto = tempFolder.newFile("lib.manifest"); + File tempDir = tempFolder.newFolder("temp_files"); + File outputClassJar = tempFolder.newFile("lib-idl.jar"); + File outputSourceJar = tempFolder.newFile("lib-idl-src.jar"); + + List<String> classes = + Arrays.asList( + "Baz.class", + "Baz$0.class", + "Baz$1.class", + "c/g/Foo.class", + "c/g/Foo$0.class", + "c/g/Foo$Inner.class", + "c/g/Foo$Inner$InnerMost.class", + "c/g/Bar.class", + "c/g/Bar2.class", + "c/g/Bar$Inner.class", + "c/g/Bar2$Inner.class"); + + try (OutputStream os = new FileOutputStream(classJar); + ZipOutputStream zos = new ZipOutputStream(os)) { + for (String path : classes) { + zos.putNextEntry(new ZipEntry(path)); + } + } + + tempFolder.newFolder("c"); + tempFolder.newFolder("c/g"); + tempFolder.newFolder("wrong"); + tempFolder.newFolder("wrong/source"); + tempFolder.newFolder("wrong/source/dir"); + for (String file : Arrays.asList("c/g/Foo.java", "c/g/Bar.java", "wrong/source/dir/Baz.java")) { + tempFolder.newFile(file); + } + + try (OutputStream os = new FileOutputStream(manifestProto)) { + MANIFEST.writeTo(os); + } + + IdlClass.main( + new String[]{ + "--manifest_proto", + manifestProto.toString(), + "--class_jar", + classJar.toString(), + "--output_class_jar", + outputClassJar.toString(), + "--output_source_jar", + outputSourceJar.toString(), + "--temp_dir", + tempDir.toString(), + "--idl_source_base_dir", + tempFolder.getRoot().getPath(), + "c/g/Bar.java", + "wrong/source/dir/Baz.java" + }); + + List<String> classJarEntries = getJarEntries(outputClassJar); + assertThat(classJarEntries) + .containsExactly( + "c/g/Bar.class", + "c/g/Bar$Inner.class", + "c/g/Bar2.class", + "c/g/Bar2$Inner.class", + "Baz.class", + "Baz$0.class", + "Baz$1.class"); + + List<String> sourceJarEntries = getJarEntries(outputSourceJar); + assertThat(sourceJarEntries) + .containsExactly( + "c/g/Bar.java", + "Baz.java"); + } + + private List<String> getJarEntries(File outputJar) throws IOException { + List<String> jarEntries = new ArrayList<>(); + try (ZipFile zf = new ZipFile(outputJar)) { + Enumeration<? extends ZipEntry> entries = zf.entries(); + while (entries.hasMoreElements()) { + String name = entries.nextElement().getName(); + if (name.endsWith("/") || name.equals("META-INF/MANIFEST.MF")) { + continue; + } + jarEntries.add(name); + } + } + return jarEntries; + } +} diff --git a/src/test/java/com/google/devtools/build/lib/ideinfo/AndroidStudioInfoAspectTest.java b/src/test/java/com/google/devtools/build/lib/ideinfo/AndroidStudioInfoAspectTest.java index 2383b6497b..aff6d9251b 100644 --- a/src/test/java/com/google/devtools/build/lib/ideinfo/AndroidStudioInfoAspectTest.java +++ b/src/test/java/com/google/devtools/build/lib/ideinfo/AndroidStudioInfoAspectTest.java @@ -520,6 +520,40 @@ public class AndroidStudioInfoAspectTest extends BuildViewTestCase { assertThat(lRuleInfo.getAndroidRuleIdeInfo().getJavaPackage()).isEqualTo("com.google.example"); } + public void testAndroidLibraryWithoutAidlHasNoIdlJars() throws Exception { + scratch.file( + "java/com/google/example/BUILD", + "android_library(", + " name = 'no_idl',", + " srcs = ['Test.java'],", + ")" + ); + String noIdlTarget = "//java/com/google/example:no_idl"; + Map<String, RuleIdeInfo> ruleIdeInfos = buildRuleIdeInfo(noIdlTarget); + RuleIdeInfo noIdlRuleInfo = getRuleInfoAndVerifyLabel(noIdlTarget, ruleIdeInfos); + + assertThat(noIdlRuleInfo.getAndroidRuleIdeInfo().getHasIdlSources()).isFalse(); + } + + public void testAndroidLibraryWithAidlHasIdlJars() throws Exception { + scratch.file( + "java/com/google/example/BUILD", + "android_library(", + " name = 'has_idl',", + " idl_srcs = ['a.aidl'],", + ")" + ); + String idlTarget = "//java/com/google/example:has_idl"; + Map<String, RuleIdeInfo> ruleIdeInfos = buildRuleIdeInfo(idlTarget); + RuleIdeInfo idlRuleInfo = getRuleInfoAndVerifyLabel(idlTarget, ruleIdeInfos); + + assertThat(idlRuleInfo.getAndroidRuleIdeInfo().getHasIdlSources()).isTrue(); + assertThat(LIBRARY_ARTIFACT_TO_STRING.apply(idlRuleInfo.getAndroidRuleIdeInfo().getIdlJar())) + .isEqualTo( + "<jar:java/com/google/example/libhas_idl-idl.jar>" + + "<source:java/com/google/example/libhas_idl-idl.srcjar>"); + } + private Map<String, RuleIdeInfo> buildRuleIdeInfo(String target) throws Exception { AnalysisResult analysisResult = update( diff --git a/src/test/shell/bazel/test-setup.sh b/src/test/shell/bazel/test-setup.sh index 1b37a13d5e..d08484d958 100755 --- a/src/test/shell/bazel/test-setup.sh +++ b/src/test/shell/bazel/test-setup.sh @@ -175,6 +175,7 @@ sh_binary( EOF cat > third_party/java/jarjar/fail.sh <<EOF + #!/bin/bash exit 1 @@ -182,6 +183,25 @@ EOF chmod +x third_party/java/jarjar/fail.sh + mkdir -p src/tools/android/java/com/google/devtools/build/android/idlclass + cat > src/tools/android/java/com/google/devtools/build/android/idlclass/BUILD <<EOF +licenses(["unencumbered"]) +sh_binary( + name = "IdlClass", + srcs = ["fail.sh"], + visibility = ["//visibility:public"], +) +EOF + + cat > src/tools/android/java/com/google/devtools/build/android/idlclass/fail.sh <<EOF + +#!/bin/bash + +exit 1 +EOF + + chmod +x src/tools/android/java/com/google/devtools/build/android/idlclass/fail.sh + ANDROID_NDK=$PWD/android_ndk ANDROID_SDK=$PWD/android_sdk diff --git a/src/tools/android/java/com/google/devtools/build/android/idlclass/BUILD b/src/tools/android/java/com/google/devtools/build/android/idlclass/BUILD new file mode 100644 index 0000000000..a22f833748 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/idlclass/BUILD @@ -0,0 +1,23 @@ +java_binary( + name = "IdlClass", + main_class = "com.google.devtools.build.android.idlclass.IdlClass", + visibility = ["//visibility:public"], + runtime_deps = [":idlclass_lib"], +) + +java_library( + name = "idlclass_lib", + srcs = glob(["*.java"]), + visibility = [ + "//devtools/blaze/integration:__pkg__", + "//src/test/java/com/google/devtools/build/android/idlclass:__pkg__", + ], + deps = [ + "//src/java_tools/buildjar:jarhelper", + "//src/main/java:options", + "//src/main/protobuf:proto_java_compilation", + "//src/tools/android/java/com/google/devtools/build/android:android_builder_lib", + "//third_party:guava", + "//third_party:jsr305", + ], +) diff --git a/src/tools/android/java/com/google/devtools/build/android/idlclass/IdlClass.java b/src/tools/android/java/com/google/devtools/build/android/idlclass/IdlClass.java new file mode 100644 index 0000000000..c31a66a8f9 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/idlclass/IdlClass.java @@ -0,0 +1,175 @@ +// Copyright 2015 Google Inc. 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.android.idlclass; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import com.google.devtools.build.buildjar.JarCreator; +import com.google.devtools.build.buildjar.proto.JavaCompilation.CompilationUnit; +import com.google.devtools.build.buildjar.proto.JavaCompilation.Manifest; +import com.google.devtools.common.options.OptionsParser; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Enumeration; +import java.util.List; +import java.util.Set; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +/** + * IdlClass post-processes the output of a Java compilation, and produces + * a jar containing only the class files for sources that were generated + * from idl processing. + */ +public class IdlClass { + + public static void main(String[] args) throws IOException { + OptionsParser optionsParser = OptionsParser.newOptionsParser(IdlClassOptions.class); + optionsParser.parseAndExitUponError(args); + IdlClassOptions options = optionsParser.getOptions(IdlClassOptions.class); + Preconditions.checkNotNull(options.manifestProto); + Preconditions.checkNotNull(options.classJar); + Preconditions.checkNotNull(options.outputClassJar); + Preconditions.checkNotNull(options.outputSourceJar); + Preconditions.checkNotNull(options.tempDir); + + List<Path> idlSources = Lists.newArrayList(); + for (String idlSource : optionsParser.getResidue()) { + idlSources.add(Paths.get(idlSource)); + } + + Manifest manifest = readManifest(options.manifestProto); + writeClassJar(options, idlSources, manifest); + writeSourceJar(options, idlSources, manifest); + } + + private static void writeClassJar(IdlClassOptions options, + List<Path> idlSources, Manifest manifest) throws IOException { + Path tempDir = options.tempDir.resolve("classjar"); + Set<String> idlSourceSet = Sets.newHashSet(); + for (Path path : idlSources) { + idlSourceSet.add(path.toString()); + } + extractIdlClasses(options.classJar, manifest, tempDir, idlSourceSet); + writeOutputJar(options.outputClassJar, tempDir); + } + + private static void writeSourceJar(IdlClassOptions options, + List<Path> idlSources, Manifest manifest) throws IOException { + Path tempDir = options.tempDir.resolve("sourcejar"); + Path idlSourceBaseDir = options.idlSourceBaseDir; + + for (Path path : idlSources) { + for (CompilationUnit unit : manifest.getCompilationUnitList()) { + if (unit.getPath().equals(path.toString())) { + String pkg = unit.getPkg(); + Path source = idlSourceBaseDir != null ? idlSourceBaseDir.resolve(path) : path; + Path target = tempDir.resolve(pkg.replace('.', '/')).resolve(path.getFileName()); + Files.createDirectories(target.getParent()); + Files.copy(source, target); + break; + } + } + } + writeOutputJar(options.outputSourceJar, tempDir); + } + + /** + * Reads the compilation manifest. + */ + private static Manifest readManifest(Path path) throws IOException { + Manifest manifest; + try (InputStream inputStream = Files.newInputStream(path)) { + manifest = Manifest.parseFrom(inputStream); + } + return manifest; + } + + /** + * For each top-level class in the compilation, determine the path prefix + * of classes corresponding to that compilation unit. + * + * <p>Prefixes are used to correctly handle inner classes, e.g. the top-level + * class "c.g.Foo" may correspond to "c/g/Foo.class" and also + * "c/g/Foo$Inner.class" or "c/g/Foo$0.class". + */ + @VisibleForTesting + static ImmutableSet<String> getIdlPrefixes(Manifest manifest, Set<String> idlSources) { + ImmutableSet.Builder<String> prefixes = ImmutableSet.builder(); + for (CompilationUnit unit : manifest.getCompilationUnitList()) { + if (!idlSources.contains(unit.getPath())) { + continue; + } + String pkg; + if (unit.hasPkg()) { + pkg = unit.getPkg().replace('.', '/') + "/"; + } else { + pkg = ""; + } + for (String toplevel : unit.getTopLevelList()) { + prefixes.add(pkg + toplevel); + } + } + return prefixes.build(); + } + + /** + * Unzip all the class files that correspond to idl processor- + * generated sources into the temporary directory. + */ + private static void extractIdlClasses( + Path classJar, + Manifest manifest, + Path tempDir, + Set<String> idlSources) + throws IOException { + ImmutableSet<String> prefixes = getIdlPrefixes(manifest, idlSources); + try (JarFile jar = new JarFile(classJar.toFile())) { + Enumeration<JarEntry> entries = jar.entries(); + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + String name = entry.getName(); + if (!name.endsWith(".class")) { + continue; + } + String prefix = name.substring(0, name.length() - ".class".length()); + int idx = prefix.indexOf('$'); + if (idx > 0) { + prefix = prefix.substring(0, idx); + } + if (prefixes.contains(prefix)) { + Files.createDirectories(tempDir.resolve(name).getParent()); + Files.copy(jar.getInputStream(entry), tempDir.resolve(name)); + } + } + } + } + + /** Writes the generated class files to the output jar. */ + private static void writeOutputJar(Path outputJar, Path tempDir) throws IOException { + JarCreator output = new JarCreator(outputJar.toString()); + output.setCompression(true); + output.setNormalize(true); + output.addDirectory(tempDir.toString()); + output.execute(); + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/idlclass/IdlClassOptions.java b/src/tools/android/java/com/google/devtools/build/android/idlclass/IdlClassOptions.java new file mode 100644 index 0000000000..d36bd1a1fd --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/idlclass/IdlClassOptions.java @@ -0,0 +1,67 @@ +// Copyright 2015 Google Inc. 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.android.idlclass; + +import com.google.devtools.build.android.Converters.ExistingPathConverter; +import com.google.devtools.build.android.Converters.PathConverter; +import com.google.devtools.common.options.Option; +import com.google.devtools.common.options.OptionsBase; + +import java.nio.file.Path; + +/** The options for a {@IdlClass} action. */ +public final class IdlClassOptions extends OptionsBase { + @Option(name = "manifest_proto", + defaultValue = "null", + converter = ExistingPathConverter.class, + category = "input", + help = "The path to the manifest file output by the Java compiler.") + public Path manifestProto; + + @Option(name = "class_jar", + defaultValue = "null", + converter = ExistingPathConverter.class, + category = "input", + help = "The path to the class jar output by the Java compiler.") + public Path classJar; + + @Option(name = "output_class_jar", + defaultValue = "null", + converter = PathConverter.class, + category = "output", + help = "The path to write the class jar output to.") + public Path outputClassJar; + + @Option(name = "output_source_jar", + defaultValue = "null", + converter = PathConverter.class, + category = "output", + help = "The path to write the source jar output to.") + public Path outputSourceJar; + + @Option(name = "temp_dir", + defaultValue = "null", + converter = PathConverter.class, + category = "input", + help = "The path to a temp directory.") + public Path tempDir; + + @Option(name = "idl_source_base_dir", + defaultValue = "null", + converter = PathConverter.class, + category = "input", + help = "The path to the base directory of the idl sources. Optional; Used for testing.") + public Path idlSourceBaseDir; +} |