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 supress
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");
118 if (command
== Command
.Unknown
&& IsSwitch (args
[0])) {
119 Console
.WriteLine ("Unknown command: " + args
[0]);
121 } else if (command
== Command
.Unknown
) {
123 } else if (command
== Command
.Help
) {
128 if (gacdir
== null) {
129 gacdir
= GetGacDir ();
130 libdir
= GetLibDir ();
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
;
140 libdir
= Path
.Combine (root
, "mono");
141 gacdir
= Path
.Combine (libdir
, "gac");
145 case Command
.Install
:
147 WriteLine ("Option " + command_str
+ " takes 1 argument");
150 if (!Install (check_refs
, name
, package
, gacdir
, link_gacdir
, libdir
, link_libdir
))
153 case Command
.InstallFromList
:
155 WriteLine ("Option " + command_str
+ " takes 1 argument");
158 if (!InstallFromList (check_refs
, name
, package
, gacdir
, link_gacdir
, libdir
, link_libdir
))
161 case Command
.Uninstall
:
163 WriteLine ("Option " + command_str
+ " takes 1 argument");
166 if (!Uninstall (name
, package
, gacdir
, libdir
))
167 Environment
.Exit (1);
169 case Command
.UninstallFromList
:
171 WriteLine ("Option " + command_str
+ " takes 1 argument");
174 if (!UninstallFromList (name
, package
, gacdir
, libdir
))
177 case Command
.UninstallSpecific
:
179 WriteLine ("Opetion " + command_str
+ " takes 1 argument");
182 if (!UninstallSpecific (name
, package
, gacdir
, libdir
))
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;
207 an
= AssemblyName
.GetAssemblyName (name
);
209 WriteLine (failure_msg
+ "The file specified is not a valid assembly.");
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.");
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.");
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
);
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
);
241 WriteLine (failure_msg
+ "gac directories could not be created, " +
242 "possibly permission issues.");
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
);
262 Directory
.CreateDirectory (ref_dir
);
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
);
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];
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
);
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");
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
);
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.");
332 AssemblyName an
= null;
335 an
= AssemblyName
.GetAssemblyName (name
);
337 WriteLine (failure_msg
+ "The file specified is not a valid assembly.");
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:");
350 FilteredList (name
, gacdir
);
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
));
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];
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");
383 string search
= GetSearchString (asm_info
);
384 string [] dir_list
= Directory
.GetDirectories (asmdir
, search
);
387 foreach (string dir
in dir_list
) {
388 WriteLine (AsmbNameFromVersionString ((string) asm_info
["assembly"],
389 new DirectoryInfo (dir
).Name
));
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;
404 s
= new StreamReader (list_file
);
407 while ((line
= s
.ReadLine ()) != null) {
408 if (!Install (check_refs
, line
, package
, gacdir
, link_gacdir
,
409 libdir
, link_libdir
))
414 } catch (IOException ioe
) {
415 WriteLine ("Failed to open assemblies list file " + list_file
+ ".");
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;
434 s
= new StreamReader (list_file
);
437 while ((line
= s
.ReadLine ()) != null) {
438 if (!Uninstall (line
, package
, gacdir
, libdir
))
442 } catch (IOException ioe
) {
443 WriteLine ("Failed to open assemblies list file " + list_file
+ ".");
453 private static bool CheckReferencedAssemblies (AssemblyName an
)
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
463 byte [] pt
= ref_an
.GetPublicKeyToken ();
464 if (pt
== null || pt
.Length
== 0) {
465 WriteLine ("Assembly " + ref_an
.Name
+ " is not strong named.");
469 } catch (Exception e
) {
470 WriteLine (e
.ToString ()); // This should be removed pre beta3
475 AppDomain
.Unload (d
);
483 private static string GetSearchString (Hashtable asm_info
)
485 if (asm_info
.Keys
.Count
== 1)
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]),
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
;
524 case "--install-from-list":
525 c
= Command
.InstallFromList
;
531 c
= Command
.Uninstall
;
535 case "--uninstall-from-list":
536 c
= Command
.UninstallFromList
;
540 case "--uninstall-specific":
541 c
= Command
.UninstallSpecific
;
557 private static void Symlink (string oldpath
, string newpath
) {
558 if (Path
.DirectorySeparatorChar
== '/') {
559 symlink (oldpath
, newpath
);
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
);
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
);
612 return Path
.Combine (dir
, "lib");
615 private static void WriteLine ()
619 Console
.WriteLine ();
622 private static void WriteLine (string line
)
626 Console
.WriteLine (line
);
629 private static void WriteLine (string line
, params object [] p
)
633 Console
.WriteLine (line
, p
);
636 private static void Usage ()
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.");
650 WriteLine ("\t<assembly_path> is the name of the file that contains the " +
651 "\tassembly manifest\n" +
652 "\tExample: -i myDll.dll");
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.");
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");
668 WriteLine ("-u <assembly_display_name> [-package NAME] [-root ROOTDIR] [-gacdir GACDIR]");
669 WriteLine ("\tUninstalls an assembly from the global assembly cache.");
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");
678 WriteLine ("-ul <assembly_list_file> [-package NAME] [-root ROOTDIR] [-gacdir GACDIR]");
679 WriteLine ("\tUninstalls one or more assemblies from the global assembly cache.");
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");
690 WriteLine ("-us <assembly_path> [-package NAME] [-root ROOTDIR] [-gacdir GACDIR]");
691 WriteLine ("\tUninstalls an assembly using the specifed assemblies full name.");
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");
700 WriteLine ("-l [assembly_name] [-root ROOTDIR] [-gacdir GACDIR]");
701 WriteLine ("\tLists the contents of the global assembly cache.");
703 WriteLine ("\tWhen the <assembly_name> parameter is specified only matching\n" +
704 "\tassemblies are listed.");
709 WriteLine ("\tDisplays a detailed help screen");
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.");
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.");
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).");
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.");
741 WriteLine ("Ignored Options:");
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.");
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.");