// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using EpicGames.Core;
using System.Linq;
namespace UnrealBuildTool
{
///
/// Xcode project file generator implementation
///
class XcodeProjectFileGenerator : ProjectFileGenerator
{
// always seed the random number the same, so multiple runs of the generator will generate the same project
static Random Rand = new Random(0);
///
/// Mark for distribution builds
///
bool bForDistribution = false;
///
/// Override BundleID
///
string BundleIdentifier = "";
///
/// Override AppName
///
string AppName = "";
public XcodeProjectFileGenerator(FileReference? InOnlyGameProject, CommandLineArguments CommandLine)
: base(InOnlyGameProject)
{
if (CommandLine.HasOption("-distribution"))
{
bForDistribution = true;
}
if (CommandLine.HasValue("-bundleID="))
{
BundleIdentifier = CommandLine.GetString("-bundleID=");
}
if (CommandLine.HasValue("-appname="))
{
AppName = CommandLine.GetString("-appname=");
}
}
///
/// Make a random Guid string usable by Xcode (24 characters exactly)
///
public static string MakeXcodeGuid()
{
string Guid = "";
byte[] Randoms = new byte[12];
Rand.NextBytes(Randoms);
for (int Index = 0; Index < 12; Index++)
{
Guid += Randoms[Index].ToString("X2");
}
return Guid;
}
/// File extension for project files we'll be generating (e.g. ".vcxproj")
override public string ProjectFileExtension
{
get
{
return ".xcodeproj";
}
}
///
///
public override void CleanProjectFiles(DirectoryReference InPrimaryProjectDirectory, string InPrimaryProjectName, DirectoryReference InIntermediateProjectFilesPath)
{
DirectoryReference PrimaryProjDeleteFilename = DirectoryReference.Combine(InPrimaryProjectDirectory, InPrimaryProjectName + ".xcworkspace");
if (DirectoryReference.Exists(PrimaryProjDeleteFilename))
{
DirectoryReference.Delete(PrimaryProjDeleteFilename, true);
}
// Delete the project files folder
if (DirectoryReference.Exists(InIntermediateProjectFilesPath))
{
try
{
DirectoryReference.Delete(InIntermediateProjectFilesPath, true);
}
catch (Exception Ex)
{
Log.TraceInformation("Error while trying to clean project files path {0}. Ignored.", InIntermediateProjectFilesPath);
Log.TraceInformation("\t" + Ex.Message);
}
}
}
///
/// Allocates a generator-specific project file object
///
/// Path to the project file
/// The base directory for files within this project
/// The newly allocated project file object
protected override ProjectFile AllocateProjectFile(FileReference InitFilePath, DirectoryReference BaseDir)
{
return new XcodeProjectFile(InitFilePath, BaseDir, bForDistribution, BundleIdentifier, AppName);
}
private bool WriteWorkspaceSettingsFile(string Path)
{
StringBuilder WorkspaceSettingsContent = new StringBuilder();
WorkspaceSettingsContent.Append("" + ProjectFileGenerator.NewLine);
WorkspaceSettingsContent.Append("" + ProjectFileGenerator.NewLine);
WorkspaceSettingsContent.Append("" + ProjectFileGenerator.NewLine);
WorkspaceSettingsContent.Append("" + ProjectFileGenerator.NewLine);
WorkspaceSettingsContent.Append("\tBuildSystemType" + ProjectFileGenerator.NewLine);
WorkspaceSettingsContent.Append("\tOriginal" + ProjectFileGenerator.NewLine);
WorkspaceSettingsContent.Append("\tBuildLocationStyle" + ProjectFileGenerator.NewLine);
WorkspaceSettingsContent.Append("\tUseTargetSettings" + ProjectFileGenerator.NewLine);
WorkspaceSettingsContent.Append("\tCustomBuildLocationType" + ProjectFileGenerator.NewLine);
WorkspaceSettingsContent.Append("\tRelativeToDerivedData" + ProjectFileGenerator.NewLine);
WorkspaceSettingsContent.Append("\tDerivedDataLocationStyle" + ProjectFileGenerator.NewLine);
WorkspaceSettingsContent.Append("\tDefault" + ProjectFileGenerator.NewLine);
WorkspaceSettingsContent.Append("\tIssueFilterStyle" + ProjectFileGenerator.NewLine);
WorkspaceSettingsContent.Append("\tShowAll" + ProjectFileGenerator.NewLine);
WorkspaceSettingsContent.Append("\tLiveSourceIssuesEnabled" + ProjectFileGenerator.NewLine);
WorkspaceSettingsContent.Append("\t" + ProjectFileGenerator.NewLine);
WorkspaceSettingsContent.Append("\tSnapshotAutomaticallyBeforeSignificantChanges" + ProjectFileGenerator.NewLine);
WorkspaceSettingsContent.Append("\t" + ProjectFileGenerator.NewLine);
WorkspaceSettingsContent.Append("\tSnapshotLocationStyle" + ProjectFileGenerator.NewLine);
WorkspaceSettingsContent.Append("\tDefault" + ProjectFileGenerator.NewLine);
WorkspaceSettingsContent.Append("" + ProjectFileGenerator.NewLine);
WorkspaceSettingsContent.Append("" + ProjectFileGenerator.NewLine);
return WriteFileIfChanged(Path, WorkspaceSettingsContent.ToString(), new UTF8Encoding());
}
private bool WriteWorkspaceSharedSettingsFile(string Path)
{
StringBuilder WorkspaceSettingsContent = new StringBuilder();
WorkspaceSettingsContent.Append("" + ProjectFileGenerator.NewLine);
WorkspaceSettingsContent.Append("" + ProjectFileGenerator.NewLine);
WorkspaceSettingsContent.Append("" + ProjectFileGenerator.NewLine);
WorkspaceSettingsContent.Append("" + ProjectFileGenerator.NewLine);
WorkspaceSettingsContent.Append("\tDisableBuildSystemDeprecationWarning" + ProjectFileGenerator.NewLine);
WorkspaceSettingsContent.Append("\t" + ProjectFileGenerator.NewLine);
WorkspaceSettingsContent.Append("\tDisableBuildSystemDeprecationDiagnostic" + ProjectFileGenerator.NewLine);
WorkspaceSettingsContent.Append("\t" + ProjectFileGenerator.NewLine);
WorkspaceSettingsContent.Append("" + ProjectFileGenerator.NewLine);
WorkspaceSettingsContent.Append("" + ProjectFileGenerator.NewLine);
return WriteFileIfChanged(Path, WorkspaceSettingsContent.ToString(), new UTF8Encoding());
}
private bool WriteXcodeWorkspace()
{
bool bSuccess = true;
StringBuilder WorkspaceDataContent = new StringBuilder();
WorkspaceDataContent.Append("" + ProjectFileGenerator.NewLine);
WorkspaceDataContent.Append("" + ProjectFileGenerator.NewLine);
List BuildableProjects = new List();
System.Action< List /* Folders */, string /* Ident */ >? AddProjectsFunction = null;
AddProjectsFunction = (FolderList, Ident) =>
{
foreach (PrimaryProjectFolder CurFolder in FolderList)
{
WorkspaceDataContent.Append(Ident + " " + ProjectFileGenerator.NewLine);
AddProjectsFunction!(CurFolder.SubFolders, Ident + " ");
// Filter out anything that isn't an XC project, and that shouldn't be in the workspace
IEnumerable SupportedProjects =
CurFolder.ChildProjects.OfType()
.Where(P => P.ShouldIncludeProjectInWorkspace())
.OrderBy(P => P.ProjectFilePath.GetFileName());
foreach (XcodeProjectFile XcodeProject in SupportedProjects)
{
WorkspaceDataContent.Append(Ident + " " + ProjectFileGenerator.NewLine);
WorkspaceDataContent.Append(Ident + " " + ProjectFileGenerator.NewLine);
}
BuildableProjects.AddRange(SupportedProjects);
WorkspaceDataContent.Append(Ident + " " + ProjectFileGenerator.NewLine);
}
};
AddProjectsFunction(RootFolder.SubFolders, "");
WorkspaceDataContent.Append("" + ProjectFileGenerator.NewLine);
// Also, update project's schemes index so that the schemes are in a sensible order
// (Game, Editor, Client, Server, Programs)
int SchemeIndex = 0;
BuildableProjects.Sort((ProjA, ProjB) => {
ProjectTarget TargetA = ProjA.ProjectTargets.OfType().OrderBy(T => T.TargetRules!.Type).First();
ProjectTarget TargetB = ProjB.ProjectTargets.OfType().OrderBy(T => T.TargetRules!.Type).First();
TargetType TypeA = TargetA.TargetRules!.Type;
TargetType TypeB = TargetB.TargetRules!.Type;
if (TypeA != TypeB)
{
return TypeA.CompareTo(TypeB);
}
return TargetA.Name.CompareTo(TargetB.Name);
});
foreach (XcodeProjectFile XcodeProject in BuildableProjects)
{
FileReference SchemeManagementFile = XcodeProject.ProjectFilePath + "/xcuserdata/" + Environment.UserName + ".xcuserdatad/xcschemes/xcschememanagement.plist";
if (FileReference.Exists(SchemeManagementFile))
{
string SchemeManagementContent = FileReference.ReadAllText(SchemeManagementFile);
SchemeManagementContent = SchemeManagementContent.Replace("orderHint\n\t\t\t1", "orderHint\n\t\t\t" + SchemeIndex.ToString() + "");
FileReference.WriteAllText(SchemeManagementFile, SchemeManagementContent);
SchemeIndex++;
}
}
string ProjectName = PrimaryProjectName;
if (ProjectFilePlatform != XcodeProjectFilePlatform.All)
{
ProjectName += ProjectFilePlatform == XcodeProjectFilePlatform.Mac ? "_Mac" : (ProjectFilePlatform == XcodeProjectFilePlatform.iOS ? "_IOS" : "_TVOS");
}
string WorkspaceDataFilePath = PrimaryProjectPath + "/" + ProjectName + ".xcworkspace/contents.xcworkspacedata";
bSuccess = WriteFileIfChanged(WorkspaceDataFilePath, WorkspaceDataContent.ToString(), new UTF8Encoding());
if (bSuccess)
{
string WorkspaceSettingsFilePath = PrimaryProjectPath + "/" + ProjectName + ".xcworkspace/xcuserdata/" + Environment.UserName + ".xcuserdatad/WorkspaceSettings.xcsettings";
bSuccess = WriteWorkspaceSettingsFile(WorkspaceSettingsFilePath);
string WorkspaceSharedSettingsFilePath = PrimaryProjectPath + "/" + ProjectName + ".xcworkspace/xcshareddata/WorkspaceSettings.xcsettings";
bSuccess = WriteWorkspaceSharedSettingsFile(WorkspaceSharedSettingsFilePath);
}
return bSuccess;
}
protected override bool WritePrimaryProjectFile(ProjectFile? UBTProject, PlatformProjectGeneratorCollection PlatformProjectGenerators)
{
return WriteXcodeWorkspace();
}
[Flags]
public enum XcodeProjectFilePlatform
{
Mac = 1 << 0,
iOS = 1 << 1,
tvOS = 1 << 2,
All = Mac | iOS | tvOS
}
/// Which platforms we should generate targets for
static public XcodeProjectFilePlatform ProjectFilePlatform = XcodeProjectFilePlatform.All;
/// Should we generate a special project to use for iOS signing instead of a normal one
static public bool bGeneratingRunIOSProject = false;
/// Should we generate a special project to use for tvOS signing instead of a normal one
static public bool bGeneratingRunTVOSProject = false;
///
/// Configures project generator based on command-line options
///
/// Arguments passed into the program
/// True if all platforms should be included
protected override void ConfigureProjectFileGeneration(string[] Arguments, ref bool IncludeAllPlatforms)
{
// Call parent implementation first
base.ConfigureProjectFileGeneration(Arguments, ref IncludeAllPlatforms);
ProjectFilePlatform = IncludeAllPlatforms ? XcodeProjectFilePlatform.All : XcodeProjectFilePlatform.Mac;
foreach (string CurArgument in Arguments)
{
if (CurArgument.StartsWith("-iOSDeployOnly", StringComparison.InvariantCultureIgnoreCase))
{
bGeneratingRunIOSProject = true;
break;
}
if (CurArgument.StartsWith("-tvOSDeployOnly", StringComparison.InvariantCultureIgnoreCase))
{
bGeneratingRunTVOSProject = true;
break;
}
}
if (bGeneratingGameProjectFiles)
{
if (bGeneratingRunIOSProject || bGeneratingRunTVOSProject || UnrealBuildBase.Unreal.IsEngineInstalled())
{
// an Engine target is required in order to be able to get Xcode to sign blueprint projects
// always include the engine target for installed builds.
IncludeEnginePrograms = true;
}
bIncludeEngineSource = true;
}
}
}
}