// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.Text; using System.IO; namespace UnrealBuildTool { /// /// Represents a folder within the master project (e.g. Visual Studio solution) /// class XcodeProjectFolder : MasterProjectFolder { public XcodeProjectFolder(ProjectFileGenerator InitOwnerProjectFileGenerator, string InitFolderName) : base(InitOwnerProjectFileGenerator, InitFolderName) { } } /// /// 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); public XcodeProjectFileGenerator(FileReference InOnlyGameProject) : base(InOnlyGameProject) { } /// /// 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 InMasterProjectDirectory, string InMasterProjectName, DirectoryReference InIntermediateProjectFilesPath) { DirectoryReference MasterProjDeleteFilename = DirectoryReference.Combine(InMasterProjectDirectory, InMasterProjectName + ".xcworkspace"); if (MasterProjDeleteFilename.Exists()) { Directory.Delete(MasterProjDeleteFilename.FullName, true); } // Delete the project files folder if (InIntermediateProjectFilesPath.Exists()) { try { Directory.Delete(InIntermediateProjectFilesPath.FullName, 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 newly allocated project file object protected override ProjectFile AllocateProjectFile(FileReference InitFilePath) { return new XcodeProjectFile(InitFilePath, OnlyGameProject); } /// ProjectFileGenerator interface public override MasterProjectFolder AllocateMasterProjectFolder(ProjectFileGenerator InitOwnerProjectFileGenerator, string InitFolderName) { return new XcodeProjectFolder(InitOwnerProjectFileGenerator, InitFolderName); } private bool WriteWorkspaceSettingsFile(string Path) { var WorkspaceSettingsContent = new StringBuilder(); WorkspaceSettingsContent.Append("" + ProjectFileGenerator.NewLine); WorkspaceSettingsContent.Append("" + ProjectFileGenerator.NewLine); WorkspaceSettingsContent.Append("" + ProjectFileGenerator.NewLine); WorkspaceSettingsContent.Append("" + 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 WriteXcodeWorkspace() { bool bSuccess = true; var WorkspaceDataContent = new StringBuilder(); WorkspaceDataContent.Append("" + ProjectFileGenerator.NewLine); WorkspaceDataContent.Append("" + ProjectFileGenerator.NewLine); System.Action< List /* Folders */, string /* Ident */ > AddProjectsFunction = null; AddProjectsFunction = (FolderList, Ident) => { foreach (XcodeProjectFolder CurFolder in FolderList) { WorkspaceDataContent.Append(Ident + " " + ProjectFileGenerator.NewLine); AddProjectsFunction(CurFolder.SubFolders, Ident + " "); foreach (ProjectFile CurProject in CurFolder.ChildProjects) { XcodeProjectFile XcodeProject = CurProject as XcodeProjectFile; if (XcodeProject != null) { WorkspaceDataContent.Append(Ident + " " + ProjectFileGenerator.NewLine); WorkspaceDataContent.Append(Ident + " " + ProjectFileGenerator.NewLine); } } WorkspaceDataContent.Append(Ident + " " + ProjectFileGenerator.NewLine); } }; AddProjectsFunction(RootFolder.SubFolders, ""); WorkspaceDataContent.Append("" + ProjectFileGenerator.NewLine); string ProjectName = MasterProjectName; if (ProjectFilePlatform != XcodeProjectFilePlatform.All) { ProjectName += ProjectFilePlatform == XcodeProjectFilePlatform.Mac ? "_Mac" : (ProjectFilePlatform == XcodeProjectFilePlatform.iOS ? "_IOS" : "_TVOS"); } var WorkspaceDataFilePath = MasterProjectPath + "/" + ProjectName + ".xcworkspace/contents.xcworkspacedata"; bSuccess = WriteFileIfChanged(WorkspaceDataFilePath, WorkspaceDataContent.ToString(), new UTF8Encoding()); if (bSuccess) { var WorkspaceSettingsFilePath = MasterProjectPath + "/" + ProjectName + ".xcworkspace/xcuserdata/" + Environment.UserName + ".xcuserdatad/WorkspaceSettings.xcsettings"; bSuccess = WriteWorkspaceSettingsFile(WorkspaceSettingsFilePath); } return bSuccess; } protected override bool WriteMasterProjectFile(ProjectFile UBTProject) { 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 (var CurArgument in Arguments) { if (CurArgument.StartsWith("-iOSDeployOnly", StringComparison.InvariantCultureIgnoreCase)) { bGeneratingRunIOSProject = true; break; } if (CurArgument.StartsWith("-tvOSDeployOnly", StringComparison.InvariantCultureIgnoreCase)) { bGeneratingRunTVOSProject = true; break; } } if (bGeneratingGameProjectFiles) { IncludeEngineSource = true; } } } }