5 // Lluis Sanchez Gual <lluis@novell.com>
7 // Copyright (c) 2009 Novell, Inc (http://www.novell.com)
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:
16 // The above copyright notice and this permission notice shall be included in
17 // all copies or substantial portions of the Software.
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
31 using System.Collections.Generic;
33 // IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT
34 // This code is shared with xbuild, which has to build with .NET 2.0,
35 // so no c# 3.0 syntax is allowed here.
37 namespace Mono.PkgConfig
39 internal interface IPcFileCacheContext<TP> where TP:PackageInfo, new()
41 // In the implementation of this method, the host application can extract
42 // information from the pc file and store it in the PackageInfo object
43 void StoreCustomData (PcFile pcfile, TP pkg);
45 // Should return false if the provided package does not have required
47 bool IsCustomDataComplete (string pcfile, TP pkg);
49 // Called to report errors
50 void ReportError (string message, Exception ex);
53 internal interface IPcFileCacheContext: IPcFileCacheContext<PackageInfo>
57 internal abstract class PcFileCache: PcFileCache<PackageInfo>
59 public PcFileCache (IPcFileCacheContext ctx): base (ctx)
64 internal abstract class PcFileCache<TP> where TP:PackageInfo, new()
66 const string CACHE_VERSION = "2";
67 const string MacOSXExternalPkgConfigDir = "/Library/Frameworks/Mono.framework/External/pkgconfig";
69 Dictionary<string, TP> infos = new Dictionary<string, TP> ();
70 Dictionary<string, List<TP>> filesByFolder = new Dictionary<string, List<TP>> ();
74 IPcFileCacheContext<TP> ctx;
75 IEnumerable<string> defaultPaths;
77 public PcFileCache (IPcFileCacheContext<TP> ctx)
81 string path = CacheDirectory;
82 if (!Directory.Exists (path))
83 Directory.CreateDirectory (path);
84 cacheFile = Path.Combine (path, "pkgconfig-cache-" + CACHE_VERSION + ".xml");
86 if (File.Exists (cacheFile))
89 } catch (Exception ex) {
90 ctx.ReportError ("pc file cache could not be loaded.", ex);
94 protected abstract string CacheDirectory { get; }
96 // Updates the pkg-config index, using the default search directories
99 Update (GetDefaultPaths ());
102 // Updates the pkg-config index, looking for .pc files in the provided directories
103 public void Update (IEnumerable<string> pkgConfigDirs)
105 foreach (string pcdir in pkgConfigDirs) {
106 foreach (string pcfile in Directory.GetFiles (pcdir, "*.pc"))
107 GetPackageInfo (pcfile);
112 public IEnumerable<TP> GetPackages ()
114 return GetPackages (null);
117 public IEnumerable<TP> GetPackages (IEnumerable<string> pkgConfigDirs)
119 if (pkgConfigDirs == null)
120 pkgConfigDirs = GetDefaultPaths ();
122 foreach (string sp in pkgConfigDirs) {
124 if (filesByFolder.TryGetValue (Path.GetFullPath (sp), out list)) {
125 foreach (TP p in list)
131 public TP GetPackageInfoByName (string name)
133 return GetPackageInfoByName (name, null);
136 public TP GetPackageInfoByName (string name, IEnumerable<string> pkgConfigDirs)
138 foreach (TP p in GetPackages (pkgConfigDirs))
144 // Returns information about a .pc file
145 public TP GetPackageInfo (string file)
147 TP info, oldInfo = null;
148 file = Path.GetFullPath (file);
150 DateTime wtime = File.GetLastWriteTime (file);
153 if (infos.TryGetValue (file, out info)) {
154 if (info.LastWriteTime == wtime)
161 info = ParsePackageInfo (file);
162 } catch (Exception ex) {
163 ctx.ReportError ("Error while parsing .pc file: " + file, ex);
168 if (!info.IsValidPackage)
169 info = new TP (); // Create a default empty instance
170 info.LastWriteTime = wtime;
171 Add (file, info, oldInfo);
178 void Add (string file, TP info, TP replacedInfo)
181 string dir = Path.GetFullPath (Path.GetDirectoryName (file));
183 if (!filesByFolder.TryGetValue (dir, out list)) {
184 list = new List<TP> ();
185 filesByFolder [dir] = list;
187 if (replacedInfo != null) {
188 int i = list.IndexOf (replacedInfo);
197 FileStream OpenFile (FileAccess access)
200 FileMode mode = access == FileAccess.Read ? FileMode.Open : FileMode.Create;
201 Exception lastException = null;
203 while (retries > 0) {
205 return new FileStream (cacheFile, mode, access, FileShare.None);
206 } catch (Exception ex) {
207 // the file may be locked by another app. Wait a bit and try again
209 System.Threading.Thread.Sleep (200);
213 ctx.ReportError ("File could not be opened: " + cacheFile, lastException);
219 // The serializer can't be used because this file is reused in xbuild
220 using (FileStream fs = OpenFile (FileAccess.Read)) {
223 XmlTextReader xr = new XmlTextReader (fs);
225 xr.ReadStartElement ();
228 while (xr.NodeType == XmlNodeType.Element)
235 // The serializer can't be used because this file is reused in xbuild
240 using (FileStream fs = OpenFile (FileAccess.Write)) {
243 XmlTextWriter tw = new XmlTextWriter (new StreamWriter (fs));
244 tw.Formatting = Formatting.Indented;
246 tw.WriteStartElement ("PcFileCache");
247 foreach (KeyValuePair<string,TP> file in infos) {
248 WritePackage (tw, file.Key, file.Value);
250 tw.WriteEndElement (); // PcFileCache
258 void WritePackage (XmlTextWriter tw, string file, TP pinfo)
260 tw.WriteStartElement ("File");
261 tw.WriteAttributeString ("path", file);
262 tw.WriteAttributeString ("lastWriteTime", XmlConvert.ToString (pinfo.LastWriteTime, XmlDateTimeSerializationMode.Local));
264 if (pinfo.IsValidPackage) {
265 if (pinfo.Name != null)
266 tw.WriteAttributeString ("name", pinfo.Name);
267 if (pinfo.Version != null)
268 tw.WriteAttributeString ("version", pinfo.Version);
269 if (!string.IsNullOrEmpty (pinfo.Description))
270 tw.WriteAttributeString ("description", pinfo.Description);
271 if (!string.IsNullOrEmpty (pinfo.Requires))
272 tw.WriteAttributeString ("requires", pinfo.Requires);
273 if (pinfo.CustomData != null) {
274 foreach (KeyValuePair<string,string> cd in pinfo.CustomData)
275 tw.WriteAttributeString (cd.Key, cd.Value);
277 WritePackageContent (tw, file, pinfo);
279 tw.WriteEndElement (); // File
282 protected virtual void WritePackageContent (XmlTextWriter tw, string file, TP pinfo)
286 void ReadPackage (XmlReader tr)
288 TP pinfo = new TP ();
291 tr.MoveToFirstAttribute ();
293 switch (tr.LocalName) {
294 case "path": file = tr.Value; break;
295 case "lastWriteTime": pinfo.LastWriteTime = XmlConvert.ToDateTime (tr.Value, XmlDateTimeSerializationMode.Local); break;
296 case "name": pinfo.Name = tr.Value; break;
297 case "version": pinfo.Version = tr.Value; break;
298 case "description": pinfo.Description = tr.Value; break;
299 case "requires": pinfo.Requires = tr.Value; break;
300 default: pinfo.SetData (tr.LocalName, tr.Value); break;
302 } while (tr.MoveToNextAttribute ());
306 if (!tr.IsEmptyElement) {
307 tr.ReadStartElement ();
309 ReadPackageContent (tr, pinfo);
311 tr.ReadEndElement ();
316 if (!pinfo.IsValidPackage || ctx.IsCustomDataComplete (file, pinfo))
317 Add (file, pinfo, null);
320 protected virtual void ReadPackageContent (XmlReader tr, TP pinfo)
324 public object SyncRoot {
325 get { return infos; }
329 TP ParsePackageInfo (string pcfile)
331 PcFile file = new PcFile ();
334 TP pinfo = new TP ();
335 pinfo.Name = Path.GetFileNameWithoutExtension (file.FilePath);
337 if (!file.HasErrors) {
338 pinfo.Version = file.Version;
339 pinfo.Description = file.Description;
340 pinfo.Requires = file.Requires;
341 ParsePackageInfo (file, pinfo);
342 if (pinfo.IsValidPackage)
343 ctx.StoreCustomData (file, pinfo);
348 protected virtual void ParsePackageInfo (PcFile file, TP pinfo)
352 IEnumerable<string> GetDefaultPaths ()
354 if (defaultPaths == null) {
355 // For mac osx, look in the 'External' dir on macosx,
357 string pkgConfigPath = String.Format ("{0}:{1}",
358 Mono.XBuild.Utilities.MSBuildUtils.RunningOnMac ? MacOSXExternalPkgConfigDir : String.Empty,
359 Environment.GetEnvironmentVariable ("PKG_CONFIG_PATH") ?? String.Empty);
361 string pkgConfigDir = Environment.GetEnvironmentVariable ("PKG_CONFIG_LIBDIR");
362 defaultPaths = GetPkgconfigPaths (null, pkgConfigPath, pkgConfigDir);
367 public IEnumerable<string> GetPkgconfigPaths (string prefix, string pkgConfigPath, string pkgConfigLibdir)
369 char[] sep = new char[] { Path.PathSeparator };
371 string[] pkgConfigPaths = null;
372 if (!String.IsNullOrEmpty (pkgConfigPath)) {
373 pkgConfigPaths = pkgConfigPath.Split (sep, StringSplitOptions.RemoveEmptyEntries);
374 if (pkgConfigPaths.Length == 0)
375 pkgConfigPaths = null;
378 string[] pkgConfigLibdirs = null;
379 if (!String.IsNullOrEmpty (pkgConfigLibdir)) {
380 pkgConfigLibdirs = pkgConfigLibdir.Split (sep, StringSplitOptions.RemoveEmptyEntries);
381 if (pkgConfigLibdirs.Length == 0)
382 pkgConfigLibdirs = null;
386 prefix = PathUp (typeof (int).Assembly.Location, 4);
388 IEnumerable<string> paths = GetUnfilteredPkgConfigDirs (pkgConfigPaths, pkgConfigLibdirs, new string [] { prefix });
389 return NormaliseAndFilterPaths (paths, Environment.CurrentDirectory);
392 IEnumerable<string> GetUnfilteredPkgConfigDirs (IEnumerable<string> pkgConfigPaths, IEnumerable<string> pkgConfigLibdirs, IEnumerable<string> systemPrefixes)
394 if (pkgConfigPaths != null) {
395 foreach (string dir in pkgConfigPaths)
399 if (pkgConfigLibdirs != null) {
400 foreach (string dir in pkgConfigLibdirs)
402 } else if (systemPrefixes != null) {
403 string[] suffixes = new string [] {
404 //FIXME: is this the correct order? share should be before lib but not sure about others.
405 Path.Combine ("share", "pkgconfig"),
406 Path.Combine ("lib", "pkgconfig"),
407 Path.Combine ("lib64", "pkgconfig"),
408 Path.Combine ("libdata", "pkgconfig"),
410 foreach (string prefix in systemPrefixes)
411 foreach (string suffix in suffixes)
412 yield return Path.Combine (prefix, suffix);
416 IEnumerable<string> NormaliseAndFilterPaths (IEnumerable<string> paths, string workingDirectory)
418 Dictionary<string,string> filtered = new Dictionary<string,string> ();
419 foreach (string p in paths) {
421 if (!Path.IsPathRooted (path))
422 path = Path.Combine (workingDirectory, path);
423 path = Path.GetFullPath (path);
424 if (filtered.ContainsKey (path))
426 filtered.Add (path,path);
428 if (!Directory.Exists (path))
430 } catch (IOException ex) {
431 ctx.ReportError ("Error checking for directory '" + path + "'.", ex);
437 static string PathUp (string path, int up)
441 for (int i = path.Length -1; i >= 0; i--) {
442 if (path[i] == Path.DirectorySeparatorChar) {
445 return path.Substring (0, i);
452 internal class PcFile
454 Dictionary<string,string> variables = new Dictionary<string, string> ();
457 public string Description {
458 get { return description; }
459 set { description = value; }
463 public string FilePath {
464 get { return filePath; }
465 set { filePath = value; }
469 public bool HasErrors {
470 get { return hasErrors; }
471 set { hasErrors = value; }
477 set { libs = value; }
483 set { name = value; }
487 public string Version {
488 get { return version; }
489 set { version = value; }
493 public string Requires {
494 get { return requires; }
495 set { requires = value; }
498 public string GetVariable (string varName)
501 variables.TryGetValue (varName, out val);
505 public void Load (string pcfile)
508 variables.Add ("pcfiledir", Path.GetDirectoryName (pcfile));
509 using (StreamReader reader = new StreamReader (pcfile)) {
511 while ((line = reader.ReadLine ()) != null) {
512 int i = line.IndexOf (':');
513 int j = line.IndexOf ('=');
514 int k = System.Math.Min (i != -1 ? i : int.MaxValue, j != -1 ? j : int.MaxValue);
515 if (k == int.MaxValue)
517 string var = line.Substring (0, k).Trim ();
518 string value = line.Substring (k + 1).Trim ();
519 value = Evaluate (value);
523 variables [var] = value;
527 case "Name": Name = value; break;
528 case "Description": Description = value; break;
529 case "Version": Version = value; break;
530 case "Libs": Libs = value; break;
531 case "Requires": Requires = value; break;
538 string Evaluate (string value)
540 int i = value.IndexOf ("${");
544 StringBuilder sb = new StringBuilder ();
546 while (i != -1 && i < value.Length) {
547 sb.Append (value.Substring (last, i - last));
548 if (i == 0 || value [i - 1] != '$') {
549 // Evaluate if var is not escaped
551 int n = value.IndexOf ('}', i);
552 if (n == -1 || n == i) {
553 // Closing bracket not found or empty name
557 string rname = value.Substring (i, n - i);
559 if (variables.TryGetValue (rname, out rval))
570 if (i < value.Length)
571 i = value.IndexOf ("${", i);
573 sb.Append (value.Substring (last, value.Length - last));
574 return sb.ToString ();
578 internal class PackageInfo
580 Dictionary<string,string> customData;
581 DateTime lastWriteTime;
586 set { name = value; }
590 public string Version {
591 get { return version; }
592 set { version = value; }
596 public string Description {
597 get { return description; }
598 set { description = value; }
602 public string Requires {
603 get { return requires; }
604 set { requires = value; }
607 public string GetData (string name)
609 if (customData == null)
612 customData.TryGetValue (name, out res);
616 public void SetData (string name, string value)
618 if (customData == null)
619 customData = new Dictionary<string, string> ();
620 customData [name] = value;
623 public void RemoveData (string name)
625 if (customData != null)
626 customData.Remove (name);
629 internal Dictionary<string,string> CustomData {
630 get { return customData; }
633 internal DateTime LastWriteTime {
634 get { return lastWriteTime; }
635 set { lastWriteTime = value; }
638 internal bool HasCustomData {
639 get { return customData != null && customData.Count > 0; }
642 internal protected virtual bool IsValidPackage {
643 get { return HasCustomData; }