diff options
Diffstat (limited to 'mcs/class/Mono.XBuild.Tasks')
7 files changed, 1334 insertions, 0 deletions
diff --git a/mcs/class/Mono.XBuild.Tasks/Assembly/AssemblyInfo.cs b/mcs/class/Mono.XBuild.Tasks/Assembly/AssemblyInfo.cs new file mode 100644 index 0000000000..c5543a4af2 --- /dev/null +++ b/mcs/class/Mono.XBuild.Tasks/Assembly/AssemblyInfo.cs @@ -0,0 +1,53 @@ +// +// AssemblyInfo.cs +// +// Author: +// Antonius Riha <antoniusriha@gmail.com> +// +// Copyright (c) 2013 Antonius Riha +// +// 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.Reflection; +using System.Resources; +using System.Runtime.InteropServices; + +// General Information about the Mono.XBuild.Tasks assembly + +[assembly: AssemblyTitle ("Mono.XBuild.Tasks.dll")] +[assembly: AssemblyDescription ("Mono.XBuild.Tasks.dll")] +[assembly: AssemblyDefaultAlias ("Mono.XBuild.Tasks.dll")] + +[assembly: AssemblyCompany (Consts.MonoCompany)] +[assembly: AssemblyProduct (Consts.MonoProduct)] +[assembly: AssemblyCopyright (Consts.MonoCopyright)] +[assembly: AssemblyVersion (Consts.FxVersion)] +[assembly: SatelliteContractVersion (Consts.FxVersion)] +[assembly: AssemblyInformationalVersion (Consts.FxFileVersion)] + +[assembly: NeutralResourcesLanguage ("en-US")] + +[assembly: ComVisible (false)] +[assembly: CLSCompliant (true)] +[assembly: AssemblyDelaySign (true)] +[assembly: AssemblyKeyFile("../mono.pub")] + +[assembly: AssemblyFileVersion (Consts.FxFileVersion)] diff --git a/mcs/class/Mono.XBuild.Tasks/Makefile b/mcs/class/Mono.XBuild.Tasks/Makefile new file mode 100644 index 0000000000..7135c45e3c --- /dev/null +++ b/mcs/class/Mono.XBuild.Tasks/Makefile @@ -0,0 +1,17 @@ +thisdir = class/Mono.XBuild.Tasks +SUBDIRS = +include ../../build/rules.make + +XBUILD_DIR=$(topdir)/tools/xbuild +include $(XBUILD_DIR)/xbuild.make + +LIBRARY = Mono.XBuild.Tasks.dll + +LIB_MCS_FLAGS = \ + /r:$(corlib) \ + /r:System.dll \ + /r:System.Xml.dll + +include $(XBUILD_DIR)/xbuild_test.make + +include ../../build/library.make diff --git a/mcs/class/Mono.XBuild.Tasks/Mono.XBuild.Tasks.dll.sources b/mcs/class/Mono.XBuild.Tasks/Mono.XBuild.Tasks.dll.sources new file mode 100644 index 0000000000..60955c07e0 --- /dev/null +++ b/mcs/class/Mono.XBuild.Tasks/Mono.XBuild.Tasks.dll.sources @@ -0,0 +1,6 @@ +Assembly/AssemblyInfo.cs +../../build/common/Consts.cs +../../build/common/MonoTODOAttribute.cs +Mono.XBuild.Tasks/PcFileCache.cs +Mono.XBuild.Tasks/LibraryPcFileCache.cs +../Microsoft.Build.Utilities/Mono.XBuild.Utilities/MSBuildUtils.cs diff --git a/mcs/class/Mono.XBuild.Tasks/Mono.XBuild.Tasks/LibraryPcFileCache.cs b/mcs/class/Mono.XBuild.Tasks/Mono.XBuild.Tasks/LibraryPcFileCache.cs new file mode 100644 index 0000000000..c89cba9a95 --- /dev/null +++ b/mcs/class/Mono.XBuild.Tasks/Mono.XBuild.Tasks/LibraryPcFileCache.cs @@ -0,0 +1,321 @@ +// +// PcFileCacheAssembly.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 class LibraryPcFileCache: PcFileCache<LibraryPackageInfo> + { + Dictionary<string, PackageAssemblyInfo> assemblyLocations; + + public LibraryPcFileCache (IPcFileCacheContext<LibraryPackageInfo> ctx): base (ctx) + { + } + + protected override string CacheDirectory { + get { + string path = Environment.GetFolderPath (Environment.SpecialFolder.ApplicationData); + path = Path.Combine (path, "xbuild"); + return path; + } + } + + // Returns the location of an assembly, given the full name + public PackageAssemblyInfo GetAssemblyLocation (string fullName) + { + return GetAssemblyLocation (fullName, null); + } + + public PackageAssemblyInfo GetAssemblyLocation (string fullName, IEnumerable<string> searchPaths) + { + lock (SyncRoot) { + if (assemblyLocations == null) { + // Populate on demand + assemblyLocations = new Dictionary<string, PackageAssemblyInfo> (); + foreach (LibraryPackageInfo info in GetPackages (searchPaths)) { + if (info.IsValidPackage) { + foreach (PackageAssemblyInfo asm in info.Assemblies) + assemblyLocations [NormalizeAsmName (asm.FullName)] = asm; + } + } + } + } + // This collection is read-only once built, so there is no need for a lock + PackageAssemblyInfo pasm; + assemblyLocations.TryGetValue (NormalizeAsmName (fullName), out pasm); + return pasm; + } + + public IEnumerable<PackageAssemblyInfo> ResolveAssemblyName (string name) + { + return ResolveAssemblyName (name, null); + } + + public IEnumerable<PackageAssemblyInfo> ResolveAssemblyName (string name, IEnumerable<string> searchPaths) + { + foreach (LibraryPackageInfo pinfo in GetPackages (searchPaths)) { + if (pinfo.IsValidPackage) { + foreach (PackageAssemblyInfo asm in pinfo.Assemblies) { + if (asm.Name == name) + yield return asm; + } + } + } + } + + protected override void WritePackageContent (XmlTextWriter tw, string file, LibraryPackageInfo pinfo) + { + foreach (PackageAssemblyInfo asm in pinfo.Assemblies) { + tw.WriteStartElement ("Assembly"); + tw.WriteAttributeString ("name", asm.Name); + tw.WriteAttributeString ("version", asm.Version); + tw.WriteAttributeString ("culture", asm.Culture); + tw.WriteAttributeString ("publicKeyToken", asm.PublicKeyToken); + tw.WriteAttributeString ("file", asm.File); + tw.WriteEndElement (); // Assembly + } + } + + protected override void ReadPackageContent (XmlReader tr, LibraryPackageInfo pinfo) + { + while (tr.NodeType == XmlNodeType.Element) { + PackageAssemblyInfo asm = new PackageAssemblyInfo (); + asm.Name = tr.GetAttribute ("name"); + asm.Version = tr.GetAttribute ("version"); + asm.Culture = tr.GetAttribute ("culture"); + asm.PublicKeyToken = tr.GetAttribute ("publicKeyToken"); + asm.File = tr.GetAttribute ("file"); + if (pinfo.Assemblies == null) + pinfo.Assemblies = new List<PackageAssemblyInfo> (); + asm.ParentPackage = pinfo; + pinfo.Assemblies.Add (asm); + tr.Read (); + tr.MoveToContent (); + } + } + + protected override void ParsePackageInfo (PcFile file, LibraryPackageInfo pinfo) + { + List<string> fullassemblies = null; + bool gacPackageSet = false; + + if (file.Libs != null && file.Libs.IndexOf (".dll") != -1) { + if (file.Libs.IndexOf ("-lib:") != -1 || file.Libs.IndexOf ("/lib:") != -1) { + fullassemblies = GetAssembliesWithLibInfo (file.Libs); + } else { + fullassemblies = GetAssembliesWithoutLibInfo (file.Libs); + } + } + + string value = file.GetVariable ("Libraries"); + if (!string.IsNullOrEmpty (value)) + fullassemblies = GetAssembliesFromLibrariesVar (value); + + value = file.GetVariable ("GacPackage"); + if (value != null) { + pinfo.IsGacPackage = + string.Equals (value, "yes", StringComparison.OrdinalIgnoreCase) || + string.Equals (value, "true", StringComparison.OrdinalIgnoreCase); + gacPackageSet = true; + } + + if (fullassemblies == null) + return; + + string pcDir = Path.GetDirectoryName (file.FilePath); + string monoPrefix = Path.GetDirectoryName (Path.GetDirectoryName (pcDir)); + monoPrefix = Path.GetFullPath (monoPrefix + Path.DirectorySeparatorChar + "lib" + Path.DirectorySeparatorChar + "mono" + Path.DirectorySeparatorChar); + + List<PackageAssemblyInfo> list = new List<PackageAssemblyInfo> (); + foreach (string assembly in fullassemblies) { + string asm; + if (Path.IsPathRooted (assembly)) + asm = Path.GetFullPath (assembly); + else { + if (Path.GetDirectoryName (assembly).Length == 0) { + asm = assembly; + } else { + asm = Path.GetFullPath (Path.Combine (pcDir, assembly)); + } + } + if (File.Exists (asm)) { + PackageAssemblyInfo pi = new PackageAssemblyInfo (); + pi.File = asm; + pi.ParentPackage = pinfo; + pi.UpdateFromFile (pi.File); + list.Add (pi); + if (!gacPackageSet && !asm.StartsWith (monoPrefix) && Path.IsPathRooted (asm)) { + // Assembly installed outside $(prefix)/lib/mono. It is most likely not a gac package. + gacPackageSet = true; + pinfo.IsGacPackage = false; + } + } + } + pinfo.Assemblies = list; + } + + private List<string> GetAssembliesWithLibInfo (string line) + { + List<string> references = new List<string> (); + List<string> libdirs = new List<string> (); + List<string> retval = new List<string> (); + foreach (string piece in line.Split (' ')) { + if (IsReferenceParameter (piece)) { + references.Add (piece.Substring (3).Trim ()); + } else if (piece.TrimStart ().StartsWith ("/lib:", StringComparison.OrdinalIgnoreCase) || + piece.TrimStart ().StartsWith ("-lib:", StringComparison.OrdinalIgnoreCase)) { + libdirs.Add (piece.Substring (5).Trim ()); + } + } + + foreach (string refrnc in references) { + foreach (string libdir in libdirs) { + if (File.Exists (libdir + Path.DirectorySeparatorChar + refrnc)) { + retval.Add (libdir + Path.DirectorySeparatorChar + refrnc); + } + } + } + + return retval; + } + + static bool IsReferenceParameter (string value) + { + return value.TrimStart ().StartsWith ("/r:", StringComparison.OrdinalIgnoreCase) || + value.TrimStart ().StartsWith ("-r:", StringComparison.OrdinalIgnoreCase); + } + + List<string> GetAssembliesFromLibrariesVar (string line) + { + List<string> references = new List<string> (); + foreach (string reference in line.Split (' ')) { + if (!string.IsNullOrEmpty (reference)) + references.Add (reference); + } + return references; + } + + private List<string> GetAssembliesWithoutLibInfo (string line) + { + List<string> references = new List<string> (); + foreach (string reference in line.Split (' ')) { + if (IsReferenceParameter (reference)) { + string final_ref = reference.Substring (3).Trim (); + references.Add (final_ref); + } + } + return references; + } + + public static string NormalizeAsmName (string name) + { + int i = name.IndexOf (", publickeytoken=null", StringComparison.OrdinalIgnoreCase); + if (i != -1) + name = name.Substring (0, i).Trim (); + i = name.IndexOf (", processorarchitecture=", StringComparison.OrdinalIgnoreCase); + if (i != -1) + name = name.Substring (0, i).Trim (); + return name; + } + } + + public class LibraryPackageInfo: PackageInfo + { + public bool IsGacPackage { + get { return GetData ("gacPackage") != "false"; } + set { + if (value) + RemoveData ("gacPackage"); + else + SetData ("gacPackage", "false"); + } + } + + internal List<PackageAssemblyInfo> Assemblies { get; set; } + + internal protected override bool IsValidPackage { + get { return Assemblies != null && Assemblies.Count > 0; } + } + } + + public class PackageAssemblyInfo + { + public string File { get; set; } + + public string Name; + + public string Version; + + public string Culture; + + public string PublicKeyToken; + + public string FullName { + get { + string fn = Name + ", Version=" + Version; + if (!string.IsNullOrEmpty (Culture)) + fn += ", Culture=" + Culture; + if (!string.IsNullOrEmpty (PublicKeyToken)) + fn += ", PublicKeyToken=" + PublicKeyToken; + return fn; + } + } + + public LibraryPackageInfo ParentPackage { get; set; } + + public void UpdateFromFile (string file) + { + Update (System.Reflection.AssemblyName.GetAssemblyName (file)); + } + + public void Update (System.Reflection.AssemblyName aname) + { + Name = aname.Name; + Version = aname.Version.ToString (); + if (aname.CultureInfo != null) { + if (aname.CultureInfo.LCID == System.Globalization.CultureInfo.InvariantCulture.LCID) + Culture = "neutral"; + else + Culture = aname.CultureInfo.Name; + } + string fn = aname.ToString (); + string key = "publickeytoken="; + int i = fn.IndexOf (key, StringComparison.OrdinalIgnoreCase) + key.Length; + int j = fn.IndexOf (',', i); + if (j == -1) j = fn.Length; + PublicKeyToken = fn.Substring (i, j - i); + } + } +} 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; } + } + } +} diff --git a/mcs/class/Mono.XBuild.Tasks/Mono.XBuild.Tasks_test.dll.sources b/mcs/class/Mono.XBuild.Tasks/Mono.XBuild.Tasks_test.dll.sources new file mode 100644 index 0000000000..0dbb1260ba --- /dev/null +++ b/mcs/class/Mono.XBuild.Tasks/Mono.XBuild.Tasks_test.dll.sources @@ -0,0 +1 @@ +Mono.XBuild.Tasks/PcFileCacheTest.cs diff --git a/mcs/class/Mono.XBuild.Tasks/Test/Mono.XBuild.Tasks/PcFileCacheTest.cs b/mcs/class/Mono.XBuild.Tasks/Test/Mono.XBuild.Tasks/PcFileCacheTest.cs new file mode 100644 index 0000000000..a35a0821c8 --- /dev/null +++ b/mcs/class/Mono.XBuild.Tasks/Test/Mono.XBuild.Tasks/PcFileCacheTest.cs @@ -0,0 +1,285 @@ +// +// PcFileCacheTest.cs +// +// Author: +// Antonius Riha <antoniusriha@gmail.com> +// +// Copyright (c) 2013 Antonius Riha +// +// 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.Collections.Generic; +using System.IO; +using Mono.PkgConfig; +using NUnit.Framework; + +namespace MonoTests.Mono.PkgConfig +{ + [TestFixture] + public class PcFileCacheTest + { + static readonly string cacheDir = "testcache"; + static readonly string pcCacheFileName = "pkgconfig-cache-2.xml"; + static readonly string pcCacheFilePath = Path.Combine (cacheDir, pcCacheFileName); + static readonly string pkgConfigDir = "testpkgconfig"; + + [SetUp] + public void Setup () + { + Directory.CreateDirectory (cacheDir); + Directory.CreateDirectory (pkgConfigDir); + } + + [TearDown] + public void Teardown () + { + if (Directory.Exists (cacheDir)) + Directory.Delete (cacheDir, true); + if (Directory.Exists (pkgConfigDir)) + Directory.Delete (pkgConfigDir, true); + } + + [Test] + public void CreatePcFileCache () + { + PcFileCacheStub.Create (cacheDir); + + // cache dir should exist + Assert.IsTrue (Directory.Exists (cacheDir), "A1"); + + // cache file should not exist + Assert.IsFalse (File.Exists (pcCacheFilePath), "A2"); + } + + [Test] + public void CreatePcFileCacheWithExistingEmptyCacheFile () + { + // Create pc cache file + WritePcCacheFileContent (""); + PcFileCache cache = PcFileCacheStub.Create (cacheDir); + + // cache should be empty + string[] pkgConfigDirs = { pkgConfigDir }; + CollectionAssert.IsEmpty (cache.GetPackages (pkgConfigDirs), "A1"); + } + + [Test] + public void CreatePcFileCacheWithCacheFileContaining1EntryForAnExistingPcFile () + { + // Create pc cache file with an entry and corresponding pc file + string pkgConfigFileName = "gtk-sharp-2.0.pc"; + string pkgConfigFullFilePath = Path.GetFullPath (Path.Combine (pkgConfigDir, pkgConfigFileName)); + string pcCacheFileContent = @"<PcFileCache> + <File path=""" + pkgConfigFullFilePath + @""" lastWriteTime=""2013-11-23T21:18:31+01:00"" /> +</PcFileCache> +"; + + string pkgConfigFileContent = @"prefix=${pcfiledir}/../.. +exec_prefix=${prefix} +libdir=${exec_prefix}/lib +gapidir=${prefix}/share/gapi-2.0 + + +Name: Gtk# +Description: Gtk# - GNOME .NET Binding +Version: 2.12.10 +Cflags: -I:${gapidir}/pango-api.xml -I:${gapidir}/atk-api.xml -I:${gapidir}/gdk-api.xml -I:${gapidir}/gtk-api.xml +Libs: -r:${libdir}/cli/pango-sharp-2.0/pango-sharp.dll -r:${libdir}/cli/atk-sharp-2.0/atk-sharp.dll -r:${libdir}/cli/gdk-sharp-2.0/gdk-sharp.dll -r:${libdir}/cli/gtk-sharp-2.0/gtk-sharp.dll +Requires: glib-sharp-2.0 +"; + + AddPkgConfigFile (pkgConfigFileName, pkgConfigFileContent); + WritePcCacheFileContent (pcCacheFileContent); + + PcFileCache cache = PcFileCacheStub.Create (cacheDir); + + // cache should contain entry of pc file + Assert.IsNotNull (cache.GetPackageInfo (pkgConfigFullFilePath), "A1"); + } + + [Test] + public void CreatePcFileCacheWithCacheFileContainingOneOrphanedEntry () + { + string pkgConfigFileName = "gtk-sharp-2.0.pc"; + string pkgConfigFullFilePath = Path.GetFullPath (Path.Combine (pkgConfigDir, pkgConfigFileName)); + string pcCacheFileContent = @"<PcFileCache> + <File path=""" + pkgConfigFullFilePath + @""" lastWriteTime=""2013-11-23T21:18:31+01:00"" /> +</PcFileCache> +"; + WritePcCacheFileContent (pcCacheFileContent); + + PcFileCache cache = PcFileCacheStub.Create (cacheDir); + + // cache should contain orphaned entry + Assert.IsNotNull (cache.GetPackageInfo (pkgConfigFullFilePath), "A1"); + } + + [Test] + public void CreatePcFileCacheWithoutCacheFileButWithPcFile () + { + string pkgConfigFileName = "gtk-sharp-2.0.pc"; + string pkgConfigFileContent = @"prefix=${pcfiledir}/../.. +exec_prefix=${prefix} +libdir=${exec_prefix}/lib +gapidir=${prefix}/share/gapi-2.0 + + +Name: Gtk# +Description: Gtk# - GNOME .NET Binding +Version: 2.12.10 +Cflags: -I:${gapidir}/pango-api.xml -I:${gapidir}/atk-api.xml -I:${gapidir}/gdk-api.xml -I:${gapidir}/gtk-api.xml +Libs: -r:${libdir}/cli/pango-sharp-2.0/pango-sharp.dll -r:${libdir}/cli/atk-sharp-2.0/atk-sharp.dll -r:${libdir}/cli/gdk-sharp-2.0/gdk-sharp.dll -r:${libdir}/cli/gtk-sharp-2.0/gtk-sharp.dll +Requires: glib-sharp-2.0 +"; + AddPkgConfigFile (pkgConfigFileName, pkgConfigFileContent); + + PcFileCache cache = PcFileCacheStub.Create (cacheDir); + + // cache file should exist + Assert.IsFalse (File.Exists (pcCacheFilePath), "A1"); + + // cache should be empty + string[] pkgConfigDirs = { pkgConfigDir }; + CollectionAssert.IsEmpty (cache.GetPackages (pkgConfigDirs), "A2"); + } + + [Test] + public void GetPackagesOrderedByFolder () + { + string pkgConfigDir1 = "testpkgconfigdir1"; + string pkgConfigDir2 = "testpkgconfigdir2"; + Directory.CreateDirectory (pkgConfigDir1); + Directory.CreateDirectory (pkgConfigDir2); + + string pkgConfigFile11NameAttr = "gtk-sharp-2.0"; + string pkgConfigFile11FullPath = Path.GetFullPath (Path.Combine (pkgConfigDir1, "gtk-sharp-2.0.pc")); + + string pkgConfigFile21NameAttr = "art-sharp-2.0"; + string pkgConfigFile21FullPath = Path.GetFullPath (Path.Combine (pkgConfigDir2, "art-sharp-2.0.pc")); + + string pkgConfigFile12NameAttr = "cecil"; + string pkgConfigFile12FullPath = Path.GetFullPath (Path.Combine (pkgConfigDir1, "cecil.pc")); + + string pcCacheFileContent = @"<PcFileCache> + <File path=""" + pkgConfigFile11FullPath + @""" lastWriteTime=""2013-11-23T21:18:31+01:00"" name=""" + pkgConfigFile11NameAttr + @""" /> + <File path=""" + pkgConfigFile21FullPath + @""" lastWriteTime=""2011-07-12T12:04:53+02:00"" name=""" + pkgConfigFile21NameAttr + @""" /> + <File path=""" + pkgConfigFile12FullPath + @""" lastWriteTime=""2012-07-24T22:28:30+02:00"" name=""" + pkgConfigFile12NameAttr + @""" /> +</PcFileCache> +"; + + WritePcCacheFileContent (pcCacheFileContent); + + PcFileCache cache = PcFileCacheStub.Create (cacheDir); + string[] pkgConfigDirs = { pkgConfigDir1, pkgConfigDir2 }; + IEnumerable<PackageInfo> packages = cache.GetPackages (pkgConfigDirs); + + PackageInfo[] packageArray = new PackageInfo [3]; + int i = 0; + foreach (PackageInfo package in packages) + packageArray [i++] = package; + + Assert.AreEqual (pkgConfigFile11NameAttr, packageArray [0].Name, "A1"); + Assert.AreEqual (pkgConfigFile12NameAttr, packageArray [1].Name, "A2"); + Assert.AreEqual (pkgConfigFile21NameAttr, packageArray [2].Name, "A3"); + + Directory.Delete (pkgConfigDir1, true); + Directory.Delete (pkgConfigDir2, true); + } + + [Test] + public void UpdatePcFileCacheWithOrphanedEntry () + { + string pkgConfigFileNameAttr = "gtk-sharp-2.0"; + string pkgConfigFileName = "gtk-sharp-2.0.pc"; + string pkgConfigFullFilePath = Path.GetFullPath (Path.Combine (pkgConfigDir, pkgConfigFileName)); + string pcCacheFileContent = @"<PcFileCache> + <File path=""" + pkgConfigFullFilePath + @""" lastWriteTime=""2013-11-23T21:18:31+01:00"" name=""" + pkgConfigFileNameAttr + @""" /> +</PcFileCache> +"; + + WritePcCacheFileContent (pcCacheFileContent); + + PcFileCache cache = PcFileCacheStub.Create (cacheDir); + + // precondition + string[] pkgConfigDirs = { pkgConfigDir }; + Assert.IsNotNull (cache.GetPackageInfoByName (pkgConfigFileNameAttr, pkgConfigDirs), "A1"); + + cache.Update (pkgConfigDirs); + Assert.IsNull (cache.GetPackageInfoByName (pkgConfigFileNameAttr, pkgConfigDirs), "A2"); + } + + static void WritePcCacheFileContent (string content) + { + File.WriteAllText (pcCacheFilePath, content); + } + + static void AddPkgConfigFile (string fileName, string content) + { + AddPkgConfigFile (fileName, content, pkgConfigDir); + } + + static void AddPkgConfigFile (string fileName, string content, string pkgConfigDir) + { + string path = Path.Combine (pkgConfigDir, fileName); + File.WriteAllText (path, content); + } + + class PcFileCacheContextStub : IPcFileCacheContext + { + public void StoreCustomData (PcFile pcfile, PackageInfo pkg) + { + } + + public bool IsCustomDataComplete (string pcfile, PackageInfo pkg) + { + return false; + } + + public void ReportError (string message, Exception ex) + { + } + } + + class PcFileCacheStub : PcFileCache + { + static string initCacheDirectory; + readonly string cacheDirectory; + + PcFileCacheStub (string cacheDirectory) : base (new PcFileCacheContextStub ()) + { + if (cacheDirectory == null) + throw new ArgumentNullException ("cacheDirectory"); + this.cacheDirectory = cacheDirectory; + } + + protected override string CacheDirectory { + get { return initCacheDirectory == null ? cacheDirectory : initCacheDirectory; } + } + + public static PcFileCache Create (string cacheDirectory) + { + initCacheDirectory = cacheDirectory; + PcFileCache cache = new PcFileCacheStub (cacheDirectory); + initCacheDirectory = null; + return cache; + } + } + } +} |