1298 lines
54 KiB
C#
1298 lines
54 KiB
C#
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text.RegularExpressions;
|
|
using EpicGames.Core;
|
|
using Microsoft.Extensions.Logging;
|
|
using UnrealBuildBase;
|
|
|
|
namespace UnrealBuildTool
|
|
{
|
|
class MacToolChainSettings : AppleToolChainSettings
|
|
{
|
|
public override string GetTargetVersionForTargetType(TargetType Type) => ((ApplePlatformSDK)UEBuildPlatformSDK.GetSDKForPlatform("Mac")!).GetBuildTargetVersion(Type);
|
|
|
|
/// <summary>
|
|
/// Which version of the Mac OS X to allow at run time
|
|
/// </summary>
|
|
public static string MinMacBuildVersion(TargetType TargetType)
|
|
{
|
|
return ((ApplePlatformSDK)UEBuildPlatformSDK.GetSDKForPlatform("Mac")!).GetBuildTargetVersion(TargetType);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Minimum version of Mac OS X to actually run on, running on earlier versions will display the system minimum version error dialog and exit.
|
|
/// </summary>
|
|
public static string MinMacDeploymentVersion(TargetType TargetType)
|
|
{
|
|
return ((ApplePlatformSDK)UEBuildPlatformSDK.GetSDKForPlatform("Mac")!).GetDeploymentTargetVersion(TargetType);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructor
|
|
/// </summary>
|
|
/// <param name="bVerbose">Whether to output verbose logging</param>
|
|
/// <param name="Logger">Logger for output</param>
|
|
public MacToolChainSettings(bool bVerbose, ILogger Logger)
|
|
// passing 0.0 for target version because we have per-target versions when making the Tuple
|
|
: base("MacOSX", null, "macos", "0.0", bVerbose, Logger)
|
|
{
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Mac toolchain wrapper
|
|
/// </summary>
|
|
class MacToolChain : AppleToolChain
|
|
{
|
|
public MacToolChain(ReadOnlyTargetRules? Target, ClangToolChainOptions InOptions, ILogger InLogger)
|
|
: base(Target, () => new MacToolChainSettings(true, InLogger), InOptions, InLogger)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Which compiler\linker frontend to use
|
|
/// </summary>
|
|
private const string MacCompiler = "clang++";
|
|
|
|
/// <summary>
|
|
/// Which archiver to use
|
|
/// </summary>
|
|
private const string MacArchiver = "libtool";
|
|
|
|
protected override ClangToolChainInfo GetToolChainInfo()
|
|
{
|
|
FileReference CompilerPath = FileReference.Combine(Settings.ToolchainDir, MacCompiler);
|
|
FileReference ArchiverPath = FileReference.Combine(Settings.ToolchainDir, MacArchiver);
|
|
return new AppleToolChainInfo(UnrealTargetPlatform.Mac, ApplePlatformSDK.DeveloperDir, CompilerPath, ArchiverPath, Logger);
|
|
}
|
|
|
|
public static DirectoryReference FindProductDirectory(FileReference? ProjectFile, DirectoryReference BinaryDir, string? NameIfProgram)
|
|
{
|
|
// a project file is always used if there is one
|
|
if (ProjectFile != null)
|
|
{
|
|
return ProjectFile.Directory;
|
|
}
|
|
// look to see if this is a program, and hunt down where the Programs directory is
|
|
if (NameIfProgram != null)
|
|
{
|
|
return FindProgramDirectoryFromSource(BinaryDir, NameIfProgram, true) ?? Unreal.EngineDirectory;
|
|
}
|
|
return Unreal.EngineDirectory;
|
|
}
|
|
|
|
public static DirectoryReference? FindProgramDirectoryFromSource(DirectoryReference StartingDir, string ProgramName, bool bCreateIfNotFound)
|
|
{
|
|
DirectoryReference? ProgramFinder = StartingDir;
|
|
while (ProgramFinder != null &&
|
|
!String.Equals(ProgramFinder.GetDirectoryName(), "Source", StringComparison.CurrentCultureIgnoreCase) &&
|
|
!String.Equals(ProgramFinder.GetDirectoryName(), "Intermediate", StringComparison.CurrentCultureIgnoreCase) &&
|
|
!String.Equals(ProgramFinder.GetDirectoryName(), "Binaries", StringComparison.CurrentCultureIgnoreCase))
|
|
{
|
|
ProgramFinder = ProgramFinder.ParentDirectory;
|
|
}
|
|
|
|
// we are now at Source or similar directory, go up one more, then into Programs, and finally the "project" directory
|
|
if (ProgramFinder != null)
|
|
{
|
|
DirectoryReference ProgramsDir = DirectoryReference.Combine(ProgramFinder, "../Programs");
|
|
// if it doesn't exist, something went wrong, and we can't use this. throw an exception to catch it, for now
|
|
if (!DirectoryReference.Exists(ProgramsDir))
|
|
{
|
|
throw new BuildException($"Unable to find Programs directory for {ProgramName}, when starting in {StartingDir}");
|
|
}
|
|
|
|
ProgramFinder = DirectoryReference.Combine(ProgramsDir, ProgramName);
|
|
// if it exists, we have a ProductDir we can use for plists, icons, etc
|
|
if (!DirectoryReference.Exists(ProgramFinder))
|
|
{
|
|
if (bCreateIfNotFound)
|
|
{
|
|
DirectoryReference.CreateDirectory(ProgramFinder);
|
|
return ProgramFinder;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return ProgramFinder;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
protected override void GetCompileArguments_WarningsAndErrors(CppCompileEnvironment CompileEnvironment, List<string> Arguments)
|
|
{
|
|
base.GetCompileArguments_WarningsAndErrors(CompileEnvironment, Arguments);
|
|
|
|
//Arguments.Add("-Wsign-compare"); // fed up of not seeing the signed/unsigned warnings we get on Windows - lets enable them here too.
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
protected override void GetCompileArguments_Debugging(CppCompileEnvironment CompileEnvironment, List<string> Arguments)
|
|
{
|
|
base.GetCompileArguments_Debugging(CompileEnvironment, Arguments);
|
|
|
|
// TODO: Mac always enables exceptions, is this correct?
|
|
if (!CompileEnvironment.bEnableExceptions)
|
|
{
|
|
Arguments.Remove("-fno-exceptions");
|
|
Arguments.Remove("-DPLATFORM_EXCEPTIONS_DISABLED=1");
|
|
}
|
|
Arguments.Add("-fexceptions");
|
|
Arguments.Add("-DPLATFORM_EXCEPTIONS_DISABLED=0");
|
|
|
|
if (CompileEnvironment.bHideSymbolsByDefault)
|
|
{
|
|
Arguments.Add("-fvisibility-ms-compat");
|
|
Arguments.Add("-fvisibility-inlines-hidden");
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
protected override void GetCompileArguments_AdditionalArgs(CppCompileEnvironment CompileEnvironment, List<string> Arguments)
|
|
{
|
|
if (!String.IsNullOrWhiteSpace(CompileEnvironment.AdditionalArguments))
|
|
{
|
|
string EscapedAdditionalArgs = String.Empty;
|
|
foreach (string AdditionalArg in CompileEnvironment.AdditionalArguments.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries))
|
|
{
|
|
Match DefinitionMatch = Regex.Match(AdditionalArg, "-D\"?(?<Name>.*)=(?<Value>.*)\"?");
|
|
if (DefinitionMatch.Success)
|
|
{
|
|
EscapedAdditionalArgs += String.Format(" -D{0}=\"{1}\"", DefinitionMatch.Groups["Name"].Value, DefinitionMatch.Groups["Value"].Value);
|
|
}
|
|
else
|
|
{
|
|
EscapedAdditionalArgs += " " + AdditionalArg;
|
|
}
|
|
}
|
|
|
|
if (!String.IsNullOrWhiteSpace(EscapedAdditionalArgs))
|
|
{
|
|
Arguments.Add(EscapedAdditionalArgs);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
protected override void GetCompileArguments_Global(CppCompileEnvironment CompileEnvironment, List<string> Arguments)
|
|
{
|
|
base.GetCompileArguments_Global(CompileEnvironment, Arguments);
|
|
|
|
CppRootPaths RootPaths = CompileEnvironment.RootPaths;
|
|
|
|
Arguments.Add("-fasm-blocks");
|
|
|
|
// Disable FMA contraction on arm builds in order to preserve consistency with x64
|
|
// derived data builds.
|
|
// This is also added to the x64 builds in case someone ever adds -mfma.
|
|
Arguments.Add("-ffp-contract=off");
|
|
|
|
if (CompileEnvironment.bEnableOSX109Support)
|
|
{
|
|
Arguments.Add("-faligned-new"); // aligned operator new is supported only on macOS 10.14 and above
|
|
}
|
|
|
|
// Pass through architecture and OS info
|
|
Arguments.Add("" + FormatArchitectureArg(CompileEnvironment.Architectures));
|
|
Arguments.Add($"-isysroot \"{NormalizeCommandLinePath(Settings.GetSDKPath(CompileEnvironment.Architecture), RootPaths)}\"");
|
|
|
|
List<string> FrameworksSearchPaths = new List<string>();
|
|
foreach (UEBuildFramework Framework in CompileEnvironment.AdditionalFrameworks)
|
|
{
|
|
FileReference FrameworkPath = new FileReference(Path.GetFullPath(Framework.Name));
|
|
if (!FrameworksSearchPaths.Contains(FrameworkPath.Directory.FullName))
|
|
{
|
|
Arguments.Add($"-F \"{NormalizeCommandLinePath(FrameworkPath.Directory, RootPaths)}\"");
|
|
FrameworksSearchPaths.Add(FrameworkPath.Directory.FullName);
|
|
}
|
|
}
|
|
}
|
|
|
|
void AddFrameworkToLinkCommand(List<string> LinkArguments, string FrameworkName, string Arg = "-framework")
|
|
{
|
|
if (FrameworkName.EndsWith(".framework"))
|
|
{
|
|
LinkArguments.Add("-F \"" + Path.GetDirectoryName(Path.GetFullPath(FrameworkName)) + "\"");
|
|
FrameworkName = Path.GetFileNameWithoutExtension(FrameworkName);
|
|
}
|
|
LinkArguments.Add(Arg + " \"" + FrameworkName + "\"");
|
|
}
|
|
|
|
protected override void GetLinkArguments_Global(LinkEnvironment LinkEnvironment, List<string> Arguments)
|
|
{
|
|
base.GetLinkArguments_Global(LinkEnvironment, Arguments);
|
|
// Pass through architecture and OS info
|
|
Arguments.Add(FormatArchitectureArg(LinkEnvironment.Architectures));
|
|
Arguments.Add(String.Format("-isysroot \"{0}\"", NormalizeCommandLinePath(Settings.GetSDKPath(LinkEnvironment.Architecture), LinkEnvironment.RootPaths)));
|
|
Arguments.Add($"-target {Settings.GetTargetTuple(LinkEnvironment.Architecture, Target!.Platform, Target!.Type)}");
|
|
Arguments.Add("-dead_strip");
|
|
|
|
// Temporary workaround for linker warning with Xcode 14:
|
|
// 'ld: warning: could not create compact unwind for _inflate_fast: registers 27 not saved contiguously in frame'
|
|
if (CompilerVersionLessThan(14, 0, 0))
|
|
{
|
|
Arguments.Add("-Wl,-fatal_warnings");
|
|
}
|
|
|
|
if (Options.HasFlag(ClangToolChainOptions.EnableAddressSanitizer) ||
|
|
Options.HasFlag(ClangToolChainOptions.EnableThreadSanitizer) ||
|
|
Options.HasFlag(ClangToolChainOptions.EnableUndefinedBehaviorSanitizer) ||
|
|
Options.HasFlag(ClangToolChainOptions.EnableLibFuzzer))
|
|
{
|
|
Arguments.Add("-g");
|
|
if (Options.HasFlag(ClangToolChainOptions.EnableAddressSanitizer))
|
|
{
|
|
Arguments.Add("-fsanitize=address");
|
|
}
|
|
else if (Options.HasFlag(ClangToolChainOptions.EnableThreadSanitizer))
|
|
{
|
|
Arguments.Add("-fsanitize=thread");
|
|
}
|
|
else if (Options.HasFlag(ClangToolChainOptions.EnableUndefinedBehaviorSanitizer))
|
|
{
|
|
Arguments.Add("-fsanitize=undefined");
|
|
}
|
|
if (Options.HasFlag(ClangToolChainOptions.EnableLibFuzzer))
|
|
{
|
|
Arguments.Add("-fsanitize=fuzzer");
|
|
}
|
|
}
|
|
|
|
if (LinkEnvironment.bIsBuildingDLL)
|
|
{
|
|
Arguments.Add("-dynamiclib");
|
|
}
|
|
|
|
if (LinkEnvironment.Configuration == CppConfiguration.Debug)
|
|
{
|
|
// Apple's Clang is not supposed to run the de-duplication pass when linking in debug configs. Xcode adds this flag automatically, we need it as well, otherwise linking would take very long
|
|
Arguments.Add("-Wl,-no_deduplicate");
|
|
}
|
|
|
|
// Needed to make sure install_name_tool will be able to update paths in Mach-O headers
|
|
Arguments.Add("-headerpad_max_install_names");
|
|
}
|
|
|
|
void GetArchiveArguments_Global(LinkEnvironment LinkEnvironment, List<string> Arguments)
|
|
{
|
|
Arguments.Add("-static");
|
|
}
|
|
|
|
private void AppendMacLine(StreamWriter Writer, string Format, params object[] Arg)
|
|
{
|
|
string PreLine = String.Format(Format, Arg);
|
|
Writer.Write(PreLine + "\n");
|
|
}
|
|
|
|
private int LoadEngineCL()
|
|
{
|
|
BuildVersion? Version;
|
|
if (BuildVersion.TryRead(BuildVersion.GetDefaultFileName(), out Version))
|
|
{
|
|
return Version.Changelist;
|
|
}
|
|
else
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
public static string LoadEngineDisplayVersion(bool bIgnorePatchVersion = false)
|
|
{
|
|
BuildVersion? Version;
|
|
if (BuildVersion.TryRead(BuildVersion.GetDefaultFileName(), out Version))
|
|
{
|
|
return $"{Version.MajorVersion}.{Version.MinorVersion}.{(bIgnorePatchVersion ? 0 : Version.PatchVersion)}";
|
|
}
|
|
else
|
|
{
|
|
return "4.0.0";
|
|
}
|
|
}
|
|
|
|
private int LoadBuiltFromChangelistValue()
|
|
{
|
|
return LoadEngineCL();
|
|
}
|
|
|
|
private string LoadEngineAPIVersion()
|
|
{
|
|
if (BuildVersion.TryRead(BuildVersion.GetDefaultFileName(), out BuildVersion? Version))
|
|
{
|
|
if ((Target?.bPrecompile == true || Version.IsPromotedBuild) && Version.EffectiveCompatibleChangelist > 0)
|
|
{
|
|
int CL = Version.EffectiveCompatibleChangelist;
|
|
return $"{CL / (100 * 100)}.{(CL / 100) % 100}.{CL % 100}";
|
|
}
|
|
}
|
|
return LoadEngineDisplayVersion(true);
|
|
}
|
|
|
|
private string GetGameNameFromExecutablePath(string ExePath)
|
|
{
|
|
string ExeName = Path.GetFileName(ExePath);
|
|
string[] ExeNameParts = ExeName.Split('-');
|
|
string GameName = ExeNameParts[0];
|
|
|
|
if (GameName == "EpicGamesBootstrapLauncher")
|
|
{
|
|
GameName = "EpicGamesLauncher";
|
|
}
|
|
else if (GameName == "UE5" && ProjectFile != null)
|
|
{
|
|
GameName = ProjectFile.GetFileNameWithoutAnyExtensions();
|
|
}
|
|
|
|
return GameName;
|
|
}
|
|
|
|
private void AddLibraryPathToRPaths(string Library, string ExeAbsolutePath, ref List<string> RPaths, List<string> LinkArguments, bool bIsBuildingAppBundle, ILogger Logger)
|
|
{
|
|
string LibraryFullPath = Path.GetFullPath(Library);
|
|
string LibraryDir = Path.GetDirectoryName(LibraryFullPath)!;
|
|
string ExeDir = Path.GetDirectoryName(ExeAbsolutePath)!;
|
|
|
|
// Only dylibs and frameworks, and only those that are outside of Engine/Binaries/Mac and Engine/Source/ThirdParty, and outside of the folder where the executable is need an additional RPATH entry
|
|
if ((Library.EndsWith("dylib") || Library.EndsWith(".framework"))
|
|
&& !LibraryFullPath.Contains("/Engine/Source/ThirdParty/") && LibraryDir != ExeDir && !RPaths.Contains(LibraryDir))
|
|
{
|
|
// macOS gatekeeper erroneously complains about not seeing the CEF3 framework in the codesigned Launcher because it's only present in one of the folders specified in RPATHs.
|
|
// To work around this we will only add a single RPATH entry for it, for the framework stored in .app/Contents/UE/ subfolder of the packaged app bundle
|
|
bool bCanUseMultipleRPATHs = !ExeAbsolutePath.Contains("EpicGamesLauncher-Mac-Shipping") || !Library.Contains("CEF3");
|
|
|
|
// First, add a path relative to the executable.
|
|
string FinalExeDir = ExeDir;
|
|
if (bIsBuildingAppBundle)
|
|
{
|
|
FinalExeDir = ExeAbsolutePath + ".app/Contents/MacOS";
|
|
}
|
|
string RelativePath = Utils.MakePathRelativeTo(LibraryDir, FinalExeDir).Replace("\\", "/");
|
|
|
|
if (bCanUseMultipleRPATHs)
|
|
{
|
|
LinkArguments.Add("-rpath \"@loader_path/" + RelativePath + "\"");
|
|
|
|
// We are referencing plugins that need to be relative to the engine not the dylib we are building
|
|
// To be safe leave the previous relative loader_path in place and add a relative engine executable path
|
|
if (!bIsBuildingAppBundle && LibraryDir.Contains("/Engine/Plugins/") && ExeAbsolutePath.EndsWith("dylib"))
|
|
{
|
|
int EngineDirStrIndex = LibraryDir.IndexOf("Engine");
|
|
if (EngineDirStrIndex >= 0)
|
|
{
|
|
LinkArguments.Add("-rpath \"@executable_path/../../../../../../" + LibraryDir.Substring(EngineDirStrIndex) + "\"");
|
|
}
|
|
}
|
|
}
|
|
|
|
// If building an app bundle, we also need an RPATH for use in packaged game and a separate one for staged builds
|
|
if (bIsBuildingAppBundle)
|
|
{
|
|
string EngineDir = Unreal.RootDirectory.ToString();
|
|
string? ProjectDir = ProjectFile?.Directory.FullName;
|
|
|
|
// In packaged games dylibs are stored in Contents/UE subfolders, for example in GameName.app/Contents/UE/Engine/Binaries/ThirdParty/PhysX/Mac
|
|
string BundleUEDir = Path.GetFullPath(ExeDir + "/../../Contents/UE");
|
|
string? BundleLibraryDir = null;
|
|
if (LibraryDir.StartsWith(EngineDir + "/"))
|
|
{
|
|
BundleLibraryDir = LibraryDir.Replace(EngineDir, BundleUEDir);
|
|
}
|
|
else if (ProjectDir != null && LibraryDir.StartsWith(ProjectDir + "/"))
|
|
{
|
|
string GameName = GetGameNameFromExecutablePath(ExeAbsolutePath);
|
|
BundleLibraryDir = LibraryDir.Replace(ProjectDir, BundleUEDir + "/" + GameName);
|
|
}
|
|
|
|
if (BundleLibraryDir != null)
|
|
{
|
|
string BundleRelativeDir = Utils.MakePathRelativeTo(BundleLibraryDir, ExeDir).Replace("\\", "/");
|
|
LinkArguments.Add("-rpath \"@loader_path/" + BundleRelativeDir + "\"");
|
|
}
|
|
else
|
|
{
|
|
Logger.LogWarning("Unexpected third party dylib location when generating RPATH entries: {LibraryFullPath}. Skipping.", LibraryFullPath);
|
|
}
|
|
|
|
// For staged code-based games we need additional entry if the game is not stored directly in the engine's root directory
|
|
if (bCanUseMultipleRPATHs)
|
|
{
|
|
string StagedUEDir = Path.GetFullPath(ExeDir + "/../../../../../..");
|
|
string StagedLibraryDir = LibraryDir.Replace(EngineDir, StagedUEDir);
|
|
string StagedRelativeDir = Utils.MakePathRelativeTo(StagedLibraryDir, ExeDir).Replace("\\", "/");
|
|
if (StagedRelativeDir != RelativePath)
|
|
{
|
|
LinkArguments.Add("-rpath \"@loader_path/" + StagedRelativeDir + "\"");
|
|
}
|
|
}
|
|
}
|
|
|
|
RPaths.Add(LibraryDir);
|
|
}
|
|
}
|
|
|
|
public override FileItem[] LinkImportLibrary(LinkEnvironment LinkEnvironment, IActionGraphBuilder Graph)
|
|
{
|
|
// we actually create Actions for every dylib, but we never return a FileItem. Instead depend on a Link action
|
|
// linking to the stub dylib to make thie action take place. If we returned FileItems here, UBT would link a
|
|
// stub lib for every dylub even if not needed by anything
|
|
if (LinkEnvironment.bIsBuildingDLL)
|
|
{
|
|
LinkAllFiles(LinkEnvironment, true, Graph);
|
|
}
|
|
|
|
return Array.Empty<FileItem>();
|
|
}
|
|
|
|
public override FileItem? LinkFiles(LinkEnvironment LinkEnvironment, bool bBuildImportLibraryOnly, IActionGraphBuilder Graph)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
public override FileItem[] LinkAllFiles(LinkEnvironment LinkEnvironment, bool bBuildImportLibraryOnly, IActionGraphBuilder Graph)
|
|
{
|
|
List<FileItem> OutputFiles = new();
|
|
// To solve the problem with cross dependencies, for now we create a broken dylib that does not link with other engine dylibs.
|
|
// This is fixed in later step, FixDylibDependencies. For this and to know what libraries to copy whilst creating an app bundle,
|
|
// we gather the list of engine dylibs.
|
|
List<string> EngineAndGameLibraries = new List<string>();
|
|
|
|
bool bIsBuildingLibrary = LinkEnvironment.bIsBuildingLibrary || bBuildImportLibraryOnly;
|
|
bool bIsBuildingAppBundle = !LinkEnvironment.bIsBuildingDLL && !LinkEnvironment.bIsBuildingLibrary && !LinkEnvironment.bIsBuildingConsoleApplication;
|
|
FileReference LinkerPath = bIsBuildingLibrary ? Info.Archiver : Info.Clang;
|
|
|
|
if (LinkEnvironment.Architectures.bIsMultiArch)
|
|
{
|
|
List<FileItem> PerArchOutputFiles = new();
|
|
foreach (UnrealArch Arch in LinkEnvironment.Architectures.Architectures)
|
|
{
|
|
LinkEnvironment ArchEnvironment = new LinkEnvironment(LinkEnvironment, Arch);
|
|
if (!bBuildImportLibraryOnly)
|
|
{
|
|
ArchEnvironment.OutputFilePaths = LinkEnvironment.OutputFilePaths.Select(x => new FileReference($"{LinkEnvironment.IntermediateDirectory}/{Path.GetFileName(x.FullName)}_{Arch}")).ToList();
|
|
}
|
|
|
|
PerArchOutputFiles.Add(LinkArchitectureFiles(ArchEnvironment, LinkEnvironment.OutputFilePath, bBuildImportLibraryOnly, Graph));
|
|
}
|
|
|
|
if (bBuildImportLibraryOnly)
|
|
{
|
|
return PerArchOutputFiles.ToArray();
|
|
}
|
|
|
|
FileItem OutputFileItem;
|
|
if (bBuildImportLibraryOnly)
|
|
{
|
|
OutputFileItem = MakeStubItem(LinkEnvironment, LinkEnvironment.OutputFilePath.FullName);
|
|
}
|
|
else
|
|
{
|
|
OutputFileItem = FileItem.GetItemByFileReference(LinkEnvironment.OutputFilePath);
|
|
}
|
|
OutputFiles.Add(OutputFileItem);
|
|
|
|
Action LipoAction = Graph.CreateAction(ActionType.Link);
|
|
LipoAction.PrerequisiteItems.UnionWith(PerArchOutputFiles);
|
|
LipoAction.ProducedItems.Add(OutputFileItem);
|
|
LipoAction.WorkingDirectory = GetMacDevSrcRoot();
|
|
LipoAction.CommandPath = FileReference.Combine(Info.Clang.Directory, "lipo");
|
|
LipoAction.CommandDescription = (bBuildImportLibraryOnly ? "LipoStub" : "Lipo");
|
|
LipoAction.CommandVersion = Info.ClangVersionString;
|
|
LipoAction.CommandArguments = String.Join(" ", PerArchOutputFiles.Select(x => $"\"{x}\"")) + $" -create -output \"{OutputFileItem.AbsolutePath}\"";
|
|
LipoAction.StatusDescription = Path.GetFileName(OutputFileItem.AbsolutePath);
|
|
LipoAction.bCanExecuteRemotely = false;
|
|
}
|
|
else
|
|
{
|
|
OutputFiles.Add(LinkArchitectureFiles(LinkEnvironment, LinkEnvironment.OutputFilePath, bBuildImportLibraryOnly, Graph));
|
|
}
|
|
|
|
if (!DirectoryReference.Exists(LinkEnvironment.IntermediateDirectory!))
|
|
{
|
|
DirectoryReference.CreateDirectory(LinkEnvironment.IntermediateDirectory!);
|
|
}
|
|
|
|
// For non-console application, prepare a script that will create the app bundle. It'll be run by FinalizeAppBundle action
|
|
if (bIsBuildingAppBundle && !bUseModernXcode)
|
|
{
|
|
FileReference FinalizeAppBundleScriptPath = FileReference.Combine(LinkEnvironment.IntermediateDirectory!, "FinalizeAppBundle.sh");
|
|
StreamWriter FinalizeAppBundleScript = File.CreateText(FinalizeAppBundleScriptPath.FullName);
|
|
AppendMacLine(FinalizeAppBundleScript, "#!/bin/sh");
|
|
string BinariesPath = Path.GetDirectoryName(OutputFiles[0].AbsolutePath)!;
|
|
BinariesPath = Path.GetDirectoryName(BinariesPath.Substring(0, BinariesPath.IndexOf(".app")))!;
|
|
AppendMacLine(FinalizeAppBundleScript, "cd \"{0}\"", BinariesPath.Replace("$", "\\$"));
|
|
|
|
string BundleVersion = LinkEnvironment.BundleVersion!;
|
|
BundleVersion ??= LoadEngineDisplayVersion();
|
|
|
|
string ExeName = Path.GetFileName(OutputFiles[0].AbsolutePath);
|
|
bool bIsLauncherProduct = ExeName.StartsWith("EpicGamesLauncher") || ExeName.StartsWith("EpicGamesBootstrapLauncher");
|
|
string[] ExeNameParts = ExeName.Split('-');
|
|
string GameName = GetGameNameFromExecutablePath(OutputFiles[0].AbsolutePath);
|
|
|
|
// bundle identifier
|
|
// plist replacements
|
|
DirectoryReference? DirRef = (!String.IsNullOrEmpty(UnrealBuildTool.GetRemoteIniPath()) ? new DirectoryReference(UnrealBuildTool.GetRemoteIniPath()!) : (ProjectFile?.Directory));
|
|
ConfigHierarchy IOSIni = ConfigCache.ReadHierarchy(ConfigHierarchyType.Engine, DirRef, UnrealTargetPlatform.IOS);
|
|
|
|
string BundleIdentifier;
|
|
IOSIni.GetString("/Script/IOSRuntimeSettings.IOSRuntimeSettings", "BundleIdentifier", out BundleIdentifier!);
|
|
|
|
string ProjectName = GameName;
|
|
FileReference? UProjectFilePath = ProjectFile;
|
|
|
|
if (UProjectFilePath != null)
|
|
{
|
|
ProjectName = UProjectFilePath.GetFileNameWithoutAnyExtensions();
|
|
}
|
|
|
|
AppendMacLine(FinalizeAppBundleScript, "mkdir -p \"{0}.app/Contents/MacOS\"", ExeName);
|
|
AppendMacLine(FinalizeAppBundleScript, "mkdir -p \"{0}.app/Contents/Resources\"", ExeName);
|
|
|
|
string IconName = "UnrealEngine";
|
|
string EngineSourcePath = Directory.GetCurrentDirectory().Replace("$", "\\$");
|
|
string CustomResourcesPath = "";
|
|
string CustomBuildPath = "";
|
|
if (UProjectFilePath == null)
|
|
{
|
|
string[] TargetFiles = Directory.GetFiles(Directory.GetCurrentDirectory(), GameName + ".Target.cs", SearchOption.AllDirectories);
|
|
if (TargetFiles.Length == 1)
|
|
{
|
|
CustomResourcesPath = Path.GetDirectoryName(TargetFiles[0]) + "/Resources/Mac";
|
|
CustomBuildPath = Path.GetDirectoryName(TargetFiles[0]) + "../Build/Mac";
|
|
}
|
|
else
|
|
{
|
|
Logger.LogWarning("Found {NumFiles} Target.cs files for {GameName} in alldir search of directory {Dir}", TargetFiles.Length, GameName, Directory.GetCurrentDirectory());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
string ResourceParentFolderName = bIsLauncherProduct ? "Application" : GameName;
|
|
CustomResourcesPath = Path.GetDirectoryName(UProjectFilePath.FullName) + "/Source/" + ResourceParentFolderName + "/Resources/Mac";
|
|
if (!Directory.Exists(CustomResourcesPath))
|
|
{
|
|
CustomResourcesPath = Path.GetDirectoryName(UProjectFilePath.FullName) + "/Source/" + ProjectName + "/Resources/Mac";
|
|
}
|
|
CustomBuildPath = Path.GetDirectoryName(UProjectFilePath.FullName) + "/Build/Mac";
|
|
}
|
|
|
|
bool bBuildingEditor = GameName.EndsWith("Editor");
|
|
|
|
// Copy resources
|
|
string DefaultIcon = EngineSourcePath + "/Runtime/Launch/Resources/Mac/" + IconName + ".icns";
|
|
string CustomIcon = "";
|
|
if (bBuildingEditor)
|
|
{
|
|
CustomIcon = DefaultIcon;
|
|
}
|
|
else
|
|
{
|
|
CustomIcon = CustomBuildPath + "/Application.icns";
|
|
if (!File.Exists(CustomIcon))
|
|
{
|
|
CustomIcon = CustomResourcesPath + "/" + GameName + ".icns";
|
|
if (!File.Exists(CustomIcon))
|
|
{
|
|
CustomIcon = DefaultIcon;
|
|
}
|
|
}
|
|
}
|
|
AppendMacLine(FinalizeAppBundleScript, FormatCopyCommand(CustomIcon, String.Format("{0}.app/Contents/Resources/{1}.icns", ExeName, GameName)));
|
|
|
|
if (ExeName.StartsWith("UnrealEditor"))
|
|
{
|
|
AppendMacLine(FinalizeAppBundleScript, FormatCopyCommand(String.Format("{0}/Runtime/Launch/Resources/Mac/UProject.icns", EngineSourcePath), String.Format("{0}.app/Contents/Resources/UProject.icns", ExeName)));
|
|
}
|
|
|
|
string InfoPlistFile = CustomResourcesPath + (bBuildingEditor ? "/Info-Editor.plist" : "/Info.plist");
|
|
if (!File.Exists(InfoPlistFile))
|
|
{
|
|
InfoPlistFile = EngineSourcePath + "/Runtime/Launch/Resources/Mac/" + (bBuildingEditor ? "Info-Editor.plist" : "Info.plist");
|
|
}
|
|
|
|
string TempInfoPlist = "$TMPDIR/TempInfo.plist";
|
|
AppendMacLine(FinalizeAppBundleScript, FormatCopyCommand(InfoPlistFile, TempInfoPlist));
|
|
|
|
// Fix contents of Info.plist
|
|
AppendMacLine(FinalizeAppBundleScript, "/usr/bin/sed -i \"\" -e \"s/\\${0}/{1}/g\" \"{2}\"", "{EXECUTABLE_NAME}", ExeName, TempInfoPlist);
|
|
AppendMacLine(FinalizeAppBundleScript, "/usr/bin/sed -i \"\" -e \"s/\\${0}/{1}/g\" \"{2}\"", "{APP_NAME}", bBuildingEditor ? ("com.epicgames." + GameName) : (BundleIdentifier.Replace("[PROJECT_NAME]", GameName).Replace("_", "")), TempInfoPlist);
|
|
AppendMacLine(FinalizeAppBundleScript, "/usr/bin/sed -i \"\" -e \"s/\\${0}/{1}/g\" \"{2}\"", "{MACOSX_DEPLOYMENT_TARGET}", MacToolChainSettings.MinMacDeploymentVersion(Target!.Type), TempInfoPlist);
|
|
AppendMacLine(FinalizeAppBundleScript, "/usr/bin/sed -i \"\" -e \"s/\\${0}/{1}/g\" \"{2}\"", "{ICON_NAME}", GameName, TempInfoPlist);
|
|
AppendMacLine(FinalizeAppBundleScript, "/usr/bin/sed -i \"\" -e \"s/\\${0}/{1}/g\" \"{2}\"", "{BUNDLE_VERSION}", BundleVersion, TempInfoPlist);
|
|
|
|
// Copy it into place
|
|
AppendMacLine(FinalizeAppBundleScript, FormatCopyCommand(TempInfoPlist, String.Format("{0}.app/Contents/Info.plist", ExeName)));
|
|
AppendMacLine(FinalizeAppBundleScript, "chmod 644 \"{0}.app/Contents/Info.plist\"", ExeName);
|
|
|
|
// Also copy it to where Xcode will look for it, now that Xcode 14 requires we have one set up - it can't point into the .app because that
|
|
// is where it will copy to, so it will error with reading and writing to the same location
|
|
string IntermediateDirectory = (ProjectFile == null ? Unreal.EngineDirectory : ProjectFile.Directory) + "/Intermediate/Mac";
|
|
string XcodeInputPListFile = IntermediateDirectory + "/" + ExeName + "-Info.plist";
|
|
AppendMacLine(FinalizeAppBundleScript, "mkdir -p \"{0}\"", IntermediateDirectory);
|
|
AppendMacLine(FinalizeAppBundleScript, FormatCopyCommand(TempInfoPlist, XcodeInputPListFile));
|
|
AppendMacLine(FinalizeAppBundleScript, "chmod 644 \"{0}\"", XcodeInputPListFile);
|
|
|
|
// Generate PkgInfo file
|
|
string TempPkgInfo = "$TMPDIR/TempPkgInfo";
|
|
AppendMacLine(FinalizeAppBundleScript, "echo 'echo -n \"APPL????\"' | bash > \"{0}\"", TempPkgInfo);
|
|
AppendMacLine(FinalizeAppBundleScript, FormatCopyCommand(TempPkgInfo, String.Format("{0}.app/Contents/PkgInfo", ExeName)));
|
|
|
|
// Make sure OS X knows the bundle was updated
|
|
AppendMacLine(FinalizeAppBundleScript, "touch -c \"{0}.app\"", ExeName);
|
|
|
|
// codesign with ad-hoc signature for when building outside of Xcode the there will be at least some signature
|
|
// (and it can be a BuildProduct down in ModifyBuildProducts)
|
|
AppendMacLine(FinalizeAppBundleScript, "codesign -f -s - \"{0}.app\"", ExeName);
|
|
AppendMacLine(FinalizeAppBundleScript, "codesign -f -s \"Developer ID Application\" \"{0}.app\" 2> /dev/null", ExeName);
|
|
AppendMacLine(FinalizeAppBundleScript, "echo done > /dev/null");
|
|
|
|
FinalizeAppBundleScript.Close();
|
|
}
|
|
|
|
return OutputFiles.ToArray();
|
|
}
|
|
|
|
private FileItem MakeStubItem(LinkEnvironment LinkEnvironment, string DylibPath)
|
|
{
|
|
FileReference DylibPathRef = new FileReference(DylibPath);
|
|
bool bIsEngineDylib = DylibPathRef.IsUnderDirectory(Unreal.EngineDirectory);
|
|
|
|
string IntermediateDirectory = (ProjectFile == null || LinkEnvironment.OutputFilePaths.All(x => x.IsUnderDirectory(Unreal.EngineDirectory)) || bIsEngineDylib ? Unreal.EngineDirectory : ProjectFile.Directory) + "/Intermediate/Mac";
|
|
return FileItem.GetItemByPath(Path.Combine(IntermediateDirectory, "Stubs", LinkEnvironment.Architecture.ToString(), LinkEnvironment.Configuration.ToString(), Path.GetFileName(DylibPath)));
|
|
}
|
|
|
|
private FileItem LinkArchitectureFiles(LinkEnvironment LinkEnvironment, FileReference MultiArchOutputFile, bool bBuildImportLibraryOnly, IActionGraphBuilder Graph)
|
|
{
|
|
List<FileItem> InputFiles = LinkEnvironment.InputFiles;
|
|
|
|
// with precompiled builds, we end up trying to link the arm64 and x64 versions of the engine .o files. Remove the wrong one
|
|
// until we can separate out the .o files by architecture
|
|
string ArchToRemove = LinkEnvironment.Architecture == UnrealArch.Arm64 ? "x64" : "arm64";
|
|
InputFiles.RemoveAll(x => x.Location.ContainsName(ArchToRemove, 0));
|
|
|
|
// Create an action that invokes the linker.
|
|
Action LinkAction = Graph.CreateAction(ActionType.Link);
|
|
LinkAction.RootPaths = LinkEnvironment.RootPaths;
|
|
|
|
FileReference LinkerPath = LinkEnvironment.bIsBuildingLibrary ? Info.Archiver : Info.Clang;
|
|
|
|
LinkAction.WorkingDirectory = GetMacDevSrcRoot();
|
|
LinkAction.CommandPath = LinkerPath;
|
|
string Arch = UnrealArchitectureConfig.ForPlatform(UnrealTargetPlatform.Mac).ConvertToReadableArchitecture(LinkEnvironment.Architecture);
|
|
LinkAction.CommandDescription = (bBuildImportLibraryOnly ? "LinkStub" : "Link") + $" [{Arch}]";
|
|
LinkAction.CommandVersion = Info.ClangVersionString;
|
|
LinkAction.bProducesImportLibrary = bBuildImportLibraryOnly || LinkEnvironment.bIsBuildingDLL;
|
|
LinkAction.bCanExecuteInUBA = true;
|
|
LinkAction.CacheBucket = GetCacheBucket(Target, null);
|
|
LinkAction.ArtifactMode = ArtifactMode.Enabled;
|
|
|
|
List<string> LinkArguments = new();
|
|
if (LinkEnvironment.bIsBuildingLibrary)
|
|
{
|
|
GetArchiveArguments_Global(LinkEnvironment, LinkArguments);
|
|
}
|
|
else
|
|
{
|
|
GetLinkArguments_Global(LinkEnvironment, LinkArguments);
|
|
}
|
|
|
|
if (LinkEnvironment.bIsBuildingDLL)
|
|
{
|
|
LinkArguments.Add("-current_version " + LoadEngineAPIVersion());
|
|
LinkArguments.Add("-compatibility_version " + LoadEngineDisplayVersion(true));
|
|
}
|
|
|
|
// Tell the action that we're building an import library here and it should conditionally be
|
|
// ignored as a prerequisite for other actions
|
|
LinkAction.bProducesImportLibrary = bBuildImportLibraryOnly || LinkEnvironment.bIsBuildingDLL;
|
|
|
|
// Add the output file as a production of the link action.
|
|
FileItem OutputFile;
|
|
if (bBuildImportLibraryOnly)
|
|
{
|
|
OutputFile = MakeStubItem(LinkEnvironment, LinkEnvironment.OutputFilePath.FullName);
|
|
}
|
|
else
|
|
{
|
|
OutputFile = FileItem.GetItemByFileReference(LinkEnvironment.OutputFilePath);
|
|
}
|
|
|
|
string DylibsPath = "@rpath";
|
|
|
|
string AbsolutePath = MultiArchOutputFile.FullName.Replace("\\", "/");
|
|
if (!LinkEnvironment.bIsBuildingLibrary)
|
|
{
|
|
LinkArguments.Add("-rpath @loader_path/ -rpath @executable_path/");
|
|
}
|
|
|
|
bool bIsBuildingAppBundle = !LinkEnvironment.bIsBuildingDLL && !LinkEnvironment.bIsBuildingLibrary && !LinkEnvironment.bIsBuildingConsoleApplication;
|
|
if (bIsBuildingAppBundle)
|
|
{
|
|
LinkArguments.Add("-rpath @executable_path/../../../");
|
|
}
|
|
|
|
List<string> RPaths = new List<string>();
|
|
|
|
// static libs and stub dylibs don't need to look other libs
|
|
if (bBuildImportLibraryOnly)
|
|
{
|
|
LinkArguments.Add("-undefined dynamic_lookup");
|
|
LinkArguments.Add("-Wl,-no_fixup_chains");
|
|
}
|
|
else if (!LinkEnvironment.bIsBuildingLibrary)
|
|
{
|
|
IEnumerable<string> AdditionalLibraries = Enumerable.Concat(LinkEnvironment.SystemLibraries, LinkEnvironment.Libraries.Select(x => x.FullName));
|
|
|
|
// @todo set this to false if we have any rpath issues, or maybe set it to false when making Monolithic
|
|
// we will remove the old path entirely after a lot of wide testing
|
|
bool bCanUseSimpleRPaths = LinkEnvironment.LinkType == TargetLinkType.Monolithic;
|
|
if (bCanUseSimpleRPaths)
|
|
{
|
|
string StagedBundlePrefix = bIsBuildingAppBundle ? "../UE/" : "";
|
|
string UnstagedBundlePrefix = bIsBuildingAppBundle ? "../../../" : "";
|
|
|
|
HashSet<DirectoryReference> UsedDestDirs = new();
|
|
HashSet<FileReference> UsedSourceLibs = new();
|
|
var ProcessLib = (FileReference SourceLib, FileReference DestLib, FileReference ReferencingFile) =>
|
|
{
|
|
if (string.Equals(SourceLib.GetExtension(), ".dylib", StringComparison.InvariantCultureIgnoreCase))
|
|
{
|
|
DirectoryReference DestDir = DestLib.Directory;
|
|
if (UsedSourceLibs.Contains(SourceLib) || UsedDestDirs.Contains(DestDir))
|
|
{
|
|
return;
|
|
}
|
|
|
|
UsedDestDirs.Add(DestDir);
|
|
UsedSourceLibs.Add(SourceLib);
|
|
|
|
string RPathEntry;
|
|
// handle staged rpaths
|
|
if (ProjectFile != null && DestDir.IsUnderDirectory(ProjectFile.Directory))
|
|
{
|
|
RPathEntry = StagedBundlePrefix + ProjectFile.GetFileNameWithoutAnyExtensions() + "/" + DestDir.MakeRelativeTo(ProjectFile.Directory);
|
|
}
|
|
else
|
|
{
|
|
RPathEntry = StagedBundlePrefix + DestDir.MakeRelativeTo(Unreal.EngineDirectory.ParentDirectory!);
|
|
}
|
|
LinkArguments.Add($"-rpath \"@executable_path/{RPathEntry}\"");
|
|
|
|
// handle unstaged paths
|
|
RPathEntry = UnstagedBundlePrefix + DestDir.MakeRelativeTo(ReferencingFile.Directory);
|
|
LinkArguments.Add($"-rpath \"@loader_path/{RPathEntry}\"");
|
|
}
|
|
};
|
|
|
|
// first look in the RuntimeDependency list to look for remapped locations, anything not remapped we will deal with
|
|
// after this loop
|
|
foreach (ModuleRules.RuntimeDependency SrcDep in LinkEnvironment.RuntimeDependencies)
|
|
{
|
|
FileReference DestPath = new(SrcDep.Path);
|
|
FileReference SourcePath = SrcDep.SourcePath == null ? DestPath : new FileReference(SrcDep.SourcePath);
|
|
ProcessLib(SourcePath, DestPath, MultiArchOutputFile);
|
|
}
|
|
foreach (string AdditionalLibrary in AdditionalLibraries)
|
|
{
|
|
FileReference LibPath = new(AdditionalLibrary);
|
|
ProcessLib(LibPath, LibPath, MultiArchOutputFile);
|
|
}
|
|
}
|
|
|
|
// Add the additional libraries to the argument list.
|
|
foreach (string AdditionalLibrary in AdditionalLibraries)
|
|
{
|
|
if (!String.IsNullOrEmpty(Path.GetDirectoryName(AdditionalLibrary)) &&
|
|
(Path.GetDirectoryName(AdditionalLibrary)!.Contains("Binaries/Mac") ||
|
|
Path.GetDirectoryName(AdditionalLibrary)!.Contains("Binaries\\Mac")))
|
|
{
|
|
// It's an engine or game dylib. Save it for later
|
|
FileItem LinkedLib;
|
|
if (LinkEnvironment.bIsCrossReferenced)
|
|
{
|
|
LinkedLib = MakeStubItem(LinkEnvironment, AdditionalLibrary);
|
|
}
|
|
else
|
|
{
|
|
LinkedLib = FileItem.GetItemByPath(AdditionalLibrary);
|
|
}
|
|
|
|
LinkAction.PrerequisiteItems.Add(LinkedLib);
|
|
LinkArguments.Add($"\"{NormalizeCommandLinePath(LinkedLib, LinkEnvironment.RootPaths)}\"");
|
|
}
|
|
else if (AdditionalLibrary.Contains(".framework/"))
|
|
{
|
|
LinkArguments.Add(String.Format("\"{0}\"", AdditionalLibrary));
|
|
}
|
|
else if (String.IsNullOrEmpty(Path.GetDirectoryName(AdditionalLibrary)) && String.IsNullOrEmpty(Path.GetExtension(AdditionalLibrary)))
|
|
{
|
|
LinkArguments.Add(String.Format("-l\"{0}\"", AdditionalLibrary));
|
|
}
|
|
else
|
|
{
|
|
LinkArguments.Add($"\"{NormalizeCommandLinePath(FileItem.GetItemByPath(AdditionalLibrary), LinkEnvironment.RootPaths)}\"");
|
|
}
|
|
|
|
if (!bCanUseSimpleRPaths)
|
|
{
|
|
AddLibraryPathToRPaths(AdditionalLibrary, AbsolutePath, ref RPaths, LinkArguments, bIsBuildingAppBundle, Logger);
|
|
}
|
|
}
|
|
|
|
foreach (string AdditionalLibrary in LinkEnvironment.DelayLoadDLLs)
|
|
{
|
|
LinkArguments.Add(String.Format("-weak_library \"{0}\"", Path.GetFullPath(AdditionalLibrary)));
|
|
|
|
if (!bCanUseSimpleRPaths)
|
|
{
|
|
AddLibraryPathToRPaths(AdditionalLibrary, AbsolutePath, ref RPaths, LinkArguments, bIsBuildingAppBundle, Logger);
|
|
}
|
|
}
|
|
|
|
// Add frameworks
|
|
Dictionary<string, bool> AllFrameworks = new Dictionary<string, bool>();
|
|
foreach (string Framework in LinkEnvironment.Frameworks)
|
|
{
|
|
if (!AllFrameworks.ContainsKey(Framework))
|
|
{
|
|
AllFrameworks.Add(Framework, false);
|
|
}
|
|
}
|
|
foreach (UEBuildFramework Framework in LinkEnvironment.AdditionalFrameworks)
|
|
{
|
|
if (Framework.ZipFile != null)
|
|
{
|
|
FileItem ExtractedTokenFile = ExtractFramework(Framework, Graph, Logger);
|
|
LinkAction.PrerequisiteItems.Add(ExtractedTokenFile);
|
|
}
|
|
|
|
if (Framework.bLinkFramework && !AllFrameworks.ContainsKey(Framework.Name))
|
|
{
|
|
AllFrameworks.Add(Framework.Name, false);
|
|
}
|
|
}
|
|
foreach (string Framework in LinkEnvironment.WeakFrameworks)
|
|
{
|
|
if (!AllFrameworks.ContainsKey(Framework))
|
|
{
|
|
AllFrameworks.Add(Framework, true);
|
|
}
|
|
}
|
|
|
|
foreach (KeyValuePair<string, bool> Framework in AllFrameworks)
|
|
{
|
|
AddFrameworkToLinkCommand(LinkArguments, Framework.Key, Framework.Value ? "-weak_framework" : "-framework");
|
|
AddLibraryPathToRPaths(Framework.Key, AbsolutePath, ref RPaths, LinkArguments, bIsBuildingAppBundle, Logger);
|
|
}
|
|
|
|
// Write the MAP file to the output directory.
|
|
if (LinkEnvironment.bCreateMapFile)
|
|
{
|
|
string MapFileBaseName = OutputFile.AbsolutePath;
|
|
|
|
int AppIdx = MapFileBaseName.IndexOf(".app/Contents/MacOS");
|
|
if (AppIdx != -1)
|
|
{
|
|
MapFileBaseName = MapFileBaseName.Substring(0, AppIdx);
|
|
}
|
|
|
|
FileReference MapFilePath = new FileReference(MapFileBaseName + ".map");
|
|
FileItem MapFile = FileItem.GetItemByFileReference(MapFilePath);
|
|
LinkArguments.Add(String.Format("-Wl,-map,\"{0}\"", MapFilePath));
|
|
LinkAction.ProducedItems.Add(MapFile);
|
|
}
|
|
}
|
|
|
|
if (LinkEnvironment.bIsBuildingDLL)
|
|
{
|
|
// Add the output file to the command-line.
|
|
string? InstallName = LinkEnvironment.InstallName;
|
|
InstallName ??= String.Format("{0}/{1}", DylibsPath, Path.GetFileName(OutputFile.AbsolutePath).Replace($".dylib_{LinkEnvironment.Architecture}", ".dylib"));
|
|
LinkArguments.Add(String.Format("-install_name \"{0}\"", InstallName));
|
|
}
|
|
|
|
List<string> InputFileNames = new List<string>();
|
|
foreach (FileItem InputFile in InputFiles)
|
|
{
|
|
InputFileNames.Add(String.Format("\"{0}\"", NormalizeCommandLinePath(InputFile, LinkEnvironment.RootPaths)));
|
|
LinkAction.PrerequisiteItems.Add(InputFile);
|
|
}
|
|
|
|
foreach (string Filename in InputFileNames)
|
|
{
|
|
LinkArguments.Add(Filename);
|
|
}
|
|
|
|
// Add the output file to the command-line.
|
|
LinkArguments.Add(String.Format("-o \"{0}\"", NormalizeCommandLinePath(OutputFile, LinkEnvironment.RootPaths)));
|
|
|
|
// Add the additional arguments specified by the environment.
|
|
LinkArguments.Add(LinkEnvironment.AdditionalArguments);
|
|
|
|
FileReference ResponseFileName;
|
|
if (bBuildImportLibraryOnly)
|
|
{
|
|
ResponseFileName = FileReference.FromString(OutputFile.FullName + ResponseExt);
|
|
}
|
|
else
|
|
{
|
|
ResponseFileName = GetResponseFileName(LinkEnvironment, OutputFile);
|
|
}
|
|
|
|
FileItem ResponseFileItem = Graph.CreateIntermediateTextFile(ResponseFileName, LinkArguments);
|
|
string ResponsFileArgument = GetResponseFileArgument(ResponseFileItem, LinkEnvironment.RootPaths);
|
|
|
|
LinkAction.PrerequisiteItems.Add(ResponseFileItem);
|
|
|
|
LinkAction.CommandArguments = ResponsFileArgument;
|
|
|
|
// Only execute linking on the local Mac.
|
|
LinkAction.bCanExecuteRemotely = false;
|
|
|
|
LinkAction.StatusDescription = Path.GetFileName(OutputFile.AbsolutePath);
|
|
|
|
LinkAction.ProducedItems.Add(OutputFile);
|
|
|
|
// Delete all items we produce
|
|
LinkAction.DeleteItems.UnionWith(LinkAction.ProducedItems);
|
|
|
|
return OutputFile;
|
|
}
|
|
|
|
static string FormatCopyCommand(string SourceFile, string TargetFile)
|
|
{
|
|
return String.Format("rsync --checksum \"{0}\" \"{1}\"", SourceFile, TargetFile);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generates debug info for a given executable
|
|
/// </summary>
|
|
/// <param name="MachOBinary">FileItem describing the executable or dylib to generate debug info for</param>
|
|
/// <param name="LinkEnvironment"></param>
|
|
/// <param name="Graph">List of actions to be executed. Additional actions will be added to this list.</param>
|
|
/// <param name="Logger">Logger for output</param>
|
|
public FileItem GenerateDebugInfo(FileItem MachOBinary, LinkEnvironment LinkEnvironment, IActionGraphBuilder Graph, ILogger Logger)
|
|
{
|
|
string BinaryPath = MachOBinary.AbsolutePath;
|
|
if (BinaryPath.Contains(".app"))
|
|
{
|
|
while (BinaryPath.Contains(".app"))
|
|
{
|
|
BinaryPath = Path.GetDirectoryName(BinaryPath)!;
|
|
}
|
|
BinaryPath = Path.Combine(BinaryPath, Path.GetFileName(Path.ChangeExtension(MachOBinary.AbsolutePath, ".dSYM")));
|
|
}
|
|
else
|
|
{
|
|
BinaryPath = Path.ChangeExtension(BinaryPath, ".dSYM");
|
|
}
|
|
|
|
FileItem OutputFile = FileItem.GetItemByPath(BinaryPath);
|
|
|
|
// Delete on the local machine
|
|
if (Directory.Exists(OutputFile.AbsolutePath))
|
|
{
|
|
Directory.Delete(OutputFile.AbsolutePath, true);
|
|
}
|
|
|
|
// Make the compile action
|
|
Action GenDebugAction = Graph.CreateAction(ActionType.GenerateDebugInfo);
|
|
GenDebugAction.WorkingDirectory = GetMacDevSrcRoot();
|
|
GenDebugAction.CommandPath = BuildHostPlatform.Current.Shell;
|
|
|
|
// Deletes ay existing file on the building machine. Also, waits 30 seconds, if needed, for the input file to be created in an attempt to work around
|
|
// a problem where dsymutil would exit with an error saying the input file did not exist.
|
|
// Note that the source and dest are switched from a copy command
|
|
string DsymutilPath = "/usr/bin/dsymutil";
|
|
string UniversalDsymutilScriptPath = FileReference.Combine(Unreal.EngineDirectory, "Build/BatchFiles/Mac/GenerateUniversalDSYM.sh").FullName;
|
|
|
|
string ArgumentString = "-c \"";
|
|
ArgumentString += String.Format("for i in {{1..30}}; ");
|
|
ArgumentString += String.Format("do if [ -f \\\"{0}\\\" ] ; ", MachOBinary.AbsolutePath);
|
|
ArgumentString += String.Format("then ");
|
|
ArgumentString += String.Format("break; ");
|
|
ArgumentString += String.Format("else ");
|
|
ArgumentString += String.Format("sleep 1; ");
|
|
ArgumentString += String.Format("fi; ");
|
|
ArgumentString += String.Format("done; ");
|
|
|
|
ArgumentString += String.Format("if [ ! -f \\\"{1}\\\" ] || [ \\\"{0}\\\" -nt \\\"{1}\\\" ] ; ", MachOBinary.AbsolutePath, OutputFile.AbsolutePath);
|
|
ArgumentString += String.Format("then ");
|
|
ArgumentString += String.Format("rm -rf \\\"{0}\\\"; ", OutputFile.AbsolutePath);
|
|
// use the new script for monolthic (ie large) targets
|
|
if (LinkEnvironment.LinkType == TargetLinkType.Monolithic)
|
|
{
|
|
ArgumentString += String.Format(" \\\"{0}\\\" \\\"{1}\\\" \\\"{2}\\\"; ",
|
|
UniversalDsymutilScriptPath,
|
|
MachOBinary.AbsolutePath,
|
|
OutputFile.AbsolutePath);
|
|
}
|
|
else
|
|
{
|
|
ArgumentString += String.Format(" \\\"{0}\\\" -f \\\"{1}\\\" -o \\\"{2}\\\"; ",
|
|
DsymutilPath,
|
|
MachOBinary.AbsolutePath,
|
|
OutputFile.AbsolutePath);
|
|
}
|
|
ArgumentString += String.Format("fi; ");
|
|
ArgumentString += "\"";
|
|
|
|
GenDebugAction.CommandArguments = ArgumentString;
|
|
|
|
GenDebugAction.PrerequisiteItems.Add(MachOBinary);
|
|
GenDebugAction.ProducedItems.Add(OutputFile);
|
|
GenDebugAction.CommandDescription = "";
|
|
GenDebugAction.StatusDescription = "Generating " + Path.GetFileName(BinaryPath);
|
|
GenDebugAction.bCanExecuteRemotely = false;
|
|
|
|
return OutputFile;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates app bundle for a given executable
|
|
/// </summary>
|
|
/// <param name="Target"></param>
|
|
/// <param name="LinkEnvironment"></param>
|
|
/// <param name="Executable">FileItem describing the executable to generate app bundle for</param>
|
|
/// <param name="Graph">List of actions to be executed. Additional actions will be added to this list.</param>
|
|
FileItem FinalizeAppBundle(ReadOnlyTargetRules Target, LinkEnvironment LinkEnvironment, FileItem Executable, IActionGraphBuilder Graph)
|
|
{
|
|
// Make a file item for the source and destination files
|
|
string FullDestPath = Executable.AbsolutePath.Substring(0, Executable.AbsolutePath.IndexOf(".app") + 4);
|
|
FileItem DestFile = FileItem.GetItemByPath(FullDestPath);
|
|
|
|
// Make the compile action
|
|
Action FinalizeAppBundleAction = Graph.CreateAction(ActionType.CreateAppBundle);
|
|
FinalizeAppBundleAction.WorkingDirectory = GetMacDevSrcRoot(); // Path.GetFullPath(".");
|
|
FinalizeAppBundleAction.CommandPath = BuildHostPlatform.Current.Shell;
|
|
FinalizeAppBundleAction.CommandDescription = "";
|
|
|
|
// make path to the script
|
|
FileItem BundleScript = FileItem.GetItemByFileReference(FileReference.Combine(LinkEnvironment.IntermediateDirectory!, "FinalizeAppBundle.sh"));
|
|
|
|
FinalizeAppBundleAction.CommandArguments = "\"" + BundleScript.AbsolutePath + "\"";
|
|
FinalizeAppBundleAction.PrerequisiteItems.Add(Executable);
|
|
FinalizeAppBundleAction.ProducedItems.Add(DestFile);
|
|
FinalizeAppBundleAction.StatusDescription = String.Format("Finalizing app bundle: {0}.app", Path.GetFileName(Executable.AbsolutePath));
|
|
FinalizeAppBundleAction.bCanExecuteRemotely = false;
|
|
|
|
return DestFile;
|
|
}
|
|
|
|
FileItem CopyBundleResource(UEBuildBundleResource Resource, FileItem Executable, DirectoryReference BundleDirectory, IActionGraphBuilder Graph)
|
|
{
|
|
Action CopyAction = Graph.CreateAction(ActionType.CreateAppBundle);
|
|
CopyAction.WorkingDirectory = GetMacDevSrcRoot(); // Path.GetFullPath(".");
|
|
CopyAction.CommandPath = BuildHostPlatform.Current.Shell;
|
|
CopyAction.CommandDescription = "";
|
|
|
|
string BundlePath = BundleDirectory.FullName;
|
|
string SourcePath = Path.Combine(Path.GetFullPath("."), Resource.ResourcePath!);
|
|
string TargetPath = Path.Combine(BundlePath, "Contents", Resource.BundleContentsSubdir!, Path.GetFileName(Resource.ResourcePath)!);
|
|
|
|
FileItem TargetItem = FileItem.GetItemByPath(TargetPath);
|
|
|
|
CopyAction.CommandArguments = String.Format("-c \"cp -f -R \\\"{0}\\\" \\\"{1}\\\"; touch -c \\\"{2}\\\"\"", SourcePath, Path.GetDirectoryName(TargetPath)!.Replace('\\', '/') + "/", TargetPath.Replace('\\', '/'));
|
|
CopyAction.PrerequisiteItems.Add(Executable);
|
|
CopyAction.ProducedItems.Add(TargetItem);
|
|
CopyAction.bShouldOutputStatusDescription = Resource.bShouldLog;
|
|
CopyAction.StatusDescription = String.Format("Copying {0} to app bundle", Path.GetFileName(Resource.ResourcePath));
|
|
CopyAction.bCanExecuteRemotely = false;
|
|
|
|
return TargetItem;
|
|
}
|
|
|
|
private static Dictionary<ReadOnlyTargetRules, DirectoryReference> BundleContentsDirectories = new();
|
|
|
|
public override void ModifyBuildProducts(ReadOnlyTargetRules Target, UEBuildBinary Binary, IEnumerable<string> Libraries, IEnumerable<UEBuildBundleResource> BundleResources, Dictionary<FileReference, BuildProductType> BuildProducts)
|
|
{
|
|
if (Target.bUsePDBFiles == true)
|
|
{
|
|
KeyValuePair<FileReference, BuildProductType>[] BuildProductsArray = BuildProducts.ToArray();
|
|
|
|
foreach (KeyValuePair<FileReference, BuildProductType> BuildProductPair in BuildProductsArray)
|
|
{
|
|
string[] DebugExtensions = Array.Empty<string>();
|
|
switch (BuildProductPair.Value)
|
|
{
|
|
case BuildProductType.Executable:
|
|
DebugExtensions = UEBuildPlatform.GetBuildPlatform(Target.Platform).GetDebugInfoExtensions(Target, UEBuildBinaryType.Executable);
|
|
break;
|
|
case BuildProductType.DynamicLibrary:
|
|
DebugExtensions = UEBuildPlatform.GetBuildPlatform(Target.Platform).GetDebugInfoExtensions(Target, UEBuildBinaryType.DynamicLinkLibrary);
|
|
break;
|
|
}
|
|
string? DSYMExtension = Array.Find(DebugExtensions, element => element == ".dSYM");
|
|
if (!String.IsNullOrEmpty(DSYMExtension))
|
|
{
|
|
string BinaryPath = BuildProductPair.Key.FullName;
|
|
if (BinaryPath.Contains(".app"))
|
|
{
|
|
while (BinaryPath.Contains(".app"))
|
|
{
|
|
BinaryPath = Path.GetDirectoryName(BinaryPath)!;
|
|
}
|
|
BinaryPath = Path.Combine(BinaryPath, BuildProductPair.Key.GetFileName());
|
|
BinaryPath = Path.ChangeExtension(BinaryPath, DSYMExtension);
|
|
FileReference Ref = new FileReference(BinaryPath);
|
|
BuildProducts[Ref] = BuildProductType.SymbolFile;
|
|
}
|
|
}
|
|
else if (BuildProductPair.Value == BuildProductType.SymbolFile && BuildProductPair.Key.FullName.Contains(".app"))
|
|
{
|
|
BuildProducts.Remove(BuildProductPair.Key);
|
|
}
|
|
if (BuildProductPair.Value == BuildProductType.DynamicLibrary && Target.bCreateMapFile)
|
|
{
|
|
BuildProducts.Add(new FileReference(BuildProductPair.Key.FullName + ".map"), BuildProductType.MapFile);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Target.bIsBuildingConsoleApplication)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!BundleContentsDirectories.ContainsKey(Target) && Binary.Type == UEBuildBinaryType.Executable)
|
|
{
|
|
// For Mac binary executables, we may build it outside of app, but expect it to be inside of .app after modern Xcode does its thing
|
|
// We still keep the executable outside of app as RequiredResource, so that Horde will copy in-between agents
|
|
FileReference FinalBinaryPath = Binary.OutputFilePath;
|
|
if (!FinalBinaryPath.Directory.FullName.EndsWith(".app/Contents/MacOS", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
FinalBinaryPath = FileReference.Combine(FinalBinaryPath.Directory, FinalBinaryPath.GetFileName() + ".app", "Contents", "MacOS", FinalBinaryPath.GetFileName());
|
|
BuildProducts[Binary.OutputFilePath] = BuildProductType.RequiredResource;
|
|
BuildProducts.Add(FinalBinaryPath, BuildProductType.Executable);
|
|
}
|
|
BundleContentsDirectories.Add(Target, FinalBinaryPath.Directory.ParentDirectory!);
|
|
}
|
|
DirectoryReference? BundleContentsDirectory = BundleContentsDirectories.GetValueOrDefault(Target);
|
|
|
|
// We need to know what third party dylibs would be copied to the bundle
|
|
if (Binary.Type != UEBuildBinaryType.StaticLibrary)
|
|
{
|
|
foreach (UEBuildBundleResource Resource in BundleResources)
|
|
{
|
|
if (Directory.Exists(Resource.ResourcePath))
|
|
{
|
|
foreach (string ResourceFile in Directory.GetFiles(Resource.ResourcePath, "*", SearchOption.AllDirectories))
|
|
{
|
|
BuildProducts.Add(FileReference.Combine(BundleContentsDirectory!, Resource.BundleContentsSubdir!, ResourceFile.Substring(Path.GetDirectoryName(Resource.ResourcePath)!.Length + 1)), BuildProductType.RequiredResource);
|
|
}
|
|
}
|
|
else if (BundleContentsDirectory != null)
|
|
{
|
|
BuildProducts.Add(FileReference.Combine(BundleContentsDirectory, Resource.BundleContentsSubdir!, Path.GetFileName(Resource.ResourcePath)!), BuildProductType.RequiredResource);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Binary.Type == UEBuildBinaryType.Executable)
|
|
{
|
|
// And we also need all the resources
|
|
BuildProducts.Add(FileReference.Combine(BundleContentsDirectory!, "Info.plist"), BuildProductType.RequiredResource);
|
|
BuildProducts.Add(FileReference.Combine(BundleContentsDirectory!, "PkgInfo"), BuildProductType.RequiredResource);
|
|
|
|
// when we codesign, we need to copy the signature around on build machines, etc. this will put the CodeResources (that were created by post-build signing)
|
|
// is in the .target receipt file
|
|
BuildProducts.Add(FileReference.Combine(BundleContentsDirectory!, "_CodeSignature", "CodeResources"), BuildProductType.RequiredResource);
|
|
|
|
// modern xcode doesn't use the bootstrap launcher because it can make full .app with staged data inside it
|
|
if (!bUseModernXcode)
|
|
{
|
|
if (Target.Type == TargetType.Editor)
|
|
{
|
|
BuildProducts.Add(FileReference.Combine(BundleContentsDirectory!, "Resources/UnrealEditor.icns"), BuildProductType.RequiredResource);
|
|
BuildProducts.Add(FileReference.Combine(BundleContentsDirectory!, "Resources/UProject.icns"), BuildProductType.RequiredResource);
|
|
}
|
|
else
|
|
{
|
|
string IconName = Target.Name;
|
|
if (IconName == "EpicGamesBootstrapLauncher")
|
|
{
|
|
IconName = "EpicGamesLauncher";
|
|
}
|
|
BuildProducts.Add(FileReference.Combine(BundleContentsDirectory!, "Resources/" + IconName + ".icns"), BuildProductType.RequiredResource);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private List<FileItem> DebugInfoFiles = new List<FileItem>();
|
|
|
|
public override void FinalizeOutput(ReadOnlyTargetRules Target, TargetMakefileBuilder MakefileBuilder)
|
|
{
|
|
base.FinalizeOutput(Target, MakefileBuilder);
|
|
|
|
TargetMakefile Makefile = MakefileBuilder.Makefile;
|
|
|
|
// Re-add any .dSYM files that may have been stripped out.
|
|
List<string> OutputFiles = Makefile.OutputItems.Select(Item => Path.ChangeExtension(Item.FullName, ".dSYM")).Distinct().ToList();
|
|
foreach (FileItem DebugItem in DebugInfoFiles)
|
|
{
|
|
if (OutputFiles.Any(Item => String.Equals(Item, DebugItem.FullName, StringComparison.InvariantCultureIgnoreCase)))
|
|
{
|
|
Makefile.OutputItems.Add(DebugItem);
|
|
}
|
|
}
|
|
}
|
|
|
|
public override ICollection<FileItem> PostBuild(ReadOnlyTargetRules Target, FileItem Executable, LinkEnvironment BinaryLinkEnvironment, IActionGraphBuilder Graph)
|
|
{
|
|
ICollection<FileItem> OutputFiles = base.PostBuild(Target, Executable, BinaryLinkEnvironment, Graph);
|
|
|
|
if (BinaryLinkEnvironment.bIsBuildingLibrary)
|
|
{
|
|
return OutputFiles;
|
|
}
|
|
|
|
// modern handle bundles via xcode
|
|
if (!bUseModernXcode)
|
|
{
|
|
if (BinaryLinkEnvironment.BundleDirectory != null)
|
|
{
|
|
foreach (UEBuildBundleResource Resource in BinaryLinkEnvironment.AdditionalBundleResources)
|
|
{
|
|
OutputFiles.Add(CopyBundleResource(Resource, Executable, BinaryLinkEnvironment.BundleDirectory, Graph));
|
|
}
|
|
}
|
|
}
|
|
|
|
// For Mac, generate the dSYM file if the config file is set to do so
|
|
if (BinaryLinkEnvironment.bUsePDBFiles == true)
|
|
{
|
|
DebugInfoFiles.Add(GenerateDebugInfo(Executable, BinaryLinkEnvironment, Graph, Logger));
|
|
}
|
|
|
|
if ((BinaryLinkEnvironment.bIsBuildingDLL && (Options & ClangToolChainOptions.OutputDylib) == 0) || (BinaryLinkEnvironment.bIsBuildingConsoleApplication && Executable.Name.EndsWith("-Cmd")))
|
|
{
|
|
return OutputFiles;
|
|
}
|
|
|
|
bool bIsBuildingAppBundle = !BinaryLinkEnvironment.bIsBuildingDLL && !BinaryLinkEnvironment.bIsBuildingLibrary && !BinaryLinkEnvironment.bIsBuildingConsoleApplication;
|
|
if (bIsBuildingAppBundle)
|
|
{
|
|
if (!bUseModernXcode)
|
|
{
|
|
OutputFiles.Add(FinalizeAppBundle(Target, BinaryLinkEnvironment, Executable, Graph));
|
|
}
|
|
}
|
|
|
|
return OutputFiles;
|
|
}
|
|
|
|
public void StripSymbols(FileReference SourceFile, FileReference TargetFile)
|
|
{
|
|
StripSymbolsWithXcode(SourceFile, TargetFile, Settings.ToolchainDir);
|
|
}
|
|
};
|
|
}
|