Architecture - BredaUniversityGames/JenkinsLib GitHub Wiki
Each tool has its own vars/ script with implementation classes in src/com/buas/:
| Script | Methods | Purpose |
|---|---|---|
perforce.groovy |
perforce.sync() |
Perforce/Helix Core |
git.groovy |
git.sync() |
Git/GitHub |
ue5.groovy |
ue5.build(), ue5.test()
|
Unreal Engine 5 |
vs.groovy |
vs.build(), vs.test()
|
Visual Studio / MSBuild |
cmake.groovy |
cmake.build(), cmake.test(), cmake.pack(), cmake.workflow()
|
CMake / CTest / CPack |
steam.groovy |
steam.deploy() |
Steam deployment |
itch.groovy |
itch.deploy() |
itch.io deployment |
gdrive.groovy |
gdrive.deploy() |
Google Drive deployment |
epic.groovy |
epic.deploy() |
Epic Games Store deployment |
swarm.groovy |
swarm.review() |
Helix Swarm code reviews |
sentry.groovy |
sentry.upload() |
Sentry debug symbols |
discord.groovy |
discord.alert() |
Discord notifications |
stages.groovy |
stages { ... } |
Pipeline orchestrator |
matrix.groovy |
matrix(axes) { ... } |
Multi-axis build combinations |
only.groovy |
only(condition) { ... } |
Conditional stage filter |
Utilities:
| Script | Purpose |
|---|---|
log.groovy |
Logging (log(), log.warning(), log.error()) |
utilWin.groovy |
Windows path helpers |
utilZip.groovy |
Zip/unzip operations |
utilPython.groovy |
Python script runner |
When you write:
stages {
perforce.sync()
ue5.build()
steam.deploy()
discord.alert()
}Each method (e.g., steam.deploy()) creates an implementation class and registers a module with the orchestrator:
def deploy(Map overrides = [:]) {
def impl = new com.buas.deploy.Steam(this)
ModuleRegistry.register(
category: 'deploy',
name: 'Steam',
params: impl.pipelineParams(overrides),
execute: { params, ctx -> impl.execute(params, ctx) },
hasCleanup: false
)
}stages.groovy collects all registered modules, then executes them in order:
- Parameter collection — all module parameters are merged and registered with Jenkins
- Pre-matrix stages — modules registered before the matrix run by category order
- Matrix stages (if present) — each axis combination runs inner modules sequentially, with overridden params and isolated context per combination
- Post-matrix stages — modules registered after the matrix run by category order
- Deploy stages run in parallel — within each phase, multiple deploy targets execute simultaneously
-
Notifications run in the
finallyblock (always execute, even on failure) - Cleanup runs for modules that registered a cleanup closure
When no matrix() block is used, the pipeline runs identically to a single-phase execution (all modules are "pre-matrix").
matrix() and only() use ModuleRegistry.push() / pop() to capture modules registered inside their closures. push() places a sentinel marker on the registry; pop() returns all modules added after the most recent sentinel. This allows nested scoping — an only() inside a matrix() works correctly because each push/pop pair operates on its own sentinel.
A ctx map is passed through all stages, allowing modules to share data:
perforce.sync() → sets ctx.revision, ctx.changelist, ctx.vcsType
ue5.build() → sets ctx.buildConfig, ctx.buildPlatform, ctx.engineRoot
cmake.build() → sets ctx.buildEngine, ctx.cmakeBuildPreset (or ctx.buildConfig, ctx.buildPlatform, ctx.cmakeBuildDir)
ue5.test() → sets ctx.testResults
steam.deploy() → reads ctx.outputDir, ctx.buildPlatform
discord.alert() → reads ctx.revision, ctx.buildConfig, ctx.testResults
In a matrix build, each combination gets a cloned ctx with its own outputDir (${WORKSPACE}\Output\<value1>\<value2>\...). Changes to ctx inside one combination don't affect other combinations.
- Stateless modules — no module-level mutable state. All config passed via Map parameters.
-
Pluggable modules — add new implementations by creating a class in
src/com/buas/and avars/script. - Parallel deployments — independent deploy stages run simultaneously.
-
Secure credentials — all secrets use Jenkins
withCredentials, never raw parameters. - Closure-based dispatch — modules register execute/cleanup/notify closures.
Each implementation class follows this pattern:
package com.buas.deploy
class Steam implements Serializable {
def steps // pipeline context for Jenkins DSL steps
Steam(steps) {
this.steps = steps
}
def pipelineParams(Map overrides = [:]) {
return [
steps.string(name: 'STEAM_APP_ID', defaultValue: overrides.STEAM_APP_ID ?: '',
description: 'Steam App ID'),
// ...
]
}
def execute(Map params, Map ctx) {
deploy(appId: params.STEAM_APP_ID, /* ... */)
}
def deploy(Map config) {
steps.bat(label: "Deploy to Steam", script: "...")
}
}Jenkins DSL calls (bat, withCredentials, writeFile, etc.) are accessed via steps.bat(...), steps.withCredentials(...), etc.
-
Create the implementation class at
src/com/buas/alert/Slack.groovy:package com.buas.alert class Slack implements Serializable { def steps Slack(steps) { this.steps = steps } def pipelineParams(Map overrides = [:]) { return [ steps.string(name: 'SLACK_WEBHOOK', defaultValue: overrides.SLACK_WEBHOOK ?: '', description: 'Slack webhook URL') ] } def executeNotify(String status, Map params, Map ctx) { if (!params.SLACK_WEBHOOK) return // Send Slack notification... } }
-
Create a
vars/slack.groovyscript:def alert(Map overrides = [:]) { def impl = new com.buas.alert.Slack(this) ModuleRegistry.register( category: 'alert', name: 'Slack', params: impl.pipelineParams(overrides), alert: { status, params, ctx -> impl.executeNotify(status, params, ctx) }, hasCleanup: false ) }
-
Use it in a Jenkinsfile:
stages { perforce.sync() ue5.build() discord.alert() slack.alert() }
The swarm.review() and discord.alert() modules use a JSON groups format for managing team participants and Discord mentions:
{
"groups": [
{
"name": "TeamLead",
"discordID": "123456789",
"swarmID": ["swarmuser1"],
"type": "user"
},
{
"name": "Developers",
"discordID": "987654321",
"swarmID": ["dev1", "dev2"],
"type": "role"
}
]
}Types control Discord mention format:
| Type | Discord Format |
|---|---|
user |
<@ID> |
role |
<@&ID> |
channel |
<#ID> |