Files
Josh Adams eb3569c187 - Fixed various blocking issues with making Mac/IOS builds that can be uploaded to AppStore in Xcode
* Missing ThinApp.sh
  * Missing some quotes around a variable in GenerateUniversalDSYM.sh
  * Correctly filter out the stub .app's and such when copying the Staged data directory into the .app
  * Disabled the Mac's Sign To Run Locally option by default, as that causes Xcode's Validation/Upload to not have the team name embedded in it, causing a hassle while pushing up to AppStore/TestFlight
  * Fixed the PRODUCT_NAME for BP projects
  * Made CrashReportClient be a sandboxed app that inherits from parent
  * Fix Hybrid apps to check all platforms before in the project generator, so that if a project needs a temp target for IOS (via a plugin)
  * Disabled the LoginFlow module from OnlineFramework plugin - this was causing issues with having CEF and EpicWebHelper embedded into a sandboxed .app, and LoginFlow isn't seemingly actually used by anything
  * Set the no-encryption setting in the template Mac plist to make uploading to TestFlight/AppStore easier
  * Fixed editor to not compile as universal when using -distribution, only the client should be universal
  * Further improvements to stripping out nested .app's in a final .app (remove them from the staged directory helps)
  * Changed how the Mac app name is displayed, since the .app name itself is shown in the Finder,  unlike IOS where the CFBundleName is shown (the archived .app name in the .xcarchive is named by a project setting, falling back to the .uproject name if not set)
  * Disabled the SignExecutables function on Modern because they attempt to sign the wrong .apps, and one is no longer (previously it was uselessly signing .apps, but now it throws an error due to changes for the third item in this list)
  * Legacy Xcode mode is forced with Window's Remote Mac builds, so use the old legacy code when running under windows
  * Added a third party .cpp file to get copied to remote macs
  * Fix Tools -> Open Xcode not working with modern Xcode

#jira UE-197465

[CL 28723100 by Josh Adams in 5.3 branch]
2023-10-12 15:15:43 -04:00

273 lines
8.6 KiB
C#

// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using EpicGames.Core;
using Microsoft.Extensions.Logging;
namespace UnrealBuildTool.XcodeProjectXcconfig
{
static class StringBuilderExtensions
{
public static void WriteLine(this StringBuilder SB, string Line = "")
{
SB.Append(Line);
SB.Append(ProjectFileGenerator.NewLine);
}
public static void WriteLine(this StringBuilder SB, int Indent, string Line = "")
{
SB.Append(new String('\t', Indent));
SB.Append(Line);
SB.Append(ProjectFileGenerator.NewLine);
}
}
static class XcodeUtils
{
// null platform means use the old way, with multiple platforms per project
private static string Suffix(UnrealTargetPlatform? Platform)
{
return Platform == null ? "" : $" ({Platform})";
}
public static DirectoryReference ProjectDirPathForPlatform(DirectoryReference ProjectFilePath, UnrealTargetPlatform? Platform)
{
FileReference ProjectAsFile = new FileReference(ProjectFilePath.FullName);
return DirectoryReference.Combine(ProjectAsFile.Directory, $"{ProjectAsFile.GetFileNameWithoutExtension()}{Suffix(Platform)}{ProjectAsFile.GetExtension()}");
}
private static IEnumerable<UnrealTargetConfiguration> GetSupportedConfigurations()
{
return new UnrealTargetConfiguration[] {
UnrealTargetConfiguration.Debug,
UnrealTargetConfiguration.DebugGame,
UnrealTargetConfiguration.Development,
UnrealTargetConfiguration.Test,
UnrealTargetConfiguration.Shipping
};
}
public static bool ShouldIncludeProjectInWorkspace(ProjectFile Proj, ILogger Logger)
{
// since IOS/TVOS don't have the UnrealEditor project as valid, force it so that we get the source code
// this is likely temporary until we can put source code into UnrealGame
if (Proj.ProjectFilePath.GetFileNameWithoutAnyExtensions() == "UnrealEditor")
{
return true;
}
foreach (Project ProjectTarget in Proj.ProjectTargets)
{
foreach (UnrealTargetPlatform Platform in XcodeProjectFileGenerator.XcodePlatforms)
{
foreach (UnrealTargetConfiguration Config in GetSupportedConfigurations())
{
if (MSBuildProjectFile.IsValidProjectPlatformAndConfiguration(ProjectTarget, Platform, Config, Logger))
{
return true;
}
}
}
}
return false;
}
// cache for the below function
static Dictionary<string, UnrealArchitectures> CachedMacProjectArchitectures = new();
/// <summary>
/// Returns the Mac architectures that should be configured for the provided target. If the target has a project we'll adhere
/// to whether it's set as Intel/Universal/Apple unless the type is denied (pretty much just Editor)
///
/// If the target has no project we'll support allow-listed targets for installed builds and all non-editor architectures
/// for source builds. Not all programs are going to compile for Apple Silicon, but being able to build and fail is useful...
/// </summary>
/// <param name="TargetName">The target we're generatin forg</param>
/// <param name="InProjectFile">Path to the project file, or null if the target has no project</param>
/// <returns></returns>
public static UnrealArchitectures GetSupportedMacArchitectures(string TargetName, FileReference? InProjectFile)
{
// All architectures supported
UnrealArchitectures AllArchitectures = new(new[] { UnrealArch.X64, UnrealArch.Arm64 });
// Add a way on the command line of forcing a project file with all architectures (there isn't a good way to let this be
// set and checked where we can access it).
bool ForceAllArchitectures = Environment.GetCommandLineArgs().Contains("AllArchitectures", StringComparer.OrdinalIgnoreCase);
if (ForceAllArchitectures)
{
return AllArchitectures;
}
UnrealArchitectures Arches;
lock (CachedMacProjectArchitectures)
{
// First time seeing this target?
if (!CachedMacProjectArchitectures.ContainsKey(TargetName))
{
CachedMacProjectArchitectures[TargetName] = UnrealArchitectureConfig.ForPlatform(UnrealTargetPlatform.Mac).ProjectSupportedArchitectures(InProjectFile, TargetName);
}
Arches = CachedMacProjectArchitectures[TargetName];
}
return Arches;
}
public static void FindPlistId(MetadataItem PlistItem, string Key, ref string? BundleId)
{
if (PlistItem.File == null || !FileReference.Exists(PlistItem.File))
{
return;
}
string Identifier = Plist($"Print :{Key}", PlistItem.File.FullName);
// handle error
if (String.IsNullOrEmpty(Identifier) || Identifier.StartsWith("Print:"))
{
if (PlistItem.Mode == MetadataMode.UsePremade)
{
Log.TraceErrorOnce($"Premade .plist file '{PlistItem.File}' was found, but it did not contain {Key} (Key is missing or value is empty)");
}
}
else
{
BundleId = Identifier;
}
}
private static string? ActivePlistFile;
public static void SetActivePlistFile(string PlistFile)
{
ActivePlistFile = PlistFile;
}
public static string Plist(string Command, string PlistFile)
{
Command = Command.Replace("\"", "\\\"");
return Utils.RunLocalProcessAndReturnStdOut("/usr/libexec/PlistBuddy", $"-c \"{Command}\" \"{PlistFile}\"");
}
public static string Plist(string Command)
{
return Plist(Command, ActivePlistFile!);
}
public static void PlistSetAdd(string Entry, string Value, string Type = "string")
{
string AddOutput = Plist($"Add {Entry} {Type} {Value}");
// error will be non-empty string
if (!String.IsNullOrEmpty(AddOutput))
{
Plist($"Set {Entry} {Value}");
}
}
public static bool PlistSetUpdate(string Entry, string Value)
{
// see if the setting is already there
string ExistingSetting = Plist($"Print {Entry}");
// Print errors start with Print
if (!ExistingSetting.StartsWith("Print:") && ExistingSetting != Value)
{
Plist($"Set {Entry} {Value}");
return true;
}
return false;
}
public static IEnumerable<string> PlistArray(string Entry)
{
return Plist($"Print {Entry}")
.Replace("Array {", "")
.Replace("}", "")
.Trim()
.ReplaceLineEndings()
.Split(Environment.NewLine)
.Select(x => x.Trim());
}
public static List<string> PlistObjects()
{
List<string> Result = new();
IEnumerable<string> Lines = Plist("print :objects")
.ReplaceLineEndings()
.Split(Environment.NewLine);
Regex Regex = new Regex("^\\s*(\\S*) = Dict {$");
foreach (string Line in Lines)
{
Match Match = Regex.Match(Line);
if (Match.Success)
{
Result.Add(Match.Groups[1].Value);
}
}
return Result;
}
public static string? PlistFixPath(string Entry, string RelativeToProject)
{
string ExistingPath = Plist($"Print {Entry}");
// skip of errors, or it's an absolute path
if (!ExistingPath.StartsWith("Print:") && !ExistingPath.StartsWith("/"))
{
// fixup the path to be relative to new project instead of old
string FixedPath = Utils.CollapseRelativeDirectories(Path.Combine(RelativeToProject, ExistingPath));
// and set it back
Plist($"Set {Entry} {FixedPath}");
return FixedPath;
}
return null;
}
public static List<string> GetSupportedOrientations(ConfigHierarchy Ini)
{
List<string> Orientations = new();
bool bSupported = true;
if (Ini.TryGetValue("/Script/IOSRuntimeSettings.IOSRuntimeSettings", "bSupportsPortraitOrientation", out bSupported) && bSupported)
{
Orientations.Add("UIInterfaceOrientationPortrait");
}
if (Ini.TryGetValue("/Script/IOSRuntimeSettings.IOSRuntimeSettings", "bSupportsUpsideDownOrientation", out bSupported) && bSupported)
{
Orientations.Add("UIInterfaceOrientationPortraitUpsideDown");
}
string? PreferredLandscapeOrientation;
Ini.TryGetValue("/Script/IOSRuntimeSettings.IOSRuntimeSettings", "PreferredLandscapeOrientation", out PreferredLandscapeOrientation);
bool bSupportsLandscapeLeft = false;
Ini.TryGetValue("/Script/IOSRuntimeSettings.IOSRuntimeSettings", "bSupportsLandscapeLeftOrientation", out bSupportsLandscapeLeft);
bool bSupportsLandscapeRight = false;
Ini.TryGetValue("/Script/IOSRuntimeSettings.IOSRuntimeSettings", "bSupportsLandscapeRightOrientation", out bSupportsLandscapeRight);
if (bSupportsLandscapeLeft && PreferredLandscapeOrientation == "LandscapeLeft")
{
Orientations.Add("UIInterfaceOrientationLandscapeLeft");
}
if (bSupportsLandscapeRight)
{
Orientations.Add("UIInterfaceOrientationLandscapeRight");
}
if (bSupportsLandscapeLeft && PreferredLandscapeOrientation != "LandscapeLeft")
{
Orientations.Add("UIInterfaceOrientationLandscapeLeft");
}
return Orientations;
}
}
}