Edgemaul Legends Jenkins Pipeline
This is the dev pipeline I setup for Edgemaul Legends
Edgemaul Legends Series
- Workflow
- This post
Unreal Engine Jenkins Pipeline Series
This one is going to be a bit different from my other pipeline tutorials. This more so will be a brain dump of how I set up my game as of now. My current pipeline has 4 sections, first is an editor build, second is a testing phase where I run functional tests and lint blueprints and finally I build my game. I’m going to jump right into it, if any of this confuses you or you need help setting up the basics, look at my other tutorials in the link at the top.
Jenkinsfile
pipeline {
agent { label 'UnrealEngineWin' }
environment {
WIN_ENGINE_DIRECTORY = 'C:\\EpicGames\\UnrealEngine\\Engine'
GAME_TARGET = 'EdgemaulLegends'
}
options {
timeout(time: 3, unit: 'HOURS')
}
stages {
stage('Editor') {
steps {
echo 'Building the Editor'
bat "$WIN_ENGINE_DIRECTORY\\Build\\BatchFiles\\Build.bat ${GAME_TARGET}Editor Win64 Development $WORKSPACE\\${GAME_TARGET}.uproject -waitmutex"
}
}
stage('Testing') {
steps {
echo 'Linting'
bat "$WIN_ENGINE_DIRECTORY\\Binaries\\Win64\\UnrealEditor-Cmd.exe $WORKSPACE\\${GAME_TARGET}.uproject -run=Linter -RuleSet=ue4.style -TreatWarningsAsErrors /Game/${GAME_TARGET} -platform=Win64 -json=Lintreport.json"
echo 'Functional Testing'
bat "$WIN_ENGINE_DIRECTORY\\Build\\BatchFiles\\RunUAT.bat RunUnreal -project=$WORKSPACE\\${GAME_TARGET}.uproject -platform=Win64 -configuration=Development -build=editor -scriptdir=$WORKSPACE\\ -nullrhi -unattended -nopause -test=DaedalicTestAutomationPlugin.Automation.DaeGauntletTest(JUnitReportPath=$WORKSPACE\\Saved\\Reports\\junit-report.xml,ReportPath=$WORKSPACE\\Saved\\Reports)"
echo 'Posting Test results'
junit "**\\Saved\\Reports\\*.xml"
}
}
stage('Windows Client') {
steps {
//echo 'Updating Project Version'
//bat 'X:\\scripts\\update-project-version.exe -iniPath=%WORKSPACE%\\Config\\DefaultGame.ini -version=%BRANCH_NAME%.%BUILD_NUMBER% -secretKey=%ELG_PLAYFAB_API_KEY% -titleId=%PLAYFAB_TITLE_ID%'
echo 'Building Windows Shipping Client'
bat "$WIN_ENGINE_DIRECTORY\\Build\\BatchFiles\\RunUAT.bat BuildCookRun -project=$WORKSPACE\\${GAME_TARGET}.uproject -archivedirectory=$WORKSPACE\\dist -package -nocompileeditor -installed -nop4 -cook -stage -archive -ddc=DerivedDataBackendGraph -pak -prereqs -distribution -nodebuginfo -targetplatform=Win64 -build -target=${GAME_TARGET} -clientconfig=DebugGame -utf8output"
}
}
stage('Linux Server') {
steps {
echo 'Building Linux Dedicated Server'
bat "$WIN_ENGINE_DIRECTORY\\Build\\BatchFiles\\RunUAT.bat BuildCookRun -project=$WORKSPACE\\${GAME_TARGET}.uproject -archivedirectory=$WORKSPACE\\dist -package -nocompileeditor -installed -nop4 -cook -stage -archive -ddc=DerivedDataBackendGraph -pak -prereqs -distribution -nodebuginfo -targetplatform=Linux -build -target=${GAME_TARGET} -serverconfig=DebugGame -utf8output"
}
}
}
}
Build Fields
This will make sure our build is only ran on the agents tagged with UnrealEngineWin
agent { label 'UnrealEngineWin' }
- WINENGINEDIRECTORY = The location where you installed your engine at. Make sure to give the path with \Engine appended at the end
- GAME_TARGET = Name of your project. Should be the portion before .uproject. I did it this way to make this easily transferrable to another game
environment {
WIN_ENGINE_DIRECTORY = 'C:\\EpicGames\\UnrealEngine\\Engine'
GAME_TARGET = 'EdgemaulLegends'
}
The timeout is a little superfluous atm. At first it was 30 min (using engine source), but there is an active Jenkins bug where your builds idle time counts towards its execution. In the future I planned on using scaling cloud agents so each build will have its own unique server, this should alleviate the problem but for now, the solution is just to adjust the timeout. This section is also optional and you can forgo a timeout alltogether
options {
timeout(time: 3, unit: 'HOURS')
}
Editor Phase
In order to run tests, you need to build the editor version of your project
bat "$WIN_ENGINE_DIRECTORY\\Build\\BatchFiles\\Build.bat ${GAME_TARGET}Editor Win64 Development $WORKSPACE\\${GAME_TARGET}.uproject -waitmutex"
- WINENGINEDIRECTORY = The env variable from earlier
- ${GAME_TARGET}Editor = will build EdgemaulLegendsEditor
- Win64 Development = Will build the development editor for Win64 platform
- $WORKSPACE\${GAME_TARGET}.uproject = location to my uproject file. $WORKSPACE is a built in Jenkins variable that equates to the folder where the build is happening at
- waitmutex = locks the engine so no other builds can be ran
Testing
This is going to be a long section. There was a lot that went into setting this up. I decided make a separate post for setting up Gauntlet, I’ll share the link in that section
Linting
Perforce has a pretty good rundown of what linting is and why to use it. For Unreal Engine, the marketplace plugin LinterV2 that takes care of this need for us. The problem, this hasn’t been updated in a while and the CLI for it is slightly broken. I have a public repo where I updated the plugin to UE5 as well as fixed the cli issue so it properly errors out if there is a lint issue found
bat "$WIN_ENGINE_DIRECTORY\\Binaries\\Win64\\UnrealEditor-Cmd.exe $WORKSPACE\\${GAME_TARGET}.uproject -run=Linter -RuleSet=ue4.style -TreatWarningsAsErrors /Game/${GAME_TARGET} -platform=Win64 -json=Lintreport.json"
- UnrealEditor-Cmd.exe = The program you want to run when you want to run editor commands
- -run=Linter = Tells the UnrealEditor to look for the Linter command and run it. This is why we built the editor in the previous step
- -RuleSet=ue4.style = The lint ruleset that you want to use
- TreatWarningsAsErrors = I consider any linting issue to be an error and I don’t want them pushed up to my dev branches. This field is optional and can be omitted
- /Game/${GAME_TARGET} = What folder to lint. I put all of my project specific blueprints in a folder like this Contents/EdgemaulLegends which this field equates to
- -json=Lintreport.json” = If there is an error, I want to see what it is quickly. This is what this field is. It will make a report under Saved/Lintreport.json. Another optional field
Gauntlet/Functional Test
Now this section was a major bitch to set up. I wrote a seperate guide on how to set it up. I will assume you went through that before trying to implement this section
echo 'Functional Testing'
bat "$WIN_ENGINE_DIRECTORY\\Build\\BatchFiles\\RunUAT.bat RunUnreal -project=$WORKSPACE\\${GAME_TARGET}.uproject -platform=Win64 -configuration=Development -build=editor -scriptdir=$WORKSPACE\\ -nullrhi -unattended -nopause -test=DaedalicTestAutomationPlugin.Automation.DaeGauntletTest(JUnitReportPath=$WORKSPACE\\Saved\\Reports\\junit-report.xml,ReportPath=$WORKSPACE\\Saved\\Reports)"
echo 'Posting Test results'
junit "**\\Saved\\Reports\\*.xml"
If you followed my guide from above, the Functional Testing phase is explained thoroughly in there. As for the Posting Test Results, Jenkins can post your test results in the build. It looks something like this when it posts
The junit command will look in the Saved\Reports\
folder for any .xml file. This is where Gauntlet will save the test report in
Windows Client
Here is the main event that you all will want, this builds your game client.
"$WIN_ENGINE_DIRECTORY\\Build\\BatchFiles\\RunUAT.bat BuildCookRun -project=$WORKSPACE\\${GAME_TARGET}.uproject -archivedirectory=$WORKSPACE\\dist -package -nocompileeditor -installed -nop4 -cook -stage -archive -ddc=DerivedDataBackendGraph -pak -prereqs -distribution -nodebuginfo -targetplatform=Win64 -build -target=${GAME_TARGET} -clientconfig=DebugGame -utf8output"
- BuildCookRun = The RunUAT command that builds and cooks game
- $WORKSPACE\${GAME_TARGET}.uproject = path to your uproject file
- package = packages the game
- nocompileeditor = we compiled the editor earlier, lets not do it again here
- nop4 = don’t check out any files from perforce or check them in
- cook = cook our game assets
- archive = archive the binaries somewhere. Path is provided in archivedirectory
- archivedirectory = The directory to put the finalized build in
- pak = pack the games content
- targetplatform = platform you want to build on
- clientconfig = tells the command to only build a client. The value you can put here is DebugGame, Development, Shipping etc
If you have your own build phase, you can use it here. Just start a build in the editor and scroll to the beginning of the logs and you’ll see the command the editor is running will be similar to what I have above. Just use that output in the cli instead of this and replace the directories as needed
Linux Server
This is to build a dedicated server. You need a full engine install for this and that is outside of the scope of this post
"$WIN_ENGINE_DIRECTORY\\Build\\BatchFiles\\RunUAT.bat BuildCookRun -project=$WORKSPACE\\${GAME_TARGET}.uproject -archivedirectory=$WORKSPACE\\dist -package -nocompileeditor -installed -nop4 -cook -stage -archive -ddc=DerivedDataBackendGraph -pak -prereqs -distribution -nodebuginfo -targetplatform=Linux -build -target=${GAME_TARGET} -serverconfig=DebugGame -utf8output"
All the commands are more or less the same. There are only 2 differences to make a server build
- targetplatform = I wanted a linux server so this value changed to linux. If you were building for windows, this could be the same and stay as Win64
- serverconfig = The same as client config except it will build a server instead of a client. If you were building only Win64, you could make this all one command by adding a serverconfig and clientconfig switch to your command line