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
.Reflection
;
16 using System
.Reflection
.Emit
;
17 using System
.Security
.Cryptography
;
19 using System
.Configuration
.Assemblies
;
21 using Mono
.Security
.Cryptography
;
23 namespace Mono
.AssemblyLinker
26 public string fileName
;
32 public string fileName
;
34 public bool isEmbedded
;
35 public bool isPrivate
;
50 public class AssemblyLinker
{
52 ArrayList inputFiles
= new ArrayList ();
53 ArrayList resources
= new ArrayList ();
54 ArrayList cattrs
= new ArrayList ();
61 bool isTemplateFile
= false;
62 Target target
= Target
.Dll
;
63 DelaySign delaysign
= DelaySign
.NotSet
;
68 public static int Main (String
[] args
) {
69 return new AssemblyLinker ().DynMain (args
);
72 private int DynMain (String
[] args
) {
80 private void ParseArgs (string[] args
)
82 ArrayList flat_args
= new ArrayList ();
84 // Process response files
85 Hashtable response_files
= new Hashtable ();
86 foreach (string str
in args
) {
93 ReportMissingFileSpec ("@");
95 string resfile_name
= Path
.GetFullPath (str
.Substring (1));
96 if (response_files
.ContainsKey (resfile_name
))
97 Report (1006, "Response file '" + resfile_name
+ "' was already included");
98 response_files
[resfile_name
] = resfile_name
;
99 LoadArgs (resfile_name
, flat_args
);
102 if (flat_args
.Count
== 0)
105 foreach (string str
in flat_args
) {
106 if ((str
[0] != '-') && (str
[0] != '/')) {
107 inputFiles
.Add (GetModuleInfo (str
));
111 if (!ParseOption(str
)) {
113 // cope with absolute filenames for modules on unix, as
114 // they also match the option pattern
116 // `/home/test.cs' is considered as a module, however
117 // '/test.cs' is considered as error
118 if (str
.Length
> 2 && str
.IndexOf ('/', 2) != -1) {
119 inputFiles
.Add (GetModuleInfo (str
));
124 Report (1013, String
.Format ("Unrecognized command line option: '{0}'", str
));
129 if ((inputFiles
.Count
== 0) && (resources
.Count
== 0))
130 Report (1016, "No valid input files were specified");
133 Report (1017, "No target filename was specified");
135 if (target
== Target
.Dll
&& (entryPoint
!= null))
136 Report (1035, "Libraries cannot have an entry point");
138 if (target
== Target
.Exe
&& (entryPoint
== null))
139 Report (1036, "Entry point required for executable applications");
142 private bool ParseOption (string str
)
145 string opt
= GetCommand (str
, out arg
);
155 ReportMissingFileSpec (opt
);
156 ResourceInfo res
= new ResourceInfo ();
157 res
.isEmbedded
= true;
158 String
[] parts
= arg
.Split (',');
159 res
.fileName
= parts
[0];
160 if (parts
.Length
> 1)
161 res
.name
= parts
[1];
162 if (parts
.Length
> 2) {
167 res
.isPrivate
= true;
170 ReportInvalidArgument (opt
, parts
[2]);
180 ReportMissingFileSpec (opt
);
181 ResourceInfo res
= new ResourceInfo ();
182 String
[] parts
= arg
.Split (',');
183 res
.fileName
= parts
[0];
184 if (parts
.Length
> 1)
185 res
.name
= parts
[1];
186 if (parts
.Length
> 2)
187 res
.target
= parts
[2];
188 if (parts
.Length
> 3) {
193 res
.isPrivate
= true;
196 ReportInvalidArgument (opt
, parts
[3]);
206 ReportMissingArgument (opt
);
208 string realArg
= arg
;
209 if (realArg
.StartsWith ("0x"))
210 realArg
= realArg
.Substring (2);
211 uint val
= Convert
.ToUInt32 (realArg
, 16);
212 AddCattr (typeof (AssemblyAlgorithmIdAttribute
), typeof (uint), val
);
213 } catch (Exception
) {
214 ReportInvalidArgument (opt
, arg
);
219 ReportNotImplemented (opt
);
223 ReportNotImplemented (opt
);
227 ReportNotImplemented (opt
);
233 ReportMissingText (opt
);
234 AddCattr (typeof (AssemblyCompanyAttribute
), arg
);
238 case "configuration":
240 ReportMissingText (opt
);
241 AddCattr (typeof (AssemblyConfigurationAttribute
), arg
);
247 ReportMissingText (opt
);
248 AddCattr (typeof (AssemblyCopyrightAttribute
), arg
);
254 ReportMissingText (opt
);
262 delaysign
= DelaySign
.Yes
;
267 delaysign
= DelaySign
.No
;
273 ReportMissingText (opt
);
274 AddCattr (typeof (AssemblyDescriptionAttribute
), arg
);
280 ReportMissingFileSpec (opt
);
281 ResourceInfo res
= new ResourceInfo ();
282 res
.name
= "Security.Evidence";
284 res
.isEmbedded
= true;
285 res
.isPrivate
= true;
291 ReportMissingText (opt
);
293 AddCattr (typeof (AssemblyFileVersionAttribute
), arg
);
298 ReportMissingArgument (opt
);
300 string realArg
= arg
;
301 if (realArg
.StartsWith ("0x"))
302 realArg
= realArg
.Substring (2);
303 uint val
= Convert
.ToUInt32 (realArg
, 16);
304 AddCattr (typeof (AssemblyFlagsAttribute
), typeof (uint), val
);
305 } catch (Exception
) {
306 ReportInvalidArgument (opt
, arg
);
317 ReportMissingText (opt
);
324 ReportMissingText (opt
);
330 ReportMissingText (opt
);
339 ReportMissingFileSpec (opt
);
346 ReportMissingText (opt
);
347 AddCattr (typeof (AssemblyProductAttribute
), arg
);
351 case "productversion":
353 ReportMissingText (opt
);
354 AddCattr (typeof (AssemblyInformationalVersionAttribute
), arg
);
360 ReportMissingText (opt
);
371 Report (0, "target:win is not implemented");
374 ReportInvalidArgument (opt
, arg
);
381 ReportMissingFileSpec (opt
);
382 isTemplateFile
= true;
383 templateFile
= Path
.Combine (Directory
.GetCurrentDirectory (), arg
);
388 ReportMissingText (opt
);
389 AddCattr (typeof (AssemblyTitleAttribute
), arg
);
395 ReportMissingText (opt
);
396 AddCattr (typeof (AssemblyTrademarkAttribute
), arg
);
401 // This option conflicts with the standard UNIX meaning
406 AddCattr (typeof (AssemblyVersionAttribute
), arg
);
411 ReportMissingFileSpec (opt
);
417 ReportMissingFileSpec (opt
);
424 private bool RunningOnUnix
{
426 // check for Unix platforms - see FAQ for more details
427 // http://www.mono-project.com/FAQ:_Technical#How_to_detect_the_execution_platform_.3F
428 int platform
= (int) Environment
.OSVersion
.Platform
;
429 return ((platform
== 4) || (platform
== 128) || (platform
== 6));
433 private ModuleInfo
GetModuleInfo (string str
)
435 string [] parts
= str
.Split (',');
436 ModuleInfo mod
= new ModuleInfo ();
437 mod
.fileName
= parts
[0];
438 if (parts
.Length
> 1)
439 mod
.target
= parts
[1];
443 private string GetCommand (string str
, out string command_arg
) {
444 if ((str
[0] == '-') && (str
.Length
> 1) && (str
[1] == '-'))
445 str
= str
.Substring (1);
447 int end_index
= str
.IndexOfAny (new char[] {':', '='}
, 1);
448 string command
= str
.Substring (1,
449 end_index
== -1 ? str
.Length
- 1 : end_index
- 1);
451 if (end_index
!= -1) {
452 command_arg
= str
.Substring (end_index
+1);
453 if (command_arg
== String
.Empty
)
459 return command
.ToLower ();
462 private void AddCattr (Type attrType
, Type arg
, object value) {
463 cattrs
.Add (new CustomAttributeBuilder (attrType
.GetConstructor (new Type
[] { arg }
), new object [] { value }
));
466 private void AddCattr (Type attrType
, object value) {
467 AddCattr (attrType
, typeof (string), value);
470 private void PrintVersion () {
471 Console
.WriteLine ("Mono Assembly Linker (al.exe) version " + Consts
.MonoVersion
);
474 private void Version () {
476 Environment
.Exit (0);
479 private void Usage () {
482 foreach (string s
in usage
)
483 Console
.WriteLine (s
);
484 Environment
.Exit (0);
487 private void Report (int errorNum
, string msg
) {
488 Console
.WriteLine (String
.Format ("ALINK: error A{0:0000}: {1}", errorNum
, msg
));
489 Environment
.Exit (1);
492 private void ReportWarning (int errorNum
, string msg
) {
493 Console
.WriteLine (String
.Format ("ALINK: warning A{0:0000}: {1}", errorNum
, msg
));
496 private void ReportInvalidArgument (string option
, string value) {
497 Report (1012, String
.Format ("'{0}' is not a valid setting for option '{1}'", value, option
));
500 private void ReportMissingArgument (string option
) {
501 Report (1003, String
.Format ("Compiler option '{0}' must be followed by an argument", option
));
504 private void ReportNotImplemented (string option
) {
505 Report (0, String
.Format ("Compiler option '{0}' is not implemented", option
));
508 private void ReportMissingFileSpec (string option
) {
509 Report (1008, String
.Format ("Missing file specification for '{0}' command-line option", option
));
512 private void ReportMissingText (string option
) {
513 Report (1010, String
.Format ("Missing ':<text>' for '{0}' option", option
));
516 // copied from /mcs/mcs/codegen.cs
517 private void SetPublicKey (AssemblyName an
, byte[] strongNameBlob
)
519 // check for possible ECMA key
520 if (strongNameBlob
.Length
== 16) {
521 // will be rejected if not "the" ECMA key
522 an
.SetPublicKey (strongNameBlob
);
524 // take it, with or without, a private key
525 RSA rsa
= CryptoConvert
.FromCapiKeyBlob (strongNameBlob
);
526 // and make sure we only feed the public part to Sys.Ref
527 byte[] publickey
= CryptoConvert
.ToCapiPublicKeyBlob (rsa
);
529 // AssemblyName.SetPublicKey requires an additional header
530 byte[] publicKeyHeader
= new byte [12] { 0x00, 0x24, 0x00, 0x00, 0x04, 0x80, 0x00, 0x00, 0x94, 0x00, 0x00, 0x00 }
;
532 byte[] encodedPublicKey
= new byte [12 + publickey
.Length
];
533 Buffer
.BlockCopy (publicKeyHeader
, 0, encodedPublicKey
, 0, 12);
534 Buffer
.BlockCopy (publickey
, 0, encodedPublicKey
, 12, publickey
.Length
);
535 an
.SetPublicKey (encodedPublicKey
);
539 private void SetKeyPair (AssemblyName aname
)
544 AddCattr (typeof (AssemblyDelaySignAttribute
),
545 typeof (bool), true);
548 AddCattr (typeof (AssemblyDelaySignAttribute
),
549 typeof (bool), false);
554 if (keyfile
!= null) {
555 if (!File
.Exists (keyfile
)) {
556 Report (1044, String
.Format ("Couldn't open '{0}' key file.", keyfile
));
560 AddCattr (typeof (AssemblyKeyFileAttribute
),
564 using (FileStream fs
= File
.OpenRead (keyfile
)) {
565 byte[] data
= new byte [fs
.Length
];
567 fs
.Read (data
, 0, data
.Length
);
569 if (delaysign
== DelaySign
.Yes
) {
570 SetPublicKey (aname
, data
);
572 CryptoConvert
.FromCapiPrivateKeyBlob (data
);
573 aname
.KeyPair
= new StrongNameKeyPair (data
);
576 catch (CryptographicException
) {
577 if (delaysign
!= DelaySign
.Yes
) {
578 if (data
.Length
== 16) {
579 // error # is different for ECMA key
580 Report (1019, "Could not strongname the assembly. " +
581 "ECMA key can only be used to delay-sign assemblies");
583 Report (1028, String
.Format ("Key file {0}' is missing it's private key " +
584 "or couldn't be decoded.", keyfile
));
587 Report (1044, String
.Format ("Couldn't decode '{0}' key file.", keyfile
));
592 } else if (keyname
!= null) {
594 AddCattr (typeof (AssemblyKeyNameAttribute
),
597 // delay-sign doesn't apply to key containers
598 aname
.KeyPair
= new StrongNameKeyPair (keyname
);
602 private void DoIt () {
603 AssemblyName aname
= new AssemblyName ();
604 aname
.Name
= Path
.GetFileNameWithoutExtension (outFile
);
606 aname
.CultureInfo
= new CultureInfo (culture
);
608 string fileName
= Path
.GetFileName (outFile
);
616 if (isTemplateFile
) {
617 // LAMESPEC: according to MSDN, the template assembly must have a
618 // strong name but this is not enforced
619 Assembly assembly
= Assembly
.LoadFrom (templateFile
);
621 // inherit signing related settings from template, but do not
622 // override command-line options
623 object [] attrs
= assembly
.GetCustomAttributes (true);
624 foreach (object o
in attrs
) {
625 if (o
is AssemblyKeyFileAttribute
) {
627 // ignore if specified on command line
629 AssemblyKeyFileAttribute keyFileAttr
= (AssemblyKeyFileAttribute
) o
;
630 // ignore null or zero-length keyfile
631 if (keyFileAttr
.KeyFile
== null || keyFileAttr
.KeyFile
.Length
== 0)
633 keyfile
= Path
.Combine (Path
.GetDirectoryName(templateFile
),
634 keyFileAttr
.KeyFile
);
635 } else if (o
is AssemblyDelaySignAttribute
) {
636 if (delaysign
!= DelaySign
.NotSet
)
637 // ignore if specified on command line
639 AssemblyDelaySignAttribute delaySignAttr
= (AssemblyDelaySignAttribute
) o
;
640 delaysign
= delaySignAttr
.DelaySign
? DelaySign
.Yes
:
642 } else if (o
is AssemblyKeyNameAttribute
) {
644 // ignore if specified on command line
646 AssemblyKeyNameAttribute keynameAttr
= (AssemblyKeyNameAttribute
) o
;
647 // ignore null or zero-length keyname
648 if (keynameAttr
.KeyName
== null || keynameAttr
.KeyName
.Length
== 0)
650 keyname
= keynameAttr
.KeyName
;
653 aname
.Version
= assembly
.GetName().Version
;
654 aname
.HashAlgorithm
= assembly
.GetName().HashAlgorithm
;
659 if (fileName
!= outFile
)
660 ab
= AppDomain
.CurrentDomain
.DefineDynamicAssembly (aname
, AssemblyBuilderAccess
.Save
, Path
.GetDirectoryName (outFile
));
662 ab
= AppDomain
.CurrentDomain
.DefineDynamicAssembly (aname
, AssemblyBuilderAccess
.Save
);
664 foreach (CustomAttributeBuilder cb
in cattrs
)
665 ab
.SetCustomAttribute (cb
);
671 foreach (ModuleInfo mod
in inputFiles
) {
672 MethodInfo mi
= typeof (AssemblyBuilder
).GetMethod ("AddModule", BindingFlags
.Instance
|BindingFlags
.Public
|BindingFlags
.NonPublic
);
674 Report (0, "Cannot add modules on this runtime: try the Mono runtime instead.");
676 if (mod
.target
!= null) {
677 File
.Copy (mod
.fileName
, mod
.target
, true);
678 mod
.fileName
= mod
.target
;
681 bool isAssembly
= false;
683 AssemblyName
.GetAssemblyName (mod
.fileName
);
690 ReportWarning (1020, "Ignoring included assembly '" + mod
.fileName
+ "'");
692 mi
.Invoke (ab
, new object [] { mod.fileName }
);
699 if (entryPoint
!= null) {
700 string mainClass
= entryPoint
.Substring (0, entryPoint
.LastIndexOf ('.'));
701 string mainMethod
= entryPoint
.Substring (entryPoint
.LastIndexOf ('.') + 1);
703 MethodInfo mainMethodInfo
= null;
706 Type mainType
= ab
.GetType (mainClass
);
707 if (mainType
!= null)
708 mainMethodInfo
= mainType
.GetMethod (mainMethod
);
710 catch (Exception ex
) {
711 Console
.WriteLine (ex
);
713 if (mainMethodInfo
!= null)
714 ab
.SetEntryPoint (mainMethodInfo
);
716 Report (1037, "Unable to find the entry point method '" + entryPoint
+ "'");
723 ab
.DefineVersionInfoResource ();
725 if (win32IconFile
!= null) {
727 MethodInfo mi
= typeof (AssemblyBuilder
).GetMethod ("DefineIconResource", BindingFlags
.Instance
|BindingFlags
.Public
|BindingFlags
.NonPublic
);
729 Report (0, "Cannot embed win32 icons on this runtime: try the Mono runtime instead.");
730 mi
.Invoke (ab
, new object [] { win32IconFile }
);
732 catch (Exception ex
) {
733 Report (1031, "Error reading icon '" + win32IconFile
+ "' --" + ex
);
737 if (win32ResFile
!= null) {
739 ab
.DefineUnmanagedResource (win32ResFile
);
741 catch (Exception ex
) {
742 Report (1019, "Metadata failure creating assembly -- " + ex
);
746 foreach (ResourceInfo res
in resources
) {
747 if (res
.name
== null)
748 res
.name
= Path
.GetFileName (res
.fileName
);
750 foreach (ResourceInfo res2
in resources
)
751 if ((res
!= res2
) && (res
.name
== res2
.name
))
752 Report (1046, String
.Format ("Resource identifier '{0}' has already been used in this assembly", res
.name
));
754 if (res
.isEmbedded
) {
755 MethodInfo mi
= typeof (AssemblyBuilder
).GetMethod ("EmbedResourceFile", BindingFlags
.Instance
|BindingFlags
.Public
|BindingFlags
.NonPublic
,
756 null, CallingConventions
.Any
, new Type
[] { typeof (string), typeof (string) }
, null);
758 Report (0, "Cannot embed resources on this runtime: try the Mono runtime instead.");
759 mi
.Invoke (ab
, new object [] { res.name, res.fileName }
);
762 if (res
.target
!= null) {
763 File
.Copy (res
.fileName
, res
.target
, true);
764 res
.fileName
= res
.target
;
767 ab
.AddResourceFile (res
.name
, res
.fileName
,
768 res
.isPrivate
? ResourceAttributes
.Private
: ResourceAttributes
.Public
);
775 catch (Exception ex
) {
776 Report (1019, "Metadata failure creating assembly -- " + ex
);
780 private void LoadArgs (string file
, ArrayList args
) {
781 StreamReader f
= null;
784 f
= new StreamReader (file
);
786 StringBuilder sb
= new StringBuilder ();
788 while ((line
= f
.ReadLine ()) != null){
791 for (int i
= 0; i
< t
; i
++){
794 if (c
== '"' || c
== '\''){
797 for (i
++; i
< t
; i
++){
804 } else if (c
== ' '){
806 args
.Add (sb
.ToString ());
813 args
.Add (sb
.ToString ());
817 } catch (Exception ex
) {
818 Report (1007, "Error opening response file '" + file
+ "' -- '" + ex
.Message
+ "'");
826 "Usage: al [options] [sources]",
827 "Options: ('/out' must be specified)",
829 " /? or /help Display this usage message",
830 " @<filename> Read response file for more options",
831 " /algid:<id> Algorithm used to hash files (in hexadecimal)",
832 " /base[address]:<addr> Base address for the library",
833 " /bugreport:<filename> Create a 'Bug Report' file",
834 " /comp[any]:<text> Company name",
835 " /config[uration]:<text> Configuration string",
836 " /copy[right]:<text> Copyright message",
837 " /c[ulture]:<text> Supported culture",
838 " /delay[sign][+|-] Delay sign this assembly",
839 " /descr[iption]:<text> Description",
840 " /e[vidence]:<filename> Security evidence file to embed",
841 " /fileversion:<version> Optional Win32 version (overrides assembly version)",
842 " /flags:<flags> Assembly flags (in hexadecimal)",
843 " /fullpaths Display files using fully-qualified filenames",
844 " /keyf[ile]:<filename> File containing key to sign the assembly",
845 " /keyn[ame]:<text> Key container name of key to sign assembly",
846 " /main:<method> Specifies the method name of the entry point",
847 " /nologo Suppress the startup banner and copyright message",
848 " /out:<filename> Output file name for the assembly manifest",
849 " /prod[uct]:<text> Product name",
850 " /productv[ersion]:<text> Product version",
851 " /t[arget]:lib[rary] Create a library",
852 " /t[arget]:exe Create a console executable",
853 " /t[arget]:win[exe] Create a Windows executable",
854 " /template:<filename> Specifies an assembly to get default options from",
855 " /title:<text> Title",
856 " /trade[mark]:<text> Trademark message",
857 " /v[ersion]:<version> Version (use * to auto-generate remaining numbers)",
858 " /win32icon:<filename> Use this icon for the output",
859 " /win32res:<filename> Specifies the Win32 resource file",
861 "Sources: (at least one source input is required)",
862 " <filename>[,<targetfile>] add file to assembly",
863 " /embed[resource]:<filename>[,<name>[,Private]]",
864 " embed the file as a resource in the assembly",
865 " /link[resource]:<filename>[,<name>[,<targetfile>[,Private]]]",
866 " link the file as a resource to the assembly",