Get dependencies from resolve configuration on Gradle 4.1 - android

On previous versions of android plugin for Gradle I could, with my own task, take the path of the jar dependencies using this:
android.libraryVariants.all { variant ->
task "copyDependencies${variant.name.capitalize()}"(type: Copy) {
configurations.compile.files().each { dependency ->
from dependency.path
}
into project.projectDir.path + "/build/libs/${variant.name}"
}
}
But in the latest version of this plugin, compile pass to deprecated and they introduced api and implementation configurations, so when I try to use the previous code, gradle said that:
Resolving configuration 'api' directly is not allowed
Any suggestion for this new change introduced?
UPDATE
I have got a dependencies list and filter for configurations doing this:
android.libraryVariants.all { variant ->
task "copyDependencies${variant.name.capitalize()}"(type: Copy) {
from {
variant.getCompileConfiguration().files().each { dependency ->
configurations.api.getDependencies().each { configDep ->
if (dependency.name.contains(configDep.name)) {
from dependency.path
}
}
}
}
into project.projectDir.path + "/build/libs/${variant.name}"
}
}
But this solution still has problems, in addiction when project B is dependent on project A. Both with this task defined, Gradle doesn't build.

Finally, I found a solution for copy dependencies from a configuration:
android.libraryVariants.all { variant ->
task "copyDependencies${variant.name.capitalize()}"() {
outputs.upToDateWhen { false }
doLast {
println "Executing from in copyDependencies${variant.name.capitalize()}"
variant.getCompileConfiguration().getAllDependencies().each { dependency ->
configurations.api.getDependencies().each { configDep ->
if (dependency.name.contains(configDep.name)) {
println "Dependency detected: " + dependency.name
variant.getCompileClasspath().each { fileDependency ->
if (fileDependency.absolutePath.contains(dependency.name)) {
println fileDependency.absolutePath
copy {
from fileDependency.absolutePath
into project.projectDir.path + "/build/intermediates/bundles/${variant.name}/libs"
exclude '**/classes.jar'
}
}
}
}
}
}
}
}
}

Please see chapter 48.4 of the userguide to find all available configurations and which are to be consumed and / or resolved.

Related

Javadoc with shadow jar

I'm trying to create a Javadoc task in Gradle 7.2/android Gradle plugin 7.1.2 to document android libraries. One of the libraries creates a shadow jar at build time. The problem I'm having is defining the classpath of the Javadoc task.
afterEvaluate { project ->
task androidJavadocs(type: Javadoc) {
def releaseVariant = android.libraryVariants.matching { variant -> variant.name == "release" }.iterator().next()
source = android.sourceSets.main.java.source
releaseVariant.javaCompileProvider.get().classpath.files.each { v -> println "$v" }
println "---"
println project.android.getBootClasspath().each { v -> println "$v" }
println "---"
println files("$buildDir/intermediates/classes/release").files.each { v -> println "$v" }
classpath += project.files(releaseVariant.javaCompileProvider.get().classpath,
project.android.getBootClasspath().join(File.pathSeparator), files("$buildDir/intermediates/classes/release"))
title = null
options.noTimestamp(false)
options.doclet = "com.google.doclava.Doclava"
options.docletpath = configurations.doclava.files.asType(List)
}
}
For the library which uses dagger, this prints:
<snip>/RxAndroidBle/rxandroidble/build/intermediates/compile_r_class_jar/release/R.jar
/Users/nick/.gradle/caches/modules-2/files-2.1/com.jakewharton.rxrelay2/rxrelay/2.1.1/b5cb329f502caba70863749b6559f13eb25b6285/rxrelay-2.1.1.jar
/Users/nick/.gradle/caches/modules-2/files-2.1/io.reactivex.rxjava2/rxjava/2.2.17/a10e8688b858b3cc2ae5850224dd6ddd87652b83/rxjava-2.2.17.jar
/Users/nick/.gradle/caches/modules-2/files-2.1/androidx.annotation/annotation/1.1.0/e3a6fb2f40e3a3842e6b7472628ba4ce416ea4c8/annotation-1.1.0.jar
<snip>/RxAndroidBle/dagger-library-shadow/build/libs/dagger-library-shadow-1.13.0.jar
/Users/nick/.gradle/caches/modules-2/files-2.1/org.reactivestreams/reactive-streams/1.0.3/d9fb7a7926ffa635b3dcaa5049fb2bfa25b3e7d0/reactive-streams-1.0.3.jar
---
/Users/nick/Library/Android/sdk/platforms/android-31/android.jar
/Users/nick/Library/Android/sdk/build-tools/30.0.3/core-lambda-stubs.jar
---
<snip>/RxAndroidBle/rxandroidble/build/intermediates/classes/release
If I have already built the project, this works. But if I haven't, Gradle can't find dagger-library-shadow-1.13.0.jar. This error can be seen here: https://github.com/NRB-Tech/RxAndroidBle/runs/5599933013?check_suite_focus=true
Code with the problem: https://github.com/NRB-Tech/RxAndroidBle/blob/gradle-7/gradle/gradle-mvn-push.gradle#L119-L128
How can I define the classpath so it will only discover the files at build time? Or is there a way to build the shadow jar before evaluating the Javadoc task?

Build jitpack library with multi flavor in Android

I am using Gradle 4.1 with Gradle-Android plugin 3.0.1 on Android Studio 3.2
I have 2 flavors 'production' and 'staging' and I am unable to build my project as a library with different build variants.
app build.gradle:
apply plugin: 'com.android.library'
apply plugin: 'com.github.dcendents.android-maven'
android {
...
productFlavors {
production {
}
staging {
}
}
defaultPublishConfig "productionRelease"
publishNonDefault true
}
if( android.productFlavors.size() > 0 ) {
android.libraryVariants.all { variant ->
if( android.publishNonDefault && variant.name == android.defaultPublishConfig ) {
def bundleTask = tasks["bundle${name.capitalize()}"]
artifacts {
archives(bundleTask.archivePath) {
classifier name.replace('-' + variant.name, '')
builtBy bundleTask
name name.replace('-' + variant.name, '')
}
}
}
...
Then I run: ./gradlew clean install, errors I got is:
Execution failed for task ‘:app:install’.
Could not publish configuration ‘archives’
A POM cannot have multiple artifacts with the same type and classifier. Already have MavenArtifact app:aar:aar:null, trying to add MavenArtifact app:aar:aar:null.
And to get this code to compile, I need to swap android.publishNonDefault with true, otherwise I will get an error of: Cannot get the value of write-only property 'publishNonDefault'
Any suggestions or hint would be really helpful, the aim is to build the library module on jitpack, where we can import it in project with build variants. thanks!
After digging into this for 2 days, and emailing Jitpack support, the issue is because the lib has updated and publishNonDefault is deprecated. you just need to change your app build.gradle to:
apply plugin: 'com.github.dcendents.android-maven'
dependencies {...}
group = 'com.github.your-group'
if (android.productFlavors.size() > 0) {
android.libraryVariants.all { variant ->
if (variant.name.toLowerCase().contains("debug")) {
return
}
def bundleTask = tasks["bundle${variant.name.capitalize()}"]
artifacts {
archives(bundleTask.archivePath) {
classifier variant.flavorName
builtBy bundleTask
name = project.name
}
}
}
}
problem was to create multiple flavors using jitpack
so what I do is, create a variable which stores the flavor name, after that loop through the available variant, pass the flavorBuild to artifact so when u push the code to GitHub and create artifact via jitpack then jitpack create the required implementation based on your build flavor and then u can use it. You need to just change build flavor
publishing {
publications {
def flavorBuild ='production'
android.libraryVariants.all { variant ->
"maven${variant.name.capitalize()}Aar"(MavenPublication) {
from components.findByName("android${variant.name.capitalize()}")
groupId = 'com.examplesdk'
artifactId = 'examplesdk'
version "1.0.0-${variant.name}"
artifact "$buildDir/outputs/aar/examplesdk-${flavorBuild}-release.aar"
pom.withXml {
def dependenciesNode = asNode().appendNode('dependencies')
configurations.api.allDependencies.each { dependency ->
def dependencyNode = dependenciesNode.appendNode('dependency')
dependencyNode.appendNode('groupId', dependency.group)
dependencyNode.appendNode('artifactId', dependency.name)
dependencyNode.appendNode('version', dependency.version)
}
}
}
}
}
repositories{
maven {
url "$buildDir/repo"
}
}
}

Exclude BuildConfig.class and R.class from Android library jar in Gradle

When building an Android Library Project with Gradle, what's the correct way to exclude BuildConfig.class and R.class from the resulting .jar?
Probably the most elegant solution is to add at the end of the library's build.gradle:
afterEvaluate {
generateReleaseBuildConfig.enabled = false
}
To excude R and BuildConfig use this
afterEvaluate {
generateReleaseBuildConfig.enabled = false
generateDebugBuildConfig.enabled = false
generateReleaseResValues.enabled = false
generateDebugResValues.enabled = false
}
Don't forget to clean the project before building
Add a custom task:
task androidReleaseJar(type: Jar, dependsOn: assembleRelease) {
from "$buildDir/intermediates/classes/release/"
exclude '**/BuildConfig.class'
exclude '**/R.class'
exclude '**/R$*.class'
}
Reference:
1.https://github.com/facebook/rebound/blob/master/build.gradle
2.https://github.com/keyboardsurfer/Crouton/blob/master/library/build.gradle
3.https://github.com/SnowdreamFramework/android-log/commit/4297a0244c972e3fcb9042b5e12181b21c33b524
you should get it right after the CompileReleaseSources step.
this worked for me:
task removeBuildConfig(dependsOn: "compileReleaseSources") {
doFirst {
file("$buildDir/intermediates/classes/release/pathToFile/BuildConfig.class").delete()
}
}
This works for me
afterEvaluate {
bundleDebug.dependsOn "removeBuildConfigDebug"
bundleRelease.dependsOn "removeBuildConfigRelease"
}
task removeBuildConfigDebug(dependsOn: "generateDebugBuildConfig") {
doFirst {
file("$buildDir/intermediates/classes/debug/com/amazon/geo/routing/BuildConfig.class").delete()
file("$buildDir/generated/source/buildConfig/debug/com/amazon/geo/routing/BuildConfig.java").delete()
}
}
task removeBuildConfigRelease(dependsOn: "generateReleaseBuildConfig") {
doFirst {
file("$buildDir/intermediates/classes/release/com/amazon/geo/routing/BuildConfig.class").delete()
file("$buildDir/generated/source/buildConfig/release/com/amazon/geo/routing/BuildConfig.java").delete()
}
}
Here is my code, when bundle task added, we can hook it here.
tasks.whenTaskAdded {
if (it.name == 'bundleRelease' || it.name == 'bundleDebug') {
// bundle task is Zip
it.exclude '**/BuildConfig.class', '**/R.class', '**/R$*.class'
}
}

Android AspectJ with Gradle in multimodule project

Is there any way to apply aspectJ pointcuts from library module to whole project? It means that I need attach library to existitng project, and it must intercept method calls from main project with all it dependencies (also including Android SDK methods)
For now I trying to do this by script in build.gradle:
android.libraryVariants.all { variant ->
variant.javaCompile.doLast {
// Find the android.jar and add to iajc classpath
def androidSdk = android.adbExe.parent + "/../platforms/" + android.compileSdkVersion + "/android.jar"
println 'Android SDK android.jar path: ' + androidSdk
def iajcClasspath;
iajcClasspath = androidSdk;
project.rootProject.allprojects.each { proj ->
if (proj.configurations.hasProperty("compile"))
iajcClasspath += ":" + proj.configurations.compile.asPath
// handle aar dependencies pulled in by gradle (Android support library and etc)
tree = fileTree(dir: "${proj.buildDir}/exploded-aar", include: '**/classes.jar')
tree.each { jarFile ->
iajcClasspath += ":" + jarFile
}
}
println 'Classpath for iajc: ' + iajcClasspath
ant.taskdef(resource: "org/aspectj/tools/ant/taskdefs/aspectjTaskdefs.properties", classpath: configurations.ajc.asPath)
println 'ajctPath : ' + configurations.ajc.asPath.toString()
println 'aspectPath : ' + configurations.aspects.asPath.toString()
println 'inpath : ' + configurations.ajInpath.asPath.toString()
ant.iajc(
source: sourceCompatibility,
target: targetCompatibility,
fork: true,
destDir: "${project.buildDir}/classes/${variant.dirName}",
aspectPath: configurations.aspects.asPath,
inpath: configurations.ajInpath.asPath,
sourceRootCopyFilter: "**/*.java",
classpath: iajcClasspath
) {
sourceroots {
android.sourceSets.main.java.srcDirs.each {
pathelement(location: it.absolutePath)
}
// Build config file
pathelement(location: "${project.buildDir}/source/buildConfig/${variant.dirName}")
// Android resources R.***
pathelement(location: "${project.buildDir}/source/r/${variant.dirName}")
}
}
}
}
But pointcuts works only on methods, called from this module. Also, even if I move script to main build.gradle, and replace android.libraryVariants to android.applicationVariants, pointcuts aren't work on attached .jar libraries and modules, but work on gradle dependencies (such as compile 'com.googlecode.mp4parser:isoparser:1.0.1', for example).
And if there no way to do this with AspectJ, maybe there is some other way to intercept method calls in all project from project library? The most important thing is that there should not be any changes in main module code, just in library.
After read document from ant ajcTask, I finally implement with my gradle plugin GradleAndroidAspectJPlugin.
I use the iajc classpath and inpath property to specify which classes(jars) will be compile as classpath or will be re-compile from aspectj compiler.
def aopTask = project.task("compile${buildTypeName}AspectJ") {
doFirst {
project.configurations.aspectsInPath.each {
aspectsInPaths.add(it);
aspectsInPathsAbsolute.add(it.absolutePath);
}
}
doLast {
ant.taskdef(
resource: "org/aspectj/tools/ant/taskdefs/aspectjTaskdefs.properties",
classpath: project.configurations.aspectjTaskClasspath.asPath
)
ant.iajc(
source: project.android.compileOptions.sourceCompatibility,
target: project.android.compileOptions.targetCompatibility,
fork: "true",
destDir: variant.javaCompile.destinationDir,
bootClasspath: project.android.bootClasspath.join(File.pathSeparator),
inpathDirCopyFilter: "java/**/*.class"
) {
classpath {
variant.javaCompile.classpath.each {
if (!aspectsInPathsAbsolute.contains(it)) {
pathElement(location: it)
}
}
}
inpath {
pathElement(location: copyDir)
aspectsInPaths.each {
if (!it.name.startsWith("aspectjrt")) {
pathElement(location: it)
}
}
}
}
}
}

Add dependency to specific productFlavor and buildType in gradle

I'm wondering how to add dependency to specific productFlavor and buildType in gradle.
For example I have productFlavor free and build type release, how can I add a dependency on the assembleFreeRelease task?
I've tried many variants but neither works.
For example I tried:
task('release', dependsOn: assembleProductionRelease) {
}
// error: Could not find property 'assembleProductionRelease' on root project 'app'.
Or:
task('release', dependsOn: 'assembleProductionRelease') {
}
Here there is no error but the task is executed for every flavor and build type, very confusing.
There is built-in support for flavor and buildType dependencies.
dependencies {
flavor1Compile "..."
freeReleaseCompile "..."
}
http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Sourcesets-and-Dependencies
These task are generated dynamically based on your Android plugin configuration. At the time of configuration they are not available to you yet. You can defer the creation of your task in two ways:
Wait until the project is evaluated.
afterEvaluate {
task yourTask(dependsOn: assembleFreeRelease) {
println "Your task"
}
}
Lazily declaring the task dependency as String.
task yourTask(dependsOn: 'assembleFreeRelease') {
println "Your task"
}
Looks like now in version 1 of the android gradle plugin and gradle 2.2+, you can do this with a configuration:
configurations {
freeReleaseCompile
}
android {
...
productFlavors {
free {
...
}
}
}
dependencies {
freeReleaseCompile('myLib#aar')
}
Reference: https://groups.google.com/forum/#!msg/adt-dev/E2H_rNyO6-o/h-zFJso-ncoJ
Muschko's answer didn't work for me, so this is my solution, written and posted by me here
Define the task that should only be executed on a specific buildType/variant/flavor
task doSomethingOnWhenBuildProductionRelease << {
//code
}
It's important to use the "<<" syntax or else the task will automatically be called every time.
Dynamically set the dependency when the tasks are added by gradle
tasks.whenTaskAdded { task ->
if (task.name == 'assembleProductionRelease') {
task.dependsOn doSomethingOnWhenBuildProductionRelease
}
}
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.
Here is another way that I used:
tasks.withType(JavaCompile) {
compileTask ->
def dependedTaskName = "dependedTask_";
if(compileTask.name.contains('Release') {
dependedTaskName += "Release";
}
createTask(dependedTaskName, Exec) {
........
}
compileTask.dependsOn ndkBuildTaskName
}
Another way:
tasks.whenTaskAdded { task ->
if (task.name == 'generateReleaseBuildTypeBuildConfig') {
task.dependsOn doSomethingForReleaseBuild
}
}
The 1st method is dynamic while the second one is simpler.
Update: This solution no longer works as of 12/8/2022
I now get the error message: No such property: productFlavors for class: org.gradle.api.internal.provider.DefaultProperty
All the above answers helped me, but I ended up needing to run a script before the compilation of each flavor's debug + release build. The first argument of the script takes the flavor name.
In order to have this trigger early on, but after the flavor specific task has been created, the key is binding the dependsOn to generateMyflavorDebugBuildConfig.
Here's the script I ended up with, which can be placed at the bottom of app/build.gradle
// When a task is active, loop through all app flavors, and see if the
// task.name matches the earliest task we can set up the dependsOn
// loop through all flavors, and create a task that does the work we want to do
// before anything else
android.productFlavors.all { flavor ->
task("${flavor.name}RunCommandBefore", type: Exec) {
workingDir "$projectDir/../.."
commandLine 'sh', 'scripts/run_thing.sh', "${flavor.name}"
}
}
// when tasks created, loop through and as early as we can, bind the
// RunCommandBefore task we created above
tasks.whenTaskAdded { task ->
def taskName = task.name
// loop through all flavors
android.productFlavors.all { flavor ->
def flavorName = flavor.name
// loop through release types so that this happens for debug and release
['debug', 'release'].each { releaseType ->
// generateMyflavorDebugBuildConfig is the earliest task we're able
// to set up the dependsOn to make sure that our script runs
if (taskName.toLowerCase() == "generate${flavorName.toLowerCase()}${releaseType}buildconfig") {
// now myflavorRunCommandBefore will run before
// generateMyflavorDebugBuildConfig
tasks."$taskName".dependsOn "${flavorName}RunCommandBefore"
}
}
}
}
Hope this helps someone! Amazing how difficult Android makes running a simple script first thing..

Categories

Resources