63

How can I release Jar packaging of android-library project?
I've found, classes.jar is located under build/bundles/release/classes.jar and I suppose this is correct Jar package (contains *.class files).

Is there some official way, to release library as JAR instead of AAR ?

Edit
I use Gradle to release Maven artifacts, and I'd like to release JAR along with AAR package. So JAR with signature, md5, manifest, ...
based on https://chris.banes.me/2013/08/27/pushing-aars-to-maven-central/

apply plugin: 'maven'
apply plugin: 'signing'

configurations {
    archives {
        extendsFrom configurations.default
    }
}

def sonatypeRepositoryUrl
if (isReleaseBuild()) {
    println 'RELEASE BUILD'
    sonatypeRepositoryUrl = "https://oss.sonatype.org/service/local/staging/deploy/maven2/"
} else {
    println 'DEBUG BUILD'
    sonatypeRepositoryUrl = "https://oss.sonatype.org/content/repositories/snapshots/"
}

if(!hasProperty('nexusPassword')) {
    ext.set('nexusPassword', System.console().readPassword("\n\$ Type in password for Sonatype nexus account " + nexusUsername + ": "))
}

if(!signing.hasProperty('password')) {
    ext.set('signing.password', System.console().readPassword("\n\$ Type in GPG key password: "))
}

afterEvaluate { project ->
    uploadArchives {
        repositories {
            mavenDeployer {
                beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }

                pom.artifactId = POM_ARTIFACT_ID

                repository(url: sonatypeRepositoryUrl) {
                    authentication(userName: nexusUsername, password: nexusPassword)
                }

                pom.project {
                    name POM_NAME
                    packaging POM_PACKAGING
                    description POM_DESCRIPTION
                    url POM_URL

                    scm {
                        url POM_SCM_URL
                        connection POM_SCM_CONNECTION
                        developerConnection POM_SCM_DEV_CONNECTION
                    }

                    licenses {
                        license {
                            name POM_LICENCE_NAME
                            url POM_LICENCE_URL
                            distribution POM_LICENCE_DIST
                        }
                    }

                    developers {
                        developer {
                            id "loopj"
                            name "James Smith"
                        }
                        developer {
                            id "smarek"
                            name "Marek Sebera"
                        }
                    }
                }
            }
        }
    }

    signing {
        required { isReleaseBuild() && gradle.taskGraph.hasTask("uploadArchives") }
        sign configurations.archives
    }

    task androidJavadocs(type: Javadoc) {
        source = android.sourceSets.main.java.srcDirs
    }

    task androidJavadocsJar(type: Jar) {
        classifier = 'javadoc'
        from androidJavadocs.destinationDir
    }

    task androidSourcesJar(type: Jar) {
        classifier = 'sources'
        from android.sourceSets.main.java.srcDirs
    }

    artifacts {
        archives androidSourcesJar
        archives androidJavadocsJar
    }
}

using

task androidJar(type: Jar) {
    from android.sourceSets.main.java.srcDirs
}

will package only java files, not compiled and linked against android sdk

Geng Jiawen
  • 8,904
  • 3
  • 48
  • 37
Marek Sebera
  • 39,650
  • 37
  • 158
  • 244

3 Answers3

94

While I haven't tried uploading the artifacts with a deployment to Sonatype (or even a local repo), here's what I managed to come up with a few weeks ago when trying to tackle the same problem.

android.libraryVariants.all { variant ->
  def name = variant.buildType.name
  if (name.equals(com.android.builder.core.BuilderConstants.DEBUG)) {
    return; // Skip debug builds.
  }
  def task = project.tasks.create "jar${name.capitalize()}", Jar
  task.dependsOn variant.javaCompile
  task.from variant.javaCompile.destinationDir
  artifacts.add('archives', task);
}

Then run the following:

./gradlew jarRelease
CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
Jake Wharton
  • 75,598
  • 23
  • 223
  • 230
  • I'm afraid, this is correct answer :-) Thanks, I'll post final edit I've made to get it work – Marek Sebera Oct 22 '13 at 17:15
  • 4
    I can also confirm that this works. Note that the generated task does not show up when running **`gradle tasks`**, only with **`gradle tasks --all`**. Thanks again! – CommonsWare Dec 02 '13 at 22:23
  • 6
    Thanks this does help, but it still doesn't generate the class files for dependencies. I'm trying to make an SDK jar basically that will be a single jar to pass out, but this compile does not include module projects that it is dependent on and does not pull down jars from the dependency section etc.. Do you have any suggestions to accomplish this where it compiles the library projects into the jar as well so I don't have to hand out 4 jars, one for each library project. Then it would be nice to figure out how to include the maven dependency jars too like Gson or Otto. – Sam Mar 30 '14 at 20:08
  • This seems to break for me when I add flavors because it tries to create several of the same named tasks without a flavor included. I'm no Groovy guy and cannot figure out how to add the flavor to the task name. I'd appreciate any hints on how to do that. – Kevin Jun 17 '14 at 15:07
  • I had a small addition to Jake's solution that accounts for any java resources - add `task.dependsOn variant.processJavaResources` and `task.from variant.processJavaResources.destinationDir` (sorry, couldn't work out how to format this properly) – Piklor Jul 02 '14 at 23:46
  • 2
    Can someone point out how can I use proguard with this configuration? It seems that defining the buildTypes with proguard does not apply. – MikeL Aug 13 '14 at 11:42
  • 2
    @Kevin, I exchanged the `"jar${name.capitalize()}"` part with `"jar${variant.name.capitalize()}"` to make the task account for the flavour name too (the `variant.name` is composed from both buildType name and productFlavor name) – dbm Dec 22 '14 at 12:48
  • @SamDozor please follow the link for proguard support http://www.survivingwithandroid.com/2014/05/create-jar-libray-with-gradle-using-aar.html – Jani Jan 23 '15 at 05:26
  • @Piklor, I tried adding your lines for getting the resources included but it didn't work. Could you mention the entire task code you used please – keno Mar 25 '15 at 20:48
  • @keno, unfortunately I can't locate the code in question. – Piklor Mar 26 '15 at 22:26
  • it seems this is not going to package a .png resource into the .jar, is there a way to package an image resource into the jar ? – 2cupsOfTech Aug 31 '15 at 15:56
  • @CommonsWare any thoughts on packaging images with gradle into jar? – 2cupsOfTech Aug 31 '15 at 16:08
  • @2cupsOfTech: Well, you can't put Android resources into a JAR, only an AAR. You're welcome to play around with classic Java resources in the JAR, but I have not tried it, and it's not commonly done. – CommonsWare Aug 31 '15 at 16:14
  • @CommonsWare this script doesn't produce an obfuscated jar I believe, is that possible to do? – 2cupsOfTech Sep 10 '15 at 20:48
  • @2cupsOfTech: Beats me – CommonsWare Sep 10 '15 at 20:51
  • @jake-wharton how to build an obfuscated jar using your script? – 2cupsOfTech Sep 10 '15 at 20:56
  • @2cupsOfTech to generate an obfuscated jar I used the alternative method of copying the Jar that is already generated by the library build in /build/intermediates/bundles/release (as mentioned by Jani). I guess Jani should an alternative answer here. It works for both obfuscated and non obfuscated libraries and seems simpler to me. – Olinasc Nov 03 '15 at 15:48
  • @Jani you should post an answer here with your alternative method. It would help others as it also handles obfuscated cases. – Olinasc Nov 03 '15 at 15:49
  • @Olinasc I ended up writing a small shell script to convert a jar to an obfuscated jar using proguard – 2cupsOfTech Nov 03 '15 at 16:16
  • @2cupsOfTech I understand. This seems though as more work than is needed. I will post the alternative answer here if Jani does not reply. – Olinasc Nov 03 '15 at 16:22
  • @Olinasc its a 1 line script, look into command: 'java -jar -outjars -injars', my script is not general otherwise I would post, this link could be helpful: http://proguard.sourceforge.net/manual/examples.html – 2cupsOfTech Nov 03 '15 at 16:37
  • @2cupsOfTech I no longer have access to the build.gradle file that I used this alternative method. Please post the answer. I'm sorry for the inconvenience. – Jani Nov 03 '15 at 22:08
2

Another way to generate a jar from a library project through gradle is as follows:

In your library's build.gradle:

def jarName = 'someJarName.jar'

task clearJar(type: Delete) {
    delete "${project.buildDir}/libs/" + jarName
}

task makeJar(type: Copy) {
    from("${project.buildDir}/intermediates/bundles/release/")
    into("${project.buildDir}/libs/")
    include('classes.jar')
    rename('classes.jar', jarName)
}

makeJar.dependsOn(clearJar, build)

What we are doing here is just copying the classes.jar generated by the Android Gradle plugin. Be sure to look into your build directory for this file and see if its contents are in the way you want.

Then run the makeJar task and the resulting jar will be in library/build/libs/${jarName}.jar

The will have the class files according to your configuration for release. If you are obfuscating it, then the files in the jar will be obfuscated.

Olinasc
  • 1,261
  • 1
  • 12
  • 20
0

Just because the previous answers were not fitting my needs, I share my tricky version to generate a jar with the project classes and the aar/jar dependencies.

// A tricky jar operation, we want to generate a jar files that contains only the required classes used.
// Dependencies are in jar and aar, so we need to open the aar to extract the classes and put all at the same level
// (aar is a zip that contains classes.jar, the latter is a zip that contains .class files)
task jar(type: Jar) {
    from {
        List<File> allFiles = new ArrayList<>();
        configurations.compile.collect {
            for (File f : zipTree(it).getFiles()) {
                if (f.getName().equals("classes.jar")) {
                    allFiles.addAll(zipTree(f).getAt("asFileTrees").get(0).getDir())
                }
            }
        }
        allFiles.add(new File('build/intermediates/classes/release'))
        allFiles // To return the result inside a lambda
    }
    archiveName( project.ext.appName + '.jar' )
}

This does NOT manage the build types / flavours but can be adapted (it's just ok to build on build-type release without flavour).

If ever you've a smarter or more elegant solution, please share!

Kikiwa
  • 1,213
  • 2
  • 13
  • 20