**** Merged from MCS ****
[mono-project.git] / mcs / tools / gacutil / driver.cs
bloba479440fbfaa3e3e9c1923dcd691cd94b08fb02f
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)
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;
21 using Mono.Security;
23 namespace Mono.Tools {
25 public class Driver {
27 private enum Command {
28 Unknown,
29 Install,
30 InstallFromList,
31 Uninstall,
32 UninstallFromList,
33 UninstallSpecific,
34 List,
35 Help
38 private static bool silent;
40 public static int Main (string [] args)
42 if (args.Length == 0)
43 Usage ();
45 Command command = Command.Unknown;
46 string command_str = null;
48 string libdir;
49 string name, package, gacdir, root;
50 name = package = root = gacdir = null;
51 bool check_refs = false;
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;
58 for (int i=0; i<args.Length; i++) {
59 if (IsSwitch (args [i])) {
61 // for cmd line compatibility with other gacutils
62 // we always force it though
63 if (args [i] == "-f" || args [i] == "/f")
64 continue;
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;
74 // This is already handled we just dont want to choke on it
75 if (args [i] == "-silent" || args [i] == "/silent")
76 continue;
78 if (args [i] == "-check_refs" || args [i] == "/check_refs") {
79 check_refs = true;
80 continue;
83 if (command == Command.Unknown) {
84 command = GetCommand (args [i]);
85 if (command != Command.Unknown) {
86 command_str = args [i];
87 continue;
91 if (i + 1 >= args.Length) {
92 Console.WriteLine ("Option " + args [i] + " takes 1 argument");
93 return 1;
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;
110 continue;
112 if (name == null)
113 name = args [i];
114 else
115 name += args [i];
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;
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");
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");
144 switch (command) {
145 case Command.Install:
146 if (name == null) {
147 WriteLine ("Option " + command_str + " takes 1 argument");
148 return 1;
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;
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;
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;
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;
182 if (!UninstallSpecific (name, package, gacdir, libdir))
183 return 1;
184 break;
185 case Command.List:
186 List (name, gacdir);
187 break;
190 return 0;
193 private static bool Install (bool check_refs, string name, string package,
194 string gacdir, string link_gacdir, string libdir, string link_libdir)
196 string failure_msg = "Failure adding assembly to the cache: ";
198 if (!File.Exists (name)) {
199 WriteLine (failure_msg + "The system cannot find the file specified.");
200 Environment.Exit (1);
203 AssemblyName an = null;
204 byte [] pub_tok;
206 try {
207 an = AssemblyName.GetAssemblyName (name);
208 } catch {
209 WriteLine (failure_msg + "The file specified is not a valid assembly.");
210 return false;
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;
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;
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);
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);
239 Directory.CreateDirectory (full_path);
240 } catch {
241 WriteLine (failure_msg + "gac directories could not be created, " +
242 "possibly permission issues.");
243 return false;
246 File.Copy (name, asmb_path, true);
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);
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);
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);
267 Symlink (Path.Combine (link_path, asmb_file), ref_path);
269 WriteLine ("Package exported to: " + ref_path + " -> " +
270 Path.Combine (link_path, asmb_file));
274 WriteLine ("{0} installed into the gac ({1})", an.Name, gacdir);
275 return true;
278 private static bool Uninstall (string name, string package, string gacdir, string libdir)
280 string [] assembly_pieces = name.Split (new char[] { ',' });
281 Hashtable asm_info = new Hashtable ();
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];
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;
297 string searchString = GetSearchString (asm_info);
298 string [] directories = Directory.GetDirectories (asmdir, searchString);
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);
311 WriteLine ("Assembly removed from the gac.");
314 if(Directory.GetDirectories (asmdir).Length == 0) {
315 WriteLine ("Cleaning assembly dir, its empty");
316 Directory.Delete (asmdir);
319 return true;
322 private static bool UninstallSpecific (string name, string package,
323 string gacdir, string libdir)
325 string failure_msg = "Failure to remove assembly from the cache: ";
327 if (!File.Exists (name)) {
328 WriteLine (failure_msg + "The system cannot find the file specified.");
329 return false;
332 AssemblyName an = null;
334 try {
335 an = AssemblyName.GetAssemblyName (name);
336 } catch {
337 WriteLine (failure_msg + "The file specified is not a valid assembly.");
338 return false;
341 return Uninstall (an.FullName.Replace (" ", String.Empty),
342 package, gacdir, libdir);
345 private static void List (string name, string gacdir)
347 WriteLine ("The following assemblies are installed into the GAC:");
349 if (name != null) {
350 FilteredList (name, gacdir);
351 return;
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++;
362 WriteLine ("Number of items = " + count);
365 private static void FilteredList (string name, string gacdir)
367 string [] assembly_pieces = name.Split (new char[] { ',' });
368 Hashtable asm_info = new Hashtable ();
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];
378 string asmdir = Path.Combine (gacdir, (string) asm_info ["assembly"]);
379 if (!Directory.Exists (asmdir)) {
380 WriteLine ("Number of items = 0");
381 return;
383 string search = GetSearchString (asm_info);
384 string [] dir_list = Directory.GetDirectories (asmdir, search);
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++;
392 WriteLine ("Number of items = " + count);
395 private static bool InstallFromList (bool check_refs, string list_file, string package,
396 string gacdir, string link_gacdir, string libdir, string link_libdir)
398 StreamReader s = null;
399 int processed, failed;
401 processed = failed = 0;
403 try {
404 s = new StreamReader (list_file);
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++;
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 ();
422 return true;
425 private static bool UninstallFromList (string list_file, string package,
426 string gacdir, string libdir)
428 StreamReader s = null;
429 int processed, failed;
431 processed = failed = 0;
433 try {
434 s = new StreamReader (list_file);
436 string line;
437 while ((line = s.ReadLine ()) != null) {
438 if (!Uninstall (line, package, gacdir, libdir))
439 failed++;
440 processed++;
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 ();
450 return true;
453 private static bool CheckReferencedAssemblies (AssemblyName an)
455 AppDomain d = null;
456 try {
457 Assembly a = Assembly.LoadFrom (an.CodeBase);
458 AssemblyName corlib = typeof (object).Assembly.GetName ();
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;
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 { }
480 return true;
483 private static string GetSearchString (Hashtable asm_info)
485 if (asm_info.Keys.Count == 1)
486 return "*";
487 string version, culture, token;
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));
496 return String.Format ("{0}_{1}_{2}", version, culture, token);
499 private static string AsmbNameFromVersionString (string name, string str)
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]);
507 private static bool IsSwitch (string arg)
509 return (arg [0] == '-' || arg [0] == '/');
512 private static Command GetCommand (string arg)
514 Command c = Command.Unknown;
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;
554 return c;
557 private static void Symlink (string oldpath, string newpath) {
558 if (Path.DirectorySeparatorChar == '/') {
559 symlink (oldpath, newpath);
560 } else {
561 File.Copy (oldpath, newpath);
565 [DllImport ("libc", SetLastError=true)]
566 public static extern int symlink (string oldpath, string newpath);
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);
576 MethodInfo get_gac = gac.GetGetMethod (true);
577 return (string) get_gac.Invoke (null, null);
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);
588 return Path.Combine ((string)libdir.Invoke (null, null), "mono");
591 private static string GetStringToken (byte[] tok)
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 ();
599 private static string CombinePaths (string a, string b)
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);
607 private static string EnsureLib (string dir)
609 DirectoryInfo d = new DirectoryInfo (dir);
610 if (d.Name == "lib")
611 return dir;
612 return Path.Combine (dir, "lib");
615 private static void WriteLine ()
617 if (silent)
618 return;
619 Console.WriteLine ();
622 private static void WriteLine (string line)
624 if (silent)
625 return;
626 Console.WriteLine (line);
629 private static void WriteLine (string line, params object [] p)
631 if (silent)
632 return;
633 Console.WriteLine (line, p);
636 private static void Usage ()
638 ShowHelp (false);
639 Environment.Exit (1);
642 private static void ShowHelp (bool detailed)
644 WriteLine ("Usage: gacutil.exe <commands> [ <options> ]");
645 WriteLine ("Commands:");
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");
654 WriteLine ();
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");
666 WriteLine ();
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");
676 WriteLine ();
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");
688 WriteLine ();
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");
698 WriteLine ();
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.");
706 WriteLine ();
708 WriteLine ("-?");
709 WriteLine ("\tDisplays a detailed help screen");
710 WriteLine ();
712 if (!detailed)
713 return;
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 ();
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 ();
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 ();
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.");
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.");
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.");