Files
UnrealEngineUWP/Engine/Source/Programs/UnrealBuildTool/ProjectFiles/Xcode/XcodeFrameworkWrapperProject.cs
jonathan adamczewski d58996b916 UnrealBuildTool: move some path constants into BuildUtilities
RootDirectory, EngineDirectory, UnrealBuildToolPath are now found in BuildUtilities' UnrealBuild namesapce.

The way these are computed has changed. Previously, it was assumed that the application is UnrealBuildTool, and paths were constructed relative to that assembly.

Now, the assumption is that the process is located under a "Engine/Build/DotNET" sub-path and paths are constructed relative to that.

#jira none

#ROBOMERGE-SOURCE: CL 16607440 in //UE5/Main/...
#ROBOMERGE-BOT: STARSHIP (Main -> Release-Engine-Test) (v828-16531559)

[CL 16607455 by jonathan adamczewski in ue5-release-engine-test branch]
2021-06-09 12:55:13 -04:00

318 lines
13 KiB
C#

// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.IO;
using System.Text.RegularExpressions;
using System.Linq;
using System.Collections.Generic;
using EpicGames.Core;
using UnrealBuildBase;
#nullable disable
namespace UnrealBuildTool.ProjectFiles.Xcode
{
/// <summary>
/// Generates an Xcode project that acts as a native wrapper for an Unreal Project built as a framework.
/// </summary>
class XcodeFrameworkWrapperProject
{
private readonly static string PROJECT_FILE_SEARCH_EXPRESSION = "*.pbxproj";
private readonly static string TEMPLATE_NAME = "FrameworkWrapper";
private readonly static string FRAMEWORK_WRAPPER_TEMPLATE_DIRECTORY = Path.Combine(UnrealBuild.EngineDirectory.ToNormalizedPath(), "Build", "IOS", "Resources", TEMPLATE_NAME);
private readonly static string TEMPLATE_PROJECT_NAME = "PROJECT_NAME";
private readonly static string COMMANDLINE_FILENAME = "uecommandline.txt";
/// <summary>
/// Recursively copies all of the files and directories that are inside <paramref name="SourceDirectory"/> into <paramref name="DestinationDirectory"/>.
/// </summary>
/// <param name="SourceDirectory">The directory whose contents should be copied.</param>
/// <param name="DestinationDirectory">The directory into which the files should be copied.</param>
private static void CopyAll(string SourceDirectory, string DestinationDirectory)
{
IEnumerable<string> Directories = Directory.EnumerateDirectories(SourceDirectory, "*", System.IO.SearchOption.AllDirectories);
// Create all the directories
foreach (string DirSrc in Directories)
{
string DirDst = DirSrc.ToString().Replace(SourceDirectory.ToString(), DestinationDirectory.ToString());
Directory.CreateDirectory(DirDst);
}
IEnumerable<string> Files = Directory.EnumerateFiles(SourceDirectory, "*", System.IO.SearchOption.AllDirectories);
// Copy all the files
foreach (string FileSrc in Files)
{
string FileDst = FileSrc.ToString().Replace(SourceDirectory.ToString(), DestinationDirectory.ToString());
if (!File.Exists(FileDst))
{
File.Copy(FileSrc, FileDst);
}
}
}
/// <summary>
/// An enumeration specifying the type of a filesystem entry, either directory, file, or something else.
/// </summary>
private enum EntryType { None, Directory, File }
/// <summary>
/// Gets the type of filesystem entry pointed to by <paramref name="Path"/>.
/// </summary>
/// <returns>The type of filesystem entry pointed to by <paramref name="Path"/>.</returns>
/// <param name="Path">The path to a filesystem entry.</param>
private static EntryType GetEntryType(string Path)
{
if (Directory.Exists(Path))
{
return EntryType.Directory;
}
else if (File.Exists(Path))
{
return EntryType.File;
}
else
{
return EntryType.None;
}
}
/// <summary>
/// Recursively renames all files and directories that contain <paramref name="OldValue"/> in their name by replacing
/// <paramref name="OldValue"/> with <paramref name="NewValue"/>.
/// </summary>
/// <param name="RootDirectory">Root directory.</param>
/// <param name="OldValue">Old value.</param>
/// <param name="NewValue">New value.</param>
private static void RenameFilesAndDirectories(string RootDirectory, string OldValue, string NewValue)
{
IEnumerable<string> Entries = Directory.EnumerateFileSystemEntries(RootDirectory, "*", SearchOption.TopDirectoryOnly);
foreach (string Entry in Entries)
{
string NewEntryName = Path.GetFileName(Entry).Replace(OldValue, NewValue);
string ParentDirectory = Path.GetDirectoryName(Entry);
string EntryDestination = Path.Combine(ParentDirectory, NewEntryName);
switch (GetEntryType(Entry))
{
case EntryType.Directory:
if (Entry != EntryDestination)
{
Directory.Move(Entry, EntryDestination);
}
RenameFilesAndDirectories(EntryDestination, OldValue, NewValue);
break;
case EntryType.File:
if (Entry != EntryDestination)
{
File.Move(Entry, EntryDestination);
}
break;
default:
break;
}
}
}
/// <summary>
/// Opens each file in <paramref name="RootDirectory"/> and replaces all occurrences of <paramref name="OldValue"/>
/// with <paramref name="NewValue"/>.
/// </summary>
/// <param name="RootDirectory">The directory in which all files should be subject to replacements.</param>
/// <param name="SearchPattern">Only replace text in files that match this pattern. Default is all files.</param>
/// <param name="OldValue">The value that should be replaced in all files.</param>
/// <param name="NewValue">The replacement value.</param>
private static void ReplaceTextInFiles(string RootDirectory, string OldValue, string NewValue, string SearchPattern = "*")
{
IEnumerable<string> Files = Directory.EnumerateFiles(RootDirectory, SearchPattern, SearchOption.AllDirectories);
foreach (string SrcFile in Files)
{
string FileContents = File.ReadAllText(SrcFile);
FileContents = FileContents.Replace(OldValue, NewValue);
File.WriteAllText(SrcFile, FileContents);
}
}
/// <summary>
/// Modifies the Xcode project file to change a few build settings.
/// </summary>
/// <param name="RootDirectory">The root directory of the template project that was created.</param>
/// <param name="FrameworkName">The name of the framework that this project is wrapping.</param>
/// <param name="BundleId">The Bundle ID to give to the wrapper project.</param>
/// <param name="SrcFrameworkPath">The path to the directory containing the framework to be wrapped.</param>
/// <param name="EnginePath">The path to the root Unreal Engine directory.</param>
/// <param name="CookedDataPath">The path to the 'cookeddata' folder that accompanies the framework.</param>
/// <param name="ProvisionName"></param>
/// <param name="TeamUUID"></param>
private static void SetProjectFileSettings(string RootDirectory, string FrameworkName, string BundleId, string SrcFrameworkPath, string EnginePath, string CookedDataPath, string ProvisionName, string TeamUUID)
{
List<string> ProjectFiles = Directory.EnumerateFiles(RootDirectory, PROJECT_FILE_SEARCH_EXPRESSION, SearchOption.AllDirectories).ToList();
if (ProjectFiles.Count != 1)
{
throw new BuildException(String.Format("Should only find 1 Xcode project file in the resources, but {0} were found.", ProjectFiles.Count));
}
else
{
string ProjectContents = File.ReadAllText(ProjectFiles[0]);
Dictionary<string, string> Settings = new Dictionary<string, string>()
{
["FRAMEWORK_NAME"] = FrameworkName,
["SRC_FRAMEWORK_PATH"] = SrcFrameworkPath,
["ENGINE_PATH"] = EnginePath,
["SRC_COOKEDDATA"] = CookedDataPath,
["PRODUCT_BUNDLE_IDENTIFIER"] = BundleId,
["PROVISIONING_PROFILE_SPECIFIER"] = ProvisionName,
["DEVELOPMENT_TEAM"] = TeamUUID,
};
foreach (KeyValuePair<string, string> Setting in Settings)
{
ProjectContents = ChangeProjectSetting(ProjectContents, Setting.Key, Setting.Value);
}
File.WriteAllText(ProjectFiles[0], ProjectContents);
}
}
/// <summary>
/// Removes the readonly attribute from all files in a directory file while retaining all other attributes, thus making them writeable.
/// </summary>
/// <param name="RootDirectory">The path to the directory that will be make writeable.</param>
private static void MakeAllFilesWriteable(string RootDirectory)
{
IEnumerable<string> FileNames = Directory.EnumerateFiles(RootDirectory, "*", SearchOption.AllDirectories);
foreach (string FileName in FileNames)
{
File.SetAttributes(FileName, File.GetAttributes(FileName) & ~FileAttributes.ReadOnly);
}
}
/// <summary>
/// Changes the value of a setting in a project file.
/// </summary>
/// <returns>The project file contents with the setting replaced.</returns>
/// <param name="ProjectContents">The contents of a settings file.</param>
/// <param name="SettingName">The name of the setting to change.</param>
/// <param name="SettingValue">The new value for the setting.</param>
private static string ChangeProjectSetting(string ProjectContents, string SettingName, string SettingValue)
{
string SettingNameRegexString = String.Format("(\\s+{0}\\s=\\s)\"?(.+)\"?;", SettingName);
string SettingValueReplaceString = String.Format("$1\"{0}\";", SettingValue);
Regex SettingNameRegex = new Regex(SettingNameRegexString);
return SettingNameRegex.Replace(ProjectContents, SettingValueReplaceString);
}
/// <summary>
/// There are some autogenerated directories that could have accidentally made it into the template.
/// This method tries to delete those directories as an extra precaution.
/// </summary>
/// <param name="RootDirectory">The directory which should be recursively searched for unwanted directories.</param>
private static void DeleteUnwantedDirectories(string RootDirectory)
{
HashSet<string> UnwantedDirectories = new HashSet<string>()
{
"Build",
"xcuserdata"
};
IEnumerable<string> Directories = Directory.EnumerateDirectories(RootDirectory, "*", SearchOption.AllDirectories);
foreach (string Dir in Directories)
{
string DirectoryName = Path.GetFileName(Dir);
if (UnwantedDirectories.Contains(DirectoryName) && Directory.Exists(Dir))
{
Directory.Delete(Dir, true);
}
}
}
/// <summary>
/// Generates an Xcode project that acts as a native wrapper around an Unreal Project built as a framework.
///
/// Wrapper projects are generated by copying a template xcode project from the Build Resources directory,
/// deleting any user-specific or build files, renaming files and folders to match the framework, setting specific
/// settings in the project to accommodate the framework, and replacing text in all the files to match the framework.
/// </summary>
/// <param name="OutputDirectory">The directory in which to place the framework. The framework will be placed in 'outputDirectory/frameworkName/'.</param>
/// <param name="ProjectName">The name of the project. If blueprint-only, use the actual name of the project, not just UnrealGame.</param>
/// <param name="FrameworkName">The name of the framework that this project is wrapping.</param>
/// <param name="BundleId">The Bundle ID to give to the wrapper project.</param>
/// <param name="SrcFrameworkPath">The path to the directory containing the framework to be wrapped.</param>
/// <param name="EnginePath">The path to the root Unreal Engine directory.</param>
/// <param name="CookedDataPath">The path to the 'cookeddata' folder that accompanies the framework.</param>
/// <param name="ProvisionName"></param>
/// <param name="TeamUUID"></param>
public static void GenerateXcodeFrameworkWrapper(string OutputDirectory, string ProjectName, string FrameworkName, string BundleId, string SrcFrameworkPath, string EnginePath, string CookedDataPath,string ProvisionName, string TeamUUID)
{
string OutputDir = Path.Combine(OutputDirectory, FrameworkName);
CopyAll(FRAMEWORK_WRAPPER_TEMPLATE_DIRECTORY, OutputDir);
DeleteUnwantedDirectories(OutputDir);
MakeAllFilesWriteable(OutputDir);
RenameFilesAndDirectories(OutputDir, TEMPLATE_NAME, FrameworkName);
SetProjectFileSettings(OutputDir, FrameworkName, BundleId, SrcFrameworkPath, EnginePath, CookedDataPath, ProvisionName, TeamUUID);
ReplaceTextInFiles(OutputDir, TEMPLATE_NAME, FrameworkName);
ReplaceTextInFiles(OutputDir, TEMPLATE_PROJECT_NAME, ProjectName, COMMANDLINE_FILENAME);
}
}
class XcodeFrameworkWrapperUtils
{
private static ConfigHierarchy GetIni(DirectoryReference ProjectDirectory)
{
return ConfigCache.ReadHierarchy(ConfigHierarchyType.Engine, ProjectDirectory, UnrealTargetPlatform.IOS);
//return ConfigCache.ReadHierarchy(ConfigHierarchyType.Engine, DirectoryReference.FromFile(ProjectFile), UnrealTargetPlatform.IOS);
}
public static string GetBundleID(DirectoryReference ProjectDirectory, FileReference ProjectFile)
{
ConfigHierarchy Ini = GetIni(ProjectDirectory);
string BundleId;
Ini.GetString("/Script/IOSRuntimeSettings.IOSRuntimeSettings", "BundleIdentifier", out BundleId);
BundleId = BundleId.Replace("[PROJECT_NAME]", ((ProjectFile != null) ? ProjectFile.GetFileNameWithoutAnyExtensions() : "UnrealGame")).Replace("_", "");
return BundleId;
}
public static string GetBundleName(DirectoryReference ProjectDirectory, FileReference ProjectFile)
{
ConfigHierarchy Ini = GetIni(ProjectDirectory);
string BundleName;
Ini.GetString("/Script/IOSRuntimeSettings.IOSRuntimeSettings", "BundleName", out BundleName);
BundleName = BundleName.Replace("[PROJECT_NAME]", ((ProjectFile != null) ? ProjectFile.GetFileNameWithoutAnyExtensions() : "UnrealGame")).Replace("_", "");
return BundleName;
}
public static bool GetBuildAsFramework(DirectoryReference ProjectDirectory)
{
ConfigHierarchy Ini = GetIni(ProjectDirectory);
bool bBuildAsFramework;
Ini.GetBool("/Script/IOSRuntimeSettings.IOSRuntimeSettings", "bBuildAsFramework", out bBuildAsFramework);
return bBuildAsFramework;
}
public static bool GetGenerateFrameworkWrapperProject(DirectoryReference ProjectDirectory)
{
ConfigHierarchy Ini = GetIni(ProjectDirectory);
bool bGenerateFrameworkWrapperProject;
Ini.GetBool("/Script/IOSRuntimeSettings.IOSRuntimeSettings", "bGenerateFrameworkWrapperProject", out bGenerateFrameworkWrapperProject);
return bGenerateFrameworkWrapperProject;
}
}
}