blob: 11d64c95a8510d9a4b784166d8e4cf47469553b3 [file] [log] [blame]
// 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 static com.google.devtools.build.lib.analysis.config.BuildConfiguration.StrictDepsMode.DEFAULT;
import static com.google.devtools.build.lib.analysis.config.BuildConfiguration.StrictDepsMode.ERROR;
import static com.google.devtools.build.lib.analysis.config.BuildConfiguration.StrictDepsMode.STRICT;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.actions.ResourceSet;
import com.google.devtools.build.lib.analysis.AnalysisUtils;
import com.google.devtools.build.lib.analysis.FilesToRunProvider;
import com.google.devtools.build.lib.analysis.OutputGroupProvider;
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.Runfiles;
import com.google.devtools.build.lib.analysis.RunfilesProvider;
import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
import com.google.devtools.build.lib.analysis.TransitiveInfoProvider;
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.collect.IterablesChain;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
import com.google.devtools.build.lib.collect.nestedset.Order;
import com.google.devtools.build.lib.packages.AggregatingAttributeMapper;
import com.google.devtools.build.lib.packages.AttributeMap;
import com.google.devtools.build.lib.packages.BuildType;
import com.google.devtools.build.lib.packages.Rule;
import com.google.devtools.build.lib.rules.android.AndroidResourcesProvider.ResourceContainer;
import com.google.devtools.build.lib.rules.android.AndroidResourcesProvider.ResourceType;
import com.google.devtools.build.lib.rules.android.AndroidRuleClasses.MultidexMode;
import com.google.devtools.build.lib.rules.cpp.CcLinkParams;
import com.google.devtools.build.lib.rules.cpp.CcLinkParamsProvider;
import com.google.devtools.build.lib.rules.cpp.CcLinkParamsStore;
import com.google.devtools.build.lib.rules.cpp.CcNativeLibraryProvider;
import com.google.devtools.build.lib.rules.cpp.CppFileTypes;
import com.google.devtools.build.lib.rules.cpp.LinkerInput;
import com.google.devtools.build.lib.rules.java.ClasspathConfiguredFragment;
import com.google.devtools.build.lib.rules.java.JavaCcLinkParamsProvider;
import com.google.devtools.build.lib.rules.java.JavaCommon;
import com.google.devtools.build.lib.rules.java.JavaCompilationArgs;
import com.google.devtools.build.lib.rules.java.JavaCompilationArgs.ClasspathType;
import com.google.devtools.build.lib.rules.java.JavaCompilationArgsProvider;
import com.google.devtools.build.lib.rules.java.JavaCompilationArtifacts;
import com.google.devtools.build.lib.rules.java.JavaCompilationHelper;
import com.google.devtools.build.lib.rules.java.JavaNativeLibraryProvider;
import com.google.devtools.build.lib.rules.java.JavaRuleOutputJarsProvider;
import com.google.devtools.build.lib.rules.java.JavaRuleOutputJarsProvider.OutputJar;
import com.google.devtools.build.lib.rules.java.JavaRuntimeJarProvider;
import com.google.devtools.build.lib.rules.java.JavaSemantics;
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.rules.java.proto.GeneratedExtensionRegistryProvider;
import com.google.devtools.build.lib.rules.test.InstrumentedFilesCollector.InstrumentationSpec;
import com.google.devtools.build.lib.syntax.Type;
import com.google.devtools.build.lib.util.Preconditions;
import com.google.devtools.build.lib.vfs.PathFragment;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import javax.annotation.Nullable;
/**
* A helper class for android rules.
*
* <p>Helps create the java compilation as well as handling the exporting of the java compilation
* artifacts to the other rules.
*/
public class AndroidCommon {
public static final InstrumentationSpec ANDROID_COLLECTION_SPEC = JavaCommon.JAVA_COLLECTION_SPEC
.withDependencyAttributes("deps", "data", "exports", "runtime_deps", "binary_under_test");
public static final Set<String> TRANSITIVE_ATTRIBUTES = ImmutableSet.of(
"deps",
"exports"
);
public static final <T extends TransitiveInfoProvider> Iterable<T> getTransitivePrerequisites(
RuleContext ruleContext, Mode mode, final Class<T> classType) {
IterablesChain.Builder<T> builder = IterablesChain.builder();
for (String attr : TRANSITIVE_ATTRIBUTES) {
if (ruleContext.getAttribute(attr) != null) {
builder.add(ruleContext.getPrerequisites(attr, mode, classType));
}
}
return builder.build();
}
public static final Iterable<TransitiveInfoCollection> collectTransitiveInfo(
RuleContext ruleContext, Mode mode) {
ImmutableList.Builder<TransitiveInfoCollection> builder = ImmutableList.builder();
for (String attr : TRANSITIVE_ATTRIBUTES) {
if (ruleContext.getAttribute(attr) != null) {
builder.addAll(ruleContext.getPrerequisites(attr, mode));
}
}
return builder.build();
}
private final RuleContext ruleContext;
private final JavaCommon javaCommon;
private final boolean asNeverLink;
private final boolean exportDeps;
private NestedSet<Artifact> compileTimeDependencyArtifacts;
private NestedSet<Artifact> filesToBuild;
private NestedSet<Artifact> transitiveNeverlinkLibraries =
NestedSetBuilder.emptySet(Order.STABLE_ORDER);
private JavaCompilationArgs javaCompilationArgs = JavaCompilationArgs.EMPTY_ARGS;
private JavaCompilationArgs recursiveJavaCompilationArgs = JavaCompilationArgs.EMPTY_ARGS;
private JackCompilationHelper jackCompilationHelper;
private ImmutableList<Artifact> jarsProducedForRuntime;
private Artifact classJar;
private Artifact iJar;
private Artifact srcJar;
private Artifact genClassJar;
private Artifact genSourceJar;
private Artifact resourceClassJar;
private Artifact resourceSourceJar;
private Artifact outputDepsProto;
private GeneratedExtensionRegistryProvider generatedExtensionRegistryProvider;
private final JavaSourceJarsProvider.Builder javaSourceJarsProviderBuilder =
JavaSourceJarsProvider.builder();
private final JavaRuleOutputJarsProvider.Builder javaRuleOutputJarsProviderBuilder =
JavaRuleOutputJarsProvider.builder();
private Artifact manifestProtoOutput;
private AndroidIdlHelper idlHelper;
public AndroidCommon(JavaCommon javaCommon) {
this(javaCommon, JavaCommon.isNeverLink(javaCommon.getRuleContext()), false);
}
/**
* Creates a new AndroidCommon.
* @param common the JavaCommon instance
* @param asNeverLink Boolean to indicate if this rule should be treated as a compile time dep
* by consuming rules.
* @param exportDeps Boolean to indicate if the dependencies should be treated as "exported" deps.
*/
public AndroidCommon(JavaCommon common, boolean asNeverLink, boolean exportDeps) {
this.ruleContext = common.getRuleContext();
this.asNeverLink = asNeverLink;
this.exportDeps = exportDeps;
this.javaCommon = common;
}
/**
* Collects the transitive neverlink dependencies.
*
* @param ruleContext the context of the rule neverlink deps are to be computed for
* @param deps the targets to be treated as dependencies
* @param runtimeJars the runtime jars produced by the rule (non-transitive)
*
* @return a nested set of the neverlink deps.
*/
public static NestedSet<Artifact> collectTransitiveNeverlinkLibraries(
RuleContext ruleContext, Iterable<? extends TransitiveInfoCollection> deps,
ImmutableList<Artifact> runtimeJars) {
NestedSetBuilder<Artifact> builder = NestedSetBuilder.naiveLinkOrder();
for (AndroidNeverLinkLibrariesProvider provider : AnalysisUtils.getProviders(deps,
AndroidNeverLinkLibrariesProvider.class)) {
builder.addTransitive(provider.getTransitiveNeverLinkLibraries());
}
if (JavaCommon.isNeverLink(ruleContext)) {
builder.addAll(runtimeJars);
for (JavaCompilationArgsProvider provider : AnalysisUtils.getProviders(
deps, JavaCompilationArgsProvider.class)) {
builder.addTransitive(provider.getRecursiveJavaCompilationArgs().getRuntimeJars());
}
}
return builder.build();
}
/**
* Creates an action that converts {@code jarToDex} to a dex file. The output will be stored in
* the {@link com.google.devtools.build.lib.actions.Artifact} {@code dxJar}.
*/
public static void createDexAction(
RuleContext ruleContext,
Artifact jarToDex, Artifact classesDex, List<String> dexOptions, boolean multidex,
Artifact mainDexList) {
List<String> args = new ArrayList<>();
args.add("--dex");
// Add --no-locals to coverage builds. Older coverage tools don't correctly preserve local
// variable information in stack frame maps that are required since Java 7, so to avoid runtime
// errors we just don't add local variable info in the first place. This may no longer be
// necessary, however, as long as we use a coverage tool that generates stack frame maps.
if (ruleContext.getConfiguration().isCodeCoverageEnabled()) {
args.add("--no-locals"); // TODO(bazel-team): Is this still needed?
}
// Multithreaded dex does not work when using --multi-dex.
if (!multidex) {
// Multithreaded dex tends to run faster, but only up to about 5 threads (at which point the
// law of diminishing returns kicks in). This was determined experimentally, with 5-thread dex
// performing about 25% faster than 1-thread dex.
args.add("--num-threads=5");
}
args.addAll(dexOptions);
if (multidex) {
args.add("--multi-dex");
if (mainDexList != null) {
args.add("--main-dex-list=" + mainDexList.getExecPathString());
}
}
args.add("--output=" + classesDex.getExecPathString());
args.add(jarToDex.getExecPathString());
SpawnAction.Builder builder = new SpawnAction.Builder()
.setExecutable(AndroidSdkProvider.fromRuleContext(ruleContext).getDx())
.addInput(jarToDex)
.addOutput(classesDex)
.addArguments(args)
.setProgressMessage("Converting " + jarToDex.getExecPathString() + " to dex format")
.setMnemonic("AndroidDexer")
.setResources(ResourceSet.createWithRamCpuIo(4096.0, 5.0, 0.0));
if (mainDexList != null) {
builder.addInput(mainDexList);
}
ruleContext.registerAction(builder.build(ruleContext));
}
public static AndroidIdeInfoProvider createAndroidIdeInfoProvider(
RuleContext ruleContext,
AndroidSemantics semantics,
AndroidIdlHelper idlHelper,
OutputJar resourceJar,
Artifact aar,
ResourceApk resourceApk,
Artifact zipAlignedApk,
Iterable<Artifact> apksUnderTest) {
AndroidIdeInfoProvider.Builder ideInfoProviderBuilder =
new AndroidIdeInfoProvider.Builder()
.setIdlClassJar(idlHelper.getIdlClassJar())
.setIdlSourceJar(idlHelper.getIdlSourceJar())
.setResourceJar(resourceJar)
.setAar(aar)
.addIdlParcelables(idlHelper.getIdlParcelables())
.addIdlSrcs(idlHelper.getIdlSources())
.addIdlGeneratedJavaFiles(idlHelper.getIdlGeneratedJavaSources())
.addAllApksUnderTest(apksUnderTest);
if (zipAlignedApk != null) {
ideInfoProviderBuilder.setApk(zipAlignedApk);
}
// If the rule defines resources, put those in the IDE info. Otherwise, proxy the data coming
// from the android_resources rule in its direct dependencies, if such a thing exists.
if (LocalResourceContainer.definesAndroidResources(ruleContext.attributes())) {
ideInfoProviderBuilder
.setDefinesAndroidResources(true)
.addResourceSources(resourceApk.getPrimaryResource().getArtifacts(ResourceType.RESOURCES))
.addAssetSources(
resourceApk.getPrimaryResource().getArtifacts(ResourceType.ASSETS),
getAssetDir(ruleContext))
// Sets the possibly merged manifest and the raw manifest.
.setGeneratedManifest(resourceApk.getPrimaryResource().getManifest())
.setManifest(ruleContext.getPrerequisiteArtifact("manifest", Mode.TARGET))
.setJavaPackage(getJavaPackage(ruleContext));
} else {
semantics.addNonLocalResources(ruleContext, resourceApk, ideInfoProviderBuilder);
}
return ideInfoProviderBuilder.build();
}
public static String getJavaPackage(RuleContext ruleContext) {
AttributeMap attributes = ruleContext.attributes();
if (attributes.isAttributeValueExplicitlySpecified("custom_package")) {
return attributes.get("custom_package", Type.STRING);
}
return getDefaultJavaPackage(ruleContext.getRule());
}
public static Iterable<String> getPossibleJavaPackages(Rule rule) {
AggregatingAttributeMapper attributes = AggregatingAttributeMapper.of(rule);
if (attributes.isAttributeValueExplicitlySpecified("custom_package")) {
return attributes.visitAttribute("custom_package", Type.STRING);
}
return ImmutableList.of(getDefaultJavaPackage(rule));
}
private static String getDefaultJavaPackage(Rule rule) {
PathFragment nameFragment = rule.getPackage().getNameFragment();
String packageName = JavaUtil.getJavaFullClassname(nameFragment);
if (packageName != null) {
return packageName;
} else {
// This is a workaround for libraries that don't follow the standard Bazel package format
return nameFragment.getPathString().replace('/', '.');
}
}
static PathFragment getSourceDirectoryRelativePathFromResource(Artifact resource) {
PathFragment resourceDir = LocalResourceContainer.Builder.findResourceDir(resource);
if (resourceDir == null) {
return null;
}
return trimTo(resource.getRootRelativePath(), resourceDir);
}
/**
* Finds the rightmost occurrence of the needle and returns subfragment of the haystack from
* left to the end of the occurrence inclusive of the needle.
*
* <pre>
* `Example:
* Given the haystack:
* res/research/handwriting/res/values/strings.xml
* And the needle:
* res
* Returns:
* res/research/handwriting/res
* </pre>
*/
static PathFragment trimTo(PathFragment haystack, PathFragment needle) {
if (needle.equals(PathFragment.EMPTY_FRAGMENT)) {
return haystack;
}
// Compute the overlap offset for duplicated parts of the needle.
int[] overlap = new int[needle.segmentCount() + 1];
// Start overlap at -1, as it will cancel out the increment in the search.
// See http://en.wikipedia.org/wiki/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm for the
// details.
overlap[0] = -1;
for (int i = 0, j = -1; i < needle.segmentCount(); j++, i++, overlap[i] = j) {
while (j >= 0 && !needle.getSegment(i).equals(needle.getSegment(j))) {
// Walk the overlap until the bound is found.
j = overlap[j];
}
}
// TODO(corysmith): reverse the search algorithm.
// Keep the index of the found so that the rightmost index is taken.
int found = -1;
for (int i = 0, j = 0; i < haystack.segmentCount(); i++) {
while (j >= 0 && !haystack.getSegment(i).equals(needle.getSegment(j))) {
// Not matching, walk the needle index to attempt another match.
j = overlap[j];
}
j++;
// Needle index is exhausted, so the needle must match.
if (j == needle.segmentCount()) {
// Record the found index + 1 to be inclusive of the end index.
found = i + 1;
// Subtract one from the needle index to restart the search process
j = j - 1;
}
}
if (found != -1) {
// Return the subsection of the haystack.
return haystack.subFragment(0, found);
}
throw new IllegalArgumentException(String.format("%s was not found in %s", needle, haystack));
}
Artifact compileDexWithJack(
MultidexMode mode, Optional<Artifact> mainDexList, Collection<Artifact> proguardSpecs) {
return jackCompilationHelper.compileAsDex(mode, mainDexList, proguardSpecs);
}
private void compileResources(
JavaSemantics javaSemantics,
ResourceApk resourceApk,
Artifact resourcesJar,
JavaCompilationArtifacts.Builder artifactsBuilder,
JavaTargetAttributes.Builder attributes,
NestedSetBuilder<Artifact> filesBuilder,
ImmutableList.Builder<Artifact> jarsProducedForRuntime,
boolean useRClassGenerator) throws InterruptedException {
compileResourceJar(javaSemantics, resourceApk, resourcesJar, useRClassGenerator);
// Add the compiled resource jar to the classpath of the main compilation.
attributes.addDirectJars(NestedSetBuilder.create(Order.STABLE_ORDER, resourceClassJar));
// Add the compiled resource jar to the classpath of consuming targets.
// We don't actually use the ijar. That is almost the same as the resource class jar
// except for <clinit>, but it takes time to build and waiting for that to build would
// just delay building the rest of the library.
artifactsBuilder.addCompileTimeJar(resourceClassJar);
// Combined resource constants needs to come even before our own classes that may contain
// local resource constants.
artifactsBuilder.addRuntimeJar(resourceClassJar);
jarsProducedForRuntime.add(resourceClassJar);
// Add the compiled resource jar as a declared output of the rule.
filesBuilder.add(resourceSourceJar);
filesBuilder.add(resourceClassJar);
}
private void compileResourceJar(
JavaSemantics javaSemantics, ResourceApk resourceApk, Artifact resourcesJar,
boolean useRClassGenerator)
throws InterruptedException {
resourceSourceJar = ruleContext.getImplicitOutputArtifact(
AndroidRuleClasses.ANDROID_RESOURCES_SOURCE_JAR);
resourceClassJar = ruleContext.getImplicitOutputArtifact(
AndroidRuleClasses.ANDROID_RESOURCES_CLASS_JAR);
JavaCompilationArtifacts.Builder javaArtifactsBuilder = new JavaCompilationArtifacts.Builder();
JavaTargetAttributes.Builder javacAttributes = new JavaTargetAttributes.Builder(javaSemantics)
.addSourceJar(resourcesJar);
JavaCompilationHelper javacHelper = new JavaCompilationHelper(
ruleContext, javaSemantics, getJavacOpts(), javacAttributes);
// Only build the class jar if it's not already generated internally by resource processing.
if (resourceApk.getResourceJavaClassJar() == null) {
if (useRClassGenerator) {
RClassGeneratorActionBuilder actionBuilder =
new RClassGeneratorActionBuilder(ruleContext)
.withPrimary(resourceApk.getPrimaryResource())
.withDependencies(resourceApk.getResourceDependencies())
.setClassJarOut(resourceClassJar);
actionBuilder.build();
} else {
Artifact outputDepsProto =
javacHelper.createOutputDepsProtoArtifact(resourceClassJar, javaArtifactsBuilder);
javacHelper.createCompileActionWithInstrumentation(
resourceClassJar,
null /* manifestProtoOutput */,
null /* genSourceJar */,
outputDepsProto,
javaArtifactsBuilder);
}
} else {
// Otherwise, it should have been the AndroidRuleClasses.ANDROID_RESOURCES_CLASS_JAR.
Preconditions.checkArgument(
resourceApk.getResourceJavaClassJar().equals(
ruleContext.getImplicitOutputArtifact(
AndroidRuleClasses.ANDROID_RESOURCES_CLASS_JAR)));
}
javacHelper.createSourceJarAction(resourceSourceJar, null);
}
private void createJarJarActions(
JavaTargetAttributes.Builder attributes,
ImmutableList.Builder<Artifact> jarsProducedForRuntime,
Iterable<ResourceContainer> resourceContainers,
String originalPackage,
Artifact binaryResourcesJar) {
// Now use jarjar for the rest of the resources. We need to make a copy
// of the final generated resources for each of the targets included in
// the transitive closure of this binary.
for (ResourceContainer otherContainer : resourceContainers) {
if (otherContainer.getLabel().equals(ruleContext.getLabel())) {
continue;
}
Artifact resourcesJar = createResourceJarArtifact(ruleContext, otherContainer, ".jar");
// combined resource constants copy needs to come before library classes that may contain
// their local resource constants
attributes.addRuntimeClassPathEntry(resourcesJar);
Artifact jarJarRuleFile = createResourceJarArtifact(
ruleContext, otherContainer, ".jar_jarjar_rules.txt");
String jarJarRule = String.format("rule %s.* %s.@1",
originalPackage, otherContainer.getJavaPackage());
ruleContext.registerAction(new FileWriteAction(
ruleContext.getActionOwner(), jarJarRuleFile, jarJarRule, false));
FilesToRunProvider jarjar =
ruleContext.getExecutablePrerequisite("$jarjar_bin", Mode.HOST);
ruleContext.registerAction(new SpawnAction.Builder()
.setExecutable(jarjar)
.addArgument("process")
.addInputArgument(jarJarRuleFile)
.addInputArgument(binaryResourcesJar)
.addOutputArgument(resourcesJar)
.setProgressMessage("Repackaging jar")
.setMnemonic("AndroidRepackageJar")
.build(ruleContext));
jarsProducedForRuntime.add(resourcesJar);
}
}
private static Artifact createResourceJarArtifact(RuleContext ruleContext,
ResourceContainer container, String fileNameSuffix) {
String artifactName = container.getLabel().getName() + fileNameSuffix;
// Since the Java sources are generated by combining all resources with the
// ones included in the binary, the path of the artifact has to be unique
// per binary and per library (not only per library).
Artifact artifact = ruleContext.getUniqueDirectoryArtifact("resource_jars",
container.getLabel().getPackageIdentifier().getSourceRoot().getRelative(artifactName),
ruleContext.getBinOrGenfilesDirectory());
return artifact;
}
public JavaTargetAttributes init(
JavaSemantics javaSemantics,
AndroidSemantics androidSemantics,
ResourceApk resourceApk,
boolean addCoverageSupport,
boolean collectJavaCompilationArgs,
boolean isBinary)
throws InterruptedException {
classJar = ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_LIBRARY_CLASS_JAR);
idlHelper = new AndroidIdlHelper(ruleContext, classJar);
JavaTargetAttributes.Builder attributes =
javaCommon
.initCommon(
idlHelper.getIdlGeneratedJavaSources(),
androidSemantics.getJavacArguments(ruleContext))
.setBootClassPath(
ImmutableList.of(AndroidSdkProvider.fromRuleContext(ruleContext).getAndroidJar()));
JavaCompilationArtifacts.Builder artifactsBuilder = new JavaCompilationArtifacts.Builder();
ImmutableList.Builder<Artifact> jarsProducedForRuntime = ImmutableList.builder();
NestedSetBuilder<Artifact> filesBuilder = NestedSetBuilder.<Artifact>stableOrder();
Artifact resourcesJar = resourceApk.getResourceJavaSrcJar();
if (resourcesJar != null) {
filesBuilder.add(resourcesJar);
// Use a fast-path R class generator for android_binary with local resources, where there is
// a bottleneck. For legacy resources, the srcjar and R class compiler don't match up
// (the legacy srcjar requires the createJarJar step below).
boolean useRClassGenerator =
getAndroidConfig(ruleContext).useRClassGenerator()
&& isBinary && !resourceApk.isLegacy();
compileResources(javaSemantics, resourceApk, resourcesJar, artifactsBuilder, attributes,
filesBuilder, jarsProducedForRuntime, useRClassGenerator);
if (resourceApk.isLegacy()) {
// Repackages the R.java for each dependency package and places the resultant jars before
// the dependency libraries to ensure that the generated resource ids are correct.
createJarJarActions(attributes, jarsProducedForRuntime,
resourceApk.getResourceDependencies().getResources(),
resourceApk.getPrimaryResource().getJavaPackage(), resourceClassJar);
}
}
JavaCompilationHelper helper = initAttributes(attributes, javaSemantics);
if (ruleContext.hasErrors()) {
return null;
}
if (addCoverageSupport) {
androidSemantics.addCoverageSupport(ruleContext, this, javaSemantics, true,
attributes, artifactsBuilder);
if (ruleContext.hasErrors()) {
return null;
}
}
jackCompilationHelper = initJack(helper.getAttributes());
if (ruleContext.hasErrors()) {
return null;
}
initJava(
javaSemantics,
helper,
artifactsBuilder,
collectJavaCompilationArgs,
filesBuilder,
isBinary);
if (ruleContext.hasErrors()) {
return null;
}
this.jarsProducedForRuntime = jarsProducedForRuntime.add(classJar).build();
return helper.getAttributes();
}
private JavaCompilationHelper initAttributes(
JavaTargetAttributes.Builder attributes, JavaSemantics semantics) {
JavaCompilationHelper helper = new JavaCompilationHelper(
ruleContext, semantics, javaCommon.getJavacOpts(), attributes);
helper.addLibrariesToAttributes(javaCommon.targetsTreatedAsDeps(ClasspathType.COMPILE_ONLY));
attributes.setStrictJavaDeps(getStrictAndroidDeps());
attributes.setRuleKind(ruleContext.getRule().getRuleClass());
attributes.setTargetLabel(ruleContext.getLabel());
JavaCommon.validateConstraint(ruleContext, "android",
javaCommon.targetsTreatedAsDeps(ClasspathType.BOTH));
ruleContext.checkSrcsSamePackage(true);
return helper;
}
private StrictDepsMode getStrictAndroidDeps() {
// Get command line strict_android_deps option
StrictDepsMode strict = ruleContext.getFragment(AndroidConfiguration.class).getStrictDeps();
// Use option if anything but DEFAULT, which is now equivalent to ERROR.
return (strict != DEFAULT && strict != STRICT) ? strict : ERROR;
}
JackCompilationHelper initJack(JavaTargetAttributes attributes) throws InterruptedException {
AndroidSdkProvider sdk = AndroidSdkProvider.fromRuleContext(ruleContext);
return new JackCompilationHelper.Builder()
// blaze infrastructure
.setRuleContext(ruleContext)
// configuration
.setOutputArtifact(
ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_LIBRARY_JACK_FILE))
// tools
.setJackBinary(sdk.getJack())
.setJillBinary(sdk.getJill())
.setResourceExtractorBinary(sdk.getResourceExtractor())
.setJackBaseClasspath(sdk.getAndroidBaseClasspathForJack())
// sources
.addJavaSources(attributes.getSourceFiles())
.addSourceJars(attributes.getSourceJars())
.addResources(attributes.getResources())
.addProcessorNames(attributes.getProcessorNames())
.addProcessorClasspathJars(attributes.getProcessorPath())
.addExports(JavaCommon.getExports(ruleContext))
.addClasspathDeps(javaCommon.targetsTreatedAsDeps(ClasspathType.COMPILE_ONLY))
.addRuntimeDeps(javaCommon.targetsTreatedAsDeps(ClasspathType.RUNTIME_ONLY))
.build();
}
private void initJava(
JavaSemantics javaSemantics,
JavaCompilationHelper helper,
JavaCompilationArtifacts.Builder javaArtifactsBuilder,
boolean collectJavaCompilationArgs,
NestedSetBuilder<Artifact> filesBuilder,
boolean isBinary)
throws InterruptedException {
JavaTargetAttributes attributes = helper.getAttributes();
if (ruleContext.hasErrors()) {
// Avoid leaving filesToBuild set to null, otherwise we'll get a NullPointerException masking
// the real error.
filesToBuild = filesBuilder.build();
return;
}
Artifact jar = null;
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);
jar = classJar;
}
filesBuilder.add(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.
if (helper.usesAnnotationProcessing()) {
genClassJar = helper.createGenJar(classJar);
genSourceJar = helper.createGensrcJar(classJar);
helper.createGenJarAction(classJar, manifestProtoOutput, genClassJar);
}
srcJar = ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_LIBRARY_SOURCE_JAR);
javaSourceJarsProviderBuilder
.addSourceJar(srcJar)
.addAllTransitiveSourceJars(javaCommon.collectTransitiveSourceJars(srcJar));
helper.createSourceJarAction(srcJar, genSourceJar);
outputDepsProto = helper.createOutputDepsProtoArtifact(classJar, javaArtifactsBuilder);
helper.createCompileActionWithInstrumentation(classJar, manifestProtoOutput, genSourceJar,
outputDepsProto, javaArtifactsBuilder);
if (isBinary) {
generatedExtensionRegistryProvider =
javaSemantics.createGeneratedExtensionRegistry(
ruleContext,
javaCommon,
filesBuilder,
javaArtifactsBuilder,
javaRuleOutputJarsProviderBuilder,
javaSourceJarsProviderBuilder);
}
filesToBuild = filesBuilder.build();
if ((attributes.hasSourceFiles() || attributes.hasSourceJars()) && jar != null) {
iJar = helper.createCompileTimeJarAction(jar, javaArtifactsBuilder);
}
JavaCompilationArtifacts javaArtifacts = javaArtifactsBuilder.build();
compileTimeDependencyArtifacts =
javaCommon.collectCompileTimeDependencyArtifacts(
javaArtifacts.getCompileTimeDependencyArtifact());
javaCommon.setJavaCompilationArtifacts(javaArtifacts);
javaCommon.setClassPathFragment(
new ClasspathConfiguredFragment(
javaCommon.getJavaCompilationArtifacts(),
attributes,
asNeverLink,
helper.getBootclasspathOrDefault()));
transitiveNeverlinkLibraries = collectTransitiveNeverlinkLibraries(
ruleContext,
javaCommon.getDependencies(),
javaCommon.getJavaCompilationArtifacts().getRuntimeJars());
if (collectJavaCompilationArgs) {
boolean hasSources = attributes.hasSourceFiles() || attributes.hasSourceJars();
this.javaCompilationArgs =
collectJavaCompilationArgs(exportDeps, asNeverLink, hasSources);
this.recursiveJavaCompilationArgs = collectJavaCompilationArgs(
true, asNeverLink, /* hasSources */ true);
}
}
public RuleConfiguredTargetBuilder addTransitiveInfoProviders(
RuleConfiguredTargetBuilder builder,
AndroidSemantics androidSemantics,
Artifact aar,
ResourceApk resourceApk,
Artifact zipAlignedApk,
Iterable<Artifact> apksUnderTest) {
javaCommon.addTransitiveInfoProviders(builder, filesToBuild, classJar, ANDROID_COLLECTION_SPEC);
javaCommon.addGenJarsProvider(builder, genClassJar, genSourceJar);
idlHelper.addTransitiveInfoProviders(builder, classJar, manifestProtoOutput);
if (generatedExtensionRegistryProvider != null) {
builder.add(GeneratedExtensionRegistryProvider.class, generatedExtensionRegistryProvider);
}
OutputJar resourceJar = null;
if (resourceClassJar != null && resourceSourceJar != null) {
resourceJar = new OutputJar(resourceClassJar, null, resourceSourceJar);
javaRuleOutputJarsProviderBuilder.addOutputJar(resourceJar);
}
JavaSourceJarsProvider javaSourceJarsProvider = javaSourceJarsProviderBuilder.build();
return builder
.setFilesToBuild(filesToBuild)
.add(
JavaRuleOutputJarsProvider.class,
javaRuleOutputJarsProviderBuilder
.addOutputJar(classJar, iJar, srcJar)
.setJdeps(outputDepsProto)
.build())
.add(JavaSourceJarsProvider.class, javaSourceJarsProvider)
.add(
JavaRuntimeJarProvider.class,
new JavaRuntimeJarProvider(javaCommon.getJavaCompilationArtifacts().getRuntimeJars()))
.add(RunfilesProvider.class, RunfilesProvider.simple(getRunfiles()))
.add(AndroidResourcesProvider.class, resourceApk.toResourceProvider(ruleContext.getLabel()))
.add(
AndroidIdeInfoProvider.class,
createAndroidIdeInfoProvider(
ruleContext,
androidSemantics,
idlHelper,
resourceJar,
aar,
resourceApk,
zipAlignedApk,
apksUnderTest))
.add(
JavaCompilationArgsProvider.class,
JavaCompilationArgsProvider.create(
javaCompilationArgs,
recursiveJavaCompilationArgs,
compileTimeDependencyArtifacts,
NestedSetBuilder.<Artifact>emptySet(Order.STABLE_ORDER)))
.add(
JackLibraryProvider.class,
asNeverLink
? jackCompilationHelper.compileAsNeverlinkLibrary()
: jackCompilationHelper.compileAsLibrary())
.addSkylarkTransitiveInfo(AndroidSkylarkApiProvider.NAME, new AndroidSkylarkApiProvider())
.addOutputGroup(
OutputGroupProvider.HIDDEN_TOP_LEVEL, collectHiddenTopLevelArtifacts(ruleContext))
.addOutputGroup(
JavaSemantics.SOURCE_JARS_OUTPUT_GROUP,
javaSourceJarsProvider.getTransitiveSourceJars());
}
private Runfiles getRunfiles() {
// TODO(bazel-team): why return any Runfiles in the neverlink case?
if (asNeverLink) {
return new Runfiles.Builder(
ruleContext.getWorkspaceName(), ruleContext.getConfiguration().legacyExternalRunfiles())
.addRunfiles(ruleContext, RunfilesProvider.DEFAULT_RUNFILES)
.build();
}
return JavaCommon.getRunfiles(
ruleContext, javaCommon.getJavaSemantics(), javaCommon.getJavaCompilationArtifacts(),
asNeverLink);
}
public static PathFragment getAssetDir(RuleContext ruleContext) {
return new PathFragment(ruleContext.attributes().get(
AndroidResourcesProvider.ResourceType.ASSETS.getAttribute() + "_dir",
Type.STRING));
}
public static NestedSet<LinkerInput> collectTransitiveNativeLibraries(
Iterable<? extends TransitiveInfoCollection> deps) {
NestedSetBuilder<LinkerInput> builder = NestedSetBuilder.stableOrder();
for (TransitiveInfoCollection dep : deps) {
AndroidNativeLibraryProvider android = dep.getProvider(AndroidNativeLibraryProvider.class);
if (android != null) {
builder.addTransitive(android.getTransitiveAndroidNativeLibraries());
continue;
}
JavaNativeLibraryProvider java = dep.getProvider(JavaNativeLibraryProvider.class);
if (java != null) {
builder.addTransitive(java.getTransitiveJavaNativeLibraries());
continue;
}
CcNativeLibraryProvider cc = dep.getProvider(CcNativeLibraryProvider.class);
if (cc != null) {
for (LinkerInput input : cc.getTransitiveCcNativeLibraries()) {
Artifact library = input.getOriginalLibraryArtifact();
String name = library.getFilename();
if (CppFileTypes.SHARED_LIBRARY.matches(name)
|| CppFileTypes.VERSIONED_SHARED_LIBRARY.matches(name)) {
builder.add(input);
}
}
continue;
}
}
return builder.build();
}
public static AndroidResourcesProvider getAndroidResources(RuleContext ruleContext) {
if (!ruleContext.attributes().has("resources", BuildType.LABEL)) {
return null;
}
TransitiveInfoCollection prerequisite = ruleContext.getPrerequisite("resources", Mode.TARGET);
if (prerequisite == null) {
return null;
}
ruleContext.ruleWarning(
"The use of the android_resources rule and the resources attribute is deprecated. "
+ "Please use the resource_files, assets, and manifest attributes of android_library.");
return prerequisite.getProvider(AndroidResourcesProvider.class);
}
/**
* Collects Java compilation arguments for this target.
*
* @param recursive Whether to scan dependencies recursively.
* @param isNeverLink Whether the target has the 'neverlink' attr.
* @param hasSrcs If false, deps are exported (deprecated behaviour)
*/
private JavaCompilationArgs collectJavaCompilationArgs(boolean recursive, boolean isNeverLink,
boolean hasSrcs) {
boolean exportDeps = !hasSrcs
&& ruleContext.getFragment(AndroidConfiguration.class).allowSrcsLessAndroidLibraryDeps();
return javaCommon.collectJavaCompilationArgs(recursive, isNeverLink, exportDeps);
}
public ImmutableList<String> getJavacOpts() {
return javaCommon.getJavacOpts();
}
public Artifact getGenClassJar() {
return genClassJar;
}
@Nullable public Artifact getGenSourceJar() {
return genSourceJar;
}
public ImmutableList<Artifact> getRuntimeJars() {
return javaCommon.getJavaCompilationArtifacts().getRuntimeJars();
}
public Artifact getResourceClassJar() {
return resourceClassJar;
}
/**
* Returns Jars produced by this rule that may go into the runtime classpath. By contrast
* {@link #getRuntimeJars()} returns the complete runtime classpath needed by this rule, including
* dependencies.
*/
public ImmutableList<Artifact> getJarsProducedForRuntime() {
return jarsProducedForRuntime;
}
public Artifact getInstrumentedJar() {
return javaCommon.getJavaCompilationArtifacts().getInstrumentedJar();
}
public NestedSet<Artifact> getTransitiveNeverLinkLibraries() {
return transitiveNeverlinkLibraries;
}
public boolean isNeverLink() {
return asNeverLink;
}
public CcLinkParamsStore getCcLinkParamsStore() {
return getCcLinkParamsStore(
javaCommon.targetsTreatedAsDeps(ClasspathType.BOTH), ImmutableList.<String>of());
}
public static CcLinkParamsStore getCcLinkParamsStore(
final Iterable<? extends TransitiveInfoCollection> deps, final Collection<String> linkOpts) {
return new CcLinkParamsStore() {
@Override
protected void collect(
CcLinkParams.Builder builder, boolean linkingStatically, boolean linkShared) {
builder.addTransitiveTargets(
deps,
// Link in Java-specific C++ code in the transitive closure
JavaCcLinkParamsProvider.TO_LINK_PARAMS,
// Link in Android-specific C++ code (e.g., android_libraries) in the transitive closure
AndroidCcLinkParamsProvider.TO_LINK_PARAMS,
// Link in non-language-specific C++ code in the transitive closure
CcLinkParamsProvider.TO_LINK_PARAMS);
builder.addLinkOpts(linkOpts);
}
};
}
/**
* Returns {@link AndroidConfiguration} in given context.
*/
static AndroidConfiguration getAndroidConfig(RuleContext context) {
return context.getConfiguration().getFragment(AndroidConfiguration.class);
}
private NestedSet<Artifact> collectHiddenTopLevelArtifacts(RuleContext ruleContext) {
NestedSetBuilder<Artifact> builder = NestedSetBuilder.stableOrder();
for (OutputGroupProvider provider :
getTransitivePrerequisites(ruleContext, Mode.TARGET, OutputGroupProvider.class)) {
builder.addTransitive(provider.getOutputGroup(OutputGroupProvider.HIDDEN_TOP_LEVEL));
}
return builder.build();
}
}