some cleanup in BuildContentTask. Improved handling of ProcessorParameters. Made the BuildCache much more reliable.

This commit is contained in:
Konstantin Koch 2015-11-04 23:38:46 +01:00
parent 23029218b1
commit 23466b9b31
8 changed files with 159 additions and 128 deletions

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
@ -60,5 +61,30 @@ namespace ANX.Framework.Content.Pipeline.Helpers
return type;
}
public static object ConvertFromInvariantString(object value, Type type)
{
if (value != null)
{
if (type == value.GetType())
return value;
if (value.GetType() == typeof(string))
if (type.IsEnum)
return Enum.Parse(type, (string)value);
else
return TypeDescriptor.GetConverter(type).ConvertFromInvariantString((string)value);
}
return null;
}
public static string ConvertToInvariantString(object value)
{
if (value == null)
return null;
return TypeDescriptor.GetConverter(value).ConvertToInvariantString(value);
}
}
}

View File

@ -9,6 +9,7 @@ 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
{
@ -71,19 +72,19 @@ namespace ANX.Framework.Content.Pipeline.Tasks
data.outputWriteTime = new DateTime(long.Parse(assetContentNode.InnerText, CultureInfo.InvariantCulture), DateTimeKind.Utc);
break;
case "ImporterType":
data.importerType = Type.GetType(assetContentNode.InnerText, false);
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 = Type.GetType(assetContentNode.InnerText, false);
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 = Type.GetType(assetContentNode.InnerText, false);
data.compilerType = TypeHelper.GetType(assetContentNode.InnerText);
break;
case "CompilerAssemblyWriteTime":
data.compilerAssemblyWriteTime = new DateTime(long.Parse(assetContentNode.InnerText, CultureInfo.InvariantCulture), DateTimeKind.Utc);
@ -92,9 +93,10 @@ namespace ANX.Framework.Content.Pipeline.Tasks
foreach (var paramNode in assetContentNode.ChildNodes.GetAsEnumerable())
{
string paramName = paramNode.Attributes["Key"].Value;
object paramValue = paramNode.InnerText;
data.processorParameters.Add(paramName, paramValue);
if (paramNode.Attributes["IsNull"] != null && paramNode.Attributes["IsNull"].InnerText == "true")
data.processorParameters.Add(paramName, null);
else
data.processorParameters.Add(paramName, paramNode.InnerText);
}
break;
}
@ -154,14 +156,17 @@ namespace ANX.Framework.Content.Pipeline.Tasks
writer.WriteStartElement("Param");
writer.WriteAttributeString("Key", parameterPair.Key);
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));
}
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();
}
@ -204,9 +209,9 @@ namespace ANX.Framework.Content.Pipeline.Tasks
//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 (importerType == null || data.importerType != importerType)
if (data.importerType != importerType)
return false;
else
else if (importerType != null)
{
if (!string.IsNullOrEmpty(importerType.Assembly.Location))
if (File.GetLastWriteTimeUtc(importerType.Assembly.Location) != data.importerAssemblyWriteTime)
@ -243,8 +248,14 @@ namespace ANX.Framework.Content.Pipeline.Tasks
string[] keys = buildItem.ProcessorParameters.Keys.ToArray();
for (int i = 0; i < keys.Length; i++)
{
string key = keys[i];
if (!data.processorParameters[key].Equals(buildItem.ProcessorParameters[key]))
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;
}
@ -294,7 +305,7 @@ namespace ANX.Framework.Content.Pipeline.Tasks
}
}
data.processorParameters.AddRange(buildItem.ProcessorParameters);
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;
}

View File

@ -8,6 +8,7 @@ using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Diagnostics;
namespace ANX.Framework.Content.Pipeline.Tasks
{
@ -142,7 +143,7 @@ namespace ANX.Framework.Content.Pipeline.Tasks
return Execute(new [] { item }).First();
}
public CompiledBuildItem[] Execute(IEnumerable<BuildItem> itemsToBuild, bool throwExceptions = false)
public CompiledBuildItem[] Execute(IEnumerable<BuildItem> itemsToBuild)
{
if (itemsToBuild == null)
throw new ArgumentNullException("itemsToBuild");
@ -160,25 +161,39 @@ namespace ANX.Framework.Content.Pipeline.Tasks
ContentProcessorContext processorContext;
PrepareAssetBuildCallback(this, buildItem, out importerContext, out processorContext);
if (string.IsNullOrEmpty(buildItem.ProcessorName))
throw new ArgumentNullException(string.Format("Asset \"{0}\" has no processor.", buildItem.AssetName));
//Has to be done before the cache is checked to make it has all the data it needs.
if (string.IsNullOrEmpty(buildItem.ImporterName))
buildItem.ImporterName = this.ImporterManager.GuessImporterByFileExtension(Path.GetExtension(buildItem.SourceFilename));
foreach (var parameter in this.ProcessorManager.GetProcessorParameters(buildItem.ProcessorName))
{
if (!buildItem.ProcessorParameters.ContainsKey(parameter.PropertyName))
buildItem.ProcessorParameters.Add(parameter.PropertyName, parameter.DefaultValue);
else
{
//Make sure the value is of the type it should be.
buildItem.ProcessorParameters[parameter.PropertyName] = TypeHelper.ConvertFromInvariantString(buildItem.ProcessorParameters[parameter.PropertyName], TypeHelper.GetType(parameter.PropertyType));
}
}
Uri outputFilename = new Uri(processorContext.OutputFilename, UriKind.Absolute);
if (BuildCache != null)
{
if (throwExceptions)
try
{
if (CheckIsValid(buildItem, outputFilename))
if (BuildCache.IsValid(buildItem, outputFilename))
{
result.Add(new CompiledBuildItem(null, buildItem, outputFilename.LocalPath, true));
continue;
}
}
else
catch (Exception exc)
{
try
{
if (CheckIsValid(buildItem, outputFilename))
continue;
}
catch (Exception exc)
{
BuildLogger.LogWarning(null, new ContentIdentity() { SourceTool = "BuildCache" }, exc.Message);
}
BuildLogger.LogWarning(null, new ContentIdentity() { SourceTool = "BuildCache" }, exc.Message);
Debugger.Break();
}
}
@ -187,20 +202,14 @@ namespace ANX.Framework.Content.Pipeline.Tasks
object importedObject = null;
CompiledBuildItem compiled = null;
if (throwExceptions)
try
{
importedObject = ImportAsset(buildItem, absoluteFilename, importerContext);
}
else
catch (Exception exc)
{
try
{
importedObject = ImportAsset(buildItem, absoluteFilename, importerContext);
}
catch (Exception exc)
{
LogException(exc, absoluteFilename);
}
LogException(exc, absoluteFilename);
Debugger.Break();
}
if (importedObject != null)
@ -216,20 +225,14 @@ namespace ANX.Framework.Content.Pipeline.Tasks
object compiledItem = null;
if (throwExceptions)
try
{
compiledItem = Process(buildItem, absoluteFilename, importedObject, processorContext);
}
else
catch (Exception exc)
{
try
{
compiledItem = Process(buildItem, absoluteFilename, importedObject, processorContext);
}
catch (Exception exc)
{
LogException(exc, absoluteFilename);
}
LogException(exc, absoluteFilename);
Debugger.Break();
}
if (compiledItem != null)
@ -243,6 +246,7 @@ namespace ANX.Framework.Content.Pipeline.Tasks
catch (Exception exc)
{
LogException(exc, absoluteFilename);
Debugger.Break();
}
}
}
@ -260,20 +264,14 @@ namespace ANX.Framework.Content.Pipeline.Tasks
if (BuildCache != null)
{
if (throwExceptions)
try
{
BuildCache.Refresh(compiled.OriginalBuildItem, outputFilename);
}
else
catch (Exception exc)
{
try
{
BuildCache.Refresh(compiled.OriginalBuildItem, outputFilename);
}
catch (Exception exc)
{
BuildLogger.LogWarning(null, new ContentIdentity() { SourceTool = "BuildCache" }, exc.Message);
}
BuildLogger.LogWarning(null, new ContentIdentity() { SourceTool = "BuildCache" }, exc.Message);
Debugger.Break();
}
}
}
@ -286,15 +284,6 @@ namespace ANX.Framework.Content.Pipeline.Tasks
return result.ToArray();
}
private bool CheckIsValid(BuildItem buildItem, Uri outputFilename)
{
if (BuildCache.IsValid(buildItem, outputFilename))
{
return true;
}
return false;
}
private string MakeAbsolute(string filename)
{
if (filename == null || BaseDirectory == null)
@ -310,13 +299,7 @@ namespace ANX.Framework.Content.Pipeline.Tasks
private object ImportAsset(BuildItem item, string absoluteFilename, ContentImporterContext context)
{
string importerName = item.ImporterName;
if (string.IsNullOrEmpty(importerName))
{
importerName = ImporterManager.GuessImporterByFileExtension(Path.GetExtension(item.SourceFilename));
}
IContentImporter importer = this.ImporterManager.GetInstance(importerName);
IContentImporter importer = this.ImporterManager.GetInstance(item.ImporterName);
buildLogger.LogMessage("importing {0} with importer {1}", item.SourceFilename.ToString(), importer.GetType());
object result = importer.Import(absoluteFilename, context);
@ -326,7 +309,7 @@ namespace ANX.Framework.Content.Pipeline.Tasks
var identity = new ContentIdentity();
identity.SourceFilename = absoluteFilename;
buildLogger.LogWarning("", identity, "importer \"{0}\" didn't return a value.", importerName);
buildLogger.LogWarning("", identity, "importer \"{0}\" didn't return a value.", item.ImporterName);
}
return result;
@ -334,37 +317,30 @@ namespace ANX.Framework.Content.Pipeline.Tasks
private object Process(BuildItem item, string absoluteFilename, object importedObject, ContentProcessorContext context)
{
if (String.IsNullOrEmpty(item.ProcessorName) == false)
IContentProcessor instance = this.ProcessorManager.GetInstance(item.ProcessorName);
SetProcessorParameters(instance, item.ProcessorParameters);
buildLogger.LogMessage("building with processor {0}", instance.GetType());
if (!instance.InputType.IsAssignableFrom(importedObject.GetType()))
{
IContentProcessor instance = this.ProcessorManager.GetInstance(item.ProcessorName);
SetProcessorParameters(instance, item.ProcessorParameters);
buildLogger.LogMessage("building with processor {0}", instance.GetType());
if (!instance.InputType.IsAssignableFrom(importedObject.GetType()))
ContentIdentity identity = null;
if (importedObject is ContentItem)
{
ContentIdentity identity = null;
if (importedObject is ContentItem)
{
identity = ((ContentItem)importedObject).Identity;
}
identity = ((ContentItem)importedObject).Identity;
}
if (identity == null)
{
identity = new ContentIdentity();
identity.SourceFilename = absoluteFilename;
}
buildLogger.LogWarning("", identity, "The input type of the processor \"{0}\" is not assignable from the output type of the importer \"{1}\".", item.ProcessorName, item.ImporterName);
return null;
if (identity == null)
{
identity = new ContentIdentity();
identity.SourceFilename = absoluteFilename;
}
return instance.Process(importedObject, context);
}
else
{
return importedObject;
buildLogger.LogWarning("", identity, "The input type of the processor \"{0}\" is not assignable from the output type of the importer \"{1}\".", item.ProcessorName, item.ImporterName);
return null;
}
return instance.Process(importedObject, context);
}
private void SerializeAsset(BuildItem item, object assetData, string outputDirectory, string outputFilename)

View File

@ -194,7 +194,7 @@ namespace ANX.Framework.Content.Pipeline.Tasks
writer.WriteStartAttribute("Name");
writer.WriteValue(pair.Key);
writer.WriteEndAttribute();
writer.WriteValue(pair.Value);
writer.WriteValue(TypeDescriptor.GetConverter(pair.Value).ConvertToInvariantString(pair.Value));
writer.WriteEndElement();
}
writer.WriteEndElement();

View File

@ -82,6 +82,9 @@ namespace ANX.Framework.Content.Pipeline.Tasks
if (sourceAsset == null)
throw new ArgumentNullException("sourceAsset");
if (string.IsNullOrEmpty(sourceAsset.Filename))
throw new ArgumentNullException("sourceAsset.Filename");
var buildItem = new BuildItem()
{
AssetName = Path.GetFileNameWithoutExtension(sourceAsset.Filename),
@ -110,6 +113,9 @@ namespace ANX.Framework.Content.Pipeline.Tasks
if (sourceAsset == null)
throw new ArgumentNullException("sourceAsset");
if (string.IsNullOrEmpty(sourceAsset.Filename))
throw new ArgumentNullException("sourceAsset.Filename");
var buildItem = new BuildItem()
{
AssetName = assetName,

View File

@ -14,7 +14,6 @@ namespace ContentBuilder
public static bool TryGetAnxFrameworkPath(out Uri path)
{
path = null;
#if WINDOWS
var hklm = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64);
var key = hklm.OpenSubKey(@"SOFTWARE\Microsoft\.NETFramework\AssemblyFolders\ANX.Framework", false);
if (key == null)
@ -28,15 +27,27 @@ namespace ContentBuilder
path = new Uri(value);
return true;
}
#else
return false;
#endif
}
public static string GetOutputFileName(string outputDirectory, BuildItem buildItem)
public static string GetOutputFileName(string outputDirectory, string projectDirectory, BuildItem buildItem)
{
return Path.Combine(outputDirectory, Path.GetDirectoryName(buildItem.SourceFilename), buildItem.AssetName + ContentManager.Extension);
if (!Path.IsPathRooted(projectDirectory))
throw new ArgumentException("projectDirectory is not absolute: " + projectDirectory);
var assetName = buildItem.AssetName;
if (string.IsNullOrEmpty(assetName))
assetName = Path.GetFileNameWithoutExtension(buildItem.SourceFilename);
string relativeSourceFilename = buildItem.SourceFilename;
if (Path.IsPathRooted(relativeSourceFilename))
{
if (buildItem.SourceFilename.StartsWith(projectDirectory))
relativeSourceFilename = buildItem.SourceFilename.Substring(projectDirectory.Length).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
else
throw new ArgumentException(string.Format("The buildItem is not below the project root.\nProject root: {0}\nBuildItem: {1}", projectDirectory, buildItem.SourceFilename));
}
return Path.Combine(outputDirectory, Path.GetDirectoryName(relativeSourceFilename), assetName + ContentManager.Extension);
}
internal static string CreateSafeFileName(string text)

View File

@ -272,10 +272,17 @@ namespace ContentBuilder
buildContentTask.TargetPlatform = platform;
buildContentTask.BaseDirectory = new Uri(currentDirectory, UriKind.Absolute);
buildContentTask.PrepareAssetBuildCallback = (BuildContentTask sender, BuildItem item, out ContentImporterContext importerContext, out ContentProcessorContext processorContext) =>
buildContentTask.PrepareAssetBuildCallback = (BuildContentTask task, BuildItem item, out ContentImporterContext importerContext, out ContentProcessorContext processorContext) =>
{
importerContext = new DefaultContentImporterContext(sender.BuildLogger, intermediateDirectory, outputDirectory);
processorContext = new DefaultContentProcessorContext(sender, configurationName, intermediateDirectory, outputDirectory, BuildHelper.GetOutputFileName(outputDirectory, item));
if (String.IsNullOrEmpty(item.AssetName))
item.AssetName = Path.GetFileNameWithoutExtension(item.SourceFilename);
//Make sure the path is always relative (if possible); is important for the cache.
if (Path.IsPathRooted(item.SourceFilename) && item.SourceFilename.StartsWith(currentDirectory))
item.SourceFilename = item.SourceFilename.Substring(currentDirectory.Length).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
importerContext = new DefaultContentImporterContext(task.BuildLogger, intermediateDirectory, outputDirectory);
processorContext = new DefaultContentProcessorContext(task, configurationName, intermediateDirectory, outputDirectory, BuildHelper.GetOutputFileName(outputDirectory, currentDirectory, item));
};
@ -314,7 +321,7 @@ namespace ContentBuilder
buildContentTask.BuildLogger.LogMessage("Starting up to date check.");
foreach (var buildItem in itemsToBuild)
if (!buildCache.IsValid(buildItem, new Uri(BuildHelper.GetOutputFileName(outputDirectory, buildItem))))
if (!buildCache.IsValid(buildItem, new Uri(BuildHelper.GetOutputFileName(outputDirectory, currentDirectory, buildItem))))
{
buildContentTask.BuildLogger.LogMessage("{0} is not up to date.", buildItem.SourceFilename);
Exit(ExitCode.NotUpToDate);
@ -338,20 +345,14 @@ namespace ContentBuilder
if (!string.IsNullOrWhiteSpace(intermediateDirectory) && !Directory.Exists(intermediateDirectory))
Directory.CreateDirectory(intermediateDirectory);
if (debug)
try
{
buildContentTask.Execute(itemsToBuild, debug);
buildContentTask.Execute(itemsToBuild);
}
else
catch (Exception exc)
{
try
{
buildContentTask.Execute(itemsToBuild, debug);
}
catch (Exception exc)
{
buildContentTask.BuildLogger.LogWarning(null, null, exc.Message + "\n" + exc.StackTrace);
}
buildContentTask.BuildLogger.LogWarning(null, null, exc.Message + "\n" + exc.StackTrace);
Debugger.Break();
}
try

View File

@ -516,7 +516,7 @@ namespace ANX.Framework.Build
foreach (var buildItem in buildItems)
{
if (!buildCache.IsValid(buildItem, new Uri(BuildHelper.GetOutputFileName(activeConfiguration.OutputDirectory, buildItem), UriKind.Relative)))
if (!buildCache.IsValid(buildItem, new Uri(BuildHelper.GetOutputFileName(activeConfiguration.OutputDirectory, projectHome, buildItem), UriKind.Relative)))
return false;
}