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
)
541 if (keyfile
!= null) {
542 if (!File
.Exists (keyfile
)) {
543 Report (1044, String
.Format ("Couldn't open '{0}' key file.", keyfile
));
546 using (FileStream fs
= File
.OpenRead (keyfile
)) {
547 byte[] data
= new byte [fs
.Length
];
549 fs
.Read (data
, 0, data
.Length
);
551 if (delaysign
== DelaySign
.Yes
) {
552 SetPublicKey (aname
, data
);
554 CryptoConvert
.FromCapiPrivateKeyBlob (data
);
555 aname
.KeyPair
= new StrongNameKeyPair (data
);
558 catch (CryptographicException
) {
559 if (delaysign
!= DelaySign
.Yes
) {
560 if (data
.Length
== 16) {
561 // error # is different for ECMA key
562 Report (1019, "Could not strongname the assembly. " +
563 "ECMA key can only be used to delay-sign assemblies");
565 Report (1028, String
.Format ("Key file {0}' is missing it's private key " +
566 "or couldn't be decoded.", keyfile
));
569 Report (1044, String
.Format ("Couldn't decode '{0}' key file.", keyfile
));
574 } else if (keyname
!= null) {
575 // delay-sign doesn't apply to key containers
576 aname
.KeyPair
= new StrongNameKeyPair (keyname
);
580 private void DoIt () {
581 AssemblyName aname
= new AssemblyName ();
582 aname
.Name
= Path
.GetFileNameWithoutExtension (outFile
);
584 aname
.CultureInfo
= new CultureInfo (culture
);
586 string fileName
= Path
.GetFileName (outFile
);
594 if (isTemplateFile
) {
595 // LAMESPEC: according to MSDN, the template assembly must have a
596 // strong name but this is not enforced
597 Assembly assembly
= Assembly
.LoadFrom (templateFile
);
599 // inherit signing related settings from template, but do not
600 // override command-line options
601 object [] attrs
= assembly
.GetCustomAttributes (true);
602 foreach (object o
in attrs
) {
603 if (o
is AssemblyKeyFileAttribute
) {
605 // ignore if specified on command line
607 AssemblyKeyFileAttribute keyFileAttr
= (AssemblyKeyFileAttribute
) o
;
608 // ignore null or zero-length keyfile
609 if (keyFileAttr
.KeyFile
== null || keyFileAttr
.KeyFile
.Length
== 0)
611 keyfile
= Path
.Combine (Path
.GetDirectoryName(templateFile
),
612 keyFileAttr
.KeyFile
);
613 } else if (o
is AssemblyDelaySignAttribute
) {
614 if (delaysign
!= DelaySign
.NotSet
)
615 // ignore if specified on command line
617 AssemblyDelaySignAttribute delaySignAttr
= (AssemblyDelaySignAttribute
) o
;
618 delaysign
= delaySignAttr
.DelaySign
? DelaySign
.Yes
:
620 } else if (o
is AssemblyKeyNameAttribute
) {
622 // ignore if specified on command line
624 AssemblyKeyNameAttribute keynameAttr
= (AssemblyKeyNameAttribute
) o
;
625 // ignore null or zero-length keyname
626 if (keynameAttr
.KeyName
== null || keynameAttr
.KeyName
.Length
== 0)
628 keyname
= keynameAttr
.KeyName
;
631 aname
.Version
= assembly
.GetName().Version
;
632 aname
.HashAlgorithm
= assembly
.GetName().HashAlgorithm
;
637 if (fileName
!= outFile
)
638 ab
= AppDomain
.CurrentDomain
.DefineDynamicAssembly (aname
, AssemblyBuilderAccess
.Save
, Path
.GetDirectoryName (outFile
));
640 ab
= AppDomain
.CurrentDomain
.DefineDynamicAssembly (aname
, AssemblyBuilderAccess
.Save
);
642 foreach (CustomAttributeBuilder cb
in cattrs
)
643 ab
.SetCustomAttribute (cb
);
649 foreach (ModuleInfo mod
in inputFiles
) {
650 MethodInfo mi
= typeof (AssemblyBuilder
).GetMethod ("AddModule", BindingFlags
.Instance
|BindingFlags
.Public
|BindingFlags
.NonPublic
);
652 Report (0, "Cannot add modules on this runtime: try the Mono runtime instead.");
654 if (mod
.target
!= null) {
655 File
.Copy (mod
.fileName
, mod
.target
, true);
656 mod
.fileName
= mod
.target
;
659 bool isAssembly
= false;
661 AssemblyName
.GetAssemblyName (mod
.fileName
);
668 ReportWarning (1020, "Ignoring included assembly '" + mod
.fileName
+ "'");
670 mi
.Invoke (ab
, new object [] { mod.fileName }
);
677 if (entryPoint
!= null) {
678 string mainClass
= entryPoint
.Substring (0, entryPoint
.LastIndexOf ('.'));
679 string mainMethod
= entryPoint
.Substring (entryPoint
.LastIndexOf ('.') + 1);
681 MethodInfo mainMethodInfo
= null;
684 Type mainType
= ab
.GetType (mainClass
);
685 if (mainType
!= null)
686 mainMethodInfo
= mainType
.GetMethod (mainMethod
);
688 catch (Exception ex
) {
689 Console
.WriteLine (ex
);
691 if (mainMethodInfo
!= null)
692 ab
.SetEntryPoint (mainMethodInfo
);
694 Report (1037, "Unable to find the entry point method '" + entryPoint
+ "'");
701 ab
.DefineVersionInfoResource ();
703 if (win32IconFile
!= null) {
705 MethodInfo mi
= typeof (AssemblyBuilder
).GetMethod ("DefineIconResource", BindingFlags
.Instance
|BindingFlags
.Public
|BindingFlags
.NonPublic
);
707 Report (0, "Cannot embed win32 icons on this runtime: try the Mono runtime instead.");
708 mi
.Invoke (ab
, new object [] { win32IconFile }
);
710 catch (Exception ex
) {
711 Report (1031, "Error reading icon '" + win32IconFile
+ "' --" + ex
);
715 if (win32ResFile
!= null) {
717 ab
.DefineUnmanagedResource (win32ResFile
);
719 catch (Exception ex
) {
720 Report (1019, "Metadata failure creating assembly -- " + ex
);
724 foreach (ResourceInfo res
in resources
) {
725 if (res
.name
== null)
726 res
.name
= Path
.GetFileName (res
.fileName
);
728 foreach (ResourceInfo res2
in resources
)
729 if ((res
!= res2
) && (res
.name
== res2
.name
))
730 Report (1046, String
.Format ("Resource identifier '{0}' has already been used in this assembly", res
.name
));
732 if (res
.isEmbedded
) {
733 MethodInfo mi
= typeof (AssemblyBuilder
).GetMethod ("EmbedResourceFile", BindingFlags
.Instance
|BindingFlags
.Public
|BindingFlags
.NonPublic
,
734 null, CallingConventions
.Any
, new Type
[] { typeof (string), typeof (string) }
, null);
736 Report (0, "Cannot embed resources on this runtime: try the Mono runtime instead.");
737 mi
.Invoke (ab
, new object [] { res.name, res.fileName }
);
740 if (res
.target
!= null) {
741 File
.Copy (res
.fileName
, res
.target
, true);
742 res
.fileName
= res
.target
;
745 // AddResourceFile must receive a file name and not a path.
746 // Drop directory and give warning if we have a path.
747 var resourceFileName
= Path
.GetFileName(res
.fileName
);
748 if (Path
.GetDirectoryName (res
.fileName
) != null || Path
.IsPathRooted(res
.fileName
)) {
749 ReportWarning (99999,
750 String
.Format ("Path '{0}' in the resource name is not supported. Using just file name '{1}'",
755 ab
.AddResourceFile (res
.name
, resourceFileName
,
756 res
.isPrivate
? ResourceAttributes
.Private
: ResourceAttributes
.Public
);
763 catch (Exception ex
) {
764 Report (1019, "Metadata failure creating assembly -- " + ex
);
768 private void LoadArgs (string file
, ArrayList args
) {
769 StreamReader f
= null;
772 f
= new StreamReader (file
);
774 StringBuilder sb
= new StringBuilder ();
776 while ((line
= f
.ReadLine ()) != null){
779 for (int i
= 0; i
< t
; i
++){
782 if (c
== '"' || c
== '\''){
785 for (i
++; i
< t
; i
++){
792 } else if (c
== ' '){
794 args
.Add (sb
.ToString ());
801 args
.Add (sb
.ToString ());
805 } catch (Exception ex
) {
806 Report (1007, "Error opening response file '" + file
+ "' -- '" + ex
.Message
+ "'");
814 "Usage: al [options] [sources]",
815 "Options: ('/out' must be specified)",
817 " /? or /help Display this usage message",
818 " @<filename> Read response file for more options",
819 " /algid:<id> Algorithm used to hash files (in hexadecimal)",
820 " /base[address]:<addr> Base address for the library",
821 " /bugreport:<filename> Create a 'Bug Report' file",
822 " /comp[any]:<text> Company name",
823 " /config[uration]:<text> Configuration string",
824 " /copy[right]:<text> Copyright message",
825 " /c[ulture]:<text> Supported culture",
826 " /delay[sign][+|-] Delay sign this assembly",
827 " /descr[iption]:<text> Description",
828 " /e[vidence]:<filename> Security evidence file to embed",
829 " /fileversion:<version> Optional Win32 version (overrides assembly version)",
830 " /flags:<flags> Assembly flags (in hexadecimal)",
831 " /fullpaths Display files using fully-qualified filenames",
832 " /keyf[ile]:<filename> File containing key to sign the assembly",
833 " /keyn[ame]:<text> Key container name of key to sign assembly",
834 " /main:<method> Specifies the method name of the entry point",
835 " /nologo Suppress the startup banner and copyright message",
836 " /out:<filename> Output file name for the assembly manifest",
837 " /prod[uct]:<text> Product name",
838 " /productv[ersion]:<text> Product version",
839 " /t[arget]:lib[rary] Create a library",
840 " /t[arget]:exe Create a console executable",
841 " /t[arget]:win[exe] Create a Windows executable",
842 " /template:<filename> Specifies an assembly to get default options from",
843 " /title:<text> Title",
844 " /trade[mark]:<text> Trademark message",
845 " /v[ersion]:<version> Version (use * to auto-generate remaining numbers)",
846 " /win32icon:<filename> Use this icon for the output",
847 " /win32res:<filename> Specifies the Win32 resource file",
849 "Sources: (at least one source input is required)",
850 " <filename>[,<targetfile>] add file to assembly",
851 " /embed[resource]:<filename>[,<name>[,Private]]",
852 " embed the file as a resource in the assembly",
853 " /link[resource]:<filename>[,<name>[,<targetfile>[,Private]]]",
854 " link the file as a resource to the assembly",