33

I've juste added some flavors (or productFlavors if you want) to my project.

The fact is that when I publish the library to bintray, all flavors are uploaded (which is great), but I'm unable to use them. The plugin used is the official one here.

The uploaded aar:

 androidsdk-0.0.4-fullRelease.aar
 androidsdk-0.0.4-fullDebug.aar
 androidsdk-0.0.4-lightRelease.aar
 androidsdk-0.0.4-lightDebug.aar

As you noted, the fullRelease is named as the classifier, see doc chapter 23.4.1.3.

I am searching for a solution to choose which flavors that I want to upload.

I've already looked at bintray examples (here and here) and this, with also other examples but I'm still stuck.

Here is my current script:

apply plugin: 'com.android.library'
apply plugin: 'com.github.dcendents.android-maven'
apply plugin: 'com.jfrog.bintray'

buildscript {
    repositories {
        jcenter()
    }
}

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.1"

    defaultConfig {
        minSdkVersion 9
        targetSdkVersion 23
        versionCode 64
        versionName "0.0.4"
    }

    publishNonDefault true

    productFlavors {
        full {
        }
        light {
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:23.1.1'
    compile 'com.android.support:recyclerview-v7:23.1.1'
    fullCompile 'com.squareup.picasso:picasso:2.5.0'
}

version = android.defaultConfig.versionName

uploadArchives {
    repositories.mavenDeployer {
        pom.project {

            packaging 'aar'

        }
    }
}

////////////////////////////////
// Bintray Upload configuration

Properties properties = new Properties()
properties.load(project.rootProject.file('local.properties').newDataInputStream())

bintray {
    user = properties.getProperty("bintray.user")
    key = properties.getProperty("bintray.apikey")

    configurations = ['archives']
    pkg {
        repo = "MyRepo" // repo name
        userOrg = 'hugo'
        name = "AndroidSDK" // Package name
        websiteUrl = siteUrl
        vcsUrl = gitUrl
        publish = true
    }
}

To import the library I'm currently using this:

compile ('com.example.lib:sdk:0.0.8:fullRelease@aar') {
    transitive = true;
}
Community
  • 1
  • 1
Hugo Gresse
  • 14,772
  • 8
  • 69
  • 103

5 Answers5

13

I faced the same challenge, and here's the best I could make yet:

Using mavenPublications and the gradle maven-publish plugin along the bintray plugin, you can publish any variant to mavenLocal and bintray.

Here's the publish.gradle file I apply at the end of all my project's library modules I want to publish:

def pomConfig = {
    licenses {
        license {
            name 'The Apache Software License, Version 2.0'
            url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
        }
    }
    developers {
        developer {
            id 'louiscad'
            name 'Louis CAD'
            email 'louis.cognault@gmail.com'
        }
    }
    scm {
        connection 'https://github.com/LouisCAD/Splitties.git'
        developerConnection 'https://github.com/LouisCAD/Splitties.git'
        url siteUrl
    }
}

def publicationNames = []
publishing.publications {
    android.libraryVariants.all { variant ->
        if (variant.buildType.name == "debug") return // Prevents publishing debug library

        def flavored = !variant.flavorName.isEmpty()

        /**
         * Translates "_" in flavor names to "-" for artifactIds, because "-" in flavor name is an
         * illegal character, but is well used in artifactId names.
         */
        def variantArtifactId = flavored ? variant.flavorName.replace('_', '-') : project.name

        /**
         * If the javadoc destinationDir wasn't changed per flavor, the libraryVariants would
         * overwrite the javaDoc as all variants would write in the same directory
         * before the last javadoc jar would have been built, which would cause the last javadoc
         * jar to include classes from other flavors that it doesn't include.
         *
         * Yes, tricky.
         *
         * Note that "${buildDir}/docs/javadoc" is the default javadoc destinationDir.
         */
        def javaDocDestDir = file("${buildDir}/docs/javadoc ${flavored ? variantArtifactId : ""}")

        /**
         * Includes
         */
        def sourceDirs = variant.sourceSets.collect {
            it.javaDirectories // Also includes kotlin sources if any.
        }
        def javadoc = task("${variant.name}Javadoc", type: Javadoc) {
            description "Generates Javadoc for ${variant.name}."
            source = variant.javaCompile.source // Yes, javaCompile is deprecated,
            // but I didn't find any working alternative. Please, tweet @Louis_CAD if you find one.
            destinationDir = javaDocDestDir
            classpath += files(android.getBootClasspath().join(File.pathSeparator))
            classpath += files(configurations.compile)
            options.links("http://docs.oracle.com/javase/7/docs/api/");
            options.links("http://d.android.com/reference/");
            exclude '**/BuildConfig.java'
            exclude '**/R.java'
            failOnError false
        }
        def javadocJar = task("${variant.name}JavadocJar", type: Jar, dependsOn: javadoc) {
            description "Puts Javadoc for ${variant.name} in a jar."
            classifier = 'javadoc'
            from javadoc.destinationDir
        }
        def sourcesJar = task("${variant.name}SourcesJar", type: Jar) {
            description "Puts sources for ${variant.name} in a jar."
            from sourceDirs
            classifier = 'sources'
        }

        def publicationName = "splitties${variant.name.capitalize()}Library"
        publicationNames.add(publicationName)

        "$publicationName"(MavenPublication) {
            artifactId variantArtifactId
            group groupId
            version libraryVersion

            artifact variant.outputs[0].packageLibrary // This is the aar library
            artifact sourcesJar
            artifact javadocJar

            pom {
                packaging 'aar'
                withXml {
                    def root = asNode()
                    root.appendNode("name", 'Splitties')
                    root.appendNode("url", siteUrl)
                    root.children().last() + pomConfig
                    def depsNode = root["dependencies"][0] ?: root.appendNode("dependencies")

                    def addDep = {
                        if (it.group == null) return // Avoid empty dependency nodes
                        def dependencyNode = depsNode.appendNode('dependency')
                        dependencyNode.appendNode('groupId', it.group)
                        dependencyNode.appendNode('artifactId', it.name)
                        dependencyNode.appendNode('version', it.version)
                        if (it.hasProperty('optional') && it.optional) {
                            dependencyNode.appendNode('optional', 'true')
                        }
                    }

                    // Add deps that everyone has
                    configurations.compile.allDependencies.each addDep
                    // Add flavor specific deps
                    if (flavored) {
                        configurations["${variant.flavorName}Compile"].allDependencies.each addDep
                    }
                    // NOTE: This library doesn't use builtTypes specific dependencies, so no need to add them.
                }
            }
        }
    }
}

group = groupId
version = libraryVersion

afterEvaluate {
    bintray {
        user = bintray_user
        key = bintray_api_key
        publications = publicationNames

        override = true
        pkg {
            repo = 'splitties'
            name = project.name
            desc = libraryDesc
            websiteUrl = siteUrl
            issueTrackerUrl = 'https://github.com/LouisCAD/Splitties/issues'
            vcsUrl = gitUrl
            licenses = ['Apache-2.0']
            labels = ['aar', 'android']
            publicDownloadNumbers = true
            githubRepo = 'LouisCAD/Splitties'
        }
    }
}

In order for this to work, I need to have the bintray_user and bintray_api_key properties defined. I personally just have them in my ~/.gradle/gradle.properties file like this:

bintray_user=my_bintray_user_name
bintray_api_key=my_private_bintray_api_key

I also need to define the following ext properties I used in the publish.gradle file in my root project's build.gradle file:

allprojects {
    ...
    ext {
        ...
        // Libraries
        groupId = "xyz.louiscad.splitties"
        libraryVersion = "1.2.1"
        siteUrl = 'https://github.com/LouisCAD/Splitties'
        gitUrl = 'https://github.com/LouisCAD/Splitties.git'
    }
}

And now, I can finally use it in my android library module, where I have multiple productFlavors. Here's a snippet from a publishable library module's build.gradle file:

plugins {
    id "com.jfrog.bintray" version "1.7.3" // Enables publishing to bintray
    id "com.github.dcendents.android-maven" version "1.5" // Allows aar in mavenPublications
}

apply plugin: 'com.android.library'
apply plugin: 'maven-publish' // Used for mavenPublications

android {
    ...
    defaultPublishConfig "myLibraryDebug" // Allows using this library in another
    // module in this project without publishing to mavenLocal or Bintray.
    // Useful for debug purposes, or for your library's sample app.
    defaultConfig {
        ...
        versionName libraryVersion
        ...
    }
    ...
    productFlavors {
        myLibrary
        myLibrary_logged // Here, the "_" will be replaced "-" in artifactId when publishing.
        myOtherLibraryFlavor
    }
    ...
}

dependencies {
    ...
    // Timber, a log utility.
    myLibrary_loggedCompile "com.jakewharton.timber:timber:${timberVersion}"; // Just an example
}
...

ext {
    libraryDesc = "Delegates for kotlin on android that check UI thread"
}

apply from: '../publish.gradle' // Makes this library publishable

When you have all of this setup properly, with the name of your library instead of mine's (which you can use as an example), you can try publishing a version of your flavored library by trying to first publishing to mavenLocal. To do so, run this command:

myLibrary $ ../gradlew publishToMavenLocal

You can then try adding mavenLocal in your app's repositories (example here) and try adding your library as a dependency (artifactId should be the flavor name, with "_" replaced with "-") and building it. You can also check with your file explorer (use cmd+shift+G on Mac in Finder to access hidden folder) the directory ~/.m2 and look for your library.

When it's time to publish to bintray/jcenter, you just have to run this command:

myLibrary $ ../gradlew bintrayUpload

Important:

Before you publish your library to mavenLocal, Bintray or another maven repository, you'll usually want to try your library against a sample app which uses the library. This sample app, which should be another module in the same project just need to have the project dependency, which should look like this: compile project(':myLibrary'). However, since your library has multiple productFlavors, you'll want to test all of them. Unfortunately, it's currently impossible to specify which configuration you want to use from your sample app's build.gradle file (unless, you use publishNonDefault true in your library's build.gradle file, which breaks maven and bintray publications), but you can specify the default configuration (i.e. buildVariant) in your library's module as such: defaultPublishConfig "myLibraryDebug" in the android closure. You can see the available build variants for your library in the "Build Variants" tool Windows in Android Studio.

Feel free to explore my library "Splitties" here if you need an example. The flavored module is named concurrency, but I use my script for unflavored library modules too, and I tested it throughly on all the library modules in my project.

You can reach me out if you need help setting it up for you.

Louis CAD
  • 9,299
  • 2
  • 31
  • 50
  • as a note about the bottom section, you can get the consuming application to align to the correct configuration by specifying the missingDimensionStrategy like so `missingDimensionStrategy 'brand', 'XXX' missingDimensionStrategy 'environment', 'ZZZ'` – MrTristan Jan 12 '18 at 02:11
  • Thanks for great answer. I'm facing one issue : In ~/.m2 directory I'm having different .jar files for each flavor but when I run bintrayUpload then it's uploading same content in each product flavor's jar file. – Vikram Nov 29 '18 at 09:50
  • @VikramBhati As said in the answer, you need to change `defaultPublishConfig` before publishing that specified flavor. – Louis CAD Nov 29 '18 at 14:06
  • @LouisCAD I just tried what you have told but still it's uploading same .jar file for all flavors. As of now, running bintrayUpload even with defaultPublishConfig "abc", publishes all the flavors on bintray. So I don't think if there is any impact of defaultPublishConfig in bintrayUpload. Please correct me if I missed something. – Vikram Nov 29 '18 at 14:38
  • @LouisCAD It seems publishNonDefault is deprecated. Got warning while running bintrayUpload - publishNonDefault is deprecated and has no effect anymore. All variants are now published. – Vikram Nov 29 '18 at 17:01
  • That explains. Note that I no longer use this. I prefer to make multiple modules, one with shared code, and others with things I want to change, then I pick one or the other. – Louis CAD Nov 30 '18 at 19:45
6

The setup:

buildTypes {
  debug {
  }
  release {
  }
}

publishNonDefault true

The fix:

defaultPublishConfig 'release'

// Fix for defaultPublishConfig not working as expected
// ref: https://github.com/dcendents/android-maven-gradle-plugin/issues/11
libraryVariants.all { variant ->
  if( publishNonDefault && variant.name == defaultPublishConfig ) {
    def bundleTask = tasks["bundle${variant.name.capitalize()}"]
    artifacts {
      archives(bundleTask.archivePath) {
        classifier null //necessary to get rid of the suffix in the artifact
        builtBy bundleTask
        name name.replace('-' + variant.name, '')//necessary to get rid of the suffix from the folder name
      }
    }
  }
}

This fix will still publish all the artifacts, but it will publish a default artifact without the flavour suffix, which is enough to make it all work.

The fix to upload only the default artifact would be this (if the bintray plugin knew what POM filters are):

install {
  repositories.mavenInstaller {
    /*
    POM filters can be used to block artifacts from certain build variants.

    However, Bintray does not respect POM filters, therefore this only works for maven deploy plugin.
    Also, bintray crashes with named filters, since it always expects a /build/pom/pom-default.xml,
  which does not happen with named filters.
    */
    filter { artifact, file ->
      // this how the default classifier is identified in case the defaultPublishConfig fix is applied
      artifact.attributes.classifier == null
    }
  }
}
Ricardo Freitas
  • 503
  • 6
  • 8
3

I didn't try it so I will delete the answer if it doesn't resolve the issue.

You should post a different artifact for each flavor (or build variant if you prefer).
In this way you will have in jcenter x artifacts, each of them with a pom file.

Something like:

groupId
|--library-full
|----.pom
|----.aar
|--library-light
|----.pom
|----.aar

In your top level file you can define

allprojects {
    repositories {
        jcenter()
    }

    project.ext {
        groupId="xxx" 
        libraryName = ""
        ......
    }
}

Then in your library module:

productFlavors {
        full {
            project.ext.set("libraryName", "library-full");
        }
        light {
            project.ext.set("libraryName", "library-light");
        }
}

bintray {

    //...
    pkg {
        //...Do the same for other variables
        name = project.ext.libraryName
    }
}

Finally make sure to publish only the release build type (why also the debug version?)

Gabriele Mariotti
  • 192,671
  • 57
  • 469
  • 489
  • ok, with this I'm able to upload the differents flavors to different bintray package. But the classifier is still here and each package have all the flavor. – Hugo Gresse Dec 18 '15 at 09:22
  • Sorry but I don't know what you are referring with classifier. I am checking the pom file in the jcenter and you should have groupId/artifactId to identify a library. – Gabriele Mariotti Dec 18 '15 at 09:50
  • yes, I have this but I have the classifier is here too, so I have both `androidsdk-0.0.4-lightDebug.aar` and `androidsdk-0.0.4-lightDebug.aar` on each package. So there is currently two issue with this. The classifier is `lightDebug` for example. – Hugo Gresse Dec 29 '15 at 08:46
  • Did you find an answer? – Ali Naddaf Feb 07 '16 at 03:58
  • no, I still have 4 libraries uploaded (2 flavors & releae + debug build), I need to remove the debug build each time I release new version on bintray. – Hugo Gresse Dec 08 '16 at 12:32
2

If someone is still stuck with this problem here's what worked for me -

Let's say you want to publish the release build for your flavour1 add this to your build.gradle

android {
    ...
    defaultPublishConfig "flavour1Release"
}

Remove publishNonDefault true if it is present in your gradle file.

Add this inside the bintray block like this

bintray {
    ...
    archivesBaseName = 'YOUR_ARTIFACT_ID'
    ...
}

Then just run the bintrayUpload task as you would.

The defaultPublishConfig will have to be changed everytime you need to publish a new flavour.

k1slay
  • 1,038
  • 1
  • 10
  • 18
  • Removing `publishNonDefault true` will prevent the module to be used in other module in debug, it's a good idea but does not solve the problem entirely – Hugo Gresse Dec 09 '16 at 08:23
  • Yes, if you want to use the library module in debug, you will have to use something like `defaultPublishConfig "flavour1Debug"`. You switch to `defaultPublishConfig "flavour1Release"` only when you have to publish to bintray – k1slay Dec 09 '16 at 09:40
1

It sounds like you don't want the classifier in the filename. It looks like the classifier is the same as the generated library file name. Have you tried giving them the same filename but outputting them to separate directories? E.g. in the android scope:

libraryVariants.all { variant ->
    variant.outputs.each { output ->
        def outputFile = output.outputFile
        if (outputFile != null && outputFile.name.endsWith('.aar')) {
            def fileName = "same_name-${version}.aar"
            output.outputFile = new File(outputFile.parent+"/${archivesBaseName}", fileName)
        }
    }
}
Jim Baca
  • 5,684
  • 2
  • 20
  • 30
  • that allow me to change the .aar file name (from the `build/outputs/aar/androidsdk/` but not the one used by the bintray plugin, so It didn't help. – Hugo Gresse Jan 12 '16 at 15:18