How to Publish a Single Repository — Multiple Libraries on JCenter using CI

Mithat Sinan Sarı
Hipo
Published in
7 min readNov 18, 2020

--

How can we make publishing a library as easy as possible?

That was the thought in our mind as we set out on this road. As Hipo, we decided to publish our Android libraries we use in our projects as open source and created Macaron. Macaron is a single repository that contains multiple open source library modules, like Retrofit. With this approach, libraries become easier to maintain and have a more organized structure. Also you can implement what you need. For example there are around 10 converters in Retrofit-converters package, but we need only Gson. So we can implement Gson only, without implementing any unnecessary converter.

Since multiple developers are involved in development, we had to make the process easy to understand, easy to develop and easy to publish. We decided to use Bintray like we did before and we also decided to use CI for Macaron. We are currently using Bitrise for CI in Hipo. For Macaron, we decided to follow the same pattern.

Macaron development flow

To publish the library — after everything is set up — only thing a developer needs to do is to add a tag and push it to GitHub. That’s it!

Last time, we talked about how to publish an open source library on JCenter. If you want to get more detail about JCenter and publishing single repo-single library, you can check the link below;

First we need to understand Bintray. What does Bintray need? We separate Bitray’s needs into 6 topics and created files for each of them.

  • Bintray repository
  • Developer properties
  • Library properties
  • License properties
  • Local properties
  • Repository properties

Bintray Repository

We are not able to push our packages if we don’t have a Bintray repository. It is pretty easy to create one;

Sign In -> Click Add New Repository on Dashboard -> Fill necessary fields.

Make sure to choose Maven for type. We will publish on Maven in this example.

Developer Properties

This file includes information about developer that developed the library module such as; Id, name, email and organisation.

developerId='[BINTRAY_DEVELOPER_ID]'
developerName='[DEVELOPER_NAME]'
developerEmail='[DEVELOPER_EMAIL]'
developerOrganisation=[DEVELOPER_ORGANISATION]

Library Properties

Bintray use these properties to create library package.

artifact=photocropping
libraryName=com.hipo.photocropping
libraryVersion=1.0.0
libraryDescription=Photo cropping library that uses PhotoView and has extensions for file provider

Here you can see what they are used for;

Do not confuse the library name with implementation parameter. It will be described in Repository Properties.

License Properties

Since we share all libraries with Apache-2.0, we didn’t separate license.properties file for each library module. These properties is being used to set library license;

licenseName = 'The Apache Software License, Version 2.0'
licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
allLicenses = ["Apache-2.0"]

Local Properties

bintrayUser=YOUR_BINTRAY_USERNAME
bintrayApiKey=YOUR_BINTRAY_API_KEY

To publish without Bitrise, username and API key must be defined in local.properties to avoid from pushing it to GitHub.

Repository Properties

bintrayRepoName=Macaron
publishedGroupId=com.hipo.macaron

macaronSiteUrl=https://github.com/Hipo/macaron
macaronGitUrl=https://github.com/Hipo/macaron.git

Here we give our GitHub repository url and vcs url.

bintrayRepoName has to be same as name of the repo you’ve created on Bintray.

publishedGroupId determines what dependency parameter will be. For example we set our artifact as photocropping. Bintray will get this artifact name and combine with published group id and the result will be;

These are what Bintray needs.

How do we create pom / aar packages and upload them to Bintray?

First, we need a Gradle file! Let’s dive into our publish.gradle which is responsible of checking properties, installing pom&aar packages and uploading them to Bintray.

Defining property files

Here we just read files that we’ve created before. license.properties, developer.properties, library.properties, macaron.properties…

I want you to focus on if check on line 13. As we mentioned, we want Bitrise to publish our library. Since we keep our Bintray API key and username in local.properties, we can not push it to GitHub. So we have to check if we have local.properties on current environment or not. If we don’t have it, we use System.getenv() to get API key & username from Bitrise Secret Envs.

Checking if everything is OK

checkProperties reads every property in given files and throws exception if there is any missing one. If there isn’t a file called local.properties on current environment, it skips that file.

Installing POM / AAR packages

Install task is responsible of creating POM and AAR packages. It gets information it needs from files we’ve created before. We use Dcendents/Android-Maven-Gradle-Plugin for this task.

Uploading to Bintray

In bintray scope, we give what Bintray needs. Bintray repo, library name etc… They are also defined in files we’ve created before.

Reason of reading properties from files is to use only one publish.gradle to publish all libraries.

To make this happen, we get publish.gradle by adding the line below at the end of the build.gradle in library module.

This line makes build.gradle read publish.gradle from our GitHub repository.

You can see full publish.gradle below:

DO NOT FORGET TO ADD PLUGIN DEPENDENCIES INTO YOUR PROJECT build.gradle

It’s time to set up Bitrise.

I won’t discuss how to add a new Bitrise app and what steps/workflows mean. Bitrise has pretty good documentation and UI.

Since we control everything from our GitHub repo, we decided to create a simple .sh script and let Bitrise run it for us, so we don’t have to update Bitrise workflows if we add new task to any of our gradle files.

Just remove default steps and add a script step to your deploy workflow and give your script path to Script Content. Bitrise also let you write your script into there.

Let’s add our Bintray credentials to Bitrise. Go to Secrets and add your Bintray credentials as shown below. Keys have to match with System.getenv() parameters in publish.gradle.

Since we use Gradle task in our bash script, we need to define Gradlew path in Env Vars. Make sure the key is GRADLEW_PATH;

Now, Bitrise knows how to check if there is any missing property, install packages and upload them to Bintray with our credentials. But, how does Bitrise know when to do these tasks? Triggers for the win!

We set our trigger tag as *.*.*-*, so whenever we push a tag which has the same pattern, our deploy workflow will run. By doing that we can choose which module to publish independently.

For example, there are 3 library modules but we want to publish only one of them. In this case, our tag will be 1.0.0-alpha03-photocropping. Bash script will parse module name and will call necessary tasks, like; photocropping:install.

Bitrise settings is done!

Last but not least: Project Structure

Project
...
|
+-- library_module_1
| |
| |-- library.properties
| \-- developer.properties
|
+-- library_module_2
| |
| |-- library.properties
| \-- developer.properties
|
+-- publish
| |
| |-- bintray-publish-script.sh
| |-- license.properties
| |-- macaron.properties
| \-- publish.gradle
|
+-- local.properties
...

This is our project structure for Macaron.

  • Since license.properties and macaron.properties are the same for all library modules, we don’t need to separate them. But each library module has to have their own library.properties and developer.properties.
  • As we mentioned before, they will all use same publish.gradle which was pushed to GitHub repository.
  • Bitrise will check ./publish/bintray-publish-script.sh to run bash script. So make sure that you give the right path for the script.

That’s it!

Now you can create your open source project and give back to the community.

--

--