diff options
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/rules/android/AndroidIdlHelper.java')
-rw-r--r-- | src/main/java/com/google/devtools/build/lib/rules/android/AndroidIdlHelper.java | 373 |
1 files changed, 373 insertions, 0 deletions
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); + } +} |