Github Package
We recently encountered the problem that we needed to patch a 3rd party library. Now this library lives in an eco-system of many dependencies and we just wanted to do some small changes to two of the modules. The question is, how do you patch a library and then make it available for your other projects to use?
Well, in our case we’re using JVM. Our projects are using Gradle, and the library we want to patch is using Maven.
The simplest scenario is to solve this problem on the local developer machine. If you run the command mvn install
in a project folder, you publish that project into your local maven repository. If you then in gradle make sure to have the following code:
repositories {
mavenLocal()
mavenCentral()
}
Note that the order here is important, this makes sure we try to find our packages first in your local maven and then in the central maven repository.
You might wonder why we do the install. Wouldn’t it be possible to just run mvn package
and then import the jar file into your project? If that would be possible it would be really nice, then you could have the dependency checked into your repository and it would work wherever cloned. Well, gradle supports this as well, it looks like this:
repositories {
flatDir {
dirs 'lib'
}
mavenCentral()
}
where lib
would be the place where we store our jar files. This works, BUT. There’s a difference between how gradle handle imports maven
imports and flatDir
dependencies. In the case of flatDir
gradle doesn’t resolve any transitive dependencies. Which means you’d have to manually reference all of the transitive dependencies in your build.
What if you bundled all the dependencies into a fat jar? It does seem like a plausible solution, When I tried it I ran into problems with Quarkus
though, which performs some optimizations on the code compile time.
Okay. Back to our dependency again. Now it’s working on your developer machine (cool!). But you problably have a build system that also needs to run this build, and you probably can’t or don’t want to run mvn install
on that machine. So how will this build system get access to your edited depdency?
You could deploy the dependency to Maven Central under a different name. But (I believe) getting an artifact published there requires some review process. I mean that would make sense because if you can publish any code there you might really be able to cause to harm if unknowing users start using your potentially malignant code.
Github supports publishing packages, and that’s the route that I selected. First you then need to configure your maven environment to publish to your github project. In the ~/.m2/settings.xml
you add something like:
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
http://maven.apache.org/xsd/settings-1.0.0.xsd">
<activeProfiles>
<activeProfile>github</activeProfile>
</activeProfiles>
<profiles>
<profile>
<id>github</id>
<repositories>
<repository>
<id>central</id>
<url>https://repo1.maven.org/maven2</url>
</repository>
<repository>
<id>github</id>
<url>https://maven.pkg.github.com/OWNER/REPO</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
</profile>
</profiles>
<servers>
<server>
<id>github</id>
<username>USER_NAME</username>
<password>TOKEN</password>
</server>
</servers>
</settings>
Where you configure OWNER
to be the owner of the repo, REPO
is the repository itself. USER_NAME
is your username, and TOKEN
is a token you need to configure to enable access to the repository. It makes sense that you need the token to publish artifacts. Unfortunately Github doesn’t support unauthorized pulling of packages, which means you also need to have this token when pulling from the repository.
For gradle on your local machine you can do this by providing the token in a gradle.properties
file in your gradle folder:
gpr.token=TOKEN
The name gpr.token
could be anything, it’s short for github private repository. In your build.gradle file you then add:
repositories {
maven {
url("https://maven.pkg.github.com/OWNER/REPO")
credentials(HttpHeaderCredentials) {
name = "Authorization"
value = "Bearer ${project.findProperty("gpr.token")}"
}
authentication {
register("header", HttpHeaderAuthentication)
}
}
mavenCentral()
}
This will provide the token into the authorization and your now get the package from the new place. But what about Jenkins? It doesn’t have a gradle.properties file. Instead, by using the Credentials
and Credentials Bindings
plugins you can inject the token using an environment variable. Your gradle code changes to :
repositories {
maven {
url("https://maven.pkg.github.com/OWNER/REPO")
credentials(HttpHeaderCredentials) {
name = "Authorization"
value = "Bearer ${project.findProperty("gpr.token") ?: System.getenv("GPR_TOKEN")}"
}
authentication {
register("header", HttpHeaderAuthentication)
}
}
mavenCentral()
}
And your Jenkinsfile adds the following:
pipeline {
agent any
environment {
GPR_TOKEN = credentials('gpr.token')
}
where you have to save your credentials as a secret text
with the name gpr.token