// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Diagnostics; using System.Security.AccessControl; using System.Text; using System.Linq; using Ionic.Zip; namespace UnrealBuildTool { class MacToolChain : AppleToolChain { public MacToolChain(FileReference InProjectFile) : base(CPPTargetPlatform.Mac, UnrealTargetPlatform.Mac, InProjectFile) { } /*********************************************************************** * NOTE: * Do NOT change the defaults to set your values, instead you should set the environment variables * properly in your system, as other tools make use of them to work properly! * The defaults are there simply for examples so you know what to put in your env vars... ***********************************************************************/ /// /// Which version of the Mac OS SDK to target at build time /// public static string MacOSSDKVersion = "latest"; public static float MacOSSDKVersionFloat = 0.0f; /// /// Which version of the Mac OS X to allow at run time /// public static string MacOSVersion = "10.9"; /// /// Minimum version of Mac OS X to actually run on, running on earlier versions will display the system minimum version error dialog & exit. /// public static string MinMacOSVersion = "10.9.2"; /// /// Which developer directory to root from? If this is "xcode-select", UBT will query for the currently selected Xcode /// private static string XcodeDeveloperDir = "xcode-select"; /// /// Directory for the developer binaries /// private static string ToolchainDir = ""; /// /// Location of the SDKs /// private static string BaseSDKDir; /// /// Which compiler frontend to use /// private static string MacCompiler = "clang++"; /// /// Which linker frontend to use /// private static string MacLinker = "clang++"; /// /// Which archiver to use /// private static string MacArchiver = "libtool"; /// /// Track which scripts need to be deleted before appending to /// private bool bHasWipedCopyDylibScript = false; private bool bHasWipedFixDylibScript = false; private static List BundleDependencies = new List(); private static void SetupXcodePaths(bool bVerbose) { SelectXcode(ref XcodeDeveloperDir, bVerbose); BaseSDKDir = XcodeDeveloperDir + "Platforms/MacOSX.platform/Developer/SDKs"; ToolchainDir = XcodeDeveloperDir + "Toolchains/XcodeDefault.xctoolchain/usr/bin/"; SelectSDK(BaseSDKDir, "MacOSX", ref MacOSSDKVersion, bVerbose); // convert to float for easy comparison MacOSSDKVersionFloat = float.Parse(MacOSSDKVersion, System.Globalization.CultureInfo.InvariantCulture); } public override void SetUpGlobalEnvironment() { base.SetUpGlobalEnvironment(); SetupXcodePaths(true); } static string GetCompileArguments_Global(CPPEnvironment CompileEnvironment) { string Result = ""; Result += " -fmessage-length=0"; Result += " -pipe"; Result += " -fpascal-strings"; Result += " -fexceptions"; Result += " -fasm-blocks"; Result += " -Wall -Werror"; Result += " -Wno-unused-variable"; Result += " -Wno-unused-value"; // This will hide the warnings about static functions in headers that aren't used in every single .cpp file Result += " -Wno-unused-function"; // This hides the "enumeration value 'XXXXX' not handled in switch [-Wswitch]" warnings - we should maybe remove this at some point and add UE_LOG(, Fatal, ) to default cases Result += " -Wno-switch"; // This hides the "warning : comparison of unsigned expression < 0 is always false" type warnings due to constant comparisons, which are possible with template arguments Result += " -Wno-tautological-compare"; // This will prevent the issue of warnings for unused private variables. Result += " -Wno-unused-private-field"; Result += " -Wno-invalid-offsetof"; // needed to suppress warnings about using offsetof on non-POD types. if (MacOSSDKVersionFloat < 10.9f && MacOSSDKVersionFloat >= 10.11f) { Result += " -Wno-inconsistent-missing-override"; // too many missing overrides... Result += " -Wno-unused-local-typedef"; // PhysX has some, hard to remove } if (CompileEnvironment.Config.bEnableShadowVariableWarning) { Result += " -Wshadow" + (BuildConfiguration.bShadowVariableErrors ? "" : " -Wno-error=shadow"); } // @todo: Remove these two when the code is fixed and they're no longer needed Result += " -Wno-logical-op-parentheses"; Result += " -Wno-unknown-pragmas"; Result += " -c"; Result += " -arch x86_64"; Result += " -isysroot " + BaseSDKDir + "/MacOSX" + MacOSSDKVersion + ".sdk"; Result += " -mmacosx-version-min=" + MacOSVersion; // Optimize non- debug builds. if (CompileEnvironment.Config.Target.Configuration != CPPTargetConfiguration.Debug) { if (UEBuildConfiguration.bCompileForSize) { Result += " -Oz"; } else { Result += " -O3"; } } else { Result += " -O0"; } // Create DWARF format debug info if wanted, if (CompileEnvironment.Config.bCreateDebugInfo) { Result += " -gdwarf-2"; } return Result; } static string GetCompileArguments_CPP() { string Result = ""; Result += " -x objective-c++"; Result += " -fobjc-abi-version=2"; Result += " -fobjc-legacy-dispatch"; Result += " -fno-rtti"; Result += " -std=c++11"; Result += " -stdlib=libc++"; return Result; } static string GetCompileArguments_MM() { string Result = ""; Result += " -x objective-c++"; Result += " -fobjc-abi-version=2"; Result += " -fobjc-legacy-dispatch"; Result += " -fno-rtti"; Result += " -std=c++11"; Result += " -stdlib=libc++"; return Result; } static string GetCompileArguments_M() { string Result = ""; Result += " -x objective-c"; Result += " -fobjc-abi-version=2"; Result += " -fobjc-legacy-dispatch"; Result += " -std=c++11"; Result += " -stdlib=libc++"; return Result; } static string GetCompileArguments_C() { string Result = ""; Result += " -x c"; return Result; } static string GetCompileArguments_PCH() { string Result = ""; Result += " -x objective-c++-header"; Result += " -fobjc-abi-version=2"; Result += " -fobjc-legacy-dispatch"; Result += " -fno-rtti"; Result += " -std=c++11"; Result += " -stdlib=libc++"; return Result; } string AddFrameworkToLinkCommand(string FrameworkName, string Arg = "-framework") { string Result = ""; if (FrameworkName.EndsWith(".framework")) { Result += " -F \"" + ConvertPath(Path.GetDirectoryName(Path.GetFullPath(FrameworkName))) + "\""; FrameworkName = Path.GetFileNameWithoutExtension(FrameworkName); } Result += " " + Arg + " \"" + FrameworkName + "\""; return Result; } string GetLinkArguments_Global(LinkEnvironment LinkEnvironment) { string Result = ""; Result += " -arch x86_64"; Result += " -isysroot " + BaseSDKDir + "/MacOSX" + MacOSSDKVersion + ".sdk"; Result += " -mmacosx-version-min=" + MacOSVersion; Result += " -dead_strip"; if (LinkEnvironment.Config.bIsBuildingDLL) { Result += " -dynamiclib"; } // Needed to make sure install_name_tool will be able to update paths in Mach-O headers Result += " -headerpad_max_install_names"; Result += " -lc++"; return Result; } static string GetArchiveArguments_Global(LinkEnvironment LinkEnvironment) { string Result = ""; Result += " -static"; return Result; } public override CPPOutput CompileCPPFiles(UEBuildTarget Target, CPPEnvironment CompileEnvironment, List SourceFiles, string ModuleName) { var Arguments = new StringBuilder(); var PCHArguments = new StringBuilder(); Arguments.Append(GetCompileArguments_Global(CompileEnvironment)); if (CompileEnvironment.Config.PrecompiledHeaderAction == PrecompiledHeaderAction.Include) { // Add the precompiled header file's path to the include path so GCC can find it. // This needs to be before the other include paths to ensure GCC uses it instead of the source header file. var PrecompiledFileExtension = UEBuildPlatform.BuildPlatformDictionary[UnrealTargetPlatform.Mac].GetBinaryExtension(UEBuildBinaryType.PrecompiledHeader); PCHArguments.Append(" -include \""); PCHArguments.Append(CompileEnvironment.PrecompiledHeaderFile.AbsolutePath.Replace(PrecompiledFileExtension, "")); PCHArguments.Append("\""); } // Add include paths to the argument list. HashSet AllIncludes = new HashSet(CompileEnvironment.Config.CPPIncludeInfo.IncludePaths); AllIncludes.UnionWith(CompileEnvironment.Config.CPPIncludeInfo.SystemIncludePaths); foreach (string IncludePath in AllIncludes) { Arguments.Append(" -I\""); if (BuildHostPlatform.Current.Platform != UnrealTargetPlatform.Mac) { Arguments.Append(ConvertPath(Path.GetFullPath(IncludePath))); // sync any third party headers we may need if (IncludePath.Contains("ThirdParty")) { string[] FileList = Directory.GetFiles(IncludePath, "*.h", SearchOption.AllDirectories); foreach (string File in FileList) { FileItem ExternalDependency = FileItem.GetItemByPath(File); LocalToRemoteFileItem(ExternalDependency, true); } FileList = Directory.GetFiles(IncludePath, "*.cpp", SearchOption.AllDirectories); foreach (string File in FileList) { FileItem ExternalDependency = FileItem.GetItemByPath(File); LocalToRemoteFileItem(ExternalDependency, true); } } } else { Arguments.Append(IncludePath); } Arguments.Append("\""); } foreach (string Definition in CompileEnvironment.Config.Definitions) { Arguments.Append(" -D\""); Arguments.Append(Definition); Arguments.Append("\""); } var BuildPlatform = UEBuildPlatform.GetBuildPlatformForCPPTargetPlatform(CompileEnvironment.Config.Target.Platform); CPPOutput Result = new CPPOutput(); // Create a compile action for each source file. foreach (FileItem SourceFile in SourceFiles) { Action CompileAction = new Action(ActionType.Compile); string FileArguments = ""; string Extension = Path.GetExtension(SourceFile.AbsolutePath).ToUpperInvariant(); if (CompileEnvironment.Config.PrecompiledHeaderAction == PrecompiledHeaderAction.Create) { // Compile the file as a C++ PCH. FileArguments += GetCompileArguments_PCH(); } else if (Extension == ".C") { // Compile the file as C code. FileArguments += GetCompileArguments_C(); } else if (Extension == ".CC") { // Compile the file as C++ code. FileArguments += GetCompileArguments_CPP(); } else if (Extension == ".MM") { // Compile the file as Objective-C++ code. FileArguments += GetCompileArguments_MM(); } else if (Extension == ".M") { // Compile the file as Objective-C++ code. FileArguments += GetCompileArguments_M(); } else { // Compile the file as C++ code. FileArguments += GetCompileArguments_CPP(); // only use PCH for .cpp files FileArguments += PCHArguments.ToString(); } // Add the C++ source file and its included files to the prerequisite item list. AddPrerequisiteSourceFile(Target, BuildPlatform, CompileEnvironment, SourceFile, CompileAction.PrerequisiteItems); if (CompileEnvironment.Config.PrecompiledHeaderAction == PrecompiledHeaderAction.Create) { var PrecompiledHeaderExtension = UEBuildPlatform.BuildPlatformDictionary[UnrealTargetPlatform.Mac].GetBinaryExtension(UEBuildBinaryType.PrecompiledHeader); // Add the precompiled header file to the produced item list. FileItem PrecompiledHeaderFile = FileItem.GetItemByFileReference( FileReference.Combine( CompileEnvironment.Config.OutputDirectory, Path.GetFileName(SourceFile.AbsolutePath) + PrecompiledHeaderExtension ) ); FileItem RemotePrecompiledHeaderFile = LocalToRemoteFileItem(PrecompiledHeaderFile, false); CompileAction.ProducedItems.Add(RemotePrecompiledHeaderFile); Result.PrecompiledHeaderFile = RemotePrecompiledHeaderFile; // Add the parameters needed to compile the precompiled header file to the command-line. FileArguments += string.Format(" -o \"{0}\"", RemotePrecompiledHeaderFile.AbsolutePath, false); } else { if (CompileEnvironment.Config.PrecompiledHeaderAction == PrecompiledHeaderAction.Include) { CompileAction.bIsUsingPCH = true; CompileAction.PrerequisiteItems.Add(CompileEnvironment.PrecompiledHeaderFile); } var ObjectFileExtension = UEBuildPlatform.BuildPlatformDictionary[UnrealTargetPlatform.Mac].GetBinaryExtension(UEBuildBinaryType.Object); // Add the object file to the produced item list. FileItem ObjectFile = FileItem.GetItemByFileReference( FileReference.Combine( CompileEnvironment.Config.OutputDirectory, Path.GetFileName(SourceFile.AbsolutePath) + ObjectFileExtension ) ); FileItem RemoteObjectFile = LocalToRemoteFileItem(ObjectFile, false); CompileAction.ProducedItems.Add(RemoteObjectFile); Result.ObjectFiles.Add(RemoteObjectFile); FileArguments += string.Format(" -o \"{0}\"", RemoteObjectFile.AbsolutePath, false); } // Add the source file path to the command-line. FileArguments += string.Format(" \"{0}\"", ConvertPath(SourceFile.AbsolutePath), false); if (BuildHostPlatform.Current.Platform != UnrealTargetPlatform.Mac) { CompileAction.ActionHandler = new Action.BlockingActionHandler(RPCUtilHelper.RPCActionHandler); } CompileAction.WorkingDirectory = GetMacDevSrcRoot(); CompileAction.CommandPath = ToolchainDir + MacCompiler; CompileAction.CommandArguments = Arguments + FileArguments + CompileEnvironment.Config.AdditionalArguments; CompileAction.CommandDescription = "Compile"; CompileAction.StatusDescription = Path.GetFileName(SourceFile.AbsolutePath); CompileAction.bIsGCCCompiler = true; // We're already distributing the command by execution on Mac. CompileAction.bCanExecuteRemotely = false; CompileAction.OutputEventHandler = new DataReceivedEventHandler(RemoteOutputReceivedEventHandler); } return Result; } 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("../Build/Build.version", out Version)) { return Version.Changelist; } else { return 0; } } private string LoadEngineDisplayVersion(bool bIgnorePatchVersion = false) { string[] VersionHeader = Utils.ReadAllText("../Source/Runtime/Launch/Resources/Version.h").Replace("\r\n", "\n").Replace("\t", " ").Split('\n'); string EngineVersionMajor = "4"; string EngineVersionMinor = "0"; string EngineVersionPatch = "0"; foreach (string Line in VersionHeader) { if (Line.StartsWith("#define ENGINE_MAJOR_VERSION ")) { EngineVersionMajor = Line.Split(' ')[2]; } else if (Line.StartsWith("#define ENGINE_MINOR_VERSION ")) { EngineVersionMinor = Line.Split(' ')[2]; } else if (Line.StartsWith("#define ENGINE_PATCH_VERSION ") && !bIgnorePatchVersion) { EngineVersionPatch = Line.Split(' ')[2]; } } return EngineVersionMajor + "." + EngineVersionMinor + "." + EngineVersionPatch; } private string LoadLauncherDisplayVersion() { string[] VersionHeader = Utils.ReadAllText("../../Portal/Source/Layers/DataAccess/Public/Version.h").Replace("\r\n", "\n").Replace("\t", " ").Split('\n'); string LauncherVersionMajor = "1"; string LauncherVersionMinor = "0"; string LauncherVersionPatch = "0"; foreach (string Line in VersionHeader) { if (Line.StartsWith("#define PORTAL_MAJOR_VERSION ")) { LauncherVersionMajor = Line.Split(' ')[2]; } else if (Line.StartsWith("#define PORTAL_MINOR_VERSION ")) { LauncherVersionMinor = Line.Split(' ')[2]; } else if (Line.StartsWith("#define PORTAL_PATCH_VERSION ")) { LauncherVersionPatch = Line.Split(' ')[2]; } } return LauncherVersionMajor + "." + LauncherVersionMinor + "." + LauncherVersionPatch; } private int LoadBuiltFromChangelistValue() { string[] VersionHeader = Utils.ReadAllText("../Source/Runtime/Launch/Resources/Version.h").Replace("\r\n", "\n").Replace("\t", " ").Split('\n'); foreach (string Line in VersionHeader) { if (Line.StartsWith("#define BUILT_FROM_CHANGELIST ")) { return int.Parse(Line.Split(' ')[2]); } } return 0; } private int LoadIsLicenseeVersionValue() { string[] VersionHeader = Utils.ReadAllText("../Source/Runtime/Launch/Resources/Version.h").Replace("\r\n", "\n").Replace("\t", " ").Split('\n'); foreach (string Line in VersionHeader) { if (Line.StartsWith("#define ENGINE_IS_LICENSEE_VERSION ")) { return int.Parse(Line.Split(' ')[2]); } } return 0; } private string LoadEngineAPIVersion() { int CL = 0; // @todo: Temp solution to work around a problem with parsing ModuleVersion.h updated for 4.4.1 hotfix int BuiltFromChangelist = LoadBuiltFromChangelistValue(); if (BuiltFromChangelist > 0) { bool bFirstApiVersionDefine = true; foreach (string Line in File.ReadAllLines("../Source/Runtime/Core/Public/Modules/ModuleVersion.h")) { string[] Tokens = Line.Split(new char[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries); if (Tokens.Length >= 3 && Tokens[0] == "#define" && Tokens[1] == "MODULE_API_VERSION") { if (!bFirstApiVersionDefine || LoadIsLicenseeVersionValue() != 0) { if (Tokens[2] == "BUILT_FROM_CHANGELIST") { CL = LoadEngineCL(); } else { CL = int.Parse(Tokens[2]); } break; } bFirstApiVersionDefine = false; } } } return String.Format("{0}.{1}.{2}", CL / (100 * 100), (CL / 100) % 100, CL % 100); } private void AddLibraryPathToRPaths(string Library, string ExeAbsolutePath, ref List RPaths, ref string LinkCommand, bool bIsBuildingAppBundle) { string LibraryDir = Path.GetDirectoryName(Library); string ExeDir = Path.GetDirectoryName(ExeAbsolutePath); if (!Library.Contains("/Engine/Binaries/Mac/") && (Library.EndsWith("dylib") || Library.EndsWith(".framework")) && LibraryDir != ExeDir) { string RelativePath = Utils.MakePathRelativeTo(LibraryDir, ExeDir).Replace("\\", "/"); if (!RelativePath.Contains(LibraryDir) && !RPaths.Contains(RelativePath)) { RPaths.Add(RelativePath); LinkCommand += " -rpath \"@loader_path/" + RelativePath + "\""; if (bIsBuildingAppBundle) { string PathInBundle = Path.Combine(Path.GetDirectoryName(ExeDir), "UE4/Engine/Binaries/Mac", RelativePath.Substring(9)); Utils.CollapseRelativeDirectories(ref PathInBundle); string RelativePathInBundle = Utils.MakePathRelativeTo(PathInBundle, ExeDir).Replace("\\", "/"); LinkCommand += " -rpath \"@loader_path/" + RelativePathInBundle + "\""; } } } } public override FileItem LinkFiles(LinkEnvironment LinkEnvironment, bool bBuildImportLibraryOnly) { bool bIsBuildingLibrary = LinkEnvironment.Config.bIsBuildingLibrary || bBuildImportLibraryOnly; // Create an action that invokes the linker. Action LinkAction = new Action(ActionType.Link); if (BuildHostPlatform.Current.Platform != UnrealTargetPlatform.Mac) { LinkAction.ActionHandler = new Action.BlockingActionHandler(RPCUtilHelper.RPCActionHandler); } LinkAction.WorkingDirectory = GetMacDevSrcRoot(); LinkAction.CommandPath = "/bin/sh"; LinkAction.CommandDescription = "Link"; string EngineAPIVersion = LoadEngineAPIVersion(); string EngineDisplayVersion = LoadEngineDisplayVersion(true); string VersionArg = LinkEnvironment.Config.bIsBuildingDLL ? " -current_version " + EngineAPIVersion + " -compatibility_version " + EngineDisplayVersion : ""; string Linker = bIsBuildingLibrary ? MacArchiver : MacLinker; string LinkCommand = ToolchainDir + Linker + VersionArg + " " + (bIsBuildingLibrary ? GetArchiveArguments_Global(LinkEnvironment) : GetLinkArguments_Global(LinkEnvironment)); // 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 = !Utils.IsRunningOnMono && (bBuildImportLibraryOnly || LinkEnvironment.Config.bIsBuildingDLL); // Add the output file as a production of the link action. FileItem OutputFile = FileItem.GetItemByFileReference(LinkEnvironment.Config.OutputFilePath); OutputFile.bNeedsHotReloadNumbersDLLCleanUp = LinkEnvironment.Config.bIsBuildingDLL; FileItem RemoteOutputFile = LocalToRemoteFileItem(OutputFile, false); // 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 EngineAndGameLibraries = new List(); string DylibsPath = "@rpath"; string AbsolutePath = OutputFile.AbsolutePath.Replace("\\", "/"); if (!bIsBuildingLibrary) { LinkCommand += " -rpath @loader_path/ -rpath @executable_path/"; } List ThirdPartyLibraries = new List(); if (BuildHostPlatform.Current.Platform != UnrealTargetPlatform.Mac) { // Add any additional files that we'll need in order to link the app foreach (string AdditionalShadowFile in LinkEnvironment.Config.AdditionalShadowFiles) { FileItem ShadowFile = FileItem.GetExistingItemByPath(AdditionalShadowFile); if (ShadowFile != null) { QueueFileForBatchUpload(ShadowFile); LinkAction.PrerequisiteItems.Add(ShadowFile); } else { throw new BuildException("Couldn't find required additional file to shadow: {0}", AdditionalShadowFile); } } // Add any frameworks to be shadowed to the remote foreach (string FrameworkPath in LinkEnvironment.Config.Frameworks) { if (FrameworkPath.EndsWith(".framework")) { foreach (string FrameworkFile in Directory.EnumerateFiles(FrameworkPath, "*", SearchOption.AllDirectories)) { FileItem FrameworkFileItem = FileItem.GetExistingItemByPath(FrameworkFile); QueueFileForBatchUpload(FrameworkFileItem); LinkAction.PrerequisiteItems.Add(FrameworkFileItem); } } } } bool bIsBuildingAppBundle = !LinkEnvironment.Config.bIsBuildingDLL && !LinkEnvironment.Config.bIsBuildingLibrary && !LinkEnvironment.Config.bIsBuildingConsoleApplication; List RPaths = new List(); if (!bIsBuildingLibrary || LinkEnvironment.Config.bIncludeDependentLibrariesInLibrary) { // Add the additional libraries to the argument list. foreach (string AdditionalLibrary in LinkEnvironment.Config.AdditionalLibraries) { // Can't link dynamic libraries when creating a static one if (bIsBuildingLibrary && (Path.GetExtension(AdditionalLibrary) == ".dylib" || AdditionalLibrary == "z")) { continue; } if (Path.GetFileName(AdditionalLibrary).StartsWith("lib")) { LinkCommand += string.Format(" \"{0}\"", ConvertPath(Path.GetFullPath(AdditionalLibrary))); if (Path.GetExtension(AdditionalLibrary) == ".dylib") { ThirdPartyLibraries.Add(AdditionalLibrary); } if (BuildHostPlatform.Current.Platform != UnrealTargetPlatform.Mac) { // copy over libs we may need FileItem ShadowFile = FileItem.GetExistingItemByPath(AdditionalLibrary); if (ShadowFile != null) { QueueFileForBatchUpload(ShadowFile); } } } else if (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 EngineAndGameLibraries.Add(ConvertPath(Path.GetFullPath(AdditionalLibrary))); if (!Utils.IsRunningOnMono) { FileItem EngineLibDependency = FileItem.GetItemByPath(AdditionalLibrary); LinkAction.PrerequisiteItems.Add(EngineLibDependency); FileItem RemoteEngineLibDependency = FileItem.GetRemoteItemByPath(ConvertPath(Path.GetFullPath(AdditionalLibrary)), UnrealTargetPlatform.Mac); LinkAction.PrerequisiteItems.Add(RemoteEngineLibDependency); //Log.TraceInformation("Adding {0} / {1} as a prereq to {2}", EngineLibDependency.AbsolutePath, RemoteEngineLibDependency.AbsolutePath, RemoteOutputFile.AbsolutePath); } else if (LinkEnvironment.Config.bIsCrossReferenced == false) { FileItem EngineLibDependency = FileItem.GetItemByPath(AdditionalLibrary); LinkAction.PrerequisiteItems.Add(EngineLibDependency); } } else if (AdditionalLibrary.Contains(".framework/")) { LinkCommand += string.Format(" \'{0}\'", AdditionalLibrary); } else { LinkCommand += string.Format(" -l{0}", AdditionalLibrary); } AddLibraryPathToRPaths(AdditionalLibrary, AbsolutePath, ref RPaths, ref LinkCommand, bIsBuildingAppBundle); } foreach (string AdditionalLibrary in LinkEnvironment.Config.DelayLoadDLLs) { // Can't link dynamic libraries when creating a static one if (bIsBuildingLibrary && (Path.GetExtension(AdditionalLibrary) == ".dylib" || AdditionalLibrary == "z")) { continue; } LinkCommand += string.Format(" -weak_library \"{0}\"", ConvertPath(Path.GetFullPath(AdditionalLibrary))); AddLibraryPathToRPaths(AdditionalLibrary, AbsolutePath, ref RPaths, ref LinkCommand, bIsBuildingAppBundle); } } // Add frameworks Dictionary AllFrameworks = new Dictionary(); foreach (string Framework in LinkEnvironment.Config.Frameworks) { if (!AllFrameworks.ContainsKey(Framework)) { AllFrameworks.Add(Framework, false); } } foreach (UEBuildFramework Framework in LinkEnvironment.Config.AdditionalFrameworks) { if (!AllFrameworks.ContainsKey(Framework.FrameworkName)) { AllFrameworks.Add(Framework.FrameworkName, false); } } foreach (string Framework in LinkEnvironment.Config.WeakFrameworks) { if (!AllFrameworks.ContainsKey(Framework)) { AllFrameworks.Add(Framework, true); } } if (!bIsBuildingLibrary) { foreach (var Framework in AllFrameworks) { LinkCommand += AddFrameworkToLinkCommand(Framework.Key, Framework.Value ? "-weak_framework" : "-framework"); AddLibraryPathToRPaths(Framework.Key, AbsolutePath, ref RPaths, ref LinkCommand, bIsBuildingAppBundle); } } // Add the input files to a response file, and pass the response file on the command-line. List InputFileNames = new List(); foreach (FileItem InputFile in LinkEnvironment.InputFiles) { if (bIsBuildingLibrary) { InputFileNames.Add(string.Format("\"{0}\"", InputFile.AbsolutePath)); } else { string EnginePath = ConvertPath(Path.GetDirectoryName(Directory.GetCurrentDirectory())); string InputFileRelativePath = InputFile.AbsolutePath.Replace(EnginePath, ".."); InputFileNames.Add(string.Format("\"{0}\"", InputFileRelativePath)); } LinkAction.PrerequisiteItems.Add(InputFile); } if (bIsBuildingLibrary) { foreach (string Filename in InputFileNames) { LinkCommand += " " + Filename; } } else { // Write the list of input files to a response file, with a tempfilename, on remote machine FileReference ResponsePath = FileReference.Combine(LinkEnvironment.Config.IntermediateDirectory, Path.GetFileName(OutputFile.AbsolutePath) + ".response"); // Never create response files when we are only generating IntelliSense data if (!ProjectFileGenerator.bGenerateProjectFiles) { ResponseFile.Create(ResponsePath, InputFileNames); if (BuildHostPlatform.Current.Platform != UnrealTargetPlatform.Mac) { RPCUtilHelper.CopyFile(ResponsePath.FullName, ConvertPath(ResponsePath.FullName), true); } } LinkCommand += string.Format(" @\"{0}\"", ConvertPath(ResponsePath.FullName)); } if (LinkEnvironment.Config.bIsBuildingDLL) { // Add the output file to the command-line. string Filename = ""; int Index = OutputFile.AbsolutePath.LastIndexOf(".app/Contents/MacOS/"); if (Index > -1) { Index += ".app/Contents/MacOS/".Length; Filename = OutputFile.AbsolutePath.Substring(Index); } else { Filename = Path.GetFileName(OutputFile.AbsolutePath); } LinkCommand += string.Format(" -install_name {0}/{1}", DylibsPath, Filename); } if (!bIsBuildingLibrary) { if (UnrealBuildTool.RunningRocket() || (Utils.IsRunningOnMono && LinkEnvironment.Config.bIsCrossReferenced == false)) { foreach (string Library in EngineAndGameLibraries) { string LibraryPath = Library; if (!File.Exists(Library)) { string LibraryDir = Path.GetDirectoryName(Library); string LibraryName = Path.GetFileName(Library); string AppBundleName = "UE4Editor"; if (LibraryName.Contains("UE4Editor-Mac-")) { string[] Parts = LibraryName.Split('-'); AppBundleName += "-" + Parts[1] + "-" + Parts[2]; } AppBundleName += ".app"; LibraryPath = LibraryDir + "/" + AppBundleName + "/Contents/MacOS/" + LibraryName; if (!File.Exists(LibraryPath)) { LibraryPath = Library; } } LinkCommand += " \"" + LibraryPath + "\""; } } else { // Tell linker to ignore unresolved symbols, so we don't have a problem with cross dependent dylibs that do not exist yet. // This is fixed in later step, FixDylibDependencies. LinkCommand += string.Format(" -undefined dynamic_lookup"); } } // Add the output file to the command-line. LinkCommand += string.Format(" -o \"{0}\"", RemoteOutputFile.AbsolutePath); // Add the additional arguments specified by the environment. LinkCommand += LinkEnvironment.Config.AdditionalArguments; if (!bIsBuildingLibrary) { // Fix the paths for third party libs foreach (string Library in ThirdPartyLibraries) { string LibraryFileName = Path.GetFileName(Library); LinkCommand += "; " + ToolchainDir + "install_name_tool -change " + LibraryFileName + " " + DylibsPath + "/" + LibraryFileName + " \"" + ConvertPath(OutputFile.AbsolutePath) + "\""; } } LinkAction.CommandArguments = "-c '" + LinkCommand + "'"; // Only execute linking on the local Mac. LinkAction.bCanExecuteRemotely = false; LinkAction.StatusDescription = Path.GetFileName(OutputFile.AbsolutePath); LinkAction.OutputEventHandler = new DataReceivedEventHandler(RemoteOutputReceivedEventHandler); LinkAction.ProducedItems.Add(RemoteOutputFile); if (!LinkEnvironment.Config.IntermediateDirectory.Exists()) { return OutputFile; } if (!bIsBuildingLibrary) { // Prepare a script that will run later, once every dylibs and the executable are created. This script will be called by action created in FixDylibDependencies() FileReference FixDylibDepsScriptPath = FileReference.Combine(LinkEnvironment.Config.LocalShadowDirectory, "FixDylibDependencies.sh"); if (!bHasWipedFixDylibScript) { if (FixDylibDepsScriptPath.Exists()) { FixDylibDepsScriptPath.Delete(); } bHasWipedFixDylibScript = true; } if (!LinkEnvironment.Config.LocalShadowDirectory.Exists()) { LinkEnvironment.Config.LocalShadowDirectory.CreateDirectory(); } StreamWriter FixDylibDepsScript = File.AppendText(FixDylibDepsScriptPath.FullName); if (LinkEnvironment.Config.bIsCrossReferenced || !Utils.IsRunningOnMono) { string EngineAndGameLibrariesString = ""; foreach (string Library in EngineAndGameLibraries) { EngineAndGameLibrariesString += " \"" + Library + "\""; } string FixDylibLine = "pushd \"" + ConvertPath(Directory.GetCurrentDirectory()) + "\" > /dev/null; "; FixDylibLine += string.Format("TIMESTAMP=`stat -n -f \"%Sm\" -t \"%Y%m%d%H%M.%S\" \"{0}\"`; ", RemoteOutputFile.AbsolutePath); FixDylibLine += LinkCommand.Replace("-undefined dynamic_lookup", EngineAndGameLibrariesString).Replace("$", "\\$"); FixDylibLine += string.Format("; touch -t $TIMESTAMP \"{0}\"; if [[ $? -ne 0 ]]; then exit 1; fi; ", RemoteOutputFile.AbsolutePath); FixDylibLine += "popd > /dev/null"; AppendMacLine(FixDylibDepsScript, FixDylibLine); } FixDylibDepsScript.Close(); // Prepare a script that will be called by FinalizeAppBundle.sh to copy all necessary third party dylibs to the app bundle // This is done this way as FinalizeAppBundle.sh script can be created before all the libraries are processed, so // at the time of it's creation we don't have the full list of third party dylibs all modules need. FileReference DylibCopyScriptPath = FileReference.Combine(LinkEnvironment.Config.IntermediateDirectory, "DylibCopy.sh"); if (!bHasWipedCopyDylibScript) { if (DylibCopyScriptPath.Exists()) { DylibCopyScriptPath.Delete(); } bHasWipedCopyDylibScript = true; } string ExistingScript = DylibCopyScriptPath.Exists() ? File.ReadAllText(DylibCopyScriptPath.FullName) : ""; StreamWriter DylibCopyScript = File.AppendText(DylibCopyScriptPath.FullName); foreach (string Library in ThirdPartyLibraries) { string CopyCommandLineEntry = string.Format("cp -f \"{0}\" \"$1.app/Contents/MacOS\"", ConvertPath(Path.GetFullPath(Library)).Replace("$", "\\$")); if (!ExistingScript.Contains(CopyCommandLineEntry)) { AppendMacLine(DylibCopyScript, CopyCommandLineEntry); } } DylibCopyScript.Close(); // For non-console application, prepare a script that will create the app bundle. It'll be run by FinalizeAppBundle action if (bIsBuildingAppBundle) { FileReference FinalizeAppBundleScriptPath = FileReference.Combine(LinkEnvironment.Config.IntermediateDirectory, "FinalizeAppBundle.sh"); StreamWriter FinalizeAppBundleScript = File.CreateText(FinalizeAppBundleScriptPath.FullName); AppendMacLine(FinalizeAppBundleScript, "#!/bin/sh"); string BinariesPath = Path.GetDirectoryName(OutputFile.AbsolutePath); BinariesPath = Path.GetDirectoryName(BinariesPath.Substring(0, BinariesPath.IndexOf(".app"))); AppendMacLine(FinalizeAppBundleScript, "cd \"{0}\"", ConvertPath(BinariesPath).Replace("$", "\\$")); string ExeName = Path.GetFileName(OutputFile.AbsolutePath); string[] ExeNameParts = ExeName.Split('-'); string GameName = ExeNameParts[0]; AppendMacLine(FinalizeAppBundleScript, "mkdir -p \"{0}.app/Contents/MacOS\"", ExeName); AppendMacLine(FinalizeAppBundleScript, "mkdir -p \"{0}.app/Contents/Resources\"", ExeName); // Copy third party dylibs by calling additional script prepared earlier AppendMacLine(FinalizeAppBundleScript, "sh \"{0}\" \"{1}\"", ConvertPath(DylibCopyScriptPath.FullName).Replace("$", "\\$"), ExeName); string IconName = "UE4"; string BundleVersion = ExeName.StartsWith("EpicGamesLauncher") ? LoadLauncherDisplayVersion() : LoadEngineDisplayVersion(); string EngineSourcePath = ConvertPath(Directory.GetCurrentDirectory()).Replace("$", "\\$"); FileReference UProjectFilePath; string CustomResourcesPath = ""; string CustomBuildPath = ""; if (!UProjectInfo.TryGetProjectFileName(GameName, out UProjectFilePath)) { 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 { Log.TraceWarning("Found {0} Target.cs files for {1} in alldir search of directory {2}", TargetFiles.Length, GameName, Directory.GetCurrentDirectory()); } } else { string ResourceParentFolderName = ExeName.StartsWith("EpicGamesLauncher") ? "Application" : GameName; CustomResourcesPath = Path.GetDirectoryName(UProjectFilePath.FullName) + "/Source/" + ResourceParentFolderName + "/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; } } if (CustomIcon != DefaultIcon) { QueueFileForBatchUpload(FileItem.GetItemByFileReference(new FileReference(CustomIcon))); CustomIcon = ConvertPath(CustomIcon); } } AppendMacLine(FinalizeAppBundleScript, "cp -f \"{0}\" \"{2}.app/Contents/Resources/{1}.icns\"", CustomIcon, GameName, ExeName); if (ExeName.StartsWith("UE4Editor")) { AppendMacLine(FinalizeAppBundleScript, "cp -f \"{0}/Runtime/Launch/Resources/Mac/UProject.icns\" \"{1}.app/Contents/Resources/UProject.icns\"", EngineSourcePath, ExeName); } string InfoPlistFile = CustomResourcesPath + "/Info.plist"; if (!File.Exists(InfoPlistFile)) { InfoPlistFile = EngineSourcePath + "/Runtime/Launch/Resources/Mac/" + (bBuildingEditor ? "Info-Editor.plist" : "Info.plist"); } else { QueueFileForBatchUpload(FileItem.GetItemByFileReference(new FileReference(InfoPlistFile))); InfoPlistFile = ConvertPath(InfoPlistFile); } AppendMacLine(FinalizeAppBundleScript, "cp -f \"{0}\" \"{1}.app/Contents/Info.plist\"", InfoPlistFile, ExeName); // Fix contents of Info.plist AppendMacLine(FinalizeAppBundleScript, "sed -i \"\" \"s/\\${0}/{1}/g\" \"{1}.app/Contents/Info.plist\"", "{EXECUTABLE_NAME}", ExeName); AppendMacLine(FinalizeAppBundleScript, "sed -i \"\" \"s/\\${0}/{1}/g\" \"{2}.app/Contents/Info.plist\"", "{APP_NAME}", GameName, ExeName); AppendMacLine(FinalizeAppBundleScript, "sed -i \"\" \"s/\\${0}/{1}/g\" \"{2}.app/Contents/Info.plist\"", "{MACOSX_DEPLOYMENT_TARGET}", MinMacOSVersion, ExeName); AppendMacLine(FinalizeAppBundleScript, "sed -i \"\" \"s/\\${0}/{1}/g\" \"{2}.app/Contents/Info.plist\"", "{ICON_NAME}", GameName, ExeName); AppendMacLine(FinalizeAppBundleScript, "sed -i \"\" \"s/\\${0}/{1}/g\" \"{2}.app/Contents/Info.plist\"", "{BUNDLE_VERSION}", BundleVersion, ExeName); // Generate PkgInfo file AppendMacLine(FinalizeAppBundleScript, "echo 'echo -n \"APPL????\"' | bash > \"{0}.app/Contents/PkgInfo\"", ExeName); // Make sure OS X knows the bundle was updated AppendMacLine(FinalizeAppBundleScript, "touch -c \"{0}.app\"", ExeName); FinalizeAppBundleScript.Close(); // copy over some needed files // @todo mac: Make a QueueDirectoryForBatchUpload QueueFileForBatchUpload(FileItem.GetItemByFileReference(new FileReference("../../Engine/Source/Runtime/Launch/Resources/Mac/" + GameName + ".icns"))); QueueFileForBatchUpload(FileItem.GetItemByFileReference(new FileReference("../../Engine/Source/Runtime/Launch/Resources/Mac/UProject.icns"))); QueueFileForBatchUpload(FileItem.GetItemByFileReference(new FileReference("../../Engine/Source/Runtime/Launch/Resources/Mac/Info.plist"))); QueueFileForBatchUpload(FileItem.GetItemByFileReference(FileReference.Combine(LinkEnvironment.Config.IntermediateDirectory, "DylibCopy.sh"))); } } // For Mac, generate the dSYM file if the config file is set to do so if ((BuildConfiguration.bGeneratedSYMFile == true || BuildConfiguration.bUsePDBFiles == true) && (!bIsBuildingLibrary || LinkEnvironment.Config.bIsBuildingDLL)) { Log.TraceInformation("Generating dSYM file for {0} - this will add some time to your build...", Path.GetFileName(OutputFile.AbsolutePath)); RemoteOutputFile = GenerateDebugInfo(OutputFile); } return RemoteOutputFile; } FileItem FixDylibDependencies(LinkEnvironment LinkEnvironment, FileItem Executable) { Action LinkAction = new Action(ActionType.Link); LinkAction.WorkingDirectory = UnrealBuildTool.EngineSourceDirectory.FullName; LinkAction.CommandPath = "/bin/sh"; LinkAction.CommandDescription = ""; // Call the FixDylibDependencies.sh script which will link the dylibs and the main executable, this time proper ones, as it's called // once all are already created, so the cross dependency problem no longer prevents linking. // The script is deleted after it's executed so it's empty when we start appending link commands for the next executable. FileItem FixDylibDepsScript = FileItem.GetItemByFileReference(FileReference.Combine(LinkEnvironment.Config.LocalShadowDirectory, "FixDylibDependencies.sh")); FileItem RemoteFixDylibDepsScript = LocalToRemoteFileItem(FixDylibDepsScript, true); LinkAction.CommandArguments = "-c 'chmod +x \"" + RemoteFixDylibDepsScript.AbsolutePath + "\"; \"" + RemoteFixDylibDepsScript.AbsolutePath + "\"; if [[ $? -ne 0 ]]; then exit 1; fi; "; if (BuildHostPlatform.Current.Platform != UnrealTargetPlatform.Mac) { LinkAction.ActionHandler = new Action.BlockingActionHandler(RPCUtilHelper.RPCActionHandler); } // Make sure this action is executed after all the dylibs and the main executable are created foreach (FileItem Dependency in BundleDependencies) { LinkAction.PrerequisiteItems.Add(Dependency); } BundleDependencies.Clear(); LinkAction.StatusDescription = string.Format("Fixing dylib dependencies for {0}", Path.GetFileName(Executable.AbsolutePath)); LinkAction.bCanExecuteRemotely = false; FileItem OutputFile = FileItem.GetItemByFileReference(FileReference.Combine(LinkEnvironment.Config.LocalShadowDirectory, Path.GetFileNameWithoutExtension(Executable.AbsolutePath) + ".link")); FileItem RemoteOutputFile = LocalToRemoteFileItem(OutputFile, false); LinkAction.CommandArguments += "echo \"Dummy\" >> \"" + RemoteOutputFile.AbsolutePath + "\""; LinkAction.CommandArguments += "'"; LinkAction.ProducedItems.Add(RemoteOutputFile); return RemoteOutputFile; } private static Dictionary DebugOutputMap = new Dictionary(); static public void RPCDebugInfoActionHandler(Action Action, out int ExitCode, out string Output) { RPCUtilHelper.RPCActionHandler(Action, out ExitCode, out Output); if (DebugOutputMap.ContainsKey(Action)) { if (ExitCode == 0) { RPCUtilHelper.CopyDirectory(Action.ProducedItems[0].AbsolutePath, DebugOutputMap[Action], RPCUtilHelper.ECopyOptions.None); } DebugOutputMap.Remove(Action); } } /// /// Generates debug info for a given executable /// /// FileItem describing the executable or dylib to generate debug info for public FileItem GenerateDebugInfo(FileItem MachOBinary) { // Make a file item for the source and destination files string FullDestPath = Path.ChangeExtension(MachOBinary.AbsolutePath, ".dSYM"); FileItem OutputFile = FileItem.GetItemByPath(FullDestPath); FileItem DestFile = LocalToRemoteFileItem(OutputFile, false); FileItem InputFile = LocalToRemoteFileItem(MachOBinary, false); // Delete on the local machine if (Directory.Exists(OutputFile.AbsolutePath)) { Directory.Delete(OutputFile.AbsolutePath, true); } // Make the compile action Action GenDebugAction = new Action(ActionType.GenerateDebugInfo); if (BuildHostPlatform.Current.Platform != UnrealTargetPlatform.Mac) { DebugOutputMap.Add(GenDebugAction, OutputFile.AbsolutePath); GenDebugAction.ActionHandler = new Action.BlockingActionHandler(MacToolChain.RPCDebugInfoActionHandler); } GenDebugAction.WorkingDirectory = GetMacDevSrcRoot(); GenDebugAction.CommandPath = "sh"; // Deletes ay existing file on the building machine, // note that the source and dest are switched from a copy command GenDebugAction.CommandArguments = string.Format("-c 'rm -rf \"{2}\"; \"{0}\"dsymutil -f \"{1}\" -o \"{2}\"'", ToolchainDir, InputFile.AbsolutePath, DestFile.AbsolutePath); GenDebugAction.PrerequisiteItems.Add(InputFile); GenDebugAction.ProducedItems.Add(DestFile); GenDebugAction.StatusDescription = GenDebugAction.CommandArguments; GenDebugAction.bCanExecuteRemotely = false; return DestFile; } /// /// Creates app bundle for a given executable /// /// FileItem describing the executable to generate app bundle for FileItem FinalizeAppBundle(LinkEnvironment LinkEnvironment, FileItem Executable, FileItem FixDylibOutputFile) { // 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); FileItem RemoteDestFile = LocalToRemoteFileItem(DestFile, false); // Make the compile action Action FinalizeAppBundleAction = new Action(ActionType.CreateAppBundle); FinalizeAppBundleAction.WorkingDirectory = GetMacDevSrcRoot(); // Path.GetFullPath("."); FinalizeAppBundleAction.CommandPath = "/bin/sh"; FinalizeAppBundleAction.CommandDescription = ""; // make path to the script FileItem BundleScript = FileItem.GetItemByFileReference(FileReference.Combine(LinkEnvironment.Config.IntermediateDirectory, "FinalizeAppBundle.sh")); FileItem RemoteBundleScript = LocalToRemoteFileItem(BundleScript, true); if (BuildHostPlatform.Current.Platform != UnrealTargetPlatform.Mac) { FinalizeAppBundleAction.ActionHandler = new Action.BlockingActionHandler(RPCUtilHelper.RPCActionHandler); } FinalizeAppBundleAction.CommandArguments = "\"" + RemoteBundleScript.AbsolutePath + "\""; FinalizeAppBundleAction.PrerequisiteItems.Add(FixDylibOutputFile); FinalizeAppBundleAction.ProducedItems.Add(RemoteDestFile); FinalizeAppBundleAction.StatusDescription = string.Format("Finalizing app bundle: {0}.app", Path.GetFileName(Executable.AbsolutePath)); FinalizeAppBundleAction.bCanExecuteRemotely = false; return RemoteDestFile; } FileItem CopyBundleResource(UEBuildBundleResource Resource, FileItem Executable) { Action CopyAction = new Action(ActionType.CreateAppBundle); CopyAction.WorkingDirectory = GetMacDevSrcRoot(); // Path.GetFullPath("."); CopyAction.CommandPath = "/bin/sh"; CopyAction.CommandDescription = ""; string BundlePath = Executable.AbsolutePath.Substring(0, Executable.AbsolutePath.IndexOf(".app") + 4); string SourcePath = Path.Combine(Path.GetFullPath("."), Resource.ResourcePath); string TargetPath = Path.Combine(BundlePath, "Contents", Resource.BundleContentsSubdir, Path.GetFileName(Resource.ResourcePath)); FileItem TargetItem; if (BuildHostPlatform.Current.Platform == UnrealTargetPlatform.Mac) { TargetItem = FileItem.GetItemByPath(TargetPath); } else { TargetItem = FileItem.GetRemoteItemByPath(TargetPath, RemoteToolChainPlatform); } CopyAction.CommandArguments = string.Format("-c 'cp -f -R \"{0}\" \"{1}\"; touch -c \"{2}\"'", ConvertPath(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; if (BuildHostPlatform.Current.Platform != UnrealTargetPlatform.Mac) { CopyAction.ActionHandler = new Action.BlockingActionHandler(RPCUtilHelper.RPCActionHandler); } if (Directory.Exists(Resource.ResourcePath)) { foreach (string ResourceFile in Directory.GetFiles(Resource.ResourcePath, "*", SearchOption.AllDirectories)) { QueueFileForBatchUpload(FileItem.GetItemByFileReference(new FileReference(ResourceFile))); } } else { QueueFileForBatchUpload(FileItem.GetItemByFileReference(new FileReference(SourcePath))); } return TargetItem; } public override void SetupBundleDependencies(List Binaries, string GameName) { base.SetupBundleDependencies(Binaries, GameName); foreach (UEBuildBinary Binary in Binaries) { BundleDependencies.Add(FileItem.GetItemByFileReference(Binary.Config.OutputFilePath)); } } public override void FixBundleBinariesPaths(UEBuildTarget Target, List Binaries) { base.FixBundleBinariesPaths(Target, Binaries); string BundleContentsPath = Target.OutputPath.FullName + ".app/Contents/"; foreach (UEBuildBinary Binary in Binaries) { string BinaryFileName = Path.GetFileName(Binary.Config.OutputFilePath.FullName); if (BinaryFileName.EndsWith(".dylib")) { // Only dylibs from the same folder as the executable should be moved to the bundle. UE4Editor-*Game* dylibs and plugins will be loaded // from their Binaries/Mac folders. string DylibDir = Path.GetDirectoryName(Path.GetFullPath(Binary.Config.OutputFilePath.FullName)); string ExeDir = Path.GetDirectoryName(Path.GetFullPath(Target.OutputPath.FullName)); if (DylibDir.StartsWith(ExeDir)) { // get the subdir, which is the DylibDir - ExeDir string SubDir = DylibDir.Replace(ExeDir, ""); Binary.Config.OutputFilePaths[0] = new FileReference(BundleContentsPath + "MacOS" + SubDir + "/" + BinaryFileName); } } else if (!BinaryFileName.EndsWith(".a") && !Binary.Config.OutputFilePath.FullName.Contains(".app/Contents/MacOS/")) // Binaries can contain duplicates { Binary.Config.OutputFilePaths[0] += ".app/Contents/MacOS/" + BinaryFileName; } } } static private DirectoryReference BundleContentsDirectory; public override void ModifyBuildProducts(UEBuildBinary Binary, Dictionary BuildProducts) { // The cross-platform code adds .dSYMs for static libraries, which is just wrong, so // eliminate them here for now. string DebugExtension = UEBuildPlatform.GetBuildPlatform(Binary.Target.Platform).GetDebugInfoExtension(Binary.Config.Type); if (DebugExtension == ".dSYM") { KeyValuePair[] BuildProductsArray = BuildProducts.ToArray(); foreach (KeyValuePair BuildProductPair in BuildProductsArray) { if (BuildProductPair.Key.HasExtension(DebugExtension)) { BuildProducts.Remove(BuildProductPair.Key); } } foreach (KeyValuePair BuildProductPair in BuildProductsArray) { if (BuildProductPair.Value == BuildProductType.Executable || BuildProductPair.Value == BuildProductType.DynamicLibrary) { BuildProducts.Add(BuildProductPair.Key.ChangeExtension(DebugExtension), BuildProductType.SymbolFile); } } } if (Binary.Target.GlobalLinkEnvironment.Config.bIsBuildingConsoleApplication) { return; } if (BundleContentsDirectory == null && Binary.Config.Type == UEBuildBinaryType.Executable) { BundleContentsDirectory = Binary.Config.OutputFilePath.Directory.ParentDirectory; } // We need to know what third party dylibs would be copied to the bundle if (Binary.Config.Type != UEBuildBinaryType.StaticLibrary) { var Modules = Binary.GetAllDependencyModules(bIncludeDynamicallyLoaded: false, bForceCircular: false); var BinaryLinkEnvironment = Binary.Target.GlobalLinkEnvironment.DeepCopy(); var BinaryDependencies = new List(); var LinkEnvironmentVisitedModules = new HashSet(); foreach (var Module in Modules) { Module.SetupPrivateLinkEnvironment(Binary, BinaryLinkEnvironment, BinaryDependencies, LinkEnvironmentVisitedModules); } foreach (string AdditionalLibrary in BinaryLinkEnvironment.Config.AdditionalLibraries) { string LibName = Path.GetFileName(AdditionalLibrary); if (LibName.StartsWith("lib")) { if (Path.GetExtension(AdditionalLibrary) == ".dylib" && BundleContentsDirectory != null) { FileReference Entry = FileReference.Combine(BundleContentsDirectory, "MacOS", LibName); BuildProducts.Add(Entry, BuildProductType.DynamicLibrary); } } } foreach (UEBuildBundleResource Resource in BinaryLinkEnvironment.Config.AdditionalBundleResources) { 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 { BuildProducts.Add(FileReference.Combine(BundleContentsDirectory, Resource.BundleContentsSubdir, Path.GetFileName(Resource.ResourcePath)), BuildProductType.RequiredResource); } } } if (Binary.Config.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); if (Binary.Target.TargetType == TargetRules.TargetType.Editor) { BuildProducts.Add(FileReference.Combine(BundleContentsDirectory, "Resources/UE4Editor.icns"), BuildProductType.RequiredResource); BuildProducts.Add(FileReference.Combine(BundleContentsDirectory, "Resources/UProject.icns"), BuildProductType.RequiredResource); } else { BuildProducts.Add(FileReference.Combine(BundleContentsDirectory, "Resources/" + Binary.Target.TargetName + ".icns"), BuildProductType.RequiredResource); } } } // @todo Mac: Full implementation. public override void CompileCSharpProject(CSharpEnvironment CompileEnvironment, FileReference ProjectFileName, FileReference DestinationFile) { string ProjectDirectory = Path.GetDirectoryName(ProjectFileName.FullName); if (BuildHostPlatform.Current.Platform != UnrealTargetPlatform.Mac) { RPCUtilHelper.CopyFile(ProjectFileName.FullName, ConvertPath(ProjectFileName.FullName), true); RPCUtilHelper.CopyFile("Engine/Source/Programs/DotNETCommon/MetaData.cs", ConvertPath("Engine/Source/Programs/DotNETCommon/MetaData.cs"), true); string[] FileList = Directory.GetFiles(ProjectDirectory, "*.cs", SearchOption.AllDirectories); foreach (string File in FileList) { RPCUtilHelper.CopyFile(File, ConvertPath(File), true); } } string XBuildArgs = "/verbosity:quiet /nologo /target:Rebuild /property:Configuration=Development /property:Platform=AnyCPU " + ProjectFileName.GetFileName(); if (BuildHostPlatform.Current.Platform != UnrealTargetPlatform.Mac) { RPCUtilHelper.Command(ConvertPath(ProjectDirectory), "xbuild", XBuildArgs, null); } else { Process XBuildProcess = new Process(); XBuildProcess.StartInfo.WorkingDirectory = ProjectDirectory; XBuildProcess.StartInfo.FileName = "sh"; XBuildProcess.StartInfo.Arguments = "-c 'xbuild " + XBuildArgs + " |grep -i error; if [ $? -ne 1 ]; then exit 1; else exit 0; fi'"; XBuildProcess.OutputDataReceived += new DataReceivedEventHandler(OutputReceivedDataEventHandler); XBuildProcess.ErrorDataReceived += new DataReceivedEventHandler(OutputReceivedDataEventHandler); Utils.RunLocalProcess(XBuildProcess); } } public override void PreBuildSync() { base.PreBuildSync(); } public override void PostBuildSync(UEBuildTarget InTarget) { if (BuildHostPlatform.Current.Platform != UnrealTargetPlatform.Mac) { List BuiltBinaries = new List(); foreach (UEBuildBinary Binary in InTarget.AppBinaries) { BuiltBinaries.Add(Path.GetFullPath(Binary.ToString())); } string IntermediateDirectory = InTarget.EngineIntermediateDirectory.FullName; if (!Directory.Exists(IntermediateDirectory)) { IntermediateDirectory = Path.GetFullPath("../../" + InTarget.AppName + "/Intermediate/Build/Mac/" + InTarget.AppName + "/" + InTarget.Configuration); if (!Directory.Exists(IntermediateDirectory)) { IntermediateDirectory = Path.GetFullPath("../../Engine/Intermediate/Build/Mac/" + InTarget.AppName + "/" + InTarget.Configuration); } } string FixDylibDepsScript = Path.Combine(IntermediateDirectory, "FixDylibDependencies.sh"); string FinalizeAppBundleScript = Path.Combine(IntermediateDirectory, "FinalizeAppBundle.sh"); string RemoteWorkingDir = ""; bool bIsStaticLibrary = InTarget.OutputPath.HasExtension(".a"); if (!bIsStaticLibrary) { // Copy the command scripts to the intermediate on the target Mac. string RemoteFixDylibDepsScript = ConvertPath(Path.GetFullPath(FixDylibDepsScript)); RemoteFixDylibDepsScript = RemoteFixDylibDepsScript.Replace("../../../../", "../../"); RPCUtilHelper.CopyFile(Path.GetFullPath(FixDylibDepsScript), RemoteFixDylibDepsScript, true); if (!InTarget.GlobalLinkEnvironment.Config.bIsBuildingConsoleApplication) { string RemoteFinalizeAppBundleScript = ConvertPath(Path.GetFullPath(FinalizeAppBundleScript)); RemoteFinalizeAppBundleScript = RemoteFinalizeAppBundleScript.Replace("../../../../", "../../"); RPCUtilHelper.CopyFile(Path.GetFullPath(FinalizeAppBundleScript), RemoteFinalizeAppBundleScript, true); } // run it remotely RemoteWorkingDir = ConvertPath(Path.GetDirectoryName(Path.GetFullPath(FixDylibDepsScript))); Log.TraceInformation("Running FixDylibDependencies.sh..."); Hashtable Results = RPCUtilHelper.Command(RemoteWorkingDir, "/bin/sh", "FixDylibDependencies.sh", null); if (Results != null) { string Result = (string)Results["CommandOutput"]; if (Result != null) { Log.TraceInformation(Result); } } if (!InTarget.GlobalLinkEnvironment.Config.bIsBuildingConsoleApplication) { Log.TraceInformation("Running FinalizeAppBundle.sh..."); Results = RPCUtilHelper.Command(RemoteWorkingDir, "/bin/sh", "FinalizeAppBundle.sh", null); if (Results != null) { string Result = (string)Results["CommandOutput"]; if (Result != null) { Log.TraceInformation(Result); } } } } // If it is requested, send the app bundle back to the platform executing these commands. if (BuildConfiguration.bCopyAppBundleBackToDevice) { Log.TraceInformation("Copying binaries back to this device..."); try { string BinaryDir = InTarget.OutputPath.Directory + "\\"; if (BinaryDir.EndsWith(InTarget.AppName + "\\Binaries\\Mac\\") && InTarget.TargetType != TargetRules.TargetType.Game) { BinaryDir = BinaryDir.Replace(InTarget.TargetType.ToString(), "Game"); } string RemoteBinariesDir = ConvertPath(BinaryDir); string LocalBinariesDir = BinaryDir; // Get the app bundle's name string AppFullName = InTarget.AppName; if (InTarget.Configuration != InTarget.Rules.UndecoratedConfiguration) { AppFullName += "-" + InTarget.Platform.ToString(); AppFullName += "-" + InTarget.Configuration.ToString(); } if (!InTarget.GlobalLinkEnvironment.Config.bIsBuildingConsoleApplication) { AppFullName += ".app"; } List NotBundledBinaries = new List(); foreach (string BinaryPath in BuiltBinaries) { if (InTarget.GlobalLinkEnvironment.Config.bIsBuildingConsoleApplication || bIsStaticLibrary || !BinaryPath.StartsWith(LocalBinariesDir + AppFullName)) { NotBundledBinaries.Add(BinaryPath); } } // Zip the app bundle for transferring. if (!InTarget.GlobalLinkEnvironment.Config.bIsBuildingConsoleApplication && !bIsStaticLibrary) { string ZipCommand = "zip -0 -r -y -T \"" + AppFullName + ".zip\" \"" + AppFullName + "\""; RPCUtilHelper.Command(RemoteBinariesDir, ZipCommand, "", null); // Copy the AppBundle back to the source machine string LocalZipFileLocation = LocalBinariesDir + AppFullName + ".zip "; string RemoteZipFileLocation = RemoteBinariesDir + AppFullName + ".zip"; RPCUtilHelper.CopyFile(RemoteZipFileLocation, LocalZipFileLocation, false); // Extract the copied app bundle (in zip format) to the local binaries directory using (ZipFile AppBundleZip = ZipFile.Read(LocalZipFileLocation)) { foreach (ZipEntry Entry in AppBundleZip) { Entry.Extract(LocalBinariesDir, ExtractExistingFileAction.OverwriteSilently); } } // Delete the zip as we no longer need/want it. File.Delete(LocalZipFileLocation); RPCUtilHelper.Command(RemoteBinariesDir, "rm -f \"" + AppFullName + ".zip\"", "", null); } if (NotBundledBinaries.Count > 0) { foreach (string BinaryPath in NotBundledBinaries) { RPCUtilHelper.CopyFile(ConvertPath(BinaryPath), BinaryPath, false); } } Log.TraceInformation("Copied binaries successfully."); } catch (Exception) { Log.TraceInformation("Copying binaries back to this device failed."); } } } base.PostBuildSync(InTarget); } public override ICollection PostBuild(FileItem Executable, LinkEnvironment BinaryLinkEnvironment) { var OutputFiles = base.PostBuild(Executable, BinaryLinkEnvironment); if (BinaryLinkEnvironment.Config.bIsBuildingLibrary) { return OutputFiles; } foreach (UEBuildBundleResource Resource in BinaryLinkEnvironment.Config.AdditionalBundleResources) { OutputFiles.Add(CopyBundleResource(Resource, Executable)); } // If building for Mac on a Mac, use actions to finalize the builds (otherwise, we use Deploy) if (BuildHostPlatform.Current.Platform != UnrealTargetPlatform.Mac) { return OutputFiles; } if (BinaryLinkEnvironment.Config.bIsBuildingDLL || BinaryLinkEnvironment.Config.bIsBuildingLibrary) { return OutputFiles; } FileItem FixDylibOutputFile = FixDylibDependencies(BinaryLinkEnvironment, Executable); OutputFiles.Add(FixDylibOutputFile); if (!BinaryLinkEnvironment.Config.bIsBuildingConsoleApplication) { OutputFiles.Add(FinalizeAppBundle(BinaryLinkEnvironment, Executable, FixDylibOutputFile)); } return OutputFiles; } public override void StripSymbols(string SourceFileName, string TargetFileName) { SetupXcodePaths(false); StripSymbolsWithXcode(SourceFileName, TargetFileName, ToolchainDir); } }; }