blob: bfc479631b6f315ce4793e722975a164e46b914c [file] [log] [blame]
/*
* Copyright (C) 2017 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.scope;
import static com.android.SdkConstants.DOT_ANDROID_PACKAGE;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.build.FilterData;
import com.android.build.OutputFile;
import com.android.build.gradle.internal.core.GradleVariantConfiguration;
import com.android.build.gradle.internal.ide.FilterDataImpl;
import com.android.utils.Pair;
import com.android.utils.StringHelper;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableList;
import java.io.File;
import java.util.Objects;
import java.util.function.Supplier;
import java.util.stream.Collectors;
/** Factory for {@link ApkData} instances. Cannot be stored in any model related objects. */
public class OutputFactory {
static final String UNIVERSAL = "universal";
private final String projectBaseName;
private final GradleVariantConfiguration variantConfiguration;
private final OutputScope.Builder outputScopeBuilder;
private final Supplier<OutputScope> outputSupplier;
public OutputFactory(String projectBaseName, GradleVariantConfiguration variantConfiguration) {
this.projectBaseName = projectBaseName;
this.variantConfiguration = variantConfiguration;
this.outputScopeBuilder = new OutputScope.Builder();
this.outputSupplier = Suppliers.memoize(outputScopeBuilder::build);
}
private String getOutputFileName(String baseName) {
// we only know if it is signed during configuration, if its the base module.
// Otherwise, don't differentiate between signed and unsigned.
String suffix =
(variantConfiguration.isSigningReady()
|| !variantConfiguration.getType().isBaseModule())
? DOT_ANDROID_PACKAGE
: "-unsigned.apk";
return projectBaseName + "-" + baseName + suffix;
}
public ApkData addMainOutput(String defaultFilename) {
ApkData mainOutput =
new Main(
variantConfiguration.getBaseName(),
variantConfiguration.getFullName(),
defaultFilename);
outputScopeBuilder.addMainSplit(mainOutput);
return mainOutput;
}
public ApkData addMainApk() {
return addMainOutput(getOutputFileName(variantConfiguration.getBaseName()));
}
public ApkData addUniversalApk() {
String baseName = variantConfiguration.computeBaseNameWithSplits(UNIVERSAL);
ApkData mainApk =
new Universal(
baseName,
variantConfiguration.computeFullNameWithSplits(UNIVERSAL),
getOutputFileName(baseName));
outputScopeBuilder.addMainSplit(mainApk);
return mainApk;
}
public ApkData addFullSplit(ImmutableList<Pair<OutputFile.FilterType, String>> filters) {
ImmutableList<FilterData> filtersList =
ImmutableList.copyOf(
filters.stream()
.map(
filter ->
new FilterDataImpl(
filter.getFirst(), filter.getSecond()))
.collect(Collectors.toList()));
String filterName = FullSplit._getFilterName(filtersList);
String baseName = variantConfiguration.computeBaseNameWithSplits(filterName);
ApkData apkData =
new FullSplit(
filterName,
baseName,
variantConfiguration.computeFullNameWithSplits(filterName),
getOutputFileName(baseName),
filtersList);
outputScopeBuilder.addSplit(apkData);
return apkData;
}
public ApkData addConfigurationSplit(OutputFile.FilterType filterType, String filterValue) {
String baseName = variantConfiguration.computeBaseNameWithSplits(filterValue);
return addConfigurationSplit(filterType, filterValue, getOutputFileName(baseName));
}
@VisibleForTesting
public ApkData addConfigurationSplit(
OutputFile.FilterType filterType, String filterValue, String fileName) {
ApkData apkData =
new ConfigurationSplitApkData(
filterType,
filterValue,
variantConfiguration.computeBaseNameWithSplits(filterValue),
variantConfiguration.getFullName(),
variantConfiguration.getDirName(),
fileName);
outputScopeBuilder.addSplit(apkData);
return apkData;
}
public OutputScope getOutput() {
return outputSupplier.get();
}
private static final class Main extends ApkData {
private final String baseName, fullName;
private Main(String baseName, String fullName, String fileName) {
this.baseName = baseName;
this.fullName = fullName;
setOutputFileName(fileName);
}
@NonNull
@Override
public OutputType getType() {
return OutputType.MAIN;
}
@Nullable
@Override
public String getFilterName() {
return null;
}
@NonNull
@Override
public String getBaseName() {
return baseName;
}
@NonNull
@Override
public String getFullName() {
return fullName;
}
// The main output should not have a dirName set as all the getXXXOutputDirectory
// in variant scope already include the variant name.
// TODO: We probably should clean this up, having the getXXXOutputDirectory APIs
// return the top level folder and have all users use the getDirName() as part of
// the task output folder configuration.
@NonNull
@Override
public String getDirName() {
return "";
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), baseName, fullName);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
if (!super.equals(o)) {
return false;
}
Main that = (Main) o;
return Objects.equals(baseName, that.baseName)
&& Objects.equals(fullName, that.fullName);
}
}
private static class Universal extends ApkData {
private final String baseName, fullName;
private Universal(String baseName, String fullName, String fileName) {
this.baseName = baseName;
this.fullName = fullName;
setOutputFileName(fileName);
}
@NonNull
@Override
public OutputType getType() {
return OutputType.FULL_SPLIT;
}
@Nullable
@Override
public String getFilterName() {
return UNIVERSAL;
}
@NonNull
@Override
public String getBaseName() {
return baseName;
}
@NonNull
@Override
public String getFullName() {
return fullName;
}
@NonNull
@Override
public String getDirName() {
Preconditions.checkState(
getFilters().isEmpty(), "Universal APKs shouldn't have any filters set.");
return UNIVERSAL;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
if (!super.equals(o)) {
return false;
}
Universal that = (Universal) o;
return Objects.equals(baseName, that.baseName)
&& Objects.equals(fullName, that.fullName);
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), baseName, fullName);
}
}
static class FullSplit extends Universal {
private final ImmutableList<FilterData> filters;
private final String filterName;
private FullSplit(
String filterName,
String baseName,
String fullName,
String fileName,
ImmutableList<FilterData> filters) {
super(baseName, fullName, fileName);
this.filterName = filterName;
this.filters = filters;
}
private static String _getFilterName(ImmutableList<FilterData> filters) {
StringBuilder sb = new StringBuilder();
String densityFilter = ApkData.getFilter(filters, FilterType.DENSITY);
if (densityFilter != null) {
sb.append(densityFilter);
}
String abiFilter = getFilter(filters, FilterType.ABI);
if (abiFilter != null) {
StringHelper.appendCamelCase(sb, abiFilter);
}
return sb.toString();
}
@NonNull
@Override
public OutputType getType() {
return OutputType.FULL_SPLIT;
}
@NonNull
@Override
public ImmutableList<FilterData> getFilters() {
return filters;
}
@Nullable
@Override
public String getFilterName() {
return filterName;
}
@NonNull
@Override
public String getDirName() {
StringBuilder sb = new StringBuilder();
for (FilterData filter : getFilters()) {
sb.append(filter.getIdentifier()).append(File.separatorChar);
}
return sb.toString();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
if (!super.equals(o)) {
return false;
}
FullSplit that = (FullSplit) o;
return Objects.equals(filterName, that.filterName)
&& Objects.equals(filters, that.filters);
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), filterName, filters);
}
}
public static class ConfigurationSplitApkData extends ApkData {
@NonNull private final String filterName, baseName, fullName, dirName;
private final ImmutableList<FilterData> filters;
public ConfigurationSplitApkData(
@NonNull OutputFile.FilterType filterType,
@NonNull String filterName,
@NonNull String baseName,
@NonNull String fullName,
@NonNull String dirName,
@NonNull String fileName) {
this.filters = ImmutableList.of(new FilterDataImpl(filterType, filterName));
this.filterName = filterName;
this.baseName = baseName;
this.fullName = fullName;
this.dirName = dirName;
setOutputFileName(fileName);
}
@NonNull
@Override
public OutputType getType() {
return OutputType.SPLIT;
}
@Override
public boolean requiresAapt() {
return false;
}
@NonNull
@Override
public String getFilterName() {
return filterName;
}
@NonNull
@Override
public ImmutableList<FilterData> getFilters() {
return filters;
}
@NonNull
@Override
public String getBaseName() {
return baseName;
}
@NonNull
@Override
public String getFullName() {
return fullName;
}
@NonNull
@Override
public String getDirName() {
return dirName;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
if (!super.equals(o)) {
return false;
}
ConfigurationSplitApkData that = (ConfigurationSplitApkData) o;
return Objects.equals(baseName, that.baseName)
&& Objects.equals(fullName, that.fullName)
&& Objects.equals(dirName, that.dirName)
// faster to compare the filterName than filters.
&& Objects.equals(filterName, that.filterName);
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), baseName, fullName, dirName, filterName);
}
}
}