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