GitLab CI template for Gradle¶
This project implements a GitLab CI/CD template to build, test and analyse your Gradle-based projects.
Usage¶
This template can be used both as a CI/CD component
or using the legacy include:project
syntax.
Use as a CI/CD component¶
Add the following to your .gitlab-ci.yml
:
include:
# 1: include the component
- component: $CI_SERVER_FQDN/to-be-continuous/gradle/gitlab-ci-gradle@2.8.0
# 2: set/override component inputs
inputs:
image: "registry.hub.docker.com/library/gradle:jdk11" # ⚠ this is only an example
Use as a CI/CD template (legacy)¶
Add the following to your .gitlab-ci.yml
:
include:
# 1: include the template
- project: 'to-be-continuous/gradle'
ref: '2.8.0'
file: '/templates/gitlab-ci-gradle.yml'
variables:
# 2: set/override template variables
GRADLE_IMAGE: "registry.hub.docker.com/library/gradle:jdk11" # ⚠ this is only an example
Global configuration¶
The Gradle template uses some global configuration used throughout all jobs.
Input / Variable | Description | Default value |
---|---|---|
image / GRADLE_IMAGE |
The Docker image used to run Gradle set the version required by your project |
registry.hub.docker.com/library/gradle:latest |
cli-opts / GRADLE_CLI_OPTS |
Additional Gradle options used on the command line | None |
cli-bin / GRADLE_CLI_BIN |
The location of the gradle binary. If you prefer using a gradle wrapper you should override this (for e.g. gradlew ) |
gradle |
user-home / GRADLE_USER_HOME |
The gradle user home | $CI_PROJECT_DIR/.gradle |
daemon / GRADLE_DAEMON |
Whether to use or not gradle daemon | false |
project-dir / GRADLE_PROJECT_DIR |
Gradle project root directory | . |
As you can see the GRADLE_USER_HOME
is set to a directory inside $CI_PROJECT_DIR
. This will allow you to do some gradle caching
(declared in all gradle jobs) but also to provide a custom gradle.properties
file in your directory.
You don't need to provide proxy and daemon properties in your gradle.properties
as they will be set by each gradle job (they won't be overwritten if you do so).
Template supports http_proxy
, https_proxy
and no_proxy
that will be put in gradle.properties
.
For example, if you provide:
variables:
http_proxy: "http://the-proxy:8080"
https_proxy: "http://the-proxy:8080"
no_proxy: "localhost,my.neighbour.service"
and if you kept default GRADLE_DAEMON
variable and did not specify proxy and daemon properties, the properties added to $GRADLE_USER_HOME/gradle.properties
will be:
org.gradle.daemon=false
systemProp.http.proxyHost=the-proxy
systemProp.http.proxyPort=8080
systemProp.http.nonProxyHosts=localhost|my.neighbour.service
systemProp.https.proxyHost=the-proxy
systemProp.https.proxyPort=8080
systemProp.https.nonProxyHosts=localhost|my.neighbour.service
Jobs¶
Build jobs¶
The Gradle template features a single job gradle-build
that performs build (including tests) at once.
This stage is performed in a single job for optimization purpose (it saves time) and also
for test jobs dependency reasons.
It uses the following variable:
Input / Variable | Description | Default value |
---|---|---|
build-args / GRADLE_BUILD_ARGS |
Gradle arguments for the build & test job | build |
About Code Coverage¶
You can in your build.gradle
file apply some code coverage such as jacoco.
For example, by doing this in your build file (more information about the jacoco plugin ):
plugins {
//...
id 'jacoco'
//...
}
//...
jacoco {
toolVersion = "0.8.3"
}
jacocoTestReport {
reports {
// mandatory as it will be read by the pipeline
csv.enabled true
// optional for gitlab-ci pipeline yet required if you need sonar analysis
xml.enabled true
}
}
By adding the plugin, the template will automatically add jacocoTestReport
and jacocoTestCoverageVerification
tasks to the build
phase.
The jacoco coverage display in gitlab uses the following variable
Input / Variable | Description | Default value |
---|---|---|
jacoco-csv-report / JACOCO_CSV_REPORT |
Name of report | jacocoTestReport.csv |
gradle-sonar
job — SonarQube analysis¶
This job is disabled by default and performs a SonarQube analysis of your code.
The job is bound to the test
stage and uses the following variables:
Input / Variable | Description | Default value |
---|---|---|
sonar-host-url / SONAR_HOST_URL |
SonarQube server url | none (disabled) |
SONAR_TOKEN |
SonarQube authentication token | none |
sonar-base-args / SONAR_BASE_ARGS |
SonarQube analysis arguments | sonar -Dsonar.links.homepage=${CI_PROJECT_URL} -Dsonar.links.ci=${CI_PROJECT_URL}/-/pipelines -Dsonar.links.issue=${CI_PROJECT_URL}/-/issues |
sonar-quality-gate-enabled / SONAR_QUALITY_GATE_ENABLED |
Set to true to enable SonarQube Quality Gate verification.Uses sonar.qualitygate.wait parameter (see doc). |
none (disabled) |
Dependency-check¶
The Gradle template features a job gradle-dependency-check
that performs a manual Dependency-Check analysis.
It is bound to the test
stage and use the following variable :
Input / Variable | Description | Default value |
---|---|---|
GRADLE_DEPENDENCY_CHECK_DISABLED |
Set to true to disable this job |
none (enabled) |
dependency-check-task / GRADLE_DEPENDENCY_CHECK_TASK |
Name of the gradle task launching the analysis | dependencyCheckAnalyze |
A Dependency-Check is a quite long operation and therefore the job is configured to be ran manually by default (overridable).
You need to configure your build.gradle
to include the Dependency-Check module, the exemples below can help you kickstart the use of the plugin
Here is a generic configuration :
buildscript {
repositories {
mavenCentral()
// ...
}
dependencies {
// ...
classpath 'org.owasp.dependency-check-gradle:5.3.2.1'
}
}
apply plugin: 'org.owasp.dependencycheck'
dependencyCheck {
formats=['HTML', 'JSON']
analyzers {
assemblyEnabled=false
retirejs {
enabled=false
}
}
}
Managing NVD API requests limit¶
Dependency-Check fetches its vulnerabilities database from the National vulnerability Database (NVD) API, that has requests limit. If nothing is done, fetching the database will be very slow.
There are two options to solve this:
- Use an NVD API key (this will increase the rate limit and therefore reduce the execution time of Dependency-Check)
- Request an NVD API key,
- Set the
NVD_API_KEY
variable as a (masked) GitLab variable, - Configure the API key in your Gradle file:
// ... dependencyCheck { // ... nvd { // NVD_API_KEY got from environment (set it in your GitLab CI/CD variables) apiKey = System.getenv("NVD_API_KEY") } // ... }
- Setup a local mirror of NVD Data files (this solution might be required if your GitLab runners cannot reach the NVD API).
- You can use the vulnz tool to automate the sync of Data files from the NVD API. It fetches the vulnerabilities and store them as data feeds (the data feeds will be stored as JSON files with the NVD Vulnerability Data API version 2.0 schema).
You should store those files in a place reachable by your GitLab runners. - Then simply configure this URL as a
datafeedUrl
in your Gradle file:// ... dependencyCheck { // ... nvd { datafeedUrl="https://URL-OF-LOCAL-CACHE-NVD-DATA-FEED" } // ... }
- You can use the vulnz tool to automate the sync of Data files from the NVD API. It fetches the vulnerabilities and store them as data feeds (the data feeds will be stored as JSON files with the NVD Vulnerability Data API version 2.0 schema).
If you're using an Artifactory as a repository/mirror, you might need to add a repositories
block and add an artifactory
block in the job definition.
Here is an example (change the urls accordingly):
buildscript {
// ...
repositories {
maven {
url "https://artifactory.acme.host/artifactory/maven-virtual-repo"
}
}
}
// ...
dependencyCheck {
// ...
analyzers {
artifactory {
enabled=true
url="https://artifactory.acme.host/artifactory"
}
// ...
}
}
The injection of systemProp.http(s).nonProxyHosts
(through either custom gradle.properties
file or no_proxy
environment variable) into gradle.properties
crashes for version 5.3.x below 5.3.2.1 (bug) and is ignored for 5.3.2.1 version of plugin (bug) so if you need to specify exceptions for your proxy for this task, you should either :
- use the 5.2.4 version or below
- or removing injection for the task and add a
proxy
block like this :
dependencyCheck {
// ...
proxy {
server=my.proxy.org
port=8080
nonProxyHosts=["127.0.0.1", "mycorp.com"]
}
}
More info on how you can configure the gradle Dependency-Check plugin can be found in the official documentation
Software Bill Of Materials¶
This job generates a SBOM file listing all dependencies using cyclonedx-gradle-plugin.
It is bound to the test
stage, and uses the following variables:
Input / Variable | Description | Default value |
---|---|---|
sbom-disabled / GRADLE_SBOM_DISABLED |
Set to true to disable this job |
none |
sbom-version / GRADLE_SBOM_VERSION |
Version of the cyclonedx-gradle-plugin used for SBOM analysisWhen unset, the latest version will be used |
none |
maven-plugin-url / GRADLE_MAVEN_PLUGIN_URL |
Maven Repository that is used to download the cyclonedx-gradle-plugin . No trailing slash. |
https://plugins.gradle.org/m2 |
This job injects cyclonedx plugin in your project. This can be disabled by defining the plugin in your build.gradle
and setting $GRADLE_SBOM_VERSION
variable to disabled
.
plugins {
id 'org.cyclonedx.bom' version '1.7.2'
}
Publish jobs¶
Currently the pipeline exposes two manual jobs of publication:
gradle-snapshot
: launched on branchesgradle-release
: launched on tags
Both jobs use the following variables
Input / Variable | Description | Default value |
---|---|---|
publish-version / GRADLE_PUBLISH_VERSION |
The value is propagated as gradle properties named version . It should be used in your publish task |
${CI_COMMIT_REF_SLUG}-SNAPSHOT |
publish-args / GRADLE_PUBLISH_ARGS |
The publish task that is invoked | publish |
no-publish / GRADLE_NO_PUBLISH |
Set this variable if you wish to disable publish phase | None |
If you keep default value for GRADLE_PUBLISH_VERSION
, it will have the following values
- for a snapshot:
<branch name>-SNAPSHOT
- for a release:
<tag name>
Here is an example of a build.gradle
publishing on Artifactory:
plugins {
// ...
id 'maven-publish'
}
def snapshotsRepoUrl = "https://artifactory.acme.host/artifactory/maven-snapshot-repo/"
def releasesRepoUrl = "https://artifactory.acme.host/artifactory/maven-release-repo/"
task sourceJar(type: Jar) {
classifier "sources"
}
task dokkaJavadoc(type: org.jetbrains.dokka.gradle.DokkaTask) {
outputFormat = 'javadoc'
outputDirectory = javadoc.destinationDir
inputs.dir 'src/main/kotlin'
}
task dokkaJavadocJar(type: Jar, dependsOn: dokkaJavadoc) {
classifier = 'javadoc'
from javadoc.destinationDir
}
publishing {
repositories {
maven {
url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl
authentication {
basic(BasicAuthentication)
}
credentials(PasswordCredentials) {
// Artifactory credentials got from environment (set it in your GitLab CI/CD variables)
username System.getenv("ARTIFACTORY_USERNAME")
password System.getenv("ARTIFACTORY_PASSWORD")
}
}
}
publications {
maven(MavenPublication) {
from components.kotlin
artifact(sourceJar)
artifact(dokkaJavadocJar)
}
}
}