I have a large number of gradle modules I use to build my android applications and libraries. My application is decomposed into multiple java & C++ libraries. Each module might be a java library, or an APK project. For each module representing an APK, I have a build.gradle which specifies various productFlavors sections that control how to build them. Example:
flavorDimensions "mode"
productFlavors {
arm {
dimension "mode"
externalNativeBuild {
ndk { abiFilters "armeabi-v7a" }
}
}
x86 {
dimension "mode"
externalNativeBuild {
ndk { abiFilters "x86" }
}
}
full {
dimension "mode"
externalNativeBuild {
ndk { abiFilters "x86", "armeabi-v7a" }
}
}
}
What I'm trying to avoid is duplicating the above configuration in each leaf build.gradle that represents an APK output. How can I store the product flavors at the root level and somehow have those transitive to the leave build.gradle files so I do not need to duplicate them?
The support library does something very similar, and you can see their method here (in particular, the SupportLibraryPlugin).
At a high level the strategy is to create a Gradle plugin in your buildSrc directory that you apply to each of your projects. This plugin will apply the common configuration.
Your plugin might look something like this:
[project]/buildSrc/src/main/groovy/com/example/ConfigurationPlugin.groovy
class ConfigurationPlugin implements Plugin<Project> {
#Override
public void apply(Project project) {
LibraryExtension library = project.extensions.findByType(LibraryExtension.class);
library.flavorDimensions "mode"
library.productFlavors {
arm {
dimension "mode"
externalNativeBuild {
ndk { abiFilters "armeabi-v7a" }
}
}
x86 {
dimension "mode"
externalNativeBuild {
ndk { abiFilters "x86" }
}
}
full {
dimension "mode"
externalNativeBuild {
ndk { abiFilters "x86", "armeabi-v7a" }
}
}
}
}
}
[project]/builSrc/build.gradle
apply plugin: 'groovy'
repositories {
google()
}
dependencies {
compile 'com.android.tools.build:gradle:3.0.0-beta7'
}
Library module build.gradle:
apply plugin: 'com.android.library'
apply plugin: ConfigurationPlugin
android {
// ...
}
You can reference another Gradle project within your project using ':project-name', e.g. compile ':pluginproject'. You'll also want to update settings.gradle to include the other project. The project will be built when it is needed to apply the plugin.
Related
I have been facing many issues with updating a project with very complex flavors to gradle 3.+.
The problem: Some flavors that do not depend on local libraries. Although, some flavors do not rely on the library, each app flavor is forcing me to add a matching fallback of the lib flavor. Even if I put the local lib dependency declared inside a single flavor of the app, it doesn't change anything. Is there any way to suppress this error for specific app flavors or do I just have to put matching fallbacks for each flavor regardless of its dependence on the local lib?
If I do have to put matchingFallback in every flavor even if the app doesn't depend on the lib, why didn't they allow us to declare matchingFallbacks, inside of the default config, and override it inside our app flavors like missingDimensionStrategy?
Local Library build.gradle
Note: This gradle has two distinct flavors for a single dimension. Requiring matchingFallbacks in app to reference a flavor that share the same dimension.
apply plugin: 'com.android.library'
android {
...
defaultConfig {
...
}
buildTypes {
release {...}
debug {...}
}
flavorDimensions flavor.default
productFlavors {
libFlavor1 {
}
libFlavor2 {
}
}
}
dependencies {
...
}
App build.gradle
apply plugin: 'com.android.application'
android {
...
defaultConfig {...}
buildTypes {
release {...}
debug {...}
}
flavorDimensions flavor.default
productFlavors {
// Flavor that requires library
appFlavor1 {
matchingFallbacks = ['libFlavor1', 'libFlavor2']
}
// Does not require library but still requires matchingFallbacks?
appFlavor2 {}
}
}
dependencies {
implementation project(':library')
}
Failed solution
I have tried to move the local lib to the flavor but the other libs still require a matching fallback.
apply plugin: 'com.android.application'
android {
...
defaultConfig {...}
buildTypes {
release {...}
debug {...}
}
flavorDimensions flavor.default
productFlavors {
// Flavor that requires library
// I have tried to move the local lib to the flavor
// But the other flavor requires a fallback
appFlavor1 {
matchingFallbacks = ['libFlavor1', 'libFlavor2']
dependencies {
implementation project(':library')
}
}
// Does not require library but still requires matchingFallbacks?
appFlavor2 {}
}
}
dependencies {
// implementation project(':library')
...
}
If you want to add a dependency to a particular flavor, you can use <flavor's name>Implementation key word:
android {
...
productFlavors {
appFlavor1 {
//MatchingFallbacks is required because of its dependencies to the project 'library'
matchingFallbacks = ['libFlavor1', 'libFlavor2']
//Don't use dependencies node here, it will add dependencies to the other flavors
}
appFlavor2{/*no need to use matchingFallbacks here, there is no dependencies to project 'library'*/}
}
...
}
dependencies {
appFlavor1Implementation project(':library')
}
I need to have a separate CMakeLists.txt for each Android ABI. I tried to use product flavor to set the path for CMakeLists.txt. But I am getting following error on running ./gradlew assembleDebug or any other gradle command from command line.
Could not find method path() for arguments [CMakeLists.txt] on object
of type
com.android.build.gradle.internal.dsl.ExternalNativeCmakeOptions.
Here is how I have set product flavor in build.gradle.
productFlavors {
arm64_v8a {
ndk {
abiFilters "arm64-v8a"
}
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
}
x86_64 {
ndk {
abiFilters "x86_64"
}
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
}
}
NOTE - I had initially named the files as "CMakeLists_arm64-v8a.txt" and "CMakeLists_x86_64.txt". But that was failing so tried same name.
How to fix this or is there a workaround for this?
No, you cannot have CMakeLists.txt paths different for different flavors and/or ABIs, but you can use the arguments to add conditionals in your cmake script, for example like this:
flavorDimensions "abi"
productFlavors {
arm64_v8a {
dimension "abi"
ndk {
abiFilters "arm64-v8a"
}
externalNativeBuild {
cmake {
arguments "-DFLAVOR=ARM"
}
}
}
x86_64 {
dimension "abi"
ndk {
abiFilters "x86_64"
}
externalNativeBuild {
cmake {
arguments "-DFLAVOR=x86"
}
}
}
}
Now you can check this in your CMakeLists.txt:
if (FLAVOR STREQUAL 'ARM')
include(arm.cmake)
endif()
But in your case, you can rely on the argument that is defined by Android Studio, and don't need your own parameter:
if (ANDROID_ABI STREQUAL 'arm64-v8a')
include(arm.cmake)
endif()
Actually, you probably don't need separate productFlavors at all, but rather use splits to produce thin APKs for each ABI.
I would like to have multiple android builds, but it isn't building the way I would like.
I am using Android Studio to make my app and build it with the following productFlavors in the gradle file:
android {
productFlavors {
armv7 {
ndk {
abiFilters "armeabi-v7a", "x86"
}
}
}
}
When I have Android Studio build the application, it only builds the x86 version. If I remove the x86 from the flavors, it builds armeabi-v7a. How can I get it to build both versions?
You should probably do this to get both:
android {
productFlavors {
armv7 {
ndk {
abiFilters "armeabi-v7a"
}
}
x86 {
ndk {
abiFilters "x86"
}
}
}
}
I already know about split option available on gradle, which generates multiple apk for different cpu architectures.
How can I generate an apk included just armeabi-v7a and x86 architecture?
Step 1: Add the class path dependency to the project level build.gradle file.
buildscript {
repositories {
jcenter()
}
dependencies {
classpath "io.realm:realm-gradle-plugin:3.7.1"
}
}
Step 2: Apply the realm-android plugin to the top of the application level build.gradle file.
apply plugin: 'realm-android'
Step3: Add this to the application’s build.gradle:
realm {
syncEnabled = true;
}
Step4:Add this to the application’s build.gradle:
android{
defaultConfig{
applicatioId "com.android.abi.myapplication"
minSdkVersion 16
targetSdkVersion 25
ndk{
abiFilters "armeabi-v7a", "x86" // Add only required cpu architectures here
}
}
}
and Then sync the gradle and build the apk , will find only library files for armeabi-v7a and x86.
I'm trying to include mupdf as an android library module with precompiled .so binaries into my project.
I have an application module which includes my library module like so.
dependencies {
compile project(':mupdf')
}
In my mupdf library module I have my compiled binaries like so.
mupdf
- jniLibs
- arm64-v8a
libmupdf.so
- armeabi
libmupdf.so
- armeabi-v7a
libmupdf.so
- x86
libmupdf.so
- x86_64
libmupdf.so
My build.gradle for the mupdf library project:
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
android {
compileSdkVersion 23
buildToolsVersion "23.0.1"
defaultConfig {
minSdkVersion 15
targetSdkVersion 23
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
publishNonDefault true
productFlavors {
x86 {
ndk {
abiFilter "x86"
}
}
x64 {
ndk {
abiFilter "x86_64"
}
}
arm {
ndk {
abiFilter "armeabi"
}
}
arm_v7a {
ndk {
abiFilter "armeabi-v7a"
}
}
arm64_v8a {
ndk {
abiFilter "arm64-v8a"
}
}
fat
}
}
dependencies{
compile project(':core')
}
The problem is soon as I add the product flavors my app doesn't compile because it doesn't find any of the java files from the mupdf module.
If I delete the product flavors part from the gradle file it compiles. But it crashes at runtime because it can't resolve/load the libmupdf library.
You can do this without product flavors. Just remove them and call System.loadLibrary("mupdf") on startup/at some point before the library is used. This call will figure out which native lib to use for the current devices architecture.
Indeed I have to build a fat module for the .aar library and apply the productflavors in my app.gradle.
I was missing this in my library's build.gradle
sourceSets.main {
jni.srcDirs = [] // This prevents the auto generation of Android.mk
jniLibs.srcDir 'src/main/jni'
}
Without this no methods were able to be resolved.