Files
UnrealEngineUWP/Engine/Source/Programs/UnrealBuildTool/ProjectFiles/Xcode/XcodeFrameworkWrapperProject.cs
Ben Marsh 03675533ea Rename UE4Game -> UnrealGame, UE4Client -> UnrealClient, UE4Server -> UnrealServer.
Mostly a find/replace, though I have looked through the changes and attempted to update references to other things as necessary (eg. renaming IOS plist files for IOS). I'm not set up to test on any platforms other than windows, and was hoping to get your blessing to submit and give QA enough time as possible to uncover issues before the next milestone release.

Particular things that I know I'm not sure about:
- Android references /UE4Game/ paths everywhere (for paths on device, I think). I have no idea if I've got them all.
- I've renamed the iOS mobileprovisions, but I don't know if they need regenerating for the new app name.
- Likewise, not sure what needs to be updated for icon bundles on iOS.

Things that have not been changed:
- Windows still uses IDI_UE4ICON for its icon
- UE4CommandLine.txt
- There's still a UE4Game module which is used by content-only projects

#rb none

[CL 14301890 by Ben Marsh in ue5-main branch]
2020-09-11 15:54:42 -04:00

315 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 Tools.DotNETCommon;
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(UnrealBuildTool.EngineDirectory.ToNormalizedPath(), "Build", "IOS", "Resources", TEMPLATE_NAME);
private readonly static string TEMPLATE_PROJECT_NAME = "PROJECT_NAME";
private readonly static string COMMANDLINE_FILENAME = "ue4commandline.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;
}
}
}