Blog Infos
Author
Published
Topics
, , , ,
Published
Create Your Publishing Convention Plugin

Publishing libraries as a Java, Kotlin, or Android developer can be a complex and time-consuming process. Instead of focusing on development, we often find ourselves bogged down in configurations, setup, and version management. This was especially true for me when I first decided to publish a library. The challenge of even a one-time configuration was daunting, particularly because I didn’t want to rely on existing libraries. This made the task even more challenging.

Have you ever wondered why the publishing process has to be so complicated? Why can’t we just publish our libraries without all the usual headaches? These questions led me to a realization:Β Could there be a simpler and more efficient way to handle this process?

That’s when the idea of creating a custom Gradle plugin sparked in my mind β€” a solution that could eliminate all these complexities and make my work easier. Having experience working with convention plugins, I knew the power they could bring to managing dependencies and configurations. If you’re not familiar with these concepts, I recommend reading the articleΒ Mastering Android Dependency ManagementΒ to get a solid foundation.

In this article, I’ll take you on the journey I embarked on to create such a plugin. From the initial challenges to the final implementation, you’ll learn how to do the same for your projects.

1. Setting the Stage: Creating the Core Class

To start, we need to create the following class. This class allows us to define the necessary information in the modules we want to publish, and we’ll later read this information in the plugin:

open class ModuleInfoExtension {
    var groupId: String = ""
    var artifactId: String = ""
}
2. Simplifying Access: Adding an Extension Function

In the next step, to make accessing this information easier within the plugin, we create the following extension function:

val Project.moduleInfo
    get() = extensions.getByType(ModuleInfoExtension::class)
3. Building the Foundation: Creating the Java Plugin

Next, we’ll create the Java plugin for publishing our Java/Kotlin library, as shown below:

import your.package.name.configureJvmModulePublishing
import your.package.name.moduleInfo
import java.io.FileInputStream
import java.util.Properties
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.publish.PublishingExtension
import org.gradle.api.publish.maven.MavenPublication
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.create
import org.gradle.kotlin.dsl.get

class JvmLibraryPublisherConventionPlugin : Plugin<Project> {
    override fun apply(target: Project) {
        with(target) {
            with(pluginManager) {
                apply("maven-publish")
            }
            configureJvmModulePublishing()
            configure<PublishingExtension> {
                afterEvaluate {
                    publications {
                        create<MavenPublication>(
                            name = "androidFramework",
                            configuration = {
                                artifactId = moduleInfo.artifactId
                                groupId = moduleInfo.groupId
                                version = version
                                from(components["java"])
                            }
                        )
                    }
                    val nexusPropertiesFile = rootProject.file("nexus-config.properties")
                    val nexusProperties = Properties()
                    nexusProperties.load(FileInputStream(nexusPropertiesFile))

                    repositories {
                        maven {
                            name = nexusProperties["name"] as String
                            url = uri(nexusProperties["url"] as String)
                            credentials {
                                username = nexusProperties["username"] as String
                                password = nexusProperties["password"] as String
                            }
                        }
                    }
                }
            }
        }
    }
}
4. Configuring Module Publishing: JVM and Android

To make the process of publishing easier and more consistent across your project, I’ve included two key functions:Β configureJvmModulePublishingΒ andΒ configureAndroidModulePublishing. These functions centralize the publishing configurations for both JVM and Android modules, ensuring that you don’t need to repeat the same settings in each module.

configureJvmModulePublishing

This function is designed to handle the publishing setup for JVM-based modules. It allows you to define and manage the necessary configurations in one place, simplifying the process of publishing your Java and Kotlin libraries.

internal fun Project.configureJvmModulePublishing() {
    extensions.create("configurePublisher", ModuleInfoExtension::class)
}

configureAndroidModulePublishing

Similarly,Β configureAndroidModulePublishingΒ focuses on the Android modules in your project. It streamlines the setup by automatically configuring the necessary artifacts, such as source jars and Javadoc jars, making the publishing process more efficient.

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

,

Meta-programming with K2 compiler plugins

Let’s see what’s possible with plugins using the new K2 compiler, FIR. This live demo session will go through possible use cases that reduce boilerplate code and make your code safer.
Watch Video

Meta-programming with K2 compiler plugins

Tadeas Kriz
Senior Kotlin Developer
Touchlab

Meta-programming with K2 compiler plugins

Tadeas Kriz
Senior Kotlin Develo ...
Touchlab

Meta-programming with K2 compiler plugins

Tadeas Kriz
Senior Kotlin Developer
Touchlab

Jobs

internal fun Project.configureAndroidModulePublishing() {
    extensions.configure<LibraryExtension> {
        publishing {
            singleVariant("release") {
                withSourcesJar()
                withJavadocJar()
            }
        }
    }
    extensions.create("configurePublisher", ModuleInfoExtension::class)
}

By using these functions, you can maintain a clean and consistent configuration across your entire project, reducing redundancy and potential errors in your publishing setup.

5. Streamlining Configurations: Centralizing Your Settings

Now, let’s break down the following configuration:

configure<PublishingExtension> {
    afterEvaluate {
        publications {
            create<MavenPublication>(
                name = "androidFramework",
                configuration = {
                    artifactId = moduleInfo.artifactId
                    groupId = moduleInfo.groupId
                    version = version
                    from(components["java"])
                }
            )
        }
        val nexusPropertiesFile = rootProject.file("nexus-config.properties")
        val nexusProperties = Properties()
        nexusProperties.load(FileInputStream(nexusPropertiesFile))

        repositories {
            maven {
                name = nexusProperties["name"] as String
                url = uri(nexusProperties["url"] as String)
                credentials {
                    username = nexusProperties["username"] as String
                    password = nexusProperties["password"] as String
                }
            }
        }
    }
}
Explanation:

The purpose of this configuration is to avoid duplicating the same settings across multiple modules. By centralizing these configurations within the plugin, we ensure that all modules within the project adhere to the same publishing setup without needing to manually replicate these settings in each module.

6. Making it Modular: Enabling Per-Module Configurations

The following line:

extensions.create("configurePublisher", ModuleInfoExtension::class)

enables you to open a lambda in each module and write the necessary configurations, as shown below:

plugins {
    id("framework.android.publisher") // Don't forgot to add this
}

android {
    namespace = "framework.network.ktor"
}

dependencies {
    .....
}

configurePublisher {
    groupId = "framework.network"
    artifactId = "ktor"
    version = "1.0.0"
}

Note: I’ll explain later how to create theΒ framework.android.publisherΒ plugin.

7. Registering Your Plugin: Integrating with Gradle

The following block of code is used to register your custom Gradle plugin:

gradlePlugin {
    plugins {
        register("frameworkJvmPublisher") {
            id = "framework.jvm.publisher"
            implementationClass = "JvmLibraryPublisherConventionPlugin"
        }
    }
}
Explanation:
  • gradlePlugin {}Β Block:
    This block is used to define and configure custom plugins within your Gradle project. It specifies how the plugin should be registered and what class implements its functionality.
  • plugins {}Β Block:
    Inside this block, you can register multiple plugins by specifying an ID and an implementation class.
  • register(“frameworkJvmPublisher”) {}:
    This line registers a new plugin under the nameΒ "frameworkJvmPublisher". This is a unique name used within theΒ gradlePluginΒ block for reference purposes.
  • id = “framework.jvm.publisher”:
    TheΒ idΒ is the unique identifier for the plugin. This ID is used in theΒ pluginsΒ block of a Gradle project to apply the plugin. For example, a project would useΒ id("framework.jvm.publisher")Β to apply this plugin.
  • implementationClass = “JvmLibraryPublisherConventionPlugin”:
    This specifies the fully qualified name of the class that implements the plugin. In this case, theΒ JvmLibraryPublisherConventionPluginΒ class contains the logic that will be executed when the plugin is applied to a project.

Before concluding, let’s also include a plugin specifically designed for Android libraries. This plugin helps streamline the process of publishing Android libraries by automating the necessary configurations.

class AndroidLibraryPublisherConventionPlugin : Plugin<Project> {
    override fun apply(target: Project) {
        with(target) {
            with(pluginManager) {
                apply("maven-publish")
            }
            configureAndroidModulePublishing()
            configure<PublishingExtension> {
                afterEvaluate {
                    publications {
                        create<MavenPublication>(
                            name = "androidFramework",
                            configuration = {
                                artifactId = moduleInfo.artifactId
                                groupId = moduleInfo.groupId
                                version = version
                                from(components["release"])
                            }
                        )
                    }
                    val nexusPropertiesFile = rootProject.file("nexus-config.properties")
                    val nexusProperties = Properties()
                    nexusProperties.load(FileInputStream(nexusPropertiesFile))
                    repositories {
                        maven {
                            name = nexusProperties["name"] as String
                            url = uri(nexusProperties["url"] as String)
                            credentials {
                                username = nexusProperties["username"] as String
                                password = nexusProperties["password"] as String
                            }
                        }
                    }
                }
            }
        }
    }
}
Final Thoughts: The Power of Custom Gradle Plugins in Streamlining Your Workflow

Creating a custom Gradle plugin might seem daunting at first, but the benefits far outweigh the initial effort. By automating and centralizing your library publishing process, you can save time, reduce errors, and maintain consistency across all your projects. With the steps outlined in this guide, you are now equipped to build and customize your plugins, tailored specifically to your needs.

Remember, every challenge in development is an opportunity to create something better. If you encounter any issues along the way or need further assistance, feel free to reach out. Happy coding! πŸš€

Eager to Enhance Your Dependency Management?

Unlock the full potential of Gradle Version Catalog and Convention Plugins to optimize your Android project’s dependency management. Transform your development workflow and put an end to version conflicts and compatibility headaches. If you’re looking to learn more about Version Catalog, Convention Plugins, and other advanced Gradle techniques, check out ourΒ ComposeNewsΒ project, where we’ve implemented these concepts extensively. Dive into the code and see how these tools can revolutionize your Android development process!

This article is previously published on proandroiddev.com

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
It’s one of the common UX across apps to provide swipe to dismiss so…
READ MORE
blog
Hi, today I come to you with a quick tip on how to update…
READ MORE
blog
Automation is a key point of Software Testing once it make possible to reproduce…
READ MORE
blog
Drag and Drop reordering in Recyclerview can be achieved with ItemTouchHelper (checkout implementation reference).…
READ MORE
Menu