├── FASTBuild.cs ├── LICENSE └── README.md /FASTBuild.cs: -------------------------------------------------------------------------------- 1 | // 2 | // The MIT License(MIT) 3 | // 4 | // Copyright(c) 2019 Ilia Kormin 5 | // 6 | // Substantial portions: 7 | // Copyright(c) 2016-2018 KnownShippable(Yassine Riahi & Liam Flookes) 8 | // (see https://github.com/liamkf/Unreal_FASTBuild/blob/master/LICENSE) 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy 11 | // of this software and associated documentation files (the "Software"), to deal 12 | // in the Software without restriction, including without limitation the rights 13 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | // copies of the Software, and to permit persons to whom the Software is 15 | // furnished to do so, subject to the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be included in all 18 | // copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | // SOFTWARE. 27 | // 28 | 29 | // 30 | // Implementation notes: 31 | // 32 | // Currently supported platforms: 33 | // - Win64 - tested with Windows 10, Visual Studio 2019 Community, Unreal Engine 4.22.3, FastBuild v0.98. 34 | // Recently supported platforms: 35 | // - Durango was fully supported using VS2015 at some point in the past, but VS2015 is not supported since 4.22; 36 | // - Orbis will require some changes. 37 | // Not supported platforms: 38 | // - Android; 39 | // - iOS; 40 | // - Linux; 41 | // - and others... 42 | // 43 | // 44 | // Main idea: 45 | // 46 | // The main difference from Yassine Riahi & Liam Flookes version is in added support for dependency files. 47 | // It was implemented using FastBuild "-report" argument. 48 | // Generated report (see ReportFilePath) contains include files usage. 49 | // Since there is no source code for cl-filter.exe, it is not possible to guarantee same result. 50 | // Moreover it is fact, that output contains more include files than when using cl-filter.exe. 51 | // Maybe more precise filtration could be applied, some ideas: 52 | // - exclude pch includes usages from dependent compilation units; 53 | // - exclude files from outside engine folder; 54 | // - exclude using cl-filter.exe.sn-dbs-tool.ini. 55 | // 56 | // 57 | // FastBuild version: 58 | // 59 | // Don't use FastBuild 0.96 or recent versions, since it has processes management error. 60 | // For more information see https://github.com/fastbuild/fastbuild/issues/223. 61 | // 62 | // 63 | // Installation steps: 64 | // 65 | // 1. Save copy of this file as ...\Engine\Source\Programs\UnrealBuildTool\System\FASTBuild.cs; 66 | // 2. Add it to UnrealBuildTool project; 67 | // 3. Apply fix from https://github.com/TheEmidee/UE4FastBuild: 68 | // VS2019 required same behaviour as for VS2017; 69 | // 4. Add FastBuild executor to ExecuteActions(...) inside ActionGraph.cs: 70 | // ... 71 | // // Figure out which executor to use 72 | // ActionExecutor Executor; 73 | // if (FASTBuild.IsAvailable()) 74 | // { 75 | // Executor = new FASTBuild(); 76 | // } 77 | // else if (BuildConfiguration.bAllowHybridExecutor && HybridExecutor.IsAvailable()) 78 | // ... 79 | // 5. Add "public" access modifier to GetVCToolPath64(...) method inside VCEnvironment.cs. 80 | // 81 | // 82 | // Original version: 83 | // 84 | // https://github.com/liamkf/Unreal_FASTBuild. 85 | // 86 | 87 | using System; 88 | using System.Collections; 89 | using System.Collections.Generic; 90 | using System.IO; 91 | using System.Diagnostics; 92 | using System.Linq; 93 | using Tools.DotNETCommon; 94 | 95 | 96 | namespace UnrealBuildTool 97 | { 98 | class FASTBuild : ActionExecutor 99 | { 100 | /*---- Configurable User settings ----*/ 101 | 102 | // Used to specify a non-standard location for the FBuild.exe, for example if you have not added it to your PATH environment variable. 103 | public static string FBuildExePathOverride = ""; 104 | 105 | // Controls network build distribution 106 | private bool bEnableDistribution = true; 107 | 108 | // Controls whether to use caching at all. CachePath and CacheMode are only relevant if this is enabled. 109 | private bool bEnableCaching = false; 110 | 111 | // Location of the shared cache, it could be a local or network path (i.e: @"\\DESKTOP-BEAST\FASTBuildCache"). 112 | // Only relevant if bEnableCaching is true; 113 | private string CachePath = @"\\SharedDrive\FASTBuildCache"; 114 | 115 | public enum eCacheMode 116 | { 117 | ReadWrite, // This machine will both read and write to the cache 118 | ReadOnly, // This machine will only read from the cache, use for developer machines when you have centralized build machines 119 | WriteOnly, // This machine will only write from the cache, use for build machines when you have centralized build machines 120 | } 121 | 122 | // Cache access mode 123 | // Only relevant if bEnableCaching is true; 124 | private eCacheMode CacheMode = eCacheMode.ReadWrite; 125 | 126 | /*--------------------------------------*/ 127 | 128 | public override string Name 129 | { 130 | get { return "FASTBuild"; } 131 | } 132 | 133 | public static bool IsAvailable() 134 | { 135 | if (FBuildExePathOverride != "") 136 | { 137 | return File.Exists(FBuildExePathOverride); 138 | } 139 | 140 | // Get the name of the FASTBuild executable. 141 | string fbuild = "fbuild"; 142 | if (BuildHostPlatform.Current.Platform == UnrealTargetPlatform.Win64) 143 | { 144 | fbuild = "fbuild.exe"; 145 | } 146 | 147 | // Search the path for it 148 | string PathVariable = Environment.GetEnvironmentVariable("PATH"); 149 | foreach (string SearchPath in PathVariable.Split(Path.PathSeparator)) 150 | { 151 | try 152 | { 153 | string PotentialPath = Path.Combine(SearchPath, fbuild); 154 | 155 | if (File.Exists(PotentialPath)) 156 | { 157 | return true; 158 | } 159 | } 160 | catch (ArgumentException) 161 | { 162 | // PATH variable may contain illegal characters; just ignore them. 163 | } 164 | } 165 | return false; 166 | } 167 | 168 | private HashSet ForceLocalCompileModules = new HashSet() 169 | {"Module.ProxyLODMeshReduction", 170 | "GoogleVRController"}; 171 | 172 | private enum FBBuildType 173 | { 174 | Windows, 175 | XBOne, 176 | PS4, 177 | } 178 | 179 | private FBBuildType BuildType = FBBuildType.Windows; 180 | 181 | private void DetectBuildType(List Actions) 182 | { 183 | foreach (Action action in Actions) 184 | { 185 | if (action.ActionType != ActionType.Compile && action.ActionType != ActionType.Link) 186 | continue; 187 | 188 | if (action.CommandPath.FullName.Contains("orbis")) 189 | { 190 | BuildType = FBBuildType.PS4; 191 | return; 192 | } 193 | else if (action.CommandArguments.Contains("Intermediate\\Build\\XboxOne")) 194 | { 195 | BuildType = FBBuildType.XBOne; 196 | return; 197 | } 198 | else if (action.CommandPath.FullName.Contains("Microsoft")) //Not a great test. 199 | { 200 | BuildType = FBBuildType.Windows; 201 | return; 202 | } 203 | } 204 | } 205 | 206 | private bool IsMSVC() { return BuildType == FBBuildType.Windows || BuildType == FBBuildType.XBOne; } 207 | private bool IsPS4() { return BuildType == FBBuildType.PS4; } 208 | private bool IsXBOnePDBUtil(Action action) { return action.CommandPath.FullName.Contains("XboxOnePDBFileUtil.exe"); } 209 | private bool IsPS4SymbolTool(Action action) { return action.CommandPath.FullName.Contains("PS4SymbolTool.exe"); } 210 | private string GetCompilerName() 211 | { 212 | switch (BuildType) 213 | { 214 | default: 215 | case FBBuildType.XBOne: 216 | case FBBuildType.Windows: return "UE4Compiler"; 217 | case FBBuildType.PS4: return "UE4PS4Compiler"; 218 | } 219 | } 220 | 221 | //Run FASTBuild on the list of actions. Relies on fbuild.exe being in the path. 222 | public override bool ExecuteActions(List Actions, bool bLogDetailedActionStats) 223 | { 224 | bool FASTBuildResult = true; 225 | if (Actions.Count > 0) 226 | { 227 | DetectBuildType(Actions); 228 | 229 | string FASTBuildFilePath = Path.Combine(UnrealBuildTool.EngineDirectory.FullName, "Intermediate", "Build", "fbuild.bff"); 230 | 231 | List LocalExecutorActions = new List(); 232 | 233 | if (CreateBffFile(Actions, FASTBuildFilePath, LocalExecutorActions)) 234 | { 235 | FASTBuildResult = ExecuteBffFile(FASTBuildFilePath); 236 | 237 | if (FASTBuildResult) 238 | { 239 | LocalExecutor localExecutor = new LocalExecutor(); 240 | FASTBuildResult = localExecutor.ExecuteActions(LocalExecutorActions, bLogDetailedActionStats); 241 | } 242 | 243 | // Generate dependency files in any case 244 | string ReportFilePath = Path.Combine(UnrealBuildTool.EngineDirectory.MakeRelativeTo(DirectoryReference.GetCurrentDirectory()), "Source", "report.html"); 245 | GenerateDependencyFiles(Actions, ReportFilePath); 246 | } 247 | else 248 | { 249 | FASTBuildResult = false; 250 | } 251 | } 252 | 253 | return FASTBuildResult; 254 | } 255 | 256 | private void AddText(string StringToWrite) 257 | { 258 | byte[] Info = new System.Text.UTF8Encoding(true).GetBytes(StringToWrite); 259 | bffOutputFileStream.Write(Info, 0, Info.Length); 260 | } 261 | 262 | 263 | private string SubstituteEnvironmentVariables(string commandLineString) 264 | { 265 | string outputString = commandLineString.Replace("$(DurangoXDK)", "$DurangoXDK$"); 266 | outputString = outputString.Replace("$(SCE_ORBIS_SDK_DIR)", "$SCE_ORBIS_SDK_DIR$"); 267 | outputString = outputString.Replace("$(DXSDK_DIR)", "$DXSDK_DIR$"); 268 | outputString = outputString.Replace("$(CommonProgramFiles)", "$CommonProgramFiles$"); 269 | 270 | return outputString; 271 | } 272 | 273 | private void ParseOriginalCommandPathAndArguments(Action Action, ref string CommandPath, ref string CommandArguments) 274 | { 275 | bool isClFilterUsed = Action.CommandPath.FullName.EndsWith("cl-filter.exe"); 276 | if (!isClFilterUsed) 277 | { 278 | CommandPath = Action.CommandPath.FullName; 279 | CommandArguments = Action.CommandArguments; 280 | } 281 | else 282 | { 283 | // In case of using cl-filter.exe we have to extract original command path and arguments 284 | // Command line has format {Action.DependencyListFile.Location} -- {Action.CommandPath} {Action.CommandArguments} /showIncludes 285 | int SeparatorIndex = Action.CommandArguments.IndexOf(" -- "); 286 | string CommandPathAndArguments = Action.CommandArguments.Substring(SeparatorIndex + 4).Replace("/showIncludes", ""); 287 | List Tokens = CommandLineParser.Parse(CommandPathAndArguments); 288 | CommandPath = Tokens[0]; 289 | CommandArguments = string.Join(" ", Tokens.GetRange(1, Tokens.Count - 1)); 290 | } 291 | } 292 | 293 | private Dictionary ParseCommandArguments(string CommandArguments, string[] SpecialOptions, bool SkipInputFile = false) 294 | { 295 | CommandArguments = SubstituteEnvironmentVariables(CommandArguments); 296 | List Tokens = CommandLineParser.Parse(CommandArguments); 297 | Dictionary ParsedCompilerOptions = new Dictionary(); 298 | 299 | // Replace response file with its content 300 | for (int i = 0; i < Tokens.Count; i++) 301 | { 302 | if (!Tokens[i].StartsWith("@\"")) 303 | { 304 | continue; 305 | } 306 | 307 | string ResponseFilePath = Tokens[i].Substring(2, Tokens[i].Length - 3); 308 | string ResponseFileText = SubstituteEnvironmentVariables(File.ReadAllText(ResponseFilePath)); 309 | 310 | Tokens.RemoveAt(i); 311 | Tokens.InsertRange(i, CommandLineParser.Parse(ResponseFileText)); 312 | 313 | if (ParsedCompilerOptions.ContainsKey("@")) 314 | { 315 | throw new Exception("Only one response file expected"); 316 | } 317 | 318 | ParsedCompilerOptions["@"] = ResponseFilePath; 319 | } 320 | 321 | // Search tokens for special options 322 | foreach (string SpecialOption in SpecialOptions) 323 | { 324 | for (int i = 0; i < Tokens.Count; ++i) 325 | { 326 | if (Tokens[i] == SpecialOption && i + 1 < Tokens.Count) 327 | { 328 | ParsedCompilerOptions[SpecialOption] = Tokens[i + 1]; 329 | Tokens.RemoveRange(i, 2); 330 | break; 331 | } 332 | else if (Tokens[i].StartsWith(SpecialOption)) 333 | { 334 | ParsedCompilerOptions[SpecialOption] = Tokens[i].Replace(SpecialOption, null); 335 | Tokens.RemoveAt(i); 336 | break; 337 | } 338 | } 339 | } 340 | 341 | //The search for the input file... we take the first non-argument we can find 342 | if (!SkipInputFile) 343 | { 344 | for (int i = 0; i < Tokens.Count; ++i) 345 | { 346 | string Token = Tokens[i]; 347 | // Skip tokens with values, I for cpp includes, l for resource compiler includes 348 | if (new[] { "/I", "/l", "/D", "-D", "-x", "-include" }.Contains(Token)) 349 | { 350 | ++i; 351 | } 352 | else if (!Token.StartsWith("/") && !Token.StartsWith("-") && !Token.StartsWith("\"-")) 353 | { 354 | ParsedCompilerOptions["InputFile"] = Token; 355 | Tokens.RemoveAt(i); 356 | break; 357 | } 358 | } 359 | } 360 | 361 | ParsedCompilerOptions["OtherOptions"] = string.Join(" ", Tokens) + " "; 362 | 363 | return ParsedCompilerOptions; 364 | } 365 | 366 | private bool AreActionsSorted(List InActions) 367 | { 368 | for (int ActionIndex = 0; ActionIndex < InActions.Count; ActionIndex++) 369 | { 370 | foreach (Action PrerequisiteAction in InActions[ActionIndex].PrerequisiteActions) 371 | { 372 | if (ActionIndex < InActions.IndexOf(PrerequisiteAction)) 373 | { 374 | return false; 375 | } 376 | } 377 | } 378 | 379 | return true; 380 | } 381 | 382 | private void AddActionSorted(Action ActionToAdd, List Actions, HashSet AddedActions, Stack DependencyChain) 383 | { 384 | DependencyChain.Push(ActionToAdd); 385 | 386 | foreach (Action PrerequisiteAction in ActionToAdd.PrerequisiteActions) 387 | { 388 | if (DependencyChain.Contains(PrerequisiteAction)) 389 | { 390 | Log.TraceError("Action is not topologically sorted."); 391 | Log.TraceError(" {0} {1}", ActionToAdd.CommandPath, ActionToAdd.CommandArguments); 392 | Log.TraceError("Dependency"); 393 | Log.TraceError(" {0} {1}", PrerequisiteAction.CommandPath, PrerequisiteAction.CommandArguments); 394 | throw new BuildException("Cyclical Dependency in action graph."); 395 | } 396 | 397 | AddActionSorted(PrerequisiteAction, Actions, AddedActions, DependencyChain); 398 | } 399 | 400 | DependencyChain.Pop(); 401 | 402 | if (!AddedActions.Contains(ActionToAdd)) 403 | { 404 | AddedActions.Add(ActionToAdd); 405 | Actions.Add(ActionToAdd); 406 | } 407 | } 408 | 409 | private List SortActions(List InActions) 410 | { 411 | if (AreActionsSorted(InActions)) 412 | { 413 | return InActions; 414 | } 415 | 416 | List Actions = new List(); 417 | HashSet AddedActions = new HashSet(); 418 | foreach (Action Action in InActions) 419 | { 420 | if (!AddedActions.Contains(Action)) 421 | { 422 | AddActionSorted(Action, Actions, AddedActions, new Stack()); 423 | } 424 | } 425 | 426 | if (!Actions.All(A => InActions.Contains(A))) 427 | { 428 | throw new BuildException("Prerequisite actions not in source list."); 429 | } 430 | 431 | return Actions; 432 | } 433 | 434 | private string GetOptionValue(Dictionary OptionsDictionary, string Key, Action Action, bool ProblemIfNotFound = false) 435 | { 436 | string Value = string.Empty; 437 | if (OptionsDictionary.TryGetValue(Key, out Value)) 438 | { 439 | return Value.Trim(new Char[] { '\"' }); 440 | } 441 | 442 | if (ProblemIfNotFound) 443 | { 444 | Log.TraceError("We failed to find " + Key + ", which may be a problem."); 445 | Log.TraceError("Action.CommandArguments: " + Action.CommandArguments); 446 | } 447 | 448 | return Value; 449 | } 450 | 451 | public string GetRegistryValue(string keyName, string valueName, object defaultValue) 452 | { 453 | object returnValue = (string)Microsoft.Win32.Registry.GetValue("HKEY_LOCAL_MACHINE\\SOFTWARE\\" + keyName, valueName, defaultValue); 454 | if (returnValue != null) 455 | return returnValue.ToString(); 456 | 457 | returnValue = Microsoft.Win32.Registry.GetValue("HKEY_CURRENT_USER\\SOFTWARE\\" + keyName, valueName, defaultValue); 458 | if (returnValue != null) 459 | return returnValue.ToString(); 460 | 461 | returnValue = (string)Microsoft.Win32.Registry.GetValue("HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\" + keyName, valueName, defaultValue); 462 | if (returnValue != null) 463 | return returnValue.ToString(); 464 | 465 | returnValue = Microsoft.Win32.Registry.GetValue("HKEY_CURRENT_USER\\SOFTWARE\\Wow6432Node\\" + keyName, valueName, defaultValue); 466 | if (returnValue != null) 467 | return returnValue.ToString(); 468 | 469 | return defaultValue.ToString(); 470 | } 471 | 472 | private void WriteEnvironmentSetup() 473 | { 474 | DirectoryReference VCInstallDir = null; 475 | string VCToolPath64 = ""; 476 | VCEnvironment VCEnv = null; 477 | 478 | try 479 | { 480 | // This may fail if the caller emptied PATH; we try to ignore the problem since 481 | // it probably means we are building for another platform. 482 | if (BuildType == FBBuildType.Windows) 483 | { 484 | VCEnv = VCEnvironment.Create(WindowsPlatform.GetDefaultCompiler(null), CppPlatform.Win64, null, null); 485 | } 486 | else if (BuildType == FBBuildType.XBOne) 487 | { 488 | // If you have XboxOne source access, uncommenting the line below will be better for selecting the appropriate version of the compiler. 489 | // Translate the XboxOne compiler to the right Windows compiler to set the VC environment vars correctly... 490 | // WindowsCompiler windowsCompiler = XboxOnePlatform.GetDefaultCompiler() == XboxOneCompiler.VisualStudio2015 ? WindowsCompiler.VisualStudio2015 : WindowsCompiler.VisualStudio2017; 491 | // VCEnv = VCEnvironment.Create(windowsCompiler, CppPlatform.Win64, null, null); 492 | } 493 | } 494 | catch (Exception) 495 | { 496 | Log.TraceError("Failed to get Visual Studio environment."); 497 | } 498 | 499 | // Copy environment into a case-insensitive dictionary for easier key lookups 500 | Dictionary envVars = new Dictionary(StringComparer.OrdinalIgnoreCase); 501 | foreach (DictionaryEntry entry in Environment.GetEnvironmentVariables()) 502 | { 503 | envVars[(string)entry.Key] = (string)entry.Value; 504 | } 505 | 506 | if (envVars.ContainsKey("CommonProgramFiles")) 507 | { 508 | AddText("#import CommonProgramFiles\n"); 509 | } 510 | 511 | if (envVars.ContainsKey("DXSDK_DIR")) 512 | { 513 | AddText("#import DXSDK_DIR\n"); 514 | } 515 | 516 | if (envVars.ContainsKey("DurangoXDK")) 517 | { 518 | AddText("#import DurangoXDK\n"); 519 | } 520 | 521 | if (VCEnv != null) 522 | { 523 | string platformVersionNumber = "VSVersionUnknown"; 524 | 525 | switch (VCEnv.Compiler) 526 | { 527 | case WindowsCompiler.VisualStudio2015: 528 | platformVersionNumber = "140"; 529 | break; 530 | 531 | case WindowsCompiler.VisualStudio2017: 532 | // For now we are working with the 140 version, might need to change to 141 or 150 depending on the version of the Toolchain you chose 533 | // to install 534 | platformVersionNumber = "140"; 535 | break; 536 | 537 | case WindowsCompiler.VisualStudio2019: 538 | platformVersionNumber = "140"; 539 | break; 540 | 541 | default: 542 | string exceptionString = "Error: Unsupported Visual Studio Version."; 543 | Log.TraceError(exceptionString); 544 | throw new BuildException(exceptionString); 545 | } 546 | 547 | 548 | if (!WindowsPlatform.TryGetVSInstallDir(WindowsPlatform.GetDefaultCompiler(null), out VCInstallDir)) 549 | { 550 | string exceptionString = "Error: Cannot locate Visual Studio Installation."; 551 | Log.TraceError(exceptionString); 552 | throw new BuildException(exceptionString); 553 | } 554 | 555 | VCToolPath64 = VCEnvironment.GetVCToolPath64(WindowsPlatform.GetDefaultCompiler(null), VCEnv.ToolChainDir).ToString(); 556 | 557 | string debugVCToolPath64 = VCEnv.CompilerPath.Directory.ToString(); 558 | 559 | AddText(string.Format(".WindowsSDKBasePath = '{0}'\n", VCEnv.WindowsSdkDir)); 560 | 561 | AddText("Compiler('UE4ResourceCompiler') \n{\n"); 562 | AddText(string.Format("\t.Executable = '{0}'\n", VCEnv.ResourceCompilerPath)); 563 | AddText("\t.CompilerFamily = 'custom'\n"); 564 | AddText("}\n\n"); 565 | 566 | 567 | AddText("Compiler('UE4Compiler') \n{\n"); 568 | 569 | AddText(string.Format("\t.Root = '{0}'\n", VCEnv.CompilerPath.Directory)); 570 | AddText("\t.Executable = '$Root$/cl.exe'\n"); 571 | AddText("\t.ExtraFiles =\n\t{\n"); 572 | AddText("\t\t'$Root$/c1.dll'\n"); 573 | AddText("\t\t'$Root$/c1xx.dll'\n"); 574 | AddText("\t\t'$Root$/c2.dll'\n"); 575 | 576 | if (File.Exists(FileReference.Combine(VCEnv.CompilerPath.Directory, "1033/clui.dll").ToString())) //Check English first... 577 | { 578 | AddText("\t\t'$Root$/1033/clui.dll'\n"); 579 | } 580 | else 581 | { 582 | var numericDirectories = Directory.GetDirectories(VCToolPath64).Where(d => Path.GetFileName(d).All(char.IsDigit)); 583 | var cluiDirectories = numericDirectories.Where(d => Directory.GetFiles(d, "clui.dll").Any()); 584 | if (cluiDirectories.Any()) 585 | { 586 | AddText(string.Format("\t\t'$Root$/{0}/clui.dll'\n", Path.GetFileName(cluiDirectories.First()))); 587 | } 588 | } 589 | AddText("\t\t'$Root$/mspdbsrv.exe'\n"); 590 | AddText("\t\t'$Root$/mspdbcore.dll'\n"); 591 | 592 | AddText(string.Format("\t\t'$Root$/mspft{0}.dll'\n", platformVersionNumber)); 593 | AddText(string.Format("\t\t'$Root$/msobj{0}.dll'\n", platformVersionNumber)); 594 | AddText(string.Format("\t\t'$Root$/mspdb{0}.dll'\n", platformVersionNumber)); 595 | 596 | if (VCEnv.Compiler == WindowsCompiler.VisualStudio2015) 597 | { 598 | AddText(string.Format("\t\t'{0}/VC/redist/x64/Microsoft.VC{1}.CRT/msvcp{2}.dll'\n", VCInstallDir.ToString(), platformVersionNumber, platformVersionNumber)); 599 | AddText(string.Format("\t\t'{0}/VC/redist/x64/Microsoft.VC{1}.CRT/vccorlib{2}.dll'\n", VCInstallDir.ToString(), platformVersionNumber, platformVersionNumber)); 600 | } 601 | else if (VCEnv.Compiler == WindowsCompiler.VisualStudio2017) 602 | { 603 | //VS 2017 is really confusing in terms of version numbers and paths so these values might need to be modified depending on what version of the tool chain you 604 | // chose to install. 605 | AddText(string.Format("\t\t'{0}/VC/Redist/MSVC/14.13.26020/x64/Microsoft.VC141.CRT/msvcp{1}.dll'\n", VCInstallDir.ToString(), platformVersionNumber)); 606 | AddText(string.Format("\t\t'{0}/VC/Redist/MSVC/14.13.26020/x64/Microsoft.VC141.CRT/vccorlib{1}.dll'\n", VCInstallDir.ToString(), platformVersionNumber)); 607 | } 608 | else if (VCEnv.Compiler == WindowsCompiler.VisualStudio2019) 609 | { 610 | AddText(string.Format("\t\t'{0}/VC/Redist/MSVC/14.22.27821/x64/Microsoft.VC142.CRT/msvcp{1}.dll'\n", VCInstallDir.ToString(), platformVersionNumber)); 611 | AddText(string.Format("\t\t'{0}/VC/Redist/MSVC/14.22.27821/x64/Microsoft.VC142.CRT/vccorlib{1}.dll'\n", VCInstallDir.ToString(), platformVersionNumber)); 612 | } 613 | 614 | AddText("\t}\n"); //End extra files 615 | 616 | AddText("}\n\n"); //End compiler 617 | } 618 | 619 | if (envVars.ContainsKey("SCE_ORBIS_SDK_DIR")) 620 | { 621 | AddText(string.Format(".SCE_ORBIS_SDK_DIR = '{0}'\n", envVars["SCE_ORBIS_SDK_DIR"])); 622 | AddText(string.Format(".PS4BasePath = '{0}/host_tools/bin'\n\n", envVars["SCE_ORBIS_SDK_DIR"])); 623 | AddText("Compiler('UE4PS4Compiler') \n{\n"); 624 | AddText("\t.Executable = '$PS4BasePath$/orbis-clang.exe'\n"); 625 | AddText("\t.ExtraFiles = '$PS4BasePath$/orbis-snarl.exe'\n"); 626 | AddText("}\n\n"); 627 | } 628 | 629 | AddText("Settings \n{\n"); 630 | 631 | // Optional cachePath user setting 632 | if (bEnableCaching && CachePath != "") 633 | { 634 | AddText(string.Format("\t.CachePath = '{0}'\n", CachePath)); 635 | } 636 | 637 | //Start Environment 638 | AddText("\t.Environment = \n\t{\n"); 639 | if (VCEnv != null) 640 | { 641 | AddText(string.Format("\t\t\"PATH={0}\\Common7\\IDE\\;{1}\",\n", VCInstallDir.ToString(), VCToolPath64)); 642 | if (VCEnv.IncludePaths.Count() > 0) 643 | { 644 | AddText(string.Format("\t\t\"INCLUDE={0}\",\n", String.Join(";", VCEnv.IncludePaths.Select(x => x)))); 645 | } 646 | 647 | if (VCEnv.LibraryPaths.Count() > 0) 648 | { 649 | AddText(string.Format("\t\t\"LIB={0}\",\n", String.Join(";", VCEnv.LibraryPaths.Select(x => x)))); 650 | } 651 | } 652 | if (envVars.ContainsKey("TMP")) 653 | AddText(string.Format("\t\t\"TMP={0}\",\n", envVars["TMP"])); 654 | if (envVars.ContainsKey("SystemRoot")) 655 | AddText(string.Format("\t\t\"SystemRoot={0}\",\n", envVars["SystemRoot"])); 656 | if (envVars.ContainsKey("INCLUDE")) 657 | AddText(string.Format("\t\t\"INCLUDE={0}\",\n", envVars["INCLUDE"])); 658 | if (envVars.ContainsKey("LIB")) 659 | AddText(string.Format("\t\t\"LIB={0}\",\n", envVars["LIB"])); 660 | 661 | AddText("\t}\n"); //End environment 662 | AddText("}\n\n"); //End Settings 663 | } 664 | 665 | private void AddCompileAction(Action Action, int ActionIndex, List DependencyIndices) 666 | { 667 | string CompilerName = GetCompilerName(); 668 | if (Action.CommandPath.FullName.Contains("rc.exe")) 669 | { 670 | CompilerName = "UE4ResourceCompiler"; 671 | } 672 | 673 | string OriginalCommandPath = null; 674 | string OriginalCommandArguments = null; 675 | ParseOriginalCommandPathAndArguments(Action, ref OriginalCommandPath, ref OriginalCommandArguments); 676 | 677 | string[] SpecialCompilerOptions = { "/Fo", "/fo", "/Yc", "/Yu", "/Fp", "-o" }; 678 | var ParsedCompilerOptions = ParseCommandArguments(OriginalCommandArguments, SpecialCompilerOptions); 679 | 680 | string OutputObjectFileName = GetOptionValue(ParsedCompilerOptions, IsMSVC() ? "/Fo" : "-o", Action, ProblemIfNotFound: !IsMSVC()); 681 | 682 | string SourceFileExtension = ".cpp"; 683 | if (OutputObjectFileName != null && (OutputObjectFileName.EndsWith("c.obj") || OutputObjectFileName.EndsWith("c.o"))) 684 | { 685 | SourceFileExtension = ".c"; 686 | } 687 | 688 | if (IsMSVC() && string.IsNullOrEmpty(OutputObjectFileName)) // Didn't find /Fo, try /fo 689 | { 690 | OutputObjectFileName = GetOptionValue(ParsedCompilerOptions, "/fo", Action, ProblemIfNotFound: true); 691 | } 692 | 693 | if (string.IsNullOrEmpty(OutputObjectFileName)) //No /Fo or /fo, we're probably in trouble. 694 | { 695 | Log.TraceError("We have no OutputObjectFileName. Bailing."); 696 | return; 697 | } 698 | 699 | string IntermediatePath = Path.GetDirectoryName(OutputObjectFileName); 700 | if (string.IsNullOrEmpty(IntermediatePath)) 701 | { 702 | Log.TraceError("We have no IntermediatePath. Bailing."); 703 | Log.TraceError("Our Action.CommandArguments were: " + Action.CommandArguments); 704 | return; 705 | } 706 | 707 | string InputFile = GetOptionValue(ParsedCompilerOptions, "InputFile", Action, ProblemIfNotFound: true); 708 | if (string.IsNullOrEmpty(InputFile)) 709 | { 710 | Log.TraceError("We have no InputFile. Bailing."); 711 | return; 712 | } 713 | 714 | AddText(string.Format("; \"{0}\" {1}\n", Action.CommandPath, Action.CommandArguments)); 715 | AddText(string.Format("ObjectList('Action_{0}')\n{{\n", ActionIndex)); 716 | AddText(string.Format("\t.Compiler = '{0}' \n", CompilerName)); 717 | AddText(string.Format("\t.CompilerInputFiles = \"{0}\"\n", InputFile)); 718 | AddText(string.Format("\t.CompilerOutputPath = \"{0}\"\n", IntermediatePath)); 719 | 720 | 721 | bool bSkipDistribution = false; 722 | foreach (var it in ForceLocalCompileModules) 723 | { 724 | if (Path.GetFullPath(InputFile).Contains(it)) 725 | { 726 | bSkipDistribution = true; 727 | break; 728 | } 729 | } 730 | 731 | 732 | if (!Action.bCanExecuteRemotely || !Action.bCanExecuteRemotelyWithSNDBS || bSkipDistribution) 733 | { 734 | AddText(string.Format("\t.AllowDistribution = false\n")); 735 | } 736 | 737 | string OtherCompilerOptions = GetOptionValue(ParsedCompilerOptions, "OtherOptions", Action); 738 | string CompilerOutputExtension = ".unset"; 739 | 740 | if (ParsedCompilerOptions.ContainsKey("/Yc")) //Create PCH 741 | { 742 | string PCHIncludeHeader = GetOptionValue(ParsedCompilerOptions, "/Yc", Action, ProblemIfNotFound: true); 743 | string PCHOutputFile = GetOptionValue(ParsedCompilerOptions, "/Fp", Action, ProblemIfNotFound: true); 744 | 745 | AddText(string.Format("\t.CompilerOptions = '\"%1\" /Fo\"%2\" /Fp\"{0}\" /Yu\"{1}\" {2} '\n", PCHOutputFile, PCHIncludeHeader, OtherCompilerOptions)); 746 | 747 | AddText(string.Format("\t.PCHOptions = '\"%1\" /Fp\"%2\" /Yc\"{0}\" {1} /Fo\"{2}\"'\n", PCHIncludeHeader, OtherCompilerOptions, OutputObjectFileName)); 748 | AddText(string.Format("\t.PCHInputFile = \"{0}\"\n", InputFile)); 749 | AddText(string.Format("\t.PCHOutputFile = \"{0}\"\n", PCHOutputFile)); 750 | CompilerOutputExtension = ".obj"; 751 | } 752 | else if (ParsedCompilerOptions.ContainsKey("/Yu")) //Use PCH 753 | { 754 | string PCHIncludeHeader = GetOptionValue(ParsedCompilerOptions, "/Yu", Action, ProblemIfNotFound: true); 755 | string PCHOutputFile = GetOptionValue(ParsedCompilerOptions, "/Fp", Action, ProblemIfNotFound: true); 756 | string PCHToForceInclude = PCHOutputFile.Replace(".pch", ""); 757 | AddText(string.Format("\t.CompilerOptions = '\"%1\" /Fo\"%2\" /Fp\"{0}\" /Yu\"{1}\" /FI\"{2}\" {3} '\n", PCHOutputFile, PCHIncludeHeader, PCHToForceInclude, OtherCompilerOptions)); 758 | CompilerOutputExtension = SourceFileExtension + ".obj"; 759 | } 760 | else 761 | { 762 | if (CompilerName == "UE4ResourceCompiler") 763 | { 764 | AddText(string.Format("\t.CompilerOptions = '{0} /fo\"%2\" \"%1\" '\n", OtherCompilerOptions)); 765 | CompilerOutputExtension = Path.GetExtension(InputFile) + ".res"; 766 | } 767 | else 768 | { 769 | if (IsMSVC()) 770 | { 771 | AddText(string.Format("\t.CompilerOptions = '{0} /Fo\"%2\" \"%1\" '\n", OtherCompilerOptions)); 772 | CompilerOutputExtension = SourceFileExtension + ".obj"; 773 | } 774 | else 775 | { 776 | AddText(string.Format("\t.CompilerOptions = '{0} -o \"%2\" \"%1\" '\n", OtherCompilerOptions)); 777 | CompilerOutputExtension = SourceFileExtension + ".o"; 778 | } 779 | } 780 | } 781 | 782 | AddText(string.Format("\t.CompilerOutputExtension = '{0}' \n", CompilerOutputExtension)); 783 | 784 | if (DependencyIndices.Count > 0) 785 | { 786 | List DependencyNames = DependencyIndices.ConvertAll(x => string.Format("'Action_{0}'", x)); 787 | AddText(string.Format("\t.PreBuildDependencies = {{ {0} }}\n", string.Join(",", DependencyNames.ToArray()))); 788 | } 789 | 790 | AddText(string.Format("}}\n\n")); 791 | } 792 | 793 | private void AddLinkAction(List Actions, int ActionIndex, List DependencyIndices) 794 | { 795 | Action Action = Actions[ActionIndex]; 796 | 797 | string OriginalCommandPath = null; 798 | string OriginalCommandArguments = null; 799 | ParseOriginalCommandPathAndArguments(Action, ref OriginalCommandPath, ref OriginalCommandArguments); 800 | 801 | string[] SpecialLinkerOptions = { "/OUT:", "-o" }; 802 | var ParsedLinkerOptions = ParseCommandArguments(OriginalCommandArguments, SpecialLinkerOptions, SkipInputFile: OriginalCommandPath.Contains("orbis-clang")); 803 | 804 | string OutputFile; 805 | 806 | if (IsXBOnePDBUtil(Action)) 807 | { 808 | OutputFile = ParsedLinkerOptions["OtherOptions"].Trim(' ').Trim('"'); 809 | } 810 | else if (IsMSVC()) 811 | { 812 | OutputFile = GetOptionValue(ParsedLinkerOptions, "/OUT:", Action, ProblemIfNotFound: true); 813 | } 814 | else //PS4 815 | { 816 | OutputFile = GetOptionValue(ParsedLinkerOptions, "-o", Action, ProblemIfNotFound: false); 817 | if (string.IsNullOrEmpty(OutputFile)) 818 | { 819 | OutputFile = GetOptionValue(ParsedLinkerOptions, "InputFile", Action, ProblemIfNotFound: true); 820 | } 821 | } 822 | 823 | if (string.IsNullOrEmpty(OutputFile)) 824 | { 825 | Log.TraceError("Failed to find output file. Bailing."); 826 | return; 827 | } 828 | 829 | string ResponseFilePath = GetOptionValue(ParsedLinkerOptions, "@", Action); 830 | string OtherCompilerOptions = GetOptionValue(ParsedLinkerOptions, "OtherOptions", Action); 831 | 832 | List PrebuildDependencies = new List(); 833 | 834 | AddText(string.Format("; \"{0}\" {1}\n", Action.CommandPath, Action.CommandArguments)); 835 | 836 | if (IsXBOnePDBUtil(Action)) 837 | { 838 | AddText(string.Format("Exec('Action_{0}')\n{{\n", ActionIndex)); 839 | AddText(string.Format("\t.ExecExecutable = '{0}'\n", Action.CommandPath)); 840 | AddText(string.Format("\t.ExecArguments = '{0}'\n", Action.CommandArguments)); 841 | AddText(string.Format("\t.ExecInput = {{ {0} }} \n", ParsedLinkerOptions["InputFile"])); 842 | AddText(string.Format("\t.ExecOutput = '{0}' \n", OutputFile)); 843 | AddText(string.Format("\t.PreBuildDependencies = {{ {0} }} \n", ParsedLinkerOptions["InputFile"])); // Why is InputFile used? No actions with such names! 844 | AddText(string.Format("}}\n\n")); 845 | } 846 | else if (IsPS4SymbolTool(Action)) 847 | { 848 | string searchString = "-map=\""; 849 | int execArgumentStart = Action.CommandArguments.LastIndexOf(searchString) + searchString.Length; 850 | int execArgumentEnd = Action.CommandArguments.IndexOf("\"", execArgumentStart); 851 | string ExecOutput = Action.CommandArguments.Substring(execArgumentStart, execArgumentEnd - execArgumentStart); 852 | 853 | AddText(string.Format("Exec('Action_{0}')\n{{\n", ActionIndex)); 854 | AddText(string.Format("\t.ExecExecutable = '{0}'\n", Action.CommandPath)); 855 | AddText(string.Format("\t.ExecArguments = '{0}'\n", Action.CommandArguments)); 856 | AddText(string.Format("\t.ExecOutput = '{0}'\n", ExecOutput)); 857 | AddText(string.Format("\t.PreBuildDependencies = {{ 'Action_{0}' }} \n", ActionIndex - 1)); // Should it be used real dependencies instead of previous action? 858 | AddText(string.Format("}}\n\n")); 859 | } 860 | else if (OriginalCommandPath.Contains("lib.exe") || OriginalCommandPath.Contains("orbis-snarl")) 861 | { 862 | if (DependencyIndices.Count > 0) 863 | { 864 | for (int i = 0; i < DependencyIndices.Count; ++i) //Don't specify pch or resource files, they have the wrong name and the response file will have them anyways. 865 | { 866 | int depIndex = DependencyIndices[i]; 867 | foreach (FileItem item in Actions[depIndex].ProducedItems) 868 | { 869 | if (item.ToString().Contains(".pch") || item.ToString().Contains(".res")) 870 | { 871 | DependencyIndices.RemoveAt(i); 872 | i--; 873 | PrebuildDependencies.Add(depIndex); 874 | break; 875 | } 876 | } 877 | } 878 | } 879 | 880 | AddText(string.Format("Library('Action_{0}')\n{{\n", ActionIndex)); 881 | AddText(string.Format("\t.Compiler = '{0}'\n", GetCompilerName())); 882 | if (IsMSVC()) 883 | AddText(string.Format("\t.CompilerOptions = '\"%1\" /Fo\"%2\" /c'\n")); 884 | else 885 | AddText(string.Format("\t.CompilerOptions = '\"%1\" -o \"%2\" -c'\n")); 886 | AddText(string.Format("\t.CompilerOutputPath = \"{0}\"\n", Path.GetDirectoryName(OutputFile))); 887 | AddText(string.Format("\t.Librarian = '{0}' \n", Action.CommandPath)); 888 | 889 | if (!string.IsNullOrEmpty(ResponseFilePath)) 890 | { 891 | if (IsMSVC()) 892 | // /ignore:4042 to turn off the linker warning about the output option being present twice (command-line + rsp file) 893 | AddText(string.Format("\t.LibrarianOptions = ' /OUT:\"%2\" /ignore:4042 @\"{0}\" \"%1\"' \n", ResponseFilePath)); 894 | else if (IsPS4()) 895 | AddText(string.Format("\t.LibrarianOptions = '\"%2\" @\"%1\"' \n", ResponseFilePath)); 896 | else 897 | AddText(string.Format("\t.LibrarianOptions = '\"%2\" @\"%1\" {0}' \n", OtherCompilerOptions)); 898 | } 899 | else 900 | { 901 | if (IsMSVC()) 902 | AddText(string.Format("\t.LibrarianOptions = ' /OUT:\"%2\" {0} \"%1\"' \n", OtherCompilerOptions)); 903 | } 904 | 905 | if (DependencyIndices.Count > 0) 906 | { 907 | List DependencyNames = DependencyIndices.ConvertAll(x => string.Format("'Action_{0}'", x)); 908 | 909 | if (IsPS4()) 910 | AddText(string.Format("\t.LibrarianAdditionalInputs = {{ '{0}' }} \n", ResponseFilePath)); // Hack...Because FastBuild needs at least one Input file 911 | else if (!string.IsNullOrEmpty(ResponseFilePath)) 912 | AddText(string.Format("\t.LibrarianAdditionalInputs = {{ {0} }} \n", DependencyNames[0])); // Hack...Because FastBuild needs at least one Input file 913 | else if (IsMSVC()) 914 | AddText(string.Format("\t.LibrarianAdditionalInputs = {{ {0} }} \n", string.Join(",", DependencyNames.ToArray()))); 915 | 916 | PrebuildDependencies.AddRange(DependencyIndices); 917 | } 918 | else 919 | { 920 | string InputFile = GetOptionValue(ParsedLinkerOptions, "InputFile", Action, ProblemIfNotFound: true); 921 | if (InputFile != null && InputFile.Length > 0) 922 | AddText(string.Format("\t.LibrarianAdditionalInputs = {{ '{0}' }} \n", InputFile)); 923 | } 924 | 925 | if (PrebuildDependencies.Count > 0) 926 | { 927 | List PrebuildDependencyNames = PrebuildDependencies.ConvertAll(x => string.Format("'Action_{0}'", x)); 928 | AddText(string.Format("\t.PreBuildDependencies = {{ {0} }} \n", string.Join(",", PrebuildDependencyNames.ToArray()))); 929 | } 930 | 931 | AddText(string.Format("\t.LibrarianOutput = '{0}' \n", OutputFile)); 932 | AddText(string.Format("}}\n\n")); 933 | } 934 | else if (OriginalCommandPath.Contains("link.exe") || OriginalCommandPath.Contains("orbis-clang")) 935 | { 936 | if (DependencyIndices.Count > 0) //Insert a dummy node to make sure all of the dependencies are finished. 937 | //If FASTBuild supports PreBuildDependencies on the Executable action we can remove this. 938 | { 939 | string dummyText = string.IsNullOrEmpty(ResponseFilePath) ? GetOptionValue(ParsedLinkerOptions, "InputFile", Action) : ResponseFilePath; 940 | File.SetLastAccessTimeUtc(dummyText, DateTime.UtcNow); 941 | AddText(string.Format("Copy('Action_{0}_dummy')\n{{ \n", ActionIndex)); 942 | AddText(string.Format("\t.Source = '{0}' \n", dummyText)); 943 | AddText(string.Format("\t.Dest = '{0}' \n", dummyText + ".dummy")); 944 | List DependencyNames = DependencyIndices.ConvertAll(x => string.Format("\t\t'Action_{0}', ;{1}", x, Actions[x].StatusDescription)); 945 | AddText(string.Format("\t.PreBuildDependencies = {{\n{0}\n\t}} \n", string.Join("\n", DependencyNames.ToArray()))); 946 | AddText(string.Format("}}\n\n")); 947 | } 948 | 949 | AddText(string.Format("Executable('Action_{0}')\n{{ \n", ActionIndex)); 950 | AddText(string.Format("\t.Linker = '{0}' \n", Action.CommandPath)); 951 | 952 | if (DependencyIndices.Count == 0) 953 | { 954 | AddText(string.Format("\t.Libraries = {{ '{0}' }} \n", ResponseFilePath)); 955 | if (IsMSVC()) 956 | { 957 | if (BuildType == FBBuildType.XBOne) 958 | { 959 | AddText(string.Format("\t.LinkerOptions = '/TLBOUT:\"%1\" /Out:\"%2\" @\"{0}\" {1} ' \n", ResponseFilePath, OtherCompilerOptions)); // The TLBOUT is a huge bodge to consume the %1. 960 | } 961 | else 962 | { 963 | // /ignore:4042 to turn off the linker warning about the output option being present twice (command-line + rsp file) 964 | AddText(string.Format("\t.LinkerOptions = '/TLBOUT:\"%1\" /ignore:4042 /Out:\"%2\" @\"{0}\" ' \n", ResponseFilePath)); // The TLBOUT is a huge bodge to consume the %1. 965 | } 966 | } 967 | else 968 | AddText(string.Format("\t.LinkerOptions = '{0} -o \"%2\" @\"%1\"' \n", OtherCompilerOptions)); // The MQ is a huge bodge to consume the %1. 969 | } 970 | else 971 | { 972 | AddText(string.Format("\t.Libraries = 'Action_{0}_dummy' \n", ActionIndex)); 973 | if (IsMSVC()) 974 | { 975 | if (BuildType == FBBuildType.XBOne) 976 | { 977 | AddText(string.Format("\t.LinkerOptions = '/TLBOUT:\"%1\" /Out:\"%2\" @\"{0}\" {1} ' \n", ResponseFilePath, OtherCompilerOptions)); // The TLBOUT is a huge bodge to consume the %1. 978 | } 979 | else 980 | { 981 | AddText(string.Format("\t.LinkerOptions = '/TLBOUT:\"%1\" /Out:\"%2\" @\"{0}\" ' \n", ResponseFilePath)); // The TLBOUT is a huge bodge to consume the %1. 982 | } 983 | } 984 | else 985 | AddText(string.Format("\t.LinkerOptions = '{0} -o \"%2\" @\"%1\"' \n", OtherCompilerOptions)); // The MQ is a huge bodge to consume the %1. 986 | } 987 | 988 | AddText(string.Format("\t.LinkerOutput = '{0}' \n", OutputFile)); 989 | AddText(string.Format("}}\n\n")); 990 | } 991 | } 992 | 993 | private FileStream bffOutputFileStream = null; 994 | 995 | private bool IsSupportedAction(Action Action) 996 | { 997 | return Action.ActionType == ActionType.Compile || Action.ActionType == ActionType.Link; 998 | } 999 | 1000 | private bool CreateBffFile(List InActions, string BffFilePath, List LocalExecutorActions) 1001 | { 1002 | List Actions = SortActions(InActions); 1003 | 1004 | try 1005 | { 1006 | bffOutputFileStream = new FileStream(BffFilePath, FileMode.Create, FileAccess.Write); 1007 | 1008 | WriteEnvironmentSetup(); //Compiler, environment variables and base paths 1009 | 1010 | for (int ActionIndex = 0; ActionIndex < Actions.Count; ActionIndex++) 1011 | { 1012 | Action Action = Actions[ActionIndex]; 1013 | 1014 | // Resolve dependencies 1015 | List DependencyIndices = new List(); 1016 | foreach (Action PrerequisiteAction in Action.PrerequisiteActions) 1017 | { 1018 | if (IsSupportedAction(PrerequisiteAction)) 1019 | { 1020 | DependencyIndices.Add(Actions.IndexOf(PrerequisiteAction)); 1021 | } 1022 | } 1023 | 1024 | if (Action.ActionType == ActionType.Compile) 1025 | { 1026 | AddCompileAction(Action, ActionIndex, DependencyIndices); 1027 | } 1028 | else if (Action.ActionType == ActionType.Link) 1029 | { 1030 | AddLinkAction(Actions, ActionIndex, DependencyIndices); 1031 | } 1032 | else if (Action.ActionType == ActionType.BuildProject || Action.ActionType == ActionType.PostBuildStep || Action.ActionType == ActionType.WriteMetadata) 1033 | { 1034 | LocalExecutorActions.Add(Action); 1035 | Log.TraceLog("Unsupported action will will be executed using local executor, {0}, \"{0}\" {1}", Action.ActionType.ToString(), Action.CommandPath, Action.CommandArguments); 1036 | } 1037 | else 1038 | { 1039 | Log.TraceWarning("Unsupported action will be ignored, {0}, \"{0}\" {1}", Action.ActionType.ToString(), Action.CommandPath, Action.CommandArguments); 1040 | } 1041 | } 1042 | 1043 | int LastSupportedAction = Actions.FindLastIndex(Action => IsSupportedAction(Action)); 1044 | if (LastSupportedAction != -1) 1045 | { 1046 | AddText("Alias( 'all' ) \n{\n"); 1047 | AddText("\t.Targets = { \n"); 1048 | for (int ActionIndex = 0; ActionIndex <= LastSupportedAction; ActionIndex++) 1049 | { 1050 | if (IsSupportedAction(Actions[ActionIndex])) 1051 | { 1052 | AddText(string.Format("\t\t'Action_{0}'{1}", ActionIndex, ActionIndex < LastSupportedAction ? ",\n" : "\n\t}\n")); 1053 | } 1054 | } 1055 | AddText("}\n"); 1056 | } 1057 | else 1058 | { 1059 | AddText("RemoveDir( 'all' )\n{\n"); 1060 | AddText("\t.RemovePaths = 'ThereIsNoFolderWithSuchNameIThink' \n"); 1061 | AddText("}\n"); 1062 | } 1063 | 1064 | bffOutputFileStream.Close(); 1065 | } 1066 | catch (Exception e) 1067 | { 1068 | Log.TraceError("Exception while creating bff file: " + e.ToString()); 1069 | return false; 1070 | } 1071 | 1072 | return true; 1073 | } 1074 | 1075 | private bool ExecuteBffFile(string BffFilePath) 1076 | { 1077 | string cacheArgument = ""; 1078 | 1079 | if (bEnableCaching) 1080 | { 1081 | switch (CacheMode) 1082 | { 1083 | case eCacheMode.ReadOnly: 1084 | cacheArgument = "-cacheread"; 1085 | break; 1086 | case eCacheMode.WriteOnly: 1087 | cacheArgument = "-cachewrite"; 1088 | break; 1089 | case eCacheMode.ReadWrite: 1090 | cacheArgument = "-cache"; 1091 | break; 1092 | } 1093 | } 1094 | 1095 | string distArgument = bEnableDistribution ? "-dist" : ""; 1096 | 1097 | //Interesting flags for FASTBuild: -nostoponerror, -verbose, -monitor (if FASTBuild Monitor Visual Studio Extension is installed!) 1098 | // Yassine: The -clean is to bypass the FastBuild internal dependencies checks (cached in the fdb) as it could create some conflicts with UBT. 1099 | // Basically we want FB to stupidly compile what UBT tells it to. 1100 | string FBCommandLine = string.Format("-monitor -report -summary {0} {1} -ide -clean -config {2}", distArgument, cacheArgument, BffFilePath); 1101 | 1102 | ProcessStartInfo FBStartInfo = new ProcessStartInfo(string.IsNullOrEmpty(FBuildExePathOverride) ? "fbuild" : FBuildExePathOverride, FBCommandLine); 1103 | 1104 | FBStartInfo.UseShellExecute = false; 1105 | FBStartInfo.WorkingDirectory = Path.Combine(UnrealBuildTool.EngineDirectory.MakeRelativeTo(DirectoryReference.GetCurrentDirectory()), "Source"); 1106 | 1107 | string ReportFilePath = Path.Combine(FBStartInfo.WorkingDirectory, "report.html"); 1108 | if (File.Exists(ReportFilePath)) 1109 | { 1110 | File.Delete(ReportFilePath); 1111 | } 1112 | 1113 | try 1114 | { 1115 | Process FBProcess = new Process(); 1116 | FBProcess.StartInfo = FBStartInfo; 1117 | 1118 | FBStartInfo.RedirectStandardError = true; 1119 | FBStartInfo.RedirectStandardOutput = true; 1120 | FBProcess.EnableRaisingEvents = true; 1121 | 1122 | DataReceivedEventHandler OutputEventHandler = (Sender, Args) => 1123 | { 1124 | if (Args.Data != null) 1125 | Console.WriteLine(Args.Data); 1126 | }; 1127 | 1128 | FBProcess.OutputDataReceived += OutputEventHandler; 1129 | FBProcess.ErrorDataReceived += OutputEventHandler; 1130 | 1131 | FBProcess.Start(); 1132 | 1133 | FBProcess.BeginOutputReadLine(); 1134 | FBProcess.BeginErrorReadLine(); 1135 | 1136 | FBProcess.WaitForExit(); 1137 | return FBProcess.ExitCode == 0; 1138 | } 1139 | catch (Exception e) 1140 | { 1141 | Log.TraceError("Exception launching fbuild process. Is it in your path?" + e.ToString()); 1142 | return false; 1143 | } 1144 | } 1145 | 1146 | private void GenerateDependencyFiles(List Actions, string ReportFilePath) 1147 | { 1148 | if (!File.Exists(ReportFilePath)) 1149 | { 1150 | Log.TraceWarning("FASTBuild report.html not found, skipping dependency files generation"); 1151 | return; 1152 | } 1153 | 1154 | string ReportFileText = File.ReadAllText(ReportFilePath); 1155 | int Cursor = 0; 1156 | while(true) 1157 | { 1158 | int HeaderBegin = ReportFileText.IndexOf("

", Cursor); 1159 | if (HeaderBegin == -1) 1160 | { 1161 | break; 1162 | } 1163 | 1164 | int HeaderEnd = ReportFileText.IndexOf("

", HeaderBegin); 1165 | 1166 | int NextHeader = ReportFileText.IndexOf("

", HeaderEnd); 1167 | if (NextHeader == -1) 1168 | { 1169 | NextHeader = ReportFileText.Length; 1170 | } 1171 | 1172 | string ActionHeader = ReportFileText.Substring(HeaderBegin + 4, HeaderEnd - HeaderBegin - 4); 1173 | int ActionIndex = Int32.Parse(ActionHeader.Substring(7)); // skipping "Action_" 1174 | Action SourceAction = Actions[ActionIndex]; 1175 | 1176 | if (SourceAction.DependencyListFile == null) 1177 | { 1178 | Cursor = NextHeader; 1179 | continue; 1180 | } 1181 | 1182 | Cursor = HeaderEnd; 1183 | 1184 | List Includes = new List(); 1185 | while(true) 1186 | { 1187 | int IncludeEnd = ReportFileText.IndexOf("", Cursor); 1188 | if (IncludeEnd == -1 || IncludeEnd > NextHeader) 1189 | { 1190 | break; 1191 | } 1192 | 1193 | int IncludeBegin = ReportFileText.LastIndexOf("", IncludeEnd); 1194 | Cursor = IncludeEnd + 10; 1195 | 1196 | string Include = ReportFileText.Substring(IncludeBegin + 9, IncludeEnd - IncludeBegin - 9); 1197 | if (!Include.Contains("Microsoft Visual Studio") && !Include.Contains("Windows Kits")) 1198 | { 1199 | Includes.Add(Include); 1200 | } 1201 | } 1202 | 1203 | File.WriteAllLines(SourceAction.DependencyListFile.AbsolutePath, Includes); 1204 | } 1205 | } 1206 | } 1207 | class CommandLineParser 1208 | { 1209 | enum ParserState 1210 | { 1211 | OutsideToken, 1212 | InsideToken, 1213 | InsideTokenQuotes, 1214 | } 1215 | 1216 | public static List Parse(string CommandLine) 1217 | { 1218 | List Tokens = new List(); 1219 | 1220 | ParserState State = ParserState.OutsideToken; 1221 | int Cursor = 0; 1222 | int TokenStartPos = 0; 1223 | while (Cursor < CommandLine.Length) 1224 | { 1225 | char c = CommandLine[Cursor]; 1226 | if (State == ParserState.OutsideToken) 1227 | { 1228 | if (c == ' ' || c == '\r' || c == '\n') 1229 | { 1230 | Cursor++; 1231 | } 1232 | else 1233 | { 1234 | TokenStartPos = Cursor; 1235 | State = ParserState.InsideToken; 1236 | } 1237 | } 1238 | else if (State == ParserState.InsideToken) 1239 | { 1240 | if (c == '\\') 1241 | { 1242 | Cursor+=2; 1243 | } 1244 | else if (c == '"') 1245 | { 1246 | State = ParserState.InsideTokenQuotes; 1247 | Cursor++; 1248 | } 1249 | else if (c == ' ' || c == '\r' || c == '\n') 1250 | { 1251 | Tokens.Add(CommandLine.Substring(TokenStartPos, Cursor - TokenStartPos)); 1252 | State = ParserState.OutsideToken; 1253 | } 1254 | else 1255 | { 1256 | Cursor++; 1257 | } 1258 | } 1259 | else if (State == ParserState.InsideTokenQuotes) 1260 | { 1261 | if (c == '\\') 1262 | { 1263 | Cursor++; 1264 | } 1265 | else if (c == '"') 1266 | { 1267 | State = ParserState.InsideToken; 1268 | } 1269 | 1270 | Cursor++; 1271 | } 1272 | else 1273 | { 1274 | throw new NotImplementedException(); 1275 | } 1276 | } 1277 | 1278 | if (State == ParserState.InsideTokenQuotes) 1279 | { 1280 | throw new Exception("Failed to parse command line, no closing quotes found: " + CommandLine); 1281 | } 1282 | 1283 | if (State == ParserState.InsideToken) 1284 | { 1285 | Tokens.Add(CommandLine.Substring(TokenStartPos)); 1286 | } 1287 | 1288 | return Tokens; 1289 | } 1290 | } 1291 | } 1292 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Ilia Kormin 4 | 5 | Substantial portions: 6 | Copyright(c) 2016-2018 KnownShippable(Yassine Riahi & Liam Flookes) 7 | (see https://github.com/liamkf/Unreal_FASTBuild/blob/master/LICENSE) 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy 10 | of this software and associated documentation files (the "Software"), to deal 11 | in the Software without restriction, including without limitation the rights 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the Software is 14 | furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in all 17 | copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Compile Unreal Engine 4.22 using FastBuild 2 | 3 | ## Implementation notes 4 | #### Currently supported platforms 5 | - Win64 - tested with Windows 10, Visual Studio 2019 Community, Unreal Engine 4.22.3, FastBuild v0.98. 6 | 7 | #### Recently supported platforms 8 | - Durango was fully supported using VS2015 at some point in the past, but VS2015 is not supported since 4.22; 9 | - Orbis will require some changes. 10 | 11 | #### Not supported platforms 12 | - Android; 13 | - iOS; 14 | - Linux; 15 | - and others... 16 | 17 | ## Main idea 18 | The main difference from Yassine Riahi & Liam Flookes version is in added support for dependency files. 19 | It was implemented using FastBuild "-report" argument. 20 | Generated report (see ReportFilePath) contains include files usage. 21 | Since there is no source code for cl-filter.exe, it is not possible to guarantee same result. 22 | Moreover it is fact, that output contains more include files than when using cl-filter.exe. 23 | Maybe more precise filtration could be applied, some ideas: 24 | - exclude pch includes usages from dependent compilation units; 25 | - exclude files from outside engine folder; 26 | - exclude using cl-filter.exe.sn-dbs-tool.ini. 27 | 28 | ## FastBuild version 29 | Don't use FastBuild 0.96 or older versions, since it has processes management error. 30 | 31 | For more information see https://github.com/fastbuild/fastbuild/issues/223. 32 | 33 | ## Installation steps 34 | 1. Save copy of this file as ...\Engine\Source\Programs\UnrealBuildTool\System\FASTBuild.cs; 35 | 2. Add it to UnrealBuildTool project; 36 | 3. Apply fix from https://github.com/TheEmidee/UE4FastBuild: 37 | VS2019 required same behaviour as for VS2017; 38 | 4. Add FastBuild executor to ExecuteActions(...) inside ActionGraph.cs: 39 | ``` 40 | ... 41 | // Figure out which executor to use 42 | ActionExecutor Executor; 43 | if (FASTBuild.IsAvailable()) 44 | { 45 | Executor = new FASTBuild(); 46 | } 47 | else if (BuildConfiguration.bAllowHybridExecutor && HybridExecutor.IsAvailable()) 48 | ... 49 | ``` 50 | 5. Add "public" access modifier to GetVCToolPath64(...) method inside VCEnvironment.cs. 51 | 52 | 53 | ## Original version 54 | https://github.com/liamkf/Unreal_FASTBuild 55 | --------------------------------------------------------------------------------