blob: 5df9a3146d531073a36d2fb0eb1b64872d82604c [file] [log] [blame]
/*
* Copyright (C) 2012 The Android Open Source Project
*
* 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.android.build.gradle.internal.dependency;
import static com.android.build.gradle.internal.publishing.AndroidArtifacts.ARTIFACT_TYPE;
import static com.android.build.gradle.internal.publishing.AndroidArtifacts.ArtifactScope.PROJECT;
import static com.android.build.gradle.internal.publishing.AndroidArtifacts.ConsumedConfigType.PROVIDED_CLASSPATH;
import static com.android.build.gradle.internal.publishing.AndroidArtifacts.ConsumedConfigType.REVERSE_METADATA_VALUES;
import static com.android.build.gradle.internal.publishing.AndroidArtifacts.ConsumedConfigType.RUNTIME_CLASSPATH;
import static com.google.common.base.Preconditions.checkArgument;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.build.api.variant.impl.VariantPropertiesImpl;
import com.android.build.gradle.internal.publishing.AndroidArtifacts;
import com.android.build.gradle.internal.publishing.AndroidArtifacts.PublishedConfigType;
import com.android.build.gradle.options.BooleanOption;
import com.android.build.gradle.options.ProjectOptions;
import com.android.builder.core.VariantType;
import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import java.util.Collection;
import java.util.Map;
import org.gradle.api.Action;
import org.gradle.api.Project;
import org.gradle.api.artifacts.ArtifactCollection;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.Dependency;
import org.gradle.api.artifacts.component.ComponentIdentifier;
import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
import org.gradle.api.artifacts.component.ProjectComponentIdentifier;
import org.gradle.api.attributes.Attribute;
import org.gradle.api.attributes.AttributeContainer;
import org.gradle.api.file.FileCollection;
import org.gradle.api.specs.Spec;
/**
* Object that represents the dependencies of variant.
*
* <p>The dependencies are expressed as composite Gradle configuration objects that extends
* all the configuration objects of the "configs".</p>
*
* <p>It optionally contains the dependencies for a test config for the given config.</p>
*/
public class VariantDependencies {
public static final String CONFIG_NAME_COMPILE = "compile";
public static final String CONFIG_NAME_PUBLISH = "publish";
public static final String CONFIG_NAME_APK = "apk";
public static final String CONFIG_NAME_PROVIDED = "provided";
public static final String CONFIG_NAME_WEAR_APP = "wearApp";
public static final String CONFIG_NAME_ANDROID_APIS = "androidApis";
public static final String CONFIG_NAME_ANNOTATION_PROCESSOR = "annotationProcessor";
public static final String CONFIG_NAME_API = "api";
public static final String CONFIG_NAME_COMPILE_ONLY = "compileOnly";
public static final String CONFIG_NAME_IMPLEMENTATION = "implementation";
public static final String CONFIG_NAME_RUNTIME_ONLY = "runtimeOnly";
@Deprecated public static final String CONFIG_NAME_FEATURE = "feature";
public static final String CONFIG_NAME_APPLICATION = "application";
public static final String CONFIG_NAME_LINTCHECKS = "lintChecks";
public static final String CONFIG_NAME_LINTPUBLISH = "lintPublish";
public static final String CONFIG_NAME_TESTED_APKS = "testedApks";
public static final String CONFIG_NAME_CORE_LIBRARY_DESUGARING = "coreLibraryDesugaring";
@NonNull private final String variantName;
@NonNull private final VariantType variantType;
@Nullable private final VariantPropertiesImpl testedVariant;
@NonNull private final Configuration compileClasspath;
@NonNull private final Configuration runtimeClasspath;
@NonNull private final Configuration providedClasspath;
@NonNull private final Collection<Configuration> sourceSetRuntimeConfigurations;
@NonNull private final Collection<Configuration> sourceSetImplementationConfigurations;
@NonNull private final Configuration annotationProcessorConfiguration;
@Nullable private final Configuration wearAppConfiguration;
@Nullable private final Configuration reverseMetadataValuesConfiguration;
@NonNull private final ImmutableMap<PublishedConfigType, Configuration> elements;
@NonNull private final Project project;
@NonNull private final ProjectOptions projectOptions;
VariantDependencies(
@NonNull String variantName,
@NonNull VariantType variantType,
@NonNull Configuration compileClasspath,
@NonNull Configuration runtimeClasspath,
@NonNull Collection<Configuration> sourceSetRuntimeConfigurations,
@NonNull Collection<Configuration> sourceSetImplementationConfigurations,
@NonNull Map<PublishedConfigType, Configuration> elements,
@NonNull Configuration providedClasspath,
@NonNull Configuration annotationProcessorConfiguration,
@Nullable Configuration reverseMetadataValuesConfiguration,
@Nullable Configuration wearAppConfiguration,
@Nullable VariantPropertiesImpl testedVariant,
@NonNull Project project,
@NonNull ProjectOptions projectOptions) {
Preconditions.checkState(
!variantType.isTestComponent() || testedVariant != null,
"testedVariantDependencies null for test component");
this.variantName = variantName;
this.variantType = variantType;
this.compileClasspath = compileClasspath;
this.runtimeClasspath = runtimeClasspath;
this.sourceSetRuntimeConfigurations = sourceSetRuntimeConfigurations;
this.sourceSetImplementationConfigurations = sourceSetImplementationConfigurations;
this.elements = Maps.immutableEnumMap(elements);
this.providedClasspath = providedClasspath;
this.annotationProcessorConfiguration = annotationProcessorConfiguration;
this.reverseMetadataValuesConfiguration = reverseMetadataValuesConfiguration;
this.wearAppConfiguration = wearAppConfiguration;
this.testedVariant = testedVariant;
this.project = project;
this.projectOptions = projectOptions;
}
public String getName() {
return variantName;
}
@NonNull
public Configuration getCompileClasspath() {
return compileClasspath;
}
@NonNull
public Configuration getRuntimeClasspath() {
return runtimeClasspath;
}
@NonNull
public Collection<Dependency> getIncomingRuntimeDependencies() {
ImmutableList.Builder<Dependency> builder = ImmutableList.builder();
for (Configuration classpath : sourceSetRuntimeConfigurations) {
builder.addAll(classpath.getIncoming().getDependencies());
}
return builder.build();
}
@Nullable
public Configuration getElements(PublishedConfigType configType) {
return elements.get(configType);
}
@NonNull
public Configuration getAnnotationProcessorConfiguration() {
return annotationProcessorConfiguration;
}
@Nullable
public Configuration getWearAppConfiguration() {
return wearAppConfiguration;
}
@Nullable
public Configuration getReverseMetadataValuesConfiguration() {
return reverseMetadataValuesConfiguration;
}
@NonNull
public Collection<Configuration> getSourceSetImplementationConfigurations() {
return sourceSetImplementationConfigurations;
}
@NonNull
public Collection<Configuration> getSourceSetRuntimeConfigurations() {
return sourceSetRuntimeConfigurations;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this).add("name", variantName).toString();
}
@NonNull
public FileCollection getArtifactFileCollection(
@NonNull AndroidArtifacts.ConsumedConfigType configType,
@NonNull AndroidArtifacts.ArtifactScope scope,
@NonNull AndroidArtifacts.ArtifactType artifactType) {
return getArtifactFileCollection(configType, scope, artifactType, null);
}
@NonNull
public FileCollection getArtifactFileCollection(
@NonNull AndroidArtifacts.ConsumedConfigType configType,
@NonNull AndroidArtifacts.ArtifactScope scope,
@NonNull AndroidArtifacts.ArtifactType artifactType,
@Nullable Map<Attribute<String>, String> attributeMap) {
if (configType.needsTestedComponents()) {
return getArtifactCollection(configType, scope, artifactType, attributeMap)
.getArtifactFiles();
}
ArtifactCollection artifacts =
computeArtifactCollection(configType, scope, artifactType, attributeMap);
FileCollection fileCollection;
if (configType == RUNTIME_CLASSPATH && isArtifactTypeExcluded(artifactType)) {
FileCollection excludedDirectories =
computeArtifactCollection(
PROVIDED_CLASSPATH,
PROJECT,
AndroidArtifacts.ArtifactType.PACKAGED_DEPENDENCIES,
attributeMap)
.getArtifactFiles();
fileCollection =
new FilteringSpec(artifacts, excludedDirectories)
.getFilteredFileCollection(project);
} else {
fileCollection = artifacts.getArtifactFiles();
}
return fileCollection;
}
@NonNull
public ArtifactCollection getArtifactCollection(
@NonNull AndroidArtifacts.ConsumedConfigType configType,
@NonNull AndroidArtifacts.ArtifactScope scope,
@NonNull AndroidArtifacts.ArtifactType artifactType) {
return getArtifactCollection(configType, scope, artifactType, null);
}
@NonNull
public ArtifactCollection getArtifactCollection(
@NonNull AndroidArtifacts.ConsumedConfigType configType,
@NonNull AndroidArtifacts.ArtifactScope scope,
@NonNull AndroidArtifacts.ArtifactType artifactType,
@Nullable Map<Attribute<String>, String> attributeMap) {
ArtifactCollection artifacts =
computeArtifactCollection(configType, scope, artifactType, attributeMap);
if (configType == RUNTIME_CLASSPATH && isArtifactTypeExcluded(artifactType)) {
FileCollection excludedDirectories =
computeArtifactCollection(
PROVIDED_CLASSPATH,
PROJECT,
AndroidArtifacts.ArtifactType.PACKAGED_DEPENDENCIES,
null)
.getArtifactFiles();
artifacts =
new FilteredArtifactCollection(
project, new FilteringSpec(artifacts, excludedDirectories));
}
if (!configType.needsTestedComponents() || !variantType.isTestComponent()) {
return artifacts;
}
// get the matching file collection for the tested variant, if any.
if (testedVariant == null) {
return artifacts;
}
// For artifact that should not be duplicated between test APk and tested APK (e.g. classes)
// we remove duplicates from test APK. More specifically, for androidTest variants for base
// and dynamic features, we need to remove artifacts that are already packaged in the tested
// variant. Also, we remove artifacts already packaged in base/features that the tested
// feature depends on.
if (!variantType.isApk()) {
// Don't filter unit tests.
return artifacts;
}
if (configType != RUNTIME_CLASSPATH) {
// Only filter runtime classpath.
return artifacts;
}
if (testedVariant.getVariantType().isAar()) {
// Don't filter test APKs for library projects, as there is no tested APK.
return artifacts;
}
if (!isArtifactTypeSubtractedForInstrumentationTests(artifactType)) {
return artifacts;
}
if (testedVariant.getVariantType().isDynamicFeature()) {
// If we're in an androidTest for a dynamic feature we need to filter out artifacts from
// the base and dynamic features this dynamic feature depends on.
FileCollection excludedDirectories =
testedVariant
.getVariantDependencies()
.computeArtifactCollection(
PROVIDED_CLASSPATH,
PROJECT,
AndroidArtifacts.ArtifactType.PACKAGED_DEPENDENCIES,
null)
.getArtifactFiles();
artifacts =
new FilteredArtifactCollection(
project, new FilteringSpec(artifacts, excludedDirectories));
}
ArtifactCollection testedArtifactCollection =
testedVariant
.getVariantDependencies()
.getArtifactCollection(configType, scope, artifactType, attributeMap);
artifacts =
new SubtractingArtifactCollection(
artifacts, testedArtifactCollection, project.getObjects());
return artifacts;
}
private boolean isArtifactTypeExcluded(@NonNull AndroidArtifacts.ArtifactType artifactType) {
if (variantType.isDynamicFeature()) {
return artifactType != AndroidArtifacts.ArtifactType.PACKAGED_DEPENDENCIES
&& artifactType != AndroidArtifacts.ArtifactType.APKS_FROM_BUNDLE
&& artifactType != AndroidArtifacts.ArtifactType.FEATURE_DEX
&& artifactType != AndroidArtifacts.ArtifactType.FEATURE_NAME;
}
if (variantType.isSeparateTestProject()) {
return isArtifactTypeSubtractedForInstrumentationTests(artifactType);
}
return false;
}
private static boolean isArtifactTypeSubtractedForInstrumentationTests(
@NonNull AndroidArtifacts.ArtifactType artifactType) {
return artifactType != AndroidArtifacts.ArtifactType.ANDROID_RES
&& artifactType != AndroidArtifacts.ArtifactType.COMPILED_DEPENDENCIES_RESOURCES;
}
@NonNull
private Configuration getConfiguration(
@NonNull AndroidArtifacts.ConsumedConfigType configType) {
switch (configType) {
case COMPILE_CLASSPATH:
return getCompileClasspath();
case RUNTIME_CLASSPATH:
return getRuntimeClasspath();
case PROVIDED_CLASSPATH:
return providedClasspath;
case ANNOTATION_PROCESSOR:
return getAnnotationProcessorConfiguration();
case REVERSE_METADATA_VALUES:
return Preconditions.checkNotNull(getReverseMetadataValuesConfiguration());
default:
throw new RuntimeException("unknown ConfigType value " + configType);
}
}
@NonNull
public ArtifactCollection getArtifactCollectionForToolingModel(
@NonNull AndroidArtifacts.ConsumedConfigType configType,
@NonNull AndroidArtifacts.ArtifactScope scope,
@NonNull AndroidArtifacts.ArtifactType artifactType) {
return computeArtifactCollection(configType, scope, artifactType, null);
}
@NonNull
private ArtifactCollection computeArtifactCollection(
@NonNull AndroidArtifacts.ConsumedConfigType configType,
@NonNull AndroidArtifacts.ArtifactScope scope,
@NonNull AndroidArtifacts.ArtifactType artifactType,
@Nullable Map<Attribute<String>, String> attributeMap) {
checkComputeArtifactCollectionArguments(configType, scope, artifactType);
Configuration configuration = getConfiguration(configType);
Action<AttributeContainer> attributes =
container -> {
container.attribute(ARTIFACT_TYPE, artifactType.getType());
if (attributeMap != null) {
for (Attribute<String> attribute : attributeMap.keySet()) {
container.attribute(attribute, attributeMap.get(attribute));
}
}
};
Spec<ComponentIdentifier> filter = getComponentFilter(scope);
boolean lenientMode =
Boolean.TRUE.equals(projectOptions.get(BooleanOption.IDE_BUILD_MODEL_ONLY));
return configuration
.getIncoming()
.artifactView(
config -> {
config.attributes(attributes);
if (filter != null) {
config.componentFilter(filter);
}
// TODO somehow read the unresolved dependencies?
config.lenient(lenientMode);
})
.getArtifacts();
}
private static void checkComputeArtifactCollectionArguments(
@NonNull AndroidArtifacts.ConsumedConfigType configType,
@NonNull AndroidArtifacts.ArtifactScope scope,
@NonNull AndroidArtifacts.ArtifactType artifactType) {
switch (artifactType) {
case PACKAGED_DEPENDENCIES:
checkArgument(
configType == PROVIDED_CLASSPATH || configType == REVERSE_METADATA_VALUES,
"Packaged dependencies must only be requested from the PROVIDED_CLASSPATH or REVERSE_METADATA_VALUES");
break;
default:
break; // No validation
}
switch (configType) {
case PROVIDED_CLASSPATH:
checkArgument(
artifactType == AndroidArtifacts.ArtifactType.PACKAGED_DEPENDENCIES
|| artifactType == AndroidArtifacts.ArtifactType.APK,
"Provided classpath must only be used for from the PACKAGED_DEPENDENCIES and APKS");
break;
default:
break; // No validation
}
}
@Nullable
private static Spec<ComponentIdentifier> getComponentFilter(
@NonNull AndroidArtifacts.ArtifactScope scope) {
switch (scope) {
case ALL:
return null;
case EXTERNAL:
// since we want both Module dependencies and file based dependencies in this case
// the best thing to do is search for non ProjectComponentIdentifier.
return id -> !(id instanceof ProjectComponentIdentifier);
case PROJECT:
return id -> id instanceof ProjectComponentIdentifier;
case REPOSITORY_MODULE:
return id -> id instanceof ModuleComponentIdentifier;
case FILE:
return id ->
!(id instanceof ProjectComponentIdentifier
|| id instanceof ModuleComponentIdentifier);
}
throw new RuntimeException("unknown ArtifactScope value");
}
}