blob: 15834ce337079328c1f7ae146dc771fa2f7a93ed [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.dsl;
import com.android.annotations.NonNull;
import com.android.build.gradle.internal.VariantManager;
import com.android.build.gradle.internal.errors.DeprecationReporter;
import com.android.builder.model.BaseConfig;
import com.google.common.collect.ImmutableList;
import java.util.List;
import javax.inject.Inject;
import org.gradle.api.Project;
import org.gradle.api.logging.Logger;
import org.gradle.api.model.ObjectFactory;
import org.gradle.api.provider.Property;
/**
* Encapsulates all product flavors properties for this project.
*
* <p>Product flavors represent different versions of your project that you expect to co-exist on a
* single device, the Google Play store, or repository. For example, you can configure 'demo' and
* 'full' product flavors for your app, and each of those flavors can specify different features,
* device requirements, resources, and application ID's--while sharing common source code and
* resources. So, product flavors allow you to output different versions of your project by simply
* changing only the components and settings that are different between them.
*
* <p>Configuring product flavors is similar to <a
* href="https://developer.android.com/studio/build/build-variants.html#build-types">configuring
* build types</a>: add them to the <code>productFlavors</code> block of your module's <code>
* build.gradle</code> file and configure the settings you want. Product flavors support the same
* properties as the {@link com.android.build.gradle.BaseExtension#getDefaultConfig} block—this is
* because <code>defaultConfig</code> defines a {@link ProductFlavor} object that the plugin uses as
* the base configuration for all other flavors. Each flavor you configure can then override any of
* the default values in <code>defaultConfig</code>, such as the <a
* href="https://d.android.com/studio/build/application-id.html"><code>applicationId</code></a>.
*
* <p>When using Android plugin 3.0.0 and higher, <a
* href="com.android.build.gradle.internal.dsl.ProductFlavor.html#com.android.build.gradle.internal.dsl.ProductFlavor:dimension"><em>each
* flavor must belong to a <code>dimension</code></a></em>.
*
* <p>When you configure product flavors, the Android plugin automatically combines them with your
* {@link com.android.build.gradle.internal.dsl.BuildType} configurations to <a
* href="https://developer.android.com/studio/build/build-variants.html">create build variants</a>.
* If the plugin creates certain build variants that you don't want, you can <a
* href="https://developer.android.com/studio/build/build-variants.html#filter-variants">filter
* variants using <code>android.variantFilter</code></a>.
*/
public class ProductFlavor extends BaseFlavor {
@Inject
public ProductFlavor(
@NonNull String name,
@NonNull Project project,
@NonNull ObjectFactory objectFactory,
@NonNull DeprecationReporter deprecationReporter,
@NonNull Logger logger) {
super(name, project, objectFactory, deprecationReporter, logger);
isDefault = objectFactory.property(Boolean.class).convention(false);
}
private final Property<Boolean> isDefault;
private ImmutableList<String> matchingFallbacks;
public void setMatchingFallbacks(String... fallbacks) {
this.matchingFallbacks = ImmutableList.copyOf(fallbacks);
}
public void setMatchingFallbacks(String fallback) {
this.matchingFallbacks = ImmutableList.of(fallback);
}
public void setMatchingFallbacks(List<String> fallbacks) {
this.matchingFallbacks = ImmutableList.copyOf(fallbacks);
}
/**
* Specifies a sorted list of product flavors that the plugin should try to use when a direct
* variant match with a local module dependency is not possible.
*
* <p>Android plugin 3.0.0 and higher try to match each variant of your module with the same one
* from its dependencies. For example, when you build a "freeDebug" version of your app, the
* plugin tries to match it with "freeDebug" versions of the local library modules the app
* depends on.
*
* <p>However, there may be situations in which, for a given flavor dimension that exists in
* both the app and its library dependencies, <b>your app includes flavors that a dependency
* does not</b>. For example, consider if both your app and its library dependencies include a
* "tier" flavor dimension. However, the "tier" dimension in the app includes "free" and "paid"
* flavors, but one of its dependencies includes only "demo" and "paid" flavors for the same
* dimension. When the plugin tries to build the "free" version of your app, it won't know which
* version of the dependency to use, and you'll see an error message similar to the following:
*
* <pre>
* Error:Failed to resolve: Could not resolve project :mylibrary.
* Required by:
* project :app
* </pre>
*
* <p>In this situation, you should use <code>matchingFallbacks</code> to specify alternative
* matches for the app's "free" product flavor, as shown below:
*
* <pre>
* // In the app's build.gradle file.
* android {
* flavorDimensions 'tier'
* productFlavors {
* paid {
* dimension 'tier'
* // Because the dependency already includes a "paid" flavor in its
* // "tier" dimension, you don't need to provide a list of fallbacks
* // for the "paid" flavor.
* }
* free {
* dimension 'tier'
* // Specifies a sorted list of fallback flavors that the plugin
* // should try to use when a dependency's matching dimension does
* // not include a "free" flavor. You may specify as many
* // fallbacks as you like, and the plugin selects the first flavor
* // that's available in the dependency's "tier" dimension.
* matchingFallbacks = ['demo', 'trial']
* }
* }
* }
* </pre>
*
* <p>Note that, for a given flavor dimension that exists in both the app and its library
* dependencies, there is no issue when a library includes a product flavor that your app does
* not. That's because the plugin simply never requests that flavor from the dependency.
*
* <p>If instead you are trying to resolve an issue in which <b>a library dependency includes a
* flavor dimension that your app does not</b>, use <a
* href="com.android.build.gradle.internal.dsl.DefaultConfig.html#com.android.build.gradle.internal.dsl.DefaultConfig:missingDimensionStrategy(java.lang.String,
* java.lang.String)"> <code>missingDimensionStrategy</code></a>.
*
* @return the names of product flavors to use, in descending priority order
*/
public List<String> getMatchingFallbacks() {
if (matchingFallbacks == null) {
return ImmutableList.of();
}
return matchingFallbacks;
}
/** Whether this product flavor should be selected in Studio by default */
public Property<Boolean> getIsDefault() {
return isDefault;
}
@Override
@NonNull
protected DimensionRequest computeRequestedAndFallBacks(@NonNull List<String> requestedValues) {
// in order to have different fallbacks per variant for missing dimensions, we are
// going to actually have the flavor request itself (in the other dimension), with
// a modified name (in order to not have collision in case 2 dimensions have the same
// flavor names). So we will always fail to find the actual request and try for
// the fallbacks.
return new DimensionRequest(
VariantManager.getModifiedName(getName()), ImmutableList.copyOf(requestedValues));
}
@Override
protected void _initWith(@NonNull BaseConfig that) {
// we need to avoid doing this because of Property objects that cannot
// be set from themselves
if (this == that) {
return;
}
super._initWith(that);
if (that instanceof ProductFlavor) {
matchingFallbacks = ((ProductFlavor) that).matchingFallbacks;
}
}
}