summaryrefslogtreecommitdiff
path: root/mcs/class/Mono.XBuild.Tasks
diff options
context:
space:
mode:
Diffstat (limited to 'mcs/class/Mono.XBuild.Tasks')
-rw-r--r--mcs/class/Mono.XBuild.Tasks/Assembly/AssemblyInfo.cs53
-rw-r--r--mcs/class/Mono.XBuild.Tasks/Makefile17
-rw-r--r--mcs/class/Mono.XBuild.Tasks/Mono.XBuild.Tasks.dll.sources6
-rw-r--r--mcs/class/Mono.XBuild.Tasks/Mono.XBuild.Tasks/LibraryPcFileCache.cs321
-rw-r--r--mcs/class/Mono.XBuild.Tasks/Mono.XBuild.Tasks/PcFileCache.cs651
-rw-r--r--mcs/class/Mono.XBuild.Tasks/Mono.XBuild.Tasks_test.dll.sources1
-rw-r--r--mcs/class/Mono.XBuild.Tasks/Test/Mono.XBuild.Tasks/PcFileCacheTest.cs285
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;
+ }
+ }
+ }
+}