36

I would like to create a Jar out of an Android library project. It is set up the following way:

ProjectName
    \- lib
    |   \- lib
    |       \- armeabi
    |           \- libNativeFirst.so
    |           \- libNativeSecond.so
    \- src
        \- main
            \- java
                \- com.package.sdk
                    \- PackageSDK.java

I would like for all of this to be packaged in a Jar, but without revealing the source code present in PackageSDK.java.

I set up my build.gradle file like so:

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:0.5.+'
    }
}

apply plugin: 'android-library'

repositories {
    mavenCentral()
}

android {
    compileSdkVersion 18
    buildToolsVersion "18.0.1"

    defaultConfig {
        minSdkVersion 10
        targetSdkVersion 18
    }

    sourceSets {
        main {
            java {
                srcDir 'src/main/java'
            }
            resources {
                srcDir 'src/../lib'
            }
        }
    }
}

task jar(type: Jar) {
    from android.sourceSets.main.allSource
}

When I run gradlew clean jar in the project's directory, a Jar file is created in ProjectName\build\libs called ProjectName.jar. It's structure is as follows:

ProjectName.jar
    \- lib
    |   \- armeabi
    |       \- libNativeFirst.so
    |       \- libNativeSecond.so
    \- com
        \- package
            \- sdk
                \- PackageSDK.java

I would like for the compiled PackageSDK.class to be included instead of the PackageSDK.java file when executing the jar task. What can I change to achieve this?

Edit:

Per Ben Manes's suggestion, I changed the configuration of the sourceSets to the following:

sourceSets {
    main {
        java {
            srcDir 'src/main/java'
        }
        resources {
            srcDir 'src/../lib'
        }
        output {
            classesDir 'build/classes'
            resourcesDir 'build/javaResources'
        }
    }
}

And the jar task to the following:

task jar(type: Jar) {
    from android.sourceSets.main.output
}

Gradle is now giving me this output:

Could not find method output() for arguments [build_25r1m0a3etn5cudtt5odlegprd$_run_closure2_closure9_closure10_closure13@138532dc] on source set main.

BVB
  • 5,380
  • 8
  • 41
  • 62
  • try using `android.sourceSets.main.output` for class files instead of sources to build the jar from – Ben Manes Sep 26 '13 at 18:28
  • What should I set `sourceSets { main { output { srcDir '_____' } } }` to? – BVB Sep 26 '13 at 18:38
  • does the android plugin not use the Java plugin? Most of this is pre-configured. Maybe this has to be `output.dirs`. Take a look at the Gradle [user guide](http://www.gradle.org/docs/current/dsl/org.gradle.api.tasks.SourceSet.html) – Ben Manes Sep 26 '13 at 18:49
  • I think they differ in some ways, but I'm not 100% sure. I tried it with `.dirs`, but it had the same issue. I found that I can just use the `classes.jar` file from `build/bundles/release` and rename it to `ProjectName.jar`, but I wish I didn't have to do this. – BVB Sep 26 '13 at 19:19

2 Answers2

69

Note: The answer has been edited. Please see the 07/28/2014 update below.

Here is a solution I ended up coming up with. There may be a better way available, but I have not found it yet.

android {
    compileSdkVersion 18
    buildToolsVersion "18.0.1"

    defaultConfig {
        minSdkVersion 10
        targetSdkVersion 18
    }

    sourceSets {
        main {
            java {
                srcDir 'src/main/java'
            }
            resources {
                srcDir 'src/../lib'
            }
        }
    }
}

task clearJar(type: Delete) {
    delete 'build/libs/ProjectName.jar'
}

task makeJar(type: Copy) {
    from('build/bundles/release/')
    into('build/libs/')
    include('classes.jar')
    rename ('classes.jar', 'ProjectName.jar')
}

makeJar.dependsOn(clearJar, build)

Running gradlew makeJar creates a ProjectName.jar in the build/libs directory. The structure of this jar is as follows:

ProjectName.jar
    \- lib
    |   \- armeabi
    |       \- libNativeFirst.so
    |       \- libNativeSecond.so
    \- com
        \- package
            \- sdk
                \- PackageSDK.class

This is the exact result I needed. I am now able to use ProjectName.jar successfully in other projects.

EDIT: While I am able to use the resulting jar in projects within Android Studio, I cannot do so in projects created in ADT due to a warning about native code being present inside a jar file. Supposedly there is a flag to turn off this check in settings, but it does not function correctly. Thus, if you want to create a library that uses native code, those using ADT will have to manually copy the armeabi directory into libs/.

07/28/2014 Update:

As of Android Studio 0.8.0, Gradle output directories have been changed and the configuration outlined above will not work. I have changed my configuration to the following:

task clearJar(type: Delete) {
    delete 'build/outputs/ProjectName.jar'
}

task makeJar(type: Copy) {
    from('build/intermediates/bundles/release/')
    into('build/outputs/')
    include('classes.jar')
    rename ('classes.jar', 'ProjectName.jar')
}

IMPORTANT: Please note that ProjectName.jar will now be placed into build/outputs/ and NOT into build/libs/.

BVB
  • 5,380
  • 8
  • 41
  • 62
  • 1
    It was working earlier but not with the recent version of studio, Can you please check it and update. – Piyush Agarwal Jul 25 '14 at 12:33
  • 1
    @pyus13 I have updated the answer with the configuration changes to make it work with the latest Android Studio – BVB Jul 28 '14 at 16:23
  • thank you so much. But I am still confused and wanted to know the reason why the directory matters if we are writing the task by our own. I knew that the output directory have changed in 0.8.0 for default task(linke apk generation and all) but how can it impacts on the task we have written, I want to know just for my knowledge ? – Piyush Agarwal Jul 29 '14 at 07:00
  • That's just it - the Gradle tasks moved around files from certain directories. Because the directories have changed, the tasks needed to be updated to look at the correct ones. Otherwise, everything is pretty much the same. – BVB Jul 29 '14 at 18:11
  • +1 for keeping this answer up-to-date. Please continue doing so :) – Martin Konecny Aug 01 '14 at 04:59
  • @BVB Could you please help me to solve my question ? http://stackoverflow.com/questions/30520314/how-can-i-use-gradle-to-build-a-jar-with-required-libraries-in-a-sub-folder-lik – Jerikc XIONG May 29 '15 at 03:19
  • classes.jar was not created in my build directory, however the classes were there and I was able to jar up with this command. task jar(type: Jar) { from('build/intermediates/classes/release') } – rlshep Oct 18 '15 at 20:32
  • @rlshep, did you try the suggestion in the 07/28/2014 update? What was the result? – BVB Oct 19 '15 at 22:48
  • @BVB Nothing was created. I assume because there is no classes.jar in the build directory. – rlshep Oct 20 '15 at 13:47
  • @rlshep Ah, so your build process didn't create a ```build/intermediates/bundles/release/``` directory, only ```build/intermediates/classes/release/```? – BVB Oct 20 '15 at 15:55
  • @BVB Can we make classes.jar outside the project directory some where else in the drive? – Sameer Hussain Dec 10 '15 at 08:04
  • Theoretically you should be able to do so. Modify the makeJar task to suit your needs. You can specify different paths and filenames there. – BVB Dec 10 '15 at 17:18
  • Looks like this no longer works using Gradle 4.4, any tips on how to solve the issue? Thanks! – Aweb Apr 25 '18 at 12:40
  • Sorry, I haven't had the need nor the chance to experiment with newer versions of Gradle. What errors are you seeing? – BVB Jun 16 '21 at 03:40
8

Just to add a slight alternative to @BVB's answer (although heavily based on it) here's what I had to do to output a jar myapp-api.jar which was for a Java only project that dealt with rest API interaction. It's dependant on Android.jar hence the need to use apply plugin: 'com.android.application' rather than just apply plugin: 'java'

Calling ./gradlew build jar from the myJavaAPIProject to build and generate the .jar to myJavaAPIProject/build/libs/myapp-api.jar

build.gradle

//Even though this is a Java project, we need to apply the android plugin otherwise it cannot find the SDK/android.jar and so cannot compile
apply plugin: 'com.android.application'

dependencies {
    //this ensures we have gson.jar and anything else in the /lib folder
    compile fileTree(dir: 'lib', include: '*.jar')
}

repositories {
    mavenCentral()
}
android{
    compileSdkVersion 21
    buildToolsVersion "21.0.1"

    defaultConfig {
        minSdkVersion 10
        targetSdkVersion 21
    }

    sourceSets {
        main {
            java {
                //points to an empty manifest, needed just to get the build to work
                manifest.srcFile 'AndroidManifest.xml'
                //defined our src dir as it's not the default dir gradle looks for
                java.srcDirs = ['src']

            }
        }
    }

    //enforce java 7
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_7
        targetCompatibility JavaVersion.VERSION_1_7
    }
}

//Actually created the .jar file
task jar(type: Jar) {
    //from android.sourceSets.main.java
    from 'build/intermediates/classes/release/'
    archiveName 'myapp-api.jar'
}

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<!-- this is a dummy file needed to ensure gradle validates and builds ok -->
<manifest
    package="com.myapp.android"
    />
scottyab
  • 23,621
  • 16
  • 94
  • 105