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)
13 using System.Collections;
14 using System.Reflection;
15 using System.Reflection.Emit;
16 using System.Security.Cryptography;
18 using System.Configuration.Assemblies;
20 using Mono.Security.Cryptography;
22 namespace Mono.AssemblyLinker
25 public string fileName;
31 public string fileName;
33 public bool isEmbedded;
34 public bool isPrivate;
43 public class AssemblyLinker {
45 ArrayList inputFiles = new ArrayList ();
46 ArrayList resources = new ArrayList ();
47 ArrayList cattrs = new ArrayList ();
54 bool isTemplateFile = false;
60 public static int Main (String[] args) {
61 return new AssemblyLinker ().DynMain (args);
64 private int DynMain (String[] args) {
72 static bool IsStrongNamed (Assembly assembly)
74 object[] attrs = assembly.GetCustomAttributes (true);
75 foreach (object o in attrs)
77 if (o is AssemblyKeyFileAttribute)
79 else if (o is AssemblyKeyNameAttribute)
85 private void ParseArgs (string[] args)
88 ArrayList flat_args = new ArrayList ();
90 // Process response files
91 Hashtable response_files = new Hashtable ();
92 foreach (string str in args) {
99 ReportMissingFileSpec ("@");
101 string resfile_name = Path.GetFullPath (str.Substring (1));
102 if (response_files.ContainsKey (resfile_name))
103 Report (1006, "Response file '" + resfile_name + "' was already included");
104 response_files [resfile_name] = resfile_name;
105 LoadArgs (resfile_name, flat_args);
108 if (flat_args.Count == 0)
111 foreach (string str in flat_args) {
112 if ((str [0] != '-') && (str [0] != '/')) {
113 string[] parts = str.Split (',');
114 ModuleInfo mod = new ModuleInfo ();
115 mod.fileName = parts [0];
116 if (parts.Length > 1)
117 mod.target = parts [1];
118 inputFiles.Add (mod);
123 string opt = GetCommand (str, out arg);
134 ReportMissingFileSpec (opt);
135 res = new ResourceInfo ();
136 res.isEmbedded = true;
137 String[] parts = arg.Split (',');
138 res.fileName = parts [0];
139 if (parts.Length > 1)
140 res.name = parts [1];
141 if (parts.Length > 2) {
146 res.isPrivate = true;
149 ReportInvalidArgument (opt, parts [2]);
159 ReportMissingFileSpec (opt);
160 res = new ResourceInfo ();
161 String[] parts = arg.Split (',');
162 res.fileName = parts [0];
163 if (parts.Length > 1)
164 res.name = parts [1];
165 if (parts.Length > 2)
166 res.target = parts [2];
167 if (parts.Length > 3) {
172 res.isPrivate = true;
175 ReportInvalidArgument (opt, parts [3]);
185 ReportMissingArgument (opt);
187 string realArg = arg;
188 if (realArg.StartsWith ("0x"))
189 realArg = realArg.Substring (2);
190 uint val = Convert.ToUInt32 (realArg, 16);
191 AddCattr (typeof (AssemblyAlgorithmIdAttribute), typeof (uint), val);
194 ReportInvalidArgument (opt, arg);
199 ReportNotImplemented (opt);
203 ReportNotImplemented (opt);
207 ReportNotImplemented (opt);
213 ReportMissingText (opt);
214 AddCattr (typeof (AssemblyCompanyAttribute), arg);
218 case "configuration":
220 ReportMissingText (opt);
221 AddCattr (typeof (AssemblyConfigurationAttribute), arg);
227 ReportMissingText (opt);
228 AddCattr (typeof (AssemblyCopyrightAttribute), arg);
234 ReportMissingText (opt);
235 AddCattr (typeof (AssemblyCultureAttribute), arg);
242 AddCattr (typeof (AssemblyDelaySignAttribute), typeof (bool), true);
254 ReportMissingText (opt);
255 AddCattr (typeof (AssemblyDescriptionAttribute), arg);
261 ReportMissingFileSpec (opt);
262 res = new ResourceInfo ();
263 res.name = "Security.Evidence";
265 res.isEmbedded = true;
266 res.isPrivate = true;
272 ReportMissingText (opt);
274 AddCattr (typeof (AssemblyFileVersionAttribute), arg);
279 ReportMissingArgument (opt);
281 string realArg = arg;
282 if (realArg.StartsWith ("0x"))
283 realArg = realArg.Substring (2);
284 uint val = Convert.ToUInt32 (realArg, 16);
285 AddCattr (typeof (AssemblyFlagsAttribute), typeof (uint), val);
288 ReportInvalidArgument (opt, arg);
299 ReportMissingText (opt);
300 AddCattr (typeof (AssemblyKeyFileAttribute), arg);
307 ReportMissingText (opt);
308 AddCattr (typeof (AssemblyKeyNameAttribute), arg);
314 ReportMissingText (opt);
323 ReportMissingFileSpec (opt);
330 ReportMissingText (opt);
331 AddCattr (typeof (AssemblyProductAttribute), arg);
335 case "productversion":
337 ReportMissingText (opt);
338 AddCattr (typeof (AssemblyInformationalVersionAttribute), arg);
344 ReportMissingText (opt);
355 Report (0, "target:win is not implemented");
358 ReportInvalidArgument (opt, arg);
365 ReportMissingFileSpec (opt);
366 isTemplateFile = true;
372 ReportMissingText (opt);
373 AddCattr (typeof (AssemblyTitleAttribute), arg);
379 ReportMissingText (opt);
380 AddCattr (typeof (AssemblyTrademarkAttribute), arg);
385 // This option conflicts with the standard UNIX meaning
390 AddCattr (typeof (AssemblyVersionAttribute), arg);
395 ReportMissingFileSpec (opt);
401 ReportMissingFileSpec (opt);
406 Report (1013, String.Format ("Unrecognized command line option: '{0}'", opt));
411 if ((inputFiles.Count == 0) && (resources.Count == 0))
412 Report (1016, "No valid input files were specified");
415 Report (1017, "No target filename was specified");
417 if (target == Target.Dll && (entryPoint != null))
418 Report (1035, "Libraries cannot have an entry point");
420 if (target == Target.Exe && (entryPoint == null))
421 Report (1036, "Entry point required for executable applications");
424 private string GetCommand (string str, out string command_arg) {
425 if ((str [0] == '-') && (str.Length > 1) && (str [1] == '-'))
426 str = str.Substring (1);
428 int end_index = str.IndexOfAny (new char[] {':', '='}, 1);
429 string command = str.Substring (1,
430 end_index == -1 ? str.Length - 1 : end_index - 1);
432 if (end_index != -1) {
433 command_arg = str.Substring (end_index+1);
434 if (command_arg == String.Empty)
440 return command.ToLower ();
443 private void AddCattr (Type attrType, Type arg, object value) {
444 cattrs.Add (new CustomAttributeBuilder (attrType.GetConstructor (new Type [] { arg }), new object [] { value }));
447 private void AddCattr (Type attrType, object value) {
448 AddCattr (attrType, typeof (string), value);
451 private void AddResource (ResourceInfo res) {
452 foreach (ResourceInfo res2 in resources) {
453 if (res.name == res2.name) {
460 private void PrintVersion () {
461 Console.WriteLine ("Mono Assembly Linker (al.exe) version " + Assembly.GetExecutingAssembly ().GetName ().Version.ToString ());
464 private void Version () {
466 Environment.Exit (0);
469 private void Usage () {
472 foreach (string s in usage)
473 Console.WriteLine (s);
474 Environment.Exit (0);
477 private void Report (int errorNum, string msg) {
478 Console.WriteLine (String.Format ("ALINK: error A{0:0000}: {1}", errorNum, msg));
479 Environment.Exit (1);
482 private void ReportWarning (int errorNum, string msg) {
483 Console.WriteLine (String.Format ("ALINK: warning A{0:0000}: {1}", errorNum, msg));
486 private void ReportInvalidArgument (string option, string value) {
487 Report (1012, String.Format ("'{0}' is not a valid setting for option '{1}'", value, option));
490 private void ReportMissingArgument (string option) {
491 Report (1003, String.Format ("Compiler option '{0}' must be followed by an argument", option));
494 private void ReportNotImplemented (string option) {
495 Report (0, String.Format ("Compiler option '{0}' is not implemented", option));
498 private void ReportMissingFileSpec (string option) {
499 Report (1008, String.Format ("Missing file specification for '{0}' command-line option", option));
502 private void ReportMissingText (string option) {
503 Report (1010, String.Format ("Missing ':<text>' for '{0}' option", option));
506 // copied from /mcs/mcs/codegen.cs
507 private void SetPublicKey (AssemblyName an, byte[] strongNameBlob)
509 // check for possible ECMA key
510 if (strongNameBlob.Length == 16) {
511 // will be rejected if not "the" ECMA key
512 an.SetPublicKey (strongNameBlob);
514 // take it, with or without, a private key
515 RSA rsa = CryptoConvert.FromCapiKeyBlob (strongNameBlob);
516 // and make sure we only feed the public part to Sys.Ref
517 byte[] publickey = CryptoConvert.ToCapiPublicKeyBlob (rsa);
519 // AssemblyName.SetPublicKey requires an additional header
520 byte[] publicKeyHeader = new byte [12] { 0x00, 0x24, 0x00, 0x00, 0x04, 0x80, 0x00, 0x00, 0x94, 0x00, 0x00, 0x00 };
522 byte[] encodedPublicKey = new byte [12 + publickey.Length];
523 Buffer.BlockCopy (publicKeyHeader, 0, encodedPublicKey, 0, 12);
524 Buffer.BlockCopy (publickey, 0, encodedPublicKey, 12, publickey.Length);
525 an.SetPublicKey (encodedPublicKey);
529 private void SetKeyPair (AssemblyName aname)
531 if (keyfile != null) {
532 if (!File.Exists (keyfile)) {
533 Report (1044, String.Format ("Couldn't open '{0}' key file.", keyfile));
536 using (FileStream fs = File.OpenRead (keyfile)) {
537 byte[] data = new byte [fs.Length];
539 fs.Read (data, 0, data.Length);
542 SetPublicKey (aname, data);
544 CryptoConvert.FromCapiPrivateKeyBlob (data);
545 aname.KeyPair = new StrongNameKeyPair (data);
548 catch (CryptographicException) {
550 if (data.Length == 16) {
551 // error # is different for ECMA key
552 Report (1019, "Could not strongname the assembly. " +
553 "ECMA key can only be used to delay-sign assemblies");
555 Report (1028, String.Format ("Key file {0}' is missing it's private key " +
556 "or couldn't be decoded.", keyfile));
559 Report (1044, String.Format ("Couldn't decode '{0}' key file.", keyfile));
564 } else if (keyname != null) {
565 // delay-sign doesn't apply to key containers
566 aname.KeyPair = new StrongNameKeyPair (keyname);
570 private void DoIt () {
571 AssemblyName aname = new AssemblyName ();
572 aname.Name = Path.GetFileNameWithoutExtension (outFile);
575 string fileName = Path.GetFileName (outFile);
586 AssemblyName myAssm = new AssemblyName();
587 myAssm.Name = Path.GetFileNameWithoutExtension (templateFile);
588 Assembly assembly = Assembly.Load(myAssm);
590 if (!IsStrongNamed(assembly)){
591 Report (1055, String.Format ("Assembly specified does not have Strong Name '{0}'","template"));
593 pk = assembly.GetName().GetPublicKey();
595 aname.SetPublicKey(pk);
596 aname.HashAlgorithm = assembly.GetName().HashAlgorithm;
597 aname.Version = assembly.GetName().Version;
598 aname.HashAlgorithm = assembly.GetName().HashAlgorithm;
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 ab.AddResourceFile (res.name, res.fileName,
710 res.isPrivate ? ResourceAttributes.Private : ResourceAttributes.Public);
717 catch (Exception ex) {
718 Report (1019, "Metadata failure creating assembly -- " + ex);
722 private void LoadArgs (string file, ArrayList args) {
723 StreamReader f = null;
726 f = new StreamReader (file);
728 StringBuilder sb = new StringBuilder ();
730 while ((line = f.ReadLine ()) != null){
733 for (int i = 0; i < t; i++){
736 if (c == '"' || c == '\''){
739 for (i++; i < t; i++){
746 } else if (c == ' '){
748 args.Add (sb.ToString ());
755 args.Add (sb.ToString ());
759 } catch (Exception ex) {
760 Report (1007, "Error opening response file '" + file + "' -- '" + ex.Message + "'");
768 "Usage: al [options] [sources]",
769 "Options: ('/out' must be specified)",
771 " /? or /help Display this usage message",
772 " @<filename> Read response file for more options",
773 " /algid:<id> Algorithm used to hash files (in hexadecimal)",
774 " /base[address]:<addr> Base address for the library",
775 " /bugreport:<filename> Create a 'Bug Report' file",
776 " /comp[any]:<text> Company name",
777 " /config[uration]:<text> Configuration string",
778 " /copy[right]:<text> Copyright message",
779 " /c[ulture]:<text> Supported culture",
780 " /delay[sign][+|-] Delay sign this assembly",
781 " /descr[iption]:<text> Description",
782 " /e[vidence]:<filename> Security evidence file to embed",
783 " /fileversion:<version> Optional Win32 version (overrides assembly version)",
784 " /flags:<flags> Assembly flags (in hexadecimal)",
785 " /fullpaths Display files using fully-qualified filenames",
786 " /keyf[ile]:<filename> File containing key to sign the assembly",
787 " /keyn[ame]:<text> Key container name of key to sign assembly",
788 " /main:<method> Specifies the method name of the entry point",
789 " /nologo Suppress the startup banner and copyright message",
790 " /out:<filename> Output file name for the assembly manifest",
791 " /prod[uct]:<text> Product name",
792 " /productv[ersion]:<text> Product version",
793 " /t[arget]:lib[rary] Create a library",
794 " /t[arget]:exe Create a console executable",
795 " /t[arget]:win[exe] Create a Windows executable",
796 " /template:<filename> Specifies an assembly to get default options from",
797 " /title:<text> Title",
798 " /trade[mark]:<text> Trademark message",
799 " /v[ersion]:<version> Version (use * to auto-generate remaining numbers)",
800 " /win32icon:<filename> Use this icon for the output",
801 " /win32res:<filename> Specifies the Win32 resource file",
803 "Sources: (at least one source input is required)",
804 " <filename>[,<targetfile>] add file to assembly",
805 " /embed[resource]:<filename>[,<name>[,Private]]",
806 " embed the file as a resource in the assembly",
807 " /link[resource]:<filename>[,<name>[,<targetfile>[,Private]]]",
808 " link the file as a resource to the assembly",