2022-05-30 09:17:09 -04:00
// Copyright Epic Games, Inc. All Rights Reserved.
2022-05-27 18:34:49 -04:00
using System ;
using System.Collections.Generic ;
using System.IO ;
using System.Xml ;
using System.Xml.XPath ;
using System.Xml.Linq ;
using System.Linq ;
using System.Text ;
using EpicGames.Core ;
using UnrealBuildBase ;
using Microsoft.Extensions.Logging ;
2022-08-08 11:11:04 -04:00
using System.Text.RegularExpressions ;
using System.Diagnostics.CodeAnalysis ;
/ * * * * *
Here ' s how this works :
* An XcodeProjectFile ( subclass of generic ProjectFile class ) is created , along with it - UnrealData and XcodeFileCollection objects are made
* High level code calls AddModule ( ) which this code will use to cache information about the Modules in the project ( including build settings , etc )
* These are used to determine what source files can be indexed together ( we use native xcode code compilation for indexing , so we make compiling succesful for best index )
* A few # defines are removed or modified ( FOO_API , etc ) which would otherwise make every module a separate target
* High level code then calls WriteProjectFile ( ) which is the meat of all this
* This code then creates a hierarchy / reference - chain of xcode project nodes ( an xcode project is basically a series of Guid / object pairs that reference each other )
* Then each node writes itself into the project file that is saved to disk
. xcconfig files :
* We also now use Xcconfig files for all of the build settings , instead of jamming them into the xcode project file itself
* This makes it easier to see the various settings ( you can also see them in the Xcode UI as before , but now with more read - only columns - they must be set via editing the file )
* The files are in the Xcode file browsing pane , in the an Xcconfigs folder under each project
* The files are :
* _Project - settings apply to all targets in the projectapplies to all targets ) , one for each configuration ( Debug , Development Editor , etc ) which applies to all targets
* _Debug / _DevelopmentEditor , etc - applies to all targets when building in that configuration
* _Run - applies to the native run project , which does no compiling
* _Index / SubIndex0 , etc - applies when Indexing , which uses native Xcode compiling ( these will include # defines from UBT , etc )
* There is currently no _Build xcconfig file since it ' s a makefile project and has no build setting needs
Known issues :
* No PBXFrameworksBuildPhase nodes are made
Future ideas :
* I have started working on a Template project that we merge Build / Index targets into , which will allow a licensee to setup codesigning , add extensions , etc .
* Always make a final build xcodeproj from UBT for handling the codesigning / extensions / frameworks
* Allow for non - conflicting # defines to share SubIndex targets , hopefully will greatly reduce the sub targets in UE5
* * /
2022-05-27 18:34:49 -04:00
2022-07-19 13:24:38 -04:00
namespace UnrealBuildTool.XcodeProjectXcconfig
2022-05-27 18:34:49 -04:00
{
2022-08-08 11:11:04 -04:00
static class StringBuilderExtensions
2022-05-27 18:34:49 -04:00
{
2022-08-08 11:11:04 -04:00
public static void WriteLine ( this StringBuilder SB , string Line = "" )
2022-05-27 18:34:49 -04:00
{
2022-08-08 11:11:04 -04:00
SB . Append ( Line ) ;
SB . Append ( ProjectFileGenerator . NewLine ) ;
2022-05-27 18:34:49 -04:00
}
2022-08-08 11:11:04 -04:00
public static void WriteLine ( this StringBuilder SB , int Indent , string Line = "" )
2022-05-27 18:34:49 -04:00
{
2022-08-08 11:11:04 -04:00
SB . Append ( new String ( '\t' , Indent ) ) ;
SB . Append ( Line ) ;
SB . Append ( ProjectFileGenerator . NewLine ) ;
2022-05-27 18:34:49 -04:00
}
}
2022-08-08 11:11:04 -04:00
class UnrealBuildConfig
2022-05-27 18:34:49 -04:00
{
2022-08-08 11:11:04 -04:00
public UnrealBuildConfig ( string InDisplayName , string InBuildTarget , FileReference ? InMacExecutablePath , FileReference ? InIOSExecutablePath , FileReference ? InTVOSExecutablePath ,
2022-05-27 18:34:49 -04:00
ProjectTarget ? InProjectTarget , UnrealTargetConfiguration InBuildConfig )
{
DisplayName = InDisplayName ;
MacExecutablePath = InMacExecutablePath ;
IOSExecutablePath = InIOSExecutablePath ;
TVOSExecutablePath = InTVOSExecutablePath ;
BuildTarget = InBuildTarget ;
ProjectTarget = InProjectTarget ;
BuildConfig = InBuildConfig ;
}
public string DisplayName ;
public FileReference ? MacExecutablePath ;
public FileReference ? IOSExecutablePath ;
public FileReference ? TVOSExecutablePath ;
public string BuildTarget ;
public ProjectTarget ? ProjectTarget ;
public UnrealTargetConfiguration BuildConfig ;
2022-07-19 13:24:38 -04:00
public bool bSupportsMac { get = >
XcodeProjectFileGenerator . ProjectFilePlatform . HasFlag ( XcodeProjectFileGenerator . XcodeProjectFilePlatform . Mac ) & &
( ProjectTarget = = null | | ProjectTarget . SupportedPlatforms . Contains ( UnrealTargetPlatform . Mac ) ) ;
}
public bool bSupportsIOS { get = >
XcodeProjectFileGenerator . ProjectFilePlatform . HasFlag ( XcodeProjectFileGenerator . XcodeProjectFilePlatform . iOS ) & &
( ProjectTarget = = null | | ProjectTarget . SupportedPlatforms . Contains ( UnrealTargetPlatform . IOS ) ) ;
}
public bool bSupportsTVOS { get = >
XcodeProjectFileGenerator . ProjectFilePlatform . HasFlag ( XcodeProjectFileGenerator . XcodeProjectFilePlatform . tvOS ) & &
( ProjectTarget = = null | | ProjectTarget . SupportedPlatforms . Contains ( UnrealTargetPlatform . TVOS ) ) ;
}
2022-05-27 18:34:49 -04:00
} ;
2022-08-08 11:11:04 -04:00
class UnrealBatchedFiles
2022-05-27 18:34:49 -04:00
{
2022-08-08 11:11:04 -04:00
// build settings that cause uniqueness
public string Definitions = "" ;
public FileReference ? PCHFile = null ;
public bool bEnableRTTI = false ;
// union of settings for all modules
public HashSet < string > APIDefines = new ( ) ;
public HashSet < DirectoryReference > SystemIncludePaths = new ( ) ;
public HashSet < DirectoryReference > UserIncludePaths = new ( ) ;
public List < XcodeSourceFile > Files = new ( ) ;
public List < UEBuildModuleCPP > Modules = new List < UEBuildModuleCPP > ( ) ;
public void WriteXcconfigSettings ( StringBuilder Content )
2022-05-27 18:34:49 -04:00
{
2022-08-08 11:11:04 -04:00
Content . WriteLine ( $"GCC_PREPROCESSOR_DEFINITIONS = __INTELLISENSE__ MONOLITHIC_BUILD=1 {Definitions} {string.Join(" ", APIDefines)}" ) ;
if ( PCHFile ! = null )
{
Content . WriteLine ( $"GCC_PREFIX_HEADER = {PCHFile.FullName}" ) ;
}
Content . WriteLine ( $"GCC_ENABLE_CPP_RTTI = " + ( bEnableRTTI ? "YES" : "NO" ) ) ;
2022-05-27 18:34:49 -04:00
2022-08-08 11:11:04 -04:00
string SearchPaths = string . Join ( " " , SystemIncludePaths . Select ( x = > x . FullName . Contains ( ' ' ) ? $"\" { x . FullName } \ "" : x . FullName ) ) ;
SearchPaths = SearchPaths . Replace ( "/x86_64/" , "/$(ARCHS)/" ) ;
Content . WriteLine ( $"HEADER_SEARCH_PATHS = {SearchPaths}" ) ;
2022-05-27 18:34:49 -04:00
2022-08-08 11:11:04 -04:00
SearchPaths = string . Join ( " " , UserIncludePaths . Select ( x = > x . FullName . Contains ( ' ' ) ? $"\" { x . FullName } \ "" : x . FullName ) ) ;
SearchPaths = SearchPaths . Replace ( "/x86_64/" , "/$(ARCHS)/" ) ;
Content . WriteLine ( $"USER_HEADER_SEARCH_PATHS = {SearchPaths}" ) ;
2022-07-14 13:20:15 -04:00
}
}
2022-08-08 11:11:04 -04:00
class UnrealData
2022-05-27 18:34:49 -04:00
{
2022-08-08 11:11:04 -04:00
public bool bIsAppBundle ;
public bool bHasEditorConfiguration ;
public bool bUseAutomaticSigning = false ;
2022-08-10 14:30:33 -04:00
public bool bIsMergingProjects = false ;
public bool bWriteCodeSigningSettings = true ;
2022-05-27 18:34:49 -04:00
2022-08-08 11:11:04 -04:00
public List < UnrealBuildConfig > AllConfigs = new ( ) ;
2022-07-14 13:20:15 -04:00
2022-08-08 11:11:04 -04:00
public List < UnrealExtensionInfo > AllExtensions = new ( ) ;
2022-05-27 18:34:49 -04:00
2022-08-08 11:11:04 -04:00
public List < UnrealBatchedFiles > BatchedFiles = new ( ) ;
public FileReference ? UProjectFileLocation = null ;
public FileReference ? XcodeProjectFileLocation = null ;
// settings read from project configs
public IOSProjectSettings ? IOSProjectSettings ;
public IOSProvisioningData ? IOSProvisioningData ;
public TVOSProjectSettings ? TVOSProjectSettings ;
public TVOSProvisioningData ? TVOSProvisioningData ;
public string ProductName = "" ;
2022-05-27 18:34:49 -04:00
/// <summary>
/// Used to mark the project for distribution (some platforms require this)
/// </summary>
2022-08-08 11:11:04 -04:00
public bool bForDistribution = false ;
2022-05-27 18:34:49 -04:00
/// <summary>
/// Override for bundle identifier
/// </summary>
2022-08-08 11:11:04 -04:00
public string BundleIdentifier = "" ;
2022-05-27 18:34:49 -04:00
/// <summary>
/// Override AppName
/// </summary>
2022-08-08 11:11:04 -04:00
public string AppName = "" ;
2022-05-27 18:34:49 -04:00
/// <summary>
/// Architectures supported for iOS
/// </summary>
2022-08-08 11:11:04 -04:00
public string [ ] SupportedIOSArchitectures = { "arm64" } ;
2022-05-27 18:34:49 -04:00
2022-07-14 13:20:15 -04:00
/// <summary>
2022-08-08 11:11:04 -04:00
/// UBT logger object
2022-07-14 13:20:15 -04:00
/// </summary>
2022-08-08 11:11:04 -04:00
public ILogger ? Logger ;
2022-07-14 13:20:15 -04:00
2022-08-08 11:11:04 -04:00
private XcodeProjectFile ? ProjectFile ;
public bool Initialize ( XcodeProjectFile ProjectFile , List < UnrealTargetPlatform > Platforms , List < UnrealTargetConfiguration > Configurations , ILogger Logger )
2022-05-27 18:34:49 -04:00
{
2022-08-08 11:11:04 -04:00
this . ProjectFile = ProjectFile ;
this . Logger = Logger ;
ProductName = ProjectFile . ProjectFilePath . GetFileNameWithoutAnyExtensions ( ) ;
XcodeProjectFileLocation = ProjectFile . ProjectFilePath ;
// Figure out all the desired configurations on the unreal side
AllConfigs = GetSupportedBuildConfigs ( Platforms , Configurations , Logger ) ;
// if we can't find any configs, we will fail to create a project
if ( AllConfigs . Count = = 0 )
2022-05-27 18:34:49 -04:00
{
2022-08-08 11:11:04 -04:00
return false ;
2022-05-27 18:34:49 -04:00
}
2022-08-08 11:11:04 -04:00
// find a uproject file (UE5 target won't have one)
foreach ( Project Target in ProjectFile . ProjectTargets )
2022-05-27 18:34:49 -04:00
{
2022-08-08 11:11:04 -04:00
if ( Target . UnrealProjectFilePath ! = null )
2022-05-27 18:34:49 -04:00
{
2022-08-08 11:11:04 -04:00
UProjectFileLocation = Target . UnrealProjectFilePath ;
break ;
}
}
// this project makes an app bundle (.app directory instead of a raw executable or dylib) if none of the fings make a non-appbundle
bIsAppBundle = ! AllConfigs . Any ( x = > x . ProjectTarget ! . TargetRules ! . bIsBuildingConsoleApplication | | x . ProjectTarget . TargetRules . bShouldCompileAsDLL ) ;
bHasEditorConfiguration = AllConfigs . Any ( x = > x . ProjectTarget ! . TargetRules ! . Type = = TargetType . Editor ) ;
// read config settings
if ( InstalledPlatformInfo . IsValidPlatform ( UnrealTargetPlatform . IOS , EProjectType . Code ) )
{
IOSPlatform IOSPlatform = ( ( IOSPlatform ) UEBuildPlatform . GetBuildPlatform ( UnrealTargetPlatform . IOS ) ) ;
IOSProjectSettings = IOSPlatform . ReadProjectSettings ( UProjectFileLocation ) ;
IOSProvisioningData = IOSPlatform . ReadProvisioningData ( IOSProjectSettings , bForDistribution ) ;
bUseAutomaticSigning | = IOSProjectSettings . bAutomaticSigning ;
}
if ( InstalledPlatformInfo . IsValidPlatform ( UnrealTargetPlatform . IOS , EProjectType . Code ) )
{
TVOSPlatform TVOSPlatform = ( ( TVOSPlatform ) UEBuildPlatform . GetBuildPlatform ( UnrealTargetPlatform . TVOS ) ) ;
TVOSProjectSettings = TVOSPlatform . ReadProjectSettings ( UProjectFileLocation ) ;
TVOSProvisioningData = TVOSPlatform . ReadProvisioningData ( TVOSProjectSettings , bForDistribution ) ;
bUseAutomaticSigning | = TVOSProjectSettings . bAutomaticSigning ;
}
return true ;
}
public void AddModule ( UEBuildModuleCPP Module , CppCompileEnvironment CompileEnvironment )
{
// we need to keep all _API defines, but we can gather them up to append at the end, instead of using them to compute sameness
// remove some extraneous defines that will cause every module to be unique, but we can do without
List < string > Defines = new ( ) ;
List < string > APIDefines = new ( ) ;
Regex Regex = new Regex ( "#define ([A-Z0-9_]*) ?(.*)?" ) ;
string DefinesString = "" ;
FileReference ? PCHFile = null ;
if ( CompileEnvironment . ForceIncludeFiles . Count = = 0 )
{
// if there are no ForceInclude files, then that means it's a module that forces the includes to come from a generated PCH file
// and so we will use this for definitions and uniqueness
PCHFile = CompileEnvironment . PrecompiledHeaderIncludeFilename ;
}
else
{
foreach ( string Line in File . ReadAllLines ( CompileEnvironment . ForceIncludeFiles . First ( x = > x . FullName . Contains ( "Definitions" ) ) . FullName ) )
{
Match Match = Regex . Match ( Line ) ;
if ( ! Match . Success )
2022-05-27 18:34:49 -04:00
{
2022-08-08 11:11:04 -04:00
continue ;
}
string Key = Match . Groups [ 1 ] . Value ;
string Value = Match . Groups [ 2 ] . Value ;
// if no value, then just define with no = stuff
if ( ! Match . Groups [ 2 ] . Success )
{
Defines . Add ( Key ) ;
continue ;
}
// skip some known per-module defines we don't need
if ( Key . StartsWith ( "UE_MODULE_NAME" ) | | Key . StartsWith ( "UE_PLUGIN_NAME" ) | | Key . StartsWith ( "UBT_MODULE_MANIFEST" ) )
{
continue ;
}
// these API ones are per module but still need to be defined, can be defined to nothing
else if ( Key . Contains ( "_API" ) )
{
APIDefines . Add ( $"{Key}=" ) ;
}
else
{
// if the value is normal, just add the define as is
if ( Value . All ( x = > char . IsLetterOrDigit ( x ) | | x = = '_' ) )
{
Defines . Add ( $"{Key}={Value}" ) ;
}
else
{
// escape any quotes in the define, then quote the whole thing
Value = Value . Replace ( "\"" , "\\\"" ) ;
Defines . Add ( $"{Key}=\" { Value } \ "" ) ;
}
2022-05-27 18:34:49 -04:00
}
}
2022-08-08 11:11:04 -04:00
// sort and joing them into a single string to act as a key (and happily string to put into .xcconfig)
Defines . Sort ( ) ;
DefinesString = string . Join ( " " , Defines ) ;
2022-05-27 18:34:49 -04:00
}
2022-08-08 11:11:04 -04:00
// now find a matching SubTarget, and make a new one if needed
UnrealBatchedFiles ? FileBatch = null ;
foreach ( UnrealBatchedFiles Search in BatchedFiles )
2022-05-27 18:34:49 -04:00
{
2022-08-08 11:11:04 -04:00
if ( Search . Definitions = = DefinesString & & Search . PCHFile = = PCHFile & &
Search . bEnableRTTI = = CompileEnvironment . bUseRTTI )
2022-05-27 18:34:49 -04:00
{
2022-08-08 11:11:04 -04:00
FileBatch = Search ;
break ;
2022-05-27 18:34:49 -04:00
}
2022-08-08 11:11:04 -04:00
}
if ( FileBatch = = null )
{
//FileBatch = new UnrealBatchedFiles(UnrealData, SubTargets.Count + 1);
FileBatch = new UnrealBatchedFiles ( ) ;
BatchedFiles . Add ( FileBatch ) ;
FileBatch . Definitions = DefinesString ;
FileBatch . PCHFile = PCHFile ;
FileBatch . bEnableRTTI = CompileEnvironment . bUseRTTI ;
2022-05-27 18:34:49 -04:00
}
2022-08-08 11:11:04 -04:00
FileBatch . Modules . Add ( Module ) ;
FileBatch . APIDefines . UnionWith ( APIDefines ) ;
FileBatch . SystemIncludePaths . UnionWith ( CompileEnvironment . SystemIncludePaths ) ;
FileBatch . UserIncludePaths . UnionWith ( CompileEnvironment . UserIncludePaths ) ;
2022-05-27 18:34:49 -04:00
}
2022-08-08 11:11:04 -04:00
public void FilterBuildableFiles ( XcodeFileCollection FileCollection )
2022-05-27 18:34:49 -04:00
{
2022-08-08 11:11:04 -04:00
foreach ( XcodeSourceFile File in FileCollection . BuildableFiles )
2022-05-27 18:34:49 -04:00
{
2022-08-08 11:11:04 -04:00
AddFileToBatch ( File ) ;
2022-05-27 18:34:49 -04:00
}
}
2022-08-08 11:11:04 -04:00
internal void AddFileToBatch ( XcodeSourceFile File )
2022-05-27 18:34:49 -04:00
{
2022-08-08 11:11:04 -04:00
foreach ( UnrealBatchedFiles Batch in BatchedFiles )
2022-05-27 18:34:49 -04:00
{
2022-08-08 11:11:04 -04:00
foreach ( UEBuildModuleCPP Module in Batch . Modules )
2022-05-27 18:34:49 -04:00
{
2022-08-08 11:11:04 -04:00
if ( Module . ContainsFile ( File . Reference ) )
{
Batch . Files . Add ( File ) ;
return ;
}
2022-05-27 18:34:49 -04:00
}
2022-08-08 11:11:04 -04:00
}
}
2022-05-27 18:34:49 -04:00
2022-08-08 11:11:04 -04:00
private List < UnrealBuildConfig > GetSupportedBuildConfigs ( List < UnrealTargetPlatform > Platforms , List < UnrealTargetConfiguration > Configurations , ILogger Logger )
{
List < UnrealBuildConfig > BuildConfigs = new List < UnrealBuildConfig > ( ) ;
//string ProjectName = ProjectFilePath.GetFileNameWithoutExtension();
foreach ( UnrealTargetConfiguration Configuration in Configurations )
{
if ( InstalledPlatformInfo . IsValidConfiguration ( Configuration , EProjectType . Code ) )
2022-05-27 18:34:49 -04:00
{
2022-08-08 11:11:04 -04:00
foreach ( UnrealTargetPlatform Platform in Platforms )
{
if ( InstalledPlatformInfo . IsValidPlatform ( Platform , EProjectType . Code ) & & ( Platform = = UnrealTargetPlatform . Mac | | Platform = = UnrealTargetPlatform . IOS | | Platform = = UnrealTargetPlatform . TVOS ) ) // @todo support other platforms
{
UEBuildPlatform ? BuildPlatform ;
if ( UEBuildPlatform . TryGetBuildPlatform ( Platform , out BuildPlatform ) & & ( BuildPlatform . HasRequiredSDKsInstalled ( ) = = SDKStatus . Valid ) )
{
// Check we have targets (Expected to be no Engine targets when generating for a single .uproject)
if ( ProjectFile ! . ProjectTargets . Count = = 0 & & ProjectFile ! . BaseDir ! = Unreal . EngineDirectory )
{
throw new BuildException ( $"Expecting at least one ProjectTarget to be associated with project '{XcodeProjectFileLocation}' in the TargetProjects list " ) ;
}
2022-05-27 18:34:49 -04:00
2022-08-08 11:11:04 -04:00
// Now go through all of the target types for this project
foreach ( ProjectTarget ProjectTarget in ProjectFile . ProjectTargets . OfType < ProjectTarget > ( ) )
{
if ( MSBuildProjectFile . IsValidProjectPlatformAndConfiguration ( ProjectTarget , Platform , Configuration , Logger ) )
{
// Figure out if this is a monolithic build
bool bShouldCompileMonolithic = BuildPlatform . ShouldCompileMonolithicBinary ( Platform ) ;
bShouldCompileMonolithic | = ( ProjectTarget . CreateRulesDelegate ( Platform , Configuration ) . LinkType = = TargetLinkType . Monolithic ) ;
string ConfigName = Configuration . ToString ( ) ;
if ( ProjectTarget . TargetRules ! . Type ! = TargetType . Game & & ProjectTarget . TargetRules . Type ! = TargetType . Program )
{
ConfigName + = " " + ProjectTarget . TargetRules . Type . ToString ( ) ;
}
if ( BuildConfigs . Where ( Config = > Config . DisplayName = = ConfigName ) . ToList ( ) . Count = = 0 )
{
string TargetName = ProjectTarget . TargetFilePath . GetFileNameWithoutAnyExtensions ( ) ;
// Get the output directory
DirectoryReference RootDirectory = Unreal . EngineDirectory ;
// Unique and Monolithic both need to use the target directory not the engine directory
if ( ProjectTarget . TargetRules . Type ! = TargetType . Program & & ( bShouldCompileMonolithic | | ProjectTarget . TargetRules . BuildEnvironment = = TargetBuildEnvironment . Unique ) )
{
if ( ProjectTarget . UnrealProjectFilePath ! = null )
{
RootDirectory = ProjectTarget . UnrealProjectFilePath . Directory ;
}
}
if ( ProjectTarget . TargetRules . Type = = TargetType . Program & & ProjectTarget . UnrealProjectFilePath ! = null )
{
RootDirectory = ProjectTarget . UnrealProjectFilePath . Directory ;
}
// Get the output directory
DirectoryReference OutputDirectory = DirectoryReference . Combine ( RootDirectory , "Binaries" ) ;
string ExeName = TargetName ;
if ( ! bShouldCompileMonolithic & & ProjectTarget . TargetRules . Type ! = TargetType . Program )
{
// Figure out what the compiled binary will be called so that we can point the IDE to the correct file
if ( ProjectTarget . TargetRules . Type ! = TargetType . Game )
{
// Only if shared - unique retains the Target Name
if ( ProjectTarget . TargetRules . BuildEnvironment = = TargetBuildEnvironment . Shared )
{
ExeName = "Unreal" + ProjectTarget . TargetRules . Type . ToString ( ) ;
}
}
}
if ( BuildPlatform . Platform = = UnrealTargetPlatform . Mac )
{
string MacExecutableName = MakeExecutableFileName ( ExeName , UnrealTargetPlatform . Mac , Configuration , ProjectTarget . TargetRules . Architecture , ProjectTarget . TargetRules . UndecoratedConfiguration ) ;
string IOSExecutableName = MacExecutableName . Replace ( "-Mac-" , "-IOS-" ) ;
string TVOSExecutableName = MacExecutableName . Replace ( "-Mac-" , "-TVOS-" ) ;
BuildConfigs . Add ( new UnrealBuildConfig ( ConfigName , TargetName , FileReference . Combine ( OutputDirectory , "Mac" , MacExecutableName ) , FileReference . Combine ( OutputDirectory , "IOS" , IOSExecutableName ) , FileReference . Combine ( OutputDirectory , "TVOS" , TVOSExecutableName ) , ProjectTarget , Configuration ) ) ;
}
else if ( BuildPlatform . Platform = = UnrealTargetPlatform . IOS | | BuildPlatform . Platform = = UnrealTargetPlatform . TVOS )
{
string IOSExecutableName = MakeExecutableFileName ( ExeName , UnrealTargetPlatform . IOS , Configuration , ProjectTarget . TargetRules . Architecture , ProjectTarget . TargetRules . UndecoratedConfiguration ) ;
string TVOSExecutableName = IOSExecutableName . Replace ( "-IOS-" , "-TVOS-" ) ;
//string MacExecutableName = IOSExecutableName.Replace("-IOS-", "-Mac-");
BuildConfigs . Add ( new UnrealBuildConfig ( ConfigName , TargetName , FileReference . Combine ( OutputDirectory , "Mac" , IOSExecutableName ) , FileReference . Combine ( OutputDirectory , "IOS" , IOSExecutableName ) , FileReference . Combine ( OutputDirectory , "TVOS" , TVOSExecutableName ) , ProjectTarget , Configuration ) ) ;
}
}
}
}
}
}
}
2022-05-27 18:34:49 -04:00
}
}
2022-08-08 11:11:04 -04:00
return BuildConfigs ;
2022-05-27 18:34:49 -04:00
}
2022-08-08 11:11:04 -04:00
public static IEnumerable < UnrealTargetPlatform > GetSupportedPlatforms ( )
2022-05-27 18:34:49 -04:00
{
2022-08-08 11:11:04 -04:00
List < UnrealTargetPlatform > SupportedPlatforms = new List < UnrealTargetPlatform > ( ) ;
if ( XcodeProjectFileGenerator . ProjectFilePlatform . HasFlag ( XcodeProjectFileGenerator . XcodeProjectFilePlatform . Mac ) )
{
SupportedPlatforms . Add ( UnrealTargetPlatform . Mac ) ;
}
if ( XcodeProjectFileGenerator . ProjectFilePlatform . HasFlag ( XcodeProjectFileGenerator . XcodeProjectFilePlatform . iOS ) )
{
SupportedPlatforms . Add ( UnrealTargetPlatform . IOS ) ;
}
if ( XcodeProjectFileGenerator . ProjectFilePlatform . HasFlag ( XcodeProjectFileGenerator . XcodeProjectFilePlatform . tvOS ) )
{
SupportedPlatforms . Add ( UnrealTargetPlatform . TVOS ) ;
}
return SupportedPlatforms ;
2022-05-27 18:34:49 -04:00
}
2022-08-08 11:11:04 -04:00
public static IEnumerable < UnrealTargetConfiguration > GetSupportedConfigurations ( )
2022-05-27 18:34:49 -04:00
{
2022-08-08 11:11:04 -04:00
return new UnrealTargetConfiguration [ ] {
UnrealTargetConfiguration . Debug ,
UnrealTargetConfiguration . DebugGame ,
UnrealTargetConfiguration . Development ,
UnrealTargetConfiguration . Test ,
UnrealTargetConfiguration . Shipping
} ;
2022-05-27 18:34:49 -04:00
}
2022-08-08 11:11:04 -04:00
public static bool ShouldIncludeProjectInWorkspace ( ProjectFile Proj , ILogger Logger )
2022-05-27 18:34:49 -04:00
{
2022-08-08 11:11:04 -04:00
return CanBuildProjectLocally ( Proj , Logger ) ;
2022-05-27 18:34:49 -04:00
}
2022-08-08 11:11:04 -04:00
public static bool CanBuildProjectLocally ( ProjectFile Proj , ILogger Logger )
2022-05-27 18:34:49 -04:00
{
2022-08-08 11:11:04 -04:00
foreach ( Project ProjectTarget in Proj . ProjectTargets )
2022-05-27 18:34:49 -04:00
{
2022-08-08 11:11:04 -04:00
foreach ( UnrealTargetPlatform Platform in GetSupportedPlatforms ( ) )
2022-05-27 18:34:49 -04:00
{
2022-08-08 11:11:04 -04:00
foreach ( UnrealTargetConfiguration Config in GetSupportedConfigurations ( ) )
2022-05-27 18:34:49 -04:00
{
2022-08-08 11:11:04 -04:00
if ( MSBuildProjectFile . IsValidProjectPlatformAndConfiguration ( ProjectTarget , Platform , Config , Logger ) )
{
return true ;
}
2022-05-27 18:34:49 -04:00
}
}
}
2022-08-08 11:11:04 -04:00
return false ;
2022-05-27 18:34:49 -04:00
}
2022-08-08 11:11:04 -04:00
private static string MakeExecutableFileName ( string BinaryName , UnrealTargetPlatform Platform , UnrealTargetConfiguration Configuration , string Architecture , UnrealTargetConfiguration UndecoratedConfiguration )
2022-05-27 18:34:49 -04:00
{
2022-08-08 11:11:04 -04:00
StringBuilder Result = new StringBuilder ( ) ;
2022-05-27 18:34:49 -04:00
2022-08-08 11:11:04 -04:00
Result . Append ( BinaryName ) ;
if ( Configuration ! = UndecoratedConfiguration )
{
Result . AppendFormat ( "-{0}-{1}" , Platform . ToString ( ) , Configuration . ToString ( ) ) ;
2022-05-27 18:34:49 -04:00
}
2022-08-08 11:11:04 -04:00
UEBuildPlatform BuildPlatform = UEBuildPlatform . GetBuildPlatform ( Platform ) ;
if ( BuildPlatform . RequiresArchitectureSuffix ( ) )
{
Result . Append ( Architecture ) ;
}
return Result . ToString ( ) ;
2022-05-27 18:34:49 -04:00
}
// cache for the below function
2022-08-08 11:11:04 -04:00
Dictionary < string , IEnumerable < string > > CachedMacProjectArchitectures = new Dictionary < string , IEnumerable < string > > ( ) ;
2022-05-27 18:34:49 -04:00
/// <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="Config">Build config for the target we're generating</param>
/// <param name="InProjectFile">Path to the project file, or null if the target has no project</param>
/// <returns></returns>
2022-08-08 11:11:04 -04:00
public IEnumerable < string > GetSupportedMacArchitectures ( UnrealBuildConfig Config , FileReference ? InProjectFile )
2022-05-27 18:34:49 -04:00
{
// All architectures supported
2022-08-08 11:11:04 -04:00
IEnumerable < string > AllArchitectures = new [ ] { MacExports . IntelArchitecture , MacExports . AppleArchitecture } ;
2022-05-27 18:34:49 -04:00
// 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 ;
}
string TargetName = Config . BuildTarget ;
// First time seeing this target?
2022-08-08 11:11:04 -04:00
if ( ! CachedMacProjectArchitectures . ContainsKey ( TargetName ) )
2022-05-27 18:34:49 -04:00
{
// Default to Intel
IEnumerable < string > TargetArchitectures = new [ ] { MacExports . IntelArchitecture } ;
// These targets are known to work so are allow-listed
bool IsAllowed = MacExports . TargetsAllowedForAppleSilicon . Contains ( TargetName , StringComparer . OrdinalIgnoreCase ) ;
// determine the target architectures based on what's allowed/denied
if ( IsAllowed )
{
TargetArchitectures = AllArchitectures ;
}
else
{
// if this is an unspecified tool/program, default to Intel for installed builds because we know all of that works.
if ( Config . ProjectTarget ! . TargetRules ! . Type = = TargetType . Program )
{
// For misc tools we default to Intel for installed builds because we know all of that works.
TargetArchitectures = Unreal . IsEngineInstalled ( ) ? new [ ] { MacExports . IntelArchitecture } : AllArchitectures ;
}
else
{
// For project targets we default to Intel then check the project settings. Note the editor target will have
// been denied above already.
TargetArchitectures = new [ ] { MacExports . IntelArchitecture } ;
ConfigHierarchy EngineIni = ConfigCache . ReadHierarchy ( ConfigHierarchyType . Engine , InProjectFile ? . Directory , UnrealTargetPlatform . Mac ) ;
string TargetArchitecture ;
string Key = Config . ProjectTarget ! . TargetRules ! . Type = = TargetType . Editor ? "EditorTargetArchitecture" : "TargetArchitecture" ;
if ( EngineIni . GetString ( "/Script/MacTargetPlatform.MacTargetSettings" , Key , out TargetArchitecture ) )
{
2022-05-30 09:17:09 -04:00
if ( TargetArchitecture . Contains ( "Universal" , StringComparison . OrdinalIgnoreCase ) )
2022-05-27 18:34:49 -04:00
{
TargetArchitectures = AllArchitectures ;
}
2022-05-30 09:17:09 -04:00
else if ( TargetArchitecture . Contains ( "Intel" , StringComparison . OrdinalIgnoreCase ) )
2022-05-27 18:34:49 -04:00
{
TargetArchitectures = new [ ] { MacExports . IntelArchitecture } ;
}
2022-05-30 09:17:09 -04:00
else if ( TargetArchitecture . Contains ( "Apple" , StringComparison . OrdinalIgnoreCase ) )
2022-05-27 18:34:49 -04:00
{
TargetArchitectures = new [ ] { MacExports . AppleArchitecture } ;
}
}
}
}
// Cache this so we don't need to keep checking this file
2022-08-08 11:11:04 -04:00
CachedMacProjectArchitectures . Add ( TargetName , TargetArchitectures ) ;
2022-05-27 18:34:49 -04:00
}
2022-08-08 11:11:04 -04:00
return CachedMacProjectArchitectures [ TargetName ] ;
2022-05-27 18:34:49 -04:00
}
2022-08-08 11:11:04 -04:00
}
2022-05-27 18:34:49 -04:00
2022-08-08 11:11:04 -04:00
/// <summary>
/// Info needed to make a file a member of specific group
/// </summary>
class XcodeSourceFile : ProjectFile . SourceFile
{
/// <summary>
/// Constructor
/// </summary>
public XcodeSourceFile ( FileReference InitFilePath , DirectoryReference ? InitRelativeBaseFolder )
: base ( InitFilePath , InitRelativeBaseFolder )
{
FileGuid = XcodeProjectFileGenerator . MakeXcodeGuid ( ) ;
FileRefGuid = XcodeProjectFileGenerator . MakeXcodeGuid ( ) ;
}
/// <summary>
/// File Guid for use in Xcode project
/// </summary>
public string FileGuid
{
get ;
private set ;
}
public void ReplaceGuids ( string NewFileGuid , string NewFileRefGuid )
{
FileGuid = NewFileGuid ;
FileRefGuid = NewFileRefGuid ;
}
/// <summary>
/// File reference Guid for use in Xcode project
/// </summary>
public string FileRefGuid
{
get ;
private set ;
}
}
/// <summary>
/// Represents a group of files shown in Xcode's project navigator as a folder
/// </summary>
internal class XcodeFileGroup
{
public XcodeFileGroup ( string InName , string InPath , bool InIsReference )
{
GroupName = InName ;
GroupPath = InPath ;
GroupGuid = XcodeProjectFileGenerator . MakeXcodeGuid ( ) ;
bIsReference = InIsReference ;
}
public string GroupGuid ;
public string GroupName ;
public string GroupPath ;
public Dictionary < string , XcodeFileGroup > Children = new Dictionary < string , XcodeFileGroup > ( ) ;
public List < XcodeSourceFile > Files = new List < XcodeSourceFile > ( ) ;
public bool bIsReference ;
}
class UnrealExtensionInfo
{
public UnrealExtensionInfo ( string InName )
{
Name = InName ;
TargetDependencyGuid = XcodeProjectFileGenerator . MakeXcodeGuid ( ) ;
TargetProxyGuid = XcodeProjectFileGenerator . MakeXcodeGuid ( ) ;
TargetGuid = XcodeProjectFileGenerator . MakeXcodeGuid ( ) ;
ProductGuid = XcodeProjectFileGenerator . MakeXcodeGuid ( ) ;
ResourceBuildPhaseGuid = XcodeProjectFileGenerator . MakeXcodeGuid ( ) ;
ConfigListGuid = XcodeProjectFileGenerator . MakeXcodeGuid ( ) ;
AllConfigs = new Dictionary < string , UnrealBuildConfig > ( ) ;
}
public string Name ;
public string TargetDependencyGuid ;
public string TargetProxyGuid ;
public string TargetGuid ;
public string ProductGuid ;
public string ResourceBuildPhaseGuid ;
public string ConfigListGuid ;
public Dictionary < string , UnrealBuildConfig > AllConfigs ;
public string? ConfigurationContents ;
}
class XcconfigFile
{
public string Name ;
public string Guid ;
2022-08-08 16:21:20 -04:00
public FileReference FileRef ;
2022-08-08 11:11:04 -04:00
internal StringBuilder Text ;
2022-08-08 16:21:20 -04:00
public XcconfigFile ( DirectoryReference XcodeProjectDirectory , string ConfigName )
2022-08-08 11:11:04 -04:00
{
Name = ConfigName ;
2022-08-08 16:21:20 -04:00
FileRef = FileReference . Combine ( XcodeProjectDirectory , "Xcconfigs" , $"{ConfigName}.xcconfig" ) ;
2022-08-08 11:11:04 -04:00
Guid = XcodeProjectFileGenerator . MakeXcodeGuid ( ) ;
Text = new StringBuilder ( ) ;
}
public void AppendLine ( string Line )
{
Text . Append ( Line ) ;
Text . Append ( ProjectFileGenerator . NewLine ) ;
}
public void Write ( )
{
2022-08-08 16:21:20 -04:00
// write the file to disk
DirectoryReference . CreateDirectory ( FileRef . Directory ) ;
FileReference . WriteAllTextIfDifferent ( FileRef , Text . ToString ( ) ) ;
2022-08-08 11:11:04 -04:00
}
}
abstract class XcodeProjectNode
{
// keeps a list of other node this node references, which is used when writing out the whole xcode project file
public List < XcodeProjectNode > References = new ( ) ;
2022-08-08 16:21:20 -04:00
// optional Xcconfig file
public XcconfigFile ? Xcconfig = null ;
2022-08-08 11:11:04 -04:00
/// <summary>
/// Abstract function the individual node classes must override to write out the node to the project file
/// </summary>
/// <param name="Content"></param>
public abstract void Write ( StringBuilder Content ) ;
/// <summary>
/// Walks the references of the given node to find all nodes of the given type.
/// </summary>
/// <typeparam name="T">Parent class of the nodes to return</typeparam>
/// <param name="Node">Root node to start with</param>
/// <returns>Set of matching nodes</returns>
public static IEnumerable < T > GetNodesOfType < T > ( XcodeProjectNode Node ) where T : XcodeProjectNode
{
// gather the nodes without recursion
LinkedList < XcodeProjectNode > Nodes = new ( ) ;
Nodes . AddLast ( Node ) ;
// pull off the front of the "deque" amd add its references to the back, gather
List < XcodeProjectNode > Return = new ( ) ;
while ( Nodes . Count ( ) > 0 )
{
XcodeProjectNode Head = Nodes . First ( ) ;
Nodes . RemoveFirst ( ) ;
Head . References . ForEach ( x = > Nodes . AddLast ( x ) ) ;
// remember them all
Return . AddRange ( Head . References ) ;
}
// filter down
return Return . OfType < T > ( ) ;
}
2022-08-08 16:21:20 -04:00
public void CreateXcconfigFile ( XcodeProject Project , string Name )
2022-08-08 11:11:04 -04:00
{
2022-08-08 16:21:20 -04:00
Xcconfig = new XcconfigFile ( Project . UnrealData . XcodeProjectFileLocation ! . Directory , Name ) ;
Project . FileCollection . AddFileReference ( Xcconfig . Guid , $"Xcconfigs/{Xcconfig.FileRef.GetFileName()}" , "test.xcconfig" , "\"<group>\"" , "Xcconfigs" ) ;
2022-08-08 11:11:04 -04:00
}
2022-08-10 14:30:33 -04:00
public virtual void WriteXcconfigFile ( )
{
}
2022-08-08 11:11:04 -04:00
/// <summary>
/// THhis will walk the node reference tree and call WRite on each node to add all needed nodes to the xcode poject file
/// </summary>
/// <param name="Content"></param>
/// <param name="Node"></param>
public static void WriteNodeAndReferences ( StringBuilder Content , XcodeProjectNode Node )
{
// write the node into the xcode project file
Node . Write ( Content ) ;
2022-08-10 14:30:33 -04:00
Node . WriteXcconfigFile ( ) ;
2022-08-08 11:11:04 -04:00
foreach ( XcodeProjectNode Reference in Node . References )
{
WriteNodeAndReferences ( Content , Reference ) ;
}
}
}
class XcodeDependency : XcodeProjectNode
{
public XcodeTarget Target ;
public string Guid = XcodeProjectFileGenerator . MakeXcodeGuid ( ) ;
public string ProxyGuid = XcodeProjectFileGenerator . MakeXcodeGuid ( ) ;
public string ProjectGuid ;
public XcodeDependency ( XcodeTarget Target , string ProjectGuid )
{
this . Target = Target ;
this . ProjectGuid = ProjectGuid ;
References . Add ( Target ) ;
}
public override void Write ( StringBuilder Content )
{
Content . WriteLine ( "/* Begin PBXContainerItemProxy section */" ) ;
Content . WriteLine ( $"\t\t{ProxyGuid} /* PBXContainerItemProxy */ = {{" ) ;
Content . WriteLine ( "\t\t\tisa = PBXContainerItemProxy;" ) ;
Content . WriteLine ( $"\t\t\tcontainerPortal = {ProjectGuid} /* Project object */;" ) ;
Content . WriteLine ( "\t\t\tproxyType = 1;" ) ;
Content . WriteLine ( $"\t\t\tremoteGlobalIDString = {Target.Guid};" ) ;
Content . WriteLine ( $"\t\t\tremoteInfo = \" { Target . Name } \ ";" ) ;
Content . WriteLine ( "\t\t};" ) ;
Content . WriteLine ( "/* End PBXContainerItemProxy section */" ) ;
Content . WriteLine ( "" ) ;
Content . WriteLine ( "/* Begin PBXTargetDependency section */" ) ;
Content . WriteLine ( $"\t\t{Guid} /* PBXTargetDependency */ = {{" ) ;
Content . WriteLine ( "\t\t\tisa = PBXTargetDependency;" ) ;
Content . WriteLine ( $"\t\t\ttarget = {Target.Guid} /* {Target.Name} */;" ) ;
Content . WriteLine ( $"\t\t\ttargetProxy = {ProxyGuid} /* PBXContainerItemProxy */;" ) ;
Content . WriteLine ( "\t\t};" ) ;
Content . WriteLine ( "/* End PBXTargetDependency section */" ) ;
}
}
class XcodeBuildPhase : XcodeProjectNode
{
public string Name ;
public string Guid = XcodeProjectFileGenerator . MakeXcodeGuid ( ) ;
private List < XcodeSourceFile > Items = new ( ) ;
public XcodeBuildPhase ( string Name )
{
this . Name = Name ;
}
public void AddFile ( XcodeSourceFile File )
{
Items . Add ( File ) ;
}
public override void Write ( StringBuilder Content )
{
Content . WriteLine ( "/* Begin PBXSourcesBuildPhase section */" ) ;
Content . WriteLine ( $"\t\t{Guid} = {{" ) ;
Content . WriteLine ( "\t\t\tisa = PBXSourcesBuildPhase;" ) ;
Content . WriteLine ( "\t\t\tbuildActionMask = 2147483647;" ) ;
Content . WriteLine ( "\t\t\tfiles = (" ) ;
foreach ( XcodeSourceFile File in Items )
{
Content . WriteLine ( $"\t\t\t\t{File.FileGuid} /* {File.Reference.GetFileName()} in {Name} */," ) ;
}
Content . WriteLine ( "\t\t\t);" ) ;
Content . WriteLine ( "\t\t\trunOnlyForDeploymentPostprocessing = 0;" ) ;
Content . WriteLine ( "\t\t};" ) ;
Content . WriteLine ( "/* End PBXSourcesBuildPhase section */" ) ;
}
}
class XcodeBuildConfig : XcodeProjectNode
{
public string Guid = XcodeProjectFileGenerator . MakeXcodeGuid ( ) ;
public UnrealBuildConfig Info ;
public XcodeBuildConfig ( UnrealBuildConfig Info )
{
this . Info = Info ;
}
public override void Write ( StringBuilder Content )
{
Content . WriteLine ( 2 , $"{Guid} /* {Info.DisplayName} */ = {{" ) ;
Content . WriteLine ( 3 , "isa = XCBuildConfiguration;" ) ;
if ( Xcconfig ! = null )
{
Content . WriteLine ( 3 , $"baseConfigurationReference = {Xcconfig.Guid} /* {Xcconfig.Name}.xcconfig */;" ) ;
}
Content . WriteLine ( 3 , "buildSettings = {" ) ;
Content . WriteLine ( 3 , "};" ) ;
Content . WriteLine ( 3 , $"name = \" { Info . DisplayName } \ ";" ) ;
Content . WriteLine ( 2 , "};" ) ;
}
}
class XcodeBuildConfigList : XcodeProjectNode
{
public string Guid = XcodeProjectFileGenerator . MakeXcodeGuid ( ) ;
public string TargetName ;
public List < XcodeBuildConfig > BuildConfigs = new ( ) ;
public XcodeBuildConfigList ( string TargetName , List < UnrealBuildConfig > BuildConfigInfos )
{
if ( BuildConfigInfos . Count = = 0 )
{
throw new BuildException ( "Created a XcodeBuildConfigList with no BuildConfigs. This likely means a target was created too early" ) ;
}
this . TargetName = TargetName ;
// create build config objects for each info passed in, and them as references
BuildConfigs = BuildConfigInfos . Select ( x = > new XcodeBuildConfig ( x ) ) . ToList ( ) ;
References . AddRange ( BuildConfigs ) ;
}
public override void Write ( StringBuilder Content )
{
// figure out the default configuration to use
string Default = BuildConfigs . Any ( x = > x . Info . DisplayName . Contains ( " Editor" ) ) ? "Development Editor" : "Development" ;
Content . WriteLine ( 2 , $"{Guid} /* Build configuration list for target {TargetName} */ = {{" ) ;
Content . WriteLine ( 3 , "isa = XCConfigurationList;" ) ;
Content . WriteLine ( 3 , "buildConfigurations = (" ) ;
foreach ( XcodeBuildConfig Config in BuildConfigs )
{
Content . WriteLine ( 4 , $"{Config.Guid} /* {Config.Info.DisplayName} */," ) ;
}
Content . WriteLine ( 3 , ");" ) ;
Content . WriteLine ( 3 , "defaultConfigurationIsVisible = 0;" ) ;
Content . WriteLine ( 3 , $"defaultConfigurationName = \" { Default } \ ";" ) ;
Content . WriteLine ( 2 , "};" ) ;
}
}
class XcodeTarget : XcodeProjectNode
{
public enum Type
{
Run_App ,
Run_Tool ,
Build ,
Index ,
}
// Guid for this target
public string Guid = XcodeProjectFileGenerator . MakeXcodeGuid ( ) ;
string TargetAppGuid = XcodeProjectFileGenerator . MakeXcodeGuid ( ) ;
// com.apple.product-type.application, etc
string ProductType ;
// xcode target type name
string TargetTypeName ;
Type TargetType ;
// UE5_Build, EngineTest_SubIndex1, etc
public string Name ;
// list of build configs this target supports (for instance, the Index target only indexes a Development config)
public XcodeBuildConfigList ? BuildConfigList ;
// dependencies for this target
public List < XcodeDependency > Dependencies = new List < XcodeDependency > ( ) ;
// build phases for this target (source, resource copying, etc)
public List < XcodeBuildPhase > BuildPhases = new List < XcodeBuildPhase > ( ) ;
private FileReference ? GameProject ;
public XcodeTarget ( Type Type , UnrealData UnrealData , string? OverrideName = null )
{
GameProject = UnrealData . UProjectFileLocation ;
string ConfigName ;
TargetType = Type ;
switch ( Type )
{
case Type . Run_App :
ProductType = "com.apple.product-type.application" ;
TargetTypeName = "PBXNativeTarget" ;
ConfigName = "Run" ;
break ;
case Type . Run_Tool :
ProductType = "com.apple.product-type.tool" ;
TargetTypeName = "PBXNativeTarget" ;
ConfigName = "Run" ;
break ;
case Type . Build :
ProductType = "com.apple.product-type.library.static" ;
TargetTypeName = "PBXLegacyTarget" ;
ConfigName = "Build" ;
break ;
case Type . Index :
ProductType = "com.apple.product-type.library.static" ;
TargetTypeName = "PBXNativeTarget" ;
ConfigName = "Index" ;
break ;
default :
throw new BuildException ( $"Unhandled target type {Type}" ) ;
}
// set up names
ConfigName = ( OverrideName = = null ) ? ConfigName : OverrideName ;
Name = $"{UnrealData.ProductName}_{ConfigName}" ;
}
public void AddDependency ( XcodeTarget Target , XcodeProject Project )
{
XcodeDependency Dependency = new XcodeDependency ( Target , Project . Guid ) ;
Dependencies . Add ( Dependency ) ;
References . Add ( Dependency ) ;
}
public override void Write ( StringBuilder Content )
{
Content . WriteLine ( $"/* Begin {TargetType} section */" ) ;
Content . WriteLine ( 2 , $"{Guid} /* {Name} */ = {{" ) ;
Content . WriteLine ( 3 , $"isa = {TargetTypeName};" ) ;
Content . WriteLine ( 3 , $"buildConfigurationList = {BuildConfigList!.Guid} /* Build configuration list for {TargetTypeName} \" { Name } \ " */;" ) ;
if ( TargetType = = Type . Build )
{
// get paths to Unreal bits to be able ro tun UBT
string UProjectParam = GameProject = = null ? "" : $"{GameProject.FullName.Replace(" ", " \ \ ")}" ;
string UEDir = XcodeFileCollection . ConvertPath ( Path . GetFullPath ( Directory . GetCurrentDirectory ( ) + "../../.." ) ) ;
string BuildToolPath = UEDir + "/Engine/Build/BatchFiles/Mac/XcodeBuild.sh" ;
// insert elements to call UBT when building
Content . WriteLine ( 3 , $"buildArgumentsString = \" $ ( ACTION ) $ ( UE_BUILD_TARGET_NAME ) $ ( PLATFORM_NAME ) $ ( UE_BUILD_TARGET_CONFIG ) { UProjectParam } \ ";" ) ;
Content . WriteLine ( 3 , $"buildToolPath = \" { BuildToolPath } \ ";" ) ;
Content . WriteLine ( 3 , $"buildWorkingDirectory = \" { UEDir } \ ";" ) ;
}
Content . WriteLine ( 3 , "buildPhases = (" ) ;
foreach ( XcodeBuildPhase BuildPhase in BuildPhases )
{
Content . WriteLine ( 4 , $"{BuildPhase.Guid} /* {BuildPhase.Name} */," ) ;
}
Content . WriteLine ( 3 , ");" ) ;
Content . WriteLine ( 3 , "dependencies = (" ) ;
foreach ( XcodeDependency Dependency in Dependencies )
{
Content . WriteLine ( 4 , $"{Dependency.Guid} /* {Dependency.Target.Name} */," ) ;
}
Content . WriteLine ( 3 , ");" ) ;
Content . WriteLine ( 3 , $"name = \" { Name } \ ";" ) ;
Content . WriteLine ( 3 , "passBuildSettingsInEnvironment = 1;" ) ;
Content . WriteLine ( 3 , $"productType = \" { ProductType } \ ";" ) ;
WriteExtraTargetProperties ( Content ) ;
Content . WriteLine ( 2 , "};" ) ;
Content . WriteLine ( $"/* End {TargetType} section */" ) ;
}
/// <summary>
/// Let subclasses add extra properties into this target section
/// </summary>
protected virtual void WriteExtraTargetProperties ( StringBuilder Content )
{
// nothing by default
}
}
class XcodeRunTarget : XcodeTarget
{
private string ProductGuid = XcodeProjectFileGenerator . MakeXcodeGuid ( ) ;
private string ProductName ;
public XcodeRunTarget ( XcodeProject Project )
: base ( Project . UnrealData . bIsAppBundle ? XcodeTarget . Type . Run_App : XcodeTarget . Type . Run_Tool , Project . UnrealData )
{
BuildConfigList = new XcodeBuildConfigList ( Name , Project . UnrealData . AllConfigs ) ;
References . Add ( BuildConfigList ) ;
// add the Product item to the project to be visible in left pane
ProductName = Project . UnrealData . ProductName ;
Project . FileCollection . AddFileReference ( ProductGuid , ProductName , Project . UnrealData . bIsAppBundle ? "wrapper.application" : "\"compiled.mach-o.executable\"" , "BUILT_PRODUCTS_DIR" , "Products" ) ;
// if we are run-only, then skip some stuff
if ( ! XcodeProjectFileGenerator . bGeneratingRunIOSProject )
{
// create a biuld target only if we have source files to build
if ( Project . UnrealData . BatchedFiles . Count ! = 0 )
{
XcodeBuildTarget BuildTarget = new XcodeBuildTarget ( Project . UnrealData ) ;
AddDependency ( BuildTarget , Project ) ;
}
}
2022-08-08 16:21:20 -04:00
CreateXcconfigFile ( Project , Name ) ;
// hook up each buildconfig to this Xcconfig
BuildConfigList ! . BuildConfigs . ForEach ( x = > x . Xcconfig = Xcconfig ) ;
2022-08-08 11:11:04 -04:00
}
2022-08-10 14:30:33 -04:00
protected override void WriteExtraTargetProperties ( StringBuilder Content )
2022-08-08 11:11:04 -04:00
{
2022-08-10 14:30:33 -04:00
Content . WriteLine ( $"\t\t\tproductReference = {ProductGuid};" ) ;
Content . WriteLine ( $"\t\t\tproductName = \" { ProductName } \ ";" ) ;
}
2022-08-08 11:11:04 -04:00
2022-08-10 14:30:33 -04:00
public override void WriteXcconfigFile ( )
{
2022-08-08 16:21:20 -04:00
// also write to the Xcconfig file
Xcconfig ! . AppendLine ( "GENERATE_INFOPLIST_FILE[sdk=macosx*] = YES // Xcode 14 code-signing fix for development" ) ;
2022-08-08 11:11:04 -04:00
// #jira UE-143619: Pre Monterey macOS requires this option for a packaged app to run on iOS15 due to new code signature format. Could be removed once Monterey is miniuS.
Xcconfig . AppendLine ( "OTHER_CODE_SIGN_FLAGS = --generate-entitlement-der" ) ;
Xcconfig . AppendLine ( $"MACOSX_DEPLOYMENT_TARGET = {MacToolChain.Settings.MacOSVersion};" ) ;
Xcconfig . AppendLine ( "INFOPLIST_OUTPUT_FORMAT = xml" ) ;
Xcconfig . AppendLine ( "COMBINE_HIDPI_IMAGES = YES" ) ;
2022-08-10 14:30:33 -04:00
Xcconfig . Write ( ) ;
2022-08-08 11:11:04 -04:00
}
}
class XcodeBuildTarget : XcodeTarget
{
public XcodeBuildTarget ( UnrealData UnrealData )
: base ( XcodeTarget . Type . Build , UnrealData )
{
BuildConfigList = new XcodeBuildConfigList ( Name , UnrealData . AllConfigs ) ;
References . Add ( BuildConfigList ) ;
}
}
class XcodeIndexTarget : XcodeTarget
{
private UnrealBatchedFiles ? SoloTargetBatch ;
// just take the Project since it has everything we need, and is needed when adding target dependencies
public XcodeIndexTarget ( XcodeProject Project )
: base ( XcodeTarget . Type . Index , Project . UnrealData )
{
// do we have an editor? if so, use this Development Editor as the config to index, otherwise, use Development
string IndexConfig = Project . UnrealData . AllConfigs . Any ( x = > x . DisplayName = = "Development Editor" ) ? "Development Editor" : "Development" ;
BuildConfigList = new XcodeBuildConfigList ( Name , Project . UnrealData . AllConfigs . Where ( x = > x . DisplayName = = IndexConfig ) . ToList ( ) ) ;
References . Add ( BuildConfigList ) ;
2022-08-08 16:21:20 -04:00
CreateXcconfigFile ( Project , Name ) ;
// hook up each buildconfig to this Xcconfig
BuildConfigList ! . BuildConfigs . ForEach ( x = > x . Xcconfig = Xcconfig ) ;
2022-08-08 11:11:04 -04:00
// now look to see if all files are buildable with one batch, in which case we don't need subtargets, or if we have multiple batches, then
// make a subtarget for each batch of unique build settings
if ( Project . UnrealData . BatchedFiles . Count = = 1 )
{
SoloTargetBatch = Project . UnrealData . BatchedFiles [ 0 ] ;
XcodeBuildPhase BuildPhase = new XcodeBuildPhase ( "Sources" ) ;
BuildPhases . Add ( BuildPhase ) ;
References . Add ( BuildPhase ) ;
foreach ( XcodeSourceFile File in Project . FileCollection . BuildableFiles )
{
BuildPhase . AddFile ( File ) ;
}
}
else
{
// find the config infos this target is set to use, and copy those in to the subtargets
List < UnrealBuildConfig > Configs = BuildConfigList . BuildConfigs . Select ( x = > x . Info ) . ToList ( ) ;
for ( int Index = 0 ; Index < Project . UnrealData . BatchedFiles . Count ; Index + + )
{
2022-08-08 16:21:20 -04:00
XcodeIndexSubTarget SubTarget = new XcodeIndexSubTarget ( Project , Index , Configs ) ;
2022-08-08 11:11:04 -04:00
AddDependency ( SubTarget , Project ) ;
}
}
}
2022-08-10 14:30:33 -04:00
public override void WriteXcconfigFile ( )
2022-08-08 11:11:04 -04:00
{
// write out settings that apply whether or not we have subtargets, which #include this one, or no subtargets, and we write out more
// @todo move tis to the subtarget and remember it from the Module
2022-08-08 16:21:20 -04:00
Xcconfig ! . AppendLine ( "CLANG_CXX_LANGUAGE_STANDARD = c++17" ) ;
2022-08-08 11:11:04 -04:00
Xcconfig . AppendLine ( "GCC_WARN_CHECK_SWITCH_STATEMENTS = NO" ) ;
Xcconfig . AppendLine ( "GCC_PRECOMPILE_PREFIX_HEADER = YES" ) ;
Xcconfig . AppendLine ( "GCC_OPTIMIZATION_LEVEL = 0" ) ;
// if we have folded a file batch into this main target, write out the settings into this Xcconfig
if ( SoloTargetBatch ! = null )
{
2022-08-08 16:21:20 -04:00
// ask the single sub target to write out the contents of our Xcconfig file since we are folding them up into this target
2022-08-08 11:11:04 -04:00
SoloTargetBatch . WriteXcconfigSettings ( Xcconfig . Text ) ;
}
2022-08-10 14:30:33 -04:00
Xcconfig . Write ( ) ;
2022-08-08 11:11:04 -04:00
}
}
class XcodeIndexSubTarget : XcodeTarget
{
public string TargetGuid = XcodeProjectFileGenerator . MakeXcodeGuid ( ) ;
public string BuildPhaseGuid = XcodeProjectFileGenerator . MakeXcodeGuid ( ) ;
public string ConfigListGuid = XcodeProjectFileGenerator . MakeXcodeGuid ( ) ;
private UnrealBatchedFiles BatchedFiles ;
2022-08-08 16:21:20 -04:00
public XcodeIndexSubTarget ( XcodeProject Project , int SubTargetIndex , List < UnrealBuildConfig > Configs )
: base ( Type . Index , Project . UnrealData , $"SubIndex{SubTargetIndex}" )
2022-08-08 11:11:04 -04:00
{
// get the settings and list of files for this subtarget
2022-08-08 16:21:20 -04:00
BatchedFiles = Project . UnrealData . BatchedFiles [ SubTargetIndex ] ;
2022-08-08 11:11:04 -04:00
// set the buildconfig list in the SubTarget
BuildConfigList = new XcodeBuildConfigList ( Name , Configs ) ;
References . Add ( BuildConfigList ) ;
2022-08-08 16:21:20 -04:00
CreateXcconfigFile ( Project , Name ) ;
// hook up each buildconfig to this Xcconfig
BuildConfigList ! . BuildConfigs . ForEach ( x = > x . Xcconfig = Xcconfig ) ;
2022-08-08 11:11:04 -04:00
// create the build phase for this target for all files in the batch
XcodeBuildPhase BuildPhase = new XcodeBuildPhase ( "Sources" ) ;
BuildPhases . Add ( BuildPhase ) ;
References . Add ( BuildPhase ) ;
// and add each file to it
foreach ( XcodeSourceFile File in BatchedFiles . Files )
{
BuildPhases [ 0 ] . AddFile ( File ) ;
}
}
2022-08-10 14:30:33 -04:00
public override void WriteXcconfigFile ( )
2022-08-08 11:11:04 -04:00
{
2022-08-08 16:21:20 -04:00
// include the share Index config
Xcconfig ! . AppendLine ( $"#include \" { Name . Split ( '_' ) [ 0 ] } _Index . xcconfig \ "" ) ;
2022-08-08 11:11:04 -04:00
2022-08-08 16:21:20 -04:00
// then right the build settings from the modules we are batched together
2022-08-08 11:11:04 -04:00
BatchedFiles . WriteXcconfigSettings ( Xcconfig . Text ) ;
2022-08-10 14:30:33 -04:00
Xcconfig . Write ( ) ;
2022-08-08 11:11:04 -04:00
}
}
class XcodeProject : XcodeProjectNode
{
// the blob of data coming from unreal that we can pass around
public UnrealData UnrealData ;
// container for all files and groups
public XcodeFileCollection FileCollection ;
// Guid for the project node
public string Guid = XcodeProjectFileGenerator . MakeXcodeGuid ( ) ;
private string ProvisioningStyle ;
private XcodeRunTarget RunTarget ;
public XcodeBuildConfigList ProjectBuildConfigs ;
public XcodeProject ( UnrealData UnrealData , XcodeFileCollection FileCollection )
{
this . UnrealData = UnrealData ;
this . FileCollection = FileCollection ;
ProvisioningStyle = UnrealData . bUseAutomaticSigning ? "Automatic" : "Manual" ;
RunTarget = new XcodeRunTarget ( this ) ;
References . Add ( RunTarget ) ;
ProjectBuildConfigs = new XcodeBuildConfigList ( UnrealData . ProductName , UnrealData . AllConfigs ) ;
References . Add ( ProjectBuildConfigs ) ;
2022-08-08 16:21:20 -04:00
// create the Projet xcconfig
CreateXcconfigFile ( this , $"{UnrealData.ProductName}_Project" ) ;
// create per-config Xcconfig files
foreach ( XcodeBuildConfig Config in ProjectBuildConfigs . BuildConfigs )
{
Config . CreateXcconfigFile ( this , $"{UnrealData.ProductName}_{Config.Info.DisplayName.Replace(" ", " ")}" ) ;
}
2022-08-08 11:11:04 -04:00
// make an indexing target if we aren't just a run-only project, and it has buildable source files
if ( ! XcodeProjectFileGenerator . bGeneratingRunIOSProject & & UnrealData . BatchedFiles . Count ! = 0 )
{
// index isn't a dependency of run, it's simply a target that xcode will find to index from
XcodeIndexTarget IndexTarget = new XcodeIndexTarget ( this ) ;
References . Add ( IndexTarget ) ;
}
}
public override void Write ( StringBuilder Content )
{
Content . WriteLine ( "/* Begin PBXProject section */" ) ;
Content . WriteLine ( 2 , $"{Guid} /* Project object */ = {{" ) ;
Content . WriteLine ( 3 , "isa = PBXProject;" ) ;
Content . WriteLine ( 3 , "attributes = {" ) ;
Content . WriteLine ( 4 , "LastUpgradeCheck = 2000;" ) ;
Content . WriteLine ( 4 , "ORGANIZATIONNAME = \"Epic Games, Inc.\";" ) ;
Content . WriteLine ( 4 , "TargetAttributes = {" ) ;
Content . WriteLine ( 5 , $"{RunTarget.Guid} = {{" ) ;
Content . WriteLine ( 6 , $"ProvisioningStyle = {ProvisioningStyle};" ) ;
Content . WriteLine ( 5 , "};" ) ;
Content . WriteLine ( 4 , "};" ) ;
Content . WriteLine ( 3 , "};" ) ;
Content . WriteLine ( 3 , $"buildConfigurationList = {ProjectBuildConfigs.Guid} /* Build configuration list for PBXProject \" { ProjectBuildConfigs . TargetName } \ " */;" ) ;
Content . WriteLine ( 3 , "compatibilityVersion = \"Xcode 8.0\";" ) ;
Content . WriteLine ( 3 , "developmentRegion = English;" ) ;
Content . WriteLine ( 3 , "hasScannedForEncodings = 0;" ) ;
Content . WriteLine ( 3 , "knownRegions = (" ) ;
Content . WriteLine ( 4 , "en" ) ;
Content . WriteLine ( 3 , ");" ) ;
2022-08-10 14:30:33 -04:00
Content . WriteLine ( 3 , $"mainGroup = {FileCollection.MainGroupGuid};" ) ;
2022-08-08 11:11:04 -04:00
Content . WriteLine ( 3 , $"productRefGroup = {FileCollection.GetProductGroupGuid()};" ) ;
Content . WriteLine ( 3 , "projectDirPath = \"\";" ) ;
Content . WriteLine ( 3 , "projectRoot = \"\";" ) ;
Content . WriteLine ( 3 , "targets = (" ) ;
foreach ( XcodeTarget Target in XcodeProjectNode . GetNodesOfType < XcodeTarget > ( this ) )
{
Content . WriteLine ( 4 , $"{Target.Guid} /* {Target.Name} */," ) ;
}
Content . WriteLine ( 3 , ");" ) ;
Content . WriteLine ( 2 , "};" ) ;
Content . WriteLine ( "/* End PBXProject section */" ) ;
}
2022-08-10 14:30:33 -04:00
public override void WriteXcconfigFile ( )
2022-08-08 11:11:04 -04:00
{
string UEDir = XcodeFileCollection . ConvertPath ( Path . GetFullPath ( Directory . GetCurrentDirectory ( ) + "../../.." ) ) ;
string SupportedIOSArchitectures = string . Join ( " " , UnrealData . SupportedIOSArchitectures ) ;
2022-07-19 13:24:38 -04:00
string? IOSRunTimeVersion = null ;
string? TVOSRunTimeVersion = null ;
2022-05-27 18:34:49 -04:00
// shortcut for mac only
2022-07-19 13:24:38 -04:00
string? IOSRunTimeDevices = null ;
string? TVOSRunTimeDevices = null ;
bool bAutomaticSigning = false ;
string? UUID_IOS = "" ;
string? UUID_TVOS = "" ;
string? TEAM_IOS = "" ;
string? TEAM_TVOS = "" ;
string? IOS_CERT = "iPhone Developer" ;
string? TVOS_CERT = "iPhone Developer" ;
string IOS_BUNDLE = "" ;
string TVOS_BUNDLE = "" ;
2022-08-08 11:11:04 -04:00
if ( UnrealData . IOSProjectSettings ! = null )
2022-05-27 18:34:49 -04:00
{
2022-08-08 11:11:04 -04:00
IOSRunTimeVersion = UnrealData . IOSProjectSettings . RuntimeVersion ;
IOSRunTimeDevices = UnrealData . IOSProjectSettings . RuntimeDevices ;
bAutomaticSigning = UnrealData . IOSProjectSettings . bAutomaticSigning ;
2022-07-19 13:24:38 -04:00
if ( ! bAutomaticSigning )
{
2022-08-08 11:11:04 -04:00
UUID_IOS = UnrealData . IOSProvisioningData ! . MobileProvisionUUID ;
IOS_CERT = UnrealData . IOSProvisioningData ! . SigningCertificate ;
2022-07-19 13:24:38 -04:00
}
2022-08-08 11:11:04 -04:00
TEAM_IOS = UnrealData . IOSProvisioningData ! . TeamUUID ;
IOS_BUNDLE = UnrealData . IOSProjectSettings . BundleIdentifier ;
2022-05-27 18:34:49 -04:00
}
2022-07-19 13:24:38 -04:00
2022-08-08 11:11:04 -04:00
if ( UnrealData . TVOSProjectSettings ! = null )
2022-05-27 18:34:49 -04:00
{
2022-08-08 11:11:04 -04:00
TVOSRunTimeVersion = UnrealData . TVOSProjectSettings . RuntimeVersion ;
TVOSRunTimeDevices = UnrealData . TVOSProjectSettings . RuntimeDevices ;
2022-07-19 13:24:38 -04:00
if ( ! bAutomaticSigning )
{
2022-08-08 11:11:04 -04:00
UUID_TVOS = UnrealData . TVOSProvisioningData ! . MobileProvisionUUID ;
TVOS_CERT = UnrealData . TVOSProvisioningData ! . SigningCertificate ;
2022-07-19 13:24:38 -04:00
}
2022-08-08 11:11:04 -04:00
TEAM_TVOS = UnrealData . TVOSProvisioningData ! . TeamUUID ;
TVOS_BUNDLE = UnrealData . TVOSProjectSettings . BundleIdentifier ;
2022-07-19 13:24:38 -04:00
}
//#jira UE-50382 Xcode Address Sanitizer feature does not work on iOS
// address sanitizer dylib loader depends on the SDKROOT parameter. For macosx or default (missing, translated as macosx), the path is incorrect for iphone/appletv
2022-08-08 16:21:20 -04:00
Xcconfig ! . AppendLine ( $"SDKROOT[sdk=macosx*] = macosx" ) ;
Xcconfig . AppendLine ( $"SDKROOT[sdk=iphone*] = iphoneos" ) ;
Xcconfig . AppendLine ( $"SDKROOT[sdk=appletvos*] = appletvos" ) ;
Xcconfig . AppendLine ( "USE_HEADERMAP = NO" ) ;
Xcconfig . AppendLine ( "ONLY_ACTIVE_ARCH = YES" ) ;
2022-07-19 13:24:38 -04:00
2022-08-10 14:30:33 -04:00
if ( bAutomaticSigning & & UnrealData . bWriteCodeSigningSettings )
2022-07-19 13:24:38 -04:00
{
2022-08-08 16:21:20 -04:00
Xcconfig . AppendLine ( "CODE_SIGN_STYLE = Automatic" ) ;
2022-07-19 13:24:38 -04:00
}
if ( IOSRunTimeVersion ! = null )
{
2022-08-08 16:21:20 -04:00
Xcconfig . AppendLine ( $"VALID_ARCHS[sdk=iphone*] = {SupportedIOSArchitectures}" ) ;
Xcconfig . AppendLine ( $"IPHONEOS_DEPLOYMENT_TARGET = {IOSRunTimeVersion}" ) ;
Xcconfig . AppendLine ( $"TARGETED_DEVICE_FAMILY[sdk=iphone*] = {IOSRunTimeDevices}" ) ;
2022-08-10 14:30:33 -04:00
if ( UnrealData . bWriteCodeSigningSettings )
2022-07-19 13:24:38 -04:00
{
2022-08-10 14:30:33 -04:00
if ( ! string . IsNullOrEmpty ( TEAM_IOS ) )
{
Xcconfig . AppendLine ( $"DEVELOPMENT_TEAM[sdk=iphone*] = {TEAM_IOS}" ) ;
}
Xcconfig . AppendLine ( $"CODE_SIGN_IDENTITY[sdk=iphone*] = {IOS_CERT}" ) ;
if ( ! bAutomaticSigning & & ! string . IsNullOrEmpty ( UUID_IOS ) )
{
Xcconfig . AppendLine ( $"PROVISIONING_PROFILE_SPECIFIER[sdk=iphone*] = {UUID_IOS}" ) ;
}
2022-07-19 13:24:38 -04:00
}
2022-08-08 11:11:04 -04:00
if ( UnrealData . UProjectFileLocation ! = null )
2022-07-19 13:24:38 -04:00
{
2022-08-08 16:21:20 -04:00
Xcconfig . AppendLine ( $"PRODUCT_BUNDLE_IDENTIFIER[sdk=iphone*] = {IOS_BUNDLE}" ) ;
2022-07-19 13:24:38 -04:00
}
}
if ( TVOSRunTimeVersion ! = null )
{
2022-08-08 16:21:20 -04:00
Xcconfig . AppendLine ( $"VALID_ARCHS[sdk=appletvos*] = {SupportedIOSArchitectures}" ) ;
2022-08-10 14:30:33 -04:00
Xcconfig . AppendLine ( $"TVOS_DEPLOYMENT_TARGET = {TVOSRunTimeVersion}" ) ;
2022-08-08 16:21:20 -04:00
Xcconfig . AppendLine ( $"TARGETED_DEVICE_FAMILY[sdk=appletvos*] = {TVOSRunTimeDevices}" ) ;
2022-08-10 14:30:33 -04:00
if ( UnrealData . bWriteCodeSigningSettings )
2022-07-19 13:24:38 -04:00
{
2022-08-10 14:30:33 -04:00
if ( ! string . IsNullOrEmpty ( TEAM_TVOS ) )
{
Xcconfig . AppendLine ( $"DEVELOPMENT_TEAM[sdk=iphone*] = {TEAM_TVOS}" ) ;
}
Xcconfig . AppendLine ( $"CODE_SIGN_IDENTITY[sdk=appletvos*] = {TVOS_CERT}" ) ;
if ( ! bAutomaticSigning & & ! string . IsNullOrEmpty ( UUID_TVOS ) )
{
Xcconfig . AppendLine ( $"PROVISIONING_PROFILE_SPECIFIER[sdk=appletvos*] = {UUID_TVOS}" ) ;
}
2022-07-19 13:24:38 -04:00
}
2022-08-08 11:11:04 -04:00
if ( UnrealData . UProjectFileLocation ! = null )
2022-07-19 13:24:38 -04:00
{
2022-08-08 16:21:20 -04:00
Xcconfig . AppendLine ( $"PRODUCT_BUNDLE_IDENTIFIER[sdk=appletvos*] = {TVOS_BUNDLE}" ) ;
2022-07-19 13:24:38 -04:00
}
}
2022-08-10 14:30:33 -04:00
Xcconfig . Write ( ) ;
2022-07-19 13:24:38 -04:00
// Now for each config write out the specific settings
2022-08-08 11:11:04 -04:00
DirectoryReference ? GameDir = UnrealData . UProjectFileLocation ? . Directory ;
string? GamePath = GameDir ! = null ? XcodeFileCollection . ConvertPath ( GameDir . FullName ) : null ;
2022-07-19 13:24:38 -04:00
// Get Mac architectures supported by this project
2022-08-08 11:11:04 -04:00
foreach ( UnrealBuildConfig Config in UnrealData . AllConfigs )
2022-07-19 13:24:38 -04:00
{
2022-08-08 11:11:04 -04:00
// hook up the Buildconfig that matches this info to this xcconfig file
2022-08-08 16:21:20 -04:00
XcconfigFile ConfigXcconfig = ProjectBuildConfigs ! . BuildConfigs . First ( x = > x . Info = = Config ) . Xcconfig ! ;
2022-08-08 11:11:04 -04:00
2022-08-08 16:21:20 -04:00
ConfigXcconfig . AppendLine ( "// pull in the shared settings for all configs" ) ;
ConfigXcconfig . AppendLine ( $"#include \" { UnrealData . ProductName } _Project . xcconfig \ "" ) ;
ConfigXcconfig . AppendLine ( "" ) ;
2022-08-08 11:11:04 -04:00
bool bIsUnrealGame = Config . BuildTarget . Equals ( "UnrealGame" , StringComparison . InvariantCultureIgnoreCase ) ;
bool bIsUnrealClient = Config . BuildTarget . Equals ( "UnrealClient" , StringComparison . InvariantCultureIgnoreCase ) ;
2022-05-27 18:34:49 -04:00
2022-07-19 13:24:38 -04:00
// Setup Mac stuff
FileReference MacExecutablePath = Config . MacExecutablePath ! ;
2022-08-08 11:11:04 -04:00
string MacExecutableDir = XcodeFileCollection . ConvertPath ( MacExecutablePath . Directory . FullName ) ;
2022-07-19 13:24:38 -04:00
string MacExecutableFileName = MacExecutablePath . GetFileName ( ) ;
2022-08-08 11:11:04 -04:00
string SupportedMacArchitectures = string . Join ( " " , UnrealData . GetSupportedMacArchitectures ( Config , UnrealData . UProjectFileLocation ) ) ;
2022-05-27 18:34:49 -04:00
2022-07-19 13:24:38 -04:00
// figure out the directory that contains the Binaries and Intermediate directories
string ProjectRootDir = ( bIsUnrealGame | | bIsUnrealClient | | GamePath = = null ) ? $"{UEDir}/Engine" : GamePath ;
2022-05-27 18:34:49 -04:00
2022-07-19 13:24:38 -04:00
// UnrealClient re-uses UnrealGame
string PlistTargetName = bIsUnrealClient ? "UnrealGame" : Config . BuildTarget ;
string MacInfoPlistPath = $"{ProjectRootDir}/Intermediate/Mac/{MacExecutableFileName}-Info.plist" ;
string IOSInfoPlistPath = $"{ProjectRootDir}/Intermediate/IOS/{PlistTargetName}-Info.plist" ;
string TVOSInfoPlistPath = $"{ProjectRootDir}/Intermediate/TVOS/{PlistTargetName}-Info.plist" ;
2022-05-27 18:34:49 -04:00
2022-07-19 13:24:38 -04:00
// @todo is really necessary to only set entitlements in the non-UnrealGame, project case? what about mac entitlements?
2022-08-08 11:11:04 -04:00
bool bSetEntitlements = ! bIsUnrealGame & & ! bIsUnrealClient & & UnrealData . UProjectFileLocation ! = null ;
2022-05-27 18:34:49 -04:00
2022-07-19 13:24:38 -04:00
string SupportedPlatforms = "" ;
if ( Config . ProjectTarget ! . TargetRules ! = null )
2022-05-27 18:34:49 -04:00
{
2022-07-19 13:24:38 -04:00
if ( Config . bSupportsIOS )
2022-05-27 18:34:49 -04:00
{
2022-08-08 11:11:04 -04:00
SupportedPlatforms + = "iphoneos iphonesimulator " ;
2022-05-27 18:34:49 -04:00
}
2022-07-19 13:24:38 -04:00
if ( Config . bSupportsTVOS )
2022-05-27 18:34:49 -04:00
{
2022-07-19 13:24:38 -04:00
SupportedPlatforms + = "appletvos " ;
2022-05-27 18:34:49 -04:00
}
2022-07-19 13:24:38 -04:00
if ( Config . bSupportsMac )
2022-05-27 18:34:49 -04:00
{
2022-07-19 13:24:38 -04:00
SupportedPlatforms + = "macosx " ;
2022-05-27 18:34:49 -04:00
}
}
else
{
2022-07-19 13:24:38 -04:00
// @todo when does this case happen?
SupportedPlatforms = "macosx" ;
}
// debug settings
if ( Config . BuildConfig = = UnrealTargetConfiguration . Debug )
{
2022-08-08 16:21:20 -04:00
ConfigXcconfig . AppendLine ( "ENABLE_TESTABILITY = YES" ) ;
2022-07-19 13:24:38 -04:00
}
2022-08-08 16:21:20 -04:00
ConfigXcconfig . AppendLine ( $"UE_BUILD_TARGET_NAME = {Config.BuildTarget}" ) ;
ConfigXcconfig . AppendLine ( $"UE_BUILD_TARGET_CONFIG = {Config.BuildConfig}" ) ;
2022-07-19 13:24:38 -04:00
// list the supported platforms, which will narrow down what other settings are active
2022-08-08 16:21:20 -04:00
ConfigXcconfig . AppendLine ( $"SUPPORTED_PLATFORMS = {SupportedPlatforms}" ) ;
2022-07-19 13:24:38 -04:00
// @otodo move VALID_ARCHS up to Project config once we are always universal
if ( Config . bSupportsMac )
{
2022-08-08 16:21:20 -04:00
ConfigXcconfig . AppendLine ( "" ) ;
ConfigXcconfig . AppendLine ( "// Mac setup" ) ;
ConfigXcconfig . AppendLine ( $"VALID_ARCHS[sdk=macosx*] = {SupportedMacArchitectures}" ) ;
ConfigXcconfig . AppendLine ( $"PRODUCT_NAME[sdk=macosx*] = {MacExecutableFileName}" ) ;
ConfigXcconfig . AppendLine ( $"CONFIGURATION_BUILD_DIR[sdk=macosx*] = {MacExecutableDir}" ) ;
ConfigXcconfig . AppendLine ( $"INFOPLIST_FILE[sdk=macosx*] = {MacInfoPlistPath}" ) ;
2022-07-19 13:24:38 -04:00
}
if ( Config . bSupportsIOS )
{
2022-08-08 16:21:20 -04:00
ConfigXcconfig . AppendLine ( "" ) ;
ConfigXcconfig . AppendLine ( "// IOS setup" ) ;
ConfigXcconfig . AppendLine ( $"PRODUCT_NAME[sdk=iphone*] = {Config.BuildTarget}" ) ; // @todo: change to Path.GetFileName(Config.IOSExecutablePath) when we stop using payload
ConfigXcconfig . AppendLine ( $"CONFIGURATION_BUILD_DIR[sdk=iphone*] = {ProjectRootDir}/Binaries/IOS/Payload" ) ;
ConfigXcconfig . AppendLine ( $"INFOPLIST_FILE[sdk=iphone*] = {IOSInfoPlistPath}" ) ;
2022-07-19 13:24:38 -04:00
if ( bSetEntitlements )
2022-05-27 18:34:49 -04:00
{
2022-08-08 16:21:20 -04:00
ConfigXcconfig . AppendLine ( $"CODE_SIGN_ENTITLEMENTS[sdk=iphone*] = {ProjectRootDir}/Intermediate/IOS/{PlistTargetName}.entitlements" ) ;
2022-05-27 18:34:49 -04:00
}
2022-07-19 13:24:38 -04:00
}
if ( Config . bSupportsTVOS )
{
2022-08-08 16:21:20 -04:00
ConfigXcconfig . AppendLine ( "" ) ;
ConfigXcconfig . AppendLine ( "// TVOS setup" ) ;
ConfigXcconfig . AppendLine ( $"PRODUCT_NAME[sdk=appletvos*] = {Config.BuildTarget}" ) ; // @todo: change to Path.GetFileName(Config.IOSExecutablePath) when we stop using payload
ConfigXcconfig . AppendLine ( $"CONFIGURATION_BUILD_DIR[sdk=appletvos*] = {ProjectRootDir}/Binaries/TVOS/Payload" ) ;
ConfigXcconfig . AppendLine ( $"INFOPLIST_FILE[sdk=appletvos*] = {TVOSInfoPlistPath}" ) ;
2022-07-19 13:24:38 -04:00
if ( bSetEntitlements )
2022-05-27 18:34:49 -04:00
{
2022-08-08 16:21:20 -04:00
ConfigXcconfig . AppendLine ( $"CODE_SIGN_ENTITLEMENTS[sdk=appletvos*] = {ProjectRootDir}/Intermediate/TVOS/{PlistTargetName}.entitlements" ) ;
2022-05-27 18:34:49 -04:00
}
}
2022-07-19 13:24:38 -04:00
// @todo can we remove this and use GENERATE_INFOPLIST_FILE when plist doesn't exist?
2022-05-27 18:34:49 -04:00
// Prepare a temp Info.plist file so Xcode has some basic info about the target immediately after opening the project.
// This is needed for the target to pass the settings validation before code signing. UBT will overwrite this plist file later, with proper contents.
if ( BuildHostPlatform . Current . Platform = = UnrealTargetPlatform . Mac )
{
bool bCreateMacInfoPlist = ! File . Exists ( MacInfoPlistPath ) ;
bool bCreateIOSInfoPlist = ! File . Exists ( IOSInfoPlistPath ) & & IOSRunTimeVersion ! = null ;
bool bCreateTVOSInfoPlist = ! File . Exists ( TVOSInfoPlistPath ) & & TVOSRunTimeVersion ! = null ;
if ( bCreateMacInfoPlist | | bCreateIOSInfoPlist | | bCreateTVOSInfoPlist )
{
DirectoryReference ? ProjectPath = GameDir ;
DirectoryReference EngineDir = DirectoryReference . Combine ( new DirectoryReference ( UEDir ) , "Engine" ) ;
string GameName = Config . BuildTarget ;
bool bIsClient = false ;
if ( ProjectPath = = null )
{
ProjectPath = EngineDir ;
}
if ( bIsUnrealGame )
{
ProjectPath = EngineDir ;
GameName = "UnrealGame" ;
2022-08-08 11:11:04 -04:00
bIsClient = ( UnrealData . AppName = = "UnrealClient" ) ;
2022-05-27 18:34:49 -04:00
}
if ( bCreateMacInfoPlist )
{
Directory . CreateDirectory ( Path . GetDirectoryName ( MacInfoPlistPath ) ! ) ;
UEDeployMac . GeneratePList ( ProjectPath . FullName , bIsUnrealGame , GameName , Config . BuildTarget , EngineDir . FullName , MacExecutableFileName ) ;
}
if ( bCreateIOSInfoPlist )
{
// get the receipt
FileReference ReceiptFilename ;
if ( bIsUnrealGame )
{
ReceiptFilename = TargetReceipt . GetDefaultPath ( Unreal . EngineDirectory , "UnrealGame" , UnrealTargetPlatform . IOS , Config . BuildConfig , "" ) ;
}
else
{
ReceiptFilename = TargetReceipt . GetDefaultPath ( ProjectPath , GameName , UnrealTargetPlatform . IOS , Config . BuildConfig , "" ) ;
}
Directory . CreateDirectory ( Path . GetDirectoryName ( IOSInfoPlistPath ) ! ) ;
2022-07-19 13:24:38 -04:00
bool bSupportPortrait , bSupportLandscape ;
2022-05-27 18:34:49 -04:00
TargetReceipt ? Receipt ;
TargetReceipt . TryRead ( ReceiptFilename , out Receipt ) ;
bool bBuildAsFramework = UEDeployIOS . GetCompileAsDll ( Receipt ) ;
2022-08-08 11:11:04 -04:00
UEDeployIOS . GenerateIOSPList ( UnrealData . UProjectFileLocation , Config . BuildConfig , ProjectPath . FullName , bIsUnrealGame , GameName , bIsClient , Config . BuildTarget , EngineDir . FullName , ProjectPath + "/Binaries/IOS/Payload" , null , UnrealData . BundleIdentifier , bBuildAsFramework , UnrealData . Logger ! , out bSupportPortrait , out bSupportLandscape ) ;
2022-05-27 18:34:49 -04:00
}
if ( bCreateTVOSInfoPlist )
{
Directory . CreateDirectory ( Path . GetDirectoryName ( TVOSInfoPlistPath ) ! ) ;
2022-08-08 11:11:04 -04:00
UEDeployTVOS . GenerateTVOSPList ( ProjectPath . FullName , bIsUnrealGame , GameName , bIsClient , Config . BuildTarget , EngineDir . FullName , ProjectPath + "/Binaries/TVOS/Payload" , null , UnrealData . BundleIdentifier , UnrealData . Logger ! ) ;
2022-05-27 18:34:49 -04:00
}
}
}
2022-08-10 14:30:33 -04:00
ConfigXcconfig . Write ( ) ;
2022-05-27 18:34:49 -04:00
}
}
2022-08-08 11:11:04 -04:00
}
2022-05-27 18:34:49 -04:00
2022-08-08 11:11:04 -04:00
class XcodeFileCollection
{
class ManualFileReference
2022-05-27 18:34:49 -04:00
{
2022-08-08 11:11:04 -04:00
public string Guid = XcodeProjectFileGenerator . MakeXcodeGuid ( ) ;
public string RelativePath = "" ;
public string FileType = "" ;
public string SourceTree = "" ;
public string GroupName = "" ;
}
// we need relativ paths, so this is the directory that paths are relative to
private DirectoryReference ProjectDirectory ;
public string MainGroupGuid = "" ;
//public string ProductRefGroupGuid = XcodeProjectFileGenerator.MakeXcodeGuid();
internal Dictionary < string , XcodeFileGroup > Groups = new ( ) ;
internal List < XcodeSourceFile > BuildableFiles = new ( ) ;
internal List < XcodeSourceFile > AllFiles = new ( ) ;
private List < ManualFileReference > ManualFiles = new ( ) ;
private Dictionary < string , string > GuidsForGroups = new ( ) ;
public XcodeFileCollection ( XcodeProjectFile ProjectFile )
{
ProjectDirectory = ProjectFile . ProjectFilePath . Directory ;
}
public void ProcessFile ( XcodeSourceFile File , bool bIsForBuild )
{
//string FileName = File.Reference.GetFileName();
//string FileExtension = Path.GetExtension(FileName);
//string FilePath = File.Reference.MakeRelativeTo(ProjectFile.Directory);
//string FilePathMac = Utils.CleanDirectorySeparators(FilePath, '/');
//string FileGuid = File.FileGuid;
//string FileRefGuid = File.FileRefGuid;
// this is the main entry into the project for the file tree on the left of Xcode
// AddFileReference(FileRefGuid, FilePathMac, GetFileType(FileExtension));
AllFiles . Add ( File ) ;
// remember all buildable files - we may further filter this later when looking for SubTargets - the module the file is
// in may not be built, which means it cannot be indexed
if ( bIsForBuild & & ShouldIncludeFileInBuildPhaseSection ( File ) )
{
BuildableFiles . Add ( File ) ;
}
// group the files by path
XcodeFileGroup ? Group = FindGroupByAbsolutePath ( File . Reference . Directory . FullName ) ;
if ( Group ! = null )
{
Group . Files . Add ( File ) ;
}
}
/// <summary>
/// The project needs to point to the product group, so we expose the Guid here
/// </summary>
/// <returns></returns>
public string GetProductGroupGuid ( )
{
return GuidsForGroups [ "Products" ] ;
}
public void AddFileReference ( string Guid , string RelativePath , string FileType , string SourceTree /*="SOURCE_ROOT"*/ , string GroupName )
{
ManualFiles . Add ( new ManualFileReference ( )
{
Guid = Guid ,
RelativePath = RelativePath ,
FileType = FileType ,
SourceTree = SourceTree ,
GroupName = GroupName ,
} ) ;
// make sure each unique group has a guid
if ( ! GuidsForGroups . ContainsKey ( GroupName ) )
{
GuidsForGroups [ GroupName ] = XcodeProjectFileGenerator . MakeXcodeGuid ( ) ;
}
2022-05-27 18:34:49 -04:00
}
2022-08-08 11:11:04 -04:00
public void Write ( StringBuilder Content )
2022-05-27 18:34:49 -04:00
{
2022-08-08 11:11:04 -04:00
Content . WriteLine ( "/* Begin PBXBuildFile section */" ) ;
foreach ( XcodeSourceFile File in BuildableFiles )
2022-05-27 18:34:49 -04:00
{
2022-08-08 11:11:04 -04:00
Content . WriteLine ( $"\t\t{File.FileGuid} = {{isa = PBXBuildFile; fileRef = {File.FileRefGuid}; }}; /* {File.Reference.GetFileName()} */" ) ;
2022-05-27 18:34:49 -04:00
}
2022-08-08 11:11:04 -04:00
Content . WriteLine ( "/* End PBXBuildFile section */" ) ;
Content . WriteLine ( ) ;
2022-05-27 18:34:49 -04:00
2022-08-08 11:11:04 -04:00
Content . WriteLine ( "/* Begin PBXFileReference section */" ) ;
foreach ( XcodeSourceFile File in AllFiles )
2022-05-27 18:34:49 -04:00
{
2022-08-08 11:11:04 -04:00
string FileName = File . Reference . GetFileName ( ) ;
string FileExtension = Path . GetExtension ( FileName ) ;
string FileType = GetFileType ( FileExtension ) ;
string RelativePath = Utils . CleanDirectorySeparators ( File . Reference . MakeRelativeTo ( ProjectDirectory ) , '/' ) ;
string SourceTree = "SOURCE_ROOT" ;
2022-05-27 18:34:49 -04:00
2022-08-08 11:11:04 -04:00
Content . WriteLine ( $"\t\t{File.FileRefGuid} = {{isa = PBXFileReference; explicitFileType = {FileType}; name = \" { FileName } \ "; path = \"{RelativePath}\"; sourceTree = {SourceTree}; }};" ) ;
}
foreach ( ManualFileReference File in ManualFiles )
2022-05-27 18:34:49 -04:00
{
2022-08-08 11:11:04 -04:00
string FileName = Path . GetFileName ( File . RelativePath ) ;
Content . WriteLine ( $"\t\t{File.Guid} = {{isa = PBXFileReference; explicitFileType = {File.FileType}; name = \" { FileName } \ "; path = \"{File.RelativePath}\"; sourceTree = {File.SourceTree}; }};" ) ;
2022-05-27 18:34:49 -04:00
}
2022-08-08 11:11:04 -04:00
Content . WriteLine ( "/* End PBXFileReference section */" ) ;
Content . WriteLine ( ) ;
2022-05-27 18:34:49 -04:00
2022-08-08 11:11:04 -04:00
WriteGroups ( Content ) ;
2022-05-27 18:34:49 -04:00
}
2022-08-08 11:11:04 -04:00
private void WriteGroups ( StringBuilder Content /*, List<UnrealExtensionInfo> AllExtensions*/ )
2022-05-27 18:34:49 -04:00
{
2022-08-08 11:11:04 -04:00
XcodeFileGroup ? RootGroup = FindRootFileGroup ( Groups ) ;
if ( RootGroup = = null )
2022-05-27 18:34:49 -04:00
{
2022-08-08 11:11:04 -04:00
return ;
2022-05-27 18:34:49 -04:00
}
2022-08-08 11:11:04 -04:00
string XcconfigsRefGroupGuid = XcodeProjectFileGenerator . MakeXcodeGuid ( ) ;
2022-05-27 18:34:49 -04:00
2022-08-08 11:11:04 -04:00
Content . WriteLine ( "/* Begin PBXGroup section */" ) ;
2022-05-27 18:34:49 -04:00
2022-08-08 11:11:04 -04:00
// write main/root group and it's children (adding the manual groups to the main group, which is the true param here0
WriteGroup ( Content , RootGroup , bIsRootGroup : true ) ;
// write some manual groups
foreach ( var Pair in GuidsForGroups )
2022-05-27 18:34:49 -04:00
{
2022-08-08 11:11:04 -04:00
Content . WriteLine ( $"\t\t{Pair.Value} /* {Pair.Key} */ = {{" ) ;
Content . WriteLine ( "\t\t\tisa = PBXGroup;" ) ;
Content . WriteLine ( "\t\t\tchildren = (" ) ;
foreach ( ManualFileReference Ref in ManualFiles . Where ( x = > x . GroupName = = Pair . Key ) )
2022-05-27 18:34:49 -04:00
{
2022-08-08 11:11:04 -04:00
Content . WriteLine ( $"\t\t\t\t{Ref.Guid} /* {Path.GetFileName(Ref.RelativePath)} */," ) ;
}
Content . WriteLine ( "\t\t\t);" ) ;
Content . WriteLine ( $"\t\t\tname = {Pair.Key};" ) ;
Content . WriteLine ( "\t\t\tsourceTree = \"<group>\";" ) ;
Content . WriteLine ( "\t\t};" ) ;
}
// Products group
// Content.WriteLine($"\t\t\t\t{XcodeProject.TargetAppGuid} /* {XcodeProject.TargetName} */,");
// @todo extensions
//foreach (UnrealExtensionInfo EI in AllExtensions)
//{
// Content.WriteLine(string.Format("\t\t\t\t{0} /* {1} */,{2}", EI.ProductGuid, EI.Name, ProjectFileGenerator.NewLine));
//}
// Xcconfigs group
//Content.WriteLine($"\t\t{XcconfigsRefGroupGuid} /* XcconfigsRefGroupGuid */ = {{");
// Content.WriteLine("\t\t\tisa = PBXGroup;");
// Content.WriteLine("\t\t\tchildren = (");
// foreach (XcconfigFile Xcconfig in XcodeProject.AllXcconfigFiles)
// {
// Content.WriteLine($"\t\t\t\t{Xcconfig.Guid} /* {Xcconfig.Filename} */,{ProjectFileGenerator.NewLine}");
// }
// Content.WriteLine("\t\t\t);");
// Content.WriteLine("\t\t\tname = Xcconfigs;");
// Content.WriteLine("\t\t\tsourceTree = \"<group>\";");
//Content.WriteLine("\t\t};");
Content . WriteLine ( "/* End PBXGroup section */" ) ;
}
private void WriteGroup ( StringBuilder Content , XcodeFileGroup Group , bool bIsRootGroup )
{
if ( ! Group . bIsReference )
{
Content . WriteLine ( $"\t\t{Group.GroupGuid} = {{" ) ;
Content . WriteLine ( "\t\t\tisa = PBXGroup;" ) ;
Content . WriteLine ( "\t\t\tchildren = (" ) ;
foreach ( XcodeFileGroup ChildGroup in Group . Children . Values )
{
Content . WriteLine ( $"\t\t\t\t{ChildGroup.GroupGuid} /* {ChildGroup.GroupName} */," ) ;
}
foreach ( XcodeSourceFile File in Group . Files )
{
Content . WriteLine ( $"\t\t\t\t{File.FileRefGuid} /* {File.Reference.GetFileName()} */," ) ;
}
if ( bIsRootGroup )
{
foreach ( var Pair in GuidsForGroups )
2022-05-27 18:34:49 -04:00
{
2022-08-08 11:11:04 -04:00
Content . WriteLine ( $"\t\t\t\t{Pair.Value} /* {Pair.Key} */," ) ;
}
}
Content . WriteLine ( "\t\t\t);" ) ;
if ( ! bIsRootGroup )
{
Content . WriteLine ( "\t\t\tname = \"" + Group . GroupName + "\";" ) ;
Content . WriteLine ( "\t\t\tpath = \"" + Group . GroupPath + "\";" ) ;
Content . WriteLine ( "\t\t\tsourceTree = \"<absolute>\";" ) ;
}
else
{
Content . WriteLine ( "\t\t\tsourceTree = \"<group>\";" ) ;
}
Content . WriteLine ( "\t\t};" ) ;
2022-05-27 18:34:49 -04:00
2022-08-08 11:11:04 -04:00
foreach ( XcodeFileGroup ChildGroup in Group . Children . Values )
{
WriteGroup ( Content , ChildGroup , bIsRootGroup : false ) ;
}
}
}
2022-05-27 18:34:49 -04:00
2022-08-08 11:11:04 -04:00
public static string GetRootGroupGuid ( Dictionary < string , XcodeFileGroup > GroupsDict )
{
return FindRootFileGroup ( GroupsDict ) ! . GroupGuid ;
}
2022-05-27 18:34:49 -04:00
2022-08-08 11:11:04 -04:00
private static XcodeFileGroup ? FindRootFileGroup ( Dictionary < string , XcodeFileGroup > GroupsDict )
{
foreach ( XcodeFileGroup Group in GroupsDict . Values )
{
if ( Group . Children . Count > 1 | | Group . Files . Count > 0 )
{
return Group ;
}
else
{
XcodeFileGroup ? Found = FindRootFileGroup ( Group . Children ) ;
if ( Found ! = null )
{
return Found ;
2022-05-27 18:34:49 -04:00
}
}
}
2022-08-08 11:11:04 -04:00
return null ;
2022-05-27 18:34:49 -04:00
}
2022-08-08 11:11:04 -04:00
/// <summary>
/// Gets Xcode file category based on its extension
/// </summary>
internal static string GetFileCategory ( string Extension )
2022-05-27 18:34:49 -04:00
{
2022-08-08 11:11:04 -04:00
// @todo Mac: Handle more categories
switch ( Extension )
2022-05-27 18:34:49 -04:00
{
2022-08-08 11:11:04 -04:00
case ".framework" :
return "Frameworks" ;
default :
return "Sources" ;
2022-05-27 18:34:49 -04:00
}
}
2022-08-08 11:11:04 -04:00
/// <summary>
/// Gets Xcode file type based on its extension
/// </summary>
internal static string GetFileType ( string Extension )
{
// @todo Mac: Handle more file types
switch ( Extension )
{
case ".c" :
case ".m" :
return "sourcecode.c.objc" ;
case ".cc" :
case ".cpp" :
case ".mm" :
return "sourcecode.cpp.objcpp" ;
case ".h" :
case ".inl" :
case ".pch" :
return "sourcecode.c.h" ;
case ".framework" :
return "wrapper.framework" ;
case ".plist" :
return "text.plist.xml" ;
case ".png" :
return "image.png" ;
case ".icns" :
return "image.icns" ;
default :
return "file.text" ;
}
}
/// <summary>
/// Returns true if Extension is a known extension for files containing source code
/// </summary>
internal static bool IsSourceCode ( string Extension )
{
return Extension = = ".c" | | Extension = = ".cc" | | Extension = = ".cpp" | | Extension = = ".m" | | Extension = = ".mm" ;
}
static string [ ] ExcludedFolders = PlatformExports . GetExcludedFolderNames ( UnrealTargetPlatform . Mac ) ;
internal static bool ShouldIncludeFileInBuildPhaseSection ( XcodeSourceFile SourceFile )
{
string FileExtension = SourceFile . Reference . GetExtension ( ) ;
if ( IsSourceCode ( FileExtension ) )
{
// look if it contains any of the exluded names, and is so, don't include it
return ! ExcludedFolders . Any ( x = > SourceFile . Reference . ContainsName ( x , 0 ) ) ;
}
return false ;
}
/// <summary>
/// Returns a project navigator group to which the file should belong based on its path.
/// Creates a group tree if it doesn't exist yet.
/// </summary>
internal XcodeFileGroup ? FindGroupByAbsolutePath ( string AbsolutePath )
{
string [ ] Parts = AbsolutePath . Split ( Path . DirectorySeparatorChar ) ;
string CurrentPath = "/" ;
Dictionary < string , XcodeFileGroup > CurrentSubGroups = Groups ;
for ( int Index = 1 ; Index < Parts . Count ( ) ; + + Index )
{
string Part = Parts [ Index ] ;
if ( CurrentPath . Length > 1 )
{
CurrentPath + = Path . DirectorySeparatorChar ;
}
CurrentPath + = Part ;
XcodeFileGroup CurrentGroup ;
if ( ! CurrentSubGroups . ContainsKey ( CurrentPath ) )
{
CurrentGroup = new XcodeFileGroup ( Path . GetFileName ( CurrentPath ) , CurrentPath , CurrentPath . EndsWith ( ".xcassets" ) ) ;
if ( Groups . Count = = 0 )
{
MainGroupGuid = CurrentGroup . GroupGuid ;
}
CurrentSubGroups . Add ( CurrentPath , CurrentGroup ) ;
}
else
{
CurrentGroup = CurrentSubGroups [ CurrentPath ] ;
}
if ( CurrentPath = = AbsolutePath )
{
return CurrentGroup ;
}
CurrentSubGroups = CurrentGroup . Children ;
}
return null ;
}
/// <summary>
/// Convert all paths to Apple/Unix format (with forward slashes)
/// </summary>
/// <param name="InPath">The path to convert</param>
/// <returns>The normalized path</returns>
internal static string ConvertPath ( string InPath )
{
return InPath . Replace ( "\\" , "/" ) ;
}
}
class XcodeProjectFile : ProjectFile
{
/// <summary>
/// Constructs a new project file object
/// </summary>
/// <param name="InitFilePath">The path to the project file on disk</param>
/// <param name="BaseDir">The base directory for files within this project</param>
/// <param name="bIsForDistribution">True for distribution builds</param>
/// <param name="BundleID">Override option for bundle identifier</param>
/// <param name="AppName"></param>
public XcodeProjectFile ( FileReference InitFilePath , DirectoryReference BaseDir , bool bIsForDistribution , string BundleID , string AppName )
: base ( InitFilePath , BaseDir )
{
UnrealData = new UnrealData ( ) ;
UnrealData . bForDistribution = bIsForDistribution ;
UnrealData . BundleIdentifier = BundleID ;
UnrealData . AppName = AppName ;
// create the container for all the files that will
FileCollection = new XcodeFileCollection ( this ) ;
}
public UnrealData UnrealData ;
/// <summary>
/// The PBXPRoject node, root of everything
/// </summary>
public XcodeProject ? RootProject ;
/// <summary>
/// Gathers the files and generates project sections
/// </summary>
public XcodeFileCollection FileCollection ;
/// <summary>
/// Allocates a generator-specific source file object
/// </summary>
/// <param name="InitFilePath">Path to the source file on disk</param>
/// <param name="InitProjectSubFolder">Optional sub-folder to put the file in. If empty, this will be determined automatically from the file's path relative to the project file</param>
/// <returns>The newly allocated source file object</returns>
public override SourceFile ? AllocSourceFile ( FileReference InitFilePath , DirectoryReference ? InitProjectSubFolder )
{
if ( InitFilePath . GetFileName ( ) . StartsWith ( "." ) )
{
return null ;
}
return new XcodeSourceFile ( InitFilePath , InitProjectSubFolder ) ;
}
public override void AddModule ( UEBuildModuleCPP Module , CppCompileEnvironment CompileEnvironment )
{
UnrealData . AddModule ( Module , CompileEnvironment ) ;
}
/// <summary>
/// Generates bodies of all sections that contain a list of source files plus a dictionary of project navigator groups.
/// </summary>
private void ProcessSourceFiles ( )
{
// process the files that came from UE/cross-platform land
SourceFiles . SortBy ( x = > x . Reference . FullName ) ;
foreach ( XcodeSourceFile SourceFile in SourceFiles . OfType < XcodeSourceFile > ( ) )
{
FileCollection . ProcessFile ( SourceFile , bIsForBuild : IsGeneratedProject ) ;
}
2022-08-10 14:30:33 -04:00
// cache the main group
FileCollection . MainGroupGuid = XcodeFileCollection . GetRootGroupGuid ( FileCollection . Groups ) ;
2022-08-08 11:11:04 -04:00
UnrealData . FilterBuildableFiles ( FileCollection ) ;
}
/// Implements Project interface
public override bool WriteProjectFile ( List < UnrealTargetPlatform > InPlatforms , List < UnrealTargetConfiguration > InConfigurations , PlatformProjectGeneratorCollection PlatformProjectGenerators , ILogger Logger )
{
if ( UnrealData . Initialize ( this , InPlatforms , InConfigurations , Logger ) = = false )
{
// if we failed to initialize, we silently return to move on (it's not an error, it's a project with nothing to do)
return true ;
}
2022-08-10 14:30:33 -04:00
// look for an existing project to use as a template (if none found, create one from scratch)
DirectoryReference BuildDirLocation = UnrealData . UProjectFileLocation = = null ? Unreal . EngineDirectory : UnrealData . UProjectFileLocation . Directory ;
string ExistingProjectName = UnrealData . XcodeProjectFileLocation ! . GetFileNameWithoutAnyExtensions ( ) ;
FileReference TemplateProject = FileReference . Combine ( BuildDirLocation , "Build" , "IOS" , ExistingProjectName + ".xcodeproj" , "project.pbxproj" ) ;
UnrealData . bIsMergingProjects = FileReference . Exists ( TemplateProject ) ;
UnrealData . bWriteCodeSigningSettings = ! UnrealData . bIsMergingProjects ;
2022-08-08 11:11:04 -04:00
// turn all UE files into internal representation
ProcessSourceFiles ( ) ;
// now create the xcodeproject elements (project -> target -> buildconfigs, etc)
RootProject = new XcodeProject ( UnrealData , FileCollection ) ;
if ( TemplateProject . FullName . Contains ( "ThirdPerson" ) )
{
Console . WriteLine ( "foo" ) ;
}
bool bSuccess ;
if ( FileReference . Exists ( TemplateProject ) )
{
2022-08-10 14:30:33 -04:00
bSuccess = MergeIntoTemplateProject ( TemplateProject ) ;
2022-08-08 11:11:04 -04:00
}
else
{
2022-08-10 14:30:33 -04:00
FileReference PBXProjFilePath = ProjectFilePath + "/project.pbxproj" ;
StringBuilder Content = new StringBuilder ( ) ;
2022-08-08 11:11:04 -04:00
Content . WriteLine ( 0 , "// !$*UTF8*$!" ) ;
Content . WriteLine ( 0 , "{" ) ;
Content . WriteLine ( 1 , "archiveVersion = 1;" ) ;
Content . WriteLine ( 1 , "classes = {" ) ;
Content . WriteLine ( 1 , "};" ) ;
Content . WriteLine ( 1 , "objectVersion = 46;" ) ;
Content . WriteLine ( 1 , "objects = {" ) ;
// write out the list of files and groups
FileCollection . Write ( Content ) ;
// now write out the project node and its recursive dependent nodes
XcodeProjectNode . WriteNodeAndReferences ( Content , RootProject ) ;
Content . WriteLine ( 1 , "};" ) ;
Content . WriteLine ( 1 , $"rootObject = {RootProject.Guid} /* Project object */;" ) ;
Content . WriteLine ( 0 , "}" ) ;
// finally write out the pbxproj file!
bSuccess = ProjectFileGenerator . WriteFileIfChanged ( PBXProjFilePath . FullName , Content . ToString ( ) , Logger , new UTF8Encoding ( ) ) ;
}
bool bNeedScheme = UnrealData . CanBuildProjectLocally ( this , Logger ) ;
if ( bNeedScheme )
{
if ( bSuccess )
{
string TargetName = ProjectFilePath . GetFileNameWithoutAnyExtensions ( ) ;
string RunTargetGuid = XcodeProjectNode . GetNodesOfType < XcodeRunTarget > ( RootProject ) . First ( ) ! . Guid ;
string? BuildTargetGuid = XcodeProjectNode . GetNodesOfType < XcodeBuildTarget > ( RootProject ) . FirstOrDefault ( ) ? . Guid ;
string? IndexTargetGuid = XcodeProjectNode . GetNodesOfType < XcodeIndexTarget > ( RootProject ) . FirstOrDefault ( ) ? . Guid ;
WriteSchemeFile ( TargetName , RunTargetGuid , BuildTargetGuid , IndexTargetGuid , UnrealData . bHasEditorConfiguration , UnrealData . UProjectFileLocation ! = null ? UnrealData . UProjectFileLocation . FullName : "" ) ;
}
}
else
{
// clean this up because we don't want it persisting if we narrow our project list
DirectoryReference SchemeDir = GetProjectSchemeDirectory ( ) ;
if ( DirectoryReference . Exists ( SchemeDir ) )
{
DirectoryReference . Delete ( SchemeDir , true ) ;
}
}
return bSuccess ;
}
2022-08-10 14:30:33 -04:00
private string Plist ( string Command )
{
Command = Command . Replace ( "\"" , "\\\"" ) ;
string Output = Utils . RunLocalProcessAndReturnStdOut ( "/usr/libexec/PlistBuddy" , $"-c \" { Command } \ " \"{ProjectFilePath.FullName}/project.pbxproj\"" ) ;
//Console.WriteLine($"{Command} ==> {Output}");
return Output ;
}
private void PlistSetAdd ( string Entry , string Value , string Type = "string" )
{
string AddOutput = Plist ( $"Add {Entry} {Type} {Value}" ) ;
// error will be non-empty string
if ( AddOutput ! = "" )
{
Plist ( $"Set {Entry} {Value}" ) ;
}
}
private 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 ;
}
private IEnumerable < string > PlistArray ( string Entry )
{
return Plist ( $"Print {Entry}" )
. Replace ( "Array {" , "" )
. Replace ( "}" , "" )
. Trim ( )
. ReplaceLineEndings ( )
. Split ( Environment . NewLine )
. Select ( x = > x . Trim ( ) ) ;
}
private List < string > PlistObjects ( )
{
List < string > Result = new ( ) ;
IEnumerable < string > Lines = Plist ( "print :objects" )
. ReplaceLineEndings ( )
. Split ( Environment . NewLine ) ;
Regex Regex = new Regex ( "^ (\\S*) = Dict {$" ) ;
foreach ( string Line in Lines )
{
Match Match = Regex . Match ( Line ) ;
if ( Match . Success )
{
Result . Add ( Match . Groups [ 1 ] . Value ) ;
}
}
return Result ;
}
private 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 ;
}
bool MergeIntoTemplateProject ( FileReference TemplateProject )
{
FileReference PBXProjFilePath = ProjectFilePath + "/project.pbxproj" ;
// copy existing template project to final location
if ( FileReference . Exists ( PBXProjFilePath ) )
{
FileReference . Delete ( PBXProjFilePath ) ;
}
DirectoryReference . CreateDirectory ( PBXProjFilePath . Directory ) ;
FileReference . Copy ( TemplateProject , PBXProjFilePath ) ;
// write the nodes we need to add (Build/Index targets)
XcodeRunTarget RunTarget = XcodeProjectNode . GetNodesOfType < XcodeRunTarget > ( RootProject ! ) . First ( ) ;
XcodeBuildTarget BuildTarget = XcodeProjectNode . GetNodesOfType < XcodeBuildTarget > ( RunTarget ) . First ( ) ;
XcodeIndexTarget IndexTarget = XcodeProjectNode . GetNodesOfType < XcodeIndexTarget > ( RootProject ! ) . First ( ) ;
XcodeDependency BuildDependency = XcodeProjectNode . GetNodesOfType < XcodeDependency > ( RunTarget ) . First ( ) ;
// the runtarget and project need to write out so all of their xcconfigs get written as well,
// so write everything to a temp string that is tossed, but all xcconfigs will be done at least
StringBuilder Temp = new StringBuilder ( ) ;
XcodeProjectNode . WriteNodeAndReferences ( Temp , RootProject ! ) ;
StringBuilder Content = new StringBuilder ( ) ;
Content . WriteLine ( 0 , "{" ) ;
FileCollection . Write ( Content ) ;
XcodeProjectNode . WriteNodeAndReferences ( Content , BuildTarget ) ;
XcodeProjectNode . WriteNodeAndReferences ( Content , IndexTarget ) ;
XcodeProjectNode . WriteNodeAndReferences ( Content , BuildDependency ) ;
Content . WriteLine ( 0 , "}" ) ;
// write to disk
FileReference ImportFile = FileReference . Combine ( PBXProjFilePath . Directory , "import.plist" ) ;
File . WriteAllText ( ImportFile . FullName , Content . ToString ( ) ) ;
// cache some standard guids from the template project
string ProjectGuid = Plist ( $"Print :rootObject" ) ;
string TemplateMainGroupGuid = Plist ( $"Print :objects:{ProjectGuid}:mainGroup" ) ;
// fixup paths that were relative to original project to be relative to merged project
// List<string> ObjectGuids = PlistObjects();
IEnumerable < string > MainGroupChildrenGuids = PlistArray ( $":objects:{TemplateMainGroupGuid}:children" ) ;
string RelativeFromMergedToTemplate = TemplateProject . Directory . ParentDirectory ! . MakeRelativeTo ( PBXProjFilePath . Directory . ParentDirectory ! ) ;
// look for groups with a 'path' element that is in the main group, so that it and everything will get redirected to new location
string? FixedPath ;
foreach ( string ChildGuid in MainGroupChildrenGuids )
{
string IsA = Plist ( $"Print :objects:{ChildGuid}:isa" ) ;
// if a Group has a path
if ( IsA = = "PBXGroup" )
{
if ( ( FixedPath = PlistFixPath ( $":objects:{ChildGuid}:path" , RelativeFromMergedToTemplate ) ) ! = null )
{
// if there wasn't a name before, it will now have a nasty path as the name, so add it now
PlistSetAdd ( $":objects:{ChildGuid}:name" , Path . GetFileName ( FixedPath ) ) ;
}
}
}
// and import it into the template
Plist ( $"Merge \" { ImportFile . FullName } \ " :objects" ) ;
// get all the targets in the template that are application types
IEnumerable < string > AppTargetGuids = PlistArray ( $":objects:{ProjectGuid}:targets" )
. Where ( TargetGuid = > ( Plist ( $"Print :objects:{TargetGuid}:productType" ) = = "com.apple.product-type.application" ) ) ;
// add a dependency on the build target from the app target(s)
foreach ( string AppTargetGuid in AppTargetGuids )
{
Plist ( $"Add :objects:{AppTargetGuid}:dependencies:0 string {BuildDependency.Guid}" ) ;
}
// the BuildDependency object was in the "container" of the generated project, not the merged one, so fix it up now
Plist ( $"Set :objects:{BuildDependency.ProxyGuid}:containerPortal {ProjectGuid}" ) ;
// now add all the non-run targets from the generated
foreach ( XcodeTarget Target in XcodeProjectNode . GetNodesOfType < XcodeTarget > ( RootProject ! ) . Where ( x = > x . GetType ( ) ! = typeof ( XcodeRunTarget ) ) )
{
Plist ( $"Add :objects:{ProjectGuid}:targets:0 string {Target.Guid}" ) ;
}
// hook up Xcconfig files to the project and the project configs
// @todo how to manage with conflicts already present...
//PlistSetAdd($":objects:{ProjectGuid}:baseConfigurationReference", RootProject.Xcconfig!.Guid, "string");
// re-get the list of targets now that we merged in the other file
IEnumerable < string > AllTargetGuids = PlistArray ( $":objects:{ProjectGuid}:targets" ) ;
List < string > NodesToFix = new ( ) { ProjectGuid } ;
NodesToFix . AddRange ( AllTargetGuids ) ;
bool bIsProject = true ;
foreach ( string NodeGuid in NodesToFix )
{
bool bIsAppTarget = AppTargetGuids . Contains ( NodeGuid ) ;
// get the config list, and from there we can get the configs
string ProjectBuildConfigListGuid = Plist ( $"Print :objects:{NodeGuid}:buildConfigurationList" ) ;
IEnumerable < string > ConfigGuids = PlistArray ( $":objects:{ProjectBuildConfigListGuid}:buildConfigurations" ) ;
foreach ( string ConfigGuid in ConfigGuids )
{
// find the matching unreal generated project build config to hook up to
// for now we assume Release is Development [Editor], but we should make sure the template project has good configs
// we have to rename the template config from Release because it won't find the matching config in the build target
string ConfigName = Plist ( $"Print :objects:{ConfigGuid}:name" ) ;
if ( ConfigName = = "Release" )
{
ConfigName = UnrealData . bHasEditorConfiguration ? "Development Editor" : "Development" ;
Plist ( $"Set :objects:{ConfigGuid}:name \" { ConfigName } \ "" ) ;
}
// if there's a plist path, then it will need to be fixed up
PlistFixPath ( $":objects:{ConfigGuid}:buildSettings:INFOPLIST_FILE" , RelativeFromMergedToTemplate ) ;
if ( bIsProject )
{
Console . WriteLine ( "Looking for " + ConfigName ) ;
XcodeBuildConfig Config = RootProject ! . ProjectBuildConfigs . BuildConfigs . First ( x = > x . Info . DisplayName = = ConfigName ) ;
PlistSetAdd ( $":objects:{ConfigGuid}:baseConfigurationReference" , Config . Xcconfig ! . Guid , "string" ) ;
}
// the Build target used some ini settings to compile, and Run target must match, so we override a few settings, at
// whatever level they were already specified at (Projet and/or Target)
PlistSetUpdate ( $":objects:{ConfigGuid}:buildSettings:MACOSX_DEPLOYMENT_TARGET" , MacToolChain . Settings . MacOSVersion ) ;
if ( UnrealData . IOSProjectSettings ! = null )
{
PlistSetUpdate ( $":objects:{ConfigGuid}:buildSettings:IPHONEOS_DEPLOYMENT_TARGET" , UnrealData . IOSProjectSettings . RuntimeVersion ) ;
}
if ( UnrealData . TVOSProjectSettings ! = null )
{
PlistSetUpdate ( $":objects:{ConfigGuid}:buildSettings:TVOS_DEPLOYMENT_TARGET" , UnrealData . TVOSProjectSettings . RuntimeVersion ) ;
}
}
bIsProject = false ;
}
// now we need to merge the main groups together
string GeneratedMainGroupGuid = FileCollection . MainGroupGuid ;
int Index = 0 ;
while ( true )
{
// we copy to a high index to put the copied entries at the end in the same order
string Output = Plist ( $"Copy :objects:{GeneratedMainGroupGuid}:children:{Index} :objects:{TemplateMainGroupGuid}:children:100000000" ) ;
// loop until error
if ( Output ! = "" )
{
break ;
}
Index + + ;
}
// and remove the one we copied from
Plist ( $"Delete :objects:{GeneratedMainGroupGuid}" ) ;
return true ;
}
#region Schemes
2022-08-08 11:11:04 -04:00
2022-05-27 18:34:49 -04:00
private FileReference GetUserSchemeManagementFilePath ( )
{
return new FileReference ( ProjectFilePath . FullName + "/xcuserdata/" + Environment . UserName + ".xcuserdatad/xcschemes/xcschememanagement.plist" ) ;
}
private DirectoryReference GetProjectSchemeDirectory ( )
{
return new DirectoryReference ( ProjectFilePath . FullName + "/xcshareddata/xcschemes" ) ;
}
private FileReference GetProjectSchemeFilePathForTarget ( string TargetName )
{
return FileReference . Combine ( GetProjectSchemeDirectory ( ) , TargetName + ".xcscheme" ) ;
}
2022-08-08 11:11:04 -04:00
private void WriteSchemeFile ( string TargetName , string TargetGuid , string? BuildTargetGuid , string? IndexTargetGuid , bool bHasEditorConfiguration , string GameProjectPath )
2022-05-27 18:34:49 -04:00
{
2022-08-08 11:11:04 -04:00
2022-05-27 18:34:49 -04:00
FileReference SchemeFilePath = GetProjectSchemeFilePathForTarget ( TargetName ) ;
DirectoryReference . CreateDirectory ( SchemeFilePath . Directory ) ;
string? OldCommandLineArguments = null ;
if ( FileReference . Exists ( SchemeFilePath ) )
{
string OldContents = File . ReadAllText ( SchemeFilePath . FullName ) ;
int OldCommandLineArgumentsStart = OldContents . IndexOf ( "<CommandLineArguments>" ) + "<CommandLineArguments>" . Length ;
int OldCommandLineArgumentsEnd = OldContents . IndexOf ( "</CommandLineArguments>" ) ;
if ( OldCommandLineArgumentsStart ! = - 1 & & OldCommandLineArgumentsEnd ! = - 1 )
{
OldCommandLineArguments = OldContents . Substring ( OldCommandLineArgumentsStart , OldCommandLineArgumentsEnd - OldCommandLineArgumentsStart ) ;
}
}
string DefaultConfiguration = bHasEditorConfiguration & & ! XcodeProjectFileGenerator . bGeneratingRunIOSProject & & ! XcodeProjectFileGenerator . bGeneratingRunTVOSProject ? "Development Editor" : "Development" ;
StringBuilder Content = new StringBuilder ( ) ;
2022-08-08 11:11:04 -04:00
Content . WriteLine ( "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" ) ;
Content . WriteLine ( "<Scheme" ) ;
Content . WriteLine ( " LastUpgradeVersion = \"2000\"" ) ;
Content . WriteLine ( " version = \"1.3\">" ) ;
Content . WriteLine ( " <BuildAction" ) ;
Content . WriteLine ( " parallelizeBuildables = \"YES\"" ) ;
Content . WriteLine ( " buildImplicitDependencies = \"YES\">" ) ;
Content . WriteLine ( " <BuildActionEntries>" ) ;
Content . WriteLine ( " <BuildActionEntry" ) ;
Content . WriteLine ( " buildForTesting = \"YES\"" ) ;
Content . WriteLine ( " buildForRunning = \"YES\"" ) ;
Content . WriteLine ( " buildForProfiling = \"YES\"" ) ;
Content . WriteLine ( " buildForArchiving = \"YES\"" ) ;
Content . WriteLine ( " buildForAnalyzing = \"YES\">" ) ;
Content . WriteLine ( " <BuildableReference" ) ;
Content . WriteLine ( " BuildableIdentifier = \"primary\"" ) ;
Content . WriteLine ( " BlueprintIdentifier = \"" + TargetGuid + "\"" ) ;
Content . WriteLine ( " BuildableName = \"" + TargetName + ".app\"" ) ;
Content . WriteLine ( " BlueprintName = \"" + TargetName + "\"" ) ;
Content . WriteLine ( " ReferencedContainer = \"container:" + TargetName + ".xcodeproj\">" ) ;
Content . WriteLine ( " </BuildableReference>" ) ;
Content . WriteLine ( " </BuildActionEntry>" ) ;
Content . WriteLine ( " </BuildActionEntries>" ) ;
Content . WriteLine ( " </BuildAction>" ) ;
Content . WriteLine ( " <TestAction" ) ;
Content . WriteLine ( " buildConfiguration = \"" + DefaultConfiguration + "\"" ) ;
Content . WriteLine ( " selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"" ) ;
Content . WriteLine ( " selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"" ) ;
Content . WriteLine ( " shouldUseLaunchSchemeArgsEnv = \"YES\">" ) ;
Content . WriteLine ( " <Testables>" ) ;
Content . WriteLine ( " </Testables>" ) ;
Content . WriteLine ( " <MacroExpansion>" ) ;
Content . WriteLine ( " <BuildableReference" ) ;
Content . WriteLine ( " BuildableIdentifier = \"primary\"" ) ;
Content . WriteLine ( " BlueprintIdentifier = \"" + TargetGuid + "\"" ) ;
Content . WriteLine ( " BuildableName = \"" + TargetName + ".app\"" ) ;
Content . WriteLine ( " BlueprintName = \"" + TargetName + "\"" ) ;
Content . WriteLine ( " ReferencedContainer = \"container:" + TargetName + ".xcodeproj\">" ) ;
Content . WriteLine ( " </BuildableReference>" ) ;
Content . WriteLine ( " </MacroExpansion>" ) ;
Content . WriteLine ( " <AdditionalOptions>" ) ;
Content . WriteLine ( " </AdditionalOptions>" ) ;
Content . WriteLine ( " </TestAction>" ) ;
Content . WriteLine ( " <LaunchAction" ) ;
Content . WriteLine ( " buildConfiguration = \"" + DefaultConfiguration + "\"" ) ;
Content . WriteLine ( " selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"" ) ;
Content . WriteLine ( " selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"" ) ;
Content . WriteLine ( " launchStyle = \"0\"" ) ;
Content . WriteLine ( " useCustomWorkingDirectory = \"NO\"" ) ;
Content . WriteLine ( " ignoresPersistentStateOnLaunch = \"NO\"" ) ;
Content . WriteLine ( " debugDocumentVersioning = \"YES\"" ) ;
Content . WriteLine ( " debugServiceExtension = \"internal\"" ) ;
Content . WriteLine ( " allowLocationSimulation = \"YES\">" ) ;
Content . WriteLine ( " <BuildableProductRunnable" ) ;
Content . WriteLine ( " runnableDebuggingMode = \"0\">" ) ;
Content . WriteLine ( " <BuildableReference" ) ;
Content . WriteLine ( " BuildableIdentifier = \"primary\"" ) ;
Content . WriteLine ( " BlueprintIdentifier = \"" + TargetGuid + "\"" ) ;
Content . WriteLine ( " BuildableName = \"" + TargetName + ".app\"" ) ;
Content . WriteLine ( " BlueprintName = \"" + TargetName + "\"" ) ;
Content . WriteLine ( " ReferencedContainer = \"container:" + TargetName + ".xcodeproj\">" ) ;
Content . WriteLine ( " </BuildableReference>" ) ;
Content . WriteLine ( " </BuildableProductRunnable>" ) ;
2022-05-27 18:34:49 -04:00
if ( string . IsNullOrEmpty ( OldCommandLineArguments ) )
{
if ( bHasEditorConfiguration & & TargetName ! = "UE5" )
{
2022-08-08 11:11:04 -04:00
Content . WriteLine ( " <CommandLineArguments>" ) ;
2022-05-27 18:34:49 -04:00
if ( IsForeignProject )
{
2022-08-08 11:11:04 -04:00
Content . WriteLine ( " <CommandLineArgument" ) ;
Content . WriteLine ( " argument = \""" + GameProjectPath + ""\"" ) ;
Content . WriteLine ( " isEnabled = \"YES\">" ) ;
Content . WriteLine ( " </CommandLineArgument>" ) ;
2022-05-27 18:34:49 -04:00
}
else
{
2022-08-08 11:11:04 -04:00
Content . WriteLine ( " <CommandLineArgument" ) ;
Content . WriteLine ( " argument = \"" + TargetName + "\"" ) ;
Content . WriteLine ( " isEnabled = \"YES\">" ) ;
Content . WriteLine ( " </CommandLineArgument>" ) ;
2022-05-27 18:34:49 -04:00
}
// Always add a configuration argument
2022-08-08 11:11:04 -04:00
Content . WriteLine ( " <CommandLineArgument" ) ;
Content . WriteLine ( " argument = \"-RunConfig=$(Configuration)\"" ) ;
Content . WriteLine ( " isEnabled = \"YES\">" ) ;
Content . WriteLine ( " </CommandLineArgument>" ) ;
Content . WriteLine ( " </CommandLineArguments>" ) ;
2022-05-27 18:34:49 -04:00
}
}
else
{
2022-08-08 11:11:04 -04:00
Content . WriteLine ( " <CommandLineArguments>" + OldCommandLineArguments + "</CommandLineArguments>" ) ;
2022-05-27 18:34:49 -04:00
}
2022-08-08 11:11:04 -04:00
Content . WriteLine ( " <AdditionalOptions>" ) ;
Content . WriteLine ( " </AdditionalOptions>" ) ;
Content . WriteLine ( " </LaunchAction>" ) ;
Content . WriteLine ( " <ProfileAction" ) ;
Content . WriteLine ( " buildConfiguration = \"" + DefaultConfiguration + "\"" ) ;
Content . WriteLine ( " shouldUseLaunchSchemeArgsEnv = \"YES\"" ) ;
Content . WriteLine ( " savedToolIdentifier = \"\"" ) ;
Content . WriteLine ( " useCustomWorkingDirectory = \"NO\"" ) ;
Content . WriteLine ( " debugDocumentVersioning = \"YES\">" ) ;
Content . WriteLine ( " <BuildableProductRunnable" ) ;
Content . WriteLine ( " runnableDebuggingMode = \"0\">" ) ;
Content . WriteLine ( " <BuildableReference" ) ;
Content . WriteLine ( " BuildableIdentifier = \"primary\"" ) ;
Content . WriteLine ( " BlueprintIdentifier = \"" + TargetGuid + "\"" ) ;
Content . WriteLine ( " BuildableName = \"" + TargetName + ".app\"" ) ;
Content . WriteLine ( " BlueprintName = \"" + TargetName + "\"" ) ;
Content . WriteLine ( " ReferencedContainer = \"container:" + TargetName + ".xcodeproj\">" ) ;
Content . WriteLine ( " </BuildableReference>" ) ;
Content . WriteLine ( " </BuildableProductRunnable>" ) ;
Content . WriteLine ( " </ProfileAction>" ) ;
Content . WriteLine ( " <AnalyzeAction" ) ;
Content . WriteLine ( " buildConfiguration = \"" + DefaultConfiguration + "\">" ) ;
Content . WriteLine ( " </AnalyzeAction>" ) ;
Content . WriteLine ( " <ArchiveAction" ) ;
Content . WriteLine ( " buildConfiguration = \"" + DefaultConfiguration + "\"" ) ;
Content . WriteLine ( " revealArchiveInOrganizer = \"YES\">" ) ;
Content . WriteLine ( " </ArchiveAction>" ) ;
Content . WriteLine ( "</Scheme>" ) ;
2022-05-27 18:34:49 -04:00
File . WriteAllText ( SchemeFilePath . FullName , Content . ToString ( ) , new UTF8Encoding ( ) ) ;
Content . Clear ( ) ;
2022-08-08 11:11:04 -04:00
Content . WriteLine ( "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" ) ;
Content . WriteLine ( "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">" ) ;
Content . WriteLine ( "<plist version=\"1.0\">" ) ;
Content . WriteLine ( "<dict>" ) ;
Content . WriteLine ( "\t<key>SchemeUserState</key>" ) ;
Content . WriteLine ( "\t<dict>" ) ;
Content . WriteLine ( "\t\t<key>" + TargetName + ".xcscheme_^#shared#^_</key>" ) ;
Content . WriteLine ( "\t\t<dict>" ) ;
Content . WriteLine ( "\t\t\t<key>orderHint</key>" ) ;
Content . WriteLine ( "\t\t\t<integer>1</integer>" ) ;
Content . WriteLine ( "\t\t</dict>" ) ;
Content . WriteLine ( "\t</dict>" ) ;
Content . WriteLine ( "\t<key>SuppressBuildableAutocreation</key>" ) ;
Content . WriteLine ( "\t<dict>" ) ;
Content . WriteLine ( "\t\t<key>" + TargetGuid + "</key>" ) ;
Content . WriteLine ( "\t\t<dict>" ) ;
Content . WriteLine ( "\t\t\t<key>primary</key>" ) ;
Content . WriteLine ( "\t\t\t<true/>" ) ;
Content . WriteLine ( "\t\t</dict>" ) ;
if ( BuildTargetGuid ! = null )
{
Content . WriteLine ( "\t\t<key>" + BuildTargetGuid + "</key>" ) ;
Content . WriteLine ( "\t\t<dict>" ) ;
Content . WriteLine ( "\t\t\t<key>primary</key>" ) ;
Content . WriteLine ( "\t\t\t<true/>" ) ;
Content . WriteLine ( "\t\t</dict>" ) ;
}
if ( IndexTargetGuid ! = null )
{
Content . WriteLine ( "\t\t<key>" + IndexTargetGuid + "</key>" ) ;
Content . WriteLine ( "\t\t<dict>" ) ;
Content . WriteLine ( "\t\t\t<key>primary</key>" ) ;
Content . WriteLine ( "\t\t\t<true/>" ) ;
Content . WriteLine ( "\t\t</dict>" ) ;
}
Content . WriteLine ( "\t</dict>" ) ;
Content . WriteLine ( "</dict>" ) ;
Content . WriteLine ( "</plist>" ) ;
2022-05-27 18:34:49 -04:00
FileReference ManagementFile = GetUserSchemeManagementFilePath ( ) ;
if ( ! DirectoryReference . Exists ( ManagementFile . Directory ) )
{
DirectoryReference . CreateDirectory ( ManagementFile . Directory ) ;
}
File . WriteAllText ( ManagementFile . FullName , Content . ToString ( ) , new UTF8Encoding ( ) ) ;
}
2022-08-08 11:11:04 -04:00
#endregion
2022-07-19 13:24:38 -04:00
2022-08-08 11:11:04 -04:00
#region Utilities
public override string ToString ( )
2022-07-14 13:20:15 -04:00
{
2022-08-08 11:11:04 -04:00
return ProjectFilePath . GetFileNameWithoutExtension ( ) ;
2022-07-14 13:20:15 -04:00
}
2022-05-27 18:34:49 -04:00
2022-08-08 11:11:04 -04:00
#endregion
2022-05-27 18:34:49 -04:00
}
}