A Jenkins shared library with a collection of pipeline steps and functionality useful when setting up a Jenkins CI pipeline for Unity projects. In addition to Unity specific functionality, there is also a selection of more generic functions and steps that have proven useful when setting up continuous integration for game dev projects.
Function | Summary |
---|---|
unityBuildNumber | Get the build number set for the Standalone build in Unity. |
unityCodeCoverageReport | Runs Unity's Code Coverage reporter. |
unityCodeCoverageReportSummary | Parses Summary.xml from the Unity generated code coverage report. |
unityPublishCodeCoverageHTMLReport | Publishes the Unity Code Coverage Report to the job page. |
unityTestRunner | Uses Unity's Test Runner to execute the test suite. |
unityTestRunnerReport | Create a report from NUnit tests. |
tests.summary | Get Junit test result summary. |
Function | Summary |
---|---|
getBuildType | Retrieves the build type for the current running build. |
setJobDisplayName | Sets the display name based on the build type. |
git.branchName | Get the current branch name. |
git.commitSha | Get the full git commit sha for current commit. |
git.commitShaShort | Get the short 7 character git commit sha for current commit. |
git.getTrailerValue | Get the trailer value for the specified token. |
github.pullRequestComment | Creates or updates the Jenkins bot issue comment for the pull request. |
github.ownerRepo | Get the owner/repo part from the project's git url. |
github.pullRequest | Returns the pull request the current commit belongs to. |
github.issueComments | Retrieves all comments for an issue/pull request. |
github.createIssueComment | Create issue comment. |
github.updateIssueComment | Update issue comment. |
github.createCheckRun | Create a check run. |
github.updateCheckRun | Update a check run. |
influxdb.addMeasurement | Add a measurement for writing to InfluxDB. |
influxdb.write | Write all added measurements to InfluxDB. |
timers.start | Creates and starts a new timer with the specified name. |
timers.stop | Stops the timer with the specified name. |
timers.getDuration | Gets the duration in ms between start and stop for the timer with the specified name. |
utils.getDirectorySize | Get the size of the provided directory in bytes. |
utils.getFileSize | Get the size of the provided file in bytes. |
Read the Extending with Shared Libraries > Using Libraries section in Jenkins User Handbook for full instruction how to add a shared library to a Jenkins instance and how to access the functionality from a Jenkinsfile
.
When using the library in a Jenkinsfile, use a version specifier, to avoid surprises if future updates of the library introduces breaking changes.
@Library('[email protected]') _
Several functions in this library uses functions from the Jenkins plugin Pipeline Utility Steps. Make sure this plugin is installed in the Jenkins instance using this shared library.
For the GitHub specific steps a GitHub app must be registered to obtain the access token for Jenkins to communicate with the GitHub API. It also requires the Jenkins plugins
An overview of the pipeline steps and functionality this shared library exposes.
This function parses Unity's Project Settings and returns the build number that has been set
for the Standalone build in Unity's Project Settings > Player > Build
.
steps {
echo "Unity build number: ${unityBuildNumber()}"
}
Runs Unity's Code Coverage reporter. This assumes the Unity project has the Code Coverage package installed.
The reporter takes two parameters, unityCodeCoverageReport(assemblyFilters, pathFilters)
.
post {
always {
// Generate Reports from Unit Tests and Code Coverage.
unityCodeCoverageReport('+Bitbebop.Blitloop,+Dagobah.*,+Cosmos.*', '-**/Assets/Scripts/Input/GameInput.cs')
}
}
Parses Summary.xml
from the Unity generated code coverage report and returns the summary as an object with the summary items as properties. This can then be posted to Discord, Slack, a time series database, or any other destination.
Available properties:
- coveredLines
- uncoveredLines
- coverableLines
- totalLines
- lineCoverage
def codeCoverage = unityCodeCoverageReportSummary()
echo "${codeCoverage.lineCoverage}"
Publishes the Unity Code Coverage Report so it's available from the job page.
post {
always {
unityPublishCodeCoverageHTMLReport()
}
}
Uses Unity's Test Runner to execute the test suite.
steps {
unityTestRunner("EditMode", "iOS", true, env.CODE_COVERAGE_ASSEMBLY_FILTER, env.CODE_COVERAGE_PATH_FILTERS)
}
Creates a merged report from Unitys PlayMode and EditMode test files.
post {
always {
unityTestRunnerReport()
}
}
Parses the results from the Unity test runner and returns the summary as an object. This can then be posted to Discord, Slack, a time series database, or any other destination.
Available properties:
- total
- failed
- skipped
- passed
def testSummary = tests.summary()
echo "${testSummary.passed}"
Retrieves the build type for the current running build based on branch name prefixes. This is useful to rely on branch names to determine the build logic.
- internal:
/feature/*
- testflight:
/testflight/*
- release:
/release/*
- standard:
*
steps {
echo "Build type: ${getBuildType()}"
}
Sets name for the running job based on git branch name.
steps {
setJobDisplayName()
}
Get the current branch name.
steps {
echo "Git branch name: ${git.branchName()}"
}
Get the full git commit sha for current commit.
steps {
echo "Git commit SHA: ${git.commitSha()}"
}
Get the short 7 character git commit sha for current commit.
steps {
echo "Git short commit SHA: ${git.commitShaShort()}"
}
Get the trailer value for the specified token for the current commit.
steps {
echo "Git commit message trailer value : ${git.getTrailerValue('some-token')}"
}
Creates or updates the Jenkins bot issue comment for the pull request.
This can be used to have a comment in the PR that Jenkins updates with current information about the build.
steps {
github.pullRequestComment(stringWithComment, 'jenkins[bot]')
}
Get the <owner/repo> part from the project's git url.
def owner = github.ownerRepo()
Returns the pull request the current commit belongs to.
def pr = github.pullRequest()
Retrieves all comments for an issue/pull request.
def comment = github.issueComments(42)
Create issue comment.
github.createIssueComment(42, "some comment")
Update issue comment.
github.updateIssueComment(42, "some updated comment")
Create a check run.
Start a check run for a specific build build.
steps {
script {
github.createCheckRun('Build iOS', 'queued')
}
}
Update a check run.
steps {
script {
github.updateCheckRun('Build iOS', 'in_progress')
}
}
post {
failure {
script {
github.updateCheckRun('Build iOS', '', 'failure')
}
}
success {
script {
github.updateCheckRun('Build iOS', '', 'success')
}
}
Add a measurement for writing to InfluxDB. The data type for each field in teh measurement is set with a string.
value | Data type |
---|---|
"f" |
float |
"i" |
integer |
"s" |
string |
influxdb.addMeasurement("build",
[
platform: platform,
build_type: buildType
],
[
code_coverage: ["f", coverage.lineCoverage],
size: ["i", size],
duration_unity: ["i", timers.getDuration("Unity.${platform}")],
duration_xcode: ["i", timers.getDuration("Xcode.${platform}")],
jenkins_build_number: ["i", currentBuild.number],
unity_build_number: ["i", unityBuildNumber()],
commit_sha: ["s", env.GIT_COMMIT_SHA_SHORT]
]
)
Write all added measurements to InfluxDB.
withCredentials([string(credentialsId: 'influxdb', variable: 'INFLUXDB_TOKEN')]) {
influxdb.write(env.INFLUXDB_HOST, env.INFLUXDB_ORG, env.INFLUXDB_BUCKET, INFLUXDB_TOKEN)
}
Creates and starts a new timer with the specified name.
Multiple timers can be created and started to store the duration for different steps of the pipeline.
timers.start("unity.${platform}")
buildUnity(platform)
timers.stop("unity.${platform}")
Stops the timer with the specified name.
timers.start("unity.${platform}")
buildUnity(platform)
timers.stop("unity.${platform}")
Gets the duration in ms between start and stop for the timer with the specified name.
echo "Unity iOS Build Duration: ${timers.getDuration('unity.iOS')}"
Get the size of the provided directory in bytes.
def size = utils.getDirectorySize('build.app')
Get the size of the provided file in bytes.
def size = utils.getFileSize('build.ipa')
Gradle is used to test the pipeline during development.
The test suite is executed with:
./gradlew test
Then gradle is executed for the first time in a session a daemon is started. Manage the daemon with these commands.
## See running daemons
gradle --status
## Stop running daemons
gradle --stop
Gradle needs Java runtime, and the version of Gradle used in this project uses Java 17. Install Java 17 with Homebrew and then set JAVA_HOME
in the terminal, for the session, in the terminal.
export JAVA_HOME=/usr/local/opt/openjdk@17
During development it can be useful to get output from tests to the console.
@Test
void foo_SomeBar_GetSomeBaz() {
println(someObjectToDebug)
}
Enable log info level to display println
statements in the test output.
./gradlew test --info