2 // Mono.AssemblyLinker.AssemblyLinker
5 // Zoltan Varga (vargaz@freemail.hu)
7 // (C) Ximian, Inc. http://www.ximian.com
8 // Copyright (C) 2005 Novell, Inc (http://www.novell.com)
12 using System.Globalization;
14 using System.Collections;
15 using System.Collections.Generic;
16 using System.Reflection;
17 using System.Reflection.Emit;
18 using System.Security.Cryptography;
20 using System.Configuration.Assemblies;
22 using Mono.Security.Cryptography;
23 using IKR = IKVM.Reflection;
25 namespace Mono.AssemblyLinker
28 public string fileName;
34 public string fileName;
36 public bool isEmbedded;
37 public bool isPrivate;
52 public class AssemblyLinker {
54 ArrayList inputFiles = new ArrayList ();
55 ArrayList resources = new ArrayList ();
56 ArrayList cattrs = new ArrayList ();
63 bool isTemplateFile = false;
64 Target target = Target.Dll;
65 DelaySign delaysign = DelaySign.NotSet;
70 public static int Main (String[] args) {
71 return new AssemblyLinker ().DynMain (args);
74 private int DynMain (String[] args) {
82 private void ParseArgs (string[] args)
84 ArrayList flat_args = new ArrayList ();
86 // Process response files
87 Hashtable response_files = new Hashtable ();
88 foreach (string str in args) {
95 ReportMissingFileSpec ("@");
97 string resfile_name = Path.GetFullPath (str.Substring (1));
98 if (response_files.ContainsKey (resfile_name))
99 Report (1006, "Response file '" + resfile_name + "' was already included");
100 response_files [resfile_name] = resfile_name;
101 LoadArgs (resfile_name, flat_args);
104 if (flat_args.Count == 0)
107 foreach (string str in flat_args) {
108 if ((str [0] != '-') && (str [0] != '/')) {
109 inputFiles.Add (GetModuleInfo (str));
113 if (!ParseOption(str)) {
115 // cope with absolute filenames for modules on unix, as
116 // they also match the option pattern
118 // `/home/test.cs' is considered as a module, however
119 // '/test.cs' is considered as error
120 if (str.Length > 2 && str.IndexOf ('/', 2) != -1) {
121 inputFiles.Add (GetModuleInfo (str));
126 Report (1013, String.Format ("Unrecognized command line option: '{0}'", str));
131 if ((inputFiles.Count == 0) && (resources.Count == 0))
132 Report (1016, "No valid input files were specified");
135 Report (1017, "No target filename was specified");
137 if (target == Target.Dll && (entryPoint != null))
138 Report (1035, "Libraries cannot have an entry point");
140 if (target == Target.Exe && (entryPoint == null))
141 Report (1036, "Entry point required for executable applications");
144 private bool ParseOption (string str)
147 string opt = GetCommand (str, out arg);
157 ReportMissingFileSpec (opt);
158 ResourceInfo res = new ResourceInfo ();
159 res.isEmbedded = true;
160 String [] parts = arg.Split (',');
161 res.fileName = parts [0];
162 if (parts.Length > 1)
163 res.name = parts [1];
164 if (parts.Length > 2) {
169 res.isPrivate = true;
172 ReportInvalidArgument (opt, parts [2]);
182 ReportMissingFileSpec (opt);
183 ResourceInfo res = new ResourceInfo ();
184 String [] parts = arg.Split (',');
185 res.fileName = parts [0];
186 if (parts.Length > 1)
187 res.name = parts [1];
188 if (parts.Length > 2)
189 res.target = parts [2];
190 if (parts.Length > 3) {
195 res.isPrivate = true;
198 ReportInvalidArgument (opt, parts [3]);
208 ReportMissingArgument (opt);
210 string realArg = arg;
211 if (realArg.StartsWith ("0x"))
212 realArg = realArg.Substring (2);
213 uint val = Convert.ToUInt32 (realArg, 16);
214 AddCattr (typeof (AssemblyAlgorithmIdAttribute), typeof (uint), val);
215 } catch (Exception) {
216 ReportInvalidArgument (opt, arg);
221 ReportNotImplemented (opt);
225 ReportNotImplemented (opt);
229 ReportNotImplemented (opt);
235 ReportMissingText (opt);
236 AddCattr (typeof (AssemblyCompanyAttribute), arg);
240 case "configuration":
242 ReportMissingText (opt);
243 AddCattr (typeof (AssemblyConfigurationAttribute), arg);
249 ReportMissingText (opt);
250 AddCattr (typeof (AssemblyCopyrightAttribute), arg);
256 ReportMissingText (opt);
264 delaysign = DelaySign.Yes;
269 delaysign = DelaySign.No;
275 ReportMissingText (opt);
276 AddCattr (typeof (AssemblyDescriptionAttribute), arg);
282 ReportMissingFileSpec (opt);
283 ResourceInfo res = new ResourceInfo ();
284 res.name = "Security.Evidence";
286 res.isEmbedded = true;
287 res.isPrivate = true;
293 ReportMissingText (opt);
295 AddCattr (typeof (AssemblyFileVersionAttribute), arg);
300 ReportMissingArgument (opt);
302 string realArg = arg;
303 if (realArg.StartsWith ("0x"))
304 realArg = realArg.Substring (2);
305 uint val = Convert.ToUInt32 (realArg, 16);
306 AddCattr (typeof (AssemblyFlagsAttribute), typeof (uint), val);
307 } catch (Exception) {
308 ReportInvalidArgument (opt, arg);
319 ReportMissingText (opt);
326 ReportMissingText (opt);
332 ReportMissingText (opt);
341 ReportMissingFileSpec (opt);
348 ReportMissingText (opt);
349 AddCattr (typeof (AssemblyProductAttribute), arg);
353 case "productversion":
355 ReportMissingText (opt);
356 AddCattr (typeof (AssemblyInformationalVersionAttribute), arg);
362 ReportMissingText (opt);
373 Report (0, "target:win is not implemented");
376 ReportInvalidArgument (opt, arg);
383 ReportMissingFileSpec (opt);
384 isTemplateFile = true;
385 templateFile = Path.Combine (Directory.GetCurrentDirectory (), arg);
390 ReportMissingText (opt);
391 AddCattr (typeof (AssemblyTitleAttribute), arg);
397 ReportMissingText (opt);
398 AddCattr (typeof (AssemblyTrademarkAttribute), arg);
403 // This option conflicts with the standard UNIX meaning
408 AddCattr (typeof (AssemblyVersionAttribute), arg);
413 ReportMissingFileSpec (opt);
419 ReportMissingFileSpec (opt);
426 private bool RunningOnUnix {
428 // check for Unix platforms - see FAQ for more details
429 // http://www.mono-project.com/FAQ:_Technical#How_to_detect_the_execution_platform_.3F
430 int platform = (int) Environment.OSVersion.Platform;
431 return ((platform == 4) || (platform == 128) || (platform == 6));
435 private ModuleInfo GetModuleInfo (string str)
437 string [] parts = str.Split (',');
438 ModuleInfo mod = new ModuleInfo ();
439 mod.fileName = parts [0];
440 if (parts.Length > 1)
441 mod.target = parts [1];
445 private string GetCommand (string str, out string command_arg) {
446 if ((str [0] == '-') && (str.Length > 1) && (str [1] == '-'))
447 str = str.Substring (1);
449 int end_index = str.IndexOfAny (new char[] {':', '='}, 1);
450 string command = str.Substring (1,
451 end_index == -1 ? str.Length - 1 : end_index - 1);
453 if (end_index != -1) {
454 command_arg = str.Substring (end_index+1);
455 if (command_arg == String.Empty)
461 return command.ToLower ();
464 private void AddCattr (Type attrType, Type arg, object value) {
465 cattrs.Add (new CustomAttributeBuilder (attrType.GetConstructor (new Type [] { arg }), new object [] { value }));
468 private void AddCattr (Type attrType, object value) {
469 AddCattr (attrType, typeof (string), value);
472 private void PrintVersion () {
473 Console.WriteLine ("Mono Assembly Linker (al.exe) version " + Consts.MonoVersion);
476 private void Version () {
478 Environment.Exit (0);
481 private void Usage () {
484 foreach (string s in usage)
485 Console.WriteLine (s);
486 Environment.Exit (0);
489 private void Report (int errorNum, string msg) {
490 Console.WriteLine (String.Format ("ALINK: error A{0:0000}: {1}", errorNum, msg));
491 Environment.Exit (1);
494 private void ReportWarning (int errorNum, string msg) {
495 Console.WriteLine (String.Format ("ALINK: warning A{0:0000}: {1}", errorNum, msg));
498 private void ReportInvalidArgument (string option, string value) {
499 Report (1012, String.Format ("'{0}' is not a valid setting for option '{1}'", value, option));
502 private void ReportMissingArgument (string option) {
503 Report (1003, String.Format ("Compiler option '{0}' must be followed by an argument", option));
506 private void ReportNotImplemented (string option) {
507 Report (0, String.Format ("Compiler option '{0}' is not implemented", option));
510 private void ReportMissingFileSpec (string option) {
511 Report (1008, String.Format ("Missing file specification for '{0}' command-line option", option));
514 private void ReportMissingText (string option) {
515 Report (1010, String.Format ("Missing ':<text>' for '{0}' option", option));
518 // copied from /mcs/mcs/codegen.cs
519 private void SetPublicKey (AssemblyName an, byte[] strongNameBlob)
521 // check for possible ECMA key
522 if (strongNameBlob.Length == 16) {
523 // will be rejected if not "the" ECMA key
524 an.SetPublicKey (strongNameBlob);
526 // take it, with or without, a private key
527 RSA rsa = CryptoConvert.FromCapiKeyBlob (strongNameBlob);
528 // and make sure we only feed the public part to Sys.Ref
529 byte[] publickey = CryptoConvert.ToCapiPublicKeyBlob (rsa);
531 // AssemblyName.SetPublicKey requires an additional header
532 byte[] publicKeyHeader = new byte [12] { 0x00, 0x24, 0x00, 0x00, 0x04, 0x80, 0x00, 0x00, 0x94, 0x00, 0x00, 0x00 };
534 byte[] encodedPublicKey = new byte [12 + publickey.Length];
535 Buffer.BlockCopy (publicKeyHeader, 0, encodedPublicKey, 0, 12);
536 Buffer.BlockCopy (publickey, 0, encodedPublicKey, 12, publickey.Length);
537 an.SetPublicKey (encodedPublicKey);
541 private void SetKeyPair (AssemblyName aname)
543 if (keyfile != null) {
544 if (!File.Exists (keyfile)) {
545 Report (1044, String.Format ("Couldn't open '{0}' key file.", keyfile));
548 using (FileStream fs = File.OpenRead (keyfile)) {
549 byte[] data = new byte [fs.Length];
551 fs.Read (data, 0, data.Length);
553 if (delaysign == DelaySign.Yes) {
554 SetPublicKey (aname, data);
556 CryptoConvert.FromCapiPrivateKeyBlob (data);
557 aname.KeyPair = new StrongNameKeyPair (data);
560 catch (CryptographicException) {
561 if (delaysign != DelaySign.Yes) {
562 if (data.Length == 16) {
563 // error # is different for ECMA key
564 Report (1019, "Could not strongname the assembly. " +
565 "ECMA key can only be used to delay-sign assemblies");
567 Report (1028, String.Format ("Key file {0}' is missing it's private key " +
568 "or couldn't be decoded.", keyfile));
571 Report (1044, String.Format ("Couldn't decode '{0}' key file.", keyfile));
576 } else if (keyname != null) {
577 // delay-sign doesn't apply to key containers
578 aname.KeyPair = new StrongNameKeyPair (keyname);
582 private void DoIt () {
583 AssemblyName aname = new AssemblyName ();
584 aname.Name = Path.GetFileNameWithoutExtension (outFile);
586 aname.CultureInfo = new CultureInfo (culture);
588 string fileName = Path.GetFileName (outFile);
597 aname = ReadCustomAttributesFromTemplateFile (templateFile, aname);
601 if (fileName != outFile)
602 ab = AppDomain.CurrentDomain.DefineDynamicAssembly (aname, AssemblyBuilderAccess.Save, Path.GetDirectoryName (outFile));
604 ab = AppDomain.CurrentDomain.DefineDynamicAssembly (aname, AssemblyBuilderAccess.Save);
606 foreach (CustomAttributeBuilder cb in cattrs)
607 ab.SetCustomAttribute (cb);
613 foreach (ModuleInfo mod in inputFiles) {
614 MethodInfo mi = typeof (AssemblyBuilder).GetMethod ("AddModule", BindingFlags.Instance|BindingFlags.Public|BindingFlags.NonPublic);
616 Report (0, "Cannot add modules on this runtime: try the Mono runtime instead.");
618 if (mod.target != null) {
619 File.Copy (mod.fileName, mod.target, true);
620 mod.fileName = mod.target;
623 bool isAssembly = false;
625 AssemblyName.GetAssemblyName (mod.fileName);
632 ReportWarning (1020, "Ignoring included assembly '" + mod.fileName + "'");
634 mi.Invoke (ab, new object [] { mod.fileName });
641 if (entryPoint != null) {
642 string mainClass = entryPoint.Substring (0, entryPoint.LastIndexOf ('.'));
643 string mainMethod = entryPoint.Substring (entryPoint.LastIndexOf ('.') + 1);
645 MethodInfo mainMethodInfo = null;
648 Type mainType = ab.GetType (mainClass);
649 if (mainType != null)
650 mainMethodInfo = mainType.GetMethod (mainMethod);
652 catch (Exception ex) {
653 Console.WriteLine (ex);
655 if (mainMethodInfo != null)
656 ab.SetEntryPoint (mainMethodInfo);
658 Report (1037, "Unable to find the entry point method '" + entryPoint + "'");
665 ab.DefineVersionInfoResource ();
667 if (win32IconFile != null) {
669 MethodInfo mi = typeof (AssemblyBuilder).GetMethod ("DefineIconResource", BindingFlags.Instance|BindingFlags.Public|BindingFlags.NonPublic);
671 Report (0, "Cannot embed win32 icons on this runtime: try the Mono runtime instead.");
672 mi.Invoke (ab, new object [] { win32IconFile });
674 catch (Exception ex) {
675 Report (1031, "Error reading icon '" + win32IconFile + "' --" + ex);
679 if (win32ResFile != null) {
681 ab.DefineUnmanagedResource (win32ResFile);
683 catch (Exception ex) {
684 Report (1019, "Metadata failure creating assembly -- " + ex);
688 foreach (ResourceInfo res in resources) {
689 if (res.name == null)
690 res.name = Path.GetFileName (res.fileName);
692 foreach (ResourceInfo res2 in resources)
693 if ((res != res2) && (res.name == res2.name))
694 Report (1046, String.Format ("Resource identifier '{0}' has already been used in this assembly", res.name));
696 if (res.isEmbedded) {
697 MethodInfo mi = typeof (AssemblyBuilder).GetMethod ("EmbedResourceFile", BindingFlags.Instance|BindingFlags.Public|BindingFlags.NonPublic,
698 null, CallingConventions.Any, new Type [] { typeof (string), typeof (string) }, null);
700 Report (0, "Cannot embed resources on this runtime: try the Mono runtime instead.");
701 mi.Invoke (ab, new object [] { res.name, res.fileName });
704 if (res.target != null) {
705 File.Copy (res.fileName, res.target, true);
706 res.fileName = res.target;
709 // AddResourceFile must receive a file name and not a path.
710 // Drop directory and give warning if we have a path.
711 var resourceFileName = Path.GetFileName(res.fileName);
712 if (Path.GetDirectoryName (res.fileName) != null || Path.IsPathRooted(res.fileName)) {
713 ReportWarning (99999,
714 String.Format ("Path '{0}' in the resource name is not supported. Using just file name '{1}'",
719 ab.AddResourceFile (res.name, resourceFileName,
720 res.isPrivate ? ResourceAttributes.Private : ResourceAttributes.Public);
727 catch (Exception ex) {
728 Report (1019, "Metadata failure creating assembly -- " + ex);
732 private AssemblyName ReadCustomAttributesFromTemplateFile (string templateFile, AssemblyName aname)
734 // LAMESPEC: according to MSDN, the template assembly must have a
735 // strong name but this is not enforced
736 const IKR.UniverseOptions options = IKR.UniverseOptions.MetadataOnly;
738 var universe = new IKR.Universe (options);
739 var asm = universe.LoadFile (templateFile);
741 // Create missing assemblies, we don't want to load them!
742 // Code taken from ikdasm
743 var names = new HashSet<string> ();
744 IKR.AssemblyName[] assembly_refs = asm.ManifestModule.__GetReferencedAssemblies ();
746 var resolved_assemblies = new IKR.Assembly [assembly_refs.Length];
747 for (int i = 0; i < resolved_assemblies.Length; i++) {
748 string name = assembly_refs [i].Name;
750 while (names.Contains (name)) {
751 name = name + "_" + i;
754 resolved_assemblies [i] = universe.CreateMissingAssembly (assembly_refs [i].FullName);
756 asm.ManifestModule.__ResolveReferencedAssemblies (resolved_assemblies);
758 foreach (var attr_data in asm.__GetCustomAttributes (null, false)) {
759 string asm_name = attr_data.AttributeType.Assembly.GetName ().Name;
760 if (asm_name != "mscorlib")
763 switch (attr_data.AttributeType.FullName) {
764 case "System.Reflection.AssemblyKeyFileAttribute": {
766 // ignore if specified on command line
769 // / AssemblyKeyFileAttribute .ctor(string keyFile)
770 string key_file_value = (string) attr_data.ConstructorArguments [0].Value;
772 if (!String.IsNullOrEmpty (key_file_value))
773 keyfile = Path.Combine (Path.GetDirectoryName (templateFile), key_file_value);
777 case "System.Reflection.AssemblyDelaySignAttribute": {
778 if (delaysign != DelaySign.NotSet)
779 // ignore if specified on command line
782 // AssemblyDelaySignAttribute .ctor(bool delaySign)
783 bool delay_sign_value = (bool) attr_data.ConstructorArguments [0].Value;
784 delaysign = delay_sign_value ? DelaySign.Yes : DelaySign.No;
788 case "System.Reflection.AssemblyKeyNameAttribute": {
790 // ignore if specified on command line
793 // AssemblyKeyNameAttribute .ctor(string keyName)
794 string key_name_value = (string) attr_data.ConstructorArguments [0].Value;
796 // ignore null or zero-length keyname
797 if (!String.IsNullOrEmpty (key_name_value))
798 keyname = key_name_value;
804 var asm_name_for_template_file = asm.GetName ();
805 aname.Version = asm_name_for_template_file.Version;
806 aname.HashAlgorithm = asm_name_for_template_file.HashAlgorithm;
811 private void LoadArgs (string file, ArrayList args) {
812 StreamReader f = null;
815 f = new StreamReader (file);
817 StringBuilder sb = new StringBuilder ();
819 while ((line = f.ReadLine ()) != null){
822 for (int i = 0; i < t; i++){
825 if (c == '"' || c == '\''){
828 for (i++; i < t; i++){
835 } else if (c == ' '){
837 args.Add (sb.ToString ());
844 args.Add (sb.ToString ());
848 } catch (Exception ex) {
849 Report (1007, "Error opening response file '" + file + "' -- '" + ex.Message + "'");
857 "Usage: al [options] [sources]",
858 "Options: ('/out' must be specified)",
860 " /? or /help Display this usage message",
861 " @<filename> Read response file for more options",
862 " /algid:<id> Algorithm used to hash files (in hexadecimal)",
863 " /base[address]:<addr> Base address for the library",
864 " /bugreport:<filename> Create a 'Bug Report' file",
865 " /comp[any]:<text> Company name",
866 " /config[uration]:<text> Configuration string",
867 " /copy[right]:<text> Copyright message",
868 " /c[ulture]:<text> Supported culture",
869 " /delay[sign][+|-] Delay sign this assembly",
870 " /descr[iption]:<text> Description",
871 " /e[vidence]:<filename> Security evidence file to embed",
872 " /fileversion:<version> Optional Win32 version (overrides assembly version)",
873 " /flags:<flags> Assembly flags (in hexadecimal)",
874 " /fullpaths Display files using fully-qualified filenames",
875 " /keyf[ile]:<filename> File containing key to sign the assembly",
876 " /keyn[ame]:<text> Key container name of key to sign assembly",
877 " /main:<method> Specifies the method name of the entry point",
878 " /nologo Suppress the startup banner and copyright message",
879 " /out:<filename> Output file name for the assembly manifest",
880 " /prod[uct]:<text> Product name",
881 " /productv[ersion]:<text> Product version",
882 " /t[arget]:lib[rary] Create a library",
883 " /t[arget]:exe Create a console executable",
884 " /t[arget]:win[exe] Create a Windows executable",
885 " /template:<filename> Specifies an assembly to get default options from",
886 " /title:<text> Title",
887 " /trade[mark]:<text> Trademark message",
888 " /v[ersion]:<version> Version (use * to auto-generate remaining numbers)",
889 " /win32icon:<filename> Use this icon for the output",
890 " /win32res:<filename> Specifies the Win32 resource file",
892 "Sources: (at least one source input is required)",
893 " <filename>[,<targetfile>] add file to assembly",
894 " /embed[resource]:<filename>[,<name>[,Private]]",
895 " embed the file as a resource in the assembly",
896 " /link[resource]:<filename>[,<name>[,<targetfile>[,Private]]]",
897 " link the file as a resource to the assembly",