Ahead-of-time JitPack Builds with Custom GroupId via GitHub Actions
Tags: java, github, tutorial, kotlin
Published: 2022-08-11 12:45:47

This post is a continuation of a series of tips and tricks for [JitPack](https://jitpack.io). The [first post of this series](https://dev.to/cicirello/how-to-configure-jitpack-for-recent-jdk-versions-4pek) concerned the unique Maven repository called JitPack that builds artifacts of dependencies on-demand from a git repository, and explained how to configure your repository for successful builds with recent JDK versions, essentially covering what I had to do to get JitPack builds to work for one of my libraries that requires JDK 17. *Disclaimer: I am not affiliated with JitPack, and this is not official documentation.*

In this installment, we'll see how to use your preferred Maven `groupId` with JitPack, rather than JitPack's default of `com.github.USER` (or similar for other git hosts). More importantly, we'll see how to do so effectively with ahead-of-time builds. JitPack is designed for on-demand builds, but does include a webhook that you can configure in your GitHub or Bitbucket repository to enable ahead-of-time builds. However, there isn't a built-in way of doing the same for a customized `groupId`, such as the conventional reverse domain name. The trick explained in this post may also be useful to those using JitPack with a git host not currently supported by JitPack's webhooks. 

Additionally, the trick explained in this post utilizes GitHub Actions workflows, but you should be able to adapt the technique to CI/CD frameworks of other git hosts, or even from a shell script. 

__Table of Contents:__ The rest of this post is organized as follows:
* [How to use JitPack with a customized groupId](#how-to-use-jitpack-with-a-customized-groupid)
* [How to build ahead-of-time with a customized groupId](#how-to-build-aheadoftime-with-a-customized-groupid)
* [GitHub Actions workflows for ahead-of-time JitPack builds](#github-actions-workflows-for-aheadoftime-jitpack-builds)
* [Where you can find me](#where-you-can-find-me)


## How to use JitPack with a customized groupId

Maven Central, and other Maven repositories, typically use the reverse of a domain or subdomain controlled by a library's publisher as the Maven `groupId`. This is the conventional naming scheme for `groupId`. For example, for my libraries, I use `org.cicirello` as the `groupId` on Maven Central for all of my libraries. I can also, if I wanted to, use any `org.cicirello.SUBDOMAIN` on Maven Central, since I've verified ownership of the domain itself.

On JitPack, the `groupId` is instead based on a combination of git host (e.g., GitHub) and userid on that git host. This is because JitPack builds artifacts on-demand from the source repository the first time anyone imports a specific version via git tag, branch, commit hash, etc. The [prior post of this series](https://dev.to/cicirello/how-to-configure-jitpack-for-recent-jdk-versions-4pek) has a more detailed explanation of how JitPack works.

For consistency with artifacts published on Maven Central, or elsewhere, it would be nice to use the reverse domain approach on JitPack as well. And it turns out, JitPack does support this. You can find the details in the [JitPack documentation](https://jitpack.io/docs/). To accomplish this, you configure a TXT record with your DNS provider, mapping `git.yourdomain.extension` to `https://github.com/USERNAME`. When you, or someone else, references your library via a `groupId` equal to your reverse domain, JitPack checks for such a TXT record with your DNS provider, discovering the corresponding GitHub (or other git host) repository. It essentially creates an alias on JitPack.

The result is that via JitPack, your library can be imported either via JitPack's default git host / repository name approach, or with your reverse domain. An example for one of my libraries is as follows. 

The default way of importing from JitPack involves adding the following to the `pom.xml` to use this as a dependency:

```XML
<dependency>
  <groupId>com.github.cicirello</groupId>
  <artifactId>chips-n-salsa</artifactId>
  <version>5.2.0</version>
</dependency>
```

But once the domain is configured, the following works as well.

```XML
<dependency>
  <groupId>org.cicirello</groupId>
  <artifactId>chips-n-salsa</artifactId>
  <version>5.2.0</version>
</dependency>
```

The latter is preferred since it matches the dependency specification required if importing from Maven Central. Conveniently for me, the repository name for this library is identical to the `artifactId` that I was already using on Maven Central, because JitPack doesn't provide a mechanism for using an `artifactId` that differs from repository name.

Note however that JitPack has an odd behavior that each of the above cases is treated as if they were distinct. The first time the artifact is imported using `com.github.cicirello` will require a build, and the first time that anyone imports using `org.cicirello` will also require a build. Thus, it is not exactly creating an alias. Both will build the library, producing jar files with identical contents, but with different names to the jar files, as if they were distinct artifacts.


## How to build ahead-of-time with a customized groupId

Although JitPack is designed to build artifacts on-demand, once built, future requests by you or others for the same version no longer require a build. Thus, although the first import suffers the delay of waiting for a build, subsequent imports are as fast as most other Maven repositories (note: I have not actually timed this, but there is no human-discernible difference that I can see once built).

If you are simply using the git host / username combination for the `groupId`, then JitPack provides a webhook that you can configure on GitHub or Bitbucket. The details of [JitPacks webhooks](https://jitpack.io/docs/BUILDING/) can be found in JitPack's documentation. Once configured, each push to your default branch (e.g., `main`) will utilize the webhook causing JitPack to build the current commit. In this way, the first actual import won't suffer from a delay awaiting a build. However, as we saw in the previous section, JitPack treats the custom reverse domain as a separate `groupId` from the git host based version. So those webhooks won't execute the ahead-of-time build against the custom reverse domain `groupId`. It also appears to only support GitHub and Bitbucket, so if your project is hosted elsewhere those webhooks may not be an option.

### Trick 1: 

After each push to the default branch, use curl to trick JitPack into thinking we're importing the latest repository snapshot, but using your reverse domain. JitPack will start a build. You don't actually want the artifacts. You just want to start a build. So use curl with a timeout to avoid waiting for the build.

Here's an example, assuming that your reverse domain is `com.example`, with a repository name (and thus `artifactId`) of `REPOSITORY`, and a default branch of `main`:

```Shell
curl -s -m 30 https://jitpack.io/com/example/REPOSITORY/main-SNAPSHOT/
```

The `-m 30` above is a 30 second timeout. I've used a bit of trial-and-error on the timeout length. I started with 1 second, which often didn't start the build. Next, I tried 10 seconds, which did often start the build, but occasionally failed to get the build started on JitPack. 30 seconds seems to work consistently to get the build started.

Be aware that the above curl will actually "fail" with a non-zero error code once it times out (unless your project builds in under 30 seconds). Don't worry about it. You don't want the artifacts now anyway. And even if you did, the above curl won't give them to you. It will just serve a page where they are listed. You just want the artifacts to be available later. Assuming you use the above in a script, you might actually want something like what follows to avoid failing your entire job when curl times out.

```Shell
curl -s -m 30 https://jitpack.io/com/example/REPOSITORY/main-SNAPSHOT/ || true
```

### Trick 2: 

On each release of your library, use curl to trick JitPack into thinking we're importing the latest release via its corresponding version number. If the release tag is `v1.2.3`, then execute the following curl dropping the "v" (for consistency with other Maven repositories). This still assumes that your reverse domain is `com.example`, with a repository name of `REPOSITORY`, so just modify this for your real reverse domain and repository name.

```Shell
curl -s -m 30 https://jitpack.io/com/example/REPOSITORY/1.2.3/ || true
```

The URL in this example is where your release's artifacts will live once they are built. Requesting the URL to that directory gets the build started.


## GitHub Actions workflows for ahead-of-time JitPack builds

If your project is on GitHub, you can put all of the above into one or more GitHub Actions workflows to automate this. Here's how I'm doing it.

### On pushes to default branch: 

On each push to the default branch (assumed `main` in this example), I'm using the following workflow (just fill in the details of your reverse domain, and default branch name, where relevant). This sample workflow makes the same assumptions as the previous section (e.g., that your reversed domain is `com.example`, etc). See [jitpack-build.yml](https://github.com/cicirello/Chips-n-Salsa/blob/master/.github/workflows/jitpack-build.yml) in one of my repositories for a live example.

```YAML
name: jitpack-build

on:
  push:
    branches: [ main ]

jobs:
  jitpack:

    runs-on: ubuntu-latest

    steps:

    - name: Request main-SNAPSHOT from JitPack
      run: |
        # timeout in 30 seconds to avoid waiting for build
        curl -s -m 30 https://jitpack.io/com/example/REPOSITORY/main-SNAPSHOT/ || true
```

Most GitHub workflows usually need an `actions/checkout` step, but we don't need that here because we're not actually doing anything with the code from our repository here.


### On releases: 

I use GitHub release events to start a workflow that builds and deploys artifacts to Maven Central as well as to GitHub Packages. At the end of that workflow, I added a step to handle the ahead-of-time builds on JitPack. Below is an abridged version of that workflow that only shows what is necessary for ahead-of-time builds on JitPack. It runs when you create a release. The first step removes the v from the release tag. After which, we use the curl trick.

```YAML
name: Release

on:
  release:
    types: [created]

jobs:
  publish:

    runs-on: ubuntu-latest

    steps:
    - name: Get the release version, removing the v from the tag
      id: get_version
      run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\/v/}

    - name: Request release from JitPack to trigger build
      run: |
        JITPACK_URL="https://jitpack.io/com/example/REPOSITORY/${{ steps.get_version.outputs.VERSION }}/"
        # timeout in 30 seconds to avoid waiting for build
        curl -s -m 30 ${JITPACK_URL} || true
```

The complete live workflow from my project is found [here](https://github.com/cicirello/Chips-n-Salsa/blob/master/.github/workflows/maven-publish.yml), if you want to see how I've incorporated this into a larger workflow that builds and deploys to Maven Central and GitHub Packages. That workflow is found within the following repository:

{% github cicirello/Chips-n-Salsa %}


## Where you can find me

On the Web:

{% embed https://www.cicirello.org/ %}

Follow me here on DEV:

{% user cicirello %}

Follow me on GitHub:

{% github cicirello/cicirello %}

| [![Liberapay](https://actions.cicirello.org/images/Liberapay.svg)](https://liberapay.com/cicirello) | [![Ko-Fi](https://actions.cicirello.org/images/ko-fi.svg)](https://ko-fi.com/cicirello) |
| :---: | :---: |
| [![GitHub Sponsors](https://actions.cicirello.org/images/github-sponsors.svg)](https://github.com/sponsors/cicirello) | |