Gradle multi-project dependency doesn't include - android

I'm trying to set up a Gradle build to build and include an NDK static library into an Android project, but can't get the inclusion to work. I'm running Gradle 2.8 on a Linux system directly from the command line (AndroidStudio, Android SDK, and Android NDK are installed, but I'm not using them directly in the sample below). I've boiled down my issue to the following purely Gradle-based sample, and was hoping someone more versed in Gradle could show me the way.
Let's say I have the following directory structure:
gradle_test
mySubLibs
common.gradle
mySubA
build.gradle
settings.gradle
myProjA
common.gradle
Projects
Android
build.gradle
settings.gradle
My library sub projects are under the mySubLibs directory. I'll have gradle_test be the root project directory to get around the fact that Gradle sucks with dealing with arbitrary paths (another gripe for another time).
First, I set up my library to build with "assembleRelease" and "clean" tasks. In reality it will call to command-line compilers to work, but for testing Gradle this simplification will do.
gradle_test/mySubLibs/common.gradle
buildscript {
}
task NDKBuildReleaseLib(type: Exec) {
println "Running NDKBuildReleaseLib for " + rootProject.name
commandLine 'touch', 'obj/local/myOutput.a'
}
task NDKBuildCleanLib(type: Exec) {
println "Running NDKBuildCleanLib for " + rootProject.name
commandLine 'rm', '../libs/myOutput.a', 'obj/local/myOutput.a'
}
task copyLibs(type:Copy) {
from 'obj/local'
into '../libs'
include '*.a'
outputs.upToDateWhen {false}
}
NDKBuildReleaseLib.finalizedBy copyLib
gradle_test/mySubLibs/mySubA/build.gradle
apply from: '../common.gradle'
configurations.create('default')
allprojects {
task assembleRelease(dependsOn: NDKBuildReleaseLib) << {}
task clean(dependsOn: NDKBuildCleanLib) << {}
}
gradle_test/mySubLibs/mySubA/settings.gradle
rootProject.name = "mySubA"
include ':'
This works fine. I can call "gradle assembleRelease" from the mySubA directory, and it builds my library file (the build being simulated by running "touch" to create a file). Now let's create a similar setup for the main project.
gradle_test/myProjA/common.gradle
buildscript {
}
task NDKBuildRelease(type: Exec) {
println "Running NDKBuildRelease for " + rootProject.name
commandLine 'touch', 'obj/local/myProjOutput.a'
}
task NDKBuildClean(type: Exec) {
println "Running NDKBuildClean for " + rootProject.name
commandLine 'rm', 'obj/local/myProjOutput.a'
}
gradle_test/myProjA/Projects/Android/build.gradle
apply from: '../../common.gradle'
configurations.create('default')
dependencies {
// default project(':mySubLibs:mySubA')
}
allprojects {
task assembleRelease(dependsOn: NDKBuildRelease) << {
println "Assemble Release for " + project.name
}
task clean(dependsOn: NDKBuildClean) << {
println "Clean for " + project.name
}
}
gradle_test/myProjA/Projects/Android/settings.gradle
rootProject.projectDir = new File(settingsDir, '../../..')
rootProject.name = "myTestBase"
include ':'
include 'mySubLibs:mySubA'
include 'myProjA:Projects:Android'
This mostly works, except that it is not causing the sub project to build. So my questions are:
How do I get the sub-project to build? If I uncomment the line above in the "dependencies" section, I get an undefined token error. Almost all samples dealing with dependencies use "compile" instead of "default" there, but since this example doesn't have a compile configuration created by the java plugin inclusion, I can't do that (nor did it work when I tried that). I know the sub project is being evaluated from "gradle --info", but it doesn't run.
How would I specify an arbitrary task to build the sub-project? In the above examples, I've set both up to build off a task named "assembleRelease". How could I call "assembleRelease" to build the project, but have a task called "buildMySubLib" called to build the library project?
The end goal is to have multiple library projects called to each build their own static library and copy it to a known common location, then for the main project to include all the static lib files from that common location in its own compile and link stage. It seems silly to have to set up a task for each library in the main project that uses an Exec task to recursively call the command line version of "gradle" for each library project.
Thanks for any insight. So far Gradle is proving to be more hindrance than helpful in trying to set up what should actually be a pretty common build scenario.

Related

Android studio / Gradle javadoc task

I've been struggling to setup a gradle task to generate Javadocs for my Android library, but when there are external dependencies to other libraries, doc generation fails. This seems to be a common task, but somehow there doesn't seem to be an easy solution, as for example this answer will reveal (re-generating exploded-aar manually is a bit absurd, and also, on Android Studio 3.0 even that doesn't work anymore due to the new dependency directives).
However, I have noticed that generating Javadoc through the Android Studio GUI (Tools menu) works just fine - dependencies to other libraries are resolved etc. So how does this work - does this menu not utilize a gradle task for generating Javadoc?
Since I need to generate Javadoc using gradle as part of CI I find it very frustrating that there is no documented way of getting it to work, while there is a way that works through the menues. Doesn't the Android Studio Tools -> Generate Javadoc menu in turn use a gradle task? Since dependencies are listed with gradle files, and the Javadoc tools menu apparently is able to resolve those dependencies - how is it implemented? How does it source the jars embedded in the dependant aar libraries, etc? How can it be used stand-alone and not though the Android Studio GUI?
Maybe you have got the solution to this. Just in case not, below is how I generate API doc for my Jenkins CI.
task generateApiDoc() {
group "reporting"
description "Generates Javadoc."
}
android.libraryVariants.all { variant ->
// Only consider release
if (variant.buildType.name == "release") {
def task = project.tasks.create("generate${variant.name.capitalize()}Javadoc", Javadoc) {
group "ApiDoc"
description "Generates Javadoc for $variant.name."
// Source files from the variant
source = variant.javaCompiler.source
// Classpath from the variant + android.jar
classpath = variant.javaCompiler.classpath + files(prj.android.getBootClasspath()) + files("$buildDir/intermediates/classes/release")
/* add the excluded packages */
exclude "**/R**"
exclude "**/BuildConfig*"
options.windowTitle = "My Library"
options.memberLevel = JavadocMemberLevel.PROTECTED
options.linkSource false
options.author = true
//options.links("http://docs.oracle.com/javase/7/docs/api/", "http://d.android.com/reference");
failOnError false
}
task.dependsOn assemble
generateApiDoc.dependsOn task
}
}
Then run below gradle commands to get your api doc in place of "$buildDir/docs".
./gradlew assembleRelease
./gradlew generateApiDoc
Edit for Gradle Plugin 3.4.1
android.libraryVariants.all { variant ->
def task = project.tasks.create("generate${variant.name.capitalize()}Javadoc", Javadoc) {
title "API Documentation (${project.android.defaultConfig.versionName})"
group "ApiDoc"
description "Generates Javadoc for $variant.name."
// Source files from the variant
source = variant.sourceSets.collect { it.java.sourceFiles }.inject { m, i -> m + i }
// To fix issue: Error: Can not create variant 'android-lint' after configuration ': library: debugRuntimeElements' has been resolved
doFirst {
classpath = project.files(variant.javaCompileProvider.get().classpath.files,
project.android.getBootClasspath())
}
if (JavaVersion.current().isJava8Compatible()) {
options.addStringOption('Xdoclint:none', '-quiet')
}
exclude "**/R"
exclude "**/R.**"
exclude "**/R\$**"
exclude "**/BuildConfig*"
if (JavaVersion.current().isJava8Compatible()) {
options.addStringOption('Xdoclint:none', '-quiet')
}
options.windowTitle = "API Documentation (${project.android.defaultConfig.versionName})"
options.memberLevel = JavadocMemberLevel.PROTECTED
options.linkSource false
options.author = false
failOnError true
}
task.dependsOn "assemble${variant.name.capitalize()}"
generateApiDoc.dependsOn task
}
I use a gradle task that just executes a bash script file, with a single (pretty long) javadoc command.
What you can do is run the Javadoc generation from Android Studio once, then copy the executed javadoc command from the Studio log, with all the right parameters, and automate the execution of the same command in your gradle.
The tool to generate java style documentation is called javadoc and it comes installed in every JDK. You can configure which classes or packages you want to be included, which ones should be excluded and many other options. Type javadoc in a terminal where a JDK is available and you'll get an idea. See also https://docs.oracle.com/javase/9/javadoc/javadoc.htm#JSJAV-GUID-7A344353-3BBF-45C4-8B28-15025DDCC643
After you get to your optimal configuration, you can include a javadoc step in your CI.

How to change path for release APK artifact?

How can I configure a Gradle Android project so that a release APK built by the IDE is saved to a path of my choosing (eg the project root) rather than buried deep in the build folder?
I've added this to the defaultConfig section of the app build file to sensibly name the APK and it works well, but how can I specify where it goes, or move it post build completion?
archivesBaseName = "AppName-v$versionName" // AppName-v1.2.3-release.apk
UPDATE:
I created a task in the app-level Gradle build file that successfully copies the release APK, if I run the Gradle task manually:
task copyReleaseApk(type: Copy) {
from 'build/outputs/apk'
into '..' // project root, one-level above "app"
include '**/*release.apk'
}
But I have not yet found a way to make the task run automatically after the last build task. I tried this:
assembleRelease.finalizedBy(copySupportFiles)
But that results in "Could not get unknown property 'assembleRelease' for object of type com.android.build.gradle.AppExtension."
I also tried this:
assembleRelease.finalizedBy(copySupportFiles)
It appears not to do anything.
This worked (in the android tag of the app build.gradle file). The afterEvaluate seems to be required in order to refer to tasks like packageRelease that don't initially exist.
task copyReleaseApk(type: Copy) {
from 'build/outputs/apk'
into '..' // folder above the app folder
include '**/*release.apk'
}
afterEvaluate {
packageRelease.finalizedBy(copyReleaseApk)
}
It can be defined in the project's root build.gradle:
allprojects {
buildDir = "/path/to/build/${rootProject.name}/${project.name}"
}

How to Exclude Duplicate C Shared Libraries (.so) in a Multi-Project Android Build?

I get a "duplicate files" conflict when building a parent project with two library modules, which make use of the same libc++_shared.so shared library.
(NOTE: Please do not consider this a "duplicate question". I have read several related posts, which have helped me get this far. However, no posts have provided an answer that works in my case involving NDK artifacts.)
The build was working correctly when I only had 1 such library module. The addition of the second library module is now creating the conflict.
Consider the following project structure: 1 parent project, 2 "child" projects - but each project is located at the same directory level (i.e. Not nested hierarchically)
ProjectA/ (Parent)
LibraryModuleA1/
build/exploded-aar/com.package.name/
LibraryModuleB1/<version>/jni/armeabi-v7a/libc++_shared.so
LibraryModuleC1/<version>/jni/armeabi-v7a/libc++_shared.so
build.gradle (bgA1)
Test_APK_Module A1T/
build.gradle (bgA1T)
build.gradle (bgPA)
ProjectB/
LibraryModuleB1/ (Uses NDK)
build/lib/armeabi-v7a/libc++_shared.so
build.gradle (bgB1)
build.gradle (bgPB)
ProjectC/
LibraryModuleC1/ (Uses NDK)
build/lib/armeabi-v7a/libc++_shared.so
build.gradle (bgC1)
build.gradle (bgPC)
Library Module A1 depends on both Library Modules B1 & C1.
A1 -> B1
A1 -> C1
Projects B and C both have NDK-based code and build/test correctly. Both depend on the libc++_shared.so shared library.
However, when building Project A, I get the following error during the :LibraryModuleA1:packageDebugTest task:
Error: duplicate files during packaging of APK /ProjectA/LibraryModuleA1/build/apk/LibraryModuleA1-debug-test-unaligned.apk
Path in archive: lib/armeabi-v7a/libc++_shared.so
Origin 1: /ProjectA/LibraryModuleA1/build/exploded-aar/com.package.name/LibraryModuleB1/<version>/jni/armeabi-v7a/libc++_shared.so
Origin 2: /ProjectA/LibraryModuleA1/build/exploded-aar/com.package.name/LibraryModuleC1/<version>/jni/armeabi-v7a/libc++_shared.so
You can ignore those files in your build.gradle:
android {
packagingOptions {
exclude 'lib/armeabi-v7a/libc++_shared.so'
}
}
* What went wrong:
Execution failed for task ':LibraryModuleA1:packageDebugTest'.
> Duplicate files copied in APK lib/armeabi-v7a/libc++_shared.so
File 1: /ProjectA/LibraryModuleA1/build/exploded-aar/com.package.name/LibraryModuleC1/<version>/jni/armeabi-v7a/libc++_shared.so
File 2: /ProjectA/LibraryModuleA1/build/exploded-aar/com.package.name/LibraryModuleC1/<version>/jni/armeabi-v7a/libc++_shared.so
:LibraryModuleA1:packageDebugTest FAILED
What I've Tried So Far
I attempted to add the suggested closure to my build.gradle file, but which build.gradle file do I add it to? I have added the closure to bgA1, bgB1, and bgC1 (one at a time), with no success.
The suggested closure says to use exclude 'lib/armeabi-v7a/libc++_shared.so'. Each "child" library module builds the libc++_shared.so file under the build/lib path. However, I noticed that the parent library module copies the libc++_shared.so file under jni/armeabi-v7a/libc++_shared.so inside the build/exploded-aar directory structure. (See above) Should the closure instead read exclude 'jni/armeabi-v7a/libc++_shared.so (i.e. jni vs. lib)?
Since I am using Gradle plugin 0.9.1, I tried using pickFirst in place of exclude, but that wasn't successful either.
Can someone help determine how I should configure the `packagingOptions' closure for my given case?
Thank you for your help!
I ran into the same problem and had no luck with exclude or pickFirst. So I used a somewhat ugly workaround. The idea is to create a 'native-libs' folder in the build directory of the main project, copy all required *.so files from ndk library projects there and then tell the build system to package those libs in the apk.
In my main project (the app project), I explicitely define the list of modules that contain ndk codes on which I depend
// Ndk stuff. We have to explicitely manage our NDK dependencies
ext.jniProjects = [project(':ndklib1'), project(':ndklib2'), project(':ndklib3')]
apply from: '../depend_ndk.gradle'
And then, 'depend_ndk.gradle' is a gradle external script that contains
// Build helper for projects that depends on a native library with a NDK part
// Define the list of ndk library you depend on in project main file :
// ext.jniProjects = [project(':ndklib1')]
// apply from : 'depend_ndk.gradle'
buildscript {
repositories {
jcenter()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:0.12.+'
}
}
import com.android.build.gradle.tasks.PackageApplication
// As a workaround, we create a new 'native-libs' folder in the current project and
// copy all the .so we depend on into it
def ndkLibsDir = new File(buildDir, 'native-libs')
ndkLibsDir.mkdir()
task copyDependingNativeLibs(type: Copy) {
// Doc for copy http://www.gradle.org/docs/current/dsl/org.gradle.api.tasks.Copy.html
println 'jniProjects ' + jniProjects
jniProjects.each {
from(new File(it.buildDir, 'native-libs')) {
include '**/*.so'
}
}
into ndkLibsDir
}
tasks.withType(PackageApplication) { pkgTask ->
pkgTask.jniFolders = new HashSet<File>()
pkgTask.jniFolders.add(ndkLibsDir)
pkgTask.dependsOn copyDependingNativeLibs
}

How can I set a System.env variable in Android Studio?

In my build.gradle file in Android Studio using NDK, I have the following code. The app builds if I run it from the terminal with "./gradlew --assembleDebug" since I have set the path for ANDROID_NDK_HOME to /Users/chenige/Desktop/android-ndk-r9, but it will not build from inside Android Studio. From inside Android Studio, System.env.ANDROID_NDK_HOME is "null". Does anybody know why/how I can fix this?
task buildNative(type: Exec) {
if (System.env.ANDROID_NDK_HOME != null) {
def ndkBuild = new File(System.env.ANDROID_NDK_HOME, 'ndk-build')
commandLine ndkBuild
} else {
doLast {
println '##################'
println 'Skipping NDK build'
println 'Reason: ANDROID_NDK_HOME not set.'
println '##################'
}
}
}
}
Android Studio doesn't read environment variables, so this approach won't work. Also, using the projectDir scheme in settings.gradle will probably cause problems. Android Studio has a limitation that all of its modules need to be located underneath the project root. If you have libraries that are used in multiple projects and they can't be placed under a single project root, the best advice is to have them publish JARs or AARs to a local Maven repository that individual projects can pick up.
read more Environment variable in settings.gradle not working with Android Studio
It works for me with the follwoing steps:
Set your variable in Windows
Reboot
reach it in gradle build: "$System.env.MYVARIABLE"

How to export library to Jar in Android Studio?

I have downloaded some library sources and would like to export it as a Jar file using
Android Studio. Is there a way to export to jar file using Android studio ?
edit:
The library I want to export as jar is an Android library.
It's called "StandOut" and can be downloaded from GitHub.
https://github.com/pingpongboss/StandOut
It is not possible to export an Android library as a jar file. It is possible, however, to export it as aar file. Aar files being the new binary format for Android libraries. There's info about them in Google I/O, the New Build System video.
First, build the library in Android Studio or from command line issuing gradle build from your library's root directory.
This will result in <yourlibroot>/libs/build/yourlib.aar file.
This aar file is a binary representation of your library and can be added to your project instead of the library as a dependency project.
To add aar file as a dependency you have to publish it to the maven central or to your local maven repository, and then refer the aar file in your project's gradle.build file.
However, this step is a bit convoluted. I've found a good explanation how to do so here:
http://www.flexlabs.org/2013/06/using-local-aar-android-library-packages-in-gradle-builds
I was able to build a library source code to compiled .jar file, using approach from this solution:
https://stackoverflow.com/a/19037807/1002054
Here is the breakdown of what I did:
1. Checkout library repository
In may case it was a Volley library
2. Import library in Android Studio.
I used Android Studio 0.3.7. I've encountered some issues during that step, namely I had to copy gradle folder from new android project before I was able to import Volley library source code, this may vary depending on source code you use.
3. Modify your build.gradle file
// If your module is a library project, this is needed
//to properly recognize 'android-library' plugin
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:0.6.3'
}
}
apply plugin: 'android-library'
android {
compileSdkVersion 17
buildToolsVersion = 17
sourceSets {
main {
// Here is the path to your source code
java {
srcDir 'src'
}
}
}
}
// This is the actual solution, as in https://stackoverflow.com/a/19037807/1002054
task clearJar(type: Delete) {
delete 'build/libs/myCompiledLibrary.jar'
}
task makeJar(type: Copy) {
from('build/bundles/release/')
into('build/libs/')
include('classes.jar')
rename ('classes.jar', 'myCompiledLibrary.jar')
}
makeJar.dependsOn(clearJar, build)
4. Run gradlew makeJar command from your project root.
I my case I had to copy gradlew.bat and gradle files from new android project into my library project root.
You should find your compiled library file myCompiledLibrary.jar in build\libs directory.
I hope someone finds this useful.
Edit:
Caveat
Althought this works, you will encounter duplicate library exception while compiling a project with multiple modules, where more than one module (including application module) depends on the same jar file (eg. modules have own library directory, that is referenced in build.gradle of given module).
In case where you need to use single library in more then one module, I would recommend using this approach:
Android gradle build and the support library
Since Android Studio V1.0 the jar file is available inside the following project link:
debug ver: "your_app"\build\intermediates\bundles\debug\classes.jar
release ver: "your_app"\build\intermediates\bundles\release\classes.jar
The JAR file is created on the build procedure,
In Android Studio GUI it's from Build->Make Project and from CMD line it's "gradlew build".
Include the following into build.gradle:
android.libraryVariants.all { variant ->
task("generate${variant.name}Javadoc", type: Javadoc) {
description "Generates Javadoc for $variant.name."
source = variant.javaCompile.source
ext.androidJar = "${android.plugin.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar"
classpath = files(variant.javaCompile.classpath.files) + files(ext.androidJar)
}
task("javadoc${variant.name}", type: Jar) {
classifier = "javadoc"
description "Bundles Javadoc into a JAR file for $variant.name."
from tasks["generate${variant.name}Javadoc"]
}
task("jar${variant.name}", type: Jar) {
description "Bundles compiled .class files into a JAR file for $variant.name."
dependsOn variant.javaCompile
from variant.javaCompile.destinationDir
exclude '**/R.class', '**/R$*.class', '**/R.html', '**/R.*.html'
}
}
You can then execute gradle with: ./gradlew clean javadocRelease jarRelease which will build you your Jar and also a javadoc jar into the build/libs/ folder.
EDIT: With android gradle tools 1.10.+ getting the android SDK dir is different than before. You have to change the following (thanks Vishal!):
android.sdkDirectory
instead of
android.plugin.sdkDirectory
I was able to export a jar file in Android Studio using this tutorial:
https://www.youtube.com/watch?v=1i4I-Nph-Cw
"How To Export Jar From Android Studio "
I updated my answer to include all the steps for exporting a JAR in Android Studio:
1) Create Android application project, go to app->build.gradle
2) Change the following in this file:
modify apply plugin: 'com.android.application' to apply plugin: 'com.android.library'
remove the following: applicationId, versionCode and versionName
Add the following code:
// Task to delete old jar
task deleteOldJar(type: Delete){
delete 'release/AndroidPlugin2.jar'
}
// task to export contents as jar
task exportJar(type: Copy) {
from ('build/intermediates/bundles/release/')
into ('release/')
include ('classes.jar')
rename('classes.jar', 'AndroidPlugin2.jar')
}
exportJar.dependsOn(deleteOldJar, build)
3) Don't forget to click sync now in this file (top right or use sync button).
4) Click on Gradle tab (usually middle right) and scroll down to exportjar
5) Once you see the build successful message in the run window, using normal file explorer go to exported jar using the path: C:\Users\name\AndroidStudioProjects\ProjectName\app\release
you should see in this directory your jar file.
Good Luck :)
Here's yet another, slightly different answer with a few enhancements.
This code takes the .jar right out of the .aar. Personally, that gives me a bit more confidence that the bits being shipped via .jar are the same as the ones shipped via .aar. This also means that if you're using ProGuard, the output jar will be obfuscated as desired.
I also added a super "makeJar" task, that makes jars for all build variants.
task(makeJar) << {
// Empty. We'll add dependencies for this task below
}
// Generate jar creation tasks for all build variants
android.libraryVariants.all { variant ->
String taskName = "makeJar${variant.name.capitalize()}"
// Create a jar by extracting it from the assembled .aar
// This ensures that products distributed via .aar and .jar exactly the same bits
task (taskName, type: Copy) {
String archiveName = "${project.name}-${variant.name}"
String outputDir = "${buildDir.getPath()}/outputs"
dependsOn "assemble${variant.name.capitalize()}"
from(zipTree("${outputDir}/aar/${archiveName}.aar"))
into("${outputDir}/jar/")
include('classes.jar')
rename ('classes.jar', "${archiveName}-${variant.mergedFlavor.versionName}.jar")
}
makeJar.dependsOn tasks[taskName]
}
For the curious reader, I struggled to determine the correct variables and parameters that the com.android.library plugin uses to name .aar files. I finally found them in the Android Open Source Project here.
We can export a jar file for Android library project without resource files by Android studio. It is also requirement what I met recently.
1. Config your build.gradle file
// Task to delete old jar
task clearJar(type: Delete){
delete 'release/lunademo.jar'
}
// task to export contents as jar
task makeJar(type: Copy) {
from ('build/intermediates/bundles/release/')
into ('build/libs/')
include ('classes.jar')
rename('classes.jar', 'lunademo.jar')
}
makeJar.dependsOn(clearJar, build)
2. Run gradlew makeJar under your project root
You will see your libs under dir as build/libs/ if you are luckily.
============================================================
If you met issue as "Socket timeout exception" on command line as below,
You can follow this steps to open Gradle window in the right part and click "makeJar" on Android studio like this,
Then go to build/libs dir, you will see your jar file.
Hope that it is helpful for u.
Good Luck #.#
Luna

Categories

Resources