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