/* * 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.core; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import com.android.annotations.NonNull; import com.android.annotations.Nullable; import com.android.annotations.VisibleForTesting; import com.android.builder.dependency.DependencyContainer; import com.android.builder.dependency.JarDependency; import com.android.builder.dependency.LibraryDependency; import com.android.builder.model.ApiVersion; import com.android.builder.model.BuildType; import com.android.builder.model.ClassField; import com.android.builder.model.ProductFlavor; import com.android.builder.model.SigningConfig; import com.android.builder.model.SourceProvider; import com.android.ide.common.res2.AssetSet; import com.android.ide.common.res2.ResourceSet; import com.android.utils.StringHelper; import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import java.io.File; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; /** * A Variant configuration. * * Variants are made from the combination of: * * - a build type (base interface BuildType), and its associated sources. * - a default configuration (base interface ProductFlavor), and its associated sources. * - a optional list of product flavors (base interface ProductFlavor) and their associated sources. * - dependencies (both jar and aar). * * @param <T> the type used for the Build Type. * @param <D> the type used for the default config * @param <F> the type used for the product flavors. */ public class VariantConfiguration<T extends BuildType, D extends ProductFlavor, F extends ProductFlavor> { // per variant, as is caches some manifest data specific to this variant. private final ManifestParser sManifestParser = new DefaultManifestParser(); /** * Full, unique name of the variant in camel case, including BuildType and Flavors (and Test) */ private String mFullName; /** * Flavor Name of the variant, including all flavors in camel case (starting with a lower * case). */ private String mFlavorName; /** * Full, unique name of the variant, including BuildType, flavors and test, dash separated. * (similar to full name but with dashes) */ private String mBaseName; /** * Unique directory name (can include multiple folders) for the variant, based on build type, * flavor and test. * This always uses forward slashes ('/') as separator on all platform. * */ private String mDirName; @NonNull private final D mDefaultConfig; @NonNull private final SourceProvider mDefaultSourceProvider; @NonNull private final T mBuildType; /** SourceProvider for the BuildType. Can be null */ @Nullable private final SourceProvider mBuildTypeSourceProvider; private final List<String> mFlavorDimensionNames = Lists.newArrayList(); private final List<F> mFlavors = Lists.newArrayList(); private final List<SourceProvider> mFlavorSourceProviders = Lists.newArrayList(); /** Variant specific source provider, may be null */ @Nullable private SourceProvider mVariantSourceProvider; /** MultiFlavors specific source provider, may be null */ @Nullable private SourceProvider mMultiFlavorSourceProvider; @NonNull private final VariantType mType; /** * Optional tested config in case this variant is used for testing another variant. * * @see VariantType#isForTesting() */ 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 LibraryDependency mOutput; @NonNull private ProductFlavor mMergedFlavor; /** * External/Jar dependencies. */ private final Set<JarDependency> mExternalJars = Sets.newHashSet(); /** * Local Jar dependencies. */ private final Set<JarDependency> mLocalJars = Sets.newHashSet(); /** * List of direct library dependencies. Each object defines its own dependencies. */ private final List<LibraryDependency> mDirectLibraries = Lists.newArrayList(); /** * List of all library dependencies in a flat list. * * <p>The order is based on the order needed to call aapt: earlier libraries override resources * of latter ones. */ private final List<LibraryDependency> mFlatLibraries = Lists.newArrayList(); /** * Variant-specific build Config fields. */ private final Map<String, ClassField> mBuildConfigFields = Maps.newTreeMap(); /** * Variant-specific res values. */ private final Map<String, ClassField> mResValues = Maps.newTreeMap(); /** * Signing Override to be used instead of any signing config provided by Build Type or * Product Flavors. */ private final SigningConfig mSigningConfigOverride; /** * Parses the manifest file and return the package name. * @param manifestFile the manifest file * @return the package name found or null */ @Nullable public static String getManifestPackage(@NonNull File manifestFile) { return new DefaultManifestParser().getPackage(manifestFile); } /** * Creates the configuration with the base source sets for a given {@link VariantType}. Meant * for non-testing variants. * * @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. * @param type the type of the project. * @param signingConfigOverride an optional Signing override to be used for signing. */ public VariantConfiguration( @NonNull D defaultConfig, @NonNull SourceProvider defaultSourceProvider, @NonNull T buildType, @Nullable SourceProvider buildTypeSourceProvider, @NonNull VariantType type, @Nullable SigningConfig signingConfigOverride) { this( defaultConfig, defaultSourceProvider, buildType, buildTypeSourceProvider, type, null /*testedConfig*/, signingConfigOverride); } /** * 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. * @param type the type of the project. * @param testedConfig the reference to the tested project. Required if type is Type.ANDROID_TEST * @param signingConfigOverride an optional Signing override to be used for signing. */ public VariantConfiguration( @NonNull D defaultConfig, @NonNull SourceProvider defaultSourceProvider, @NonNull T buildType, @Nullable SourceProvider buildTypeSourceProvider, @NonNull VariantType type, @Nullable VariantConfiguration testedConfig, @Nullable SigningConfig signingConfigOverride) { checkNotNull(defaultConfig); checkNotNull(defaultSourceProvider); checkNotNull(buildType); checkNotNull(type); checkArgument( !type.isForTesting() || testedConfig != null, "You have to specify the tested variant for this variant type."); checkArgument( type.isForTesting() || testedConfig == null, "This variant type doesn't need a tested variant."); mDefaultConfig = checkNotNull(defaultConfig); mDefaultSourceProvider = checkNotNull(defaultSourceProvider); mBuildType = checkNotNull(buildType); mBuildTypeSourceProvider = buildTypeSourceProvider; mType = checkNotNull(type); mTestedConfig = testedConfig; mSigningConfigOverride = signingConfigOverride; mMergedFlavor = DefaultProductFlavor.clone(mDefaultConfig); } /** * Returns the full, unique name of the variant in camel case (starting with a lower case), * including BuildType, Flavors and Test (if applicable). * * @return the name of the variant */ @NonNull public String getFullName() { if (mFullName == null) { StringBuilder sb = new StringBuilder(); String flavorName = getFlavorName(); if (!flavorName.isEmpty()) { sb.append(flavorName); sb.append(StringHelper.capitalize(mBuildType.getName())); } else { sb.append(mBuildType.getName()); } if (mType.isForTesting()) { sb.append(mType.getSuffix()); } mFullName = sb.toString(); } return mFullName; } /** * Returns a full name that includes the given splits name. * @param splitName the split name * @return a unique name made up of the variant and split names. */ @NonNull public String computeFullNameWithSplits(@NonNull String splitName) { StringBuilder sb = new StringBuilder(); String flavorName = getFlavorName(); if (!flavorName.isEmpty()) { sb.append(flavorName); sb.append(StringHelper.capitalize(splitName)); } else { sb.append(splitName); } sb.append(StringHelper.capitalize(mBuildType.getName())); if (mType.isForTesting()) { sb.append(mType.getSuffix()); } return sb.toString(); } /** * Returns the flavor name of the variant, including all flavors in camel case (starting * with a lower case). If the variant has no flavor, then an empty string is returned. * * @return the flavor name or an empty string. */ @NonNull public String getFlavorName() { if (mFlavorName == null) { if (mFlavors.isEmpty()) { mFlavorName = ""; } else { StringBuilder sb = new StringBuilder(); boolean first = true; for (F flavor : mFlavors) { sb.append(first ? flavor.getName() : StringHelper.capitalize(flavor.getName())); first = false; } mFlavorName = sb.toString(); } } return mFlavorName; } /** * Returns the full, unique name of the variant, including BuildType, flavors and test, * dash separated. (similar to full name but with dashes) * * @return the name of the variant */ @NonNull public String getBaseName() { if (mBaseName == null) { StringBuilder sb = new StringBuilder(); if (!mFlavors.isEmpty()) { for (ProductFlavor pf : mFlavors) { sb.append(pf.getName()).append('-'); } } sb.append(mBuildType.getName()); if (mType.isForTesting()) { sb.append('-').append(mType.getPrefix()); } mBaseName = sb.toString(); } return mBaseName; } /** * Returns a base name that includes the given splits name. * @param splitName the split name * @return a unique name made up of the variant and split names. */ @NonNull public String computeBaseNameWithSplits(@NonNull String splitName) { StringBuilder sb = new StringBuilder(); if (!mFlavors.isEmpty()) { for (ProductFlavor pf : mFlavors) { sb.append(pf.getName()).append('-'); } } sb.append(splitName).append('-'); sb.append(mBuildType.getName()); if (mType.isForTesting()) { sb.append('-').append(mType.getPrefix()); } return sb.toString(); } /** * Returns a unique directory name (can include multiple folders) for the variant, * based on build type, flavor and test. * * <p>This always uses forward slashes ('/') as separator on all platform. * * @return the directory name for the variant */ @NonNull public String getDirName() { if (mDirName == null) { StringBuilder sb = new StringBuilder(); if (mType.isForTesting()) { sb.append(mType.getPrefix()).append("/"); } if (!mFlavors.isEmpty()) { boolean first = true; for (F flavor : mFlavors) { sb.append(first ? flavor.getName() : StringHelper.capitalize(flavor.getName())); first = false; } sb.append('/').append(mBuildType.getName()); } else { sb.append(mBuildType.getName()); } mDirName = sb.toString(); } return mDirName; } /** * Returns a unique directory name (can include multiple folders) for the variant, * based on build type, flavor and test, and splits. * * <p>This always uses forward slashes ('/') as separator on all platform. * * @return the directory name for the variant */ @NonNull public String computeDirNameWithSplits(@NonNull String... splitNames) { StringBuilder sb = new StringBuilder(); if (mType.isForTesting()) { sb.append(mType.getPrefix()).append("/"); } if (!mFlavors.isEmpty()) { for (F flavor : mFlavors) { sb.append(flavor.getName()); } sb.append('/'); } for (String splitName : splitNames) { if (splitName != null) { sb.append(splitName).append('/'); } } sb.append(mBuildType.getName()); return sb.toString(); } /** * Return the names of the applied flavors. * * The list contains the dimension names as well. * * @return the list, possibly empty if there are no flavors. */ @NonNull public List<String> getFlavorNamesWithDimensionNames() { if (mFlavors.isEmpty()) { return Collections.emptyList(); } List<String> names; int count = mFlavors.size(); if (count > 1) { names = Lists.newArrayListWithCapacity(count * 2); for (int i = 0 ; i < count ; i++) { names.add(mFlavors.get(i).getName()); names.add(mFlavorDimensionNames.get(i)); } } else { names = Collections.singletonList(mFlavors.get(0).getName()); } return names; } /** * 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 productFlavor the configured product flavor * @param sourceProvider the source provider for the product flavor * @param dimensionName the name of the dimension associated with the flavor * * @return the config object */ @NonNull public VariantConfiguration addProductFlavor( @NonNull F productFlavor, @NonNull SourceProvider sourceProvider, @NonNull String dimensionName) { mFlavors.add(productFlavor); mFlavorSourceProviders.add(sourceProvider); mFlavorDimensionNames.add(dimensionName); mMergedFlavor = DefaultProductFlavor.mergeFlavors(mMergedFlavor, productFlavor); return this; } /** * Sets the variant-specific source provider. * @param sourceProvider the source provider for the product flavor * * @return the config object */ public VariantConfiguration setVariantSourceProvider(@Nullable SourceProvider sourceProvider) { mVariantSourceProvider = sourceProvider; return this; } /** * Sets the variant-specific source provider. * @param sourceProvider the source provider for the product flavor * * @return the config object */ public VariantConfiguration setMultiFlavorSourceProvider(@Nullable SourceProvider sourceProvider) { mMultiFlavorSourceProvider = sourceProvider; return this; } /** * Returns the variant specific source provider * @return the source provider or null if none has been provided. */ @Nullable public SourceProvider getVariantSourceProvider() { return mVariantSourceProvider; } @Nullable public SourceProvider getMultiFlavorSourceProvider() { return mMultiFlavorSourceProvider; } /** * Sets the dependencies * * @param container a DependencyContainer. * @return the config object */ @NonNull public VariantConfiguration setDependencies(@NonNull DependencyContainer container) { // Output of mTestedConfig will not be initialized until the tasks for the tested config are // created. If library output has never been added to mDirectLibraries, checked the output // of the mTestedConfig to see if the tasks are now created. if (mTestedConfig != null && mTestedConfig.mType == VariantType.LIBRARY && mTestedConfig.mOutput != null && !mDirectLibraries.contains(mTestedConfig.mOutput)) { mDirectLibraries.add(mTestedConfig.mOutput); } mDirectLibraries.addAll(container.getAndroidDependencies()); mExternalJars.addAll(container.getJarDependencies()); mLocalJars.addAll(container.getLocalDependencies()); resolveIndirectLibraryDependencies(mDirectLibraries, mFlatLibraries); return this; } /** * Returns the list of external/module jar dependencies * @return a non null collection of Jar dependencies. */ @NonNull public Collection<JarDependency> getExternalJarDependencies() { return mExternalJars; } /** * Returns the list of local jar dependencies * @return a non null collection of Jar dependencies. */ @NonNull public Collection<JarDependency> getLocalJarDependencies() { return mLocalJars; } /** * 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 LibraryDependency that will provides the * location of all the created items. * @return the config object */ @NonNull public VariantConfiguration setOutput(LibraryDependency output) { mOutput = output; return this; } /** * Returns the {@link LibraryDependency} that this library variant produces. Used so that * related test variants can use it as a dependency. Returns null if this is not a library * variant. * * @see #mOutput */ @Nullable public LibraryDependency getOutput() { return mOutput; } @NonNull public D getDefaultConfig() { return mDefaultConfig; } @NonNull public SourceProvider getDefaultSourceSet() { return mDefaultSourceProvider; } @NonNull public ProductFlavor getMergedFlavor() { return mMergedFlavor; } @NonNull public T getBuildType() { return mBuildType; } /** * The SourceProvider for the BuildType. Can be null. */ @Nullable public SourceProvider getBuildTypeSourceSet() { return mBuildTypeSourceProvider; } public boolean hasFlavors() { return !mFlavors.isEmpty(); } /** * Returns the product flavors. Items earlier in the list override later items. */ @NonNull public List<F> getProductFlavors() { return mFlavors; } /** * Returns the list of SourceProviders for the flavors. * * The list is ordered from higher priority to lower priority. * * @return the list of Source Providers for the flavors. Never null. */ @NonNull public List<SourceProvider> getFlavorSourceProviders() { return mFlavorSourceProviders; } /** * Returns the direct library dependencies */ @NonNull public List<LibraryDependency> getDirectLibraries() { return mDirectLibraries; } /** * Returns all the library dependencies, direct and transitive. */ @NonNull public List<LibraryDependency> getAllLibraries() { return mFlatLibraries; } @NonNull public VariantType getType() { return mType; } @Nullable 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 static void resolveIndirectLibraryDependencies(List<LibraryDependency> directDependencies, List<LibraryDependency> 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--) { LibraryDependency library = directDependencies.get(i); // get its libraries Collection<LibraryDependency> dependencies = library.getDependencies(); List<LibraryDependency> depList = Lists.newArrayList(dependencies); // resolve the dependencies for those libraries resolveIndirectLibraryDependencies(depList, outFlatDependencies); // and add the current one (if needed) in front (higher priority) if (!outFlatDependencies.contains(library)) { outFlatDependencies.add(0, library); } } } /** * Returns the original application ID before any overrides from flavors. * If the variant is a test variant, then the application ID is the one coming from the * configuration of the tested variant, and this call is similar to {@link #getApplicationId()} * @return the original application ID */ @Nullable public String getOriginalApplicationId() { if (mType.isForTesting()) { return getApplicationId(); } return getPackageFromManifest(); } /** * Returns the application ID for this variant. This could be coming from the manifest or * could be overridden through the product flavors and/or the build type. * @return the application ID */ @NonNull public String getApplicationId() { String id; if (mType.isForTesting()) { checkState(mTestedConfig != null); id = mMergedFlavor.getTestApplicationId(); String testedPackage = mTestedConfig.getApplicationId(); if (id == null) { id = testedPackage + ".test"; } else { if (id.equals(testedPackage)) { throw new RuntimeException(String.format("Application and test application id " + "cannot be the same: both are '%s' for %s", id, getFullName())); } } } else { // first get package override. id = getIdOverride(); // if it's null, this means we just need the default package // from the manifest since both flavor and build type do nothing. if (id == null) { id = getPackageFromManifest(); } } if (id == null) { throw new RuntimeException("Failed to get application id for " + getFullName()); } return id; } @Nullable public String getTestedApplicationId() { if (mType.isForTesting()) { checkState(mTestedConfig != null); if (mTestedConfig.mType == VariantType.LIBRARY) { return getApplicationId(); } else { return mTestedConfig.getApplicationId(); } } return null; } /** * Returns the application id override value coming from the Product Flavor and/or the * Build Type. If the package/id is not overridden then this returns null. * * @return the id override or null */ @Nullable public String getIdOverride() { String idName = mMergedFlavor.getApplicationId(); String idSuffix = mBuildType.getApplicationIdSuffix(); if (idSuffix != null && !idSuffix.isEmpty()) { if (idName == null) { idName = getPackageFromManifest(); } if (idSuffix.charAt(0) == '.') { idName = idName + idSuffix; } else { idName = idName + '.' + idSuffix; } } return idName; } /** * 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 */ @Nullable public String getVersionName() { String versionName = mMergedFlavor.getVersionName(); String versionSuffix = mBuildType.getVersionNameSuffix(); if (versionName == null && !mType.isForTesting()) { versionName = getVersionNameFromManifest(); } if (versionSuffix != null && !versionSuffix.isEmpty()) { versionName = Strings.nullToEmpty(versionName) + versionSuffix; } return versionName; } /** * Returns the version code 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 code or -1 if there was non defined. */ public int getVersionCode() { int versionCode = mMergedFlavor.getVersionCode() != null ? mMergedFlavor.getVersionCode() : -1; if (versionCode == -1 && !mType.isForTesting()) { versionCode = getVersionCodeFromManifest(); } return versionCode; } private static final String DEFAULT_TEST_RUNNER = "android.test.InstrumentationTestRunner"; private static final Boolean DEFAULT_HANDLE_PROFILING = false; private static final Boolean DEFAULT_FUNCTIONAL_TEST = false; /** * Returns the instrumentationRunner 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 */ @NonNull public String getInstrumentationRunner() { VariantConfiguration config = this; if (mType.isForTesting()) { config = getTestedConfig(); checkState(config != null); } String runner = config.mMergedFlavor.getTestInstrumentationRunner(); return runner != null ? runner : DEFAULT_TEST_RUNNER; } /** * Returns the instrumentationRunner arguments to use to test this variant, or if the * variant is a test, the ones to use to test the tested variant */ @NonNull public Map<String, String> getInstrumentationRunnerArguments() { VariantConfiguration config = this; if (mType.isForTesting()) { config = getTestedConfig(); checkState(config != null); } return config.mMergedFlavor.getTestInstrumentationRunnerArguments(); } /** * Returns handleProfiling value to use to test this variant, or if the * variant is a test, the one to use to test the tested variant. * @return the handleProfiling value */ @NonNull public Boolean getHandleProfiling() { VariantConfiguration config = this; if (mType.isForTesting()) { config = getTestedConfig(); checkState(config != null); } Boolean handleProfiling = config.mMergedFlavor.getTestHandleProfiling(); return handleProfiling != null ? handleProfiling : DEFAULT_HANDLE_PROFILING; } /** * Returns functionalTest value to use to test this variant, or if the * variant is a test, the one to use to test the tested variant. * @return the functionalTest value */ @NonNull public Boolean getFunctionalTest() { VariantConfiguration config = this; if (mType.isForTesting()) { config = getTestedConfig(); checkState(config != null); } Boolean functionalTest = config.mMergedFlavor.getTestFunctionalTest(); return functionalTest != null ? functionalTest : DEFAULT_FUNCTIONAL_TEST; } /** * Reads the package name from the manifest. This is unmodified by the build type. */ @Nullable public String getPackageFromManifest() { checkState(!mType.isForTesting()); File manifestLocation = mDefaultSourceProvider.getManifestFile(); String packageName = sManifestParser.getPackage(manifestLocation); if (packageName == null) { throw new RuntimeException(String.format("Cannot read packageName from %1$s", mDefaultSourceProvider.getManifestFile().getAbsolutePath())); } return packageName; } /** * Reads the version name from the manifest. */ @Nullable public String getVersionNameFromManifest() { File manifestLocation = mDefaultSourceProvider.getManifestFile(); return sManifestParser.getVersionName(manifestLocation); } /** * Reads the version code from the manifest. */ public int getVersionCodeFromManifest() { File manifestLocation = mDefaultSourceProvider.getManifestFile(); return sManifestParser.getVersionCode(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 */ @NonNull public ApiVersion getMinSdkVersion() { if (mTestedConfig != null) { return mTestedConfig.getMinSdkVersion(); } ApiVersion minSdkVersion = mMergedFlavor.getMinSdkVersion(); if (minSdkVersion == null) { // read it from the main manifest File manifestLocation = mDefaultSourceProvider.getManifestFile(); minSdkVersion = DefaultApiVersion.create( sManifestParser.getMinSdkVersion(manifestLocation)); } return minSdkVersion; } /** * Return the targetSdkVersion 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 targetSdkVersion */ @NonNull public ApiVersion getTargetSdkVersion() { if (mTestedConfig != null) { return mTestedConfig.getTargetSdkVersion(); } ApiVersion targetSdkVersion = mMergedFlavor.getTargetSdkVersion(); if (targetSdkVersion == null) { // read it from the main manifest File manifestLocation = mDefaultSourceProvider.getManifestFile(); targetSdkVersion = DefaultApiVersion.create( sManifestParser.getTargetSdkVersion(manifestLocation)); } return targetSdkVersion; } @Nullable public File getMainManifest() { File defaultManifest = mDefaultSourceProvider.getManifestFile(); // this could not exist in a test project. if (defaultManifest.isFile()) { return defaultManifest; } return null; } /** * Returns a list of sorted SourceProvider in order of ascending order, meaning, the earlier * items are meant to be overridden by later items. * * @return a list of source provider */ @NonNull public List<SourceProvider> getSortedSourceProviders() { List<SourceProvider> providers = Lists.newArrayList(); // first the default source provider providers.add(mDefaultSourceProvider); // the list of flavor must be reversed to use the right overlay order. for (int n = mFlavorSourceProviders.size() - 1; n >= 0 ; n--) { providers.add(mFlavorSourceProviders.get(n)); } // multiflavor specific overrides flavor if (mMultiFlavorSourceProvider != null) { providers.add(mMultiFlavorSourceProvider); } // build type overrides flavors if (mBuildTypeSourceProvider != null) { providers.add(mBuildTypeSourceProvider); } // variant specific overrides all if (mVariantSourceProvider != null) { providers.add(mVariantSourceProvider); } return providers; } @NonNull public List<File> getManifestOverlays() { List<File> inputs = Lists.newArrayList(); if (mVariantSourceProvider != null) { File variantLocation = mVariantSourceProvider.getManifestFile(); if (variantLocation.isFile()) { inputs.add(variantLocation); } } if (mBuildTypeSourceProvider != null) { File typeLocation = mBuildTypeSourceProvider.getManifestFile(); if (typeLocation.isFile()) { inputs.add(typeLocation); } } if (mMultiFlavorSourceProvider != null) { File variantLocation = mMultiFlavorSourceProvider.getManifestFile(); if (variantLocation.isFile()) { inputs.add(variantLocation); } } for (SourceProvider sourceProvider : mFlavorSourceProviders) { File f = sourceProvider.getManifestFile(); if (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.ide.common.res2.ResourceMerger}. * * @param generatedResFolders a list of generated res folders * @param includeDependencies whether to include in the result the resources of the dependencies * * @return a list ResourceSet. */ @NonNull public List<ResourceSet> getResourceSets(@NonNull List<File> generatedResFolders, boolean includeDependencies) { List<ResourceSet> resourceSets = Lists.newArrayList(); // the list of dependency must be reversed to use the right overlay order. if (includeDependencies) { for (int n = mFlatLibraries.size() - 1 ; n >= 0 ; n--) { LibraryDependency dependency = mFlatLibraries.get(n); if (!dependency.isOptional()) { File resFolder = dependency.getResFolder(); if (resFolder.isDirectory()) { ResourceSet resourceSet = new ResourceSet(dependency.getFolder().getName()); resourceSet.addSource(resFolder); resourceSets.add(resourceSet); } } } } Collection<File> mainResDirs = mDefaultSourceProvider.getResDirectories(); // the main + generated res folders are in the same ResourceSet ResourceSet resourceSet = new ResourceSet(BuilderConstants.MAIN); resourceSet.addSources(mainResDirs); if (!generatedResFolders.isEmpty()) { for (File generatedResFolder : generatedResFolders) { resourceSet.addSource(generatedResFolder); } } 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); Collection<File> flavorResDirs = sourceProvider.getResDirectories(); // we need the same of the flavor config, but it's in a different list. // This is fine as both list are parallel collections with the same number of items. resourceSet = new ResourceSet(sourceProvider.getName()); resourceSet.addSources(flavorResDirs); resourceSets.add(resourceSet); } // multiflavor specific overrides flavor if (mMultiFlavorSourceProvider != null) { Collection<File> variantResDirs = mMultiFlavorSourceProvider.getResDirectories(); resourceSet = new ResourceSet(getFlavorName()); resourceSet.addSources(variantResDirs); resourceSets.add(resourceSet); } // build type overrides the flavors if (mBuildTypeSourceProvider != null) { Collection<File> typeResDirs = mBuildTypeSourceProvider.getResDirectories(); resourceSet = new ResourceSet(mBuildType.getName()); resourceSet.addSources(typeResDirs); resourceSets.add(resourceSet); } // variant specific overrides all if (mVariantSourceProvider != null) { Collection<File> variantResDirs = mVariantSourceProvider.getResDirectories(); resourceSet = new ResourceSet(getFullName()); resourceSet.addSources(variantResDirs); resourceSets.add(resourceSet); } return resourceSets; } /** * Returns the dynamic list of {@link AssetSet} 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.ide.common.res2.AssetMerger}. * * @return a list ResourceSet. */ @NonNull public List<AssetSet> getAssetSets(@NonNull List<File> generatedResFolders, boolean includeDependencies) { List<AssetSet> assetSets = Lists.newArrayList(); if (includeDependencies) { // the list of dependency must be reversed to use the right overlay order. for (int n = mFlatLibraries.size() - 1 ; n >= 0 ; n--) { LibraryDependency dependency = mFlatLibraries.get(n); File assetFolder = dependency.getAssetsFolder(); if (assetFolder.isDirectory()) { AssetSet assetSet = new AssetSet(dependency.getFolder().getName()); assetSet.addSource(assetFolder); assetSets.add(assetSet); } } } Collection<File> mainResDirs = mDefaultSourceProvider.getAssetsDirectories(); // the main + generated asset folders are in the same AssetSet AssetSet assetSet = new AssetSet(BuilderConstants.MAIN); assetSet.addSources(mainResDirs); if (!generatedResFolders.isEmpty()) { for (File generatedResFolder : generatedResFolders) { assetSet.addSource(generatedResFolder); } } assetSets.add(assetSet); // 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); Collection<File> flavorResDirs = sourceProvider.getAssetsDirectories(); // we need the same of the flavor config, but it's in a different list. // This is fine as both list are parallel collections with the same number of items. assetSet = new AssetSet(mFlavors.get(n).getName()); assetSet.addSources(flavorResDirs); assetSets.add(assetSet); } // multiflavor specific overrides flavor if (mMultiFlavorSourceProvider != null) { Collection<File> variantResDirs = mMultiFlavorSourceProvider.getAssetsDirectories(); assetSet = new AssetSet(getFlavorName()); assetSet.addSources(variantResDirs); assetSets.add(assetSet); } // build type overrides flavors if (mBuildTypeSourceProvider != null) { Collection<File> typeResDirs = mBuildTypeSourceProvider.getAssetsDirectories(); assetSet = new AssetSet(mBuildType.getName()); assetSet.addSources(typeResDirs); assetSets.add(assetSet); } // variant specific overrides all if (mVariantSourceProvider != null) { Collection<File> variantResDirs = mVariantSourceProvider.getAssetsDirectories(); assetSet = new AssetSet(getFullName()); assetSet.addSources(variantResDirs); assetSets.add(assetSet); } return assetSets; } @NonNull public List<File> getLibraryJniFolders() { List<File> list = Lists.newArrayListWithExpectedSize(mFlatLibraries.size()); for (int n = mFlatLibraries.size() - 1 ; n >= 0 ; n--) { LibraryDependency dependency = mFlatLibraries.get(n); File jniFolder = dependency.getJniFolder(); if (jniFolder.isDirectory()) { list.add(jniFolder); } } return list; } /** * Returns all the renderscript import folder that are outside of the current project. */ @NonNull public List<File> getRenderscriptImports() { List<File> list = Lists.newArrayList(); for (LibraryDependency lib : mFlatLibraries) { File rsLib = lib.getRenderscriptFolder(); if (rsLib.isDirectory()) { list.add(rsLib); } } return list; } /** * Returns all the renderscript source folder from the main config, the flavors and the * build type. * * @return a list of folders. */ @NonNull public List<File> getRenderscriptSourceList() { List<SourceProvider> providers = getSortedSourceProviders(); List<File> sourceList = Lists.newArrayListWithExpectedSize(providers.size()); for (SourceProvider provider : providers) { sourceList.addAll(provider.getRenderscriptDirectories()); } return sourceList; } /** * Returns all the aidl import folder that are outside of the current project. */ @NonNull public List<File> getAidlImports() { List<File> list = Lists.newArrayList(); for (LibraryDependency lib : mFlatLibraries) { File aidlLib = lib.getAidlFolder(); if (aidlLib.isDirectory()) { list.add(aidlLib); } } return list; } @NonNull public List<File> getAidlSourceList() { List<SourceProvider> providers = getSortedSourceProviders(); List<File> sourceList = Lists.newArrayListWithExpectedSize(providers.size()); for (SourceProvider provider : providers) { sourceList.addAll(provider.getAidlDirectories()); } return sourceList; } @NonNull public List<File> getJniSourceList() { List<SourceProvider> providers = getSortedSourceProviders(); List<File> sourceList = Lists.newArrayListWithExpectedSize(providers.size()); for (SourceProvider provider : providers) { sourceList.addAll(provider.getCDirectories()); } return sourceList; } @NonNull public List<File> getJniLibsList() { List<SourceProvider> providers = getSortedSourceProviders(); List<File> sourceList = Lists.newArrayListWithExpectedSize(providers.size()); for (SourceProvider provider : providers) { sourceList.addAll(provider.getJniLibsDirectories()); } return sourceList; } /** * Returns the compile classpath for this config. If the config tests a library, this * will include the classpath of the tested config * * @return a non null, but possibly empty set. */ @NonNull public Set<File> getCompileClasspath() { Set<File> classpath = Sets.newHashSetWithExpectedSize( mExternalJars.size() + mLocalJars.size() + mFlatLibraries.size()); for (LibraryDependency lib : mFlatLibraries) { classpath.add(lib.getJarFile()); for (File jarFile : lib.getLocalJars()) { classpath.add(jarFile); } } for (JarDependency jar : mExternalJars) { if (jar.isCompiled()) { classpath.add(jar.getJarFile()); } } for (JarDependency jar : mLocalJars) { if (jar.isCompiled()) { classpath.add(jar.getJarFile()); } } return classpath; } /** * Returns the list of packaged jars for this config. If the config tests a library, this * will include the jars of the tested config * * @return a non null, but possibly empty list. */ @NonNull public Set<File> getPackagedJars() { Set<File> jars = Sets.newHashSetWithExpectedSize( mExternalJars.size() + mLocalJars.size() + mFlatLibraries.size()); for (JarDependency jar : mExternalJars) { File jarFile = jar.getJarFile(); if (jar.isPackaged() && jarFile.exists()) { jars.add(jarFile); } } for (JarDependency jar : mLocalJars) { File jarFile = jar.getJarFile(); if (jar.isPackaged() && jarFile.exists()) { jars.add(jarFile); } } for (LibraryDependency libraryDependency : mFlatLibraries) { if (!libraryDependency.isOptional()) { File libJar = libraryDependency.getJarFile(); if (libJar.exists()) { jars.add(libJar); } for (File jarFile : libraryDependency.getLocalJars()) { if (jarFile.isFile()) { jars.add(jarFile); } } } } return jars; } /** * Returns the list of provided-only jars for this config. * * @return a non null, but possibly empty list. */ @NonNull public List<File> getProvidedOnlyJars() { Set<File> jars = Sets.newHashSetWithExpectedSize(mExternalJars.size() + mLocalJars.size()); for (JarDependency jar : mExternalJars) { File jarFile = jar.getJarFile(); if (jar.isCompiled() && !jar.isPackaged() && jarFile.exists()) { jars.add(jarFile); } } for (JarDependency jar : mLocalJars) { File jarFile = jar.getJarFile(); if (jar.isCompiled() && !jar.isPackaged() && jarFile.exists()) { jars.add(jarFile); } } for (LibraryDependency libraryDependency : mFlatLibraries) { if (libraryDependency.isOptional()) { File libJar = libraryDependency.getJarFile(); if (libJar.exists()) { jars.add(libJar); } for (File jarFile : libraryDependency.getLocalJars()) { if (jarFile.isFile()) { jars.add(jarFile); } } } } return Lists.newArrayList(jars); } /** * Adds a variant-specific BuildConfig field. * @param type the type of the field * @param name the name of the field * @param value the value of the field */ public void addBuildConfigField(@NonNull String type, @NonNull String name, @NonNull String value) { ClassField classField = AndroidBuilder.createClassField(type, name, value); mBuildConfigFields.put(name, classField); } /** * Adds a variant-specific res value. * @param type the type of the field * @param name the name of the field * @param value the value of the field */ public void addResValue(@NonNull String type, @NonNull String name, @NonNull String value) { ClassField classField = AndroidBuilder.createClassField(type, name, value); mResValues.put(name, classField); } /** * Returns a list of items for the BuildConfig class. * * Items can be either fields (instance of {@link com.android.builder.model.ClassField}) * or comments (instance of String). * * @return a list of items. */ @NonNull public List<Object> getBuildConfigItems() { List<Object> fullList = Lists.newArrayList(); // keep track of the names already added. This is because we show where the items // come from so we cannot just put everything a map and let the new ones override the // old ones. Set<String> usedFieldNames = Sets.newHashSet(); Collection<ClassField> list = mBuildConfigFields.values(); if (!list.isEmpty()) { fullList.add("Fields from the variant"); fillFieldList(fullList, usedFieldNames, list); } list = mBuildType.getBuildConfigFields().values(); if (!list.isEmpty()) { fullList.add("Fields from build type: " + mBuildType.getName()); fillFieldList(fullList, usedFieldNames, list); } for (F flavor : mFlavors) { list = flavor.getBuildConfigFields().values(); if (!list.isEmpty()) { fullList.add("Fields from product flavor: " + flavor.getName()); fillFieldList(fullList, usedFieldNames, list); } } list = mDefaultConfig.getBuildConfigFields().values(); if (!list.isEmpty()) { fullList.add("Fields from default config."); fillFieldList(fullList, usedFieldNames, list); } return fullList; } /** * Return the merged build config fields for the variant. * * This is made of of the variant-specific fields overlayed on top of the build type ones, * the flavors ones, and the default config ones. * * @return a map of merged fields */ @NonNull public Map<String, ClassField> getMergedBuildConfigFields() { Map<String, ClassField> mergedMap = Maps.newHashMap(); // start from the lowest priority and just add it all. Higher priority fields // will replace lower priority ones. mergedMap.putAll(mDefaultConfig.getBuildConfigFields()); for (int i = mFlavors.size() - 1; i >= 0 ; i--) { mergedMap.putAll(mFlavors.get(i).getBuildConfigFields()); } mergedMap.putAll(mBuildType.getBuildConfigFields()); mergedMap.putAll(mBuildConfigFields); return mergedMap; } /** * Return the merged res values for the variant. * * This is made of of the variant-specific fields overlayed on top of the build type ones, * the flavors ones, and the default config ones. * * @return a map of merged fields */ @NonNull public Map<String, ClassField> getMergedResValues() { Map<String, ClassField> mergedMap = Maps.newHashMap(); // start from the lowest priority and just add it all. Higher priority fields // will replace lower priority ones. mergedMap.putAll(mDefaultConfig.getResValues()); for (int i = mFlavors.size() - 1; i >= 0 ; i--) { mergedMap.putAll(mFlavors.get(i).getResValues()); } mergedMap.putAll(mBuildType.getResValues()); mergedMap.putAll(mResValues); return mergedMap; } /** * Fills a list of Object from a given list of ClassField only if the name isn't in a set. * Each new item added adds its name to the list. * @param outList the out list * @param usedFieldNames the list of field names already in the list * @param list the list to copy items from */ private static void fillFieldList( @NonNull List<Object> outList, @NonNull Set<String> usedFieldNames, @NonNull Collection<ClassField> list) { for (ClassField f : list) { String name = f.getName(); if (!usedFieldNames.contains(name)) { usedFieldNames.add(f.getName()); outList.add(f); } } } /** * Returns a list of generated resource values. * * Items can be either fields (instance of {@link com.android.builder.model.ClassField}) * or comments (instance of String). * * @return a list of items. */ @NonNull public List<Object> getResValues() { List<Object> fullList = Lists.newArrayList(); // keep track of the names already added. This is because we show where the items // come from so we cannot just put everything a map and let the new ones override the // old ones. Set<String> usedFieldNames = Sets.newHashSet(); Collection<ClassField> list = mResValues.values(); if (!list.isEmpty()) { fullList.add("Values from the variant"); fillFieldList(fullList, usedFieldNames, list); } list = mBuildType.getResValues().values(); if (!list.isEmpty()) { fullList.add("Values from build type: " + mBuildType.getName()); fillFieldList(fullList, usedFieldNames, list); } for (F flavor : mFlavors) { list = flavor.getResValues().values(); if (!list.isEmpty()) { fullList.add("Values from product flavor: " + flavor.getName()); fillFieldList(fullList, usedFieldNames, list); } } list = mDefaultConfig.getResValues().values(); if (!list.isEmpty()) { fullList.add("Values from default config."); fillFieldList(fullList, usedFieldNames, list); } return fullList; } @Nullable public SigningConfig getSigningConfig() { if (mSigningConfigOverride != null) { return mSigningConfigOverride; } SigningConfig signingConfig = mBuildType.getSigningConfig(); if (signingConfig != null) { return signingConfig; } return mMergedFlavor.getSigningConfig(); } public boolean isSigningReady() { SigningConfig signingConfig = getSigningConfig(); return signingConfig != null && signingConfig.isSigningReady(); } /** * Returns the proguard config files coming from the project but also from the dependencies. * * Note that if the method is set to include config files coming from libraries, they will * only be included if the aars have already been unzipped. * * @param includeLibraries whether to include the library dependencies. * @return a non null list of proguard files. */ @NonNull public List<File> getProguardFiles(boolean includeLibraries, List<File> defaultProguardConfig) { List<File> fullList = Lists.newArrayList(); // add the config files from the build type, main config and flavors fullList.addAll(mDefaultConfig.getProguardFiles()); fullList.addAll(mBuildType.getProguardFiles()); for (F flavor : mFlavors) { fullList.addAll(flavor.getProguardFiles()); } if (fullList.isEmpty()) { fullList.addAll(defaultProguardConfig); } // now add the one coming from the library dependencies if (includeLibraries) { for (LibraryDependency libraryDependency : mFlatLibraries) { File proguardRules = libraryDependency.getProguardRules(); if (proguardRules.exists()) { fullList.add(proguardRules); } } } return fullList; } /** * Returns the proguard config files to be used for the test APK. */ @NonNull public List<File> getTestProguardFiles() { List<File> fullList = Lists.newArrayList(); // add the config files from the build type, main config and flavors fullList.addAll(mDefaultConfig.getTestProguardFiles()); fullList.addAll(mBuildType.getTestProguardFiles()); for (F flavor : mFlavors) { fullList.addAll(flavor.getTestProguardFiles()); } return fullList; } @NonNull public List<Object> getConsumerProguardFiles() { List<Object> fullList = Lists.newArrayList(); // add the config files from the build type, main config and flavors fullList.addAll(mDefaultConfig.getConsumerProguardFiles()); fullList.addAll(mBuildType.getConsumerProguardFiles()); for (F flavor : mFlavors) { fullList.addAll(flavor.getConsumerProguardFiles()); } return fullList; } public boolean isTestCoverageEnabled() { return mBuildType.isTestCoverageEnabled(); } /** * Returns the merged manifest placeholders. All product flavors are merged first, then build * type specific placeholders are added and potentially overrides product flavors values. * @return the merged manifest placeholders for a build variant. */ @NonNull public Map<String, Object> getManifestPlaceholders() { Map<String, Object> mergedFlavorsPlaceholders = mMergedFlavor.getManifestPlaceholders(); // so far, blindly override the build type placeholders mergedFlavorsPlaceholders.putAll(mBuildType.getManifestPlaceholders()); return mergedFlavorsPlaceholders; } public boolean isMultiDexEnabled() { Boolean value = mBuildType.getMultiDexEnabled(); if (value != null) { return value; } value = mMergedFlavor.getMultiDexEnabled(); if (value != null) { return value; } return false; } public File getMultiDexKeepFile() { File value = mBuildType.getMultiDexKeepFile(); if (value != null) { return value; } value = mMergedFlavor.getMultiDexKeepFile(); if (value != null) { return value; } return null; } public File getMultiDexKeepProguard() { File value = mBuildType.getMultiDexKeepProguard(); if (value != null) { return value; } value = mMergedFlavor.getMultiDexKeepProguard(); if (value != null) { return value; } return null; } public boolean isLegacyMultiDexMode() { return isMultiDexEnabled() && getMinSdkVersion().getApiLevel() < 21; } /** * Returns the renderscript support mode. */ public boolean getRenderscriptSupportModeEnabled() { Boolean value = mMergedFlavor.getRenderscriptSupportModeEnabled(); if (value != null) { return value; } // default is false. return false; } /** * Returns the renderscript NDK mode. */ public boolean getRenderscriptNdkModeEnabled() { Boolean value = mMergedFlavor.getRenderscriptNdkModeEnabled(); if (value != null) { return value; } // default is false. return false; } public Collection<File> getJarJarRuleFiles() { ImmutableList.Builder<File> jarjarRuleFiles = ImmutableList.builder(); jarjarRuleFiles.addAll(getMergedFlavor().getJarJarRuleFiles()); jarjarRuleFiles.addAll(mBuildType.getJarJarRuleFiles()); return jarjarRuleFiles.build(); } }