1054 lines
39 KiB
C#
1054 lines
39 KiB
C#
using ANX.Framework.Content.Pipeline;
|
|
using ANX.Framework.Content.Pipeline.Helpers;
|
|
using ANX.Framework.Content.Pipeline.Tasks;
|
|
using ANX.Framework.Design;
|
|
using ANX.Framework.Graphics;
|
|
using ANX.Framework.VisualStudio;
|
|
using ANX.Framework.VisualStudio.Nodes;
|
|
using ContentBuilder;
|
|
using Microsoft.VisualStudio.Project;
|
|
using Microsoft.VisualStudio.Shell;
|
|
using Microsoft.VisualStudio.Shell.Interop;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Diagnostics;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.Drawing.Design;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using System.Runtime.InteropServices;
|
|
using System.ServiceModel;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace ANX.Framework.Build
|
|
{
|
|
public sealed class BuildAppDomain : IDisposable
|
|
{
|
|
public class RemoteProxy : MarshalByRefObject
|
|
{
|
|
private List<string> processorNames = new List<string>();
|
|
private ProcessorManager processorManager = null;
|
|
private bool processorNamesInvalid = true;
|
|
|
|
private ImporterManager importerManager = null;
|
|
private bool importerManagerInvalid = true;
|
|
private List<string> importerNames = new List<string>();
|
|
|
|
public override object InitializeLifetimeService()
|
|
{
|
|
return null;
|
|
}
|
|
|
|
public void LoadProjectAssemblies(IEnumerable<string> assemblies, IEnumerable<Uri> searchPaths, IDictionary<string, Uri> redirectAssemblies, Action<string, Exception> errorCallback)
|
|
{
|
|
if (assemblies == null)
|
|
throw new ArgumentNullException("assemblies");
|
|
|
|
if (searchPaths == null)
|
|
throw new ArgumentNullException("searchPaths");
|
|
|
|
if (redirectAssemblies == null)
|
|
throw new ArgumentNullException("redirectAssemblies");
|
|
|
|
foreach (string assembly in assemblies)
|
|
{
|
|
try
|
|
{
|
|
Assembly assemblyInstance = null;
|
|
if (File.Exists(assembly))
|
|
{
|
|
string assemblyName = Path.GetFileNameWithoutExtension(assembly);
|
|
if (IsAssemblyAlreadyLoaded(assemblyName))
|
|
continue;
|
|
|
|
assemblyInstance = LoadFrom(assembly, redirectAssemblies);
|
|
}
|
|
else
|
|
{
|
|
if (IsAssemblyAlreadyLoaded(assembly))
|
|
continue;
|
|
|
|
Uri assemblyUri;
|
|
if (Uri.TryCreate(assembly.Split(',').First() + ".dll", UriKind.RelativeOrAbsolute, out assemblyUri))
|
|
{
|
|
foreach (Uri path in searchPaths)
|
|
{
|
|
Uri temp = new Uri(path, assemblyUri);
|
|
if (File.Exists(temp.LocalPath))
|
|
{
|
|
assemblyInstance = LoadFrom(temp.LocalPath, redirectAssemblies);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (assemblyInstance == null)
|
|
assemblyInstance = Assembly.Load(assembly);
|
|
}
|
|
else
|
|
{
|
|
assemblyInstance = Assembly.Load(assembly);
|
|
}
|
|
}
|
|
|
|
ClassPreloader.Preload(assemblyInstance);
|
|
}
|
|
catch (Exception exc)
|
|
{
|
|
if (errorCallback != null)
|
|
errorCallback(assembly, exc);
|
|
else
|
|
throw;
|
|
}
|
|
}
|
|
|
|
this.processorManager = null;
|
|
this.processorNamesInvalid = true;
|
|
this.importerManager = null;
|
|
this.importerManagerInvalid = true;
|
|
}
|
|
|
|
private bool IsAssemblyAlreadyLoaded(string assemblyName)
|
|
{
|
|
bool isFullName = assemblyName.Contains(',');
|
|
var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies();
|
|
|
|
if (isFullName)
|
|
{
|
|
foreach (var loadedAssembly in loadedAssemblies)
|
|
if (loadedAssembly.FullName == assemblyName)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
foreach (var loadedAssembly in loadedAssemblies)
|
|
if (loadedAssembly.GetName().Name == assemblyName)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private Assembly LoadFrom(string assembly, IDictionary<string, Uri> redirectAssemblies)
|
|
{
|
|
string assemblyName = assembly;
|
|
if (Path.IsPathRooted(assembly))
|
|
assemblyName = Path.GetFileNameWithoutExtension(assembly);
|
|
else if (assembly.Contains(','))
|
|
assemblyName = assembly.Split(',').First();
|
|
|
|
Uri redirectedPath;
|
|
if (redirectAssemblies.TryGetValue(assemblyName, out redirectedPath))
|
|
{
|
|
return Assembly.LoadFrom(redirectedPath.LocalPath);
|
|
}
|
|
else
|
|
{
|
|
return Assembly.LoadFrom(assembly);
|
|
}
|
|
}
|
|
|
|
public string GetAssemblyRuntimeVersion(string assemblyPath)
|
|
{
|
|
if (File.Exists(assemblyPath))
|
|
{
|
|
return Assembly.ReflectionOnlyLoadFrom(assemblyPath).ImageRuntimeVersion;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public IEnumerable<AssemblyName> GetReferencedAssemblies(string assembly)
|
|
{
|
|
if (File.Exists(assembly))
|
|
{
|
|
return Assembly.ReflectionOnlyLoadFrom(assembly).GetReferencedAssemblies();
|
|
}
|
|
else
|
|
{
|
|
return Assembly.ReflectionOnlyLoad(assembly).GetReferencedAssemblies();
|
|
}
|
|
}
|
|
|
|
public string GetExistingFilesFilter()
|
|
{
|
|
ImporterManager importerManager = new ImporterManager();
|
|
|
|
SortedDictionary<string, List<string>> supportedFileExtensions = new SortedDictionary<string, List<string>>();
|
|
List<string> allExtensions = new List<string>();
|
|
|
|
foreach (var valuePair in importerManager.AvailableImporters)
|
|
{
|
|
var attribute = valuePair.Value.GetCustomAttribute<ContentImporterAttribute>(true);
|
|
IEnumerable<string> fileExtensions = attribute.FileExtensions;
|
|
string category = attribute.Category;
|
|
|
|
if (fileExtensions.Count() == 0)
|
|
continue;
|
|
|
|
if (string.IsNullOrWhiteSpace(category))
|
|
{
|
|
category = "Other Files";
|
|
}
|
|
|
|
category = category.Trim();
|
|
|
|
List<string> extensions;
|
|
if (!supportedFileExtensions.TryGetValue(category, out extensions))
|
|
{
|
|
extensions = new List<string>();
|
|
supportedFileExtensions.Add(category, extensions);
|
|
}
|
|
|
|
foreach (var fileExtension in fileExtensions)
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(fileExtension))
|
|
{
|
|
string usableExtension = "*" + fileExtension;
|
|
if (!extensions.Contains(usableExtension))
|
|
{
|
|
extensions.Add(usableExtension);
|
|
if (!allExtensions.Contains(usableExtension))
|
|
{
|
|
allExtensions.Add(usableExtension);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
string allExtensionsJoined = string.Join(";", allExtensions);
|
|
string filter = "Content Project Files|" + allExtensionsJoined;
|
|
|
|
foreach (var valuePair in supportedFileExtensions)
|
|
{
|
|
string extensionsJoined = string.Join(";", valuePair.Value);
|
|
filter += "|" + valuePair.Key + "|" + extensionsJoined;
|
|
}
|
|
|
|
return filter;
|
|
}
|
|
|
|
public IEnumerable<string> GetProcessorNames()
|
|
{
|
|
if (processorNamesInvalid)
|
|
{
|
|
processorNames.Clear();
|
|
|
|
if (processorManager == null)
|
|
{
|
|
processorManager = new ProcessorManager();
|
|
}
|
|
|
|
foreach (var value in processorManager.AvailableProcessors)
|
|
{
|
|
processorNames.Add(value.Key);
|
|
}
|
|
|
|
processorNamesInvalid = false;
|
|
}
|
|
|
|
return processorNames;
|
|
}
|
|
|
|
public IEnumerable<string> GetImporterNames()
|
|
{
|
|
if (importerManagerInvalid)
|
|
{
|
|
importerNames.Clear();
|
|
|
|
if (importerManager == null)
|
|
{
|
|
importerManager = new ImporterManager();
|
|
}
|
|
|
|
foreach (var value in importerManager.AvailableImporters)
|
|
{
|
|
importerNames.Add(value.Key);
|
|
}
|
|
|
|
importerManagerInvalid = false;
|
|
}
|
|
|
|
return importerNames;
|
|
}
|
|
|
|
public string GetImporterName(string displayName)
|
|
{
|
|
if (importerManager == null)
|
|
{
|
|
importerManager = new ImporterManager();
|
|
}
|
|
|
|
return importerManager.GetImporterName(displayName);
|
|
}
|
|
|
|
public string GetImporterDisplayName(string importer)
|
|
{
|
|
if (importerManager == null)
|
|
{
|
|
importerManager = new ImporterManager();
|
|
}
|
|
|
|
return importerManager.GetImporterDisplayName(importer);
|
|
}
|
|
|
|
public string GetProcessorName(string displayName)
|
|
{
|
|
if (processorManager == null)
|
|
{
|
|
processorManager = new ProcessorManager();
|
|
}
|
|
|
|
return processorManager.GetProcessorName(displayName);
|
|
}
|
|
|
|
public string GetProcessorDisplayName(string processor)
|
|
{
|
|
if (processorManager == null)
|
|
{
|
|
processorManager = new ProcessorManager();
|
|
}
|
|
|
|
return processorManager.GetProcessorDisplayName(processor);
|
|
}
|
|
|
|
public string GetProcessorTypeName(string processor)
|
|
{
|
|
if (processorManager == null)
|
|
{
|
|
processorManager = new ProcessorManager();
|
|
}
|
|
|
|
return processorManager.GetInstance(processor).GetType().AssemblyQualifiedName;
|
|
}
|
|
|
|
public string GuessImporterByFileExtension(string fileName)
|
|
{
|
|
if (importerManager == null)
|
|
{
|
|
importerManager = new ImporterManager();
|
|
}
|
|
|
|
return importerManager.GuessImporterByFileExtension(fileName);
|
|
}
|
|
|
|
public string GetDefaultProcessorForImporter(string importer)
|
|
{
|
|
if (importerManager == null)
|
|
importerManager = new ImporterManager();
|
|
|
|
return importerManager.GetDefaultProcessor(importer);
|
|
}
|
|
|
|
public ICollection<ANX.Framework.VisualStudio.ProcessorParameter> GetProcessorParameters(string processor)
|
|
{
|
|
if (processorManager == null)
|
|
processorManager = new ProcessorManager();
|
|
|
|
List<ANX.Framework.VisualStudio.ProcessorParameter> result = new List<ANX.Framework.VisualStudio.ProcessorParameter>();
|
|
|
|
if (!string.IsNullOrEmpty(processor))
|
|
{
|
|
ANX.Framework.Content.Pipeline.ProcessorParameterCollection processorParams = processorManager.GetProcessorParameters(processor);
|
|
foreach (var parameter in processorParams)
|
|
{
|
|
string defaultValueText = null;
|
|
string valueText = null;
|
|
|
|
object processorInstance = processorManager.GetInstance(processor);
|
|
Type processorType = processorInstance.GetType();
|
|
Type propertyType = TypeHelper.GetType(parameter.PropertyType);
|
|
|
|
PropertyInfo property = processorType.GetProperty(parameter.PropertyName, propertyType);
|
|
|
|
TypeConverter converter = property.GetConverter();
|
|
if (converter != null)
|
|
{
|
|
defaultValueText = (string)converter.ConvertTo(parameter.DefaultValue, typeof(string));
|
|
valueText = (string)converter.ConvertTo(property.GetValue(processorInstance), typeof(string));
|
|
}
|
|
|
|
result.Add(new ANX.Framework.VisualStudio.ProcessorParameter(parameter.PropertyName, parameter.DisplayName, parameter.PropertyType, valueText, defaultValueText, parameter.Description));
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
public string GetConverterTypeName(string containerType, string propertyName)
|
|
{
|
|
Type type = TypeHelper.GetType(containerType);
|
|
PropertyInfo property = type.GetProperty(propertyName);
|
|
|
|
TypeConverter converter = property.GetConverter();
|
|
if (converter != null)
|
|
return converter.GetType().AssemblyQualifiedName;
|
|
|
|
return null;
|
|
}
|
|
|
|
private object CreateInstance(Type targetType, Type typeParam)
|
|
{
|
|
var parameterTypes = new Type[] { typeof(Type) };
|
|
|
|
ConstructorInfo constructor = targetType.GetConstructor(parameterTypes);
|
|
if (constructor != null)
|
|
{
|
|
return TypeDescriptor.CreateInstance(null, targetType, parameterTypes, new object[] { typeParam });
|
|
}
|
|
return TypeDescriptor.CreateInstance(null, targetType, null, null);
|
|
}
|
|
|
|
public IEnumerable<string> GetAssemblyLocations()
|
|
{
|
|
List<string> locations = new List<string>();
|
|
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
|
|
{
|
|
if (assembly.CodeBase == null)
|
|
{
|
|
locations.Add(assembly.Location);
|
|
}
|
|
else
|
|
{
|
|
locations.Add(new Uri(assembly.CodeBase).LocalPath);
|
|
}
|
|
}
|
|
|
|
return locations;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a proxy for an editor.
|
|
/// The returned value should be wrapped by <see cref="UITypeEditorWrapper"/>
|
|
/// </summary>
|
|
/// <param name="editorBaseType">The wanted base type for the editor. Currently only supports <see cref="UITypeEditor"/>.</param>
|
|
/// <param name="typeName"></param>
|
|
/// <param name="propertyName"></param>
|
|
/// <param name="converterProxy">Must be a proxy to the <see cref="WrappedConverter"/> class.</param>
|
|
/// <returns></returns>
|
|
public IProxy GetEditor(Type editorBaseType, string typeName, string propertyName, IProxy converterProxy)
|
|
{
|
|
if (editorBaseType != typeof(UITypeEditor))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
Type type = TypeHelper.GetType(typeName);
|
|
|
|
PropertyInfo property = type.GetProperty(propertyName);
|
|
|
|
UITypeEditor editor;
|
|
var attributes = property.GetCustomAttributes<EditorAttribute>(true);
|
|
foreach (var attribute in attributes)
|
|
{
|
|
if (attribute.EditorBaseTypeName == editorBaseType.AssemblyQualifiedName)
|
|
{
|
|
Type editorType = TypeHelper.GetType(attribute.EditorTypeName);
|
|
if (editorType != null && typeof(UITypeEditor).IsAssignableFrom(editorType))
|
|
{
|
|
editor = (UITypeEditor)this.CreateInstance(editorType, property.PropertyType);
|
|
if (editor != null)
|
|
{
|
|
return UITypeEditorWrapper.CreateProxy(editor, converterProxy);
|
|
}
|
|
else
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
editor = (UITypeEditor)TypeDescriptor.GetEditor(property.PropertyType, editorBaseType);
|
|
|
|
if (editor != null)
|
|
{
|
|
return UITypeEditorWrapper.CreateProxy(editor, converterProxy);
|
|
}
|
|
else
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public string[] GetPlatformDisplayNames()
|
|
{
|
|
return Utilities.GetTargetPlatformDisplayNames();
|
|
}
|
|
|
|
public string[] GetGraphicsProfilesNames()
|
|
{
|
|
return Enum.GetNames(typeof(GraphicsProfile));
|
|
}
|
|
|
|
public bool IsUpDoDate(string projectHome, IEnumerable<BuildItem> buildItems, Configuration activeConfiguration, ErrorLoggingHelper loggingHelper)
|
|
{
|
|
BuildContentTask task = new BuildContentTask();
|
|
task.TargetPlatform = activeConfiguration.Platform;
|
|
task.TargetProfile = activeConfiguration.Profile;
|
|
|
|
var buildCache = new AnxBuildCache(task.ImporterManager, task.ProcessorManager, task.ContentCompiler);
|
|
buildCache.ProjectHome = new Uri(projectHome, UriKind.Absolute);
|
|
|
|
task.BuildCache = buildCache;
|
|
|
|
string intermediateDirectory = Path.Combine(projectHome, "obj", CreateSafeFileName(activeConfiguration.Platform.ToDisplayName()), CreateSafeFileName(activeConfiguration.Name)) + Path.DirectorySeparatorChar;
|
|
|
|
var buildCacheUri = new Uri(new Uri(intermediateDirectory, UriKind.Absolute), new Uri("build.cache", UriKind.Relative));
|
|
|
|
try
|
|
{
|
|
buildCache.LoadCache(buildCacheUri);
|
|
}
|
|
catch
|
|
{
|
|
return false;
|
|
}
|
|
|
|
foreach (var buildItem in buildItems)
|
|
{
|
|
if (!buildCache.IsValid(buildItem, new Uri(BuildHelper.GetOutputFileName(activeConfiguration.OutputDirectory, projectHome, buildItem), UriKind.Relative)))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private string CreateSafeFileName(string text)
|
|
{
|
|
foreach (var unsafeChar in Path.GetInvalidFileNameChars())
|
|
text = text.Replace(unsafeChar, '_');
|
|
|
|
return text;
|
|
}
|
|
}
|
|
|
|
//pretty much the same as if one would use lock(appDomain) but as a public api.
|
|
public sealed class BuildDomainAquire : IDisposable
|
|
{
|
|
BuildAppDomain appDomain;
|
|
|
|
internal BuildDomainAquire(BuildAppDomain appDomain)
|
|
{
|
|
Monitor.Enter(appDomain);
|
|
this.appDomain = appDomain;
|
|
}
|
|
|
|
public RemoteProxy Proxy
|
|
{
|
|
get
|
|
{
|
|
return this.appDomain.proxyInstance;
|
|
}
|
|
}
|
|
|
|
public void Unload()
|
|
{
|
|
this.appDomain.Unload();
|
|
}
|
|
|
|
public void Initialize(string identifier)
|
|
{
|
|
appDomain.Initialize(identifier);
|
|
}
|
|
|
|
public T CreateInstanceAndUnwrap<T>()
|
|
{
|
|
return appDomain.CreateInstanceAndUnwrap<T>();
|
|
}
|
|
|
|
public List<Uri> SearchPaths
|
|
{
|
|
get { return appDomain.SearchPaths; }
|
|
}
|
|
|
|
public Dictionary<string, Uri> Redirects
|
|
{
|
|
get { return appDomain.Redirects; }
|
|
}
|
|
|
|
public String MakeRelativeToSearchPaths(String path)
|
|
{
|
|
if (path == null)
|
|
throw new ArgumentNullException("path");
|
|
|
|
Uri uri = new Uri(path);
|
|
if (!uri.IsAbsoluteUri)
|
|
throw new ArgumentException("uri must be absolute.");
|
|
|
|
return uri.MakeRelativeUri(SearchPaths).OriginalString;
|
|
}
|
|
|
|
public Uri MakeRelativeToSearchPaths(Uri uri)
|
|
{
|
|
if (uri == null)
|
|
throw new ArgumentNullException("uri");
|
|
|
|
if (!uri.IsAbsoluteUri)
|
|
throw new ArgumentException("uri must be absolute.");
|
|
|
|
return uri.MakeRelativeUri(SearchPaths);
|
|
}
|
|
|
|
public String MakeAbsoluteFromSearchPaths(String path)
|
|
{
|
|
if (path == null)
|
|
throw new ArgumentNullException("path");
|
|
|
|
Uri uri;
|
|
if (!Uri.TryCreate(path, UriKind.RelativeOrAbsolute, out uri))
|
|
return path;
|
|
|
|
if (uri.IsAbsoluteUri)
|
|
{
|
|
return path;
|
|
}
|
|
|
|
foreach (Uri searchPath in SearchPaths)
|
|
{
|
|
Uri tempPath = new Uri(searchPath, uri);
|
|
if (File.Exists(tempPath.LocalPath))
|
|
return tempPath.OriginalString;
|
|
}
|
|
|
|
return uri.OriginalString;
|
|
}
|
|
|
|
public Uri MakeAbsoluteFromSearchPaths(Uri uri)
|
|
{
|
|
if (uri == null)
|
|
throw new ArgumentNullException("uri");
|
|
|
|
if (uri.IsAbsoluteUri)
|
|
{
|
|
return uri;
|
|
}
|
|
|
|
foreach (Uri path in SearchPaths)
|
|
{
|
|
Uri tempPath = new Uri(path, uri);
|
|
if (File.Exists(tempPath.LocalPath))
|
|
return tempPath;
|
|
}
|
|
|
|
return uri;
|
|
}
|
|
|
|
public Uri[] ShadowCopyDirectories
|
|
{
|
|
get { return appDomain.ShadowCopyDirectories; }
|
|
set { appDomain.ShadowCopyDirectories = value; }
|
|
}
|
|
|
|
public void AddShadowCopyDirectory(Uri path)
|
|
{
|
|
appDomain.AddShadowCopyDirectory(path);
|
|
}
|
|
|
|
public void RemoveShadowCopyDirectory(Uri path)
|
|
{
|
|
appDomain.RemoveShadowCopyDirectory(path);
|
|
}
|
|
|
|
public void Release()
|
|
{
|
|
Monitor.Exit(appDomain);
|
|
}
|
|
|
|
void IDisposable.Dispose()
|
|
{
|
|
this.Release();
|
|
}
|
|
}
|
|
|
|
object buildLock = new object();
|
|
bool disposed = false;
|
|
AppDomain appDomain;
|
|
RemoteProxy proxyInstance;
|
|
List<Uri> searchPaths = new List<Uri>();
|
|
//string shadowCopyPath;
|
|
Uri[] sourceShadowCopyDirectories = new Uri[0];
|
|
Dictionary<string, Uri> redirects = new Dictionary<string, Uri>();
|
|
ServiceHost loggerServiceHost;
|
|
ContentProjectNode projectNode;
|
|
Process buildProcess;
|
|
|
|
public BuildAppDomain(ContentProjectNode projectNode)
|
|
{
|
|
this.SearchPaths.Add(new Uri(Path.GetDirectoryName(typeof(BuildAppDomain.RemoteProxy).Assembly.Location), UriKind.Absolute));
|
|
this.Redirects.Add(typeof(Color).Assembly.GetName().Name, new Uri(typeof(Color).Assembly.CodeBase, UriKind.Absolute)); //ANX.Framework
|
|
this.Redirects.Add(typeof(IContentProcessor).Assembly.GetName().Name, new Uri(typeof(IContentProcessor).Assembly.CodeBase, UriKind.Absolute)); //ANX.Framework.Content.Pipeline
|
|
|
|
this.projectNode = projectNode;
|
|
}
|
|
|
|
public BuildDomainAquire Aquire()
|
|
{
|
|
if (disposed)
|
|
throw new ObjectDisposedException("BuildAppDomain");
|
|
|
|
return new BuildDomainAquire(this);
|
|
}
|
|
|
|
public bool IsDisposed
|
|
{
|
|
get { return disposed; }
|
|
}
|
|
|
|
private List<Uri> SearchPaths
|
|
{
|
|
get
|
|
{
|
|
return searchPaths;
|
|
}
|
|
}
|
|
|
|
private Dictionary<string, Uri> Redirects
|
|
{
|
|
get
|
|
{
|
|
return redirects;
|
|
}
|
|
}
|
|
|
|
public bool IsBuildRunning
|
|
{
|
|
get
|
|
{
|
|
return this.buildProcess != null && !this.buildProcess.HasExited;
|
|
}
|
|
}
|
|
|
|
private void AddShadowCopyDirectory(Uri path)
|
|
{
|
|
if (path == null)
|
|
throw new ArgumentNullException("path");
|
|
|
|
if (!path.IsAbsoluteUri)
|
|
throw new ArgumentException("path must be absolute.");
|
|
|
|
var newDirs = new Uri[sourceShadowCopyDirectories.Length + 1];
|
|
Array.Copy(sourceShadowCopyDirectories, newDirs, sourceShadowCopyDirectories.Length);
|
|
newDirs[newDirs.Length - 1] = path;
|
|
|
|
ShadowCopyDirectories = newDirs;
|
|
}
|
|
|
|
private void RemoveShadowCopyDirectory(Uri path)
|
|
{
|
|
if (path == null)
|
|
throw new ArgumentNullException("path");
|
|
|
|
if (!path.IsAbsoluteUri)
|
|
throw new ArgumentException("path must be absolute.");
|
|
|
|
int index = Array.IndexOf(sourceShadowCopyDirectories, path);
|
|
if (index == -1)
|
|
return;
|
|
|
|
var newDirs = new Uri[sourceShadowCopyDirectories.Length - 1];
|
|
Array.Copy(sourceShadowCopyDirectories, newDirs, index);
|
|
if (newDirs.Length - index > 0)
|
|
Array.Copy(sourceShadowCopyDirectories, index + 1, newDirs, index, newDirs.Length - index);
|
|
|
|
ShadowCopyDirectories = newDirs;
|
|
}
|
|
|
|
//Disable obsolete code warning
|
|
//Settings the shadow copy path for an appDomain is declared obsolete, it says I should use the shadow copy path in the appDomainSetup.
|
|
//Doing that would require to recreate the appDomain whenever a new reference is added and to batch the adding of references.
|
|
//Doing it this way was just the easier way.
|
|
#pragma warning disable 618
|
|
|
|
private Uri[] ShadowCopyDirectories
|
|
{
|
|
get
|
|
{
|
|
return sourceShadowCopyDirectories.ToArray();
|
|
}
|
|
set
|
|
{
|
|
if (value == null)
|
|
{
|
|
sourceShadowCopyDirectories = new Uri[0];
|
|
appDomain.SetShadowCopyPath("");
|
|
}
|
|
else
|
|
{
|
|
if (!value.All((x) => x != null && x.IsAbsoluteUri))
|
|
{
|
|
throw new ArgumentException("All Uri's must be not null and absolute.");
|
|
}
|
|
|
|
sourceShadowCopyDirectories = value;
|
|
appDomain.SetShadowCopyPath(string.Join(";", sourceShadowCopyDirectories.Where((x) => x != null).Select((x) => x.LocalPath)));
|
|
}
|
|
}
|
|
}
|
|
|
|
//Not behind the lock because we only use that has been given in the parameters and we're not using any data from foreign assemblies.
|
|
//Another reason is, when we start debugging, some project properties get refreshed and we need access to the lock for that.
|
|
public void BuildContent(Configuration activeConfiguration, IEnumerable<string> files, ErrorLoggingHelper loggingHelper, bool debug, string target)
|
|
{
|
|
if (activeConfiguration == null)
|
|
throw new ArgumentNullException("activeConfiguration");
|
|
|
|
if (loggingHelper == null)
|
|
throw new ArgumentNullException("loggingHelper");
|
|
|
|
string projectHome = this.projectNode.ProjectHome;
|
|
List<string> arguments = new List<string>();
|
|
|
|
VisualStudioContentBuildLogger logger = new VisualStudioContentBuildLogger(loggingHelper);
|
|
|
|
arguments.Add(this.projectNode.AbsoluteProjectFilePath);
|
|
arguments.Add("-c:" + activeConfiguration.Name);
|
|
arguments.Add("-t:" + activeConfiguration.Platform);
|
|
arguments.Add("-cd:" + projectHome);
|
|
|
|
if (target == MsBuildTarget.Rebuild)
|
|
arguments.Add("-ForceRebuild");
|
|
else if (target == MsBuildTarget.Clean)
|
|
arguments.Add("-CleanCache");
|
|
|
|
if (files != null && files.Count() > 0)
|
|
{
|
|
arguments.Add("-DontAddProjectBuildItems");
|
|
|
|
foreach (string file in files)
|
|
{
|
|
if (file.StartsWith(projectHome))
|
|
arguments.Add(file.Substring(projectHome.Length));
|
|
else
|
|
arguments.Add(file);
|
|
}
|
|
}
|
|
|
|
StartContentBuilder(arguments, debug, logger);
|
|
}
|
|
|
|
private void StartContentBuilder(IList<string> arguments, bool debug, IContentBuildLogger logger, int? millisecondsTimeOut = null)
|
|
{
|
|
if (buildProcess != null)
|
|
{
|
|
if (buildProcess.HasExited)
|
|
this.EndBuild();
|
|
else
|
|
throw new InvalidOperationException("There is still a build process running.");
|
|
}
|
|
|
|
lock (buildLock)
|
|
{
|
|
string workingDir = Path.GetDirectoryName(this.GetType().Assembly.Location);
|
|
string exePath = Path.Combine(workingDir, "ContentBuilder.exe");
|
|
|
|
Uri loggerUri = new Uri("net.pipe://localhost/VisualStudio/" + Process.GetCurrentProcess().Id + "/");
|
|
|
|
if (loggerServiceHost != null)
|
|
loggerServiceHost.Close();
|
|
|
|
loggerServiceHost = new ServiceHost(logger, loggerUri);
|
|
try
|
|
{
|
|
loggerServiceHost.AddServiceEndpoint(typeof(IContentBuildLogger), new NetNamedPipeBinding(), "ContentBuildLogger");
|
|
|
|
loggerServiceHost.Open();
|
|
|
|
arguments.Add("-l:" + new Uri(loggerUri, new Uri("ContentBuildLogger", UriKind.RelativeOrAbsolute)).OriginalString);
|
|
|
|
if (debug)
|
|
arguments.Add("-Debug");
|
|
|
|
//Prepare the arguments to be used for process start.
|
|
var args = arguments.Select((x) => "\"" + Regex.Replace(x, "(\\\\*)(\\\\$|\")", "$1$1\\$2") + "\"");
|
|
|
|
if (debug)
|
|
{
|
|
//May have to change the debugger interface for newer versions of visual studio.
|
|
var debugger = this.projectNode.Site.GetService(typeof(SVsShellDebugger)) as IVsDebugger4;
|
|
|
|
var targetInfo = new VsDebugTargetInfo4()
|
|
{
|
|
bstrArg = string.Join(" ", args),
|
|
bstrCurDir = workingDir,
|
|
bstrExe = exePath,
|
|
guidLaunchDebugEngine = Microsoft.VisualStudio.VSConstants.DebugEnginesGuids.ManagedOnly_guid,
|
|
project = this.projectNode,
|
|
dlo = (uint)DEBUG_LAUNCH_OPERATION.DLO_CreateProcess,
|
|
};
|
|
|
|
VsDebugTargetProcessInfo[] result = new VsDebugTargetProcessInfo[1];
|
|
debugger.LaunchDebugTargets4(1, new VsDebugTargetInfo4[] { targetInfo }, result);
|
|
|
|
buildProcess = Process.GetProcessById((int)result[0].dwProcessId);
|
|
}
|
|
else
|
|
{
|
|
buildProcess = Process.Start(new ProcessStartInfo(exePath, string.Join(" ", args))
|
|
{
|
|
CreateNoWindow = true,
|
|
UseShellExecute = false,
|
|
WorkingDirectory = workingDir,
|
|
});
|
|
}
|
|
|
|
if (millisecondsTimeOut.HasValue)
|
|
buildProcess.WaitForExit(millisecondsTimeOut.Value);
|
|
else
|
|
buildProcess.WaitForExit();
|
|
}
|
|
catch
|
|
{
|
|
if (loggerServiceHost != null)
|
|
loggerServiceHost.Abort();
|
|
|
|
throw;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void EndBuild()
|
|
{
|
|
lock (buildLock)
|
|
{
|
|
if (buildProcess != null)
|
|
{
|
|
buildProcess.Close();
|
|
buildProcess = null;
|
|
}
|
|
|
|
if (loggerServiceHost != null)
|
|
{
|
|
loggerServiceHost.Close();
|
|
loggerServiceHost = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void PrepareBuild(bool cleanBuild)
|
|
{
|
|
if (cleanBuild)
|
|
return;
|
|
|
|
lock (buildLock)
|
|
{
|
|
ErrorLoggingHelper loggingHelper = new ErrorLoggingHelper(this.projectNode, this.projectNode.ErrorListProvider, null);
|
|
//Test if ANX.Framework or ANX.Framework.Content.Pipeline are referenced, because we must use our own version.
|
|
foreach (var reference in this.projectNode.GetReferenceContainer().EnumReferences())
|
|
{
|
|
if (Redirects.ContainsKey(reference.Caption))
|
|
{
|
|
loggingHelper.LogMessage("", null, reference.Url, ErrorLoggingHelper.MessageImportance.Info, PackageResources.GetString(PackageResources.AnxFrameworkAssembliesRedirected), reference.Caption);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#pragma warning restore 618
|
|
|
|
private void Unload()
|
|
{
|
|
EndBuild();
|
|
|
|
if (appDomain != null)
|
|
{
|
|
proxyInstance = null;
|
|
AppDomain.Unload(appDomain);
|
|
appDomain = null;
|
|
}
|
|
}
|
|
|
|
private void Initialize(string identifier)
|
|
{
|
|
if (appDomain != null)
|
|
{
|
|
throw new InvalidOperationException("The AppDomain is already initialized.");
|
|
}
|
|
|
|
AppDomainSetup setup = new AppDomainSetup();
|
|
setup.ApplicationBase = Path.GetDirectoryName(typeof(BuildAppDomain.RemoteProxy).Assembly.Location);
|
|
|
|
//if (SearchPath != null)
|
|
// setup.ApplicationBase = SearchPath.OriginalString;
|
|
|
|
setup.ApplicationName = "ANX Project " + identifier;
|
|
setup.ShadowCopyFiles = "true"; //Shadow copying so that files like dll's of referenced projects can still be updated.
|
|
|
|
//I can't shadow copy everything, otherwise I wouldn't be able to use the remoteProxy in the other appDomain because if the location of the loaded assembly differs from our own, I can't cast the proxy
|
|
//in our appDomain.
|
|
setup.ShadowCopyDirectories = string.Join(";", ShadowCopyDirectories.Select((x) => x.LocalPath));
|
|
//setup.CachePath = Path.Combine(Path.GetTempPath(), "ANX Project ShadowCopies");
|
|
|
|
//I might need to setup a custom shadow copy path, because the default path is restricted by space. But if I do that, I would also have to take care of the cleanup.
|
|
//this.shadowCopyPath = Path.Combine(setup.CachePath, setup.ApplicationName);
|
|
|
|
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
|
|
|
|
appDomain = AppDomain.CreateDomain("ANX Project " + identifier, null, setup);
|
|
appDomain.AssemblyResolve += BuildDomainAssemblyResolver.appDomain_AssemblyResolve;
|
|
//appDomain.TypeResolve += BuildDomainAssemblyResolver.appDomain_AssemblyResolve;
|
|
//appDomain.Load(typeof(ContentProject).Assembly.FullName); //ANX.Framework.Build
|
|
|
|
proxyInstance = (RemoteProxy)appDomain.CreateInstanceAndUnwrap(typeof(BuildAppDomain.RemoteProxy).Assembly.FullName, typeof(BuildAppDomain.RemoteProxy).FullName);
|
|
}
|
|
|
|
[Serializable]
|
|
static class BuildDomainAssemblyResolver
|
|
{
|
|
//Help placing project assemblies from the loadFrom context into the load context, only that way we can find the contained types later by using Type.GetType(string).
|
|
public static Assembly appDomain_AssemblyResolve(object sender, ResolveEventArgs args)
|
|
{
|
|
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
|
|
{
|
|
if (assembly.FullName == args.Name)
|
|
return assembly;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
//Help placing our own assemblies into the load context of the newly created appDomain
|
|
Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
|
|
{
|
|
var assembly = AppDomain.CurrentDomain.GetAssemblies().Where((x) => x.FullName == args.Name).FirstOrDefault();
|
|
if (assembly != null)
|
|
return assembly;
|
|
|
|
return null;
|
|
}
|
|
|
|
private T CreateInstanceAndUnwrap<T>()
|
|
{
|
|
return (T)appDomain.CreateInstanceAndUnwrap(typeof(T).Assembly.FullName, typeof(T).FullName);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
Unload();
|
|
|
|
disposed = true;
|
|
|
|
/*if (Directory.Exists(shadowCopyPath))
|
|
{
|
|
//If the directory is still used by another process, the delete should silently fail.
|
|
try
|
|
{
|
|
Directory.Delete(shadowCopyPath, true);
|
|
}
|
|
catch
|
|
{ }
|
|
}*/
|
|
}
|
|
}
|
|
}
|