In tools/corcompare:
[mono.git] / mcs / tools / gacutil / driver.cs
1 //
2 // Mono.Tools.GacUtil
3 //
4 // Author(s):
5 //  Todd Berman <tberman@sevenl.net>
6 //  Jackson Harper <jackson@ximian.com>
7 //
8 // Copyright 2003, 2004 Todd Berman 
9 // Copyright 2004 Novell, Inc (http://www.novell.com)
10 //
11
12
13 using System;
14 using System.IO;
15 using System.Text;
16 using System.Reflection;
17 using System.Collections;
18 using System.Globalization;
19 using System.Runtime.InteropServices;
20
21 using Mono.Security;
22
23 namespace Mono.Tools {
24
25         public class Driver {
26
27                 private enum Command {
28                         Unknown,
29                         Install,
30                         InstallFromList,
31                         Uninstall,
32                         UninstallFromList,
33                         UninstallSpecific,
34                         List,
35                         Help
36                 }
37
38                 private static bool silent;
39
40                 public static int Main (string [] args)
41                 {
42                         if (args.Length == 0)
43                                 Usage ();
44
45                         Command command = Command.Unknown;
46                         string command_str = null;
47
48                         string libdir;
49                         string name, package, gacdir, root;
50                         name = package = root = gacdir = null;
51                         bool check_refs = false;
52
53                         // Check for silent arg first so we can supress
54                         // warnings during command line parsing
55                         if (Array.IndexOf (args, "/silent") > -1 || Array.IndexOf (args, "-silent") > -1)
56                                 silent = true;
57
58                         for (int i=0; i<args.Length; i++) {
59                                 if (IsSwitch (args [i])) {
60
61                                         // for cmd line compatibility with other gacutils
62                                         // we always force it though
63                                         if (args [i] == "-f" || args [i] == "/f")
64                                                 continue;
65
66                                         // Ignore this option for now, although we might implement it someday
67                                         if (args [i] == "/r") {
68                                                 WriteLine ("WARNING: gacutil does not support traced references." +
69                                                         "This option is being ignored.");
70                                                 i += 3;
71                                                 continue;
72                                         }
73
74                                         // This is already handled we just dont want to choke on it
75                                         if (args [i] == "-silent" || args [i] == "/silent")
76                                                 continue; 
77
78                                         if (args [i] == "-check_refs" || args [i] == "/check_refs") {
79                                                 check_refs = true;
80                                                 continue;
81                                         }
82
83                                         if (command == Command.Unknown) {
84                                                 command = GetCommand (args [i]);
85                                                 if (command != Command.Unknown) {
86                                                         command_str = args [i];
87                                                         continue;
88                                                 }
89                                         }
90
91                                         if (i + 1 >= args.Length) {
92                                                 Console.WriteLine ("Option " + args [i] + " takes 1 argument");
93                                                 return 1;
94                                         }
95
96                                         switch (args [i]) {
97                                         case "-package":
98                                         case "/package":
99                                                 package = args [++i];
100                                                 break;
101                                         case "-root":
102                                         case "/root":
103                                                 root = args [++i];
104                                                 break;
105                                         case "-gacdir":
106                                         case "/gacdir":
107                                                 gacdir = args [++i];
108                                                 break;
109                                         }
110                                         continue;
111                                 }
112                                 if (name == null)
113                                         name = args [i];
114                                 else
115                                         name += args [i];
116                         }
117
118                         if (command == Command.Unknown && IsSwitch (args [0])) {
119                                 Console.WriteLine ("Unknown command: " + args [0]);
120                                 return 1;
121                         } else if (command == Command.Unknown) {
122                                 Usage ();
123                         } else if (command == Command.Help) {
124                                 ShowHelp (true);
125                                 return 1;
126                         }
127
128                         if (gacdir == null) {
129                                 gacdir = GetGacDir ();
130                                 libdir = GetLibDir ();
131                         } else {
132                                 gacdir = EnsureLib (gacdir);
133                                 libdir = Path.Combine (gacdir, "mono");
134                                 gacdir = Path.Combine (libdir, "gac");
135                         }
136
137                         string link_gacdir = gacdir;
138                         string link_libdir = libdir;
139                         if (root != null) {
140                                 libdir = Path.Combine (root, "mono");
141                                 gacdir = Path.Combine (libdir, "gac");
142                         }
143
144                         switch (command) {
145                         case Command.Install:
146                                 if (name == null) {
147                                         WriteLine ("Option " + command_str + " takes 1 argument");
148                                         return 1;
149                                 }
150                                 if (!Install (check_refs, name, package, gacdir, link_gacdir, libdir, link_libdir))
151                                         return 1;
152                                 break;
153                         case Command.InstallFromList:
154                                 if (name == null) {
155                                         WriteLine ("Option " + command_str + " takes 1 argument");
156                                         return 1;
157                                 }
158                                 if (!InstallFromList (check_refs, name, package, gacdir, link_gacdir, libdir, link_libdir))
159                                         return 1;
160                                 break;
161                         case Command.Uninstall:
162                                 if (name == null) {
163                                         WriteLine ("Option " + command_str + " takes 1 argument");
164                                         return 1;
165                                 }
166                                 if (!Uninstall (name, package, gacdir, libdir))
167                                         Environment.Exit (1);
168                                 break;
169                         case Command.UninstallFromList:
170                                 if (name == null) {
171                                         WriteLine ("Option " + command_str + " takes 1 argument");
172                                         return 1;
173                                 }
174                                 if (!UninstallFromList (name, package, gacdir, libdir))
175                                         return 1;
176                                 break;
177                         case Command.UninstallSpecific:
178                                 if (name == null) {
179                                         WriteLine ("Opetion " + command_str + " takes 1 argument");
180                                         return 1;
181                                 }
182                                 if (!UninstallSpecific (name, package, gacdir, libdir))
183                                         return 1;
184                                 break;
185                         case Command.List:
186                                 List (name, gacdir);
187                                 break;
188                         }
189
190                         return 0;
191                 }
192
193                 private static bool Install (bool check_refs, string name, string package,
194                                 string gacdir, string link_gacdir, string libdir, string link_libdir)
195                 {
196                         string failure_msg = "Failure adding assembly to the cache: ";
197
198                         if (!File.Exists (name)) {
199                                 WriteLine (failure_msg + "The system cannot find the file specified.");
200                                 Environment.Exit (1);
201                         }
202
203                         AssemblyName an = null;
204                         byte [] pub_tok;
205
206                         try {
207                                 an = AssemblyName.GetAssemblyName (name);
208                         } catch {
209                                 WriteLine (failure_msg + "The file specified is not a valid assembly.");
210                                 return false;
211                         }
212
213                         pub_tok = an.GetPublicKeyToken ();
214                         if (pub_tok == null || pub_tok.Length == 0) {
215                                 WriteLine (failure_msg + "Attempt to install an assembly without a strong name.");
216                                 return false;
217                         }
218
219                         if (check_refs && !CheckReferencedAssemblies (an)) {
220                                 WriteLine (failure_msg + "Attempt to install an assembly that references non " +
221                                                 "strong named assemblies with -check_refs enabled.");
222                                 return false;
223                         }
224
225                         string [] siblings = { ".config", ".mdb" };
226                         string version_token = an.Version + "_" +
227                                                an.CultureInfo.Name.ToLower (CultureInfo.InvariantCulture) + "_" +
228                                                GetStringToken (pub_tok);
229                         string full_path = Path.Combine (Path.Combine (gacdir, an.Name), version_token);
230                         string asmb_file = Path.GetFileName (name);
231                         string asmb_path = Path.Combine (full_path, asmb_file);
232
233                         try {
234                                 if (Directory.Exists (full_path)) {
235                                         // Wipe out the directory. This way we ensure old assemblies    
236                                         // config files, and AOTd files are removed.
237                                         Directory.Delete (full_path, true);
238                                 }
239                                 Directory.CreateDirectory (full_path);
240                         } catch {
241                                 WriteLine (failure_msg + "gac directories could not be created, " +
242                                                 "possibly permission issues.");
243                                 return false;
244                         }
245
246                         File.Copy (name, asmb_path, true);
247
248                         foreach (string ext in siblings) {
249                                 string sibling = String.Concat (name, ext);
250                                 if (File.Exists (sibling))
251                                         File.Copy (sibling, String.Concat (asmb_path, ext), true);
252                         }
253
254                         if (package != null) {
255                                 string link_path = Path.Combine (Path.Combine (link_gacdir, an.Name), version_token);
256                                 string ref_dir = Path.Combine (libdir, package);
257                                 string ref_path = Path.Combine (ref_dir, asmb_file);
258
259                                 if (File.Exists (ref_path))
260                                         File.Delete (ref_path);
261                                 try {
262                                         Directory.CreateDirectory (ref_dir);
263                                 } catch {
264                                         WriteLine ("ERROR: Could not create package dir file.");
265                                         Environment.Exit (1);
266                                 }
267                                 Symlink (Path.Combine (link_path, asmb_file), ref_path);
268
269                                 WriteLine ("Package exported to: " + ref_path + " -> " +
270                                                 Path.Combine (link_path, asmb_file));
271
272                         }
273
274                         WriteLine ("{0} installed into the gac ({1})", an.Name, gacdir);
275                         return true;
276                 }
277
278                 private static bool Uninstall (string name, string package, string gacdir, string libdir)
279                 {
280                         string [] assembly_pieces = name.Split (new char[] { ',' });
281                         Hashtable asm_info = new Hashtable ();
282
283                         foreach (string item in assembly_pieces) {
284                                 string[] pieces = item.Trim ().Split (new char[] { '=' }, 2);
285                                 if(pieces.Length == 1)
286                                         asm_info ["assembly"] = pieces [0];
287                                 else
288                                         asm_info [pieces[0].Trim ().ToLower (CultureInfo.InvariantCulture)] = pieces [1];
289                         }
290
291                         string asmdir = Path.Combine (gacdir, (string) asm_info ["assembly"]);
292                         if (!Directory.Exists (asmdir)) {
293                                 WriteLine ("No assemblies found that match: " + name);
294                                 return false;
295                         }
296
297                         string searchString = GetSearchString (asm_info);
298                         string [] directories = Directory.GetDirectories (asmdir, searchString);
299
300                         foreach (string dir in directories) {
301                                 Directory.Delete (dir, true);
302                                 if (package != null) {
303                                         string link_dir = Path.Combine (libdir, package);
304                                         string link = Path.Combine (link_dir, (string) asm_info ["assembly"] + ".dll");
305                                         File.Delete (link);
306                                         if (Directory.GetFiles (link_dir).Length == 0) {
307                                                 WriteLine ("Cleaning package directory, it is empty.");
308                                                 Directory.Delete (link_dir);
309                                         }
310                                 }
311                                 WriteLine ("Assembly removed from the gac.");
312                         }
313
314                         if(Directory.GetDirectories (asmdir).Length == 0) {
315                                 WriteLine ("Cleaning assembly dir, its empty");
316                                 Directory.Delete (asmdir);
317                         }
318
319                         return true;
320                 }
321
322                 private static bool UninstallSpecific (string name, string package,
323                                 string gacdir, string libdir)
324                 {
325                         string failure_msg = "Failure to remove assembly from the cache: ";
326
327                         if (!File.Exists (name)) {
328                                 WriteLine (failure_msg + "The system cannot find the file specified.");
329                                 return false;
330                         }
331
332                         AssemblyName an = null;
333
334                         try {
335                                 an = AssemblyName.GetAssemblyName (name);
336                         } catch {
337                                 WriteLine (failure_msg + "The file specified is not a valid assembly.");
338                                 return false;
339                         }
340
341                         return Uninstall (an.FullName.Replace (" ", String.Empty),
342                                         package, gacdir, libdir);
343                 }
344
345                 private static void List (string name, string gacdir)
346                 {
347                         WriteLine ("The following assemblies are installed into the GAC:");
348
349                         if (name != null) {
350                                 FilteredList (name, gacdir);
351                                 return;
352                         }
353
354                         int count = 0;
355                         DirectoryInfo gacinfo = new DirectoryInfo (gacdir);
356                         foreach (DirectoryInfo parent in gacinfo.GetDirectories ()) {
357                                 foreach (DirectoryInfo dir in parent.GetDirectories ()) {
358                                         WriteLine (AsmbNameFromVersionString (parent.Name, dir.Name));
359                                         count++;
360                                 }
361                         }
362                         WriteLine ("Number of items = " + count);
363                 }
364
365                 private static void FilteredList (string name, string gacdir)
366                 {
367                         string [] assembly_pieces = name.Split (new char[] { ',' });
368                         Hashtable asm_info = new Hashtable ();
369
370                         foreach (string item in assembly_pieces) {
371                                 string[] pieces = item.Trim ().Split (new char[] { '=' }, 2);
372                                 if(pieces.Length == 1)
373                                         asm_info ["assembly"] = pieces [0];
374                                 else
375                                         asm_info [pieces[0].Trim ().ToLower (CultureInfo.InvariantCulture)] = pieces [1];
376                         }
377                         
378                         string asmdir = Path.Combine (gacdir, (string) asm_info ["assembly"]);
379                         if (!Directory.Exists (asmdir)) {
380                                 WriteLine ("Number of items = 0");
381                                 return;
382                         }
383                         string search = GetSearchString (asm_info);
384                         string [] dir_list = Directory.GetDirectories (asmdir, search);
385
386                         int count = 0;
387                         foreach (string dir in dir_list) {
388                                 WriteLine (AsmbNameFromVersionString ((string) asm_info ["assembly"],
389                                                 new DirectoryInfo (dir).Name));
390                                 count++;
391                         }
392                         WriteLine ("Number of items = " + count);
393                 }
394
395                 private static bool InstallFromList (bool check_refs, string list_file, string package,
396                                 string gacdir, string link_gacdir, string libdir, string link_libdir)
397                 {
398                         StreamReader s = null;
399                         int processed, failed;
400
401                         processed = failed = 0;
402
403                         try {
404                                 s = new StreamReader (list_file);
405
406                                 string line;
407                                 while ((line = s.ReadLine ()) != null) {
408                                         if (!Install (check_refs, line, package, gacdir, link_gacdir,
409                                                             libdir, link_libdir))
410                                                 failed++;
411                                         processed++;
412                                                         
413                                 }
414                         } catch (IOException ioe) {
415                                 WriteLine ("Failed to open assemblies list file " + list_file + ".");
416                                 return false;
417                         } finally {
418                                 if (s != null)
419                                         s.Close ();
420                         }
421
422                         return true;
423                 }
424
425                 private static bool UninstallFromList (string list_file, string package,
426                                 string gacdir, string libdir)
427                 {
428                         StreamReader s = null;
429                         int processed, failed;
430
431                         processed = failed = 0;
432
433                         try {
434                                 s = new StreamReader (list_file);
435
436                                 string line;
437                                 while ((line = s.ReadLine ()) != null) {
438                                         if (!Uninstall (line, package, gacdir, libdir))
439                                                 failed++;
440                                         processed++;
441                                 }
442                         } catch (IOException ioe) {
443                                 WriteLine ("Failed to open assemblies list file " + list_file + ".");
444                                 return false;
445                         } finally {
446                                 if (s != null)
447                                         s.Close ();
448                         }
449
450                         return true;
451                 }
452
453                 private static bool CheckReferencedAssemblies (AssemblyName an)
454                 {
455                         AppDomain d = null;
456                         try {
457                                 Assembly a = Assembly.LoadFrom (an.CodeBase);
458                                 AssemblyName corlib = typeof (object).Assembly.GetName ();
459
460                                 foreach (AssemblyName ref_an in a.GetReferencedAssemblies ()) {
461                                         if (ref_an.Name == corlib.Name) // Just do a string compare so we can install on diff versions
462                                                 continue;
463                                         byte [] pt = ref_an.GetPublicKeyToken ();
464                                         if (pt == null || pt.Length == 0) {
465                                                 WriteLine ("Assembly " + ref_an.Name + " is not strong named.");
466                                                 return false;
467                                         }
468                                 }
469                         } catch  (Exception e) {
470                                 WriteLine (e.ToString ()); // This should be removed pre beta3
471                                 return false;
472                         } finally {
473                                 if (d != null) {
474                                         try {
475                                                 AppDomain.Unload (d);
476                                         } catch { }
477                                 }
478                         }
479
480                         return true;
481                 }
482
483                 private static string GetSearchString (Hashtable asm_info)
484                 {
485                         if (asm_info.Keys.Count == 1)
486                                 return "*";
487                         string version, culture, token;
488
489                         version = asm_info ["version"] as string;
490                         version = (version == null ? "*" : version);
491                         culture = asm_info ["culture"] as string;
492                         culture = (culture == null ? "*" : culture.ToLower (CultureInfo.InvariantCulture));
493                         token = asm_info ["publickeytoken"] as string;
494                         token = (token == null ? "*" : token.ToLower (CultureInfo.InvariantCulture));
495                         
496                         return String.Format ("{0}_{1}_{2}", version, culture, token);
497                 }
498
499                 private static string AsmbNameFromVersionString (string name, string str)
500                 {
501                         string [] pieces = str.Split ('_');
502                         return String.Format ("{0}, Version={1}, Culture={2}, PublicKeyToken={3}",
503                                         name, pieces [0], (pieces [1] == String.Empty ? "neutral" : pieces [1]),
504                                         pieces [2]);
505                 }
506
507                 private static bool IsSwitch (string arg)
508                 {
509                         return (arg [0] == '-' || arg [0] == '/');
510                 }
511
512                 private static Command GetCommand (string arg)
513                 {
514                         Command c = Command.Unknown;
515
516                         switch (arg) {
517                         case "-i":
518                         case "/i":
519                         case "--install":
520                                 c = Command.Install;
521                                 break;
522                         case "-il":
523                         case "/il":
524                         case "--install-from-list":
525                                 c = Command.InstallFromList;
526                                 break;
527                         case "-u":
528                         case "/u":
529                         case "/uf":
530                         case "--uninstall":
531                                 c = Command.Uninstall;
532                                 break;
533                         case "-ul":
534                         case "/ul":
535                         case "--uninstall-from-list":
536                                 c = Command.UninstallFromList;
537                                 break;
538                         case "-us":
539                         case "/us":
540                         case "--uninstall-specific":
541                                 c = Command.UninstallSpecific;
542                                 break;
543                         case "-l":
544                         case "/l":
545                         case "--list":
546                                 c = Command.List;
547                                 break;
548                         case "-?":
549                         case "/?":
550                         case "--help":
551                                 c = Command.Help;
552                                 break;
553                         }
554                         return c;        
555                 }
556
557                 private static void Symlink (string oldpath, string newpath) {
558                         if (Path.DirectorySeparatorChar == '/') {
559                                 symlink (oldpath, newpath);
560                         } else {
561                                 File.Copy (oldpath, newpath);
562                         }
563                 }
564
565                 [DllImport ("libc", SetLastError=true)]
566                 public static extern int symlink (string oldpath, string newpath);
567
568                 private static string GetGacDir () {
569                         PropertyInfo gac = typeof (System.Environment).GetProperty ("GacPath",
570                                         BindingFlags.Static|BindingFlags.NonPublic);
571                         if (gac == null) {
572                                 WriteLine ("ERROR: Mono runtime not detected, please use " +
573                                                 "the mono runtime for gacutil.exe");
574                                 Environment.Exit (1);
575                         }
576                         MethodInfo get_gac = gac.GetGetMethod (true);
577                         return (string) get_gac.Invoke (null, null);
578                 }
579
580                 private static string GetLibDir () {
581                         MethodInfo libdir = typeof (System.Environment).GetMethod ("internalGetGacPath",
582                                         BindingFlags.Static|BindingFlags.NonPublic);
583                         if (libdir == null) {
584                                 WriteLine ("ERROR: Mono runtime not detected, please use " +
585                                                 "the mono runtime for gacutil.exe");
586                                 Environment.Exit (1);
587                         }
588                         return Path.Combine ((string)libdir.Invoke (null, null), "mono");
589                 }
590
591                 private static string GetStringToken (byte[] tok)
592                 {
593                         StringBuilder sb = new StringBuilder ();
594                         for (int i = 0; i < tok.Length ; i++)
595                                 sb.Append (tok[i].ToString ("x2"));
596                         return sb.ToString ();
597                 }
598
599                 private static string CombinePaths (string a, string b)
600                 {
601                         string dsc = Path.DirectorySeparatorChar.ToString ();
602                         string sep = (a.EndsWith (dsc) ? String.Empty : dsc);
603                         string end = (b.StartsWith (dsc) ? b.Substring (1) : b);
604                         return String.Concat (a, sep, end);
605                 }
606
607                 private static string EnsureLib (string dir)
608                 {
609                         DirectoryInfo d = new DirectoryInfo (dir);
610                         if (d.Name == "lib")
611                                 return dir;
612                         return Path.Combine (dir, "lib");
613                 }
614
615                 private static void WriteLine ()
616                 {
617                         if (silent)
618                                 return;
619                         Console.WriteLine ();
620                 }
621
622                 private static void WriteLine (string line)
623                 {
624                         if (silent)
625                                 return;
626                         Console.WriteLine (line);
627                 }
628
629                 private static void WriteLine (string line, params object [] p)
630                 {
631                         if (silent)
632                                 return; 
633                         Console.WriteLine (line, p);
634                 }
635
636                 private static void Usage ()
637                 {
638                         ShowHelp (false);
639                         Environment.Exit (1);
640                 }
641
642                 private static void ShowHelp (bool detailed)
643                 {
644                         WriteLine ("Usage: gacutil.exe <commands> [ <options> ]");
645                         WriteLine ("Commands:");
646
647                         WriteLine ("-i <assembly_path> [-check_refs] [-package NAME] [-root ROOTDIR] [-gacdir GACDIR]");
648                         WriteLine ("\tInstalls an assembly into the global assembly cache.");
649                         if (detailed) {
650                                 WriteLine ("\t<assembly_path> is the name of the file that contains the " +
651                                                 "\tassembly manifest\n" +
652                                                 "\tExample: -i myDll.dll");
653                         }
654                         WriteLine ();
655
656                         WriteLine ("-il <assembly_list_file> [-check_refs] [-package NAME] [-root ROOTDIR] [-gacdir GACDIR]");
657                         WriteLine ("\tInstalls one or more assemblies into the global assembly cache.");
658                         if (detailed) {
659                                 WriteLine ("\t<assembly_list_file> is the path to a test file containing a list of\n" +
660                                                 "\tassembly file paths on separate lines.\n" +
661                                                 "\tExample -il assembly_list.txt\n" +
662                                                 "\t\tassembly_list.txt contents:\n" +
663                                                 "\t\tassembly1.dll\n" +
664                                                 "\t\tassembly2.dll");
665                         }
666                         WriteLine ();
667                         
668                         WriteLine ("-u <assembly_display_name> [-package NAME] [-root ROOTDIR] [-gacdir GACDIR]");
669                         WriteLine ("\tUninstalls an assembly from the global assembly cache.");
670                         if (detailed) {
671                                 WriteLine ("\t<assembly_display_name> is the name of the assembly (partial or\n" +
672                                                 "\tfully qualified) to remove from the global assembly cache. If a \n" +
673                                                 "\tpartial name is specified all matching assemblies will be uninstalled.\n" +
674                                                 "\tExample: -u myDll,Version=1.2.1.0");
675                         }
676                         WriteLine ();
677
678                         WriteLine ("-ul <assembly_list_file> [-package NAME] [-root ROOTDIR] [-gacdir GACDIR]");
679                         WriteLine ("\tUninstalls one or more assemblies from the global assembly cache.");
680                         if (detailed) {
681                                 WriteLine ("\t<assembly_list_file> is the path to a test file containing a list of\n" +
682                                                 "\tassembly names on separate lines.\n" +
683                                                 "\tExample -ul assembly_list.txt\n" +
684                                                 "\t\tassembly_list.txt contents:\n" +
685                                                 "\t\tassembly1,Version=1.0.0.0,Culture=en,PublicKeyToken=0123456789abcdef\n" +
686                                                 "\t\tassembly2,Version=2.0.0.0,Culture=en,PublicKeyToken=0123456789abcdef");
687                         }
688                         WriteLine ();
689
690                         WriteLine ("-us <assembly_path> [-package NAME] [-root ROOTDIR] [-gacdir GACDIR]");
691                         WriteLine ("\tUninstalls an assembly using the specifed assemblies full name.");
692                         if (detailed) {
693                                 WriteLine ("\t<assembly path> is the path to an assembly. The full assembly name\n" +
694                                                 "\tis retrieved from the specified assembly if there is an assembly in\n" +
695                                                 "\tthe GAC with a matching name, it is removed.\n" +
696                                                 "\tExample: -us myDll.dll");
697                         }
698                         WriteLine ();
699
700                         WriteLine ("-l [assembly_name] [-root ROOTDIR] [-gacdir GACDIR]");
701                         WriteLine ("\tLists the contents of the global assembly cache.");
702                         if (detailed) {
703                                 WriteLine ("\tWhen the <assembly_name> parameter is specified only matching\n" +
704                                                 "\tassemblies are listed.");
705                         }
706                         WriteLine ();
707
708                         WriteLine ("-?");
709                         WriteLine ("\tDisplays a detailed help screen");
710                         WriteLine ();
711
712                         if (!detailed)
713                                 return;
714
715                         WriteLine ("Options:");
716                         WriteLine ("-package <NAME>");
717                         WriteLine ("\tUsed to create a directory in prefix/lib/mono with the name NAME, and a\n" +
718                                         "\tsymlink is created from NAME/assembly_name to the assembly on the GAC.\n" +
719                                         "\tThis is used so developers can reference a set of libraries at once.");
720                         WriteLine ();
721
722                         WriteLine ("-gacdir <GACDIR>");
723                         WriteLine ("\tUsed to specify the GACs base directory. Once an assembly has been installed\n" +
724                                         "\tto a non standard gacdir the MONO_GAC_PREFIX environment variable must be used\n" +
725                                         "\tto access the assembly.");
726                         WriteLine ();
727
728                         WriteLine ("-root <ROOTDIR>");
729                         WriteLine ("\tUsed by developers integrating this with automake tools or packaging tools\n" +
730                                         "\tthat require a prefix directory to  be specified. The root represents the\n" +
731                                         "\t\"libdir\" component of a prefix (typically prefix/lib).");
732                         WriteLine ();
733
734                         WriteLine ("-check_refs");
735                         WriteLine ("\tUsed to ensure that the assembly being installed into the GAC does not\n" +
736                                         "\treference any non strong named assemblies. Assemblies being installed to\n" +
737                                         "\tthe GAC should not reference non strong named assemblies, however the is\n" +
738                                         "\tan optional check.");
739
740                         WriteLine ();
741                         WriteLine ("Ignored Options:");
742                         WriteLine ("-f");
743                         WriteLine ("\tThe Mono gacutil ignores the -f option to maintian commandline compatibility with");
744                         WriteLine ("\tother gacutils. gacutil will always force the installation of a new assembly.");
745
746                         WriteLine ();
747                         WriteLine ("-r <reference_scheme> <reference_id> <description>");
748                         WriteLine ("\tThe Mono gacutil has not implemented traced references and will emit a warning");
749                         WriteLine ("\twhen this option is used.");
750                 }
751         }
752 }
753