In a Jenkins shared library repository, what do the vars/, src/ and resources/ directories each hold, and how does a Jenkinsfile reach each one?
answer
- three directories, fixed names
- file name becomes the step name
- one of them cannot call steps directly
- pass the script object in
- non-Groovy files need their own step
basics
~20 svars/ holds one file per callable pipeline step, named after the step. src/ holds a normal Groovy class hierarchy on the pipeline classpath, imported by package and class name. resources/ holds non-Groovy files read with the libraryResource step.
solid answer
~50 sThe three directories are the whole layout. Each file in `vars/` defines one global variable named after the file — `vars/deployApp.groovy` becomes `deployApp`, and if it declares a `call` method the pipeline invokes it like a built-in step, which is what makes a library feel like a DSL. `src/` is a conventional Groovy source tree, so `src/com/acme/Notifier.groovy` is the class `com.acme.Notifier`; it goes on the pipeline classpath and is used for real object-oriented logic that does not need to look like a step. The catch is that a `src` class has no implicit access to pipeline steps, so you pass the script object into it — usually `this` from the Jenkinsfile — and call `script.sh(...)` through it. `resources/` holds static, non-Groovy files such as JSON, YAML or shell scripts, read as a string with the `libraryResource` step. As a rule, put the thin, teachable entry points in `vars/` and the substance in `src/`.
code
groovy · 6 lines// vars/deployApp.groovy
def call(Map config = [:]) {
String target = config.env ?: 'staging'
echo "Deploying ${config.app} to ${target}"
sh "./deploy.sh ${config.app} ${target}"
}go deeper
Recall the three directory names and what each is for, and that the file name in vars is exactly the step name you call. Be able to point at a small vars file and read what it does.
Explain the mechanical difference: vars files execute with the pipeline script as delegate so steps work, while src classes need the script injected. Mention libraryResource for non-Groovy files.
Show a design opinion — vars as a small public API, src as the tested implementation — and be ready to discuss CPS, Serializable and @NonCPS as real constraints on library code.
Own the library's contract: which steps are public, how they are documented and versioned, and how you keep forty consuming repositories from depending on internals you want to change.
## The layout is fixed A shared library repository has exactly three meaningful top-level directories, and Jenkins ignores anything else it finds: ``` (root) +- src/ # Groovy source tree, on the pipeline classpath | +- com/acme/Notifier.groovy +- vars/ | +- deployApp.groovy # becomes the step deployApp | +- deployApp.txt # documentation shown in the globals reference +- resources/ +- com/acme/settings.json # static files, read with libraryResource ``` ## vars: the DSL surface Each `.groovy` file in `vars/` defines a *global variable* whose name is the file name, so the file name must be a valid identifier and is conventionally camelCase. If the file defines a method named `call`, the variable is callable, and Groovy's syntax makes that look exactly like a built-in step: ```groovy // vars/deployApp.groovy def call(Map config = [:]) { sh "./deploy.sh ${config.app} ${config.env ?: 'staging'}" } ``` ```groovy deployApp app: 'billing', env: 'prod' ``` A vars file can define several methods, and the pipeline reaches them as `deployApp.validate(...)`. Crucially, code in `vars/` can call pipeline steps directly — `sh`, `echo`, `withCredentials` — because the file is executed with the pipeline script as its delegate. That is the single biggest practical difference from `src/`. A matching `.txt` file next to the `.groovy` file supplies documentation, rendered on the controller's global-variable reference page. On a library that forty repositories consume, that file is the difference between a library and a mystery. One caution: the global variable is instantiated once per build, so any field you set on it is shared by everything in that build. Treat vars as stateless entry points, not as places to stash state. ## src: real classes `src/` is an ordinary package tree added to the pipeline's classpath. Use it when the logic deserves types, constructors, and unit tests rather than a flat step. Two rules matter: **A src class cannot call steps by itself.** It has no delegate, so you inject the pipeline script: ```groovy package com.acme class Notifier implements Serializable { private final def script Notifier(script) { this.script = script } void info(String msg) { script.echo "[info] ${msg}" } } ``` Called from the Jenkinsfile as `new com.acme.Notifier(this).info('built')`, or more commonly wrapped by a thin vars step so callers never see the constructor. **Implement Serializable and mind CPS.** A Pipeline can pause and be resumed after a controller restart, so its state is serialised at every step boundary. Classes that live across step calls should implement `Serializable`, and methods holding non-serialisable objects or using constructs the CPS transformer handles badly are marked `@NonCPS` — with the restriction that a `@NonCPS` method must not call pipeline steps. ## resources: everything that is not Groovy `resources/` holds files you want to ship with the library — a JSON policy document, a Helm values template, a shell script — read at runtime: ```groovy def body = libraryResource 'com/acme/settings.json' writeFile file: 'settings.json', text: body ``` `libraryResource` returns the file content as a String; it does not place it in the workspace, so you write it out yourself. Resource loading is supported for libraries retrieved from an SCM, which is the normal case. ## How to divide code between them A useful discipline: `vars/` is the public API, `src/` is the implementation. Callers should see a handful of well-named steps with named parameters; changes to the internals then happen in `src/` without touching forty Jenkinsfiles. The opposite arrangement — hundreds of lines of business logic in one enormous vars file — is the pattern teams regret, because it is the hardest part of a library to unit-test and the easiest to break. ## What interviewers listen for Naming the three directories is table stakes. The distinguishing answers are: vars files can call steps and src classes cannot without an injected script object; the file name in vars *is* the step name; and library code participates in CPS serialisation like the rest of the pipeline.
- Why does a class under src/ need the pipeline script passed into its constructor?Because steps like `sh` and `echo` are resolved against the running pipeline script, and a plain Groovy class has no link to it. Files in `vars/` are executed with the script as their delegate, so they get steps for free; `src` classes do not. Injecting `this` from the Jenkinsfile — or from a vars wrapper — gives the class a handle it can call steps through.
- What does the .txt file beside a vars file do?It supplies documentation for that global variable, rendered on the controller's global variable reference page under the pipeline syntax tooling. It accepts simple markup and is the only in-product documentation a library gets, which matters when many teams call steps they did not write.
- Why do shared library classes commonly implement Serializable?A Pipeline's state is serialised at every step boundary so a build can survive a controller restart. Objects held across step calls must therefore be serialisable, or the build fails with a NotSerializableException. Methods that must work with non-serialisable objects are marked @NonCPS, and such methods must not call pipeline steps.
- How would you ship a shell script with a library rather than embedding it in a Groovy string?Put it in `resources/`, read it with `libraryResource`, write it into the workspace with `writeFile`, then run it. That keeps the script syntax-highlighted, reviewable and testable as a script, rather than as an escaped string inside Groovy — and it survives quoting rules that mangle multi-line here-documents.
saying these in an interview costs you the question
- Thinks src classes can call sh and echo directly
- Believes vars files may be named anything and mapped later
- Puts all logic in one huge vars file
- Confuses resources with the build workspace
- Says shared library code is exempt from CPS serialisation