blob: f20c13ceccc96ca63640b1254c751c85dabc17d4 [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.builder;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.annotations.VisibleForTesting;
import com.android.builder.resources.ResourceSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import java.io.File;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
/**
* A Variant configuration.
*/
public class VariantConfiguration {
final static ManifestParser sManifestParser = new DefaultManifestParser();
private final ProductFlavor mDefaultConfig;
private final SourceProvider mDefaultSourceProvider;
private final BuildType mBuildType;
/** SourceProvider for the BuildType. Can be null */
private final SourceProvider mBuildTypeSourceProvider;
private final List<ProductFlavor> mFlavorConfigs = Lists.newArrayList();
private final List<SourceProvider> mFlavorSourceProviders = Lists.newArrayList();
private final Type mType;
/** Optional tested config in case type is Type#TEST */
private final VariantConfiguration mTestedConfig;
/** An optional output that is only valid if the type is Type#LIBRARY so that the test
* for the library can use the library as if it was a normal dependency. */
private AndroidDependency mOutput;
private ProductFlavor mMergedFlavor;
private final Set<JarDependency> mJars = Sets.newHashSet();
/** List of direct library dependencies. Each object defines its own dependencies. */
private final List<AndroidDependency> mDirectLibraries = Lists.newArrayList();
/** list of all library dependencies in a flat list.
* The order is based on the order needed to call aapt: earlier libraries override resources
* of latter ones. */
private final List<AndroidDependency> mFlatLibraries = Lists.newArrayList();
public static enum Type {
DEFAULT, LIBRARY, TEST;
}
/**
* Creates the configuration with the base source sets.
*
* This creates a config with a {@link Type#DEFAULT} type.
*
* @param defaultConfig the default configuration. Required.
* @param defaultSourceProvider the default source provider. Required
* @param buildType the build type for this variant. Required.
* @param buildTypeSourceProvider the source provider for the build type. Required.
*/
public VariantConfiguration(
@NonNull ProductFlavor defaultConfig, @NonNull SourceProvider defaultSourceProvider,
@NonNull BuildType buildType, @NonNull SourceProvider buildTypeSourceProvider) {
this(defaultConfig, defaultSourceProvider,
buildType, buildTypeSourceProvider,
Type.DEFAULT, null /*testedConfig*/);
}
/**
* Creates the configuration with the base source sets for a given {@link Type}.
*
* @param defaultConfig the default configuration. Required.
* @param defaultSourceProvider the default source provider. Required
* @param buildType the build type for this variant. Required.
* @param buildTypeSourceProvider the source provider for the build type. Required.
* @param type the type of the project.
*/
public VariantConfiguration(
@NonNull ProductFlavor defaultConfig, @NonNull SourceProvider defaultSourceProvider,
@NonNull BuildType buildType, @NonNull SourceProvider buildTypeSourceProvider,
@NonNull Type type) {
this(defaultConfig, defaultSourceProvider,
buildType, buildTypeSourceProvider,
type, null /*testedConfig*/);
}
/**
* Creates the configuration with the base source sets, and an optional tested variant.
*
* @param defaultConfig the default configuration. Required.
* @param defaultSourceProvider the default source provider. Required
* @param buildType the build type for this variant. Required.
* @param buildTypeSourceProvider the source provider for the build type. Required.
* @param type the type of the project.
* @param testedConfig the reference to the tested project. Required if type is Type.TEST
*/
public VariantConfiguration(
@NonNull ProductFlavor defaultConfig, @NonNull SourceProvider defaultSourceProvider,
@NonNull BuildType buildType, SourceProvider buildTypeSourceProvider,
@NonNull Type type, @Nullable VariantConfiguration testedConfig) {
mDefaultConfig = checkNotNull(defaultConfig);
mDefaultSourceProvider = checkNotNull(defaultSourceProvider);
mBuildType = checkNotNull(buildType);
mBuildTypeSourceProvider = buildTypeSourceProvider;
mType = checkNotNull(type);
mTestedConfig = testedConfig;
checkState(mType != Type.TEST || mTestedConfig != null);
mMergedFlavor = mDefaultConfig;
if (testedConfig != null &&
testedConfig.mType == Type.LIBRARY &&
testedConfig.mOutput != null) {
mDirectLibraries.add(testedConfig.mOutput);
}
validate();
}
/**
* Add a new configured ProductFlavor.
*
* If multiple flavors are added, the priority follows the order they are added when it
* comes to resolving Android resources overlays (ie earlier added flavors supersedes
* latter added ones).
*
* @param sourceProvider the configured product flavor
* @return the config object
*/
public VariantConfiguration addProductFlavor(@NonNull ProductFlavor productFlavor,
@NonNull SourceProvider sourceProvider) {
mFlavorConfigs.add(productFlavor);
mFlavorSourceProviders.add(sourceProvider);
mMergedFlavor = productFlavor.mergeOver(mMergedFlavor);
return this;
}
/**
* Sets the library dependencies.
*
* @param jars list of jar dependency. This should include the jar dependencies of Android
* projects.
* @return the config object
*/
public VariantConfiguration setJarDependencies(List<JarDependency> jars) {
mJars.addAll(jars);
return this;
}
/**
* Returns the list of jar dependencies
* @return a non null collection of Jar dependencies.
*/
public Collection<JarDependency> getJars() {
return mJars;
}
/**
* Set the Library Project dependencies.
* @param directLibraries list of direct dependencies. Each library object should contain
* its own dependencies. This is actually a dependency graph.
* @return the config object
*/
public VariantConfiguration setAndroidDependencies(
@NonNull List<AndroidDependency> directLibraries) {
if (directLibraries != null) {
mDirectLibraries.addAll(directLibraries);
}
resolveIndirectLibraryDependencies(mDirectLibraries, mFlatLibraries);
return this;
}
/**
* Sets the output of this variant. This is required when the variant is a library so that
* the variant that tests this library can properly include the tested library in its own
* package.
*
* @param output the output of the library as an AndroidDependency that will provides the
* location of all the created items.
* @return the config object
*/
public VariantConfiguration setOutput(AndroidDependency output) {
mOutput = output;
return this;
}
public ProductFlavor getDefaultConfig() {
return mDefaultConfig;
}
public SourceProvider getDefaultSourceSet() {
return mDefaultSourceProvider;
}
public ProductFlavor getMergedFlavor() {
return mMergedFlavor;
}
public BuildType getBuildType() {
return mBuildType;
}
/**
* The SourceProvider for the BuildType. Can be null.
*/
public SourceProvider getBuildTypeSourceSet() {
return mBuildTypeSourceProvider;
}
public boolean hasFlavors() {
return !mFlavorConfigs.isEmpty();
}
public List<ProductFlavor> getFlavorConfigs() {
return mFlavorConfigs;
}
public Iterable<SourceProvider> getFlavorSourceSets() {
return mFlavorSourceProviders;
}
public boolean hasLibraries() {
return !mDirectLibraries.isEmpty();
}
/**
* Returns the direct library dependencies
*/
@NonNull
public List<AndroidDependency> getDirectLibraries() {
return mDirectLibraries;
}
/**
* Returns all the library dependencies, direct and transitive.
*/
@NonNull
public List<AndroidDependency> getAllLibraries() {
return mFlatLibraries;
}
public List<File> getPackagedJars() {
List<File> jars = Lists.newArrayListWithCapacity(mJars.size() + mFlatLibraries.size());
for (JarDependency jar : mJars) {
File jarFile = new File(jar.getLocation());
if (jarFile.exists()) {
jars.add(jarFile);
}
}
for (AndroidDependency androidDependency : mFlatLibraries) {
File libJar = androidDependency.getJarFile();
if (libJar.exists()) {
jars.add(libJar);
}
}
return jars;
}
public Type getType() {
return mType;
}
public VariantConfiguration getTestedConfig() {
return mTestedConfig;
}
/**
* Resolves a given list of libraries, finds out if they depend on other libraries, and
* returns a flat list of all the direct and indirect dependencies in the proper order (first
* is higher priority when calling aapt).
* @param directDependencies the libraries to resolve
* @param outFlatDependencies where to store all the libraries.
*/
@VisibleForTesting
void resolveIndirectLibraryDependencies(List<AndroidDependency> directDependencies,
List<AndroidDependency> outFlatDependencies) {
if (directDependencies == null) {
return;
}
// loop in the inverse order to resolve dependencies on the libraries, so that if a library
// is required by two higher level libraries it can be inserted in the correct place
for (int i = directDependencies.size() - 1 ; i >= 0 ; i--) {
AndroidDependency library = directDependencies.get(i);
// get its libraries
List<AndroidDependency> dependencies = library.getDependencies();
// resolve the dependencies for those libraries
resolveIndirectLibraryDependencies(dependencies, outFlatDependencies);
// and add the current one (if needed) in front (higher priority)
if (outFlatDependencies.contains(library) == false) {
outFlatDependencies.add(0, library);
}
}
}
/**
* Returns the original package name before any overrides from flavors.
* If the variant is a test variant, then the package name is the one coming from the
* configuration of the tested variant, and this call is similar to #getPackageName()
* @return the package name
*/
public String getOriginalPackageName() {
if (mType == VariantConfiguration.Type.TEST) {
return getPackageName();
}
return getPackageFromManifest();
}
/**
* Returns the package name for this variant. This could be coming from the manifest or
* could be overridden through the product flavors.
* @return the package
*/
public String getPackageName() {
String packageName;
if (mType == Type.TEST) {
packageName = mMergedFlavor.getTestPackageName();
if (packageName == null) {
String testedPackage = mTestedConfig.getPackageName();
packageName = testedPackage + ".test";
}
} else {
packageName = getPackageOverride();
if (packageName == null) {
packageName = getPackageFromManifest();
}
}
return packageName;
}
public String getTestedPackageName() {
if (mType == Type.TEST) {
if (mTestedConfig.mType == Type.LIBRARY) {
return getPackageName();
} else {
return mTestedConfig.getPackageName();
}
}
return null;
}
/**
* Returns the package override values coming from the Product Flavor. If the package is not
* overridden then this returns null.
* @return the package override or null
*/
public String getPackageOverride() {
String packageName = mMergedFlavor.getPackageName();
String packageSuffix = mBuildType.getPackageNameSuffix();
if (packageSuffix != null && packageSuffix.length() > 0) {
if (packageName == null) {
packageName = getPackageFromManifest();
}
if (packageSuffix.charAt(0) == '.') {
packageName = packageName + packageSuffix;
} else {
packageName = packageName + '.' + packageSuffix;
}
}
return packageName;
}
/**
* Returns the version name for this variant. This could be coming from the manifest or
* could be overridden through the product flavors, and can have a suffix specified by
* the build type.
*
* @return the version name
*/
public String getVersionName() {
String versionName = mMergedFlavor.getVersionName();
String versionSuffix = mBuildType.getVersionNameSuffix();
if (versionSuffix != null && versionSuffix.length() > 0) {
if (versionName == null) {
versionName = getVersionNameFromManifest();
}
versionName = versionName + versionSuffix;
}
return versionName;
}
private final static String DEFAULT_TEST_RUNNER = "android.test.InstrumentationTestRunner";
/**
* Returns the instrumentionRunner to use to test this variant, or if the
* variant is a test, the one to use to test the tested variant.
* @return the instrumentation test runner name
*/
public String getInstrumentationRunner() {
VariantConfiguration config = this;
if (mType == Type.TEST) {
config = getTestedConfig();
}
String runner = config.mMergedFlavor.getTestInstrumentationRunner();
return runner != null ? runner : DEFAULT_TEST_RUNNER;
}
/**
* Reads the package name from the manifest.
*/
public String getPackageFromManifest() {
File manifestLocation = mDefaultSourceProvider.getManifestFile();
return sManifestParser.getPackage(manifestLocation);
}
/**
* Reads the version name from the manifest.
*/
public String getVersionNameFromManifest() {
File manifestLocation = mDefaultSourceProvider.getManifestFile();
return sManifestParser.getVersionName(manifestLocation);
}
/**
* Return the minSdkVersion for this variant.
*
* This uses both the value from the manifest (if present), and the override coming
* from the flavor(s) (if present).
* @return the minSdkVersion
*/
public int getMinSdkVersion() {
if (mTestedConfig != null) {
return mTestedConfig.getMinSdkVersion();
}
int minSdkVersion = mMergedFlavor.getMinSdkVersion();
if (minSdkVersion == -1) {
// read it from the main manifest
File manifestLocation = mDefaultSourceProvider.getManifestFile();
minSdkVersion = sManifestParser.getMinSdkVersion(manifestLocation);
}
return minSdkVersion;
}
public File getMainManifest() {
File defaultManifest = mDefaultSourceProvider.getManifestFile();
// this could not exist in a test project.
if (defaultManifest != null && defaultManifest.isFile()) {
return defaultManifest;
}
return null;
}
public List<File> getManifestOverlays() {
List<File> inputs = Lists.newArrayList();
if (mBuildTypeSourceProvider != null) {
File typeLocation = mBuildTypeSourceProvider.getManifestFile();
if (typeLocation != null && typeLocation.isFile()) {
inputs.add(typeLocation);
}
}
for (SourceProvider sourceProvider : mFlavorSourceProviders) {
File f = sourceProvider.getManifestFile();
if (f != null && f.isFile()) {
inputs.add(f);
}
}
return inputs;
}
/**
* Returns the dynamic list of {@link ResourceSet} based on the configuration, its dependencies,
* as well as tested config if applicable (test of a library).
*
* The list is ordered in ascending order of importance, meaning the first set is meant to be
* overridden by the 2nd one and so on. This is meant to facilitate usage of the list in a
* {@link com.android.builder.resources.ResourceMerger}.
*
* @return a list ResourceSet.
*/
@NonNull public List<ResourceSet> getResourceSets() {
List<ResourceSet> resourceSets = Lists.newArrayList();
// the list of dependency must be reversed to use the right overlay order.
for (int n = mFlatLibraries.size() - 1 ; n >= 0 ; n--) {
AndroidDependency dependency = mFlatLibraries.get(n);
File resFolder = dependency.getResFolder();
if (resFolder != null) {
ResourceSet resourceSet = new ResourceSet(dependency.getFolder().getName());
resourceSet.addSource(resFolder);
resourceSets.add(resourceSet);
}
}
Set<File> mainResDirs = mDefaultSourceProvider.getResourcesDirectories();
ResourceSet resourceSet = new ResourceSet("main");
resourceSet.addSources(mainResDirs);
resourceSets.add(resourceSet);
// the list of flavor must be reversed to use the right overlay order.
for (int n = mFlavorSourceProviders.size() - 1; n >= 0 ; n--) {
SourceProvider sourceProvider = mFlavorSourceProviders.get(n);
Set<File> flavorResDirs = sourceProvider.getResourcesDirectories();
resourceSet = new ResourceSet(mFlavorConfigs.get(n).getName());
resourceSet.addSources(flavorResDirs);
resourceSets.add(resourceSet);
}
if (mBuildTypeSourceProvider != null) {
Set<File> typeResDirs = mBuildTypeSourceProvider.getResourcesDirectories();
resourceSet = new ResourceSet(mBuildType.getName());
resourceSet.addSources(typeResDirs);
resourceSets.add(resourceSet);
}
return resourceSets;
}
public List<File> getAidlSourceList() {
List<File> sourceList = Lists.newArrayList();
sourceList.addAll(mDefaultSourceProvider.getAidlDirectories());
if (mType != Type.TEST) {
sourceList.addAll(mBuildTypeSourceProvider.getAidlDirectories());
}
if (hasFlavors()) {
for (SourceProvider flavorSourceSet : mFlavorSourceProviders) {
sourceList.addAll(flavorSourceSet.getAidlDirectories());
}
}
return sourceList;
}
/**
* Returns all the aidl import folder that are outside of the current project.
*/
public List<File> getAidlImports() {
List<File> list = Lists.newArrayList();
for (AndroidDependency lib : mFlatLibraries) {
File aidlLib = lib.getAidlFolder();
if (aidlLib != null && aidlLib.isDirectory()) {
list.add(aidlLib);
}
}
return list;
}
/**
* Returns the compile classpath for this config. If the config tests a library, this
* will include the classpath of the tested config
*/
public Set<File> getCompileClasspath() {
Set<File> classpath = Sets.newHashSet();
for (AndroidDependency lib : mFlatLibraries) {
classpath.add(lib.getJarFile());
}
for (JarDependency jar : mJars) {
classpath.add(new File(jar.getLocation()));
}
return classpath;
}
public List<String> getBuildConfigLines() {
List<String> fullList = Lists.newArrayList();
List<String> list = mDefaultConfig.getBuildConfig();
if (!list.isEmpty()) {
fullList.add("// lines from default config.");
fullList.addAll(list);
}
list = mBuildType.getBuildConfig();
if (!list.isEmpty()) {
fullList.add("// lines from build type: " + mBuildType.getName());
fullList.addAll(list);
}
for (ProductFlavor flavor : mFlavorConfigs) {
list = flavor.getBuildConfig();
if (!list.isEmpty()) {
fullList.add("// lines from product flavor: " + flavor.getName());
fullList.addAll(list);
}
}
return fullList;
}
protected void validate() {
if (mType != Type.TEST) {
File manifest = mDefaultSourceProvider.getManifestFile();
if (!manifest.isFile()) {
throw new IllegalArgumentException(
"Main Manifest missing from " + manifest.getAbsolutePath());
}
}
}
}