Я создал задачу MergeSolution для MsBuild.Он предназначен для объединения нескольких решений в один файл решения.Я использую PLINQ для параллельного анализа и обработки всех решений.Я использую свой класс DisposableProject для доступа к файлам проекта внутри каждого решения, чтобы получить некоторые специфичные для проекта значения.Класс DisposableProject был создан для устранения проблем состояния гонки, поскольку некоторые файлы проекта включены в несколько решений, а также для устранения большого потребления памяти в процессе слияния.
/// <summary>
/// Adding disposable functionality to Microsof.Build.BuildEngine.Project class.
/// Every project file is locked during processing to get a rid of huge memory consumption and race condition issues.
/// Project files are unloaded from build engine to save memory.
/// </summary>
public class DisposableProject : Project, IDisposable
{
// Build engine - is it really needed?
private static Engine buildEngine = new Engine();
// Event used for thread safe Mutex creation
static AutoResetEvent loadingEvent = new AutoResetEvent(true);
// Project file name
private string FileName {get;set;}
// Dictionary with Mutex name - Mutex
static ConcurrentDictionary<string, Mutex> mutexLocks = new ConcurrentDictionary<string, Mutex>();
/// <summary>
/// DisposableProject constructor. Project file is loaded and named Mutex is created to lock file by its file name.
/// </summary>
/// <param name="projectFileName">MsBuild project file name</param>
public DisposableProject(string projectFileName)
: base(buildEngine)
{
// Use file name as mutex name
FileName = GetMutexName(projectFileName);
// Lock file during processing
// Lock Mutex creation because multiple threads can create duplicate Mutexes causing sync exceptions
loadingEvent.WaitOne();
// Check if mutex object already exists
if(!mutexLocks.ContainsKey(FileName))
mutexLocks[FileName] = new Mutex(false, FileName);
// Unlock mutex creation
loadingEvent.Set();
// Wait for file
mutexLocks[FileName].WaitOne();
// Load project file
this.Load(projectFileName, ProjectLoadSettings.IgnoreMissingImports);
}
/// <summary>
/// Unload MsBuild project file from memory and Mutex (file lock) is released.
/// </summary>
public void Dispose()
{
// Unload from build engine to save memory
this.ParentEngine.UnloadProject(this);
// Unlock file
mutexLocks[FileName].ReleaseMutex();
}
/// <summary>
/// Create mutex name from file name.
/// </summary>
/// <param name="pathName">File name</param>
/// <returns>Mutex name</returns>
private string GetMutexName(string pathName)
{
// Replace directory separator chars with underscore
return pathName.Replace(Path.DirectorySeparatorChar, '_');
}
}
Иногда моя задача MergeSolutions терпит неудачу со следующим исключением во время загрузки первого файла проекта:
InvalidProjectFileException: Invalid static method invocation syntax: "$([Microsoft.Build.Utilities.ToolLocationHelper]::GetPathToStandardLibraries($(TargetFrameworkIdentifier), $(TargetFrameworkVersion), $(TargetFrameworkProfile)))". Static method invocation should be of the form: $([FullTypeName]::Method()), e.g. $([System.IO.Path]::Combine(`a`, `b`))\r
at Microsoft.Build.BuildEngine.Shared.ProjectErrorUtilities.VerifyThrowInvalidProject(Boolean condition, String errorSubCategoryResourceName, XmlNode xmlNode, String resourceName, Object arg0)\r
at Microsoft.Build.BuildEngine.Expander.Function.ExtractPropertyFunction(String expressionFunction, Object propertyValue)\r
at Microsoft.Build.BuildEngine.Expander.ExpandPropertyBody(String propertyBody, Object propertyValue, BuildPropertyGroup properties, ExpanderOptions options)\r
at Microsoft.Build.BuildEngine.Expander.ExpandPropertiesLeaveTypedAndEscaped(String expression, XmlNode expressionNode)\r
at Microsoft.Build.BuildEngine.Expander.ExpandAllIntoStringLeaveEscaped(String expression, XmlNode expressionNode)\r
at Microsoft.Build.BuildEngine.BuildPropertyGroup.Evaluate(BuildPropertyGroup evaluatedPropertyBag, Hashtable conditionedPropertiesTable, ProcessingPass pass)\r
at Microsoft.Build.BuildEngine.Project.ProcessProjectChildren(XmlElement projectElement, String projectDirectoryLocation, Boolean importedProject)\r
at Microsoft.Build.BuildEngine.Project.ProcessImportElement(XmlElement importElement, String projectDirectoryLocation, Boolean importedProject)\r
at Microsoft.Build.BuildEngine.Project.ProcessProjectChildren(XmlElement projectElement, String projectDirectoryLocation, Boolean importedProject)\r
at Microsoft.Build.BuildEngine.Project.ProcessImportElement(XmlElement importElement, String projectDirectoryLocation, Boolean importedProject)\r
at Microsoft.Build.BuildEngine.Project.ProcessProjectChildren(XmlElement projectElement, String projectDirectoryLocation, Boolean importedProject)\r
at Microsoft.Build.BuildEngine.Project.ProcessMainProjectElement()\r
at Microsoft.Build.BuildEngine.Project.InternalLoadFromXmlDocument(XmlDocument projectXml, ProjectLoadSettings projectLoadSettings)\r
at Microsoft.Build.BuildEngine.Project.Load(String projectFileName, BuildEventContext buildEventContext, ProjectLoadSettings projectLoadSettings)\r
at XXX.Build.Common.Types.DisposableProject..ctor(String projectFileName) in D:\XXX\DisposableProject.cs:line 35\r
Задача MergeSolution всегда выдает это исключение в начале процесса слияния, и это происходит толькоиногда.Это проблема инициализации BuildEngine, я думаю.Конструктор BuildEngine, вероятно, не является потокобезопасным.Я использую конструктор MSBuild по умолчанию.Есть идеи как это решить?