372 lines
16 KiB
C#
372 lines
16 KiB
C#
using ANX.Framework.Content.Pipeline.Serialization.Compiler;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Xml;
|
|
using ANX.Framework.Content.Pipeline.Serialization;
|
|
using System.Diagnostics;
|
|
using System.Globalization;
|
|
using ANX.Framework.Content.Pipeline.Helpers;
|
|
|
|
namespace ANX.Framework.Content.Pipeline.Tasks
|
|
{
|
|
public class AnxBuildCache : IBuildCache
|
|
{
|
|
Dictionary<Uri, CacheData> cacheData = new Dictionary<Uri, CacheData>();
|
|
ImporterManager importerManager;
|
|
ProcessorManager processorManager;
|
|
ContentCompiler contentCompiler;
|
|
|
|
public AnxBuildCache(ImporterManager importerManager, ProcessorManager processorManager, ContentCompiler contentCompiler)
|
|
{
|
|
if (importerManager == null)
|
|
throw new ArgumentNullException("importerManager");
|
|
|
|
if (processorManager == null)
|
|
throw new ArgumentNullException("processorManager");
|
|
|
|
if (contentCompiler == null)
|
|
throw new ArgumentNullException("contentCompiler");
|
|
|
|
this.importerManager = importerManager;
|
|
this.processorManager = processorManager;
|
|
this.contentCompiler = contentCompiler;
|
|
}
|
|
|
|
public void LoadCache(Uri cacheFilePath)
|
|
{
|
|
if (cacheFilePath == null)
|
|
throw new ArgumentNullException("cacheFilePath");
|
|
|
|
if (!cacheFilePath.IsAbsoluteUri)
|
|
throw new ArgumentException("cacheFilePath must be absolute.");
|
|
|
|
if (!File.Exists(cacheFilePath.LocalPath))
|
|
throw new FileNotFoundException("cache file doesn't exist.", cacheFilePath.LocalPath);
|
|
|
|
using (XmlReader reader = XmlReader.Create(cacheFilePath.LocalPath))
|
|
{
|
|
XmlDocument document = new XmlDocument();
|
|
document.Load(reader);
|
|
|
|
//skip document root.
|
|
foreach (var assetNode in document.FirstChild.NextSibling.ChildNodes.GetAsEnumerable())
|
|
{
|
|
var data = new CacheData();
|
|
|
|
var uri = new Uri(assetNode.Attributes["Uri"].Value, UriKind.RelativeOrAbsolute);
|
|
|
|
foreach (var assetContentNode in assetNode.ChildNodes.GetAsEnumerable())
|
|
{
|
|
string name = assetContentNode.Name;
|
|
|
|
switch (name)
|
|
{
|
|
case "SourceWriteTime":
|
|
data.sourceWriteTime = new DateTime(long.Parse(assetContentNode.InnerText, CultureInfo.InvariantCulture), DateTimeKind.Utc);
|
|
break;
|
|
case "OutputWriteTime":
|
|
data.outputWriteTime = new DateTime(long.Parse(assetContentNode.InnerText, CultureInfo.InvariantCulture), DateTimeKind.Utc);
|
|
break;
|
|
case "ImporterType":
|
|
data.importerType = TypeHelper.GetType(assetContentNode.InnerText);
|
|
break;
|
|
case "ImporterAssemblyWriteTime":
|
|
data.importerAssemblyWriteTime = new DateTime(long.Parse(assetContentNode.InnerText, CultureInfo.InvariantCulture), DateTimeKind.Utc);
|
|
break;
|
|
case "ProcessorType":
|
|
data.processorType = TypeHelper.GetType(assetContentNode.InnerText);
|
|
break;
|
|
case "ProcessorAssemblyWriteTime":
|
|
data.processorAssemblyWriteTime = new DateTime(long.Parse(assetContentNode.InnerText, CultureInfo.InvariantCulture), DateTimeKind.Utc);
|
|
break;
|
|
case "CompilerType":
|
|
data.compilerType = TypeHelper.GetType(assetContentNode.InnerText);
|
|
break;
|
|
case "CompilerAssemblyWriteTime":
|
|
data.compilerAssemblyWriteTime = new DateTime(long.Parse(assetContentNode.InnerText, CultureInfo.InvariantCulture), DateTimeKind.Utc);
|
|
break;
|
|
case "ProcessorParameters":
|
|
foreach (var paramNode in assetContentNode.ChildNodes.GetAsEnumerable())
|
|
{
|
|
string paramName = paramNode.Attributes["Key"].Value;
|
|
if (paramNode.Attributes["IsNull"] != null && paramNode.Attributes["IsNull"].InnerText == "true")
|
|
data.processorParameters.Add(paramName, null);
|
|
else
|
|
data.processorParameters.Add(paramName, paramNode.InnerText);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
this.cacheData[uri] = data;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void SaveCache(Uri cacheFilePath)
|
|
{
|
|
if (cacheFilePath == null)
|
|
throw new ArgumentNullException("cacheFilePath");
|
|
|
|
if (!cacheFilePath.IsAbsoluteUri)
|
|
throw new ArgumentException("cacheFilePath must be absolute.");
|
|
|
|
using (XmlWriter writer = XmlWriter.Create(cacheFilePath.LocalPath, new XmlWriterSettings() { Indent = true }))
|
|
{
|
|
writer.WriteStartDocument();
|
|
|
|
writer.WriteStartElement("CacheData");
|
|
|
|
foreach (var pair in this.cacheData)
|
|
{
|
|
var data = pair.Value;
|
|
|
|
writer.WriteStartElement("Asset");
|
|
writer.WriteAttributeString("Uri", pair.Key.OriginalString);
|
|
|
|
writer.WriteElementString("SourceWriteTime", data.sourceWriteTime.Ticks.ToString());
|
|
writer.WriteElementString("OutputWriteTime", data.outputWriteTime.Ticks.ToString());
|
|
if (data.importerType != null)
|
|
{
|
|
writer.WriteElementString("ImporterType", data.importerType.AssemblyQualifiedName);
|
|
//DateTimes are structs, so ne need to check for null.
|
|
writer.WriteElementString("ImporterAssemblyWriteTime", data.importerAssemblyWriteTime.Ticks.ToString(CultureInfo.InvariantCulture));
|
|
}
|
|
|
|
if (data.processorType != null)
|
|
{
|
|
writer.WriteElementString("ProcessorType", data.processorType.AssemblyQualifiedName);
|
|
writer.WriteElementString("ProcessorAssemblyWriteTime", data.processorAssemblyWriteTime.Ticks.ToString(CultureInfo.InvariantCulture));
|
|
}
|
|
|
|
if (data.compilerType != null)
|
|
{
|
|
writer.WriteElementString("CompilerType", data.compilerType.AssemblyQualifiedName);
|
|
writer.WriteElementString("CompilerAssemblyWriteTime", data.compilerAssemblyWriteTime.Ticks.ToString(CultureInfo.InvariantCulture));
|
|
}
|
|
|
|
writer.WriteStartElement("ProcessorParameters");
|
|
|
|
foreach (var parameterPair in data.processorParameters)
|
|
{
|
|
writer.WriteStartElement("Param");
|
|
writer.WriteAttributeString("Key", parameterPair.Key);
|
|
|
|
if (parameterPair.Value == null)
|
|
writer.WriteAttributeString("IsNull", "true");
|
|
else
|
|
try
|
|
{
|
|
writer.WriteValue(parameterPair.Value);
|
|
}
|
|
catch (Exception exc)
|
|
{
|
|
Trace.WriteLine(string.Format("Error while writing build.cache at processor parameter \"{0}\" for asset \"{1}\": {2}", parameterPair.Key, pair.Key.OriginalString, exc.Message));
|
|
}
|
|
|
|
writer.WriteEndElement();
|
|
}
|
|
|
|
writer.WriteEndElement();
|
|
|
|
writer.WriteEndElement();
|
|
}
|
|
|
|
writer.WriteEndElement();
|
|
|
|
writer.Flush();
|
|
}
|
|
}
|
|
|
|
public bool IsValid(BuildItem buildItem, Uri outputFilePath)
|
|
{
|
|
if (buildItem == null)
|
|
throw new ArgumentNullException("buildItem");
|
|
|
|
if (outputFilePath == null)
|
|
throw new ArgumentNullException("outputFilePath");
|
|
|
|
if (!File.Exists(GetAbsoluteFileName(buildItem.SourceFilename)) || !File.Exists(GetAbsoluteFileName(outputFilePath).LocalPath))
|
|
return false;
|
|
|
|
Uri filePath = new Uri(buildItem.SourceFilename, UriKind.RelativeOrAbsolute);
|
|
|
|
CacheData data;
|
|
if (cacheData.TryGetValue(filePath, out data))
|
|
{
|
|
if (File.GetLastWriteTimeUtc(filePath.OriginalString) != data.sourceWriteTime)
|
|
return false;
|
|
|
|
if (File.GetLastWriteTimeUtc(outputFilePath.OriginalString) != data.outputWriteTime)
|
|
return false;
|
|
|
|
Type importerType = importerManager.AvailableImporters.FirstOrDefault((x) => x.Key == buildItem.ImporterName).Value;
|
|
//The visual studio extension tries to set the importer types whenever it can, so the case when the importer is null should be rather rare and
|
|
//the extension should immediatly try to save the importer, same for the processor.
|
|
//For additional meta data, we check the write time of the assemblies that contain the used importer and processor. When a dependant assembly changes,
|
|
//the referencing assembly will have to relink and therefore the write time should change.
|
|
if (data.importerType != importerType)
|
|
return false;
|
|
else if (importerType != null)
|
|
{
|
|
if (!string.IsNullOrEmpty(importerType.Assembly.Location))
|
|
if (File.GetLastWriteTimeUtc(importerType.Assembly.Location) != data.importerAssemblyWriteTime)
|
|
return false;
|
|
}
|
|
|
|
var processor = processorManager.AvailableProcessors.FirstOrDefault((x) => x.Key == buildItem.ProcessorName).Value;
|
|
if (processor == null)
|
|
return false;
|
|
else
|
|
{
|
|
if (data.processorType != processor.GetType())
|
|
return false;
|
|
|
|
if (!string.IsNullOrEmpty(processor.GetType().Assembly.Location))
|
|
if (File.GetLastWriteTimeUtc(processor.GetType().Assembly.Location) != data.processorAssemblyWriteTime)
|
|
return false;
|
|
|
|
var compiler = this.contentCompiler.GetTypeWriter(processor.OutputType);
|
|
if (compiler == null)
|
|
return false;
|
|
|
|
if (compiler.GetType() != data.compilerType)
|
|
return false;
|
|
|
|
if (!string.IsNullOrEmpty(compiler.GetType().Assembly.Location))
|
|
if (File.GetLastWriteTimeUtc(compiler.GetType().Assembly.Location) != data.compilerAssemblyWriteTime)
|
|
return false;
|
|
}
|
|
|
|
if (buildItem.ProcessorParameters.Count != data.processorParameters.Count)
|
|
return false;
|
|
|
|
string[] keys = buildItem.ProcessorParameters.Keys.ToArray();
|
|
for (int i = 0; i < keys.Length; i++)
|
|
{
|
|
var key = keys[i];
|
|
|
|
object cachedValue;
|
|
if (!data.processorParameters.TryGetValue(key, out cachedValue))
|
|
return false;
|
|
|
|
var value = buildItem.ProcessorParameters[key];
|
|
if (!string.Equals(cachedValue, TypeHelper.ConvertToInvariantString(value)))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public void Refresh(BuildItem buildItem, Uri outputFilePath)
|
|
{
|
|
if (buildItem == null)
|
|
throw new ArgumentNullException("buildItem");
|
|
|
|
if (!File.Exists(GetAbsoluteFileName(buildItem.SourceFilename)))
|
|
throw new ArgumentException(string.Format("The file \"{0}\" does not exist.", buildItem.SourceFilename));
|
|
|
|
CacheData data = new CacheData();
|
|
|
|
if (File.Exists(GetAbsoluteFileName(outputFilePath.OriginalString)))
|
|
data.outputWriteTime = File.GetLastWriteTimeUtc(GetAbsoluteFileName(outputFilePath.OriginalString));
|
|
|
|
data.sourceWriteTime = File.GetLastWriteTimeUtc(buildItem.SourceFilename);
|
|
|
|
var importerType = importerManager.AvailableImporters.FirstOrDefault((x) => x.Key == buildItem.ImporterName).Value;
|
|
data.importerType = importerType;
|
|
if (importerType != null && File.Exists(importerType.Assembly.Location))
|
|
data.importerAssemblyWriteTime = File.GetLastWriteTimeUtc(importerType.Assembly.Location);
|
|
|
|
var processor = processorManager.AvailableProcessors.FirstOrDefault((x) => x.Key == buildItem.ProcessorName).Value;
|
|
if (processor == null)
|
|
data.processorType = null;
|
|
else
|
|
{
|
|
data.processorType = processor.GetType();
|
|
if (File.Exists(processor.GetType().Assembly.Location))
|
|
data.processorAssemblyWriteTime = File.GetLastWriteTimeUtc(processor.GetType().Assembly.Location);
|
|
|
|
var typeWriter = contentCompiler.GetTypeWriter(processor.OutputType);
|
|
if (typeWriter == null)
|
|
data.compilerType = null;
|
|
else
|
|
{
|
|
data.compilerType = typeWriter.GetType();
|
|
|
|
if (File.Exists(typeWriter.GetType().Assembly.Location))
|
|
data.compilerAssemblyWriteTime = File.GetLastWriteTimeUtc(typeWriter.GetType().Assembly.Location);
|
|
}
|
|
}
|
|
|
|
data.processorParameters.AddRange(buildItem.ProcessorParameters.Select((x) => new KeyValuePair<string,object>(x.Key, TypeHelper.ConvertToInvariantString(x.Value))));
|
|
|
|
this.cacheData[new Uri(buildItem.SourceFilename, UriKind.RelativeOrAbsolute)] = data;
|
|
}
|
|
|
|
public void Clear()
|
|
{
|
|
cacheData.Clear();
|
|
}
|
|
|
|
public void Remove(Uri filePath)
|
|
{
|
|
cacheData.Remove(filePath);
|
|
}
|
|
|
|
public Uri ProjectHome
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
private string GetAbsoluteFileName(string fileName)
|
|
{
|
|
if (Path.IsPathRooted(fileName))
|
|
return fileName;
|
|
|
|
var result = new Uri(GetProjectHome(), new Uri(fileName, UriKind.Relative)).LocalPath;
|
|
return result;
|
|
}
|
|
|
|
private Uri GetAbsoluteFileName(Uri fileName)
|
|
{
|
|
if (fileName == null)
|
|
throw new ArgumentNullException("fileName");
|
|
|
|
if (fileName.IsAbsoluteUri)
|
|
return fileName;
|
|
|
|
var result = new Uri(GetProjectHome(), fileName);
|
|
return result;
|
|
}
|
|
|
|
private Uri GetProjectHome()
|
|
{
|
|
if (ProjectHome != null)
|
|
return ProjectHome;
|
|
|
|
return new Uri(Environment.CurrentDirectory + "\\", UriKind.Absolute);
|
|
}
|
|
|
|
class CacheData
|
|
{
|
|
public DateTime outputWriteTime;
|
|
public DateTime sourceWriteTime;
|
|
public Type importerType;
|
|
public DateTime importerAssemblyWriteTime;
|
|
public Type processorType;
|
|
public DateTime processorAssemblyWriteTime;
|
|
public Type compilerType;
|
|
public DateTime compilerAssemblyWriteTime;
|
|
public OpaqueDataDictionary processorParameters = new OpaqueDataDictionary();
|
|
}
|
|
}
|
|
}
|