Bump mono/corefx to fix https://github.com/mono/mono/issues/14864
[mono-project.git] / sdks / wasm / packager.cs
blobda9ee656652d814a9c948e1b00f83edd094f79a9
1 using System;
2 using System.Linq;
3 using System.IO;
4 using System.Collections.Generic;
5 using Mono.Cecil;
6 using Mono.Options;
7 using Mono.Cecil.Cil;
9 //
10 // Google V8 style options:
11 // - bool: --foo/--no-foo
14 enum FlagType {
15 BoolFlag,
18 // 'Option' is already used by Mono.Options
19 class Flag {
20 public Flag (string name, string desc, FlagType type) {
21 Name = name;
22 FlagType = type;
23 Description = desc;
26 public string Name {
27 get; set;
30 public FlagType FlagType {
31 get; set;
34 public string Description {
35 get; set;
39 class BoolFlag : Flag {
40 public BoolFlag (string name, string description, bool def_value, Action<bool> action) : base (name, description, FlagType.BoolFlag) {
41 Setter = action;
42 DefaultValue = def_value;
45 public Action<bool> Setter {
46 get; set;
49 public bool DefaultValue {
50 get; set;
54 class Driver {
55 static bool enable_debug, enable_linker;
56 static string app_prefix, framework_prefix, bcl_prefix, bcl_tools_prefix, bcl_facades_prefix, out_prefix;
57 static HashSet<string> asm_map = new HashSet<string> ();
58 static List<string> file_list = new List<string> ();
59 static HashSet<string> assemblies_with_dbg_info = new HashSet<string> ();
60 static List<string> root_search_paths = new List<string>();
62 const string BINDINGS_ASM_NAME = "WebAssembly.Bindings";
63 const string BINDINGS_RUNTIME_CLASS_NAME = "WebAssembly.Runtime";
64 const string HTTP_ASM_NAME = "WebAssembly.Net.Http";
65 const string WEBSOCKETS_ASM_NAME = "WebAssembly.Net.WebSockets";
66 const string BINDINGS_MODULE = "corebindings.o";
67 const string BINDINGS_MODULE_SUPPORT = "$tool_prefix/binding_support.js";
69 class AssemblyData {
70 // Assembly name
71 public string name;
72 // Base filename
73 public string filename;
74 // Path outside build tree
75 public string src_path;
76 // Path of .bc file
77 public string bc_path;
78 // Path of the wasm object file
79 public string o_path;
80 // Path in appdir
81 public string app_path;
82 // Path of the AOT depfile
83 public string aot_depfile_path;
84 // Linker input path
85 public string linkin_path;
86 // Linker output path
87 public string linkout_path;
88 // AOT input path
89 public string aotin_path;
90 // Finaly output path after IL strip
91 public string final_path;
92 // Whenever to AOT this assembly
93 public bool aot;
96 static List<AssemblyData> assemblies = new List<AssemblyData> ();
98 enum AssemblyKind {
99 User,
100 Framework,
101 Bcl,
102 None,
105 void AddFlag (OptionSet options, Flag flag) {
106 if (flag is BoolFlag) {
107 options.Add (flag.Name, s => (flag as BoolFlag).Setter (true));
108 options.Add ("no-" + flag.Name, s => (flag as BoolFlag).Setter (false));
110 option_list.Add (flag);
113 static List<Flag> option_list = new List<Flag> ();
115 static void Usage () {
116 Console.WriteLine ("Usage: packager.exe <options> <assemblies>");
117 Console.WriteLine ("Valid options:");
118 Console.WriteLine ("\t--help Show this help message");
119 Console.WriteLine ("\t--debugrt Use the debug runtime (default release) - this has nothing to do with C# debugging");
120 Console.WriteLine ("\t--aot Enable AOT mode");
121 Console.WriteLine ("\t--aot-interp Enable AOT+INTERP mode");
122 Console.WriteLine ("\t--prefix=x Set the input assembly prefix to 'x' (default to the current directory)");
123 Console.WriteLine ("\t--out=x Set the output directory to 'x' (default to the current directory)");
124 Console.WriteLine ("\t--mono-sdkdir=x Set the mono sdk directory to 'x'");
125 Console.WriteLine ("\t--deploy=x Set the deploy prefix to 'x' (default to 'managed')");
126 Console.WriteLine ("\t--vfs=x Set the VFS prefix to 'x' (default to 'managed')");
127 Console.WriteLine ("\t--template=x Set the template name to 'x' (default to 'runtime.js')");
128 Console.WriteLine ("\t--asset=x Add specified asset 'x' to list of assets to be copied");
129 Console.WriteLine ("\t--search-path=x Add specified path 'x' to list of paths used to resolve assemblies");
130 Console.WriteLine ("\t--copy=always|ifnewer Set the type of copy to perform.");
131 Console.WriteLine ("\t\t 'always' overwrites the file if it exists.");
132 Console.WriteLine ("\t\t 'ifnewer' copies or overwrites the file if modified or size is different.");
133 Console.WriteLine ("\t--profile=x Enable the 'x' mono profiler.");
134 Console.WriteLine ("\t--aot-assemblies=x List of assemblies to AOT in AOT+INTERP mode.");
135 Console.WriteLine ("\t--link-mode=sdkonly|all Set the link type used for AOT. (EXPERIMENTAL)");
136 Console.WriteLine ("\t--pinvoke-libs=x DllImport libraries used.");
137 Console.WriteLine ("\t\t 'sdkonly' only link the Core libraries.");
138 Console.WriteLine ("\t\t 'all' link Core and User assemblies. (default)");
140 Console.WriteLine ("foo.dll Include foo.dll as one of the root assemblies");
141 Console.WriteLine ();
143 Console.WriteLine ("Additional options (--option/--no-option):");
144 foreach (var flag in option_list) {
145 if (flag is BoolFlag) {
146 Console.WriteLine (" --" + flag.Name + " (" + flag.Description + ")");
147 Console.WriteLine (" type: bool default: " + ((flag as BoolFlag).DefaultValue ? "true" : "false"));
152 static void Debug (string s) {
153 Console.WriteLine (s);
156 static string FindFrameworkAssembly (string asm) {
157 return asm;
160 static bool Try (string prefix, string name, out string out_res) {
161 out_res = null;
163 string res = (Path.Combine (prefix, name));
164 if (File.Exists (res)) {
165 out_res = Path.GetFullPath (res);
166 return true;
168 return false;
171 static string ResolveWithExtension (string prefix, string name) {
172 string res = null;
174 if (Try (prefix, name, out res))
175 return res;
176 if (Try (prefix, name + ".dll", out res))
177 return res;
178 if (Try (prefix, name + ".exe", out res))
179 return res;
180 return null;
183 static string ResolveUser (string asm_name) {
184 return ResolveWithExtension (app_prefix, asm_name);
187 static string ResolveFramework (string asm_name) {
188 return ResolveWithExtension (framework_prefix, asm_name);
191 static string ResolveBcl (string asm_name) {
192 return ResolveWithExtension (bcl_prefix, asm_name);
195 static string ResolveBclFacade (string asm_name) {
196 return ResolveWithExtension (bcl_facades_prefix, asm_name);
199 static string Resolve (string asm_name, out AssemblyKind kind) {
200 kind = AssemblyKind.User;
201 var asm = ResolveUser (asm_name);
202 if (asm != null)
203 return asm;
205 kind = AssemblyKind.Framework;
206 asm = ResolveFramework (asm_name);
207 if (asm != null)
208 return asm;
210 kind = AssemblyKind.Bcl;
211 asm = ResolveBcl (asm_name);
212 if (asm == null)
213 asm = ResolveBclFacade (asm_name);
214 if (asm != null)
215 return asm;
217 kind = AssemblyKind.None;
218 throw new Exception ($"Could not resolve {asm_name}");
221 static void Import (string ra, AssemblyKind kind) {
222 if (!asm_map.Add (ra))
223 return;
224 ReaderParameters rp = new ReaderParameters();
225 bool add_pdb = enable_debug && File.Exists (Path.ChangeExtension (ra, "pdb"));
226 if (add_pdb) {
227 rp.ReadSymbols = true;
228 // Facades do not have symbols
229 rp.ThrowIfSymbolsAreNotMatching = false;
230 rp.SymbolReaderProvider = new DefaultSymbolReaderProvider(false);
233 var resolver = new DefaultAssemblyResolver();
234 root_search_paths.ForEach(resolver.AddSearchDirectory);
235 resolver.AddSearchDirectory(bcl_facades_prefix);
236 resolver.AddSearchDirectory(bcl_prefix);
237 resolver.AddSearchDirectory(framework_prefix);
238 rp.AssemblyResolver = resolver;
240 rp.InMemory = true;
241 var image = ModuleDefinition.ReadModule (ra, rp);
242 file_list.Add (ra);
243 //Debug ($"Processing {ra} debug {add_pdb}");
245 var data = new AssemblyData () { name = image.Assembly.Name.Name, src_path = ra };
246 assemblies.Add (data);
248 if (add_pdb && (kind == AssemblyKind.User || kind == AssemblyKind.Framework)) {
249 file_list.Add (Path.ChangeExtension (ra, "pdb"));
250 assemblies_with_dbg_info.Add (Path.ChangeExtension (ra, "pdb"));
253 foreach (var ar in image.AssemblyReferences) {
254 // Resolve using root search paths first
255 var resolved = image.AssemblyResolver.Resolve(ar, rp);
257 var searchName = resolved?.MainModule.FileName ?? ar.Name;
259 var resolve = Resolve(searchName, out kind);
260 Import(resolve, kind);
264 void GenDriver (string builddir, List<string> profilers, ExecMode ee_mode, bool link_icalls) {
265 var symbols = new List<string> ();
266 foreach (var adata in assemblies) {
267 if (adata.aot)
268 symbols.Add (String.Format ("mono_aot_module_{0}_info", adata.name.Replace ('.', '_').Replace ('-', '_')));
271 var w = File.CreateText (Path.Combine (builddir, "driver-gen.c.in"));
273 foreach (var symbol in symbols) {
274 w.WriteLine ($"extern void *{symbol};");
277 w.WriteLine ("static void register_aot_modules ()");
278 w.WriteLine ("{");
279 foreach (var symbol in symbols)
280 w.WriteLine ($"\tmono_aot_register_module ({symbol});");
281 w.WriteLine ("}");
283 foreach (var profiler in profilers) {
284 w.WriteLine ($"void mono_profiler_init_{profiler} (const char *desc);");
285 w.WriteLine ("EMSCRIPTEN_KEEPALIVE void mono_wasm_load_profiler_" + profiler + " (const char *desc) { mono_profiler_init_" + profiler + " (desc); }");
288 switch (ee_mode) {
289 case ExecMode.AotInterp:
290 w.WriteLine ("#define EE_MODE_LLVMONLY_INTERP 1");
291 break;
292 case ExecMode.Aot:
293 w.WriteLine ("#define EE_MODE_LLVMONLY 1");
294 break;
295 default:
296 break;
299 if (link_icalls)
300 w.WriteLine ("#define LINK_ICALLS 1");
302 w.Close ();
305 public static int Main (string[] args) {
306 return new Driver ().Run (args);
309 enum CopyType
311 Default,
312 Always,
313 IfNewer
316 enum ExecMode {
317 Interp = 1,
318 Aot = 2,
319 AotInterp = 3
322 enum LinkMode
324 SdkOnly,
325 All
328 class WasmOptions {
329 public bool Debug;
330 public bool DebugRuntime;
331 public bool AddBinding;
332 public bool Linker;
333 public bool LinkIcalls;
334 public bool ILStrip;
335 public bool LinkerVerbose;
336 public bool EnableZLib;
339 int Run (string[] args) {
340 var add_binding = true;
341 var root_assemblies = new List<string> ();
342 enable_debug = false;
343 string builddir = null;
344 string sdkdir = null;
345 string emscripten_sdkdir = null;
346 var aot_assemblies = "";
347 out_prefix = Environment.CurrentDirectory;
348 app_prefix = Environment.CurrentDirectory;
349 var deploy_prefix = "managed";
350 var vfs_prefix = "managed";
351 var use_release_runtime = true;
352 var enable_aot = false;
353 var enable_dedup = true;
354 var print_usage = false;
355 var emit_ninja = false;
356 bool build_wasm = false;
357 bool enable_lto = false;
358 bool link_icalls = false;
359 bool gen_pinvoke = false;
360 bool enable_zlib = false;
361 var il_strip = false;
362 var runtimeTemplate = "runtime.js";
363 var assets = new List<string> ();
364 var profilers = new List<string> ();
365 var pinvoke_libs = "";
366 var copyTypeParm = "default";
367 var copyType = CopyType.Default;
368 var ee_mode = ExecMode.Interp;
369 var linkModeParm = "all";
370 var linkMode = LinkMode.All;
371 var linkDescriptor = "";
372 string coremode, usermode;
373 var linker_verbose = false;
375 var opts = new WasmOptions () {
376 AddBinding = true,
377 Debug = false,
378 DebugRuntime = false,
379 Linker = false,
380 ILStrip = true,
381 LinkerVerbose = false,
382 EnableZLib = false,
385 var p = new OptionSet () {
386 { "nobinding", s => opts.AddBinding = false },
387 { "out=", s => out_prefix = s },
388 { "appdir=", s => out_prefix = s },
389 { "builddir=", s => builddir = s },
390 { "mono-sdkdir=", s => sdkdir = s },
391 { "emscripten-sdkdir=", s => emscripten_sdkdir = s },
392 { "prefix=", s => app_prefix = s },
393 { "deploy=", s => deploy_prefix = s },
394 { "vfs=", s => vfs_prefix = s },
395 { "aot", s => ee_mode = ExecMode.Aot },
396 { "aot-interp", s => ee_mode = ExecMode.AotInterp },
397 { "template=", s => runtimeTemplate = s },
398 { "asset=", s => assets.Add(s) },
399 { "search-path=", s => root_search_paths.Add(s) },
400 { "profile=", s => profilers.Add (s) },
401 { "copy=", s => copyTypeParm = s },
402 { "aot-assemblies=", s => aot_assemblies = s },
403 { "link-mode=", s => linkModeParm = s },
404 { "link-descriptor=", s => linkDescriptor = s },
405 { "pinvoke-libs=", s => pinvoke_libs = s },
406 { "help", s => print_usage = true },
409 AddFlag (p, new BoolFlag ("debug", "enable c# debugging", opts.Debug, b => opts.Debug = b));
410 AddFlag (p, new BoolFlag ("debugrt", "enable debug runtime", opts.DebugRuntime, b => opts.DebugRuntime = b));
411 AddFlag (p, new BoolFlag ("linker", "enable the linker", opts.Linker, b => opts.Linker = b));
412 AddFlag (p, new BoolFlag ("binding", "enable the binding engine", opts.AddBinding, b => opts.AddBinding = b));
413 AddFlag (p, new BoolFlag ("link-icalls", "link away unused icalls", opts.LinkIcalls, b => opts.LinkIcalls = b));
414 AddFlag (p, new BoolFlag ("il-strip", "strip IL code from assemblies in AOT mode", opts.ILStrip, b => opts.ILStrip = b));
415 AddFlag (p, new BoolFlag ("linker-verbose", "set verbose option on linker", opts.LinkerVerbose, b => opts.LinkerVerbose = b));
416 AddFlag (p, new BoolFlag ("zlib", "enable the use of zlib for System.IO.Compression support", opts.EnableZLib, b => opts.EnableZLib = b));
418 var new_args = p.Parse (args).ToArray ();
419 foreach (var a in new_args) {
420 root_assemblies.Add (a);
423 if (print_usage) {
424 Usage ();
425 return 0;
428 if (!Enum.TryParse(copyTypeParm, true, out copyType)) {
429 Console.WriteLine("Invalid copy value");
430 Usage ();
431 return 1;
434 if (!Enum.TryParse(linkModeParm, true, out linkMode)) {
435 Console.WriteLine("Invalid link-mode value");
436 Usage ();
437 return 1;
440 enable_debug = opts.Debug;
441 enable_linker = opts.Linker;
442 add_binding = opts.AddBinding;
443 use_release_runtime = !opts.DebugRuntime;
444 il_strip = opts.ILStrip;
445 linker_verbose = opts.LinkerVerbose;
446 gen_pinvoke = pinvoke_libs != "";
447 enable_zlib = opts.EnableZLib;
449 if (ee_mode == ExecMode.Aot || ee_mode == ExecMode.AotInterp)
450 enable_aot = true;
452 if (enable_aot || opts.Linker)
453 enable_linker = true;
454 if (opts.LinkIcalls)
455 link_icalls = true;
456 if (!enable_linker || !enable_aot)
457 enable_dedup = false;
458 if (enable_aot || link_icalls || gen_pinvoke || profilers.Count > 0)
459 build_wasm = true;
460 if (!enable_aot && link_icalls)
461 enable_lto = true;
462 if (ee_mode != ExecMode.Aot)
463 // Can't strip out IL code in mixed mode, since the interpreter might execute some methods even if they have AOTed code available
464 il_strip = false;
466 if (aot_assemblies != "") {
467 if (ee_mode != ExecMode.AotInterp) {
468 Console.Error.WriteLine ("The --aot-assemblies= argument requires --aot-interp.");
469 return 1;
472 if (link_icalls && !enable_linker) {
473 Console.Error.WriteLine ("The --link-icalls option requires the --linker option.");
474 return 1;
477 var tool_prefix = Path.GetDirectoryName (typeof (Driver).Assembly.Location);
479 //are we working from the tree?
480 if (sdkdir != null) {
481 framework_prefix = Path.Combine (tool_prefix, "framework"); //all framework assemblies are currently side built to packager.exe
482 bcl_prefix = Path.Combine (sdkdir, "wasm-bcl/wasm");
483 bcl_tools_prefix = Path.Combine (sdkdir, "wasm-bcl/wasm_tools");
484 } else if (Directory.Exists (Path.Combine (tool_prefix, "../out/wasm-bcl/wasm"))) {
485 framework_prefix = Path.Combine (tool_prefix, "framework"); //all framework assemblies are currently side built to packager.exe
486 bcl_prefix = Path.Combine (tool_prefix, "../out/wasm-bcl/wasm");
487 bcl_tools_prefix = Path.Combine (tool_prefix, "../out/wasm-bcl/wasm_tools");
488 sdkdir = Path.Combine (tool_prefix, "../out");
489 } else {
490 framework_prefix = Path.Combine (tool_prefix, "framework");
491 bcl_prefix = Path.Combine (tool_prefix, "wasm-bcl/wasm");
492 bcl_tools_prefix = Path.Combine (tool_prefix, "wasm-bcl/wasm_tools");
493 sdkdir = tool_prefix;
495 bcl_facades_prefix = Path.Combine (bcl_prefix, "Facades");
497 foreach (var ra in root_assemblies) {
498 AssemblyKind kind;
499 var resolved = Resolve (ra, out kind);
500 Import (resolved, kind);
502 if (add_binding) {
503 var bindings = ResolveFramework (BINDINGS_ASM_NAME + ".dll");
504 Import (bindings, AssemblyKind.Framework);
505 var http = ResolveFramework (HTTP_ASM_NAME + ".dll");
506 Import (http, AssemblyKind.Framework);
507 var websockets = ResolveFramework (WEBSOCKETS_ASM_NAME + ".dll");
508 Import (websockets, AssemblyKind.Framework);
511 if (enable_aot) {
512 var to_aot = new Dictionary<string, bool> ();
513 to_aot ["mscorlib"] = true;
514 if (aot_assemblies != "") {
515 foreach (var s in aot_assemblies.Split (','))
516 to_aot [s] = true;
518 foreach (var ass in assemblies) {
519 if (aot_assemblies == "" || to_aot.ContainsKey (ass.name)) {
520 ass.aot = true;
521 to_aot.Remove (ass.name);
524 if (to_aot.Count > 0) {
525 Console.Error.WriteLine ("Unknown assembly name '" + to_aot.Keys.ToArray ()[0] + "' in --aot-assemblies option.");
526 return 1;
530 if (builddir != null) {
531 emit_ninja = true;
532 if (!Directory.Exists (builddir))
533 Directory.CreateDirectory (builddir);
536 if (!emit_ninja) {
537 if (!Directory.Exists (out_prefix))
538 Directory.CreateDirectory (out_prefix);
539 var bcl_dir = Path.Combine (out_prefix, deploy_prefix);
540 if (Directory.Exists (bcl_dir))
541 Directory.Delete (bcl_dir, true);
542 Directory.CreateDirectory (bcl_dir);
543 foreach (var f in file_list) {
544 CopyFile(f, Path.Combine (bcl_dir, Path.GetFileName (f)), copyType);
548 if (deploy_prefix.EndsWith ("/"))
549 deploy_prefix = deploy_prefix.Substring (0, deploy_prefix.Length - 1);
550 if (vfs_prefix.EndsWith ("/"))
551 vfs_prefix = vfs_prefix.Substring (0, vfs_prefix.Length - 1);
553 // the linker does not consider these core by default
554 var wasm_core_assemblies = new Dictionary<string, bool> ();
555 if (add_binding) {
556 wasm_core_assemblies [BINDINGS_ASM_NAME] = true;
557 wasm_core_assemblies [HTTP_ASM_NAME] = true;
558 wasm_core_assemblies [WEBSOCKETS_ASM_NAME] = true;
560 // wasm core bindings module
561 var wasm_core_bindings = string.Empty;
562 if (add_binding) {
563 wasm_core_bindings = BINDINGS_MODULE;
565 // wasm core bindings support file
566 var wasm_core_support = string.Empty;
567 var wasm_core_support_library = string.Empty;
568 if (add_binding) {
569 wasm_core_support = BINDINGS_MODULE_SUPPORT;
570 wasm_core_support_library = $"--js-library {BINDINGS_MODULE_SUPPORT}";
572 var runtime_js = Path.Combine (emit_ninja ? builddir : out_prefix, "runtime.js");
573 if (emit_ninja) {
574 File.Delete (runtime_js);
575 File.Copy (runtimeTemplate, runtime_js);
576 } else {
577 if (File.Exists(runtime_js) && (File.Exists(runtimeTemplate))) {
578 CopyFile (runtimeTemplate, runtime_js, CopyType.IfNewer, $"runtime template <{runtimeTemplate}> ");
579 } else {
580 if (File.Exists(runtimeTemplate))
581 CopyFile (runtimeTemplate, runtime_js, CopyType.IfNewer, $"runtime template <{runtimeTemplate}> ");
582 else {
583 var runtime_gen = "\nvar Module = {\n\tonRuntimeInitialized: function () {\n\t\tMONO.mono_load_runtime_and_bcl (\n\t\tconfig.vfs_prefix,\n\t\tconfig.deploy_prefix,\n\t\tconfig.enable_debugging,\n\t\tconfig.file_list,\n\t\tfunction () {\n\t\t\tApp.init ();\n\t\t}\n\t)\n\t},\n};";
584 File.Delete (runtime_js);
585 File.WriteAllText (runtime_js, runtime_gen);
590 AssemblyData dedup_asm = null;
592 if (enable_dedup) {
593 dedup_asm = new AssemblyData () { name = "aot-dummy",
594 filename = "aot-dummy.dll",
595 bc_path = "$builddir/aot-dummy.dll.bc",
596 o_path = "$builddir/aot-dummy.dll.o",
597 app_path = "$appdir/$deploy_prefix/aot-dummy.dll",
598 linkout_path = "$builddir/linker-out/aot-dummy.dll",
599 aot = true
601 assemblies.Add (dedup_asm);
602 file_list.Add ("aot-dummy.dll");
605 var file_list_str = string.Join (",", file_list.Select (f => $"\"{Path.GetFileName (f)}\"").Distinct());
606 var config = String.Format ("config = {{\n \tvfs_prefix: \"{0}\",\n \tdeploy_prefix: \"{1}\",\n \tenable_debugging: {2},\n \tfile_list: [ {3} ],\n", vfs_prefix, deploy_prefix, enable_debug ? "1" : "0", file_list_str);
607 config += "}\n";
608 var config_js = Path.Combine (emit_ninja ? builddir : out_prefix, "mono-config.js");
609 File.Delete (config_js);
610 File.WriteAllText (config_js, config);
612 string runtime_dir = Path.Combine (tool_prefix, use_release_runtime ? "release" : "debug");
613 if (!emit_ninja) {
614 File.Delete (Path.Combine (out_prefix, "mono.js"));
615 File.Delete (Path.Combine (out_prefix, "mono.wasm"));
617 File.Copy (
618 Path.Combine (runtime_dir, "mono.js"),
619 Path.Combine (out_prefix, "mono.js"));
620 File.Copy (
621 Path.Combine (runtime_dir, "mono.wasm"),
622 Path.Combine (out_prefix, "mono.wasm"));
624 foreach(var asset in assets) {
625 CopyFile (asset,
626 Path.Combine (out_prefix, Path.GetFileName (asset)), copyType, "Asset: ");
630 if (!emit_ninja)
631 return 0;
633 if (build_wasm) {
634 if (sdkdir == null) {
635 Console.WriteLine ("The --mono-sdkdir argument is required.");
636 return 1;
638 if (emscripten_sdkdir == null) {
639 Console.WriteLine ("The --emscripten-sdkdir argument is required.");
640 return 1;
642 GenDriver (builddir, profilers, ee_mode, link_icalls);
645 string runtime_libs = "";
646 if (ee_mode == ExecMode.Interp || ee_mode == ExecMode.AotInterp || link_icalls) {
647 runtime_libs += "$mono_sdkdir/wasm-runtime-release/lib/libmono-ee-interp.a $mono_sdkdir/wasm-runtime-release/lib/libmono-ilgen.a ";
648 // We need to link the icall table because the interpreter uses it to lookup icalls even if the aot-ed icall wrappers are available
649 if (!link_icalls)
650 runtime_libs += "$mono_sdkdir/wasm-runtime-release/lib/libmono-icall-table.a ";
652 runtime_libs += "$mono_sdkdir/wasm-runtime-release/lib/libmonosgen-2.0.a";
654 string aot_args = "llvm-path=$emscripten_sdkdir/upstream/bin,";
655 string profiler_libs = "";
656 string profiler_aot_args = "";
657 foreach (var profiler in profilers) {
658 profiler_libs += $"$mono_sdkdir/wasm-runtime-release/lib/libmono-profiler-{profiler}-static.a ";
659 if (profiler_aot_args != "")
660 profiler_aot_args += " ";
661 profiler_aot_args += $"--profile={profiler}";
663 if (ee_mode == ExecMode.AotInterp)
664 aot_args += "interp,";
665 if (build_wasm)
666 enable_zlib = true;
668 runtime_dir = Path.GetFullPath (runtime_dir);
669 sdkdir = Path.GetFullPath (sdkdir);
670 out_prefix = Path.GetFullPath (out_prefix);
672 string driver_deps = "";
673 if (link_icalls)
674 driver_deps += "$builddir/icall-table.h";
675 if (gen_pinvoke)
676 driver_deps += "$builddir/pinvoke-table.h";
677 string emcc_flags = "";
678 if (enable_lto)
679 emcc_flags += "--llvm-lto 1 ";
680 if (enable_zlib)
681 emcc_flags += "-s USE_ZLIB=1 ";
683 var ninja = File.CreateText (Path.Combine (builddir, "build.ninja"));
685 // Defines
686 ninja.WriteLine ($"mono_sdkdir = {sdkdir}");
687 ninja.WriteLine ($"emscripten_sdkdir = {emscripten_sdkdir}");
688 ninja.WriteLine ($"tool_prefix = {tool_prefix}");
689 ninja.WriteLine ($"appdir = {out_prefix}");
690 ninja.WriteLine ($"builddir = .");
691 ninja.WriteLine ($"wasm_runtime_dir = {runtime_dir}");
692 ninja.WriteLine ($"deploy_prefix = {deploy_prefix}");
693 ninja.WriteLine ($"bcl_dir = {bcl_prefix}");
694 ninja.WriteLine ($"bcl_facades_dir = {bcl_facades_prefix}");
695 ninja.WriteLine ($"tools_dir = {bcl_tools_prefix}");
696 if (add_binding) {
697 ninja.WriteLine ($"wasm_core_bindings = $builddir/{BINDINGS_MODULE}");
698 ninja.WriteLine ($"wasm_core_support = {wasm_core_support}");
699 ninja.WriteLine ($"wasm_core_support_library = {wasm_core_support_library}");
700 } else {
701 ninja.WriteLine ("wasm_core_bindings =");
702 ninja.WriteLine ("wasm_core_support =");
703 ninja.WriteLine ("wasm_core_support_library =");
705 ninja.WriteLine ("cross = $mono_sdkdir/wasm-cross-release/bin/wasm32-unknown-none-mono-sgen");
706 ninja.WriteLine ("emcc = source $emscripten_sdkdir/emsdk_set_env.sh && emcc");
707 ninja.WriteLine ("wasm_strip = $emscripten_sdkdir/upstream/bin/wasm-strip");
708 // -s ASSERTIONS=2 is very slow
709 ninja.WriteLine ($"emcc_flags = -Oz -g {emcc_flags}-s DISABLE_EXCEPTION_CATCHING=0 -s ASSERTIONS=1 -s WASM=1 -s ALLOW_MEMORY_GROWTH=1 -s BINARYEN=1 -s \"BINARYEN_TRAP_MODE=\'clamp\'\" -s TOTAL_MEMORY=134217728 -s ALIASING_FUNCTION_POINTERS=0 -s NO_EXIT_RUNTIME=1 -s ERROR_ON_UNDEFINED_SYMBOLS=1 -s \"EXTRA_EXPORTED_RUNTIME_METHODS=[\'ccall\', \'cwrap\', \'setValue\', \'getValue\', \'UTF8ToString\']\" -s \"EXPORTED_FUNCTIONS=[\'___cxa_is_pointer_type\', \'___cxa_can_catch\']\" -s \"DEFAULT_LIBRARY_FUNCS_TO_INCLUDE=[\'setThrew\', \'memset\']\"");
710 ninja.WriteLine ($"aot_base_args = llvmonly,asmonly,no-opt,static,direct-icalls,deterministic,{aot_args}");
712 // Rules
713 ninja.WriteLine ("rule aot");
714 ninja.WriteLine ($" command = MONO_PATH=$mono_path $cross --debug {profiler_aot_args} --aot=$aot_args,$aot_base_args,depfile=$depfile,llvm-outfile=$outfile $src_file");
715 ninja.WriteLine (" description = [AOT] $src_file -> $outfile");
716 ninja.WriteLine ("rule aot-instances");
717 ninja.WriteLine ($" command = MONO_PATH=$mono_path $cross --debug {profiler_aot_args} --aot=$aot_base_args,llvm-outfile=$outfile,dedup-include=$dedup_image $src_files");
718 ninja.WriteLine (" description = [AOT-INSTANCES] $outfile");
719 ninja.WriteLine ("rule mkdir");
720 ninja.WriteLine (" command = mkdir -p $out");
721 ninja.WriteLine ("rule cp");
722 ninja.WriteLine (" command = cp $in $out");
723 // Copy $in to $out only if it changed
724 ninja.WriteLine ("rule cpifdiff");
725 ninja.WriteLine (" command = if cmp -s $in $out ; then : ; else cp $in $out ; fi");
726 ninja.WriteLine (" restat = true");
727 ninja.WriteLine (" description = [CPIFDIFF] $in -> $out");
728 ninja.WriteLine ("rule emcc");
729 ninja.WriteLine (" command = bash -c '$emcc $emcc_flags $flags -c -o $out $in'");
730 ninja.WriteLine (" description = [EMCC] $in -> $out");
731 ninja.WriteLine ("rule emcc-link");
732 ninja.WriteLine ($" command = bash -c '$emcc $emcc_flags -o $out_js --js-library $tool_prefix/library_mono.js --js-library $tool_prefix/dotnet_support.js {wasm_core_support_library} $in' && $wasm_strip $out_wasm");
733 ninja.WriteLine (" description = [EMCC-LINK] $in -> $out_js");
734 ninja.WriteLine ("rule linker");
735 ninja.WriteLine (" command = mono $tools_dir/monolinker.exe -out $builddir/linker-out -l none --deterministic --explicit-reflection --disable-opt unreachablebodies --exclude-feature com --exclude-feature remoting --exclude-feature etw $linker_args || exit 1; for f in $out; do if test ! -f $$f; then echo > empty.cs; csc /deterministic /nologo /out:$$f /target:library empty.cs; fi; done");
736 ninja.WriteLine (" description = [IL-LINK]");
737 ninja.WriteLine ("rule aot-dummy");
738 ninja.WriteLine (" command = echo > aot-dummy.cs; csc /deterministic /out:$out /target:library aot-dummy.cs");
739 ninja.WriteLine ("rule gen-runtime-icall-table");
740 ninja.WriteLine (" command = $cross --print-icall-table > $out");
741 ninja.WriteLine ("rule gen-icall-table");
742 ninja.WriteLine (" command = mono $tools_dir/wasm-tuner.exe --gen-icall-table $runtime_table $in > $out");
743 ninja.WriteLine ("rule gen-pinvoke-table");
744 ninja.WriteLine (" command = mono $tools_dir/wasm-tuner.exe --gen-pinvoke-table $pinvoke_libs $in > $out");
745 ninja.WriteLine ("rule ilstrip");
746 ninja.WriteLine (" command = cp $in $out; mono $tools_dir/mono-cil-strip.exe $out");
747 ninja.WriteLine (" description = [IL-STRIP]");
749 // Targets
750 ninja.WriteLine ("build $appdir: mkdir");
751 ninja.WriteLine ("build $appdir/$deploy_prefix: mkdir");
752 ninja.WriteLine ("build $appdir/runtime.js: cpifdiff $builddir/runtime.js");
753 ninja.WriteLine ("build $appdir/mono-config.js: cpifdiff $builddir/mono-config.js");
754 if (build_wasm) {
755 var source_file = Path.GetFullPath (Path.Combine (tool_prefix, "driver.c"));
756 ninja.WriteLine ($"build $builddir/driver.c: cpifdiff {source_file}");
757 ninja.WriteLine ($"build $builddir/driver-gen.c: cpifdiff $builddir/driver-gen.c.in");
759 var pinvoke_file = Path.GetFullPath (Path.Combine (tool_prefix, "pinvoke-tables-default.h"));
760 ninja.WriteLine ($"build $builddir/pinvoke-tables-default.h: cpifdiff {pinvoke_file}");
761 driver_deps += $" $builddir/pinvoke-tables-default.h";
763 var driver_cflags = enable_aot ? "-DENABLE_AOT=1" : "";
765 if (add_binding) {
766 var bindings_source_file = Path.GetFullPath (Path.Combine (tool_prefix, "corebindings.c"));
767 ninja.WriteLine ($"build $builddir/corebindings.c: cpifdiff {bindings_source_file}");
769 ninja.WriteLine ($"build $builddir/corebindings.o: emcc $builddir/corebindings.c");
770 ninja.WriteLine ($" flags = -I$mono_sdkdir/wasm-runtime-release/include/mono-2.0");
771 driver_cflags += " -DCORE_BINDINGS ";
773 if (gen_pinvoke)
774 driver_cflags += " -DGEN_PINVOKE ";
776 ninja.WriteLine ($"build $builddir/driver.o: emcc $builddir/driver.c | $builddir/driver-gen.c {driver_deps}");
777 ninja.WriteLine ($" flags = {driver_cflags} -DDRIVER_GEN=1 -I$mono_sdkdir/wasm-runtime-release/include/mono-2.0");
779 if (enable_zlib) {
780 var zlib_source_file = Path.GetFullPath (Path.Combine (tool_prefix, "zlib-helper.c"));
781 ninja.WriteLine ($"build $builddir/zlib-helper.c: cpifdiff {zlib_source_file}");
783 ninja.WriteLine ($"build $builddir/zlib-helper.o: emcc $builddir/zlib-helper.c");
784 ninja.WriteLine ($" flags = -I$mono_sdkdir/wasm-runtime-release/include/mono-2.0 -I$mono_sdkdir/wasm-runtime-release/include/support");
786 } else {
787 ninja.WriteLine ("build $appdir/mono.js: cpifdiff $wasm_runtime_dir/mono.js");
788 ninja.WriteLine ("build $appdir/mono.wasm: cpifdiff $wasm_runtime_dir/mono.wasm");
791 var ofiles = "";
792 var bc_files = "";
793 string linker_infiles = "";
794 string linker_ofiles = "";
795 string dedup_infiles = "";
796 if (enable_linker) {
797 string path = Path.Combine (builddir, "linker-in");
798 if (!Directory.Exists (path))
799 Directory.CreateDirectory (path);
801 string aot_in_path = enable_linker ? "$builddir/linker-out" : "$builddir";
802 foreach (var a in assemblies) {
803 var assembly = a.src_path;
804 if (assembly == null)
805 continue;
806 string filename = Path.GetFileName (assembly);
807 var filename_noext = Path.GetFileNameWithoutExtension (filename);
808 string filename_pdb = Path.ChangeExtension (filename, "pdb");
809 var source_file_path = Path.GetFullPath (assembly);
810 var source_file_path_pdb = Path.ChangeExtension (source_file_path, "pdb");
811 string infile = "";
812 string infile_pdb = "";
813 bool emit_pdb = assemblies_with_dbg_info.Contains (source_file_path_pdb);
814 if (enable_linker) {
815 a.linkin_path = $"$builddir/linker-in/{filename}";
816 a.linkout_path = $"$builddir/linker-out/{filename}";
817 linker_infiles += $"{a.linkin_path} ";
818 linker_ofiles += $" {a.linkout_path}";
819 ninja.WriteLine ($"build {a.linkin_path}: cp {source_file_path}");
820 a.aotin_path = a.linkout_path;
821 infile = $"{a.aotin_path}";
822 } else {
823 infile = $"$builddir/{filename}";
824 ninja.WriteLine ($"build $builddir/{filename}: cpifdiff {source_file_path}");
825 if (emit_pdb){
826 ninja.WriteLine ($"build $builddir/{filename_pdb}: cpifdiff {source_file_path_pdb}");
827 infile_pdb = $"$builddir/{filename_pdb}";
831 a.final_path = infile;
832 if (il_strip) {
833 ninja.WriteLine ($"build $builddir/ilstrip-out/{filename} : ilstrip {infile}");
834 a.final_path = $"$builddir/ilstrip-out/{filename}";
837 ninja.WriteLine ($"build $appdir/$deploy_prefix/{filename}: cpifdiff {a.final_path}");
838 if (emit_pdb)
839 ninja.WriteLine ($"build $appdir/$deploy_prefix/{filename_pdb}: cpifdiff {infile_pdb}");
840 if (a.aot) {
841 a.bc_path = $"$builddir/{filename}.bc";
842 a.o_path = $"$builddir/{filename}.o";
843 a.aot_depfile_path = $"$builddir/linker-out/{filename}.depfile";
845 ninja.WriteLine ($"build {a.bc_path}.tmp: aot {infile}");
846 ninja.WriteLine ($" src_file={infile}");
847 ninja.WriteLine ($" outfile={a.bc_path}.tmp");
848 ninja.WriteLine ($" mono_path={aot_in_path}");
849 ninja.WriteLine ($" depfile={a.aot_depfile_path}");
850 if (enable_dedup)
851 ninja.WriteLine ($" aot_args=dedup-skip");
853 ninja.WriteLine ($"build {a.bc_path}: cpifdiff {a.bc_path}.tmp");
854 ninja.WriteLine ($"build {a.o_path}: emcc {a.bc_path}");
856 ofiles += " " + $"{a.o_path}";
857 bc_files += " " + $"{a.bc_path}";
858 dedup_infiles += $" {a.aotin_path}";
861 if (enable_dedup) {
863 * Run the aot compiler in dedup mode:
864 * mono --aot=<args>,dedup-include=aot-dummy.dll <assemblies> aot-dummy.dll
865 * This will process all assemblies and emit all instances into the aot image of aot-dummy.dll
867 var a = dedup_asm;
869 * The dedup process will read in the .dedup files created when running with dedup-skip, so add all the
870 * .bc files as dependencies.
872 ninja.WriteLine ($"build {a.bc_path}.tmp: aot-instances | {bc_files} {a.linkout_path}");
873 ninja.WriteLine ($" dedup_image={a.filename}");
874 ninja.WriteLine ($" src_files={dedup_infiles} {a.linkout_path}");
875 ninja.WriteLine ($" outfile={a.bc_path}.tmp");
876 ninja.WriteLine ($" mono_path={aot_in_path}");
877 ninja.WriteLine ($"build {a.app_path}: cpifdiff {a.linkout_path}");
878 ninja.WriteLine ($"build {a.linkout_path}: aot-dummy");
879 // The dedup image might not have changed
880 ninja.WriteLine ($"build {a.bc_path}: cpifdiff {a.bc_path}.tmp");
881 ninja.WriteLine ($"build {a.o_path}: emcc {a.bc_path}");
882 ofiles += $" {a.o_path}";
884 if (link_icalls) {
885 string icall_assemblies = "";
886 foreach (var a in assemblies) {
887 if (a.name == "mscorlib" || a.name == "System")
888 icall_assemblies += $"{a.linkout_path} ";
890 ninja.WriteLine ("build $builddir/icall-table.json: gen-runtime-icall-table");
891 ninja.WriteLine ($"build $builddir/icall-table.h: gen-icall-table {icall_assemblies}");
892 ninja.WriteLine ($" runtime_table=$builddir/icall-table.json");
894 if (gen_pinvoke) {
895 string pinvoke_assemblies = "";
896 foreach (var a in assemblies)
897 pinvoke_assemblies += $"{a.linkout_path} ";
898 ninja.WriteLine ($"build $builddir/pinvoke-table.h: gen-pinvoke-table {pinvoke_assemblies}");
899 ninja.WriteLine ($" pinvoke_libs=System.Native,{pinvoke_libs}");
901 if (build_wasm) {
902 string zlibhelper = enable_zlib ? "$builddir/zlib-helper.o" : "";
903 ninja.WriteLine ($"build $appdir/mono.js $appdir/mono.wasm: emcc-link $builddir/driver.o {zlibhelper} {wasm_core_bindings} {ofiles} {profiler_libs} {runtime_libs} $mono_sdkdir/wasm-runtime-release/lib/libmono-native.a | $tool_prefix/library_mono.js $tool_prefix/dotnet_support.js {wasm_core_support}");
904 ninja.WriteLine (" out_js=$appdir/mono.js");
905 ninja.WriteLine (" out_wasm=$appdir/mono.wasm");
907 if (enable_linker) {
908 switch (linkMode) {
909 case LinkMode.SdkOnly:
910 coremode = "link";
911 usermode = "copy";
912 break;
913 case LinkMode.All:
914 coremode = "link";
915 usermode = "link";
916 break;
917 default:
918 coremode = "link";
919 usermode = "link";
920 break;
923 string linker_args = "";
924 if (!string.IsNullOrEmpty (linkDescriptor)) {
925 linker_args += $"-x {linkDescriptor} ";
926 foreach (var assembly in root_assemblies) {
927 string filename = Path.GetFileName (assembly);
928 linker_args += $"-p {usermode} {filename} -r linker-in/{filename} ";
930 } else {
931 foreach (var assembly in root_assemblies) {
932 string filename = Path.GetFileName (assembly);
933 linker_args += $"-a linker-in/{filename} ";
937 // the linker does not consider these core by default
938 foreach (var assembly in wasm_core_assemblies.Keys) {
939 linker_args += $"-p {coremode} {assembly} ";
941 if (linker_verbose) {
942 linker_args += "--verbose ";
944 linker_args += $"-d linker-in -d $bcl_dir -d $bcl_facades_dir -c {coremode} -u {usermode} ";
945 foreach (var assembly in wasm_core_assemblies.Keys) {
946 linker_args += $"-r {assembly} ";
949 ninja.WriteLine ("build $builddir/linker-out: mkdir");
950 ninja.WriteLine ($"build {linker_ofiles}: linker {linker_infiles}");
951 ninja.WriteLine ($" linker_args={linker_args}");
953 if (il_strip)
954 ninja.WriteLine ("build $builddir/ilstrip-out: mkdir");
956 foreach(var asset in assets) {
957 var filename = Path.GetFileName (asset);
958 var abs_path = Path.GetFullPath (asset);
959 ninja.WriteLine ($"build $appdir/{filename}: cpifdiff {abs_path}");
962 ninja.Close ();
964 return 0;
967 static void CopyFile(string sourceFileName, string destFileName, CopyType copyType, string typeFile = "")
969 Console.WriteLine($"{typeFile}cp: {copyType} - {sourceFileName} -> {destFileName}");
970 switch (copyType)
972 case CopyType.Always:
973 File.Copy(sourceFileName, destFileName, true);
974 break;
975 case CopyType.IfNewer:
976 if (!File.Exists(destFileName))
978 File.Copy(sourceFileName, destFileName);
980 else
982 var srcInfo = new FileInfo (sourceFileName);
983 var dstInfo = new FileInfo (destFileName);
985 if (srcInfo.LastWriteTime.Ticks > dstInfo.LastWriteTime.Ticks || srcInfo.Length > dstInfo.Length)
986 File.Copy(sourceFileName, destFileName, true);
987 else
988 Console.WriteLine($" skipping: {sourceFileName}");
990 break;
991 default:
992 File.Copy(sourceFileName, destFileName);
993 break;