* driver.cs: Add -check_refs option to check if all referenced
[mono.git] / mcs / tools / gacutil / driver.cs
1 //
2 // Mono.Tools.GacUtil
3 //
4 // Author(s):
5 //  Tood 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                         Uninstall,
31                         UninstallSpecific,
32                         List,
33                         Help
34                 }
35
36                 public static int Main (string [] args)
37                 {
38                         if (args.Length == 0)
39                                 Usage ();
40
41                         Command command = GetCommand (args [0]);
42
43                         if (command == Command.Unknown && IsSwitch (args [0])) {
44                                 Console.WriteLine ("Unknown command: " + args [0]);
45                                 Environment.Exit (1);
46                         } else if (command == Command.Unknown) {
47                                 Usage ();
48                         } else if (command == Command.Help) {
49                                 ShowHelp (true);
50                                 Environment.Exit (1);
51                         }
52
53                         string libdir;
54                         string name, package, gacdir, root;
55                         name = package = root = gacdir = null;
56                         bool check_refs = false;
57
58                         for (int i=1; 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                                         if (args [i] == "-check_refs" || args [i] == "/check_refs") {
67                                                 check_refs = true;
68                                                 continue;
69                                         }
70
71                                         if (i + 1 >= args.Length) {
72                                                 Console.WriteLine ("Option " + args [i] + " takes 1 argument");
73                                                 Environment.Exit (1);
74                                         }
75
76                                         switch (args [i]) {
77                                         case "-package":
78                                         case "/package":
79                                                 package = args [++i];
80                                                 break;
81                                         case "-root":
82                                         case "/root":
83                                                 root = args [++i];
84                                                 break;
85                                         case "-gacdir":
86                                         case "/gacdir":
87                                                 gacdir = args [++i];
88                                                 break;
89                                         }
90                                         continue;
91                                 }
92                                 if (name == null)
93                                         name = args [i];
94                                 else
95                                         name += args [i];
96                         }
97
98                         if (gacdir == null) {
99                                 gacdir = GetGacDir ();
100                                 libdir = GetLibDir ();
101                         } else {
102                                 libdir = Path.Combine (gacdir, "mono");
103                                 gacdir = Path.Combine (libdir, "gac");
104                         }
105
106                         string link_gacdir = gacdir;
107                         string link_libdir = libdir;
108                         if (root != null) {
109                                 libdir = CombinePaths (root, libdir);
110                                 gacdir = CombinePaths (root, gacdir);
111                         }
112
113                         switch (command) {
114                         case Command.Install:
115                                 if (name == null) {
116                                         Console.WriteLine ("Option " + args [0] + " takes 1 argument");
117                                         Environment.Exit (1);
118                                 }
119                                 Install (check_refs, name, package, gacdir, link_gacdir, libdir, link_libdir);
120                                 break;
121                         case Command.Uninstall:
122                                 if (name == null) {
123                                         Console.WriteLine ("Option " + args [0] + " takes 1 argument");
124                                         Environment.Exit (1);
125                                 }
126                                 Uninstall (name, package, gacdir, libdir);
127                                 break;
128                         case Command.UninstallSpecific:
129                                 if (name == null) {
130                                         Console.WriteLine ("Opetion " + args [0] + " takes 1 argument");
131                                         Environment.Exit (1);
132                                 }
133                                 UninstallSpecific (name, package, gacdir, libdir);
134                                 break;
135                         case Command.List:
136                                 List (name, gacdir);
137                                 break;
138                         }
139
140                         return 0;
141                 }
142
143                 private static void Install (bool check_refs, string name, string package,
144                                 string gacdir, string link_gacdir, string libdir, string link_libdir)
145                 {
146                         string failure_msg = "Failure adding assembly to the cache: ";
147
148                         if (!File.Exists (name)) {
149                                 Console.WriteLine (failure_msg + "The system cannot find the file specified.");
150                                 Environment.Exit (1);
151                         }
152
153                         AssemblyName an = null;
154                         byte [] pub_tok;
155
156                         try {
157                                 an = AssemblyName.GetAssemblyName (name);
158                         } catch {
159                                 Console.WriteLine (failure_msg + "The file specified is not a valid assembly.");
160                                 Environment.Exit (1);
161                         }
162
163                         pub_tok = an.GetPublicKeyToken ();
164                         if (pub_tok == null || pub_tok.Length == 0) {
165                                 Console.WriteLine (failure_msg + "Attempt to install an assembly without a strong name.");
166                                 Environment.Exit (1);
167                         }
168
169                         if (check_refs && !CheckReferencedAssemblies (an)) {
170                                 Console.WriteLine (failure_msg + "Attempt to install an assembly that references non " +
171                                                 "strong named assemblies with -check_refs enabled.");
172                                 Environment.Exit (1);
173                         }
174
175                         string conf_name = name + ".config";
176                         string version_token = an.Version + "_" +
177                                                an.CultureInfo.Name.ToLower (CultureInfo.InvariantCulture) + "_" +
178                                                GetStringToken (pub_tok);
179                         string full_path = Path.Combine (Path.Combine (gacdir, an.Name), version_token);
180                         string asmb_file = Path.GetFileName (name);
181                         string asmb_path = Path.Combine (full_path, asmb_file);
182
183                         try {
184                                 if (Directory.Exists (full_path)) {
185                                         // Wipe out the directory. This way we ensure old assemblies    
186                                         // config files, and AOTd files are removed.
187                                         Directory.Delete (full_path, true);
188                                 }
189                                 Directory.CreateDirectory (full_path);
190                         } catch {
191                                 Console.WriteLine (failure_msg + "gac directories could not be created, " +
192                                                 "possibly permission issues.");
193                                 Environment.Exit (1);
194                         }
195
196                         File.Copy (name, asmb_path, true);
197                         if (File.Exists (conf_name))
198                                 File.Copy (conf_name, asmb_path + ".config", true);
199
200                         if (package != null) {
201                                 string link_path = Path.Combine (Path.Combine (link_gacdir, an.Name), version_token);
202                                 string ref_dir = Path.Combine (libdir, package);
203                                 string ref_path = Path.Combine (ref_dir, asmb_file);
204
205                                 if (File.Exists (ref_path))
206                                         File.Delete (ref_path);
207                                 try {
208                                         Directory.CreateDirectory (ref_dir);
209                                 } catch {
210                                         Console.WriteLine ("ERROR: Could not create package dir file.");
211                                         Environment.Exit (1);
212                                 }
213                                 Symlink (Path.Combine (link_path, asmb_file), ref_path);
214
215                                 Console.WriteLine ("Package exported to: " + ref_path + " -> " +
216                                                 Path.Combine (link_path, asmb_file));
217
218                         }
219
220                         Console.WriteLine ("{0} installed into the gac ({1})", an.Name, gacdir); 
221                 }
222
223                 private static void Uninstall (string name, string package, string gacdir, string libdir)
224                 {
225                         string [] assembly_pieces = name.Split (new char[] { ',' });
226                         Hashtable asm_info = new Hashtable ();
227
228                         foreach (string item in assembly_pieces) {
229                                 string[] pieces = item.Trim ().Split (new char[] { '=' }, 2);
230                                 if(pieces.Length == 1)
231                                         asm_info ["assembly"] = pieces [0];
232                                 else
233                                         asm_info [pieces[0].Trim ().ToLower (CultureInfo.InvariantCulture)] = pieces [1];
234                         }
235
236                         string asmdir = Path.Combine (gacdir, (string) asm_info ["assembly"]);
237                         if (!Directory.Exists (asmdir)) {
238                                 Console.WriteLine ("No assemblies found that match: " + name);
239                                 Environment.Exit (1);
240                         }
241
242                         string searchString = GetSearchString (asm_info);
243                         string [] directories = Directory.GetDirectories (asmdir, searchString);
244
245                         foreach (string dir in directories) {
246                                 Directory.Delete (dir, true);
247                                 if (package != null) {
248                                         string link_dir = Path.Combine (libdir, package);
249                                         string link = Path.Combine (link_dir, (string) asm_info ["assembly"] + ".dll");
250                                         File.Delete (link);
251                                         if (Directory.GetFiles (link_dir).Length == 0) {
252                                                 Console.WriteLine ("Cleaning package directory, it is empty.");
253                                                 Directory.Delete (link_dir);
254                                         }
255                                 }
256                                 Console.WriteLine ("Assembly removed from the gac.");
257                         }
258
259                         if(Directory.GetDirectories (asmdir).Length == 0) {
260                                 Console.WriteLine ("Cleaning assembly dir, its empty");
261                                 Directory.Delete (asmdir);
262                         }
263                 }
264
265                 private static void UninstallSpecific (string name, string package,
266                                 string gacdir, string libdir)
267                 {
268                         string failure_msg = "Failure to remove assembly from the cache: ";
269
270                         if (!File.Exists (name)) {
271                                 Console.WriteLine (failure_msg + "The system cannot find the file specified.");
272                                 Environment.Exit (1);
273                         }
274
275                         AssemblyName an = null;
276
277                         try {
278                                 an = AssemblyName.GetAssemblyName (name);
279                         } catch {
280                                 Console.WriteLine (failure_msg + "The file specified is not a valid assembly.");
281                                 Environment.Exit (1);
282                         }
283
284                         Uninstall (an.FullName.Replace (" ", String.Empty),
285                                         package, gacdir, libdir);
286                 }
287
288                 private static void List (string name, string gacdir)
289                 {
290                         Console.WriteLine ("The following assemblies are installed into the GAC:");
291
292                         if (name != null) {
293                                 FilteredList (name, gacdir);
294                                 return;
295                         }
296
297                         int count = 0;
298                         DirectoryInfo gacinfo = new DirectoryInfo (gacdir);
299                         foreach (DirectoryInfo parent in gacinfo.GetDirectories ()) {
300                                 foreach (DirectoryInfo dir in parent.GetDirectories ()) {
301                                         Console.WriteLine (AsmbNameFromVersionString (parent.Name, dir.Name));
302                                         count++;
303                                 }
304                         }
305                         Console.WriteLine ("Number of items = " + count);
306                 }
307
308                 private static void FilteredList (string name, string gacdir)
309                 {
310                         string [] assembly_pieces = name.Split (new char[] { ',' });
311                         Hashtable asm_info = new Hashtable ();
312
313                         foreach (string item in assembly_pieces) {
314                                 string[] pieces = item.Trim ().Split (new char[] { '=' }, 2);
315                                 if(pieces.Length == 1)
316                                         asm_info ["assembly"] = pieces [0];
317                                 else
318                                         asm_info [pieces[0].Trim ().ToLower (CultureInfo.InvariantCulture)] = pieces [1];
319                         }
320                         
321                         string asmdir = Path.Combine (gacdir, (string) asm_info ["assembly"]);
322                         if (!Directory.Exists (asmdir)) {
323                                 Console.WriteLine ("Number of items = 0");
324                                 return;
325                         }
326                         string search = GetSearchString (asm_info);
327                         string [] dir_list = Directory.GetDirectories (asmdir, search);
328
329                         int count = 0;
330                         foreach (string dir in dir_list) {
331                                 Console.WriteLine (AsmbNameFromVersionString ((string) asm_info ["assembly"],
332                                                 new DirectoryInfo (dir).Name));
333                                 count++;
334                         }
335                         Console.WriteLine ("Number of items = " + count);
336                 }
337
338                 private static bool CheckReferencedAssemblies (AssemblyName an)
339                 {
340                         AppDomain d = null;
341                         try {
342                                 Assembly a;
343                                 AssemblyName corlib = typeof (object).Assembly.GetName ();
344                                 d = AppDomain.CreateDomain (an.Name);
345                                 a = d.Load (an);
346                                 foreach (AssemblyName ref_an in a.GetReferencedAssemblies ()) {
347                                         if (ref_an.Name == corlib.Name) // Just do a string compare so we can install on diff versions
348                                                 continue;
349                                         byte [] pt = ref_an.GetPublicKeyToken ();
350                                         if (pt == null || pt.Length == 0) {
351                                                 Console.WriteLine (ref_an);
352                                                 return false;
353                                         }
354                                 }
355                         } catch  (Exception e) {
356                                 Console.WriteLine (e); // This should be removed pre beta3
357                                 return false;
358                         } finally {
359                                 if (d != null) {
360                                         try {
361                                                 AppDomain.Unload (d);
362                                         } catch { }
363                                 }
364                         }
365
366                         return true;
367                 }
368
369                 private static string GetSearchString (Hashtable asm_info)
370                 {
371                         if (asm_info.Keys.Count == 1)
372                                 return "*";
373                         string version, culture, token;
374
375                         version = asm_info ["version"] as string;
376                         version = (version == null ? "*" : version);
377                         culture = asm_info ["culture"] as string;
378                         culture = (culture == null ? "*" : culture.ToLower (CultureInfo.InvariantCulture));
379                         token = asm_info ["publickeytoken"] as string;
380                         token = (token == null ? "*" : token.ToLower (CultureInfo.InvariantCulture));
381                         
382                         return String.Format ("{0}_{1}_{2}", version, culture, token);
383                 }
384
385                 private static string AsmbNameFromVersionString (string name, string str)
386                 {
387                         string [] pieces = str.Split ('_');
388                         return String.Format ("{0}, Version={1}, Culture={2}, PublicKeyToken={3}",
389                                         name, pieces [0], (pieces [1] == String.Empty ? "neutral" : pieces [1]),
390                                         pieces [2]);
391                 }
392
393                 private static bool IsSwitch (string arg)
394                 {
395                         return (arg [0] == '-' || (arg [0] == '/' && !File.Exists (arg)));
396                 }
397
398                 private static Command GetCommand (string arg)
399                 {
400                         Command c = Command.Unknown;
401
402                         switch (arg) {
403                         case "-i":
404                         case "/i":
405                         case "--install":
406                                 c = Command.Install;
407                                 break;
408                         case "-u":
409                         case "/u":
410                         case "/uf":
411                         case "--uninstall":
412                                 c = Command.Uninstall;
413                                 break;
414                         case "-us":
415                         case "/us":
416                         case "--uninstall-specific":
417                                 c = Command.UninstallSpecific;
418                                 break;
419                         case "-l":
420                         case "/l":
421                         case "--list":
422                                 c = Command.List;
423                                 break;
424                         case "-?":
425                         case "/?":
426                         case "--help":
427                                 c = Command.Help;
428                                 break;
429                         }
430                         return c;        
431                 }
432
433                 private static void Symlink (string oldpath, string newpath) {
434                         if (Path.DirectorySeparatorChar == '/') {
435                                 symlink (oldpath, newpath);
436                         } else {
437                                 File.Copy (oldpath, newpath);
438                         }
439                 }
440
441                 [DllImport ("libc", SetLastError=true)]
442                 public static extern int symlink (string oldpath, string newpath);
443
444                 private static string GetGacDir () {
445                         PropertyInfo gac = typeof (System.Environment).GetProperty ("GacPath",
446                                         BindingFlags.Static|BindingFlags.NonPublic);
447                         if (gac == null) {
448                                 Console.WriteLine ("ERROR: Mono runtime not detected, please use " +
449                                                 "the mono runtime for gacutil.exe");
450                                 Environment.Exit (1);
451                         }
452                         MethodInfo get_gac = gac.GetGetMethod (true);
453                         return (string) get_gac.Invoke (null, null);
454                 }
455
456                 private static string GetLibDir () {
457                         MethodInfo libdir = typeof (System.Environment).GetMethod ("internalGetGacPath",
458                                         BindingFlags.Static|BindingFlags.NonPublic);
459                         if (libdir == null) {
460                                 Console.WriteLine ("ERROR: Mono runtime not detected, please use " +
461                                                 "the mono runtime for gacutil.exe");
462                                 Environment.Exit (1);
463                         }
464                         return Path.Combine ((string)libdir.Invoke (null, null), "mono");
465                 }
466
467                 private static string GetStringToken (byte[] tok)
468                 {
469                         StringBuilder sb = new StringBuilder ();
470                         for (int i = 0; i < tok.Length ; i++)
471                                 sb.Append (tok[i].ToString ("x2"));
472                         return sb.ToString ();
473                 }
474
475                 private static string CombinePaths (string a, string b)
476                 {
477                         string dsc = Path.DirectorySeparatorChar.ToString ();
478                         string sep = (a.EndsWith (dsc) ? String.Empty : dsc);
479                         string end = (b.StartsWith (dsc) ? b.Substring (1) : b);
480                         return String.Concat (a, sep, end);
481                 }
482
483                 private static void Usage ()
484                 {
485                         ShowHelp (false);
486                         Environment.Exit (1);
487                 }
488
489                 private static void ShowHelp (bool detailed)
490                 {
491                         Console.WriteLine ("Usage: gacutil.exe <commands> [ <options> ]");
492                         Console.WriteLine ("Commands:");
493
494                         Console.WriteLine ("-i <assembly_path> [-check_refs] [-package NAME] [-root ROOTDIR] [-gacdir GACDIR]");
495                         Console.WriteLine ("\tInstalls an assembly into the global assembly cache.");
496                         if (detailed) {
497                                 Console.WriteLine ("\t<assembly_path> is the name of the file that contains the " +
498                                                 "\tassembly manifest\n" +
499                                                 "\tExample: -i myDll.dll");
500                         }
501                         Console.WriteLine ();
502
503                         Console.WriteLine ("-u <assembly_display_name> [-package NAME] [-root ROOTDIR] [-gacdir GACDIR]");
504                         Console.WriteLine ("\tUninstalls an assembly from the global assembly cache.");
505                         if (detailed) {
506                                 Console.WriteLine ("\t<assembly_display_name> is the name of the assembly (partial or\n" +
507                                                 "\tfully qualified) to remove from the global assembly cache. If a \n" +
508                                                 "\tpartial name is specified all matching assemblies will be uninstalled.\n" +
509                                                 "\tExample: -u myDll,Version=1.2.1.0");
510                         }
511                         Console.WriteLine ();
512
513                         Console.WriteLine ("-us <assembly_path> [-package NAME] [-root ROOTDIR] [-gacdir GACDIR]");
514                         Console.WriteLine ("\tUninstalls an assembly using the specifed assemblies full name.");
515                         if (detailed) {
516                                 Console.WriteLine ("\t<assembly path> is the path to an assembly. The full assembly name\n" +
517                                                 "\tis retrieved from the specified assembly if there is an assembly in\n" +
518                                                 "\tthe GAC with a matching name, it is removed.\n" +
519                                                 "\tExample: -us myDll.dll");
520                         }
521                         Console.WriteLine ();
522
523                         Console.WriteLine ("-l [assembly_name] [-root ROOTDIR] [-gacdir GACDIR]");
524                         Console.WriteLine ("\tLists the contents of the global assembly cache.");
525                         if (detailed) {
526                                 Console.WriteLine ("\tWhen the <assembly_name> parameter is specified only matching\n" +
527                                                 "\tassemblies are listed.");
528                         }
529                         Console.WriteLine ();
530
531                         Console.WriteLine ("-?");
532                         Console.WriteLine ("\tDisplays a detailed help screen\n");
533                         Console.WriteLine ();
534
535                         if (!detailed)
536                                 return;
537
538                         Console.WriteLine ("Options:");
539                         Console.WriteLine ("-package <NAME>");
540                         Console.WriteLine ("Used to create a directory in prefix/lib/mono with the name NAME, and a\n" +
541                                         "\tsymlink is created from NAME/assembly_name to the assembly on the GAC.\n" +
542                                         "\tThis is used so developers can reference a set of libraries at once.");
543                         Console.WriteLine ();
544
545                         Console.WriteLine ("-gacdir <GACDIR>");
546                         Console.WriteLine ("Used to specify the GACs base directory. Once an assembly has been installed\n" +
547                                         "\tto a non standard gacdir the MONO_GAC_PATH environment variable must be used\n" +
548                                         "\tto access the assembly.");
549                         Console.WriteLine ();
550
551                         Console.WriteLine ("-root <ROOTDIR>");
552                         Console.WriteLine ("\tUsed by developers integrating this with automake tools or packaging tools\n" +
553                                         "\tthat require a prefix directory to  be specified. The root represents the\n" +
554                                         "\t\"libdir\" component of a prefix (typically prefix/lib).");
555                         Console.WriteLine ();
556
557                         Console.WriteLine ("-check_refs");
558                         Console.WriteLine ("\tUsed to ensure that the assembly being installed into the GAC does not\n" +
559                                         "\treference any non strong named assemblies. Assemblies being installed to\n" +
560                                         "\tthe GAC should not reference non strong named assemblies, however the is\n" +
561                                         "\tan optional check.\n");
562
563                 }
564         }
565 }
566