blob: bec10987387f4943781bbabda22660bff95d64df [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.VariantOutput;
import com.android.build.gradle.internal.core.VariantDslInfo;
import com.android.build.gradle.internal.ide.FilterDataImpl;
import com.android.utils.Pair;
import com.android.utils.StringHelper;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
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 VariantDslInfo variantDslInfo;
private final AtomicBoolean mainSplitAdded = new AtomicBoolean(false);
private final AtomicBoolean apkDataListFinalized = new AtomicBoolean(false);
private final List<ApkData> apkDataList = new ArrayList<>();
public OutputFactory(String projectBaseName, VariantDslInfo variantDslInfo) {
this.projectBaseName = projectBaseName;
this.variantDslInfo = variantDslInfo;
}
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 =
(variantDslInfo.isSigningReady() || !variantDslInfo.getVariantType().isBaseModule())
? DOT_ANDROID_PACKAGE
: "-unsigned.apk";
return projectBaseName + "-" + baseName + suffix;
}
public ApkData addMainOutput(String defaultFilename) {
ApkData mainOutput =
new Main(
variantDslInfo.getBaseName(),
variantDslInfo.getComponentIdentity().getName(),
defaultFilename);
checkMainSplitExistenceAndAdd(mainOutput);
return mainOutput;
}
public ApkData addMainApk() {
return addMainOutput(getOutputFileName(variantDslInfo.getBaseName()));
}
public ApkData addUniversalApk() {
String baseName = variantDslInfo.computeBaseNameWithSplits(UNIVERSAL);
ApkData mainApk =
new Universal(
baseName,
variantDslInfo.computeFullNameWithSplits(UNIVERSAL),
getOutputFileName(baseName));
checkMainSplitExistenceAndAdd(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 = variantDslInfo.computeBaseNameWithSplits(filterName);
ApkData apkData =
new FullSplit(
filterName,
baseName,
variantDslInfo.computeFullNameWithSplits(filterName),
getOutputFileName(baseName),
filtersList);
addApkDataToList(apkData);
return apkData;
}
private synchronized void addApkDataToList(ApkData apkData) {
if (apkDataListFinalized.get()) {
throw new RuntimeException("APK list already finalized.");
}
apkDataList.add(apkData);
}
private synchronized void checkMainSplitExistenceAndAdd(ApkData apkData) {
if (mainSplitAdded.get()) {
throw new RuntimeException(
"Cannot add "
+ apkData
+ " in a scope that already"
+ " has "
+ apkDataList
.stream()
.filter(it -> it.getType() == VariantOutput.OutputType.MAIN)
.map(ApkData::toString)
.collect(Collectors.joining(",")));
}
mainSplitAdded.set(true);
addApkDataToList(apkData);
}
public synchronized List<ApkData> finalizeApkDataList() {
apkDataListFinalized.set(true);
return ImmutableList.sortedCopyOf(apkDataList);
}
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 VariantOutput.OutputType getType() {
return VariantOutput.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 VariantOutput.OutputType getType() {
return VariantOutput.OutputType.FULL_SPLIT;
}
@Nullable
@Override
public String getFilterName() {
return UNIVERSAL;
}
@NonNull
@Override
public String getBaseName() {
return baseName;
}
@NonNull
@Override
public String getFullName() {
return fullName;
}
@Override
public boolean isUniversal() {
return true;
}
@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, VariantOutput.FilterType.DENSITY);
if (densityFilter != null) {
sb.append(densityFilter);
}
String abiFilter = getFilter(filters, VariantOutput.FilterType.ABI);
if (abiFilter != null) {
StringHelper.appendCamelCase(sb, abiFilter);
}
return sb.toString();
}
@NonNull
@Override
public VariantOutput.OutputType getType() {
return VariantOutput.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 isUniversal() {
return false;
}
@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);
}
}
}