5 // Todd Berman <tberman@sevenl.net>
6 // Jackson Harper <jackson@ximian.com>
8 // Copyright 2003, 2004 Todd Berman
9 // Copyright 2004 Novell, Inc (http://www.novell.com)
16 using System.Reflection;
17 using System.Collections;
18 using System.Globalization;
19 using System.Runtime.InteropServices;
23 namespace Mono.Tools {
27 private enum Command {
38 private static bool silent;
40 public static int Main (string [] args)
45 Command command = Command.Unknown;
46 string command_str = null;
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 suppress
54 // warnings during command line parsing
55 if (Array.IndexOf (args, "/silent") > -1 || Array.IndexOf (args, "-silent") > -1)
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")
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.");
74 // This is already handled we just dont want to choke on it
75 if (args [i] == "-silent" || args [i] == "/silent")
78 if (args [i] == "-check_refs" || args [i] == "/check_refs") {
83 if (command == Command.Unknown) {
84 command = GetCommand (args [i]);
85 if (command != Command.Unknown) {
86 command_str = args [i];
91 if (i + 1 >= args.Length) {
92 Console.WriteLine ("Option " + args [i] + " takes 1 argument");
117 if (command == Command.Unknown && IsSwitch (args [0])) {
118 Console.WriteLine ("Unknown command: " + args [0]);
120 } else if (command == Command.Unknown) {
122 } else if (command == Command.Help) {
127 if (gacdir == null) {
128 gacdir = GetGacDir ();
129 libdir = GetLibDir ();
131 gacdir = EnsureLib (gacdir);
132 libdir = Path.Combine (gacdir, "mono");
133 gacdir = Path.Combine (libdir, "gac");
136 string link_gacdir = gacdir;
137 string link_libdir = libdir;
139 libdir = Path.Combine (root, "mono");
140 gacdir = Path.Combine (libdir, "gac");
144 case Command.Install:
146 WriteLine ("Option " + command_str + " takes 1 argument");
149 if (!Install (check_refs, name, package, gacdir, link_gacdir, libdir, link_libdir))
152 case Command.InstallFromList:
154 WriteLine ("Option " + command_str + " takes 1 argument");
157 if (!InstallFromList (check_refs, name, package, gacdir, link_gacdir, libdir, link_libdir))
160 case Command.Uninstall:
162 WriteLine ("Option " + command_str + " takes 1 argument");
165 if (!Uninstall (name, package, gacdir, libdir))
166 Environment.Exit (1);
168 case Command.UninstallFromList:
170 WriteLine ("Option " + command_str + " takes 1 argument");
173 if (!UninstallFromList (name, package, gacdir, libdir))
176 case Command.UninstallSpecific:
178 WriteLine ("Option " + command_str + " takes 1 argument");
181 if (!UninstallSpecific (name, package, gacdir, libdir))
192 static void Copy (string source, string target, bool v)
195 File.Delete (target);
197 File.Copy (source, target, v);
200 private static bool Install (bool check_refs, string name, string package,
201 string gacdir, string link_gacdir, string libdir, string link_libdir)
203 string failure_msg = "Failure adding assembly to the cache: ";
206 if (!File.Exists (name)) {
207 WriteLine (failure_msg + "The system cannot find the file specified.");
208 Environment.Exit (1);
211 Assembly assembly = null;
212 AssemblyName an = null;
216 assembly = Assembly.LoadFrom (name);
218 WriteLine (failure_msg + "The file specified is not a valid assembly.");
222 an = assembly.GetName ();
223 pub_tok = an.GetPublicKeyToken ();
224 if (pub_tok == null || pub_tok.Length == 0) {
225 WriteLine (failure_msg + "Attempt to install an assembly without a strong name.");
229 resources = new ArrayList ();
230 foreach (string res_name in assembly.GetManifestResourceNames ()) {
231 ManifestResourceInfo res_info = assembly.GetManifestResourceInfo (res_name);
233 if ((res_info.ResourceLocation & ResourceLocation.Embedded) == 0) {
234 if (!File.Exists (res_info.FileName)) {
235 WriteLine (failure_msg + "The system cannot find resource " + res_info.FileName);
239 resources.Add (res_info);
243 if (check_refs && !CheckReferencedAssemblies (an)) {
244 WriteLine (failure_msg + "Attempt to install an assembly that references non " +
245 "strong named assemblies with -check_refs enabled.");
249 string [] siblings = { ".config", ".mdb" };
250 string version_token = an.Version + "_" +
251 an.CultureInfo.Name.ToLower (CultureInfo.InvariantCulture) + "_" +
252 GetStringToken (pub_tok);
253 string full_path = Path.Combine (Path.Combine (gacdir, an.Name), version_token);
254 string asmb_file = Path.GetFileName (name);
255 string asmb_path = Path.Combine (full_path, asmb_file);
258 if (Directory.Exists (full_path)) {
259 // Wipe out the directory. This way we ensure old assemblies
260 // config files, and AOTd files are removed.
261 Directory.Delete (full_path, true);
263 Directory.CreateDirectory (full_path);
265 WriteLine (failure_msg + "gac directories could not be created, " +
266 "possibly permission issues.");
270 Copy (name, asmb_path, true);
272 foreach (string ext in siblings) {
273 string sibling = String.Concat (name, ext);
274 if (File.Exists (sibling))
275 Copy (sibling, String.Concat (asmb_path, ext), true);
278 foreach (ManifestResourceInfo resource_info in resources) {
280 Copy (resource_info.FileName, Path.Combine (full_path, Path.GetFileName (resource_info.FileName)), true);
282 WriteLine ("ERROR: Could not install resource file " + resource_info.FileName);
283 Environment.Exit (1);
287 if (package != null) {
288 string ref_dir = Path.Combine (libdir, package);
289 string ref_path = Path.Combine (ref_dir, asmb_file);
291 if (File.Exists (ref_path))
292 File.Delete (ref_path);
294 Directory.CreateDirectory (ref_dir);
296 WriteLine ("ERROR: Could not create package dir file.");
297 Environment.Exit (1);
299 if (Path.DirectorySeparatorChar == '/') {
300 string pkg_path = "../gac/" + an.Name + "/" + version_token + "/" + asmb_file;
301 symlink (pkg_path, ref_path);
303 foreach (string ext in siblings) {
304 string sibling = String.Concat (pkg_path, ext);
305 string sref = String.Concat (ref_path, ext);
306 if (File.Exists (sibling))
307 symlink (sibling, sref);
312 // Ignore error, just delete files that should not be there.
316 WriteLine ("Package exported to: {0} -> {1}", ref_path, pkg_path);
318 // string link_path = Path.Combine (Path.Combine (link_gacdir, an.Name), version_token);
320 // We can't use 'link_path' here, since it need not be a valid path at the time 'gacutil'
321 // is run, esp. when invoked in a DESTDIR install.
322 Copy (name, ref_path, true);
323 WriteLine ("Package exported to: " + ref_path);
327 WriteLine ("{0} installed into the gac ({1})", an.Name, gacdir);
331 private static bool Uninstall (string name, string package, string gacdir, string libdir)
333 string [] assembly_pieces = name.Split (new char[] { ',' });
334 Hashtable asm_info = new Hashtable ();
336 foreach (string item in assembly_pieces) {
337 if (item == String.Empty) continue;
338 string[] pieces = item.Trim ().Split (new char[] { '=' }, 2);
339 if(pieces.Length == 1)
340 asm_info ["assembly"] = pieces [0];
342 asm_info [pieces[0].Trim ().ToLower (CultureInfo.InvariantCulture)] = pieces [1];
345 string asmdir = Path.Combine (gacdir, (string) asm_info ["assembly"]);
346 if (!Directory.Exists (asmdir)) {
347 WriteLine ("No assemblies found that match: " + name);
351 string searchString = GetSearchString (asm_info);
352 string [] directories = Directory.GetDirectories (asmdir, searchString);
354 foreach (string dir in directories) {
355 Directory.Delete (dir, true);
356 if (package != null) {
357 string link_dir = Path.Combine (libdir, package);
358 string link = Path.Combine (link_dir, (string) asm_info ["assembly"] + ".dll");
362 // The file might not exist, happens with
363 // the debugger on make uninstall
366 if (Directory.GetFiles (link_dir).Length == 0) {
367 WriteLine ("Cleaning package directory, it is empty.");
369 Directory.Delete (link_dir);
371 // Workaround: GetFiles does not list Symlinks
375 WriteLine ("Assembly removed from the gac.");
378 if(Directory.GetDirectories (asmdir).Length == 0) {
379 WriteLine ("Cleaning assembly dir, it is empty");
381 Directory.Delete (asmdir);
383 // Workaround: GetFiles does not list Symlinks
390 private static bool UninstallSpecific (string name, string package,
391 string gacdir, string libdir)
393 string failure_msg = "Failure to remove assembly from the cache: ";
395 if (!File.Exists (name)) {
396 WriteLine (failure_msg + "The system cannot find the file specified.");
400 AssemblyName an = null;
403 an = AssemblyName.GetAssemblyName (name);
405 WriteLine (failure_msg + "The file specified is not a valid assembly.");
409 return Uninstall (an.FullName.Replace (" ", String.Empty),
410 package, gacdir, libdir);
413 private static void List (string name, string gacdir)
415 WriteLine ("The following assemblies are installed into the GAC:");
418 FilteredList (name, gacdir);
423 DirectoryInfo gacinfo = new DirectoryInfo (gacdir);
424 foreach (DirectoryInfo parent in gacinfo.GetDirectories ()) {
425 foreach (DirectoryInfo dir in parent.GetDirectories ()) {
426 string asmb = Path.Combine (Path.Combine (parent.FullName, dir.Name), parent.Name) + ".dll";
427 if (File.Exists (asmb)) {
428 WriteLine (AsmbNameFromVersionString (parent.Name, dir.Name));
433 WriteLine ("Number of items = " + count);
436 private static void FilteredList (string name, string gacdir)
438 string [] assembly_pieces = name.Split (new char[] { ',' });
439 Hashtable asm_info = new Hashtable ();
441 foreach (string item in assembly_pieces) {
442 string[] pieces = item.Trim ().Split (new char[] { '=' }, 2);
443 if(pieces.Length == 1)
444 asm_info ["assembly"] = pieces [0];
446 asm_info [pieces[0].Trim ().ToLower (CultureInfo.InvariantCulture)] = pieces [1];
449 string asmdir = Path.Combine (gacdir, (string) asm_info ["assembly"]);
450 if (!Directory.Exists (asmdir)) {
451 WriteLine ("Number of items = 0");
454 string search = GetSearchString (asm_info);
455 string [] dir_list = Directory.GetDirectories (asmdir, search);
458 foreach (string dir in dir_list) {
459 string asmb = Path.Combine (dir, (string) asm_info ["assembly"]) + ".dll";
460 if (File.Exists (asmb)) {
461 WriteLine (AsmbNameFromVersionString ((string) asm_info ["assembly"],
462 new DirectoryInfo (dir).Name));
466 WriteLine ("Number of items = " + count);
469 private static bool InstallFromList (bool check_refs, string list_file, string package,
470 string gacdir, string link_gacdir, string libdir, string link_libdir)
472 StreamReader s = null;
473 int processed, failed;
475 processed = failed = 0;
478 s = new StreamReader (list_file);
481 while ((line = s.ReadLine ()) != null) {
482 if (!Install (check_refs, line, package, gacdir, link_gacdir,
483 libdir, link_libdir))
488 } catch (IOException ioe) {
489 WriteLine ("Failed to open assemblies list file " + list_file + ".");
499 private static bool UninstallFromList (string list_file, string package,
500 string gacdir, string libdir)
502 StreamReader s = null;
503 int processed, failed;
505 processed = failed = 0;
508 s = new StreamReader (list_file);
511 while ((line = s.ReadLine ()) != null) {
512 if (!Uninstall (line, package, gacdir, libdir))
516 } catch (IOException ioe) {
517 WriteLine ("Failed to open assemblies list file " + list_file + ".");
527 private static bool CheckReferencedAssemblies (AssemblyName an)
531 Assembly a = Assembly.LoadFrom (an.CodeBase);
532 AssemblyName corlib = typeof (object).Assembly.GetName ();
534 foreach (AssemblyName ref_an in a.GetReferencedAssemblies ()) {
535 if (ref_an.Name == corlib.Name) // Just do a string compare so we can install on diff versions
537 byte [] pt = ref_an.GetPublicKeyToken ();
538 if (pt == null || pt.Length == 0) {
539 WriteLine ("Assembly " + ref_an.Name + " is not strong named.");
543 } catch (Exception e) {
544 WriteLine (e.ToString ()); // This should be removed pre beta3
549 AppDomain.Unload (d);
557 private static string GetSearchString (Hashtable asm_info)
559 if (asm_info.Keys.Count == 1)
561 string version, culture, token;
563 version = asm_info ["version"] as string;
564 version = (version == null ? "*" : version);
565 culture = asm_info ["culture"] as string;
566 culture = (culture == null ? "*" : (culture == "neutral") ? String.Empty : culture.ToLower (CultureInfo.InvariantCulture));
567 token = asm_info ["publickeytoken"] as string;
568 token = (token == null ? "*" : token.ToLower (CultureInfo.InvariantCulture));
570 return String.Format ("{0}_{1}_{2}", version, culture, token);
573 private static string AsmbNameFromVersionString (string name, string str)
575 string [] pieces = str.Split ('_');
576 return String.Format ("{0}, Version={1}, Culture={2}, PublicKeyToken={3}",
577 name, pieces [0], (pieces [1] == String.Empty ? "neutral" : pieces [1]),
581 private static bool IsSwitch (string arg)
583 return (arg [0] == '-' || (arg [0] == '/' && !arg.EndsWith(".dll") && arg.IndexOf('/', 1) < 0 ) );
586 private static Command GetCommand (string arg)
588 Command c = Command.Unknown;
598 case "--install-from-list":
599 c = Command.InstallFromList;
605 c = Command.Uninstall;
609 case "--uninstall-from-list":
610 c = Command.UninstallFromList;
614 case "--uninstall-specific":
615 c = Command.UninstallSpecific;
631 [DllImport ("libc", SetLastError=true)]
632 public static extern int symlink (string oldpath, string newpath);
634 private static string GetGacDir () {
635 PropertyInfo gac = typeof (System.Environment).GetProperty ("GacPath",
636 BindingFlags.Static|BindingFlags.NonPublic);
638 WriteLine ("ERROR: Mono runtime not detected, please use " +
639 "the mono runtime for gacutil.exe");
640 Environment.Exit (1);
642 MethodInfo get_gac = gac.GetGetMethod (true);
643 return (string) get_gac.Invoke (null, null);
646 private static string GetLibDir () {
647 MethodInfo libdir = typeof (System.Environment).GetMethod ("internalGetGacPath",
648 BindingFlags.Static|BindingFlags.NonPublic);
649 if (libdir == null) {
650 WriteLine ("ERROR: Mono runtime not detected, please use " +
651 "the mono runtime for gacutil.exe");
652 Environment.Exit (1);
654 return Path.Combine ((string)libdir.Invoke (null, null), "mono");
657 private static string GetStringToken (byte[] tok)
659 StringBuilder sb = new StringBuilder ();
660 for (int i = 0; i < tok.Length ; i++)
661 sb.Append (tok[i].ToString ("x2"));
662 return sb.ToString ();
665 private static string CombinePaths (string a, string b)
667 string dsc = Path.DirectorySeparatorChar.ToString ();
668 string sep = (a.EndsWith (dsc) ? String.Empty : dsc);
669 string end = (b.StartsWith (dsc) ? b.Substring (1) : b);
670 return String.Concat (a, sep, end);
673 private static string EnsureLib (string dir)
675 DirectoryInfo d = new DirectoryInfo (dir);
678 return Path.Combine (dir, "lib");
681 private static void WriteLine ()
685 Console.WriteLine ();
688 private static void WriteLine (string line)
692 Console.WriteLine (line);
695 private static void WriteLine (string line, params object [] p)
699 Console.WriteLine (line, p);
702 private static void Usage ()
705 Environment.Exit (1);
708 private static void ShowHelp (bool detailed)
710 WriteLine ("Usage: gacutil.exe <commands> [ <options> ]");
711 WriteLine ("Commands:");
713 WriteLine ("-i <assembly_path> [-check_refs] [-package NAME] [-root ROOTDIR] [-gacdir GACDIR]");
714 WriteLine ("\tInstalls an assembly into the global assembly cache.");
716 WriteLine ("\t<assembly_path> is the name of the file that contains the " +
717 "\tassembly manifest\n" +
718 "\tExample: -i myDll.dll");
722 WriteLine ("-il <assembly_list_file> [-check_refs] [-package NAME] [-root ROOTDIR] [-gacdir GACDIR]");
723 WriteLine ("\tInstalls one or more assemblies into the global assembly cache.");
725 WriteLine ("\t<assembly_list_file> is the path to a test file containing a list of\n" +
726 "\tassembly file paths on separate lines.\n" +
727 "\tExample -il assembly_list.txt\n" +
728 "\t\tassembly_list.txt contents:\n" +
729 "\t\tassembly1.dll\n" +
730 "\t\tassembly2.dll");
734 WriteLine ("-u <assembly_display_name> [-package NAME] [-root ROOTDIR] [-gacdir GACDIR]");
735 WriteLine ("\tUninstalls an assembly from the global assembly cache.");
737 WriteLine ("\t<assembly_display_name> is the name of the assembly (partial or\n" +
738 "\tfully qualified) to remove from the global assembly cache. If a \n" +
739 "\tpartial name is specified all matching assemblies will be uninstalled.\n" +
740 "\tExample: -u myDll,Version=1.2.1.0");
744 WriteLine ("-ul <assembly_list_file> [-package NAME] [-root ROOTDIR] [-gacdir GACDIR]");
745 WriteLine ("\tUninstalls one or more assemblies from the global assembly cache.");
747 WriteLine ("\t<assembly_list_file> is the path to a test file containing a list of\n" +
748 "\tassembly names on separate lines.\n" +
749 "\tExample -ul assembly_list.txt\n" +
750 "\t\tassembly_list.txt contents:\n" +
751 "\t\tassembly1,Version=1.0.0.0,Culture=en,PublicKeyToken=0123456789abcdef\n" +
752 "\t\tassembly2,Version=2.0.0.0,Culture=en,PublicKeyToken=0123456789abcdef");
756 WriteLine ("-us <assembly_path> [-package NAME] [-root ROOTDIR] [-gacdir GACDIR]");
757 WriteLine ("\tUninstalls an assembly using the specifed assemblies full name.");
759 WriteLine ("\t<assembly path> is the path to an assembly. The full assembly name\n" +
760 "\tis retrieved from the specified assembly if there is an assembly in\n" +
761 "\tthe GAC with a matching name, it is removed.\n" +
762 "\tExample: -us myDll.dll");
766 WriteLine ("-l [assembly_name] [-root ROOTDIR] [-gacdir GACDIR]");
767 WriteLine ("\tLists the contents of the global assembly cache.");
769 WriteLine ("\tWhen the <assembly_name> parameter is specified only matching\n" +
770 "\tassemblies are listed.");
775 WriteLine ("\tDisplays a detailed help screen");
781 WriteLine ("Options:");
782 WriteLine ("-package <NAME>");
783 WriteLine ("\tUsed to create a directory in prefix/lib/mono with the name NAME, and a\n" +
784 "\tsymlink is created from NAME/assembly_name to the assembly on the GAC.\n" +
785 "\tThis is used so developers can reference a set of libraries at once.");
788 WriteLine ("-gacdir <GACDIR>");
789 WriteLine ("\tUsed to specify the GACs base directory. Once an assembly has been installed\n" +
790 "\tto a non standard gacdir the MONO_GAC_PREFIX environment variable must be used\n" +
791 "\tto access the assembly.");
794 WriteLine ("-root <ROOTDIR>");
795 WriteLine ("\tUsed by developers integrating this with automake tools or packaging tools\n" +
796 "\tthat require a prefix directory to be specified. The root represents the\n" +
797 "\t\"libdir\" component of a prefix (typically prefix/lib).");
800 WriteLine ("-check_refs");
801 WriteLine ("\tUsed to ensure that the assembly being installed into the GAC does not\n" +
802 "\treference any non strong named assemblies. Assemblies being installed to\n" +
803 "\tthe GAC should not reference non strong named assemblies, however the is\n" +
804 "\tan optional check.");
807 WriteLine ("Ignored Options:");
809 WriteLine ("\tThe Mono gacutil ignores the -f option to maintain commandline compatibility with");
810 WriteLine ("\tother gacutils. gacutil will always force the installation of a new assembly.");
813 WriteLine ("-r <reference_scheme> <reference_id> <description>");
814 WriteLine ("\tThe Mono gacutil has not implemented traced references and will emit a warning");
815 WriteLine ("\twhen this option is used.");