I would like to insert a custom build step (e.g. running a batch file) before the compilation begins. How would I go about adding a custom build step to the build process.
Sorry for the duplicate, it said it had failed.
UE4 has it own building system called UBT and uses c# build script which you can find in source, you might try to plug yourself there
Yeah, I’ve been looking through the ModuleRule.cs to see if there was a custom commands list. I was hoping to not have to make any changes to the UBT itself since I’d have thought this was a fairly standard requirement to be able to hook custom rules in to the pipeline.
Inspired by 's comment I have come up with a very hacky solution and would very much like to see the proper way of doing it.
I realised that the constructor from the projects ModuleRules must be executed so I just bunged a Process.Start
into there.
There are a few caveats:
Firstly, this is called twice in a build, so if your operation is time consuming this could be a problem.
Secondly, if you launch a batch file the working directory will be [Unreal Engine Location]\Engine\Source. To get the path, I came up with another awful hack of a solution:
StackTrace st = new StackTrace(new StackFrame(true));
StackFrame sf = st.GetFrame(0);
string fileName = sf.GetFileName();
DirectoryInfo ContaingingDirectory = new DirectoryInfo(Path.GetDirectoryName(fileName));
DirectoryInfo CorrectDirectory = ContainginDirectory.Parent.Parent;
The CorrectDirectory
is the root directory of your project.
You can use PreBuildSteps
and PostBuildSteps
string arrays in your *.Target.cs
file.
// Specifies a list of steps which should be executed before/after this target is built, in the context of the host platform's shell.
// The following variables will be expanded before execution:
// $(EngineDir), $(ProjectDir), $(TargetName), $(TargetPlatform), $(TargetConfiguration), $(TargetType), $(ProjectFile).
Emphasis on “in the context of the host platform’s shell”.
To further clarify and save time for future searchers. (UE 5.3)
Ignore the older advice of adding a PreBuildSteps
JSON field to your .uproject file.
Instead, as @Flassari indicates, add them inside your MyProject\Source\MyProject.target.cs
and/or MyProject\Source\MyProjectEditor.target.cs
files by adding string entries to the TargetRules.PreBuildSteps
list.
This gives you C# access to the TargetInfo.cs
class where you have access to the Platform, Configuration, Architecture, ProjectFile and more properties which can be easily and more reliably passed to an external script.
I use PowerShell instead of a batch file to perform Pre-Build actions, passing in any appropriate properties I require. As an example, here is the creation of a build-time injected timestamp.
MyProject.Target.cs
using UnrealBuildTool;
using System.Collections.Generic;
public class MyProjectTarget : TargetRules
{
public MyProjectTarget(TargetInfo Target) : base(Target)
{
Type = TargetType.Game;
DefaultBuildSettings = BuildSettingsVersion.V4;
ExtraModuleNames.AddRange( new string[] { "MyProject" } );
PreBuildSteps.Add("echo *** Running PreBuildSteps ***");
PreBuildSteps.Add("pwsh \"" + ProjectFile.Directory.ToString() + "\\PreBuildTasks.ps1\" -projectDir \"" + ProjectFile.Directory.ToString() + "\"");
PreBuildSteps.Add("echo *** Finished PreBuildSteps ***");
}
}
PreBuildTasks.ps1
<# Powershell 7 script for performing pre-build configuration tasks.#>
# parameters that should be made available from the shell.
param(
[string]$projectDir
);
# Inject a build timestamp into the project as a global macro value by overwriting the BuildTimeStamp.h file.
Function BuildTimeStamp{
$pragmaOnce = "#pragma once";
$timeStamp = (Get-Date).ToString('MM/dd/yyyy hh:mm tt');
$tsDefine = "#define BUILD_TIMESTAMP TEXT(`"$timeStamp`")";
$path = $projectDir + "\Source\MyProject\Content\Scripts\BuildTimeStamp.h"
$pragmaOnce + "`r`n"+ $tsDefine | Out-File -FilePath $path -Encoding utf8 -Force
}
<# Perform the following tasks #>
BuildTimeStamp
#close when finished
Write-Host "Finished tasks in PreBuildTasks.ps1";
exit
Simple example for sure, but it could be anything including multi-threaded packaging and encryption of custom non-UE asset data. (Just ensure the main thread doesn’t exit until after all spawned threads have finalized.)