protected void GenerateFileFromTemplate(string InTemplateFile, string InOutputFile, ProjectParams Params, ConfigHierarchy ConfigCache) { bool IsContentOnly = !Params.IsCodeBasedProject; string ProjectConfiguration = Params.ClientConfigsToBuild[0].ToString(); string UE4GameName = IsContentOnly ? "UE4Game" : Params.ShortProjectName; string ProjectName = Params.ShortProjectName; if (ProjectConfiguration != "Development") { UE4GameName += "-HTML5-" + ProjectConfiguration; ProjectName += "-HTML5-" + ProjectConfiguration; } string CanvasScaleMode; ConfigCache.GetString("/Script/HTML5PlatformEditor.HTML5TargetSettings", "CanvasScalingMode", out CanvasScaleMode); string HeapSize = HTML5SDKInfo.HeapSize(ConfigCache, ProjectConfiguration).ToString(); StringBuilder outputContents = new StringBuilder(); using (StreamReader reader = new StreamReader(InTemplateFile)) { string LineStr = null; while (reader.Peek() != -1) { LineStr = reader.ReadLine(); if (LineStr.Contains("%TIMESTAMP%")) { string TimeStamp = DateTime.UtcNow.ToString("yyyyMMddHHmm"); LineStr = LineStr.Replace("%TIMESTAMP%", TimeStamp); } if (LineStr.Contains("%SHORTNAME%")) { LineStr = LineStr.Replace("%SHORTNAME%", Params.ShortProjectName); } if (LineStr.Contains("%UE4GAMENAME%")) { LineStr = LineStr.Replace("%UE4GAMENAME%", UE4GameName); } if (LineStr.Contains("%PROJECTNAME%")) { LineStr = LineStr.Replace("%PROJECTNAME%", ProjectName); } if (LineStr.Contains("%SERVE_COMPRESSED%")) { LineStr = LineStr.Replace("%SERVE_COMPRESSED%", Compressed ? "true" : "false"); } if (LineStr.Contains("%DISABLE_INDEXEDDB%")) { LineStr = LineStr.Replace("%DISABLE_INDEXEDDB%", enableIndexedDB ? "" : "enableReadFromIndexedDB = false;\nenableWriteToIndexedDB = false;"); } if (LineStr.Contains("%HEAPSIZE%")) { LineStr = LineStr.Replace("%HEAPSIZE%", HeapSize + " * 1024 * 1024"); } if (LineStr.Contains("%UE4CMDLINE%")) { string ArgumentString = "'../../../" + Params.ShortProjectName + "/" + Params.ShortProjectName + ".uproject',"; ArgumentString += "'-stdout',"; // suppress double printing to console.log LineStr = LineStr.Replace("%UE4CMDLINE%", ArgumentString); } if (LineStr.Contains("%CANVASSCALEMODE%")) { string mode = "2 /*ASPECT*/"; // default if (CanvasScaleMode.Equals("stretch", StringComparison.InvariantCultureIgnoreCase)) { mode = "1 /*STRETCH*/"; } else if (CanvasScaleMode.Equals("fixed", StringComparison.InvariantCultureIgnoreCase)) { mode = "3 /*FIXED*/"; } LineStr = LineStr.Replace("%CANVASSCALEMODE%", mode); } outputContents.AppendLine(LineStr); } } if (outputContents.Length > 0) { // Save the file try { Directory.CreateDirectory(Path.GetDirectoryName(InOutputFile)); File.WriteAllText(InOutputFile, outputContents.ToString(), Encoding.UTF8); } catch (Exception) { // Unable to write to the project file. } } }
public override void Package(ProjectParams Params, DeploymentContext SC, int WorkingCL) { Log("Package {0}", Params.RawProjectPath); Log("Setting Emscripten SDK for packaging.."); HTML5SDKInfo.SetupEmscriptenTemp(); HTML5SDKInfo.SetUpEmscriptenConfigFile(); string PackagePath = Path.Combine(Path.GetDirectoryName(Params.RawProjectPath.FullName), "Binaries", "HTML5"); if (!Directory.Exists(PackagePath)) { Directory.CreateDirectory(PackagePath); } // ini configurations var ConfigCache = UnrealBuildTool.ConfigCache.ReadHierarchy(ConfigHierarchyType.Engine, DirectoryReference.FromFile(Params.RawProjectPath), UnrealTargetPlatform.HTML5); bool targetingAsmjs = false; // inverted checked - this will be going away soon... bool targetWebGL1 = false; // inverted checked - this will be going away soon... if (ConfigCache.GetBool("/Script/HTML5PlatformEditor.HTML5TargetSettings", "TargetAsmjs", out targetingAsmjs)) { targetingWasm = !targetingAsmjs; } if (ConfigCache.GetBool("/Script/HTML5PlatformEditor.HTML5TargetSettings", "TargetWebGL1", out targetWebGL1)) { targetWebGL2 = !targetWebGL1; } // Debug and Development builds are not uncompressed to: // - speed up iteration times // - ensure (IndexedDB) data are not cached/used // Shipping builds "can be": // - compressed // - (IndexedDB) cached if (Params.ClientConfigsToBuild[0].ToString() == "Shipping") { ConfigCache.GetBool("/Script/HTML5PlatformEditor.HTML5TargetSettings", "Compressed", out Compressed); ConfigCache.GetBool("/Script/HTML5PlatformEditor.HTML5TargetSettings", "EnableIndexedDB", out enableIndexedDB); } Log("HTML5Platform.Automation: TargetWasm = " + targetingWasm); Log("HTML5Platform.Automation: TargetWebGL2 = " + targetWebGL2); Log("HTML5Platform.Automation: Compressed = " + Compressed); Log("HTML5Platform.Automation: EnableIndexedDB = " + enableIndexedDB); string FinalDataLocation = Path.Combine(PackagePath, Params.ShortProjectName) + ".data"; if (HTMLPakAutomation.CanCreateMapPaks(Params)) { HTMLPakAutomation PakAutomation = new HTMLPakAutomation(Params, SC); // Create Necessary Paks. PakAutomation.CreateEnginePak(); PakAutomation.CreateGamePak(); PakAutomation.CreateContentDirectoryPak(); // Create Emscripten Package from Necessary Paks. - This will be the VFS. PakAutomation.CreateEmscriptenDataPackage(PackagePath, FinalDataLocation); // Create All Map Paks which will be downloaded on the fly. PakAutomation.CreateMapPak(); // Create Delta Paks if setup. List <string> Paks = new List <string>(); ConfigCache.GetArray("/Script/HTML5PlatformEditor.HTML5TargetSettings", "LevelTransitions", out Paks); if (Paks != null) { foreach (var Pak in Paks) { var Matched = Regex.Matches(Pak, "\"[^\"]+\"", RegexOptions.IgnoreCase); string MapFrom = Path.GetFileNameWithoutExtension(Matched[0].ToString().Replace("\"", "")); string MapTo = Path.GetFileNameWithoutExtension(Matched[1].ToString().Replace("\"", "")); PakAutomation.CreateDeltaMapPaks(MapFrom, MapTo); } } } else { // we need to operate in the root string PythonPath = HTML5SDKInfo.Python(); string PackagerPath = HTML5SDKInfo.EmscriptenPackager(); using (new ScopedEnvVar("EM_CONFIG", HTML5SDKInfo.DOT_EMSCRIPTEN)) { using (new PushedDirectory(Path.Combine(Params.BaseStageDirectory, "HTML5"))) { string CmdLine = string.Format("\"{0}\" \"{1}\" --preload . --js-output=\"{1}.js\" --no-heap-copy", PackagerPath, FinalDataLocation); RunAndLog(CmdEnv, PythonPath, CmdLine); } } } // copy the "Executable" to the package directory string GameBasename = Path.GetFileNameWithoutExtension(Params.ProjectGameExeFilename); if (Params.ClientConfigsToBuild[0].ToString() != "Development") { GameBasename += "-HTML5-" + Params.ClientConfigsToBuild[0].ToString(); } // no extension string GameBasepath = Path.GetDirectoryName(Params.ProjectGameExeFilename); string FullGameBasePath = Path.Combine(GameBasepath, GameBasename); string FullPackageGameBasePath = Path.Combine(PackagePath, GameBasename); // with extension string GameExe = GameBasename + ".js"; string FullGameExePath = Path.Combine(GameBasepath, GameExe); string FullPackageGameExePath = Path.Combine(PackagePath, GameExe); // special case -- this will be removed when asm.js has been deprecated string ASMJS_FullPackageGameExePath = Path.Combine(PackagePath, GameBasename + "_asm.js"); // ensure the ue4game binary exists, if applicable if (!SC.IsCodeBasedProject && !FileExists_NoExceptions(FullGameExePath)) { Log("Failed to find game application " + FullGameExePath); throw new AutomationException(ExitCode.Error_MissingExecutable, "Stage Failed. Could not find application {0}. You may need to build the UE4 project with your target configuration and platform.", FullGameExePath); } if (FullGameExePath != FullPackageGameExePath) // TODO: remove this check { File.Copy(FullGameExePath + ".symbols", FullPackageGameExePath + ".symbols", true); if (targetingWasm) { File.Copy(FullGameBasePath + ".wasm", FullPackageGameBasePath + ".wasm", true); File.Copy(FullGameExePath, FullPackageGameExePath, true); } else { File.Copy(FullGameExePath + ".mem", FullPackageGameExePath + ".mem", true); File.Copy(FullGameBasePath + ".asm.js", FullPackageGameBasePath + ".asm.js", true); } } File.SetAttributes(FullPackageGameExePath + ".symbols", FileAttributes.Normal); if (targetingWasm) { File.SetAttributes(FullPackageGameBasePath + ".wasm", FileAttributes.Normal); File.SetAttributes(FullPackageGameExePath, FileAttributes.Normal); } else { File.SetAttributes(FullPackageGameExePath + ".mem", FileAttributes.Normal); File.SetAttributes(FullPackageGameBasePath + ".asm.js", FileAttributes.Normal); File.Copy(FullGameExePath, ASMJS_FullPackageGameExePath, true); // --separate-asm // UE-45058 File.SetAttributes(ASMJS_FullPackageGameExePath, FileAttributes.Normal); } // put the HTML file to the package directory string TemplateFile = CombinePaths(CmdEnv.LocalRoot, "Engine/Build/HTML5/GameX.html.template"); string OutputFile = Path.Combine(PackagePath, (Params.ClientConfigsToBuild[0].ToString() != "Development" ? (Params.ShortProjectName + "-HTML5-" + Params.ClientConfigsToBuild[0].ToString()) : Params.ShortProjectName)) + ".html"; GenerateFileFromTemplate(TemplateFile, OutputFile, Params.ShortProjectName, Params.ClientConfigsToBuild[0].ToString(), Params.StageCommandline, !Params.IsCodeBasedProject, HTML5SDKInfo.HeapSize(ConfigCache, Params.ClientConfigsToBuild[0].ToString())); string MacBashTemplateFile = CombinePaths(CmdEnv.LocalRoot, "Engine/Build/HTML5/RunMacHTML5LaunchHelper.command.template"); string MacBashOutputFile = Path.Combine(PackagePath, "RunMacHTML5LaunchHelper.command"); string MonoPath = CombinePaths(CmdEnv.LocalRoot, "Engine/Build/BatchFiles/Mac/SetupMono.sh"); GenerateMacCommandFromTemplate(MacBashTemplateFile, MacBashOutputFile, MonoPath); string htaccessTemplate = CombinePaths(CmdEnv.LocalRoot, "Engine/Build/HTML5/htaccess.template"); string htaccesspath = Path.Combine(PackagePath, ".htaccess"); if (File.Exists(htaccesspath)) { FileAttributes attributes = File.GetAttributes(htaccesspath); if ((attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly) { attributes &= ~FileAttributes.ReadOnly; File.SetAttributes(htaccesspath, attributes); } } File.Copy(htaccessTemplate, htaccesspath, true); string JSDir = CombinePaths(CmdEnv.LocalRoot, "Engine/Build/HTML5"); string OutDir = PackagePath; // Gather utlity .js files and combine into one file string[] UtilityJavaScriptFiles = Directory.GetFiles(JSDir, "*.js"); string DestinationFile = OutDir + "/Utility.js"; File.Delete(DestinationFile); foreach (var UtilityFile in UtilityJavaScriptFiles) { string Data = File.ReadAllText(UtilityFile); File.AppendAllText(DestinationFile, Data); } if (Compressed) { Log("Build configuration is " + Params.ClientConfigsToBuild[0].ToString() + ", so (gzip) compressing files for web servers."); // Compress all files. These are independent tasks which can be threaded. List <Task> CompressionTasks = new List <Task>(); // Note that the main .data file is never gzip compressed, because we rely on UnrealPak having compressed it already (above), and gzipping the pak file on // top is showing negligible benefit. Check the console output from UnrealPak run to verify that it is indeed compressed. // CompressionTasks.Add(Task.Factory.StartNew(() => CompressFile(FinalDataLocation, FinalDataLocation + "gz"))); // data file .js driver. CompressionTasks.Add(Task.Factory.StartNew(() => CompressFile(FinalDataLocation + ".js", FinalDataLocation + ".jsgz"))); if (targetingWasm) { // main game code CompressionTasks.Add(Task.Factory.StartNew(() => CompressFile(FullPackageGameBasePath + ".wasm", FullPackageGameBasePath + ".wasmgz"))); // main js. CompressionTasks.Add(Task.Factory.StartNew(() => CompressFile(FullPackageGameExePath, FullPackageGameExePath + "gz"))); } else { // mem init file. CompressionTasks.Add(Task.Factory.StartNew(() => CompressFile(FullPackageGameExePath + ".mem", FullPackageGameExePath + ".memgz"))); // main js. CompressionTasks.Add(Task.Factory.StartNew(() => CompressFile(FullPackageGameBasePath + ".asm.js", FullPackageGameBasePath + ".asm.jsgz"))); // main game code CompressionTasks.Add(Task.Factory.StartNew(() => CompressFile(ASMJS_FullPackageGameExePath, ASMJS_FullPackageGameExePath + "gz"))); } // symbols file. CompressionTasks.Add(Task.Factory.StartNew(() => CompressFile(FullPackageGameExePath + ".symbols", FullPackageGameExePath + ".symbolsgz"))); // Utility CompressionTasks.Add(Task.Factory.StartNew(() => CompressFile(OutDir + "/Utility.js", OutDir + "/Utility.jsgz"))); Task.WaitAll(CompressionTasks.ToArray()); } else { Log("Build configuration is " + Params.ClientConfigsToBuild[0].ToString() + ", so not compressing. Build Shipping configuration to compress files to save space."); // nuke old compressed files to prevent using stale files File.Delete(FinalDataLocation + "gz"); File.Delete(FinalDataLocation + ".jsgz"); File.Delete(FullPackageGameExePath + "gz"); File.Delete(FullPackageGameBasePath + ".wasmgz"); File.Delete(FullPackageGameExePath + ".memgz"); File.Delete(FullPackageGameExePath + ".symbolsgz"); File.Delete(OutDir + "/Utility.jsgz"); } File.Copy(CombinePaths(CmdEnv.LocalRoot, "Engine/Binaries/DotNET/HTML5LaunchHelper.exe"), CombinePaths(OutDir, "HTML5LaunchHelper.exe"), true); // Task.WaitAll(CompressionTasks); PrintRunTime(); }
public override void Package(ProjectParams Params, DeploymentContext SC, int WorkingCL) { Log("Package {0}", Params.RawProjectPath); Log("Setting Emscripten SDK for packaging.."); HTML5SDKInfo.SetupEmscriptenTemp(); HTML5SDKInfo.SetUpEmscriptenConfigFile(); string PackagePath = Path.Combine(Path.GetDirectoryName(Params.RawProjectPath.FullName), "Binaries", "HTML5"); if (!Directory.Exists(PackagePath)) { Directory.CreateDirectory(PackagePath); } string FinalDataLocation = Path.Combine(PackagePath, Params.ShortProjectName) + ".data"; var ConfigCache = UnrealBuildTool.ConfigCache.ReadHierarchy(ConfigHierarchyType.Engine, DirectoryReference.FromFile(Params.RawProjectPath), UnrealTargetPlatform.HTML5); if (HTMLPakAutomation.CanCreateMapPaks(Params)) { HTMLPakAutomation PakAutomation = new HTMLPakAutomation(Params, SC); // Create Necessary Paks. PakAutomation.CreateEnginePak(); PakAutomation.CreateGamePak(); PakAutomation.CreateContentDirectoryPak(); // Create Emscripten Package from Necessary Paks. - This will be the VFS. PakAutomation.CreateEmscriptenDataPackage(PackagePath, FinalDataLocation); // Create All Map Paks which will be downloaded on the fly. PakAutomation.CreateMapPak(); // Create Delta Paks if setup. List <string> Paks = new List <string>(); ConfigCache.GetArray("/Script/HTML5PlatformEditor.HTML5TargetSettings", "LevelTransitions", out Paks); if (Paks != null) { foreach (var Pak in Paks) { var Matched = Regex.Matches(Pak, "\"[^\"]+\"", RegexOptions.IgnoreCase); string MapFrom = Path.GetFileNameWithoutExtension(Matched[0].ToString().Replace("\"", "")); string MapTo = Path.GetFileNameWithoutExtension(Matched[1].ToString().Replace("\"", "")); PakAutomation.CreateDeltaMapPaks(MapFrom, MapTo); } } } else { // we need to operate in the root string PythonPath = HTML5SDKInfo.Python(); string PackagerPath = HTML5SDKInfo.EmscriptenPackager(); using (new ScopedEnvVar("EM_CONFIG", HTML5SDKInfo.DOT_EMSCRIPTEN)) { using (new PushedDirectory(Path.Combine(Params.BaseStageDirectory, "HTML5"))) { string CmdLine = string.Format("\"{0}\" \"{1}\" --preload . --js-output=\"{1}.js\"", PackagerPath, FinalDataLocation); RunAndLog(CmdEnv, PythonPath, CmdLine); } } } // copy the "Executable" to the package directory string GameExe = Path.GetFileNameWithoutExtension(Params.ProjectGameExeFilename); if (Params.ClientConfigsToBuild[0].ToString() != "Development") { GameExe += "-HTML5-" + Params.ClientConfigsToBuild[0].ToString(); } GameExe += ".js"; // ensure the ue4game binary exists, if applicable string FullGameExePath = Path.Combine(Path.GetDirectoryName(Params.ProjectGameExeFilename), GameExe); if (!SC.IsCodeBasedProject && !FileExists_NoExceptions(FullGameExePath)) { Log("Failed to find game application " + FullGameExePath); throw new AutomationException(ExitCode.Error_MissingExecutable, "Stage Failed. Could not find application {0}. You may need to build the UE4 project with your target configuration and platform.", FullGameExePath); } if (Path.Combine(Path.GetDirectoryName(Params.ProjectGameExeFilename), GameExe) != Path.Combine(PackagePath, GameExe)) { File.Copy(Path.Combine(Path.GetDirectoryName(Params.ProjectGameExeFilename), GameExe), Path.Combine(PackagePath, GameExe), true); File.Copy(Path.Combine(Path.GetDirectoryName(Params.ProjectGameExeFilename), GameExe) + ".mem", Path.Combine(PackagePath, GameExe) + ".mem", true); File.Copy(Path.Combine(Path.GetDirectoryName(Params.ProjectGameExeFilename), GameExe) + ".symbols", Path.Combine(PackagePath, GameExe) + ".symbols", true); } File.SetAttributes(Path.Combine(PackagePath, GameExe), FileAttributes.Normal); File.SetAttributes(Path.Combine(PackagePath, GameExe) + ".mem", FileAttributes.Normal); File.SetAttributes(Path.Combine(PackagePath, GameExe) + ".symbols", FileAttributes.Normal); // put the HTML file to the package directory string TemplateFileName = "GameX.html.template"; string TemplateFile = Path.Combine(CombinePaths(CmdEnv.LocalRoot, "Engine"), "Build", "HTML5", TemplateFileName); string OutputFile = Path.Combine(PackagePath, (Params.ClientConfigsToBuild[0].ToString() != "Development" ? (Params.ShortProjectName + "-HTML5-" + Params.ClientConfigsToBuild[0].ToString()) : Params.ShortProjectName)) + ".html"; GenerateFileFromTemplate(TemplateFile, OutputFile, Params.ShortProjectName, Params.ClientConfigsToBuild[0].ToString(), Params.StageCommandline, !Params.IsCodeBasedProject, HTML5SDKInfo.HeapSize(ConfigCache, Params.ClientConfigsToBuild[0].ToString())); string MacBashTemplateFile = Path.Combine(CombinePaths(CmdEnv.LocalRoot, "Engine"), "Build", "HTML5", "RunMacHTML5LaunchHelper.command.template"); string MacBashOutputFile = Path.Combine(PackagePath, "RunMacHTML5LaunchHelper.command"); string MonoPath = Path.Combine(CombinePaths(CmdEnv.LocalRoot, "Engine"), "Build", "BatchFiles", "Mac", "SetupMono.sh"); GenerateMacCommandFromTemplate(MacBashTemplateFile, MacBashOutputFile, MonoPath); string htaccessTemplate = Path.Combine(CombinePaths(CmdEnv.LocalRoot, "Engine"), "Build", "HTML5", "htaccess.template"); string htaccesspath = Path.Combine(PackagePath, ".htaccess"); if (File.Exists(htaccesspath)) { FileAttributes attributes = File.GetAttributes(htaccesspath); if ((attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly) { attributes &= ~FileAttributes.ReadOnly; File.SetAttributes(htaccesspath, attributes); } } File.Copy(htaccessTemplate, htaccesspath, true); string JSDir = Path.Combine(CombinePaths(CmdEnv.LocalRoot, "Engine"), "Build", "HTML5"); string OutDir = PackagePath; // Gather utlity .js files and combine into one file string[] UtilityJavaScriptFiles = Directory.GetFiles(JSDir, "*.js"); string DestinationFile = OutDir + "/Utility.js"; File.Delete(DestinationFile); foreach (var UtilityFile in UtilityJavaScriptFiles) { string Data = File.ReadAllText(UtilityFile); File.AppendAllText(DestinationFile, Data); } // Compress all files. These are independent tasks which can be threaded. Task[] CompressionTasks = new Task[6]; //data file. CompressionTasks[0] = Task.Factory.StartNew(() => CompressFile(FinalDataLocation, FinalDataLocation + "gz")); // data file .js driver. CompressionTasks[1] = Task.Factory.StartNew(() => CompressFile(FinalDataLocation + ".js", FinalDataLocation + ".jsgz")); // main js. CompressionTasks[2] = Task.Factory.StartNew(() => CompressFile(Path.Combine(PackagePath, GameExe), Path.Combine(PackagePath, GameExe) + "gz")); // mem init file. CompressionTasks[3] = Task.Factory.StartNew(() => CompressFile(Path.Combine(PackagePath, GameExe) + ".mem", Path.Combine(PackagePath, GameExe) + ".memgz")); // symbols file. CompressionTasks[4] = Task.Factory.StartNew(() => CompressFile(Path.Combine(PackagePath, GameExe) + ".symbols", Path.Combine(PackagePath, GameExe) + ".symbolsgz")); // Utility CompressionTasks[5] = Task.Factory.StartNew(() => CompressFile(OutDir + "/Utility.js", OutDir + "/Utility.jsgz")); File.Copy(CombinePaths(CmdEnv.LocalRoot, "Engine/Binaries/DotNET/HTML5LaunchHelper.exe"), CombinePaths(OutDir, "HTML5LaunchHelper.exe"), true); Task.WaitAll(CompressionTasks); PrintRunTime(); }