How to define which so file to use in gradle packaging options - android

I'm trying to build my android application which contains two libraries. In one library, a newer version of ffmpeg is being used. In another library, a dependency of that library is using an older version of ffmpeg. Trying to use pickFirst in the package options picks the WRONG libary. Is there ANY possible way to fix this, or is this just a limitation of Gradle?
Here is the error I am getting
Execution failed for task ':app:mergeDebugNativeLibs'.
> A failure occurred while executing com.android.build.gradle.internal.tasks.MergeNativeLibsTask$MergeNativeLibsTaskWorkAction
> 2 files found with path 'lib/arm64-v8a/libavcodec.so' from inputs:

I was actually able to solve this issue myself.
Essentially, what I needed to do was to add a task to my gradle file that would intercept the merge libs task before it runs and delete the unwanted libraries:
tasks.whenTaskAdded((tas -> {
if (tas.name.contains("mergeDebugNativeLibs")) {
tasks.named("mergeDebugNativeLibs") {it
doFirst {
java.nio.file.Path notNeededDirectory = it.externalLibNativeLibs
.getFiles()
.stream()
.filter(file -> file.toString().contains("Metadata"))
.findAny()
.orElse(null)
.toPath();
Files.walk(notNeededDirectory).forEach(file -> {
if (file.toString().contains("libav") || file.toString().contains("libsw")) {
Files.delete(file);
}
});
}
}
}
if (tas.name.contains("mergeReleaseNativeLibs")) {
tasks.named("mergeReleaseNativeLibs") {it
doFirst {
java.nio.file.Path notNeededDirectory = it.externalLibNativeLibs
.getFiles()
.stream()
.filter(file -> file.toString().contains("Metadata"))
.findAny()
.orElse(null)
.toPath();
Files.walk(notNeededDirectory).forEach(file -> {
if (file.toString().contains("libav") || file.toString().contains("libsw")) {
Files.delete(file);
}
});
}
}
}
}))
In this case, the unwanted libraries are in the FFmpegMetadataMediaRetriever folder/library.

You can exclude the library from the dependency that has the "wrong" version.
dependencies {
implementation("some-library") {
exclude(group = "com.example.imgtools", module = "native")
}
}

Related

How to exclude (ignore) android build variants in gradle kts

During migration build script from groovy to kotlin I met problem with excluding build variants.
In groovy it's pretty easy:
android {
variantFilter { variant ->
if (variant.name == "lorempisum") {
setIgnore(true)
}
}
}
but in kotlin similar thing does not work. It seems to be ok in android studio, but during compilation it throws Unresolved reference: isIgnore
android {
variantFilter {
if (buildType.name == "lorempisum") {
isIgnore = true
}
}
}
from the other side this reports Unresolved reference: setIgnore, but works during compilation
android {
variantFilter {
if (buildType.name == "lorempisum") {
this.setIgnore(true)
}
}
}
Anybody has idea how do it in right way?
I'm using kotlin 1.3.72, android studio 4.0.1 and gradle 6.5.1
---- EDIT ----
I fix example ignore -> isIgnore in second block, it also not works
Soultion is ignore = true with a little deatil.
This works if you keep in top-level build.gradle.kts this line:
classpath("com.android.tools.build:gradle:4.0.1")
and not really only on buildSrc on this:
implementation("com.android.tools.build:gradle:4.0.1")
Use the beforeVariants block:
androidComponents {
beforeVariants { variantBuilder ->
// To check for a certain build type, use variantBuilder.buildType == "<buildType>"
if (variantBuilder.productFlavors.containsAll(listOf("api" to "minApi21", "mode" to "demo"))) {
// Gradle ignores any variants that satisfy the conditions above.
variantBuilder.enabled = false
}
}
}
You should first update to the last version of android studio and the plugins.
And try this
variantFilter {
this.ignore = name == "lorempisum"
}

How to get/print resolved dependencies group, name and version from Maven using Gradle?

Suppose we have a task in Gradle, that prints artifact group, name and version like this:
task printDependencies {
project.configurations.each { conf ->
conf.dependencies.each { dep ->
println "${dep.group}:${dep.name}:${dep.version}"
}
}
}
But what I would to do, is to print/get this information not only for declared dependencies, but to all resolved dependencies too. How to do this in Gradle?
One solution is to define task like this:
task printDependencies {
project.configurations.compile.resolvedConfiguration.resolvedArtifacts.each { id->
println id
}
}
The line format that will be printed will be like e.g:
play-services-ads-16.0.0.aar (com.google.android.gms:play-services-ads:16.0.0)
Which contains all needed info (file, group:name:version)

Gradle: Filter and execute tasks from a given task

I want to run ./gradlew verifyProjectDebug to run a subset of verification tasks.
verifyProjectDebug tries to extract a subset of the tasks in the project and execute them.
static def isValidTask(String name) {
def isLint = name.matches("lint.*Debug")
def isKtlint = name.matches("ktlint.*Debug.*Check")
def isUnitTest = name.matches("test((?!Prod|Staging).)*DebugUnitTest")
return (isLint || isKtlint || isUnitTest) && !name.contains("Prod")
}
task verifyProjectDebug() {
group = "verification"
description = "Runs lint, ktlint and tests for all debug non-production variants"
doLast {
getSubprojects()
.collect { it.tasks }
.flatten()
.findAll { isValidTask(it.name) }
.each { it.execute() }
}
}
Unfortunately, calling .execute() on a task does not invoke its dependencies so some of the tasks fails because its dependencies were not invoked.
Is there any way in gradle I can achieve this. Thanks a ton!
execute is a method of the Task class. You're trying to bypass Gradle build system. Executing tasks is not a simple matter or creating an instance and calling execute. Gradle handles dependency injection, caching, input & output processing, all kinds of stuff. So leverage Gradle.
1)
Create one lifecycle task that is the parent task for everything you want to execute.
final def verifyProject = tasks.register("verifyProject")
Lifecycle task is a task that doesn't do any work, it only depends on other tasks.
2)
You can only reference tasks that are already created. For example you can't reference lint task of the debug variant until the debug variant is created.
Process each variant when it's created, find all tasks you want executed and connect them to the master task.
android.applicationVariants.all {
final def cappedVariantName = name.capitalize()
// For every build variant that has build type "debug"...
if (variant.buildType == "debug") {
verifyProject.configure {
dependsOn("lint$cappedVariantName")
dependsOn("ktlint$cappedVariantName")
dependsOn("test${cappedVariantName}UnitTest")
}
}
}
Please verify the names of tasks you want executed.
Now ehen you run gradlew verifyProject all the tasks this task depends on will get executed. You're in charge of the dependencies.
If you want to use this in an Android library module replace android.applicationVariants with android.libraryVariants.
The code follows Task Conviguration Avoidance. This means the tasks you defined won't be configured unless you specifically invoke them. This should save resources (CPU & memory) when running a build.
3)
To do this automatically for all modules pick one or both of the following, and put to to your root project build.gradle.
subprojects { project ->
project.plugins.whenPluginAdded { plugin ->
// For all libraries and only libraries:
if (plugin instanceof com.android.build.gradle.LibraryPlugin) {
project.android.libraryVariants.all { variant ->
// See above.
}
}
// For all apps and only apps:
if (plugin instanceof com.android.build.gradle.AppPlugin) {
project.android.applicationVariants.all { variant ->
// See above.
}
}
}
}
Putting this in the project level gradle file did the trick.
task verifyDebugProjects() {
group = "verification"
description = "Runs lint, ktlint and tests for all debug non-production variants"
}
static def isValidVerifyDebugTask(String name) {
def isLint = name.matches("lint.*Debug")
def isKtlint = name.matches("ktlint.*Debug.*Check")
def isUnitTest = name.matches("test((?!Prod|Staging).)*DebugUnitTest")
return (isLint || isKtlint || isUnitTest) && !name.contains("Prod")
}
gradle.projectsEvaluated {
getSubprojects()
.collect { it.tasks }
.flatten()
.findAll { isValidVerifyDebugTask(it.name) }
.each { verifyDebugProjects.dependsOn it }
}

How To change Android App Bundles name (app.aab) to reflect App version and build type

While I'm building an APK I can change APK name in build.gradle script, like that:
android.applicationVariants.all { variant ->
if (variant.buildType.name != "debug") {
variant.outputs.all {
outputFileName = "${variant.applicationId}-v${variant.versionName}-${variant.name}.apk"
}
}
}
An I'll have something like this com.myapp.package-v1.x.x-release
Is there a way to do something similar with Android App Bundles, it is not convenient to always have app.aab
I have come up with the solution of how to achieve this with Gradle.
First, we have to create in App build.gradle file a Gradle task that will rename the original app.aab on copy. This method is described here.
Then for conveniance, we will add another method that will delete old app.aab file.
android{
.....
}
dependencies{
.....
}
.....
task renameBundle(type: Copy) {
from "$buildDir/outputs/bundle/release"
into "$buildDir/outputs/bundle/release"
rename 'app.aab', "${android.defaultConfig.versionName}.aab"
}
task deleteOriginalBundleFile(type: Delete) {
delete fileTree("$buildDir/outputs/bundle/release").matching {
include "app.aab"
}
}
In this example the output file name will be something like 1.5.11.aab
Then we can combine those tasks together into publishRelease task which will be used for publishing the App:
task publishRelease(type: GradleBuild) {
tasks = ['clean', 'assembleRelease', 'bundleRelease', 'renameBundle', 'deleteOriginalBundleFile']
}
android {
...
this.project.afterEvaluate { project ->
project.tasks.each { task ->
if (task.toString().contains("packageReleaseBundle")) {
task.doLast {
copy {
from "$buildDir/outputs/bundle/release"
into "${projectDir}/../../../../publish/android/"
rename "app.aab", "${android.defaultConfig.versionName}.aab"
}
}
}
}
}
}

In Android/Gradle how to define a task that only runs when building specific buildType/buildVariant/productFlavor (v0.10+)

Android Plugin for Gradle generates for every BuilType/Flavor/BuildVariant a task. The problem is that this task will be generated dynamically and thus won't be available as a dependency when defining a task like this:
task myTaskOnlyForDebugBuildType(dependsOn:assembleDebug) {
//do smth
}
A proposed workaround from this answer would be this
task myTaskOnlyForDebugBuildType(dependsOn:"assembleDebug") {
//do smth
}
or this
afterEvaluate {
task myTaskOnlyForDebugBuildType(dependsOn:assembleDebug) {
//do smth
}
}
But both didn't work for me.
Here is a full example on how to do this inspired by this post: (android plugin v.0.9.2 and gradle 1.11 at the time of writing)
We are going to define a task that only runs when we build a "debugCustomBuildType"
android {
...
buildTypes {
debugCustomBuildType {
//config
}
}
}
Define the task that should only be executed on a specific builtType/variant/flavor
task doSomethingOnWhenBuildDebugCustom {
doLast {
//task action
}
}
Dynamically set the dependency when the tasks are added by gradle
tasks.whenTaskAdded { task ->
if (task.name == 'generateDebugCustomBuildTypeBuildConfig') {
task.dependsOn doSomethingOnWhenBuildDebugCustom
}
}
Here we use the "generateBuildConfig" task, but you can use any task that works for you (also see official docs)
processManifest
aidlCompile
renderscriptCompile
mergeResourcess.
mergeAssets
processResources
generateBuildConfig
javaCompile
processJavaResources
assemble
Don't forget to use the buildTypeSpecific task (generateBuildConfig vs. generateDebugCustomBuildTypeBuildConfig)
And that's it. It's a shame this workaround isn't well documented because for me this seems like one of the simplest requirements for a build script.
I achieved it like this:
android {
ext.addDependency = {
task, flavor, dependency ->
def taskName = task.name.toLowerCase(Locale.US)
if (taskName.indexOf(flavor.toLowerCase(Locale.US)) >= 0) {
task.dependsOn dependency
}
}
productFlavors {
production {
}
other
}
task theProductionTask << {
println('only in production')
}
tasks.withType(JavaCompile) {
compileTask -> addDependency compileTask, "production", theProductionTask
}
}
To be frank, I don't which locale is used to generate names for compile taks so toLowerCase(Locale.US) may be counterproductive.
This is the only solution that worked for me:
afterEvaluate {
if (project.hasProperty("preReleaseBuild")){
tasks.preReleaseBuild.dependsOn bundleJSRelease
} else {
tasks.preDebugBuild.dependsOn bundleJSDebug
}
}
tasks.whenTaskAdded { task ->
if (task.name.contains("assembleRelease")) {
task.getDependsOn().add({
// add your logic here
})
}
}

Categories

Resources