diff options
Diffstat (limited to 'mcs/class/Mono.XBuild.Tasks/Mono.XBuild.Tasks/PcFileCache.cs')
-rw-r--r-- | mcs/class/Mono.XBuild.Tasks/Mono.XBuild.Tasks/PcFileCache.cs | 651 |
1 files changed, 651 insertions, 0 deletions
diff --git a/mcs/class/Mono.XBuild.Tasks/Mono.XBuild.Tasks/PcFileCache.cs b/mcs/class/Mono.XBuild.Tasks/Mono.XBuild.Tasks/PcFileCache.cs new file mode 100644 index 0000000000..3404656d77 --- /dev/null +++ b/mcs/class/Mono.XBuild.Tasks/Mono.XBuild.Tasks/PcFileCache.cs @@ -0,0 +1,651 @@ +// +// PcFileCache.cs +// +// Author: +// Lluis Sanchez Gual <lluis@novell.com> +// +// Copyright (c) 2009 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using System.Text; +using System.Xml; +using System.IO; +using System.Collections.Generic; + +// IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT +// This code is shared with xbuild, which has to build with .NET 2.0, +// so no c# 3.0 syntax is allowed here. + +namespace Mono.PkgConfig +{ + public interface IPcFileCacheContext<TP> where TP:PackageInfo, new() + { + // In the implementation of this method, the host application can extract + // information from the pc file and store it in the PackageInfo object + void StoreCustomData (PcFile pcfile, TP pkg); + + // Should return false if the provided package does not have required + // custom data + bool IsCustomDataComplete (string pcfile, TP pkg); + + // Called to report errors + void ReportError (string message, Exception ex); + } + + public interface IPcFileCacheContext: IPcFileCacheContext<PackageInfo> + { + } + + public abstract class PcFileCache: PcFileCache<PackageInfo> + { + public PcFileCache (IPcFileCacheContext ctx): base (ctx) + { + } + } + + public abstract class PcFileCache<TP> where TP:PackageInfo, new() + { + const string CACHE_VERSION = "2"; + const string MacOSXExternalPkgConfigDir = "/Library/Frameworks/Mono.framework/External/pkgconfig"; + + Dictionary<string, TP> infos = new Dictionary<string, TP> (); + + string cacheFile; + bool hasChanges; + IPcFileCacheContext<TP> ctx; + IEnumerable<string> defaultPaths; + + public PcFileCache (IPcFileCacheContext<TP> ctx) + { + this.ctx = ctx; + try { + string path = CacheDirectory; + if (!Directory.Exists (path)) + Directory.CreateDirectory (path); + cacheFile = Path.Combine (path, "pkgconfig-cache-" + CACHE_VERSION + ".xml"); + + if (File.Exists (cacheFile)) + Load (); + + } catch (Exception ex) { + ctx.ReportError ("pc file cache could not be loaded.", ex); + } + } + + protected abstract string CacheDirectory { get; } + + // Updates the pkg-config index, using the default search directories + public void Update () + { + Update (GetDefaultPaths ()); + } + + // Updates the pkg-config index, looking for .pc files in the provided directories + // Deletes pkg info entries, of which .pc files don't exist, from cache + public void Update (IEnumerable<string> pkgConfigDirs) + { + foreach (string pcdir in pkgConfigDirs) { + foreach (string pcfile in Directory.GetFiles (pcdir, "*.pc")) + GetPackageInfo (pcfile); + } + + lock (infos) { + string[] keys = new string [infos.Count]; + infos.Keys.CopyTo (keys, 0); + foreach (string key in keys) { + if (!File.Exists (key)) { + infos.Remove (key); + hasChanges = true; + } + } + } + + Save (); + } + + public IEnumerable<TP> GetPackages () + { + return GetPackages (null); + } + + public IEnumerable<TP> GetPackages (IEnumerable<string> pkgConfigDirs) + { + if (pkgConfigDirs == null) + pkgConfigDirs = GetDefaultPaths (); + else + pkgConfigDirs = NormaliseAndFilterPaths (pkgConfigDirs, Environment.CurrentDirectory); + + string[] keys = new string [infos.Count]; + TP[] vals = new TP [infos.Count]; + lock (infos) { + infos.Keys.CopyTo (keys, 0); + infos.Values.CopyTo (vals, 0); + } + + foreach (string sp in pkgConfigDirs) { + int i = 0; + foreach (var file in keys) { + string dirOfFile = Path.GetFullPath (Path.GetDirectoryName (file)); + if (dirOfFile == sp) + yield return vals [i]; + i++; + } + } + } + + public TP GetPackageInfoByName (string name) + { + return GetPackageInfoByName (name, null); + } + + public TP GetPackageInfoByName (string name, IEnumerable<string> pkgConfigDirs) + { + foreach (TP p in GetPackages (pkgConfigDirs)) + if (p.Name == name) + return p; + return null; + } + + // Returns information about a .pc file + public TP GetPackageInfo (string file) + { + TP info; + file = Path.GetFullPath (file); + + DateTime wtime = File.GetLastWriteTime (file); + + lock (infos) { + if (infos.TryGetValue (file, out info)) { + if (info.LastWriteTime == wtime) + return info; + } + } + + try { + info = ParsePackageInfo (file); + } catch (Exception ex) { + ctx.ReportError ("Error while parsing .pc file: " + file, ex); + info = new TP (); + } + + lock (infos) { + if (!info.IsValidPackage) + info = new TP (); // Create a default empty instance + info.LastWriteTime = wtime; + infos [file] = info; + hasChanges = true; + } + + return info; + } + + FileStream OpenFile (FileAccess access) + { + int retries = 6; + FileMode mode = access == FileAccess.Read ? FileMode.Open : FileMode.Create; + Exception lastException = null; + + while (retries > 0) { + try { + return new FileStream (cacheFile, mode, access, FileShare.None); + } catch (Exception ex) { + // the file may be locked by another app. Wait a bit and try again + lastException = ex; + System.Threading.Thread.Sleep (200); + retries--; + } + } + ctx.ReportError ("File could not be opened: " + cacheFile, lastException); + return null; + } + + void Load () + { + // The serializer can't be used because this file is reused in xbuild + using (FileStream fs = OpenFile (FileAccess.Read)) { + if (fs == null) + return; + XmlTextReader xr = new XmlTextReader (fs); + xr.MoveToContent (); + xr.ReadStartElement (); + xr.MoveToContent (); + + while (xr.NodeType == XmlNodeType.Element) + ReadPackage (xr); + } + } + + public void Save () + { + // The serializer can't be used because this file is reused in xbuild + lock (infos) { + if (!hasChanges) + return; + + using (FileStream fs = OpenFile (FileAccess.Write)) { + if (fs == null) + return; + XmlTextWriter tw = new XmlTextWriter (new StreamWriter (fs)); + tw.Formatting = Formatting.Indented; + + tw.WriteStartElement ("PcFileCache"); + foreach (KeyValuePair<string,TP> file in infos) { + WritePackage (tw, file.Key, file.Value); + } + tw.WriteEndElement (); // PcFileCache + tw.Flush (); + + hasChanges = false; + } + } + } + + void WritePackage (XmlTextWriter tw, string file, TP pinfo) + { + tw.WriteStartElement ("File"); + tw.WriteAttributeString ("path", file); + tw.WriteAttributeString ("lastWriteTime", XmlConvert.ToString (pinfo.LastWriteTime, XmlDateTimeSerializationMode.Local)); + + if (pinfo.IsValidPackage) { + if (pinfo.Name != null) + tw.WriteAttributeString ("name", pinfo.Name); + if (pinfo.Version != null) + tw.WriteAttributeString ("version", pinfo.Version); + if (!string.IsNullOrEmpty (pinfo.Description)) + tw.WriteAttributeString ("description", pinfo.Description); + if (!string.IsNullOrEmpty (pinfo.Requires)) + tw.WriteAttributeString ("requires", pinfo.Requires); + if (pinfo.CustomData != null) { + foreach (KeyValuePair<string,string> cd in pinfo.CustomData) + tw.WriteAttributeString (cd.Key, cd.Value); + } + WritePackageContent (tw, file, pinfo); + } + tw.WriteEndElement (); // File + } + + protected virtual void WritePackageContent (XmlTextWriter tw, string file, TP pinfo) + { + } + + void ReadPackage (XmlReader tr) + { + TP pinfo = new TP (); + string file = null; + + tr.MoveToFirstAttribute (); + do { + switch (tr.LocalName) { + case "path": file = tr.Value; break; + case "lastWriteTime": pinfo.LastWriteTime = XmlConvert.ToDateTime (tr.Value, XmlDateTimeSerializationMode.Local); break; + case "name": pinfo.Name = tr.Value; break; + case "version": pinfo.Version = tr.Value; break; + case "description": pinfo.Description = tr.Value; break; + case "requires": pinfo.Requires = tr.Value; break; + default: pinfo.SetData (tr.LocalName, tr.Value); break; + } + } while (tr.MoveToNextAttribute ()); + + tr.MoveToElement (); + + if (!tr.IsEmptyElement) { + tr.ReadStartElement (); + tr.MoveToContent (); + ReadPackageContent (tr, pinfo); + tr.MoveToContent (); + tr.ReadEndElement (); + } else + tr.Read (); + tr.MoveToContent (); + + if (!pinfo.IsValidPackage || ctx.IsCustomDataComplete (file, pinfo)) { + lock (infos) + infos [file] = pinfo; + } + } + + protected virtual void ReadPackageContent (XmlReader tr, TP pinfo) + { + } + + public object SyncRoot { + get { return infos; } + } + + + TP ParsePackageInfo (string pcfile) + { + PcFile file = new PcFile (); + file.Load (pcfile); + + TP pinfo = new TP (); + pinfo.Name = Path.GetFileNameWithoutExtension (file.FilePath); + + if (!file.HasErrors) { + pinfo.Version = file.Version; + pinfo.Description = file.Description; + pinfo.Requires = file.Requires; + ParsePackageInfo (file, pinfo); + if (pinfo.IsValidPackage) + ctx.StoreCustomData (file, pinfo); + } + return pinfo; + } + + protected virtual void ParsePackageInfo (PcFile file, TP pinfo) + { + } + + IEnumerable<string> GetDefaultPaths () + { + if (defaultPaths == null) { + // For mac osx, look in the 'External' dir on macosx, + // see bug #663180 + string pkgConfigPath = String.Format ("{0}:{1}", + Mono.XBuild.Utilities.MSBuildUtils.RunningOnMac ? MacOSXExternalPkgConfigDir : String.Empty, + Environment.GetEnvironmentVariable ("PKG_CONFIG_PATH") ?? String.Empty); + + string pkgConfigDir = Environment.GetEnvironmentVariable ("PKG_CONFIG_LIBDIR"); + defaultPaths = GetPkgconfigPaths (null, pkgConfigPath, pkgConfigDir); + } + return defaultPaths; + } + + public IEnumerable<string> GetPkgconfigPaths (string prefix, string pkgConfigPath, string pkgConfigLibdir) + { + char[] sep = new char[] { Path.PathSeparator }; + + string[] pkgConfigPaths = null; + if (!String.IsNullOrEmpty (pkgConfigPath)) { + pkgConfigPaths = pkgConfigPath.Split (sep, StringSplitOptions.RemoveEmptyEntries); + if (pkgConfigPaths.Length == 0) + pkgConfigPaths = null; + } + + string[] pkgConfigLibdirs = null; + if (!String.IsNullOrEmpty (pkgConfigLibdir)) { + pkgConfigLibdirs = pkgConfigLibdir.Split (sep, StringSplitOptions.RemoveEmptyEntries); + if (pkgConfigLibdirs.Length == 0) + pkgConfigLibdirs = null; + } + + if (prefix == null) + prefix = PathUp (typeof (int).Assembly.Location, 4); + + IEnumerable<string> paths = GetUnfilteredPkgConfigDirs (pkgConfigPaths, pkgConfigLibdirs, new string [] { prefix }); + return NormaliseAndFilterPaths (paths, Environment.CurrentDirectory); + } + + IEnumerable<string> GetUnfilteredPkgConfigDirs (IEnumerable<string> pkgConfigPaths, IEnumerable<string> pkgConfigLibdirs, IEnumerable<string> systemPrefixes) + { + if (pkgConfigPaths != null) { + foreach (string dir in pkgConfigPaths) + yield return dir; + } + + if (pkgConfigLibdirs != null) { + foreach (string dir in pkgConfigLibdirs) + yield return dir; + } else if (systemPrefixes != null) { + string[] suffixes = new string [] { + //FIXME: is this the correct order? share should be before lib but not sure about others. + Path.Combine ("share", "pkgconfig"), + Path.Combine ("lib", "pkgconfig"), + Path.Combine ("lib64", "pkgconfig"), + Path.Combine ("libdata", "pkgconfig"), + }; + foreach (string prefix in systemPrefixes) + foreach (string suffix in suffixes) + yield return Path.Combine (prefix, suffix); + } + } + + IEnumerable<string> NormaliseAndFilterPaths (IEnumerable<string> paths, string workingDirectory) + { + Dictionary<string,string> filtered = new Dictionary<string,string> (); + foreach (string p in paths) { + string path = p; + if (!Path.IsPathRooted (path)) + path = Path.Combine (workingDirectory, path); + path = Path.GetFullPath (path); + if (filtered.ContainsKey (path)) + continue; + filtered.Add (path,path); + try { + if (!Directory.Exists (path)) + continue; + } catch (IOException ex) { + ctx.ReportError ("Error checking for directory '" + path + "'.", ex); + } + yield return path; + } + } + + static string PathUp (string path, int up) + { + if (up == 0) + return path; + for (int i = path.Length -1; i >= 0; i--) { + if (path[i] == Path.DirectorySeparatorChar) { + up--; + if (up == 0) + return path.Substring (0, i); + } + } + return null; + } + } + + public class PcFile + { + Dictionary<string,string> variables = new Dictionary<string, string> (); + + string description; + public string Description { + get { return description; } + set { description = value; } + } + + string filePath; + public string FilePath { + get { return filePath; } + set { filePath = value; } + } + + bool hasErrors; + public bool HasErrors { + get { return hasErrors; } + set { hasErrors = value; } + } + + string libs; + public string Libs { + get { return libs; } + set { libs = value; } + } + + string name; + public string Name { + get { return name; } + set { name = value; } + } + + string version; + public string Version { + get { return version; } + set { version = value; } + } + + string requires; + public string Requires { + get { return requires; } + set { requires = value; } + } + + public string GetVariable (string varName) + { + string val; + variables.TryGetValue (varName, out val); + return val; + } + + public void Load (string pcfile) + { + FilePath = pcfile; + variables.Add ("pcfiledir", Path.GetDirectoryName (pcfile)); + using (StreamReader reader = new StreamReader (pcfile)) { + string line; + while ((line = reader.ReadLine ()) != null) { + int i = line.IndexOf (':'); + int j = line.IndexOf ('='); + int k = System.Math.Min (i != -1 ? i : int.MaxValue, j != -1 ? j : int.MaxValue); + if (k == int.MaxValue) + continue; + string var = line.Substring (0, k).Trim (); + string value = line.Substring (k + 1).Trim (); + value = Evaluate (value); + + if (k == j) { + // Is variable + variables [var] = value; + } + else { + switch (var) { + case "Name": Name = value; break; + case "Description": Description = value; break; + case "Version": Version = value; break; + case "Libs": Libs = value; break; + case "Requires": Requires = value; break; + } + } + } + } + } + + string Evaluate (string value) + { + int i = value.IndexOf ("${"); + if (i == -1) + return value; + + StringBuilder sb = new StringBuilder (); + int last = 0; + while (i != -1 && i < value.Length) { + sb.Append (value.Substring (last, i - last)); + if (i == 0 || value [i - 1] != '$') { + // Evaluate if var is not escaped + i += 2; + int n = value.IndexOf ('}', i); + if (n == -1 || n == i) { + // Closing bracket not found or empty name + HasErrors = true; + return value; + } + string rname = value.Substring (i, n - i); + string rval; + if (variables.TryGetValue (rname, out rval)) + sb.Append (rval); + else { + HasErrors = true; + return value; + } + i = n + 1; + last = i; + } else + last = i++; + + if (i < value.Length) + i = value.IndexOf ("${", i); + } + sb.Append (value.Substring (last, value.Length - last)); + return sb.ToString (); + } + } + + public class PackageInfo + { + Dictionary<string,string> customData; + DateTime lastWriteTime; + + string name; + public string Name { + get { return name; } + set { name = value; } + } + + string version; + public string Version { + get { return version; } + set { version = value; } + } + + string description; + public string Description { + get { return description; } + set { description = value; } + } + + string requires; + public string Requires { + get { return requires; } + set { requires = value; } + } + + public string GetData (string name) + { + if (customData == null) + return null; + string res; + customData.TryGetValue (name, out res); + return res; + } + + public void SetData (string name, string value) + { + if (customData == null) + customData = new Dictionary<string, string> (); + customData [name] = value; + } + + public void RemoveData (string name) + { + if (customData != null) + customData.Remove (name); + } + + internal Dictionary<string,string> CustomData { + get { return customData; } + } + + internal DateTime LastWriteTime { + get { return lastWriteTime; } + set { lastWriteTime = value; } + } + + internal bool HasCustomData { + get { return customData != null && customData.Count > 0; } + } + + internal protected virtual bool IsValidPackage { + get { return HasCustomData; } + } + } +} |