Guidance on implementing custom Gradle plug-in and tasks

Hopefully this is not a repeat question, but, being new to Gradle, I’m looking for guidance to either documentation and/or an example/tutorial that is beyond the standard “Hello, World” search results I keep finding.

I am interested in creating a plug-in for use in our CI system. The basic idea is to be able to retrieve build artifacts from our Jenkins server using a simple DSL syntax in a Gradle build file. For example,


apply plugin: 'jenkins-artifact'

jenkinsArtifact {
    // URL of the Jenkins server
    siteUrl = 'https://jenkins.server.net/'

    // Private RSA key to be used when connecting to the Jenkins CLI
    rsaKey = file('resources/id_rsa_JENKINS')

    // Where we are going to store the artifacts once downloaded;
    //     ${outputDir}/${job}/${artifactRelativePath}
    outputDir = file("${buildDir}/jenkins-artifacts")

    // One or more build artifacts, each described by the name of the
    // job that creates them, the particular build of that job, and a
    // regex pattern used to identify the item(s) to retrieve
    artifact [ job: 'Build_AppA', build: '42', namePattern: 'appA-(debug|release)\.apk$' ]
    artifact [ job: 'Build_AppB', build: 'lastSuccessfulBuild', namePattern: 'appB\.apk$' ]
}

The design is still rudimentary, and I am open to suggestions as to best practices. Thus far, I have a very basic skeleton laid out that includes the plugin (JenkinsArtifactPlugin), a download task (JenkinsArtifactDownloadTask) for doing the actual retrieval of the build artifact(s), and an extension/configuration class for the jenkinsArtifact block (JenkinsArtifactExtension), sans the artifact entries:

class JenkinsArtifactExtension {
    @Input
    def String siteUrl

    @InputFile
    def File rsaKey

    @OutputDirectory
    def File outputDir = new File("${project.buildDir}/jenkins-artifacts")

    //
    // artifacts??
    //
}

This is where I am presently blocked looking for the correct way to do the artifacts. I’ve Googled, checked Stackoverflow, went looking for books on Amazon, and I can’t seem to find something that gives decent guidance on how to implement this.

These are essentially dependencies for the build script, so should this be implemented using a configuration instead? Something like:

configurations {
    jenkinsArtifact
}

dependencies {
    jenkinsArtifact // ... what to put here since these are not described by GAV ...
}

Or is my initial sketch preferred? And, if so, what classes should I use to implement it?

The end result should be something I can query as part of the plug-in to create a JenkinsArtifactDownloadTask instance for each requested build artifact.

Any and all help is greatly appreciated. Thanks in advance,

– William

Hi William,

I am currently working on a series of guides that describe best practices and techniques for plugin development. I am not quite sure if they’d address your exact problem though I am sure it would help clearing up some design decisions. I will add a link to this post once they are out. Should be happening within the next 2 weeks.

For your implementation you’d have to decide whether you’d want to use Gradle’s dependency management system to download the files or an implementation that you write as part of a task action. I’d probably start with writing your own download implementation as I am not sure how specific that’ll look like.

What are the concrete steps to get there?

  1. Write a plugin implementation that creates the extension and an instance of your custom task type.
  2. The extension should only expose properties you want to make configurable through the DSL. BTW: I see you use input/output annotations for the extension properties. That won’t work as they can only be used for task properties and methods. You’ll have to move them there.
  3. In the action of the custom task type write your own download logic.
  4. Map the extension properties to the task properties. At the moment most plugin developers use a concept called convention mapping. However, that’s an internal API and basically not documented. In a nutshell, you’d use convention mapping to map the property and then access the property in the task action with the relevant getter method. Starting with Gradle 4.0 there will be a public API for it. For now I’d just go with convention mapping and then switch over.

I also started to write a canonical plugin example project though it will only work with Gradle 4.0 forward. Please be aware that it likely change a lot over the next couple of weeks.

Hope that helps for now. If that is not enough information please push your code to a GitHub repo and I can help you with a pull request.

Thanks, Benjamin. Looking forward to seeing your guides when they are ready.

I’m figuring that the dependency management system will not be an option, as trying to identify what to download is one of the biggest issues I’ve encountered. Most of our build jobs are nested in Jenkins “folders”, and this makes the REST API calls almost impossible. Compound that with the need for scanning the list of artifacts archived by the build for the one(s) desired, and doing this through a simple HTTP(S) download is nigh impossible. I was able to get the Jenkins CLI working to where I can identify what I want and download it, so for now that appears to be the plan of attack.

I did stumble across a Stackoverflow answer that looks sort of like what I am trying to do, and it may work for the time being. Will also look at the other links you posted.

This is very much an “alpha” level code base at the moment, and little more than a proof of concept. Will be posting to Github once I have something relatively workable.

Sounds good. Let me know if you need further assistance!

As promised we published two new guides that go into the details of designing and implementing plugins:

I hope you’ll find them useful. The guides should give you more information on how solve certain problems. There are more guides in the pipeline. Periodically check the guides section on gradle.org. Feedback welcome!

1 Like