IMPORTANT: Developer documentation for the current development branch. This content is unreleased, may change without notice, and must not be treated as Buildish release documentation.
Jenkins Support
Jenkins is a natural target for Mammoth Cache because it is widely used for large JVM builds and has no native equivalent of the Gradle/Maven caching features this action provides. The architecture’s eight seam interfaces can all be implemented, and the distributed worker/aggregator pattern maps well to Jenkins parallel stages. The main open question is not whether Jenkins can be supported but how — because Jenkins plugins are JVM-based and this codebase is TypeScript/Node.js.
Why using: node24 does not apply
GitHub Actions and Forgejo Actions support exactly three action runtime types: node20/node24,
docker, and composite. There is no jvm type. Jenkins has no equivalent constraint at all:
CI steps run arbitrary shell commands in whatever environment the agent provides. A Node.js
script, a fat JAR, and a Kotlin CLI binary are all equally valid entry points for a Jenkins
Pipeline step. The using: node24 problem is specific to the GitHub/Forgejo action model.
Interface assessment
BaseCacheBackend (src/cache/backend.ts) — no native frontend; external store required
Jenkins has no built-in cache service comparable to the GitHub Actions cache. Options:
- Artifactory or Nexus: widely deployed in organisations that also run Jenkins; the REST API can serve as an imperative cache backend.
- S3-compatible object store: MinIO, AWS S3, GCS, etc. Same approach as Bitbucket.
- Shared agent filesystem: a network-mounted directory accessible from all agents works for same-controller setups, but breaks across agent pools.
The BaseCacheBackend interface is the right abstraction for all of these — a thin adapter
wrapping the chosen store requires no changes to the shared cache orchestration logic.
WorkflowArtifactBackend (src/delta/backend.ts) — clean fit via Copy Artifact Plugin
The Copy Artifact Plugin maps almost directly to the
WorkflowArtifactBackend interface:
| Operation | Copy Artifact / Jenkins equivalent |
|---|---|
uploadArtifact |
archiveArtifacts Pipeline step |
listArtifacts |
Jenkins REST API GET /job/<name>/<build>/api/json?tree=artifacts[*] |
downloadArtifact |
copyArtifacts Pipeline step or REST artifact download URL |
deleteArtifact |
Jenkins REST API POST /job/<name>/<build>/artifact/<path>/doDelete |
Cross-execution artifact access (required for the distributed worker/aggregator model) is supported — artifacts from upstream builds are accessible to downstream builds by build number.
HostStateStore (src/host/types.ts) — workspace file + stash
Jenkins has no saveState/getState equivalent. The prepare phase writes key-value pairs to a
small JSON file in the workspace; the finalize phase reads it back. For Pipeline jobs, stash /
unstash can carry this file to a different node if prepare and finalize run on separate agents.
Since the values are plain strings, a workspace-resident JSON file is entirely sufficient.
HostReporter, HostInputSource, HostOutputSink (src/host/types.ts) — straightforward
| Adapter | Jenkins equivalent |
|---|---|
HostReporter |
echo / println in a Pipeline step; ANSI colour via AnsiColor plugin |
HostInputSource |
Pipeline parameters {} block or withCredentials for secrets |
HostOutputSink |
env.VAR = value in a Groovy step; no formal output concept needed |
ReportSink (src/host/types.ts) — HTML report via publishHTML
Jenkins has no built-in Markdown job summary equivalent. A Markdown-to-HTML conversion step
followed by publishHTML (or the Blue Ocean summary API) provides a reasonable approximation.
CiPlatformAdapter / CiJobContext (src/ci/types.ts) — fully covered
All required CiJobContext fields are available from Jenkins built-in environment variables:
| Required value | Jenkins variable |
|---|---|
| Job name | JOB_NAME |
| Run ID | BUILD_NUMBER |
| Run attempt | BUILD_NUMBER (always 1 per build; retries are new build numbers) |
| Execution URL | BUILD_URL |
| Branch name | GIT_BRANCH (Git plugin) |
| PR detection | CHANGE_ID non-empty (Multibranch Pipeline) |
| Workspace | WORKSPACE |
| Temp directory | WORKSPACE_TMP |
Finalize phase — Pipeline post {} block
Jenkins Pipeline’s post { always { ... } } block runs after the main stages {} body,
regardless of success or failure — a clean equivalent to GitHub’s post: hook:
1pipeline {
2 stages {
3 stage('Prepare cache') { steps { sh 'node dist/prepare.js' } }
4 stage('Build') { steps { sh './gradlew build' } }
5 }
6 post {
7 always { sh 'node dist/finalize.js' }
8 }
9}
For distributed mode, parallel worker stages upload delta artifacts and a sequential aggregator stage downloads and merges them — this is idiomatic Jenkins Pipeline.
Implementation paths
Three approaches are viable, in increasing implementation cost:
Option A: Jenkins Shared Library wrapping the Node.js scripts
Create a Jenkins Shared Library
in Groovy. The library provides mammothCachePrepare() and mammothCacheFinalize() global vars
that set up the environment (workspace paths, job context as environment variables) and shell out
to node dist/prepare.js / node dist/finalize.js. Jenkins-specific concerns (state file
stashing, artifact archiving/copying) are handled by the Groovy wrapper; all cache and delta
logic remains in the Node.js scripts unchanged.
Tradeoffs: Fastest path. Requires Node.js on the Jenkins agent (common in modern CI, not universal). Single codebase. Not distributable via the Jenkins update centre.
Option B: Thin Jenkins plugin delegating to Node.js
A proper hudson.Plugin extension point that declares a Node.js tool requirement and delegates
all logic to the scripts via shell steps. Same tradeoffs as option A but distributable via the
Jenkins update centre and installable without users manually configuring a shared library.
Tradeoffs: Slightly more setup than option A, but same Node.js requirement on agents.
Option C: Full Kotlin/JVM port
Rewrite the core phase logic (src/phases/, src/cache/, src/delta/) in Kotlin, implementing
the same seam interfaces. The Jenkins adapter layer (BaseCacheBackend, WorkflowArtifactBackend,
etc.) is then written as a standard Jenkins plugin using the Jenkins Java API.
Tradeoffs: The highest investment but the only path to a true zero-dependency Jenkins plugin. The existing interface boundaries are clean enough that the port is largely mechanical rather than creative. See Kotlin Multiplatform for why a single Kotlin codebase targeting both JS (GitHub/Forgejo) and JVM (Jenkins) was evaluated and not recommended.