/// <summary> /// Builds the provided project for the HTML5 platform. /// </summary> public void Build(UTinyBuildOptions options, UTinyBuildResults results) { // Final output directory results.BinaryFolder = new DirectoryInfo(Path.Combine(options.Destination.FullName, "bin")); results.BinaryFolder.Create(); // Package and export all data PackageSettings(options, results); PackageRuntime(options, results); PackageAssets(options, results); // Generate and write all applicaton code GenerateEntityGroups(options, results); GenerateBindings(options, results); GenerateSystems(options, results); GenerateScripts(options, results); GenerateMain(options, results); // Generate additional appended code GenerateWebSocketClient(options, results); GenerateWebPDecompressor(options, results); // Generate final HTML file GenerateHTML(options, results); results.PreviewFile = results.BinaryFolder.FullName; // Generate build report GenerateBuildReport(results); }
/// <summary> /// Writes user code to `code.js` /// /// Any free standing code written by users is written to this file /// </summary> private static void GenerateScripts(UTinyBuildOptions options, UTinyBuildResults results) { var project = options.Project; var registry = project.Registry; var module = project.Module.Dereference(registry); var report = results.BuildReport.GetOrAddChild(UTinyBuildReport.CodeNode).AddChild(); var writer = new UTinyCodeWriter(CodeStyle.JavaScript); PrependGeneratedHeader(writer, options.Project.Name); foreach (var script in module.EnumerateDependencies().Scripts()) { if (null == script.TextAsset) { continue; } var reportSystemPos = writer.Length; writer.WriteRaw(script.TextAsset.text) .Line(); report.AddChild(AssetDatabase.GetAssetPath(script.TextAsset), Encoding.ASCII.GetBytes(writer.Substring(reportSystemPos)), script.TextAsset); } var file = new FileInfo(Path.Combine(results.BinaryFolder.FullName, KCodeFileName)); File.WriteAllText(file.FullName, writer.ToString(), Encoding.UTF8); report.Reset(file); }
/// <summary> /// Writes components, structs and enums `bindings.js` /// </summary> private static void GenerateBindings(UTinyBuildOptions options, UTinyBuildResults results) { var file = new FileInfo(Path.Combine(results.BinaryFolder.FullName, KBindingsFileName)); // @NOTE `bind-generated.js` is the exported name from the `BindGen.exe` application File.Copy(Path.Combine(results.OutputFolder.FullName, "bind-generated.js"), file.FullName, true); results.BuildReport.GetOrAddChild("Code").AddChild(file); }
/// <summary> /// Packages settings to `settings.js` /// </summary> private static void PackageSettings(UTinyBuildOptions options, UTinyBuildResults results) { var writer = new UTinyCodeWriter(CodeStyle.JavaScript); var file = new FileInfo(Path.Combine(results.BinaryFolder.FullName, KSettingsFileName)); var settings = options.Project.Settings; writer.Line($"var Module = {{TOTAL_MEMORY: {settings.MemorySize * 1024 * 1024}}};") .Line(); // <HACK> // Workaround for issue `UTINY-1091` // Systems will not force binding generation to create namespace objects var namespaces = new HashSet <string>(); foreach (var m in options.Project.Module.Dereference(options.Project.Registry).EnumerateDependencies()) { // If we don't have types our module namespace is not generated automatically if (!m.Types.Any()) { var parts = m.Namespace.Split('.'); var name = parts[0]; namespaces.Add(name); for (var i = 1; i < parts.Length; i++) { name = $"{name}.{parts[i]}"; namespaces.Add(name); } } } if (namespaces.Count > 0) { writer.Line("/*"); writer.Line(" * Workaround for issue UTINY-1091"); writer.Line(" */"); } foreach (var n in namespaces) { writer.Line(!n.Contains('.') ? $"var {n} = {n} || {{}}" : $"{n} = {n} || {{}}"); } // <HACK> if (writer.Length <= 0) { // No settings, nothing to write return; } PrependGeneratedHeader(writer, options.Project.Name); File.WriteAllText(file.FullName, writer.ToString(), Encoding.UTF8); }
/// <summary> /// Packages the runtime to `runtime.js` /// </summary> private static void PackageRuntime(UTinyBuildOptions options, UTinyBuildResults results) { string buildFolderName; switch (options.Configuration) { case UTinyBuildConfiguration.Debug: buildFolderName = "build-js-debug"; break; case UTinyBuildConfiguration.Development: case UTinyBuildConfiguration.Release: buildFolderName = "build-js-release"; break; default: throw new ArgumentOutOfRangeException(); } var runtimeVariant = UTinyBuildPipeline.GetJsRuntimeVariant(options); var distFolder = UTinyBuildPipeline.GetRuntimeDistFolder(); var runtimeFiles = new DirectoryInfo(Path.Combine(distFolder.FullName, buildFolderName + "/runtime")).GetFiles(runtimeVariant + "*", SearchOption.TopDirectoryOnly); var reportRuntime = results.BuildReport.AddChild(UTinyBuildReport.RuntimeNode); foreach (var runtimeFile in runtimeFiles) { if (runtimeFile.Name.EndsWith(".js.symbols") || runtimeFile.Name.EndsWith(".js.map") || runtimeFile.Name.EndsWith(".dll")) { continue; } var destPath = Path.Combine(results.BinaryFolder.FullName, $"runtime{runtimeFile.Extension}"); if (runtimeFile.Name == "GeminiRuntime.js") { var dependencies = options.Project.Module.Dereference(options.Project.Registry).EnumerateDependencies(); var regex = new System.Text.RegularExpressions.Regex(@"\/\*if\(([\s\S]*?)\)\*\/([\s\S]*?)\/\*endif\(([\s\S]*?)\)\*\/"); var runtime = File.ReadAllText(runtimeFile.FullName); runtime = regex.Replace(runtime, match => match.Groups[match.Groups[1].Value.Split('|').Any(module => dependencies.WithName("UTiny." + module).Any()) ? 2 : 3].Value); File.WriteAllText(destPath, runtime); reportRuntime.AddChild(new FileInfo(destPath)); } else { reportRuntime.AddChild(runtimeFile.CopyTo(destPath)); } } }
/// <summary> /// Generates `wsclient.js` to handle live-linking /// </summary> private static void GenerateWebSocketClient(UTinyBuildOptions options, UTinyBuildResults results) { if (!options.Project.Settings.IncludeWSClient) { return; } // Put local http server address into the wsclient script var content = File.ReadAllText(Path.Combine(UTinyBuildPipeline.GetToolDirectory("wsclient"), KWebSocketClientFileName)); content = content.Replace("{{IPADDRESS}}", UTinyServer.Instance.LocalIPAddress); // Write wsclient to binary dir var file = new FileInfo(Path.Combine(results.BinaryFolder.FullName, KWebSocketClientFileName)); File.WriteAllText(file.FullName, content, Encoding.UTF8); results.BuildReport.GetOrAddChild(UTinyBuildReport.CodeNode).AddChild(file); }
public static void SendBuildEvent(UTinyProject project, UTinyBuildResults buildResults, TimeSpan duration) { if (project?.Settings == null || buildResults == null) { return; } var buildReportRoot = buildResults.BuildReport.Root; var buildReportRuntime = buildReportRoot.GetChild(UTinyBuildReport.RuntimeNode); var buildReportAssets = buildReportRoot.GetChild(UTinyBuildReport.AssetsNode); var buildReportCode = buildReportRoot.GetChild(UTinyBuildReport.CodeNode); var e = new BuildEvent() { package = TinyPackageUtility.Package, context = ContextInfo.Default, project = ProjectInfo.Create(project), duration = duration.Ticks, total_bytes = buildReportRoot?.Item.CompressedSize ?? 0, runtime_bytes = buildReportRuntime?.Item.CompressedSize ?? 0, assets_bytes = buildReportAssets?.Item.CompressedSize ?? 0, code_bytes = buildReportCode?.Item.CompressedSize ?? 0, total_raw_bytes = buildReportRoot?.Item.Size ?? 0, runtime_raw_bytes = buildReportRuntime?.Item.Size ?? 0, assets_raw_bytes = buildReportAssets?.Item.Size ?? 0, code_raw_bytes = buildReportCode?.Item.Size ?? 0, heap_size = project.Settings.MemorySize, opt_auto_resize = project.Settings.CanvasAutoResize, opt_ws_client = project.Settings.IncludeWSClient, opt_webp_decompressor = project.Settings.IncludeWebPDecompressor, opt_ecma5 = project.Settings.RunBabel, opt_single_file_output = project.Settings.SingleFileHtml, opt_embed_assets = project.Settings.EmbedAssets, default_texture_format = project.Settings.DefaultTextureSettings.FormatType.ToString() }; Send(EventName.tinyEditorBuild, e); }
/// <summary> /// Packages entity group objects to `entities.js` /// /// Since we don't have a scene format, groups are written as setup functions /// </summary> private static void GenerateEntityGroups(UTinyBuildOptions options, UTinyBuildResults results) { var writer = new UTinyCodeWriter(CodeStyle.JavaScript); var report = results.BuildReport.GetOrAddChild(UTinyBuildReport.CodeNode).AddChild(); PrependGeneratedHeader(writer, options.Project.Name); // @NOTE Namespaces are generated through through `BindGen.exe` // e.g. `{ENTITY_GROUPS}.{PROJECT_NAMESPACE}.{GROUP_NAME}` will already exist as a component using (var visitor = new EntityGroupSetupVisitor { Writer = writer, Report = report }) { options.Project.Visit(visitor); } var file = new FileInfo(Path.Combine(results.BinaryFolder.FullName, KEntityGroupsFileName)); File.WriteAllText(file.FullName, writer.ToString(), Encoding.UTF8); report.Reset(file); }
/// <summary> /// Generates `libwebp.js` to handle WebP decompressor /// </summary> private static void GenerateWebPDecompressor(UTinyBuildOptions options, UTinyBuildResults results) { // Check if project use WebP texture format var module = options.Project.Module.Dereference(options.Project.Registry); var webpUsed = AssetIterator.EnumerateAssets(module) .Select(a => a.Object) .OfType <Texture2D>() .Select(t => UTinyUtility.GetAssetExportSettings(options.Project, t)) .OfType <UTinyTextureSettings>() .Any(s => s.FormatType == TextureFormatType.WebP); // Warn about WebP usages if (options.Project.Settings.IncludeWebPDecompressor) { if (!webpUsed) { Debug.LogWarning("This project does not uses the WebP texture format, but includes the WebP decompressor code. To reduce build size, it is recommended to disable \"Include WebP Decompressor\" in project settings."); } } else // WebP decompressor not included, do not copy to binary dir { if (webpUsed) { Debug.LogWarning("This project uses the WebP texture format, but does not include the WebP decompressor code. The content will not load in browsers that do not natively support the WebP format. To ensure maximum compatibility, enable \"Include WebP Decompressor\" in project settings."); } return; } // Copy libwebp to binary dir var srcFile = Path.Combine(UTinyBuildPipeline.GetToolDirectory("libwebp"), KWebPDecompressorFileName); var dstFile = Path.Combine(results.BinaryFolder.FullName, KWebPDecompressorFileName); File.Copy(srcFile, dstFile); results.BuildReport.GetOrAddChild(UTinyBuildReport.CodeNode).AddChild(new FileInfo(dstFile)); }
/// <summary> /// Generates the `build-report.json` file /// </summary> private static void GenerateBuildReport(UTinyBuildResults results) { // Output build report data as json File.WriteAllText(Path.Combine(results.OutputFolder.FullName, "build-report.json"), results.BuildReport.ToString(), Encoding.UTF8); }
/// <summary> /// Outputs the final `index.html` file /// </summary> private static void GenerateHTML(UTinyBuildOptions options, UTinyBuildResults results) { var project = options.Project; var settingsFile = new FileInfo(Path.Combine(results.BinaryFolder.FullName, KSettingsFileName)); var runtimeFile = new FileInfo(Path.Combine(results.BinaryFolder.FullName, KRuntimeFileName)); var bindingsFile = new FileInfo(Path.Combine(results.BinaryFolder.FullName, KBindingsFileName)); var assetsFile = new FileInfo(Path.Combine(results.BinaryFolder.FullName, KAssetsFileName)); var entityGroupsFile = new FileInfo(Path.Combine(results.BinaryFolder.FullName, KEntityGroupsFileName)); var systemsFile = new FileInfo(Path.Combine(results.BinaryFolder.FullName, KSystemsFileName)); var codeFile = new FileInfo(Path.Combine(results.BinaryFolder.FullName, KCodeFileName)); var mainFile = new FileInfo(Path.Combine(results.BinaryFolder.FullName, KMainFileName)); var webSocketClientFile = new FileInfo(Path.Combine(results.BinaryFolder.FullName, KWebSocketClientFileName)); var webpDecompressorFile = new FileInfo(Path.Combine(results.BinaryFolder.FullName, KWebPDecompressorFileName)); // nb: this writer is not HTML-friendly var writer = new UTinyCodeWriter() { CodeStyle = new CodeStyle() { BeginBrace = string.Empty, EndBrace = string.Empty, BraceLayout = BraceLayout.EndOfLine, Indent = " ", NewLine = Environment.NewLine } }; writer.Line("<!DOCTYPE html>"); using (writer.Scope("<html>")) { using (writer.Scope("<head>")) { writer.Line("<meta charset=\"UTF-8\">"); if (UsesAdSupport(project)) { writer.Line("<script src=\"mraid.js\"></script>"); } if (project.Settings.RunBabel) { // Babelize user code var title = $"{UTinyConstants.ApplicationName} Build"; const string messageFormat = "Transpiling {0} to ECMAScript 5"; EditorUtility.DisplayProgressBar(title, "Transpiling to ECMAScript 5", 0.0f); try { // We only need to transpile user authored code var userCode = new [] { systemsFile, codeFile }; var babelDir = new DirectoryInfo(UTinyBuildPipeline.GetToolDirectory("babel")); for (var i = 0; i < userCode.Length; i++) { var file = userCode[i]; EditorUtility.DisplayProgressBar(title, string.Format(messageFormat, file.Name), i / (float)userCode.Length); UTinyBuildUtilities.RunNode(babelDir, "index.js", $"\"{file.FullName}\" \"{file.FullName}\""); } } finally { EditorUtility.ClearProgressBar(); } } // Gather all game files (order is important) var files = new List <FileInfo> { settingsFile, runtimeFile, bindingsFile, assetsFile, entityGroupsFile, systemsFile, codeFile, mainFile, webSocketClientFile, webpDecompressorFile }.Where(file => file != null && file.Exists).ToList(); // Extra steps for Release config if (options.Configuration == UTinyBuildConfiguration.Release) { // Minify JavaScript var gameFile = new FileInfo(Path.Combine(results.BinaryFolder.FullName, "game.js")); EditorUtility.DisplayProgressBar($"{UTinyConstants.ApplicationName} Build", "Minifying JavaScript code...", 0.0f); try { var minifyDir = new DirectoryInfo(UTinyBuildPipeline.GetToolDirectory("minify")); UTinyBuildUtilities.RunNode(minifyDir, "index.js", $"\"{gameFile.FullName}\" {String.Join(" ", files.Select(file => '"' + file.FullName + '"'))}"); files.ForEach(file => file.Delete()); } finally { EditorUtility.ClearProgressBar(); } // Package as single html file if (project.Settings.SingleFileHtml) { writer.Line("<script type=\"text/javascript\">"); writer.WriteRaw(File.ReadAllText(gameFile.FullName)); writer.Line(); writer.Line("</script>"); gameFile.Delete(); } else { writer.LineFormat("<script src=\"{0}\"></script>", gameFile.Name); } } else { files.ForEach(file => writer.LineFormat("<script src=\"{0}\"></script>", file.Name)); } writer.LineFormat("<title>{0}</title>", project.Name); writer.CodeStyle.EndBrace = "</head>"; } using (writer.Scope("<body>")) { writer.CodeStyle.EndBrace = "</body>"; } writer.CodeStyle.EndBrace = "</html>"; } // Write final index.html file var htmlFile = new FileInfo(Path.Combine(results.BinaryFolder.FullName, KHtmlFileName)); File.WriteAllText(htmlFile.FullName, writer.ToString(), Encoding.UTF8); }
/// <summary> /// Generates entry point for the applicaton `main.js` /// This script will contain the system scheduling, window setup and initial group loading /// </summary> private static void GenerateMain(UTinyBuildOptions options, UTinyBuildResults results) { var project = options.Project; var registry = project.Registry; var module = project.Module.Dereference(registry); var file = new FileInfo(Path.Combine(results.BinaryFolder.FullName, KMainFileName)); var writer = new UTinyCodeWriter(); PrependGeneratedHeader(writer, options.Project.Name); var distVersionFile = new FileInfo("UTiny/version.txt"); var versionString = "internal"; if (distVersionFile.Exists) { versionString = File.ReadAllText(distVersionFile.FullName); } writer.LineFormat("console.log('runtime version: {0}');", versionString) .Line(); var namespaces = new Dictionary <string, string>(); foreach (var m in module.EnumerateDependencies()) { if (string.IsNullOrEmpty(m.Namespace)) { continue; } if (m.IsRuntimeIncluded) { writer.Line($"ut.importModule({m.Namespace});"); continue; } string content; namespaces.TryGetValue(m.Namespace, out content); content += m.Documentation.Summary; namespaces[m.Namespace] = content; } UTinyJsdoc.WriteType(writer, "ut.World", "Singleton world instance"); writer.Line("var world;"); using (writer.Scope("ut.main = function()")) { // Create and setup the world writer .Line("world = new ut.World();") .Line("var options = WorldSetup(world);"); // Write configurations var context = new EntityGroupSetupVisitor.VisitorContext { Project = project, Module = project.Module.Dereference(project.Registry), Registry = project.Registry, Writer = writer, EntityIndexMap = null }; var configuration = project.Configuration.Dereference(registry); foreach (var component in configuration.Components) { var moduleContainingType = registry.FindAllByType <UTinyModule>().First(m => m.Types.Contains(component.Type)); if (!module.EnumerateDependencies().Contains(moduleContainingType)) { // Silently ignore components if the module is not included. // This is by design to preserve user data continue; } var type = component.Type.Dereference(component.Registry); var index = ++context.ComponentIndex; writer.Line($"var c{index} = world.config({UTinyBuildPipeline.GetJsTypeName(type)});"); component.Properties.Visit(new EntityGroupSetupVisitor.ComponentVisitor { VisitorContext = context, Path = $"c{index}", }); } // Setup the scheduler writer.Line("var scheduler = world.scheduler();"); // Schedule all systems var systems = project.Module.Dereference(project.Registry).GetSystemExecutionOrder(); foreach (var reference in systems) { var system = reference.Dereference(project.Registry); if (system == null) { Debug.LogWarning($"Can't resolve system named '{reference.Name}' with ID {reference.Id} -- ignoring, you should delete this system"); continue; } var systemModule = UTinyUtility.GetModules(system).FirstOrDefault(); var systemName = UTinyBuildPipeline.GetJsTypeName(systemModule, system); writer.LineFormat("scheduler.schedule({0});", systemName); } // Enable/disable systems foreach (var reference in systems) { var system = reference.Dereference(project.Registry); // By default systems are enabled when scheduled, nothing to write if (system == null || system.Enabled) { continue; } var systemModule = UTinyUtility.GetModules(system).FirstOrDefault(); var systemName = UTinyBuildPipeline.GetJsTypeName(systemModule, system); // @NOTE Disable currently accepts a string and NOT the `ut.System` object writer.LineFormat("scheduler.disable({0});", EscapeJsString(systemName)); } writer.Line("try { ut.Runtime.Service.run(world); } catch (e) { if (e !== 'SimulateInfiniteLoop') throw e; }"); } writer.Line(); using (writer.Scope("function WorldSetup(world)")) { writer.LineFormat("UT_ASSETS_SETUP(world);"); var startupEntityGroup = module.StartupEntityGroup.Dereference(module.Registry); if (null != startupEntityGroup) { writer.Line($"{KEntityGroupNamespace}.{module.Namespace}[\"{module.StartupEntityGroup.Dereference(module.Registry).Name}\"].load(world);"); } else { Debug.LogError($"{UTinyConstants.ApplicationName}: BuildError - No startup group has been set"); } using (writer.Scope("return")) { writer .LineFormat("canvasWidth: {0},", project.Settings.CanvasWidth) .LineFormat("canvasHeight: {0},", project.Settings.CanvasHeight) .LineFormat("canvasAutoResize: {0},", project.Settings.CanvasAutoResize ? "true" : "false"); } #if UNITY_EDITOR_WIN writer.Length -= 2; #else writer.Length -= 1; #endif writer.WriteRaw(";").Line(); } File.WriteAllText(file.FullName, writer.ToString(), Encoding.UTF8); results.BuildReport.GetOrAddChild(UTinyBuildReport.CodeNode).AddChild(file); }
/// <summary> /// Packages system objects to `systems.js` /// /// All systems and system dependencies are written to this file /// </summary> private static void GenerateSystems(UTinyBuildOptions options, UTinyBuildResults results) { var project = options.Project; var report = results.BuildReport.GetOrAddChild(UTinyBuildReport.CodeNode).AddChild(); var file = new FileInfo(Path.Combine(results.BinaryFolder.FullName, KSystemsFileName)); var writer = new UTinyCodeWriter(CodeStyle.JavaScript); PrependGeneratedHeader(writer, options.Project.Name); foreach (var reference in project.Module.Dereference(project.Registry).GetSystemExecutionOrder()) { var system = reference.Dereference(project.Registry); if (system == null) { Debug.LogWarning($"Can't resolve system named '{reference.Name}' with ID {reference.Id} -- ignoring, you should delete this system"); continue; } if (system.External) { continue; } // Fetch the module this system belongs to var systemModule = UTinyUtility.GetModules(system).FirstOrDefault(); if (system.IsRuntimeIncluded) { continue; } var reportSystemPos = writer.Length; UTinyJsdoc.WriteSystem(writer, system); writer.Line($"{UTinyBuildPipeline.GetJsTypeName(systemModule, system)}.update = {UTinyBuildPipeline.GenerateSystemPrefix(system)}"); writer.IncrementIndent(); if (system.IncludeIterator) { writer.Line(UTinyBuildPipeline.GenerateSystemIteratorPrefix(system)); writer.IncrementIndent(); } var text = system.TextAsset ? system.TextAsset.text : string.Empty; if (!string.IsNullOrEmpty(text)) { var lines = text.Split('\n'); foreach (var line in lines) { writer.Line(line); } } if (system.IncludeIterator) { writer.DecrementIndent(); writer.Line("});"); } writer.DecrementIndent(); writer.Line(UTinyBuildPipeline.GenerateSystemSuffix(system)); report.AddChild(AssetDatabase.GetAssetPath(system.TextAsset), Encoding.ASCII.GetBytes(writer.Substring(reportSystemPos)), system.TextAsset); } File.WriteAllText(file.FullName, writer.ToString(), Encoding.UTF8); report.Reset(file); }
/// <summary> /// Packages assets to `assets.js` or `Assets/*.*` /// </summary> private static void PackageAssets(UTinyBuildOptions options, UTinyBuildResults results) { var buildFolder = options.Destination; var binFolder = results.BinaryFolder; // Export assets to the build directory var buildAssetsFolder = new DirectoryInfo(Path.Combine(buildFolder.FullName, "Assets")); buildAssetsFolder.Create(); var export = UTinyAssetExporter.Export(options.Project, buildAssetsFolder); // copy assets to bin AND/OR encode assets to 'assets.js' var binAssetsFolder = new DirectoryInfo(Path.Combine(binFolder.FullName, "Assets")); binAssetsFolder.Create(); var assetsFile = new FileInfo(Path.Combine(binFolder.FullName, KAssetsFileName)); var writer = new UTinyCodeWriter(); PrependGeneratedHeader(writer, options.Project.Name); var reportAssets = results.BuildReport.AddChild(UTinyBuildReport.AssetsNode); var reportJavaScript = reportAssets.AddChild("JavaScript"); using (var jsdoc = new UTinyJsdoc.Writer(writer)) { jsdoc.Type("object"); jsdoc.Desc("Map containing URLs for all assets. If assets are included as base64 blobs, these will be data URLs."); jsdoc.Line("@example var assetUrl = UT_ASSETS[\"MyCustomAsset\"]"); } long totalBase64Size = 0; using (writer.Scope("var UT_ASSETS =")) { var i = 0; foreach (var info in export) { var reportAsset = reportAssets.AddChild(info.AssetInfo.AssetPath, 0, info.AssetInfo.Object); var settings = UTinyUtility.GetAssetExportSettings(options.Project, info.AssetInfo.Object); if (settings.Embedded) { foreach (var file in info.Exported) { var buffer = File.ReadAllBytes(file.FullName); var base64 = Convert.ToBase64String(buffer); var fileExtension = Path.GetExtension(file.FullName).ToLower(); string mimeType; switch (fileExtension) { case ".png": mimeType = "image/png"; break; case ".jpg": case ".jpeg": mimeType = "image/jpeg"; break; case ".webp": mimeType = "image/webp"; break; case ".mp3": mimeType = "audio/mpeg"; break; case ".wav": mimeType = "audio/wav"; break; case ".json": mimeType = "application/json"; break; case ".ttf": mimeType = "font/truetype"; break; default: Debug.LogWarningFormat("Asset {0} has unknown extension, included as text/plain in assets", file); mimeType = "text/plain"; break; } var comma = i != 0 ? "," : ""; writer.Line($"{comma}\"{Path.GetFileNameWithoutExtension(file.Name)}\": \"data:{mimeType};base64,{base64}\""); i++; reportAsset.AddChild(UTinyBuildPipeline.GetRelativePath(file), Encoding.ASCII.GetBytes(base64), info.AssetInfo.Object); totalBase64Size += base64.Length; file.Delete(); } } else { foreach (var file in info.Exported) { var comma = i != 0 ? "," : ""; writer.Line($"{comma}\"{Path.GetFileNameWithoutExtension(file.Name)}\": \"Assets/{file.Name}\""); i++; reportAsset.AddChild(file, info.AssetInfo.Object); } } } } writer.Line(); writer.WriteRaw("var UT_ASSETS_SETUP = "); { var registry = new UTinyRegistry(); UTinyPersistence.LoadAllModules(registry); var entityGroup = UTinyAssetEntityGroupGenerator.Generate(registry, options.Project); EntityGroupSetupVisitor.WriteEntityGroupSetupFunction(writer, options.Project, entityGroup, false, false); } // Write `assets.js` File.WriteAllText(assetsFile.FullName, writer.ToString()); reportJavaScript.Item.Size = assetsFile.Length - totalBase64Size; // Remaining assets are binplaced foreach (var info in export) { foreach (var file in info.Exported) { if (!file.Exists) { // this asset has been packaged already continue; } file.MoveTo(Path.Combine(binAssetsFolder.FullName, file.Name)); } } // Clean up the build directory buildAssetsFolder.Delete(true); // if we have no standalone assets, cleanup if (binAssetsFolder.GetFiles().Length <= 0) { binAssetsFolder.Delete(); } }
public static UTinyBuildResults Build(UTinyBuildOptions options) { if (options?.Project == null || options.Destination == null) { throw new ArgumentException($"{UTinyConstants.ApplicationName}: invalid build options provided", nameof(options)); } var buildStart = DateTime.Now; var results = new UTinyBuildResults(); IUTinyBuilder builder = null; switch (options.Platform) { case UTinyPlatform.HTML5: builder = new UTinyHTML5Builder(); break; default: throw new ArgumentException($"{UTinyConstants.ApplicationName}: build platform not supported", nameof(options)); } try { EditorUtility.DisplayProgressBar(ProgressBarTitle, "Build started for " + options.Platform.ToString(), 0.0f); var destFolder = options.Destination; destFolder.Create(); // BUILD = <DEST>/PLATFORM/CONFIG var buildFolder = new DirectoryInfo(GetBuildDirectory(options.Project, options.Platform, options.Configuration)); results.OutputFolder = buildFolder; UTinyBuildUtilities.PurgeDirectory(buildFolder); buildFolder.Create(); options.Destination = results.BinaryFolder = buildFolder; var idlFile = new FileInfo(Path.Combine(buildFolder.FullName, "generated.cs")); UTinyIDLGenerator.GenerateIDL(options.Project, idlFile); var distFolder = GetRuntimeDistFolder(); var bindGem = new FileInfo(Path.Combine( distFolder.FullName, "bindgem/BindGem/bin/Release/BindGem.exe")); var exeName = "\"" + bindGem.FullName + "\""; // always call bindgem with mono for consistency exeName = "mono " + exeName; // reference the core runtime file var bindReferences = $"-r \"{RuntimeDefsAssemblyPath}\""; UTinyBuildUtilities.RunInShell( $"{exeName} -j {bindReferences} -o bind-generated {idlFile.Name}", new ShellProcessArgs() { WorkingDirectory = buildFolder, ExtraPaths = TinyPreferences.MonoDirectory.AsEnumerable() }); // @TODO Perform a full refresh before building builder.Build(options, results); results.BuildReport.Update(); Debug.Log($"{UTinyConstants.ApplicationName} project generated at: {results.BinaryFolder.FullName}"); TinyEditorAnalytics.SendBuildEvent(options.Project, results, DateTime.Now - buildStart); return(results); } catch (Exception ex) { TinyEditorAnalytics.SendException("BuildPipeline.Build", ex); throw; } finally { EditorUtility.ClearProgressBar(); UTinyEditorUtility.RepaintAllWindows(); } }