New tests.
[mono.git] / mcs / class / Microsoft.Build.Tasks / Microsoft.Build.Tasks / PcFileCache.cs
1 // 
2 // PcFileCache.cs
3 //  
4 // Author:
5 //       Lluis Sanchez Gual <lluis@novell.com>
6 // 
7 // Copyright (c) 2009 Novell, Inc (http://www.novell.com)
8 // 
9 // Permission is hereby granted, free of charge, to any person obtaining a copy
10 // of this software and associated documentation files (the "Software"), to deal
11 // in the Software without restriction, including without limitation the rights
12 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 // copies of the Software, and to permit persons to whom the Software is
14 // furnished to do so, subject to the following conditions:
15 // 
16 // The above copyright notice and this permission notice shall be included in
17 // all copies or substantial portions of the Software.
18 // 
19 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 // THE SOFTWARE.
26
27 using System;
28 using System.Text;
29 using System.Xml;
30 using System.IO;
31 using System.Collections.Generic;
32
33 namespace Mono.PkgConfig
34 {
35         internal interface IPcFileCacheContext
36         {
37                 // In the implementation of this method, the host application can extract
38                 // information from the pc file and store it in the PackageInfo object
39                 void StoreCustomData (PcFile pcfile, PackageInfo pkg);
40                 
41                 // Should return false if the provided package does not have required
42                 // custom data
43                 bool IsCustomDataComplete (string pcfile, PackageInfo pkg);
44                 
45                 // Called to report errors
46                 void ReportError (string message, Exception ex);
47         }
48         
49         internal class PcFileCache
50         {
51                 const string CACHE_VERSION = "2";
52                 
53                 Dictionary<string, PackageInfo> infos = new Dictionary<string, PackageInfo> ();
54                 Dictionary<string, PackageAssemblyInfo> assemblyLocations;
55                 string cacheFile;
56                 bool hasChanges;
57                 IPcFileCacheContext ctx;
58                 
59                 public PcFileCache (IPcFileCacheContext ctx)
60                 {
61                         this.ctx = ctx;
62                         try {
63                                 string path = Environment.GetFolderPath (Environment.SpecialFolder.ApplicationData);
64                                 path = Path.Combine (path, "xbuild");
65                                 if (!Directory.Exists (path))
66                                         Directory.CreateDirectory (path);
67                                 cacheFile = Path.Combine (path, "pkgconfig-cache-" + CACHE_VERSION + ".xml");
68                                 
69                                 if (File.Exists (cacheFile))
70                                         Load ();
71                                 
72                         } catch (Exception ex) {
73                                 ctx.ReportError ("pc file cache could not be loaded.", ex);
74                         }
75                 }
76                 
77                 // Updates the pkg-config index, using the default search directories
78                 public void Update ()
79                 {
80                         string pkgConfigPath = Environment.GetEnvironmentVariable ("PKG_CONFIG_PATH");
81                         string pkgConfigDir = Environment.GetEnvironmentVariable ("PKG_CONFIG_LIBDIR");
82                         Update (GetPkgconfigPaths (null, pkgConfigPath, pkgConfigDir));
83                 }
84
85                 // Updates the pkg-config index, looking for .pc files in the provided directories
86                 public void Update (IEnumerable<string> pkgConfigDirs)
87                 {
88                         foreach (string pcdir in pkgConfigDirs) {
89                                 foreach (string pcfile in Directory.GetFiles (pcdir, "*.pc"))
90                                         GetPackageInfo (pcfile);
91                         }
92                         Save ();
93                 }
94                 
95                 // Returns the location of an assembly, given the full name
96                 public PackageAssemblyInfo GetAssemblyLocation (string fullName)
97                 {
98                         lock (infos) {
99                                 if (assemblyLocations == null) {
100                                         // Populate on demand
101                                         assemblyLocations = new Dictionary<string, PackageAssemblyInfo> ();
102                                         foreach (PackageInfo info in infos.Values) {
103                                                 if (info.IsValidPackage) {
104                                                         foreach (PackageAssemblyInfo asm in info.Assemblies)
105                                                                 assemblyLocations [NormalizeAsmName (asm.FullName)] = asm;
106                                                 }
107                                         }
108                                 }
109                         }
110                         // This collection is read-only once built, so there is no need for a lock
111                         PackageAssemblyInfo pasm;
112                         assemblyLocations.TryGetValue (NormalizeAsmName (fullName), out pasm);
113                         return pasm;
114                 }
115                 
116                 public IEnumerable<PackageAssemblyInfo> ResolveAssemblyName (string name)
117                 {
118                         foreach (PackageInfo pinfo in infos.Values) {
119                                 if (pinfo.IsValidPackage) {
120                                         foreach (PackageAssemblyInfo asm in pinfo.Assemblies) {
121                                                 if (asm.Name == name)
122                                                         yield return asm;
123                                         }
124                                 }
125                         }
126                 }
127                 
128                 // Returns information about a .pc file
129                 public PackageInfo GetPackageInfo (string file)
130                 {
131                         PackageInfo info;
132                         file = Path.GetFullPath (file);
133                         
134                         DateTime wtime = File.GetLastWriteTime (file);
135                         
136                         lock (infos) {
137                                 if (infos.TryGetValue (file, out info)) {
138                                         if (info.LastWriteTime == wtime)
139                                                 return info;
140                                 }
141                         }
142
143                         try {
144                                 info = ParsePackageInfo (file);
145                         } catch (Exception ex) {
146                                 ctx.ReportError ("Error while parsing .pc file", ex);
147                                 info = new PackageInfo ();
148                         }
149                         
150                         lock (infos) {
151                                 if (!info.IsValidPackage)
152                                         info = new PackageInfo (); // Create a default empty instance
153                                 info.LastWriteTime = wtime;
154                                 infos [file] = info;
155                                 hasChanges = true;
156                         }
157                         
158                         return info;
159                 }
160                 
161                 FileStream OpenFile (FileAccess access)
162                 {
163                         int retries = 6;
164                         FileMode mode = access == FileAccess.Read ? FileMode.Open : FileMode.Create;
165                         Exception lastException = null;
166                         
167                         while (retries > 0) {
168                                 try {
169                                         return new FileStream (cacheFile, mode, access, FileShare.None);
170                                 } catch (Exception ex) {
171                                         // the file may be locked by another app. Wait a bit and try again
172                                         lastException = ex;
173                                         System.Threading.Thread.Sleep (200);
174                                         retries--;
175                                 }
176                         }
177                         ctx.ReportError ("File could not be opened: " + cacheFile, lastException);
178                         return null;
179                 }
180                 
181                 void Load ()
182                 {
183                         // The serializer can't be used because this file is reused in xbuild
184                         using (FileStream fs = OpenFile (FileAccess.Read)) {
185                                 if (fs == null)
186                                         return;
187                                 XmlTextReader xr = new XmlTextReader (fs);
188                                 xr.MoveToContent ();
189                                 xr.ReadStartElement ();
190                                 xr.MoveToContent ();
191                                 
192                                 while (xr.NodeType == XmlNodeType.Element)
193                                         ReadPackage (xr);
194                         }
195                 }
196                 
197                 public void Save ()
198                 {
199                         // The serializer can't be used because this file is reused in xbuild
200                         lock (infos) {
201                                 if (!hasChanges)
202                                         return;
203                                 
204                                 using (FileStream fs = OpenFile (FileAccess.Write)) {
205                                         if (fs == null)
206                                                 return;
207                                         XmlTextWriter tw = new XmlTextWriter (new StreamWriter (fs));
208                                         tw.Formatting = Formatting.Indented;
209                                         
210                                         tw.WriteStartElement ("PcFileCache");
211                                         foreach (KeyValuePair<string,PackageInfo> file in infos) {
212                                                 WritePackage (tw, file.Key, file.Value);
213                                         }
214                                         tw.WriteEndElement (); // PcFileCache
215                                         tw.Flush ();
216                                         
217                                         hasChanges = false;
218                                 }
219                         }
220                 }
221                 
222                 void WritePackage (XmlTextWriter tw, string file, PackageInfo pinfo)
223                 {
224                         tw.WriteStartElement ("File");
225                         tw.WriteAttributeString ("path", file);
226                         tw.WriteAttributeString ("lastWriteTime", XmlConvert.ToString (pinfo.LastWriteTime, XmlDateTimeSerializationMode.Local));
227                         
228                         if (pinfo.IsValidPackage) {
229                                 if (pinfo.Name != null)
230                                         tw.WriteAttributeString ("name", pinfo.Name);
231                                 if (pinfo.Version != null)
232                                         tw.WriteAttributeString ("version", pinfo.Version);
233                                 if (!string.IsNullOrEmpty (pinfo.Description))
234                                         tw.WriteAttributeString ("description", pinfo.Description);
235                                 if (!pinfo.IsGacPackage)
236                                         tw.WriteAttributeString ("gacPackage", "false");
237                                 if (pinfo.CustomData != null) {
238                                         foreach (KeyValuePair<string,string> cd in pinfo.CustomData)
239                                                 tw.WriteAttributeString (cd.Key, cd.Value);
240                                 }
241                                 foreach (PackageAssemblyInfo asm in pinfo.Assemblies) {
242                                         tw.WriteStartElement ("Assembly");
243                                         tw.WriteAttributeString ("name", asm.Name);
244                                         tw.WriteAttributeString ("version", asm.Version);
245                                         tw.WriteAttributeString ("culture", asm.Culture);
246                                         tw.WriteAttributeString ("publicKeyToken", asm.PublicKeyToken);
247                                         tw.WriteAttributeString ("file", asm.File);
248                                         tw.WriteEndElement (); // Assembly
249                                 }
250                         }
251                         tw.WriteEndElement (); // File
252                 }
253                 
254                 void ReadPackage (XmlReader tr)
255                 {
256                         PackageInfo pinfo = new PackageInfo ();
257                         string file = null;
258                         
259                         tr.MoveToFirstAttribute ();
260                         do {
261                                 switch (tr.LocalName) {
262                                         case "path": file = tr.Value; break;
263                                         case "lastWriteTime": pinfo.LastWriteTime = XmlConvert.ToDateTime (tr.Value, XmlDateTimeSerializationMode.Local); break;
264                                         case "name": pinfo.Name = tr.Value; break;
265                                         case "version": pinfo.Version = tr.Value; break;
266                                         case "description": pinfo.Description = tr.Value; break;
267                                         case "gacPackage": pinfo.IsGacPackage = tr.Value != "false"; break;
268                                         default: pinfo.SetData (tr.LocalName, tr.Value); break;
269                                 }
270                         } while (tr.MoveToNextAttribute ());
271                         
272                         tr.MoveToElement ();
273                         
274                         if (!tr.IsEmptyElement) {
275                                 tr.ReadStartElement ();
276                                 tr.MoveToContent ();
277                                 while (tr.NodeType == XmlNodeType.Element) {
278                                         PackageAssemblyInfo asm = new PackageAssemblyInfo ();
279                                         asm.Name = tr.GetAttribute ("name");
280                                         asm.Version = tr.GetAttribute ("version");
281                                         asm.Culture = tr.GetAttribute ("culture");
282                                         asm.PublicKeyToken = tr.GetAttribute ("publicKeyToken");
283                                         asm.File = tr.GetAttribute ("file");
284                                         if (pinfo.Assemblies == null)
285                                                 pinfo.Assemblies = new List<PackageAssemblyInfo> ();
286                                         asm.ParentPackage = pinfo;
287                                         pinfo.Assemblies.Add (asm);
288                                         tr.Read ();
289                                         tr.MoveToContent ();
290                                 }
291                                 tr.MoveToContent ();
292                                 tr.ReadEndElement ();
293                         } else
294                                 tr.Read ();
295                         tr.MoveToContent ();
296                         
297                         if (!pinfo.IsValidPackage || ctx.IsCustomDataComplete (file, pinfo))
298                                 infos [file] = pinfo;
299                 }
300                 
301                 public object SyncRoot {
302                         get { return infos; }
303                 }
304                 
305                 
306                 PackageInfo ParsePackageInfo (string pcfile)
307                 {
308                         PackageInfo pinfo = new PackageInfo ();
309                         pinfo.Name = Path.GetFileNameWithoutExtension (pcfile);
310                         List<string> fullassemblies = null;
311                         bool gacPackageSet = false;
312                         
313                         PcFile file = new PcFile ();
314                         file.Load (pcfile);
315                         
316                         if (file.HasErrors)
317                                 return pinfo;
318                         
319                         if (file.Libs != null && file.Libs.IndexOf (".dll") != -1) {
320                                 if (file.Libs.IndexOf ("-lib:") != -1 || file.Libs.IndexOf ("/lib:") != -1) {
321                                         fullassemblies = GetAssembliesWithLibInfo (file.Libs);
322                                 } else {
323                                         fullassemblies = GetAssembliesWithoutLibInfo (file.Libs);
324                                 }
325                         }
326                         
327                         string value = file.GetVariable ("Libraries");
328                         if (!string.IsNullOrEmpty (value))
329                                 fullassemblies = GetAssembliesFromLibrariesVar (value);
330                         
331                         pinfo.Version = file.Version;
332                         pinfo.Description = file.Description;
333
334                         value = file.GetVariable ("GacPackage");
335                         if (value != null) {
336                                 value = value.ToLower ();
337                                 pinfo.IsGacPackage = value == "yes" || value == "true";
338                                 gacPackageSet = true;
339                         }
340         
341                         if (fullassemblies == null)
342                                 return pinfo;
343                         
344                         string pcDir = Path.GetDirectoryName (pcfile);
345                         string monoPrefix = Path.GetDirectoryName (Path.GetDirectoryName (pcDir));
346                         monoPrefix = Path.GetFullPath (monoPrefix + Path.DirectorySeparatorChar + "lib" + Path.DirectorySeparatorChar + "mono" + Path.DirectorySeparatorChar);
347
348                         List<PackageAssemblyInfo> list = new List<PackageAssemblyInfo> ();
349                         foreach (string assembly in fullassemblies) {
350                                 string asm;
351                                 if (Path.IsPathRooted (assembly))
352                                         asm = Path.GetFullPath (assembly);
353                                 else {
354                                         if (Path.GetDirectoryName (assembly).Length == 0) {
355                                                 asm = assembly;
356                                         } else {
357                                                 asm = Path.GetFullPath (Path.Combine (pcDir, assembly));
358                                         }
359                                 }
360                                 if (File.Exists (asm)) {
361                                         PackageAssemblyInfo pi = new PackageAssemblyInfo ();
362                                         pi.File = asm;
363                                         pi.ParentPackage = pinfo;
364                                         pi.UpdateFromFile (pi.File);
365                                         list.Add (pi);
366                                         if (!gacPackageSet && !asm.StartsWith (monoPrefix) && Path.IsPathRooted (asm)) {
367                                                 // Assembly installed outside $(prefix)/lib/mono. It is most likely not a gac package.
368                                                 gacPackageSet = true;
369                                                 pinfo.IsGacPackage = false;
370                                         }
371                                 }
372                         }
373                         pinfo.Assemblies = list;
374                         ctx.StoreCustomData (file, pinfo);
375                         
376                         return pinfo;
377                 }
378                 
379                 private List<string> GetAssembliesWithLibInfo (string line)
380                 {
381                         List<string> references = new List<string> ();
382                         List<string> libdirs = new List<string> ();
383                         List<string> retval = new List<string> ();
384                         foreach (string piece in line.Split (' ')) {
385                                 if (piece.ToLower ().Trim ().StartsWith ("/r:") || piece.ToLower ().Trim ().StartsWith ("-r:")) {
386                                         references.Add (piece.Substring (3).Trim ());
387                                 } else if (piece.ToLower ().Trim ().StartsWith ("/lib:") || piece.ToLower ().Trim ().StartsWith ("-lib:")) {
388                                         libdirs.Add (piece.Substring (5).Trim ());
389                                 }
390                         }
391         
392                         foreach (string refrnc in references) {
393                                 foreach (string libdir in libdirs) {
394                                         if (File.Exists (libdir + Path.DirectorySeparatorChar + refrnc)) {
395                                                 retval.Add (libdir + Path.DirectorySeparatorChar + refrnc);
396                                         }
397                                 }
398                         }
399         
400                         return retval;
401                 }
402                 
403                 List<string> GetAssembliesFromLibrariesVar (string line)
404                 {
405                         List<string> references = new List<string> ();
406                         foreach (string reference in line.Split (' ')) {
407                                 if (!string.IsNullOrEmpty (reference))
408                                         references.Add (reference);
409                         }
410                         return references;
411                 }
412         
413                 private List<string> GetAssembliesWithoutLibInfo (string line)
414                 {
415                         List<string> references = new List<string> ();
416                         foreach (string reference in line.Split (' ')) {
417                                 if (reference.ToLower ().Trim ().StartsWith ("/r:") || reference.ToLower ().Trim ().StartsWith ("-r:")) {
418                                         string final_ref = reference.Substring (3).Trim ();
419                                         references.Add (final_ref);
420                                 }
421                         }
422                         return references;
423                 }
424                 
425                 public IEnumerable<string> GetPkgconfigPaths (string prefix, string pkgConfigPath, string pkgConfigLibdir)
426                 {
427                         char[] sep = new char[] { Path.PathSeparator };
428                         
429                         string[] pkgConfigPaths = null;
430                         if (!String.IsNullOrEmpty (pkgConfigPath)) {
431                                 pkgConfigPaths = pkgConfigPath.Split (sep, StringSplitOptions.RemoveEmptyEntries);
432                                 if (pkgConfigPaths.Length == 0)
433                                         pkgConfigPaths = null;
434                         }
435                         
436                         string[] pkgConfigLibdirs = null;
437                         if (!String.IsNullOrEmpty (pkgConfigLibdir)) {
438                                 pkgConfigLibdirs = pkgConfigLibdir.Split (sep, StringSplitOptions.RemoveEmptyEntries);
439                                 if (pkgConfigLibdirs.Length == 0)
440                                         pkgConfigLibdirs = null;
441                         }
442                         
443                         if (prefix == null)
444                                 prefix = PathUp (typeof (int).Assembly.Location, 4);
445                         
446                         IEnumerable<string> paths = GetUnfilteredPkgConfigDirs (pkgConfigPaths, pkgConfigLibdirs, new string [] { prefix });
447                         return NormaliseAndFilterPaths (paths, Environment.CurrentDirectory);
448                 }
449                 
450                 IEnumerable<string> GetUnfilteredPkgConfigDirs (IEnumerable<string> pkgConfigPaths, IEnumerable<string> pkgConfigLibdirs, IEnumerable<string> systemPrefixes)
451                 {
452                         if (pkgConfigPaths != null) {
453                                 foreach (string dir in pkgConfigPaths)
454                                         yield return dir;
455                         }
456                         
457                         if (pkgConfigLibdirs != null) {
458                                 foreach (string dir in pkgConfigLibdirs)
459                                         yield return dir;
460                         } else if (systemPrefixes != null) {
461                                 string[] suffixes = new string [] {
462                                         Path.Combine ("lib", "pkgconfig"),
463                                         Path.Combine ("lib64", "pkgconfig"),
464                                         Path.Combine ("libdata", "pkgconfig"),
465                                         Path.Combine ("share", "pkgconfig"),
466                                 };
467                                 foreach (string prefix in systemPrefixes)
468                                         foreach (string suffix in suffixes)
469                                                 yield return Path.Combine (prefix, suffix);
470                         }
471                 }
472                 
473                 IEnumerable<string> NormaliseAndFilterPaths (IEnumerable<string> paths, string workingDirectory)
474                 {
475                         HashSet<string> filtered = new HashSet<string> ();
476                         foreach (string p in paths) {
477                                 string path = p;
478                                 if (!Path.IsPathRooted (path))
479                                         path = Path.Combine (workingDirectory, path);
480                                 path = Path.GetFullPath (path);
481                                 if (!filtered.Add (path))
482                                         continue;
483                                 try {
484                                         if (!Directory.Exists (path))
485                                                 continue;
486                                 } catch (IOException ex) {
487                                         ctx.ReportError ("Error checking for directory '" + path + "'.", ex);
488                                 }
489                                 yield return path;
490                         }
491                 }
492                 
493                 static string PathUp (string path, int up)
494                 {
495                         if (up == 0)
496                                 return path;
497                         for (int i = path.Length -1; i >= 0; i--) {
498                                 if (path[i] == Path.DirectorySeparatorChar) {
499                                         up--;
500                                         if (up == 0)
501                                                 return path.Substring (0, i);
502                                 }
503                         }
504                         return null;
505                 }
506                 
507                 public static string NormalizeAsmName (string name)
508                 {
509                         int i = name.ToLower ().IndexOf (", publickeytoken=null");
510                         if (i != -1)
511                                 name = name.Substring (0, i).Trim ();
512                         i = name.ToLower ().IndexOf (", processorarchitecture=");
513                         if (i != -1)
514                                 name = name.Substring (0, i).Trim ();
515                         return name;
516                 }
517         }
518
519         internal class PcFile
520         {
521                 Dictionary<string,string> variables = new Dictionary<string, string> ();
522                 
523                 public string FilePath { get; set; }
524                 public string Name { get; set; }
525                 public string Description { get; set; }
526                 public string Version { get; set; }
527                 public string Libs { get; set; }
528                 public bool HasErrors { get; set; }
529                 
530                 public string GetVariable (string varName)
531                 {
532                         string val;
533                         variables.TryGetValue (varName, out val);
534                         return val;
535                 }
536                 
537                 public void Load (string pcfile)
538                 {
539                         FilePath = pcfile;
540                         variables.Add ("pcfiledir", Path.GetDirectoryName (pcfile));
541                         using (StreamReader reader = new StreamReader (pcfile)) {
542                                 string line;
543                                 while ((line = reader.ReadLine ()) != null) {
544                                         int i = line.IndexOf (':');
545                                         int j = line.IndexOf ('=');
546                                         int k = System.Math.Min (i != -1 ? i : int.MaxValue, j != -1 ? j : int.MaxValue);
547                                         if (k == int.MaxValue)
548                                                 continue;
549                                         string var = line.Substring (0, k).Trim ();
550                                         string value = line.Substring (k + 1).Trim ();
551                                         value = Evaluate (value);
552                                         
553                                         if (k == j) {
554                                                 // Is variable
555                                                 variables [var] = value;
556                                         }
557                                         else {
558                                                 switch (var) {
559                                                         case "Name": Name = value; break;
560                                                         case "Description": Description = value; break;
561                                                         case "Version": Version = value; break;
562                                                         case "Libs": Libs = value; break;
563                                                 }
564                                         }
565                                 }
566                         }
567                 }
568                 
569                 string Evaluate (string value)
570                 {
571                         int i = value.IndexOf ("${");
572                         if (i == -1)
573                                 return value;
574
575                         StringBuilder sb = new StringBuilder ();
576                         int last = 0;
577                         while (i != -1 && i < value.Length) {
578                                 sb.Append (value.Substring (last, i - last));
579                                 if (i == 0 || value [i - 1] != '$') {
580                                         // Evaluate if var is not escaped
581                                         i += 2;
582                                         int n = value.IndexOf ('}', i);
583                                         if (n == -1 || n == i) {
584                                                 // Closing bracket not found or empty name
585                                                 HasErrors = true;
586                                                 return value;
587                                         }
588                                         string rname = value.Substring (i, n - i);
589                                         string rval;
590                                         if (variables.TryGetValue (rname, out rval))
591                                                 sb.Append (rval);
592                                         else {
593                                                 HasErrors = true;
594                                                 return value;
595                                         }
596                                         i = n + 1;
597                                         last = i;
598                                 } else
599                                         last = i++;
600                                 
601                                 if (i < value.Length - 1)
602                                         i = value.IndexOf ("${", i);
603                         }
604                         sb.Append (value.Substring (last, value.Length - last));
605                         return sb.ToString ();
606                 }
607         }
608         
609         internal class PackageInfo
610         {
611                 Dictionary<string,string> customData;
612                 
613                 public PackageInfo ()
614                 {
615                         IsGacPackage = true;
616                 }
617
618                 public string Name { get; set; }
619                 
620                 public bool IsGacPackage { get; set; }
621                 
622                 public string Version { get; set; }
623                 
624                 public string Description { get; set; }
625                 
626                 internal List<PackageAssemblyInfo> Assemblies { get; set; }
627                 
628                 public string GetData (string name)
629                 {
630                         if (customData == null)
631                                 return null;
632                         string res;
633                         customData.TryGetValue (name, out res);
634                         return res;
635                 }
636                 
637                 public void SetData (string name, string value)
638                 {
639                         if (customData == null)
640                                 customData = new Dictionary<string, string> ();
641                         customData [name] = value;
642                 }
643                 
644                 internal Dictionary<string,string> CustomData {
645                         get { return customData; }
646                 }
647                 
648                 internal DateTime LastWriteTime { get; set; }
649                 
650                 internal bool IsValidPackage {
651                         get { return Assemblies != null && Assemblies.Count > 0; }
652                 }
653                 
654                 internal bool HasCustomData {
655                         get { return customData != null && customData.Count > 0; }
656                 }
657         }
658         
659         class PackageAssemblyInfo
660         {
661                 public string File { get; set; }
662                 
663                 public string Name;
664                 
665                 public string Version;
666                 
667                 public string Culture;
668                 
669                 public string PublicKeyToken;
670                 
671                 public string FullName {
672                         get {
673                                 string fn = Name + ", Version=" + Version;
674                                 if (!string.IsNullOrEmpty (Culture))
675                                         fn += ", Culture=" + Culture;
676                                 if (!string.IsNullOrEmpty (PublicKeyToken))
677                                         fn += ", PublicKeyToken=" + PublicKeyToken;
678                                 return fn;
679                         }
680                 }
681                 
682                 public PackageInfo ParentPackage { get; set; }
683                 
684                 public void UpdateFromFile (string file)
685                 {
686                         Update (System.Reflection.AssemblyName.GetAssemblyName (file));
687                 }
688                 
689                 public void Update (System.Reflection.AssemblyName aname)
690                 {
691                         Name = aname.Name;
692                         Version = aname.Version.ToString ();
693                         if (aname.CultureInfo != null) {
694                                 if (aname.CultureInfo.LCID == System.Globalization.CultureInfo.InvariantCulture.LCID)
695                                         Culture = "neutral";
696                                 else
697                                         Culture = aname.CultureInfo.Name;
698                         }
699                         string fn = aname.ToString ();
700                         string key = "publickeytoken=";
701                         int i = fn.ToLower().IndexOf (key) + key.Length;
702                         int j = fn.IndexOf (',', i);
703                         if (j == -1) j = fn.Length;
704                         PublicKeyToken = fn.Substring (i, j - i);
705                 }
706         }
707 }