UE5 Early access RegEx in Build.cs scripts not working

Hi,

There seems to be an issue when I try to use C# regular expressions in the new UE5 build system - namely the Build.cs scripts with RegEx that were working in UE4 are not compiling in UE5.

One of our clients investigated further and they said: “The Build.cs scripts cannot use Regex expressions due to a change in how they dynamically compile the Build scripts for .NET Core.”

Is there a chance this is not working due to a bug / oversight to the new build system, or is this done intentionally?

Best Regards,
Nick J, Coherent Labs

3 Likes

Since Epic already released 5.0.1 and don’t seem to care abut us, simple folks (their regular behavior), here’s the workaround:

#if UE_5_0_OR_LATER
Assembly regexDLL;
    MethodInfo EpicNeglectsUsersMatchPatternMethod, EpicNeglectsUsersMatchSucccessMethod, EpicNeglectsUsersMatchIndexMethod, EpicNeglectsUsersMatchLengthMethod;
    Object EpicNeglectsUsersRegex;
        String artPath = Path.Combine(ModuleDirectory, "RegexWrapper.dll");
        if (File.Exists(artPath))
        {
            File.Delete(artPath);
        }

        String localProjPath = Path.Combine(ModuleDirectory, "RegexWrapper.csproj");
        if (File.Exists(localProjPath))
        {
            File.Delete(localProjPath);
        }

        using (var projWriter = new StreamWriter(localProjPath, false))
        {
            projWriter.WriteLine(
                "<?xml version=\"1.0\" encoding=\"utf-8\"?>                                                   " +
                "\n<Project ToolsVersion=\"15.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">" +
                "\n\t<Import Project=\"$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\Microsoft.Common.props\" Condition=\"Exists('$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\Microsoft.Common.props')\" />" +
                "\n\t<PropertyGroup>                                                                 " +
                "\n\t\t<Configuration Condition=\" \'$(Configuration)\' == \'\' \">Debug</Configuration>   " +
                "\n\t\t<Platform Condition=\" \'$(Platform)\' == \'\' \">AnyCPU</Platform>                 " +
                "\n\t\t<ProjectGuid>{72641D45-0B73-4130-8FD2-81535281373B}</ProjectGuid>             " +
                "\n\t\t<OutputType>Library</OutputType>                                              " +
                "\n\t\t<AppDesignerFolder>Properties</AppDesignerFolder>                             " +
                "\n\t\t<RootNamespace>RegexWrapper</RootNamespace>                                   " +
                "\n\t\t<AssemblyName>RegexWrapper</AssemblyName>                                     " +
                "\n\t\t<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>                         " +
                "\n\t\t<FileAlignment>512</FileAlignment>                                            " +
                "\n\t\t<Deterministic>true</Deterministic>                                           " +
                "\n\t</PropertyGroup>                                                                " +
                "\n\t<PropertyGroup Condition=\" \'$(Configuration)|$(Platform)\' == \'Release|AnyCPU\' \">" +
                "\n\t\t<Optimize>true</Optimize>                                                     " +
                "\n\t\t<OutputPath>.</OutputPath>                                                    " +
                "\n\t\t<DefineConstants>TRACE</DefineConstants>                                      " +
                "\n\t\t<ErrorReport>prompt</ErrorReport>                                             " +
                "\n\t\t<WarningLevel>4</WarningLevel>                                                " +
                "\n\t</PropertyGroup>                                                                " +
                "\n\t<ItemGroup>                                                                     " +
                "\n\t\t<Reference Include=\"System\" />                                              " +
                "\n\t\t<Reference Include=\"System.Core\" />                                         " +
                "\n\t\t<Reference Include=\"System.Xml.Linq\" />                                     " +
                "\n\t\t<Reference Include=\"System.Data.DataSetExtensions\" />                       " +
                "\n\t\t<Reference Include=\"Microsoft.CSharp\" />                                    " +
                "\n\t\t<Reference Include=\"System.Data\" />                                         " +
                "\n\t\t<Reference Include=\"System.Xml\" />                                          " +
                "\n\t</ItemGroup>                                                                    " +
                "\n\t<ItemGroup>                                                                     " +
                "\n\t\t<Compile Include=\"Classes.cs\" />                                            " +
                "\n\t</ItemGroup>                                                                    " +
                "\n\t<Import Project=\"$(MSBuildToolsPath)\\Microsoft.CSharp.targets\" />            " +
                "\n</Project>                                                                        "
            );
        }
        String localCodePath = Path.Combine(ModuleDirectory, "Classes.cs");
        if (File.Exists(localCodePath))
        {
            File.Delete(localCodePath);
        }

        using (var codeWriter = new StreamWriter(localCodePath, false))
        {
            codeWriter.WriteLine(
                "using System;" +
                "\nusing System.Text.RegularExpressions;\n" +
                "\npublic class EpicNeglectsUsersMatch                   " +
                "\n{                                                     " +
                "\n\t\tMatch m_Match;                                    " +
                "\n\t\tpublic EpicNeglectsUsersMatch(Match inMatch)      " +
                "\n\t\t{                                                 " +
                "\n#if DEBUG                                             " +
                "\n\t\tConsole.WriteLine(\"EpicNeglectsUsersMatch is called.\");" +
                "\n#endif                                                " +
                "\n\t\tm_Match = inMatch;                                " +
                "\n\t\t}                                                 " +
                "\n\t\tpublic bool Success() { return m_Match.Success; } " +
                "\n\t\tpublic int Index() { return m_Match.Index; }      " +
                "\n\t\tpublic int Length() { return m_Match.Length; }    " +
                "\n\t}                                                   " +
                "\n\tpublic class EpicNeglectsUsersRegex : Regex                      " +
                "\n\t{                                                   " +
                "\n\t\tpublic EpicNeglectsUsersRegex(string inPattern) : base(inPattern)" +
                "\n\t\t{                                                           " +
                "\n#if DEBUG                                                       " +
                "\n\t\tConsole.WriteLine(\"EpicNeglectsUsersRegex got pattern: {0}\", inPattern);" +
                "\n#endif                                                          " +
                "\n\t\t}                                                           " +
                "\n\t\tpublic EpicNeglectsUsersMatch EpicNeglectsUsersMatchPattern(string inTxt)" +
                "\n\t\t{                                                           " +
                "\n#if DEBUG                                                       " +
                "\n\t\tConsole.WriteLine(\"EpicNeglectsUsersMatchPattern got txt: {0}\", inTxt);" +
                "\n#endif                                                          " +
                "\n\t\treturn new EpicNeglectsUsersMatch(Match(inTxt));            " +
                "\n\t\t}                                                           " +
                "\n\t}"
            );
        }
        System.Diagnostics.Process.Start("CMD.exe", "/C msbuild " + localProjPath + " /p:configuration=Release;Platform=AnyCPU").WaitForExit();

        if (File.Exists(localCodePath))
        {
            File.Delete(localCodePath);
        }

        if (File.Exists(localProjPath))
        {
            File.Delete(localProjPath);
        }

        regexDLL = Assembly.LoadFile(Path.Combine(ModuleDirectory, "RegexWrapper.dll"));
		Console.WriteLine("\nAssembly CodeBase:" + regexDLL.CodeBase);
        AssemblyName assemName = regexDLL.GetName();
        Console.WriteLine("\nName: {0}", assemName.Name);
        Console.WriteLine("Version: {0}.{1}", assemName.Version.Major, assemName.Version.Minor);
        MethodInfo EpicNeglectsUsersMatchPatternMethod = regexDLL.GetType("EpicNeglectsUsersRegex").GetMethod("EpicNeglectsUsersMatchPattern");
        MethodInfo EpicNeglectsUsersMatchSucccessMethod = regexDLL.GetType("EpicNeglectsUsersMatch").GetMethod("Success");
        MethodInfo EpicNeglectsUsersMatchIndexMethod = regexDLL.GetType("EpicNeglectsUsersMatch").GetMethod("Index");
        MethodInfo EpicNeglectsUsersMatchLengthMethod = regexDLL.GetType("EpicNeglectsUsersMatch").GetMethod("Length");
#endif

Just copy this code into YourProject.Build.cs constructor of class that inherits from ModuleRules, you know, the regular one where you add PublicDependencyModuleNames. Then you can use regex like this:

EpicNeglectsUsersRegex = regexDLL.CreateInstance("EpicNeglectsUsersRegex", false, BindingFlags.Instance | BindingFlags.Public
                            , null, new Object[] { YOUR_REGEX_PATTERN_GOES_HERE }, null, null);
Object yourMatch = EpicNeglectsUsersMatchPatternMethod.Invoke(EpicNeglectsUsersRegex, new object[] { YOUR_STRING_TO_MATCH });
bool yourMatchSuccess = (bool)EpicNeglectsUsersMatchSucccessMethod.Invoke(yourMatch, new object[] { });
int yourMatchIndex = (int)EpicNeglectsUsersMatchIndexMethod.Invoke(yourMatch, new object[] { });
int yourMatchLength = (int)EpicNeglectsUsersMatchLengthMethod.Invoke(yourMatch, new object[] { });

Your Regex pattern goes instead YOUR_REGEX_PATTERN_GOES_HERE and text to match from, goes instead of YOUR_STRING_TO_MATCH . Add this at the end of your Build.cs script to cleanup obj folder:

#if UE_5_0_OR_LATER
        DirectoryInfo objJunkDir = new DirectoryInfo(Path.Combine(ModuleDirectory, "obj"));
        if (objJunkDir.Exists)
        {
            objJunkDir.Delete(true);
        }
#endif

For this all to work you will need path to msbuild in your Path environment variable, but if you installed VisualStudio, it should be already there. Just give full path instead of msbuild in the code if you don’t.