2 // mkbundle: tool to create bundles.
4 // Based on the `make-bundle' Perl script written by Paolo Molaro (lupus@debian.org)
9 // (C) Novell, Inc 2004
12 using System
.Diagnostics
;
14 using System
.Collections
.Generic
;
16 using System
.IO
.Compression
;
17 using System
.Runtime
.InteropServices
;
19 using IKVM
.Reflection
;
23 using System
.Threading
.Tasks
;
27 static string output
= "a.out";
28 static string object_out
= null;
29 static List
<string> link_paths
= new List
<string> ();
30 static bool autodeps
= false;
31 static bool keeptemp
= false;
32 static bool compile_only
= false;
33 static bool static_link
= false;
34 static string config_file
= null;
35 static string machine_config_file
= null;
36 static string config_dir
= null;
37 static string style
= "linux";
40 static bool? use_dos2unix
= null;
42 static int Main (string [] args
)
44 List
<string> sources
= new List
<string> ();
45 int top
= args
.Length
;
50 for (int i
= 0; i
< top
; i
++){
52 case "--help": case "-h": case "-?":
73 object_out
= args
[++i
];
81 link_paths
.Add (args
[++i
]);
97 Console
.WriteLine ("Note that statically linking the LGPL Mono runtime has more licensing restrictions than dynamically linking.");
98 Console
.WriteLine ("See http://www.mono-project.com/Licensing for details on licensing.");
106 config_file
= args
[++i
];
108 case "--machine-config":
114 machine_config_file
= args
[++i
];
116 Console
.WriteLine ("WARNING:\n Check that the machine.config file you are bundling\n doesn't contain sensitive information specific to this machine.");
124 config_dir
= args
[++i
];
144 Console
.Error
.WriteLine ("Invalid style '{0}' - only 'windows', 'mac' and 'linux' are supported for --style argument", style
);
150 sources
.Add (args
[i
]);
154 if (static_link
&& style
== "windows") {
155 Console
.Error
.WriteLine ("The option `{0}' is not supported on this platform.", args
[i
]);
160 Console
.WriteLine ("Sources: {0} Auto-dependencies: {1}", sources
.Count
, autodeps
);
161 if (sources
.Count
== 0 || output
== null) {
163 Environment
.Exit (1);
166 List
<Assembly
> assemblies
= LoadAssemblies (sources
);
167 List
<string> files
= new List
<string> ();
168 foreach (Assembly a
in assemblies
)
169 QueueAssembly (files
, a
.CodeBase
);
171 // Special casing mscorlib.dll: any specified mscorlib.dll cannot be loaded
172 // by Assembly.ReflectionFromLoadFrom(). Instead the fx assembly which runs
173 // mkbundle.exe is loaded, which is not what we want.
174 // So, replace it with whatever actually specified.
175 foreach (string srcfile
in sources
) {
176 if (Path
.GetFileName (srcfile
) == "mscorlib.dll") {
177 foreach (string file
in files
) {
178 if (Path
.GetFileName (new Uri (file
).LocalPath
) == "mscorlib.dll") {
180 files
.Add (new Uri (Path
.GetFullPath (srcfile
)).LocalPath
);
188 GenerateBundles (files
);
189 //GenerateJitWrapper ();
194 static void WriteSymbol (StreamWriter sw
, string name
, long size
)
200 "\t.section .rodata\n" +
202 "\t.type {0}, \"object\"\n" +
203 "\t.size {0}, {1}\n" +
209 "\t.section __TEXT,__text,regular,pure_instructions\n" +
219 "\t.section .rdata,\"dr\"\n" +
227 static string [] chars
= new string [256];
229 static void WriteBuffer (StreamWriter ts
, Stream stream
, byte[] buffer
)
233 // Preallocate the strings we need.
234 if (chars
[0] == null) {
235 for (int i
= 0; i
< chars
.Length
; i
++)
236 chars
[i
] = string.Format ("{0}", i
.ToString ());
239 while ((n
= stream
.Read (buffer
, 0, buffer
.Length
)) != 0) {
241 for (int i
= 0; i
< n
; i
++) {
242 if (count
% 32 == 0) {
243 ts
.Write ("\n\t.byte ");
247 ts
.Write (chars
[buffer
[i
]]);
255 static void GenerateBundles (List
<string> files
)
257 string temp_s
= "temp.s"; // Path.GetTempFileName ();
258 string temp_c
= "temp.c";
259 string temp_o
= "temp.o";
263 if (object_out
!= null)
267 List
<string> c_bundle_names
= new List
<string> ();
268 List
<string[]> config_names
= new List
<string[]> ();
270 using (StreamWriter ts
= new StreamWriter (File
.Create (temp_s
))) {
271 using (StreamWriter tc
= new StreamWriter (File
.Create (temp_c
))) {
275 tc
.WriteLine ("/* This source code was produced by mkbundle, do not edit */");
276 tc
.WriteLine ("\n#ifndef NULL\n#define NULL (void *)0\n#endif");
280 const unsigned char *data;
281 const unsigned int size;
282 } MonoBundledAssembly;
283 void mono_register_bundled_assemblies (const MonoBundledAssembly **assemblies);
284 void mono_register_config_for_assembly (const char* assembly_name, const char* config_xml);
287 tc
.WriteLine ("#include <mono/metadata/mono-config.h>");
288 tc
.WriteLine ("#include <mono/metadata/assembly.h>\n");
292 tc
.WriteLine ("typedef struct _compressed_data {");
293 tc
.WriteLine ("\tMonoBundledAssembly assembly;");
294 tc
.WriteLine ("\tint compressed_size;");
295 tc
.WriteLine ("} CompressedAssembly;\n");
298 object monitor
= new object ();
300 var streams
= new Dictionary
<string, Stream
> ();
301 var sizes
= new Dictionary
<string, long> ();
303 // Do the file reading and compression in parallel
304 Action
<string> body
= delegate (string url
) {
305 string fname
= new Uri (url
).LocalPath
;
306 Stream stream
= File
.OpenRead (fname
);
308 long real_size
= stream
.Length
;
311 byte[] cbuffer
= new byte [8192];
312 MemoryStream ms
= new MemoryStream ();
313 GZipStream deflate
= new GZipStream (ms
, CompressionMode
.Compress
, leaveOpen
:true);
314 while ((n
= stream
.Read (cbuffer
, 0, cbuffer
.Length
)) != 0){
315 deflate
.Write (cbuffer
, 0, n
);
319 byte [] bytes
= ms
.GetBuffer ();
320 stream
= new MemoryStream (bytes
, 0, (int) ms
.Length
, false, false);
324 streams
[url
] = stream
;
325 sizes
[url
] = real_size
;
331 Parallel
.ForEach (files
, body
);
333 foreach (var url
in files
)
337 // The non-parallel part
338 byte [] buffer
= new byte [8192];
339 foreach (var url
in files
) {
340 string fname
= new Uri (url
).LocalPath
;
341 string aname
= Path
.GetFileName (fname
);
342 string encoded
= aname
.Replace ("-", "_").Replace (".", "_");
347 var stream
= streams
[url
];
348 var real_size
= sizes
[url
];
350 Console
.WriteLine (" embedding: " + fname
);
352 WriteSymbol (ts
, "assembly_data_" + encoded
, stream
.Length
);
354 WriteBuffer (ts
, stream
, buffer
);
357 tc
.WriteLine ("extern const unsigned char assembly_data_{0} [];", encoded
);
358 tc
.WriteLine ("static CompressedAssembly assembly_bundle_{0} = {{{{\"{1}\"," +
359 " assembly_data_{0}, {2}}}, {3}}};",
360 encoded
, aname
, real_size
, stream
.Length
);
361 double ratio
= ((double) stream
.Length
* 100) / real_size
;
362 Console
.WriteLine (" compression ratio: {0:.00}%", ratio
);
364 tc
.WriteLine ("extern const unsigned char assembly_data_{0} [];", encoded
);
365 tc
.WriteLine ("static const MonoBundledAssembly assembly_bundle_{0} = {{\"{1}\", assembly_data_{0}, {2}}};",
366 encoded
, aname
, real_size
);
370 c_bundle_names
.Add ("assembly_bundle_" + encoded
);
373 FileStream cf
= File
.OpenRead (fname
+ ".config");
374 Console
.WriteLine (" config from: " + fname
+ ".config");
375 tc
.WriteLine ("extern const unsigned char assembly_config_{0} [];", encoded
);
376 WriteSymbol (ts
, "assembly_config_" + encoded
, cf
.Length
);
377 WriteBuffer (ts
, cf
, buffer
);
379 config_names
.Add (new string[] {aname, encoded}
);
380 } catch (FileNotFoundException
) {
381 /* we ignore if the config file doesn't exist */
385 if (config_file
!= null){
388 conf
= File
.OpenRead (config_file
);
390 Error (String
.Format ("Failure to open {0}", config_file
));
393 Console
.WriteLine ("System config from: " + config_file
);
394 tc
.WriteLine ("extern const char system_config;");
395 WriteSymbol (ts
, "system_config", config_file
.Length
);
397 WriteBuffer (ts
, conf
, buffer
);
399 ts
.Write ("\t.byte 0\n");
403 if (machine_config_file
!= null){
406 conf
= File
.OpenRead (machine_config_file
);
408 Error (String
.Format ("Failure to open {0}", machine_config_file
));
411 Console
.WriteLine ("Machine config from: " + machine_config_file
);
412 tc
.WriteLine ("extern const char machine_config;");
413 WriteSymbol (ts
, "machine_config", machine_config_file
.Length
);
415 WriteBuffer (ts
, conf
, buffer
);
416 ts
.Write ("\t.byte 0\n");
421 Console
.WriteLine ("Compiling:");
422 string cmd
= String
.Format ("{0} -o {1} {2} ", GetEnv ("AS", "as"), temp_o
, temp_s
);
423 int ret
= Execute (cmd
);
430 tc
.WriteLine ("\nstatic const CompressedAssembly *compressed [] = {");
432 tc
.WriteLine ("\nstatic const MonoBundledAssembly *bundled [] = {");
434 foreach (string c
in c_bundle_names
){
435 tc
.WriteLine ("\t&{0},", c
);
437 tc
.WriteLine ("\tNULL\n};\n");
438 tc
.WriteLine ("static char *image_name = \"{0}\";", prog
);
440 tc
.WriteLine ("\nstatic void install_dll_config_files (void) {\n");
441 foreach (string[] ass
in config_names
){
442 tc
.WriteLine ("\tmono_register_config_for_assembly (\"{0}\", assembly_config_{1});\n", ass
[0], ass
[1]);
444 if (config_file
!= null)
445 tc
.WriteLine ("\tmono_config_parse_memory (&system_config);\n");
446 if (machine_config_file
!= null)
447 tc
.WriteLine ("\tmono_register_machine_config (&machine_config);\n");
448 tc
.WriteLine ("}\n");
450 if (config_dir
!= null)
451 tc
.WriteLine ("static const char *config_dir = \"{0}\";", config_dir
);
453 tc
.WriteLine ("static const char *config_dir = NULL;");
455 Stream template_stream
;
457 template_stream
= System
.Reflection
.Assembly
.GetAssembly (typeof(MakeBundle
)).GetManifestResourceStream ("template_z.c");
459 template_stream
= System
.Reflection
.Assembly
.GetAssembly (typeof(MakeBundle
)).GetManifestResourceStream ("template.c");
462 StreamReader s
= new StreamReader (template_stream
);
463 string template
= s
.ReadToEnd ();
467 Stream template_main_stream
= System
.Reflection
.Assembly
.GetAssembly (typeof(MakeBundle
)).GetManifestResourceStream ("template_main.c");
468 StreamReader st
= new StreamReader (template_main_stream
);
469 string maintemplate
= st
.ReadToEnd ();
470 tc
.Write (maintemplate
);
478 string zlib
= (compress
? "-lz" : "");
479 string debugging
= "-g";
480 string cc
= GetEnv ("CC", IsUnix
? "cc" : "i686-pc-mingw32-gcc");
482 if (style
== "linux")
487 smonolib
= "`pkg-config --variable=libdir mono-2`/libmono-2.0.a ";
489 smonolib
= "-Wl,-Bstatic -lmono-2.0 -Wl,-Bdynamic ";
490 cmd
= String
.Format ("{4} -o {2} -Wall `pkg-config --cflags mono-2` {0} {3} " +
491 "`pkg-config --libs-only-L mono-2` " + smonolib
+
492 "`pkg-config --libs-only-l mono-2 | sed -e \"s/\\-lmono-2.0 //\"` {1}",
493 temp_c
, temp_o
, output
, zlib
, cc
);
496 cmd
= String
.Format ("{4} " + debugging
+ " -o {2} -Wall {0} `pkg-config --cflags --libs mono-2` {3} {1}",
497 temp_c
, temp_o
, output
, zlib
, cc
);
505 Console
.WriteLine ("Done");
510 if (object_out
== null){
511 File
.Delete (temp_o
);
514 File
.Delete (temp_c
);
516 File
.Delete (temp_s
);
521 static List
<Assembly
> LoadAssemblies (List
<string> sources
)
523 List
<Assembly
> assemblies
= new List
<Assembly
> ();
526 foreach (string name
in sources
){
527 Assembly a
= LoadAssembly (name
);
538 Environment
.Exit (1);
543 static readonly Universe universe
= new Universe ();
545 static void QueueAssembly (List
<string> files
, string codebase
)
547 if (files
.Contains (codebase
))
550 files
.Add (codebase
);
551 Assembly a
= universe
.LoadFile (new Uri(codebase
).LocalPath
);
556 foreach (AssemblyName an
in a
.GetReferencedAssemblies ()) {
557 a
= universe
.Load (an
.Name
);
558 QueueAssembly (files
, a
.CodeBase
);
562 static Assembly
LoadAssembly (string assembly
)
567 char[] path_chars
= { '/', '\\' }
;
569 if (assembly
.IndexOfAny (path_chars
) != -1) {
570 a
= universe
.LoadFile (assembly
);
572 string ass
= assembly
;
573 if (ass
.EndsWith (".dll"))
574 ass
= assembly
.Substring (0, assembly
.Length
- 4);
575 a
= universe
.Load (ass
);
578 } catch (FileNotFoundException
){
579 string total_log
= "";
581 foreach (string dir
in link_paths
){
582 string full_path
= Path
.Combine (dir
, assembly
);
583 if (!assembly
.EndsWith (".dll") && !assembly
.EndsWith (".exe"))
587 a
= universe
.LoadFile (full_path
);
589 } catch (FileNotFoundException ff
) {
590 total_log
+= ff
.FusionLog
;
594 Error ("Cannot find assembly `" + assembly
+ "'" );
595 Console
.WriteLine ("Log: \n" + total_log
);
596 } catch (IKVM
.Reflection
.BadImageFormatException f
) {
597 Error ("Cannot load assembly (bad file format) " + f
.Message
);
598 } catch (FileLoadException f
){
599 Error ("Cannot load assembly " + f
.Message
);
600 } catch (ArgumentNullException
){
601 Error("Cannot load assembly (null argument)");
606 static void Error (string msg
)
608 Console
.Error
.WriteLine (msg
);
609 Environment
.Exit (1);
614 Console
.WriteLine ("Usage is: mkbundle [options] assembly1 [assembly2...]\n\n" +
616 " -c Produce stub only, do not compile\n" +
617 " -o out Specifies output filename\n" +
618 " -oo obj Specifies output filename for helper object file\n" +
619 " -L path Adds `path' to the search path for assemblies\n" +
620 " --nodeps Turns off automatic dependency embedding (default)\n" +
621 " --deps Turns on automatic dependency embedding\n" +
622 " --keeptemp Keeps the temporary files\n" +
623 " --config F Bundle system config file `F'\n" +
624 " --config-dir D Set MONO_CFG_DIR to `D'\n" +
625 " --machine-config F Use the given file as the machine.config for the application.\n" +
626 " --static Statically link to mono libs\n" +
627 " --nomain Don't include a main() function, for libraries\n" +
628 " -z Compress the assemblies before embedding.\n" +
629 " You need zlib development headers and libraries.\n");
633 static extern int system (string s
);
635 static extern int uname (IntPtr buf
);
637 static void DetectOS ()
640 Console
.WriteLine ("OS is: Windows");
645 IntPtr buf
= Marshal
.AllocHGlobal (8192);
646 if (uname (buf
) != 0){
647 Console
.WriteLine ("Warning: Unable to detect OS");
648 Marshal
.FreeHGlobal (buf
);
651 string os
= Marshal
.PtrToStringAnsi (buf
);
652 Console
.WriteLine ("OS is: " + os
);
656 Marshal
.FreeHGlobal (buf
);
661 int p
= (int) Environment
.OSVersion
.Platform
;
662 return ((p
== 4) || (p
== 128) || (p
== 6));
666 static int Execute (string cmdLine
)
669 Console
.WriteLine (cmdLine
);
670 return system (cmdLine
);
673 // on Windows, we have to pipe the output of a
674 // `cmd` interpolation to dos2unix, because the shell does not
675 // strip the CRLFs generated by the native pkg-config distributed
678 // But if it's *not* on cygwin, just skip it.
680 // check if dos2unix is applicable.
681 if (use_dos2unix
== null) {
682 use_dos2unix
= false;
684 var dos2unix
= Process
.Start ("dos2unix");
685 dos2unix
.StandardInput
.WriteLine ("aaa");
686 dos2unix
.StandardInput
.WriteLine ("\u0004");
687 dos2unix
.WaitForExit ();
688 if (dos2unix
.ExitCode
== 0)
694 // and if there is no dos2unix, just run cmd /c.
695 if (use_dos2unix
== false) {
696 Console
.WriteLine (cmdLine
);
697 ProcessStartInfo dos2unix
= new ProcessStartInfo ();
698 dos2unix
.UseShellExecute
= false;
699 dos2unix
.FileName
= "cmd";
700 dos2unix
.Arguments
= String
.Format ("/c \"{0}\"", cmdLine
);
702 using (Process p
= Process
.Start (dos2unix
)) {
708 StringBuilder b
= new StringBuilder ();
710 for (int i
= 0; i
< cmdLine
.Length
; i
++) {
711 if (cmdLine
[i
] == '`') {
712 if (count
% 2 != 0) {
713 b
.Append ("|dos2unix");
717 b
.Append (cmdLine
[i
]);
719 cmdLine
= b
.ToString ();
720 Console
.WriteLine (cmdLine
);
722 ProcessStartInfo psi
= new ProcessStartInfo ();
723 psi
.UseShellExecute
= false;
725 psi
.Arguments
= String
.Format ("-c \"{0}\"", cmdLine
);
727 using (Process p
= Process
.Start (psi
)) {
733 static string GetEnv (string name
, string defaultValue
)
735 string s
= Environment
.GetEnvironmentVariable (name
);
736 return s
!= null ? s
: defaultValue
;