diff --git a/ANX.Framework.Content.Pipeline/Helpers/TypeHelper.cs b/ANX.Framework.Content.Pipeline/Helpers/TypeHelper.cs index 5a30c67b..177dc20a 100644 --- a/ANX.Framework.Content.Pipeline/Helpers/TypeHelper.cs +++ b/ANX.Framework.Content.Pipeline/Helpers/TypeHelper.cs @@ -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); + } } } diff --git a/ANX.Framework.Content.Pipeline/Tasks/AnxBuildCache.cs b/ANX.Framework.Content.Pipeline/Tasks/AnxBuildCache.cs index ef2bcc86..e7db6642 100644 --- a/ANX.Framework.Content.Pipeline/Tasks/AnxBuildCache.cs +++ b/ANX.Framework.Content.Pipeline/Tasks/AnxBuildCache.cs @@ -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(x.Key, TypeHelper.ConvertToInvariantString(x.Value)))); this.cacheData[new Uri(buildItem.SourceFilename, UriKind.RelativeOrAbsolute)] = data; } diff --git a/ANX.Framework.Content.Pipeline/Tasks/BuildContentTask.cs b/ANX.Framework.Content.Pipeline/Tasks/BuildContentTask.cs index 1b09d0be..db95ded0 100644 --- a/ANX.Framework.Content.Pipeline/Tasks/BuildContentTask.cs +++ b/ANX.Framework.Content.Pipeline/Tasks/BuildContentTask.cs @@ -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 itemsToBuild, bool throwExceptions = false) + public CompiledBuildItem[] Execute(IEnumerable 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) diff --git a/ANX.Framework.Content.Pipeline/Tasks/ContentProject.cs b/ANX.Framework.Content.Pipeline/Tasks/ContentProject.cs index 4c029f4f..a1a201ef 100644 --- a/ANX.Framework.Content.Pipeline/Tasks/ContentProject.cs +++ b/ANX.Framework.Content.Pipeline/Tasks/ContentProject.cs @@ -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(); diff --git a/ANX.Framework.Content.Pipeline/Tasks/DefaultContentProcessorContext.cs b/ANX.Framework.Content.Pipeline/Tasks/DefaultContentProcessorContext.cs index 1656b70f..1402fe18 100644 --- a/ANX.Framework.Content.Pipeline/Tasks/DefaultContentProcessorContext.cs +++ b/ANX.Framework.Content.Pipeline/Tasks/DefaultContentProcessorContext.cs @@ -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, diff --git a/Tools/ContentBuilder/BuildHelper.cs b/Tools/ContentBuilder/BuildHelper.cs index 2a9f1127..bf8a767d 100644 --- a/Tools/ContentBuilder/BuildHelper.cs +++ b/Tools/ContentBuilder/BuildHelper.cs @@ -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) diff --git a/Tools/ContentBuilder/Program.cs b/Tools/ContentBuilder/Program.cs index 4048cdf4..21962693 100644 --- a/Tools/ContentBuilder/Program.cs +++ b/Tools/ContentBuilder/Program.cs @@ -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 diff --git a/Visual Studio/ANXVisualStudioPackage/BuildAppDomain.cs b/Visual Studio/ANXVisualStudioPackage/BuildAppDomain.cs index acc54279..877a678b 100644 --- a/Visual Studio/ANXVisualStudioPackage/BuildAppDomain.cs +++ b/Visual Studio/ANXVisualStudioPackage/BuildAppDomain.cs @@ -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; }