Merge pull request #2998 from lateralusX/jlorenss/win-x64-full-aot-support
[mono.git] / mcs / tools / mkbundle / mkbundle.cs
index 7a3936fd34b0b391e8705383f27b66576b5c9417..9f75c85749a37752bcad3cd58875435b4db876a1 100755 (executable)
@@ -7,6 +7,11 @@
 //   Miguel de Icaza
 //
 // (C) Novell, Inc 2004
+// (C) 2016 Xamarin Inc
+//
+// Missing features:
+// * Add support for packaging native libraries, extracting at runtime and setting the library path.
+// * Implement --list-targets lists all the available remote targets
 //
 using System;
 using System.Diagnostics;
@@ -17,8 +22,9 @@ using System.IO.Compression;
 using System.Runtime.InteropServices;
 using System.Text;
 using IKVM.Reflection;
-
-
+using System.Linq;
+using System.Diagnostics;
+using System.Net;
 using System.Threading.Tasks;
 
 class MakeBundle {
@@ -41,6 +47,12 @@ class MakeBundle {
        static bool skip_scan;
        static string ctor_func;
        static bool quiet;
+       static string cross_target = null;
+       static string fetch_target = null;
+       static bool custom_mode = true;
+       static string embedded_options = null;
+       static string runtime = null;
+       static string target_server = "https://download.mono-project.com/runtimes/raw/";
        
        static int Main (string [] args)
        {
@@ -56,10 +68,56 @@ class MakeBundle {
                                Help ();
                                return 1;
 
+                       case "--simple":
+                               custom_mode = false;
+                               autodeps = true;
+                               break;
+                               
+                       case "--custom":
+                               custom_mode = true;
+                               break;
+                               
                        case "-c":
                                compile_only = true;
                                break;
+
+                       case "--local-targets":
+                               CommandLocalTargets ();
+                               return 0;
+
+                       case "--cross":
+                               if (i+1 == top){
+                                       Help (); 
+                                       return 1;
+                               }
+                               custom_mode = false;
+                               autodeps = true;
+                               cross_target = args [++i];
+                               break;
+
+                       case "--fetch-target":
+                               if (i+1 == top){
+                                       Help (); 
+                                       return 1;
+                               }
+                               fetch_target = args [++i];
+                               break;
+
+                       case "--list-targets":
+                               var wc = new WebClient ();
+                               var s = wc.DownloadString (new Uri (target_server + "target-list.txt"));
+                               Console.WriteLine ("Cross-compilation targets available:\n" + s);
+                               
+                               return 0;
                                
+                       case "--target-server":
+                               if (i+1 == top){
+                                       Help (); 
+                                       return 1;
+                               }
+                               target_server = args [++i];
+                               break;
+
                        case "-o": 
                                if (i+1 == top){
                                        Help (); 
@@ -68,6 +126,20 @@ class MakeBundle {
                                output = args [++i];
                                break;
 
+                       case "--options":
+                               if (i+1 == top){
+                                       Help (); 
+                                       return 1;
+                               }
+                               embedded_options = args [++i];
+                               break;
+                       case "--runtime":
+                               if (i+1 == top){
+                                       Help (); 
+                                       return 1;
+                               }
+                               runtime = args [++i];
+                               break;
                        case "-oo":
                                if (i+1 == top){
                                        Help (); 
@@ -95,12 +167,9 @@ class MakeBundle {
                        case "--keeptemp":
                                keeptemp = true;
                                break;
+                               
                        case "--static":
                                static_link = true;
-                               if (!quiet) {
-                                       Console.WriteLine ("Note that statically linking the LGPL Mono runtime has more licensing restrictions than dynamically linking.");
-                                       Console.WriteLine ("See http://www.mono-project.com/Licensing for details on licensing.");
-                               }
                                break;
                        case "--config":
                                if (i+1 == top) {
@@ -201,13 +270,60 @@ class MakeBundle {
                foreach (string file in assemblies)
                        if (!QueueAssembly (files, file))
                                return 1;
-                       
-               GenerateBundles (files);
-               //GenerateJitWrapper ();
+
+               if (fetch_target != null){
+                       var truntime = Path.Combine (targets_dir, fetch_target, "mono");
+                       Directory.CreateDirectory (Path.GetDirectoryName (truntime));
+                       var wc = new WebClient ();
+                       var uri = new Uri ($"{target_server}{fetch_target}");
+                       try {
+                               wc.DownloadFile (uri, truntime);
+                       } catch {
+                               Console.Error.WriteLine ($"Failure to download the specified runtime from {uri}");
+                               File.Delete (truntime);
+                               return 1;
+                       }
+                       return 0;
+               }
+               
+               if (custom_mode)
+                       GenerateBundles (files);
+               else {
+                       if (cross_target == "default")
+                               runtime = null;
+                       else {
+                               var truntime = Path.Combine (targets_dir, cross_target, "mono");
+                               if (!File.Exists (truntime)){
+                                       Console.Error.WriteLine ($"The runtime for the {cross_target} does not exist, use --fetch-target {cross_target} to download first");
+                                       return 1;
+                               }
+                       }                               
+                       GeneratePackage (files);
+               }
                
                return 0;
        }
 
+       static string targets_dir = Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.Personal), ".mono", "targets");
+       
+       static void CommandLocalTargets ()
+       {
+               string [] targets;
+
+               Console.WriteLine ("Available targets:");
+               Console.WriteLine ("\tdefault\t- Current System Mono");
+               try {
+                       targets = Directory.GetDirectories (targets_dir);
+               } catch {
+                       return;
+               }
+               foreach (var target in targets){
+                       var p = Path.Combine (target, "mono");
+                       if (File.Exists (p))
+                               Console.WriteLine ("\t{0}", Path.GetFileName (target));
+               }
+       }
+
        static void WriteSymbol (StreamWriter sw, string name, long size)
        {
                switch (style){
@@ -268,6 +384,138 @@ class MakeBundle {
 
                ts.WriteLine ();
        }
+
+       class PackageMaker {
+               Dictionary<string, Tuple<long,int>> locations = new Dictionary<string, Tuple<long,int>> ();
+               const int align = 4096;
+               Stream package;
+               
+               public PackageMaker (string output)
+               {
+                       package = File.Create (output, 128*1024);
+                       if (IsUnix){
+                               File.SetAttributes (output, unchecked ((FileAttributes) 0x80000000));
+                       }
+               }
+
+               public int AddFile (string fname)
+               {
+                       using (Stream fileStream = File.OpenRead (fname)){
+                               var ret = fileStream.Length;
+                               
+                               Console.WriteLine ("At {0:x} with input {1}", package.Position, fileStream.Length);
+                               fileStream.CopyTo (package);
+                               package.Position = package.Position + (align - (package.Position % align));
+
+                               return (int) ret;
+                       }
+               }
+               
+               public void Add (string entry, string fname)
+               {
+                       var p = package.Position;
+                       var size = AddFile (fname);
+                       
+                       locations [entry] = Tuple.Create(p, size);
+               }
+
+               public void AddString (string entry, string text)
+               {
+                       var bytes = Encoding.UTF8.GetBytes (text);
+                       locations [entry] = Tuple.Create (package.Position, bytes.Length);
+                       package.Write (bytes, 0, bytes.Length);
+                       package.Position = package.Position + (align - (package.Position % align));
+               }
+
+               public void Dump ()
+               {
+                       foreach (var floc in locations.Keys){
+                               Console.WriteLine ($"{floc} at {locations[floc]:x}");
+                       }
+               }
+
+               public void WriteIndex ()
+               {
+                       var indexStart = package.Position;
+                       var binary = new BinaryWriter (package);
+
+                       binary.Write (locations.Count);
+                       foreach (var entry in from entry in locations orderby entry.Value.Item1 ascending select entry){
+                               var bytes = Encoding.UTF8.GetBytes (entry.Key);
+                               binary.Write (bytes.Length+1);
+                               binary.Write (bytes);
+                               binary.Write ((byte) 0);
+                               binary.Write (entry.Value.Item1);
+                               binary.Write (entry.Value.Item2);
+                       }
+                       binary.Write (indexStart);
+                       binary.Write (Encoding.UTF8.GetBytes ("xmonkeysloveplay"));
+                       binary.Flush ();
+               }
+               
+               public void Close ()
+               {
+                       WriteIndex ();
+                       package.Close ();
+                       package = null;
+               }
+       }
+
+       static bool MaybeAddFile (PackageMaker maker, string code, string file)
+       {
+               if (file == null)
+                       return true;
+               
+               if (!File.Exists (file)){
+                       Console.Error.WriteLine ("The file {0} does not exist", file);
+                       return false;
+               }
+               maker.Add (code, file);
+               return true;
+       }
+       
+       static bool GeneratePackage (List<string> files)
+       {
+               if (runtime == null){
+                       if (IsUnix)
+                               runtime = Process.GetCurrentProcess().MainModule.FileName;
+                       else {
+                               Console.Error.WriteLine ("You must specify at least one runtime with --runtime or --cross");
+                               Environment.Exit (1);
+                       }
+               }
+               if (!File.Exists (runtime)){
+                       Console.Error.WriteLine ($"The specified runtime at {runtime} does not exist");
+                       Environment.Exit (1);
+               }
+               
+               if (ctor_func != null){
+                       Console.Error.WriteLine ("--static-ctor not supported with package bundling, you must use native compilation for this");
+                       return false;
+               }
+               
+               var maker = new PackageMaker (output);
+               maker.AddFile (runtime);
+               
+               foreach (var url in files){
+                       string fname = LocateFile (new Uri (url).LocalPath);
+                       string aname = Path.GetFileName (fname);
+
+                       maker.Add ("assembly:" + aname, fname);
+                       if (File.Exists (fname + ".config"))
+                               maker.Add ("config:" + aname, fname + ".config");
+               }
+               if (!MaybeAddFile (maker, "systemconfig:", config_file) || !MaybeAddFile (maker, "machineconfig:", machine_config_file))
+                       return false;
+
+               if (config_dir != null)
+                       maker.Add ("config_dir:", config_dir);
+               if (embedded_options != null)
+                       maker.AddString ("options:", embedded_options);
+               maker.Dump ();
+               maker.Close ();
+               return true;
+       }
        
        static void GenerateBundles (List<string> files)
        {
@@ -353,10 +601,12 @@ void          mono_register_config_for_assembly (const char* assembly_name, cons
 
                        // The non-parallel part
                        byte [] buffer = new byte [8192];
+                       // everything other than a-zA-Z0-9_ needs to be escaped in asm symbols.
+                       var symbolEscapeRE = new System.Text.RegularExpressions.Regex ("[^\\w_]");
                        foreach (var url in files) {
                                string fname = LocateFile (new Uri (url).LocalPath);
                                string aname = Path.GetFileName (fname);
-                               string encoded = aname.Replace ("-", "_").Replace (".", "_");
+                               string encoded = symbolEscapeRE.Replace (aname, "_");
 
                                if (prog == null)
                                        prog = aname;
@@ -549,14 +799,15 @@ void          mono_register_config_for_assembly (const char* assembly_name, cons
                                        compilerArgs.Add(String.Format ("/I {0}", quote (include)));
 
                                if (!nomain || custom_main != null) {
-                                       compilerArgs.Add(temp_c);
-                                       compilerArgs.Add(temp_o);
+                                       compilerArgs.Add(quote(temp_c));
+                                       compilerArgs.Add(quote(temp_o));
                                        if (custom_main != null)
                                                compilerArgs.Add(quote(custom_main));
-                                       compilerArgs.Add(monoLib);
+                                       compilerArgs.Add(quote(monoLib));
                                        compilerArgs.Add("/link");
                                        compilerArgs.Add("/NODEFAULTLIB");
-                                       compilerArgs.Add("/SUBSYSTEM:CONSOLE");
+                                       compilerArgs.Add("/SUBSYSTEM:windows");
+                                       compilerArgs.Add("/ENTRY:mainCRTStartup");
                                        compilerArgs.AddRange(linkLibraries);
                                        compilerArgs.Add("/out:"+ output);
 
@@ -599,7 +850,7 @@ void          mono_register_config_for_assembly (const char* assembly_name, cons
                                                smonolib = "`pkg-config --variable=libdir mono-2`/libmono-2.0.a ";
                                        else
                                                smonolib = "-Wl,-Bstatic -lmono-2.0 -Wl,-Bdynamic ";
-                                       cmd = String.Format("{4} -o {2} -Wall `pkg-config --cflags mono-2` {0} {3} " +
+                                       cmd = String.Format("{4} -o '{2}' -Wall `pkg-config --cflags mono-2` {0} {3} " +
                                                "`pkg-config --libs-only-L mono-2` " + smonolib +
                                                "`pkg-config --libs-only-l mono-2 | sed -e \"s/\\-lmono-2.0 //\"` {1}",
                                                temp_c, temp_o, output, zlib, cc);
@@ -607,7 +858,7 @@ void          mono_register_config_for_assembly (const char* assembly_name, cons
                                else
                                {
 
-                                       cmd = String.Format("{4} " + debugging + " -o {2} -Wall {0} `pkg-config --cflags --libs mono-2` {3} {1}",
+                                       cmd = String.Format("{4} " + debugging + " -o '{2}' -Wall {0} `pkg-config --cflags --libs mono-2` {3} {1}",
                                                temp_c, temp_o, output, zlib, cc);
                                }
                                Execute (cmd);
@@ -757,25 +1008,35 @@ void          mono_register_config_for_assembly (const char* assembly_name, cons
        {
                Console.WriteLine ("Usage is: mkbundle [options] assembly1 [assembly2...]\n\n" +
                                   "Options:\n" +
-                                  "    -c                  Produce stub only, do not compile\n" +
+                                  "    --config F          Bundle system config file `F'\n" +
+                                  "    --config-dir D      Set MONO_CFG_DIR to `D'\n" +
+                                  "    --deps              Turns on automatic dependency embedding (default on simple)\n" +
+                                  "    -L path             Adds `path' to the search path for assemblies\n" +
+                                  "    --machine-config F  Use the given file as the machine.config for the application.\n" +
                                   "    -o out              Specifies output filename\n" +
+                                  "    --nodeps            Turns off automatic dependency embedding (default on custom)\n" +
+                                  "    --skip-scan         Skip scanning assemblies that could not be loaded (but still embed them).\n" +
+                                  "\n" + 
+                                  "--simple   Simple mode does not require a C toolchain and can cross compile\n" + 
+                                  "    --cross TARGET      Generates a binary for the given TARGET\n"+
+                                  "    --local-targets     Lists locally available targets\n" +
+                                  "    --list-targets      Lists available targets on the remote server\n" +
+                                  "    --options OPTIONS   Embed the specified Mono command line options on target\n" +
+                                  "    --runtime RUNTIME   Manually specifies the Mono runtime to use\n" +
+                                  "    --target-server URL Specified a server to download targets from, default is " + target_server + "\n" +
+                                  "\n" +
+                                  "--custom   Builds a custom launcher, options for --custom\n" +
+                                  "    -c                  Produce stub only, do not compile\n" +
                                   "    -oo obj             Specifies output filename for helper object file\n" +
-                                  "    -L path             Adds `path' to the search path for assemblies\n" +
-                                  "    --nodeps            Turns off automatic dependency embedding (default)\n" +
-                                  "    --deps              Turns on automatic dependency embedding\n" +
                                   "    --dos2unix[=true|false]\n" +
                                   "                        When no value provided, or when `true` specified\n" +
                                   "                        `dos2unix` will be invoked to convert paths on Windows.\n" +
                                   "                        When `--dos2unix=false` used, dos2unix is NEVER used.\n" +
                                   "    --keeptemp          Keeps the temporary files\n" +
-                                  "    --config F          Bundle system config file `F'\n" +
-                                  "    --config-dir D      Set MONO_CFG_DIR to `D'\n" +
-                                  "    --machine-config F  Use the given file as the machine.config for the application.\n" +
                                   "    --static            Statically link to mono libs\n" +
                                   "    --nomain            Don't include a main() function, for libraries\n" +
-                                  "    --custom-main C         Link the specified compilation unit (.c or .obj) with entry point/init code\n" +
+                                  "    --custom-main C     Link the specified compilation unit (.c or .obj) with entry point/init code\n" +
                                   "    -z                  Compress the assemblies before embedding.\n" +
-                                  "    --skip-scan         Skip scanning assemblies that could not be loaded (but still embed them).\n" +
                                   "    --static-ctor ctor  Add a constructor call to the supplied function.\n" +
                                   "                        You need zlib development headers and libraries.\n");
        }