JenkinsFileGenerator transforms YAML configuration files into complete Jenkins pipeline scripts (Jenkinsfiles) by combining multiple features such as Git checkout, GitHub integration, Slack notifications, Unreal Engine builds, and more. Each feature is implemented as a separate module with its own configuration schema and template, making the system highly extensible and maintainable.
- Modular Feature System: Each pipeline capability (Git, GitHub, Slack, etc.) is implemented as a separate feature
- Dependency Resolution: Automatic handling of feature dependencies using topological sorting
- Template-based Generation: Uses Mako templates for flexible code generation
- Configuration Validation: Pydantic-based validation for all configuration options
- Documentation Generation: Automatic generation of configuration documentation
- Groovy Linting: Optional integration with npm-groovy-lint for code quality
- Clone the repository
- Set up the Python environment:
# Windows PowerShell
.\setup-env.ps1
# Or manually:
python -m venv .venv
.venv\Scripts\Activate.ps1
pip install -e .[dev]Activating the virtual environment will create a script named jenkinsfilegenerator that you can use:
jenkinsfilegenerator <config_file> [options]| Argument | Type | Required | Description |
|---|---|---|---|
config |
Path | Yes | Path to the YAML configuration file |
-o, --output |
Path | Yes | Output path for the generated Jenkinsfile |
--batch |
Path | No | Path to a YAML batch file |
-bbd, --blackboarddata |
Str | No | A comma separated list of key=value pairs |
--lint |
Flag | No | Run npm-groovy-lint on the generated file |
--generate-documentation |
Flag | No | Generates the configuration documentation |
--validate |
Flag | No | Validates the config file and the mako templates |
--list_features |
Flag | No | Output the registered features in the console |
--no-validation |
Flag | No | Skip configuration validation (not recommended) |
--verbose |
Flag | No | Show detailed messages |
--quiet |
Flag | No | Suppress non-error output |
# Basic generation
jenkinsfilegenerator config.yaml -o Jenkinsfile
# With linting
jenkinsfilegenerator config.yaml -o Jenkinsfile --lint
# With the blackboard data
jenkinsfilegenerator config.yaml -o Jenkinsfile --blackboarddata "build_type=Development,platform=Windows" --lint
# Use a batch file with linting
jenkinsfilegenerator --batch batch.yaml --lint
# Generate documentation
jenkinsfilegenerator config.yaml --generate_documentationIf you need to use the same token multiple times in a YAML config file, it is possible to specify that token in the blackboard data, with the --blackboarddata argument, which accepts a comma separated list of key=value pairs.
For example build_type=Development,platform=Windows.
In your config file, you can use the syntax ^BLACKBOARD_DATA.key^.
So for example if in your config file you have the following text:
pipeline_name: "MyGame_^BLACKBOARD_DATA.build_type^"
project:
name: "MyGame"
jenkins:
default_node_names: "UE_5.2"
features:
git:
use_simple_checkout: false
checkout:
branch_name: "refs/heads/^BLACKBOARD_DATA.branch_name^"
You can pass the string build_type=Development,branch_name=develop, which will update the config file to
pipeline_name: "MyGame_Development"
project:
name: "MyGame"
jenkins:
default_node_names: "UE_5.2"
features:
git:
use_simple_checkout: false
checkout:
branch_name: "refs/heads/develop"
before using this updated version of the config file to generate the final jenkinsfile.
Instead of using the config and --output arguments to generate a single jenkinsfile, it's possible to use the --batch argument to specify the path to a YAML file which contains a list of items which will each generate a different jenkinsfile.
The structure of the YAML file should start with an items property, which is an array of input_config_file, output_jenkinsfile and blackboard_data. The input_config_file and output_jenkinsfile properties are both relative path to the batch config file.
items:
- input_config_file: "jenkinsfile_release_template.yaml"
output_jenkinsfile: "../Jenkinsfile_Release_Development"
blackboard_data: "build_type=Development,branch_name=develop"
- input_config_file: "jenkinsfile_release_template.yaml"
output_jenkinsfile: "../Jenkinsfile_Release_Staging"
blackboard_data: "build_type=Staging,branch_name=staging"
- input_config_file: "jenkinsfile_release_template.yaml"
output_jenkinsfile: "../Jenkinsfile_Release_Production"
blackboard_data: "build_type=Production,branch_name=release"
This is a great way to generate, from the same template file, multiple outputs with different values, in one command, without having to maintain multiple config files.
Some features have entry points in the output they generate, to allow you to define custom code for your project.
You can find those entry points by looking for code like this in the mako files of this repository:
% if global_values['customization'].get('XXX'):
<%include file="${global_values['customization']['XXX']}"/>
% endif
To output your own code, you need to follow these simple steps:
- Create a folder next to your config file that will contain your customization code (Ex:
Customization) - Set the property
customization_folderof your pipeline config to the name of your folder (If you place that folder in another location, you can use a relative path here. You can also use an absolute path if you want) - In that folder, create a file for each customization you want. For example, the unreal feature supports
unreal_postBuildGraphTasks. So you would create a file namedunreal_postBuildGraphTasks.makoin that folder. Note that you can use the context data that the mako template engine uses, so you have the exact same data that is used when the feature is processed.
That's it !
You can check ue_full_example.yaml for a working configuration.
In the launch.json file of the .vscode folder, add the following lines:
{
"configurations": [
{
"name": "Python Debugger: Module",
"type": "debugpy",
"request": "launch",
"module": "generator",
"console": "integratedTerminal",
"args": [
"E:/Dev/Projects/YourGame/Scripts/Build/Jenkins/Config/jenkinsfile_pull_request.yaml",
"--output",
"E:/Dev/Projects/YourGame/Scripts/Build/Jenkins/Jenkinsfile",
"--lint"
]
}
]
}
The configuration file is a YAML file with the following top-level structure:
pipeline_name: "MyPipeline"
project:
name: "MyProject"
jenkins:
default_node_names: "linux"
workspace_suffix: "optional_suffix"
features:
# Feature configurations go hereSee the examples directory for configuration examples.
utils: Basic utilities (abort running builds, etc.)properties: Jenkins pipeline propertiesgit: Git checkout configurationgithub: GitHub integration (PR handling, status updates)plasticscm: PlasticSCM checkout configurationslack_notifications: Slack messaging for build eventsunreal: Unreal Engine BuildGraph integration
You can also look at the documentation for the features.
When you provide a YAML configuration file, the generator:
- Parses the YAML into a
PipelineConfigobject - Discovers available features by scanning the
generator/features/directory - Selects active features based on what's present in the
featuressection of your config - Validates configuration for each selected feature using Pydantic models
# Example: If your config has this...
features:
git:
use_simple_checkout: true
slack_notifications:
channel: "#ci-builds"
message_template: "Build completed"
# The generator will:
# 1. Instantiate GitFeature and SlackNotificationsFeature
# 2. Validate their configurations against GitConfig and SlackNotificationsConfig
# 3. Add any missing dependencies (utils is added automatically)Features can declare dependencies on other features:
class GitHubFeature(BaseFeature):
feature_name = "github"
@property
def dependencies(self) -> List[str]:
return ["utils"] # GitHub feature requires utils featureThe system uses topological sorting to ensure features are processed in the correct order, automatically adding missing dependencies with default configurations.
Each feature has an associated Mako template in generator/templates/ that defines code blocks:
<!-- git.mako -->
<%def name="additional_functions()">
def checkout() {
% if feature_config.use_simple_checkout:
checkout scm
% else:
// Complex checkout logic here
% endif
}
</%def>Each feature can contribute to these predefined blocks in the final Jenkinsfile:
libraries: @Library importsimports: Import statementsproperties: Pipeline propertiespre_pipeline_steps: Code before main pipelinebuild_steps: Main build logicon_build_unstable: Unstable build handlingon_build_failure: Failure handlingon_build_success: Success handlingpost_build_steps: Post-build cleanupon_exception_thrown: Exception handlingon_finally: Finally blockadditional_functions: Helper functions
The base template (base_jenkinsfile.mako) combines all feature blocks into the final Jenkinsfile:
// Generated structure
@Library('feature-libs')
import feature.imports
properties([...])
try {
// pre_pipeline_steps from all features
// build_steps from all features
if (currentBuild.result == 'UNSTABLE') {
// on_build_unstable from all features
} else if (currentBuild.result == 'FAILURE') {
// on_build_failure from all features
} else {
// on_build_success from all features
}
} catch (Exception e) {
// on_exception_thrown from all features
throw e
} finally {
// on_finally from all features
}
// additional_functions from all featuresTo create a new feature:
- Create the configuration model:
class MyFeatureConfig(FeatureConfig):
setting1: str = Field(description="Description here")
setting2: Optional[bool] = Field(default=False)- Implement the feature class:
class MyFeature(BaseFeature):
feature_name = "my_feature"
def should_include(self, config: Dict[str, Any]) -> bool:
return "my_feature" in config
def get_config_model(self) -> Type[FeatureConfig]::
return MyFeatureConfig
@property
def dependencies(self) -> List[str]:
return ["utils"] # Optional dependencies- Create the template (
templates/my_feature.mako):
<%def name="build_steps()">
// Your Jenkins pipeline code here
echo "Running my feature with setting: ${feature_config.setting1}"
</%def>The feature will be automatically discovered and available for use in configuration files.
-
Install Astral UV
-
Setup dev environment and install dependencies:
.\setup-venv.ps1 -
Linting & formatting
- Use
uv run ruff check .anduv run ruff format .Add checks to CI as required. - Use
uv run mypy .
- Use
To create a new release, you must be on the develop branch, and call invoke create_release
generator/
├── core/ # Core engine components
│ ├── jenkins_file_generator.py # Main orchestrator
│ ├── base_feature.py # Feature base class
│ ├── dependency_resolver.py # Dependency management
│ └── ...
├── features/ # Feature implementations
│ ├── git.py
│ ├── github.py
│ ├── slack_notifications.py
│ └── ...
├── templates/ # Mako templates
│ ├── base_jenkinsfile.mako
│ ├── git.mako
│ └── ...
└── utils/ # Utility modules
- Create your feature class in
generator/features/ - Create your template in
generator/templates/ - The feature will be automatically discovered and registered
- Run
--generate_documentationto update docs
MIT License - see LICENSE file for details.
- Follow the existing feature pattern
- Add appropriate validation and documentation
- Add entries in the .changelog folder for towncrier. You can read the README file for more information.
- Add if possible a configuration example in the examples folder.
- Update documentation
For detailed configuration options, see the generated documentation in the documentation/ directory.