| // Copyright 2015 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; |
| |
| 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.ParamFileInfo; |
| import com.google.devtools.build.lib.actions.ParameterFile.ParameterFileType; |
| import com.google.devtools.build.lib.analysis.OutputGroupInfo; |
| import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder; |
| import com.google.devtools.build.lib.analysis.RuleContext; |
| 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.SpawnAction; |
| import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget.Mode; |
| 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.packages.BuildType; |
| import com.google.devtools.build.lib.packages.Type; |
| import com.google.devtools.build.lib.rules.java.JavaUtil; |
| import com.google.devtools.build.lib.rules.java.ProguardSpecProvider; |
| 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.List; |
| import java.util.Map; |
| import java.util.Set; |
| import javax.annotation.Nullable; |
| |
| /** Helper class for Android IDL processing. */ |
| public class AndroidIdlHelper { |
| |
| /** |
| * Name of the output group used for idl jars (the jars containing the class files for sources |
| * generated from annotation processors). |
| */ |
| static final String IDL_JARS_OUTPUT_GROUP = |
| OutputGroupInfo.HIDDEN_OUTPUT_GROUP_PREFIX + "idl_jars"; |
| |
| private final RuleContext ruleContext; |
| private final AndroidIdlProvider androidIdlProvider; |
| private final Map<Artifact, Artifact> translatedIdlSources; |
| private final Artifact idlClassJar; |
| private final Artifact idlSourceJar; |
| |
| /** |
| * Creates a new AndroidIdlHelper wrapping the given {@code ruleContext}. |
| * |
| * @param ruleContext The rule context whose idl attributes will be used to collect .aidl files. |
| * @param baseArtifact An artifact used to calculate the paths for the IDL class and source jars. |
| */ |
| public AndroidIdlHelper(RuleContext ruleContext, Artifact baseArtifact) { |
| this.ruleContext = ruleContext; |
| |
| checkIdlRootImport(ruleContext); |
| |
| Collection<Artifact> idls = getIdlSrcs(ruleContext); |
| |
| if (!idls.isEmpty() && !ruleContext.hasErrors()) { |
| translatedIdlSources = generateTranslatedIdlArtifacts(ruleContext, idls); |
| idlClassJar = createIdlJar(baseArtifact, "-idl.jar"); |
| idlSourceJar = createIdlJar(baseArtifact, "-idl.srcjar"); |
| } else { |
| translatedIdlSources = ImmutableMap.of(); |
| idlClassJar = null; |
| idlSourceJar = null; |
| } |
| |
| androidIdlProvider = createAndroidIdlProvider(ruleContext, idlClassJar, idlSourceJar); |
| } |
| |
| /** |
| * Adds the necessary providers to the {@code builder}. |
| * |
| * <p>Adds an {@link AndroidIdlProvider} to the target, and adds the transitive generated IDL jars |
| * to the IDL_JARS_OUTPUT_GROUP. This also generates the actions to compile the .aidl files to |
| * .java, as well as the .jar and .srcjar files consisting of only the IDL-generated source and |
| * class files. |
| * |
| * @param builder The target builder to add the providers to. |
| * @param classJar The class jar to be separated into the IDL class jar. |
| * @param manifestProtoOutput The manifest generated by JavaBuilder, for identifying IDL-generated |
| * class files in the class jar. |
| */ |
| public void addTransitiveInfoProviders( |
| RuleConfiguredTargetBuilder builder, Artifact classJar, Artifact manifestProtoOutput) { |
| if (!translatedIdlSources.isEmpty()) { |
| generateAndroidIdlCompilationActions(ruleContext, androidIdlProvider, translatedIdlSources); |
| createIdlClassJarAction( |
| ruleContext, |
| classJar, |
| translatedIdlSources.values(), |
| manifestProtoOutput, |
| idlClassJar, |
| idlSourceJar); |
| } |
| builder |
| .addNativeDeclaredProvider(androidIdlProvider) |
| .addOutputGroup(IDL_JARS_OUTPUT_GROUP, androidIdlProvider.getTransitiveIdlJars()); |
| } |
| |
| /** |
| * Returns the root directory under which idl_srcs and idl_parcelables are located in this rule. |
| */ |
| public String getIdlImportRoot() { |
| return hasExplicitlySpecifiedIdlImportRoot(ruleContext) ? getIdlImportRoot(ruleContext) : null; |
| } |
| |
| /** Returns the raw (non-processed) idl_srcs, not including parcelable marker files. */ |
| public Collection<Artifact> getIdlSources() { |
| return translatedIdlSources.keySet(); |
| } |
| |
| /** Returns the idl_parcelables, consisting of parcelable marker files defined on this rule. */ |
| public Collection<Artifact> getIdlParcelables() { |
| return getIdlParcelables(ruleContext); |
| } |
| |
| /** Returns the idl_preprocessed. */ |
| public Collection<Artifact> getIdlPreprocessed() { |
| return getIdlPreprocessed(ruleContext); |
| } |
| |
| /** Returns the generated Java sources created from the idl_srcs. */ |
| public Collection<Artifact> getIdlGeneratedJavaSources() { |
| return translatedIdlSources.values(); |
| } |
| |
| /** |
| * Returns the jar containing class files derived from the .aidl files. |
| * |
| * <p>Will be null if there are no idl_srcs. |
| */ |
| @Nullable |
| public Artifact getIdlClassJar() { |
| return idlClassJar; |
| } |
| |
| /** |
| * Returns the jar containing source files derived from the .aidl files. |
| * |
| * <p>Will be null if there are no idl_srcs. |
| */ |
| @Nullable |
| public Artifact getIdlSourceJar() { |
| return idlSourceJar; |
| } |
| |
| public static boolean hasIdlSrcs(RuleContext ruleContext) { |
| return !getIdlSrcs(ruleContext).isEmpty(); |
| } |
| |
| /** |
| * Returns a new list with the idl libs added to the given list if necessary, or the same list. |
| */ |
| public static ImmutableList<TransitiveInfoCollection> maybeAddSupportLibs( |
| RuleContext ruleContext, ImmutableList<TransitiveInfoCollection> deps) { |
| if (!hasIdlSrcs(ruleContext)) { |
| return deps; |
| } |
| TransitiveInfoCollection aidlLib = AndroidSdkProvider.fromRuleContext(ruleContext).getAidlLib(); |
| if (aidlLib == null) { |
| return deps; |
| } |
| return ImmutableList.<TransitiveInfoCollection>builder().addAll(deps).add(aidlLib).build(); |
| } |
| |
| public static void maybeAddSupportLibProguardConfigs( |
| RuleContext ruleContext, NestedSetBuilder<Artifact> proguardConfigsBuilder) { |
| if (!hasIdlSrcs(ruleContext)) { |
| return; |
| } |
| TransitiveInfoCollection aidlLib = AndroidSdkProvider.fromRuleContext(ruleContext).getAidlLib(); |
| if (aidlLib == null) { |
| return; |
| } |
| ProguardSpecProvider provider = aidlLib.get(ProguardSpecProvider.PROVIDER); |
| if (provider == null) { |
| return; |
| } |
| proguardConfigsBuilder.addTransitive(provider.getTransitiveProguardSpecs()); |
| } |
| |
| /** Generates an artifact by replacing the extension of the input with the suffix. */ |
| private Artifact createIdlJar(Artifact baseArtifact, String suffix) { |
| return ruleContext.getDerivedArtifact( |
| FileSystemUtils.replaceExtension(baseArtifact.getRootRelativePath(), suffix), |
| baseArtifact.getRoot()); |
| } |
| |
| /** Returns the idl_parcelables defined on the given rule. */ |
| 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(); |
| } |
| |
| /** Returns the idl_srcs defined on the given rule. */ |
| 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(); |
| } |
| |
| /** |
| * Checks that all of the idl_srcs in the given rule are in the same package as the rule itself. |
| */ |
| 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"); |
| } |
| } |
| } |
| |
| /** |
| * Generates matching .java sources for the given .aidl sources. |
| * |
| * @return A mapping from .aidl input to .java output. |
| */ |
| 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( |
| PathFragment.create(ruleName + "_aidl").getRelative(idl.getRootRelativePath()), |
| ".java"); |
| Artifact output = ruleContext.getGenfilesArtifact(javaOutputPath.getPathString()); |
| outputJavaSources.put(idl, output); |
| } |
| return outputJavaSources.build(); |
| } |
| |
| /** |
| * Generates the actions to compile the given .aidl sources into .java sources. |
| * |
| * @param ruleContext The rule context in which to generate the actions. |
| * @param transitiveIdlImportData A provider to supply the artifacts and import roots to give to |
| * the compiler. |
| * @param translatedIdlSources A map from input .aidl to output .java of files to be compiled. |
| */ |
| private static void generateAndroidIdlCompilationActions( |
| RuleContext ruleContext, |
| AndroidIdlProvider transitiveIdlImportData, |
| Map<Artifact, Artifact> translatedIdlSources) { |
| AndroidSdkProvider sdk = AndroidSdkProvider.fromRuleContext(ruleContext); |
| List<String> preprocessedArgs = new ArrayList<>(); |
| |
| // add import roots so the aidl compiler will know where to look for the imports |
| for (String idlImport : transitiveIdlImportData.getTransitiveIdlImportRoots().toList()) { |
| preprocessedArgs.add("-I" + idlImport); |
| } |
| // add preprocessed aidl files |
| preprocessedArgs.add("-p" + sdk.getFrameworkAidl().getExecPathString()); |
| for (Artifact idlPreprocessed : |
| transitiveIdlImportData.getTransitiveIdlPreprocessed().toList()) { |
| preprocessedArgs.add("-p" + idlPreprocessed.getExecPathString()); |
| } |
| |
| for (Map.Entry<Artifact, Artifact> entry : translatedIdlSources.entrySet()) { |
| createAndroidIdlAction( |
| ruleContext, |
| entry.getKey(), |
| transitiveIdlImportData.getTransitiveIdlImports(), |
| entry.getValue(), |
| preprocessedArgs); |
| } |
| } |
| |
| /** |
| * Creates an action to split out classes and source files created by aidls. |
| * |
| * @param ruleContext The rule context in which to generate the action. |
| * @param classJar The class jar to divide into IDL class and source jars. |
| * @param generatedIdlJavaFiles The source files which should be put into the source jar and used |
| * to determine the classes to take. |
| * @param manifestProtoOutput The protobuf containing the manifest generated from JavaBuilder. |
| * @param idlClassJar The artifact into which the IDL class jar should be written. |
| * @param idlSourceJar The artifact into which the IDL source jar should be written. |
| */ |
| private static void createIdlClassJarAction( |
| RuleContext ruleContext, |
| Artifact classJar, |
| Iterable<Artifact> generatedIdlJavaFiles, |
| Artifact manifestProtoOutput, |
| Artifact idlClassJar, |
| Artifact idlSourceJar) { |
| String basename = FileSystemUtils.removeExtension(classJar.getExecPath().getBaseName()); |
| PathFragment idlTempDir = |
| ruleContext |
| .getConfiguration() |
| .getBinDirectory(ruleContext.getRule().getRepository()) |
| .getExecPath() |
| .getRelative(ruleContext.getUniqueDirectory("_idl")) |
| .getRelative(basename + "_temp"); |
| ruleContext.registerAction( |
| new SpawnAction.Builder() |
| .addInput(manifestProtoOutput) |
| .addInput(classJar) |
| .addInputs(generatedIdlJavaFiles) |
| .addOutput(idlClassJar) |
| .addOutput(idlSourceJar) |
| .setExecutable(ruleContext.getExecutablePrerequisite("$idlclass", Mode.HOST)) |
| .addCommandLine( |
| 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) |
| .addExecPaths(ImmutableList.copyOf(generatedIdlJavaFiles)) |
| .build(), |
| ParamFileInfo.builder(ParameterFileType.SHELL_QUOTED).build()) |
| .setProgressMessage("Building idl jars %s", idlClassJar.prettyPrint()) |
| .setMnemonic("AndroidIdlJars") |
| .build(ruleContext)); |
| } |
| |
| /** |
| * Creates an action to convert an .aidl source into a .java output. |
| * |
| * @param ruleContext The rule context in which to generate the action. |
| * @param idl The .aidl file to be converted to .java. |
| * @param idlImports The artifacts which should be accessible to this compilation action. |
| * @param output The .java file where the .aidl file will be converted to. |
| * @param importArgs The arguments defining the import roots and framework .aidl. |
| */ |
| private static void createAndroidIdlAction( |
| RuleContext ruleContext, |
| Artifact idl, |
| NestedSet<Artifact> idlImports, |
| Artifact output, |
| List<String> importArgs) { |
| AndroidSdkProvider sdk = AndroidSdkProvider.fromRuleContext(ruleContext); |
| ruleContext.registerAction( |
| new SpawnAction.Builder() |
| .setExecutable(sdk.getAidl()) |
| .addInput(idl) |
| .addTransitiveInputs(idlImports) |
| .addInput(sdk.getFrameworkAidl()) |
| .addInputs(getIdlPreprocessed(ruleContext)) |
| .addOutput(output) |
| .setProgressMessage("Android IDL generation") |
| .setMnemonic("AndroidIDLGenerate") |
| .addCommandLine( |
| CustomCommandLine.builder() |
| .add("-b") // Fail if trying to compile a parcelable. |
| .addAll(importArgs) |
| .addExecPath(idl) |
| .addExecPath(output) |
| .build()) |
| .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)) |
| .addAll(getIdlPreprocessed(ruleContext)) |
| .build(); |
| } |
| |
| /** |
| * Collects the importable .aidl files and AIDL class/source jars from this rule and its deps. |
| * |
| * @param ruleContext The rule context from which to harvest .aidl sources and parcelables, as |
| * well as dependencies. |
| * @param idlClassJar An artifact corresponding to an AIDL class jar for this rule, or null if one |
| * does not exist. |
| * @param idlSourceJar An artifact corresponding to an AIDL source jar for this rule, or null if |
| * one does not exist. |
| * @return A provider containing the collected data, suitable to be provided by this rule. |
| */ |
| 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(); |
| NestedSetBuilder<Artifact> preprocessedBuilder = NestedSetBuilder.naiveLinkOrder(); |
| if (idlClassJar != null) { |
| jarsBuilder.add(idlClassJar); |
| } |
| if (idlSourceJar != null) { |
| jarsBuilder.add(idlSourceJar); |
| } |
| |
| for (AndroidIdlProvider dep : |
| AndroidCommon.getTransitivePrerequisites( |
| ruleContext, Mode.TARGET, AndroidIdlProvider.PROVIDER)) { |
| rootsBuilder.addTransitive(dep.getTransitiveIdlImportRoots()); |
| importsBuilder.addTransitive(dep.getTransitiveIdlImports()); |
| preprocessedBuilder.addTransitive(dep.getTransitiveIdlPreprocessed()); |
| 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); |
| |
| Collection<Artifact> idlPreprocessed = getIdlPreprocessed(ruleContext); |
| preprocessedBuilder.addAll(idlPreprocessed); |
| |
| return new AndroidIdlProvider( |
| rootsBuilder.build(), |
| importsBuilder.build(), |
| jarsBuilder.build(), |
| preprocessedBuilder.build()); |
| } |
| |
| /** Checks that idl_import_root is only set if idl_srcs or idl_parcelables was. */ |
| 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); |
| } |
| |
| /** Returns the idl_preprocessed defined on the given rule. */ |
| private static Collection<Artifact> getIdlPreprocessed(RuleContext ruleContext) { |
| return ruleContext.isAttrDefined("idl_preprocessed", BuildType.LABEL_LIST) |
| ? ruleContext |
| .getPrerequisiteArtifacts("idl_preprocessed", Mode.TARGET) |
| .filter(AndroidRuleClasses.ANDROID_IDL) |
| .list() |
| : ImmutableList.<Artifact>of(); |
| } |
| } |