e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
824 lines
33 KiB
C#
824 lines
33 KiB
C#
//------------------------------------------------------------------------------
|
|
// <copyright file="BuildProvidersCompiler.cs" company="Microsoft">
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// </copyright>
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
namespace System.Web.Compilation {
|
|
|
|
using System;
|
|
using System.IO;
|
|
using System.Collections;
|
|
using System.Collections.Concurrent;
|
|
using System.Collections.Specialized;
|
|
using System.Reflection;
|
|
using System.Globalization;
|
|
using System.CodeDom;
|
|
using System.CodeDom.Compiler;
|
|
using System.Configuration;
|
|
using System.Linq;
|
|
using System.Runtime.ExceptionServices;
|
|
using System.Threading.Tasks;
|
|
using System.Web.Hosting;
|
|
using System.Web.Util;
|
|
using System.Web.Caching;
|
|
using System.Web.UI;
|
|
using System.Web.Configuration;
|
|
|
|
internal class BuildProvidersCompiler {
|
|
private ICollection _buildProviders;
|
|
private VirtualPath _configPath;
|
|
private bool _supportLocalization;
|
|
|
|
// The set of assemblies that we need to reference when compiling
|
|
private ICollection _referencedAssemblies;
|
|
internal ICollection ReferencedAssemblies { get { return _referencedAssemblies; } }
|
|
|
|
private AssemblyBuilder _assemblyBuilder;
|
|
|
|
// Key: CultureName string, Value: AssemblyBuilder
|
|
private IDictionary _satelliteAssemblyBuilders = null;
|
|
|
|
// If this is set, we only generate the source files into this directory
|
|
// without compiling them.
|
|
// This is used to implement ClientBuildManager.GetCodeDirectoryInformation
|
|
private string _generatedFilesDir;
|
|
|
|
internal BuildProvidersCompiler(VirtualPath configPath, string outputAssemblyName) :
|
|
this(configPath, false, outputAssemblyName) { }
|
|
|
|
internal BuildProvidersCompiler(VirtualPath configPath, bool supportLocalization,
|
|
string outputAssemblyName) {
|
|
_configPath = configPath;
|
|
_supportLocalization = supportLocalization;
|
|
_compConfig = MTConfigUtil.GetCompilationConfig(_configPath);
|
|
_referencedAssemblies = BuildManager.GetReferencedAssemblies(CompConfig);
|
|
_outputAssemblyName = outputAssemblyName;
|
|
}
|
|
|
|
internal BuildProvidersCompiler(VirtualPath configPath, bool supportLocalization,
|
|
string generatedFilesDir, int index) {
|
|
_configPath = configPath;
|
|
_supportLocalization = supportLocalization;
|
|
_compConfig = MTConfigUtil.GetCompilationConfig(_configPath);
|
|
_referencedAssemblies = BuildManager.GetReferencedAssemblies(CompConfig, index);
|
|
_generatedFilesDir = generatedFilesDir;
|
|
}
|
|
|
|
// The <compilation> config section for the set of build providers that we handle
|
|
private CompilationSection _compConfig;
|
|
internal CompilationSection CompConfig { get { return _compConfig; } }
|
|
|
|
private string _outputAssemblyName;
|
|
internal string OutputAssemblyName {
|
|
get { return _outputAssemblyName; }
|
|
}
|
|
|
|
private bool CbmGenerateOnlyMode {
|
|
get { return _generatedFilesDir != null; }
|
|
}
|
|
|
|
internal void SetBuildProviders(ICollection buildProviders) {
|
|
_buildProviders = buildProviders;
|
|
}
|
|
|
|
private void ProcessBuildProviders() {
|
|
|
|
CompilerType compilerType = null;
|
|
BuildProvider firstLanguageBuildProvider = null;
|
|
|
|
// First, delete all the existing satellite assemblies of the assembly
|
|
// we're about to build (VSWhidbey 87022) (only if it has a fixed name)
|
|
if (OutputAssemblyName != null) {
|
|
Debug.Assert(!CbmGenerateOnlyMode);
|
|
StandardDiskBuildResultCache.RemoveSatelliteAssemblies(OutputAssemblyName);
|
|
}
|
|
|
|
// List of BuildProvider's that don't ask for a specific language
|
|
ArrayList languageFreeBuildProviders = null;
|
|
|
|
foreach (BuildProvider buildProvider in _buildProviders) {
|
|
|
|
// If it's an InternalBuildProvider, give it the assembly references early on
|
|
buildProvider.SetReferencedAssemblies(_referencedAssemblies);
|
|
|
|
// Instruct the internal build providers to continue processing for more parse errors.
|
|
if (!BuildManager.ThrowOnFirstParseError) {
|
|
InternalBuildProvider provider = buildProvider as InternalBuildProvider;
|
|
if (provider != null) {
|
|
provider.ThrowOnFirstParseError = false;
|
|
}
|
|
}
|
|
|
|
// Get the language and culture
|
|
CompilerType ctwp = BuildProvider.GetCompilerTypeFromBuildProvider(buildProvider);
|
|
|
|
// Only look for a culture if we're supposed to (basically, in the resources directories)
|
|
string cultureName = null;
|
|
if (_supportLocalization)
|
|
cultureName = buildProvider.GetCultureName();
|
|
|
|
// Is it asking for a specific language?
|
|
if (ctwp != null) {
|
|
|
|
// If it specifies a language, it can't also have a culture
|
|
if (cultureName != null) {
|
|
throw new HttpException(SR.GetString(SR.Both_culture_and_language, BuildProvider.GetDisplayName(buildProvider)));
|
|
}
|
|
|
|
// Do we already know the language we'll be using
|
|
if (compilerType != null) {
|
|
|
|
// If it's different from the current one, fail
|
|
if (!ctwp.Equals(compilerType)) {
|
|
throw new HttpException(SR.GetString(SR.Inconsistent_language,
|
|
BuildProvider.GetDisplayName(buildProvider),
|
|
BuildProvider.GetDisplayName(firstLanguageBuildProvider)));
|
|
}
|
|
}
|
|
else {
|
|
// Keep track of the build provider of error handling purpose
|
|
firstLanguageBuildProvider = buildProvider;
|
|
|
|
// Keep track of the language
|
|
compilerType = ctwp;
|
|
_assemblyBuilder = compilerType.CreateAssemblyBuilder(
|
|
CompConfig, _referencedAssemblies, _generatedFilesDir, OutputAssemblyName);
|
|
}
|
|
}
|
|
else {
|
|
if (cultureName != null) {
|
|
// Ignore the culture files in generate-only mode
|
|
if (CbmGenerateOnlyMode)
|
|
continue;
|
|
|
|
if (_satelliteAssemblyBuilders == null) {
|
|
_satelliteAssemblyBuilders = new Hashtable(
|
|
StringComparer.OrdinalIgnoreCase);
|
|
}
|
|
|
|
// Check if we already have an assembly builder for this culture
|
|
AssemblyBuilder satelliteAssemblyBuilder =
|
|
(AssemblyBuilder) _satelliteAssemblyBuilders[cultureName];
|
|
|
|
// If not, create one and store it in the hashtable
|
|
if (satelliteAssemblyBuilder == null) {
|
|
satelliteAssemblyBuilder = CompilerType.GetDefaultAssemblyBuilder(
|
|
CompConfig, _referencedAssemblies, _configPath, OutputAssemblyName);
|
|
satelliteAssemblyBuilder.CultureName = cultureName;
|
|
_satelliteAssemblyBuilders[cultureName] = satelliteAssemblyBuilder;
|
|
}
|
|
|
|
satelliteAssemblyBuilder.AddBuildProvider(buildProvider);
|
|
continue;
|
|
}
|
|
|
|
if (_assemblyBuilder == null) {
|
|
// If this provider doesn't need a specific language, and we don't know
|
|
// the language yet, just keep track of it
|
|
if (languageFreeBuildProviders == null)
|
|
languageFreeBuildProviders = new ArrayList();
|
|
languageFreeBuildProviders.Add(buildProvider);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
_assemblyBuilder.AddBuildProvider(buildProvider);
|
|
}
|
|
|
|
// If we didn't get an AssemblyBuilder, use a default
|
|
if (_assemblyBuilder == null && languageFreeBuildProviders != null) {
|
|
_assemblyBuilder = CompilerType.GetDefaultAssemblyBuilder(
|
|
CompConfig, _referencedAssemblies, _configPath,
|
|
_generatedFilesDir, OutputAssemblyName);
|
|
}
|
|
|
|
// Add all the language free providers (if any) to the AssemblyBuilder
|
|
if (_assemblyBuilder != null && languageFreeBuildProviders != null) {
|
|
foreach (BuildProvider languageFreeBuildProvider in languageFreeBuildProviders) {
|
|
_assemblyBuilder.AddBuildProvider(languageFreeBuildProvider);
|
|
}
|
|
}
|
|
}
|
|
|
|
internal CompilerResults PerformBuild() {
|
|
|
|
ProcessBuildProviders();
|
|
|
|
// Build all the satellite assemblies
|
|
if (_satelliteAssemblyBuilders != null) {
|
|
int maxConcurrent = Math.Min(_satelliteAssemblyBuilders.Count, CompilationUtil.MaxConcurrentCompilations);
|
|
try {
|
|
Parallel.ForEach(_satelliteAssemblyBuilders.Values.Cast<AssemblyBuilder>(),
|
|
new ParallelOptions { MaxDegreeOfParallelism = maxConcurrent },
|
|
assemblyBuilder =>
|
|
{
|
|
assemblyBuilder.Compile();
|
|
});
|
|
}
|
|
catch (AggregateException ae) {
|
|
ExceptionDispatchInfo.Capture(ae.GetBaseException()).Throw();
|
|
}
|
|
}
|
|
|
|
// Build the main assembly
|
|
if (_assemblyBuilder != null)
|
|
return _assemblyBuilder.Compile();
|
|
|
|
return null;
|
|
}
|
|
|
|
internal void GenerateSources(out Type codeDomProviderType,
|
|
out CompilerParameters compilerParameters) {
|
|
|
|
ProcessBuildProviders();
|
|
|
|
// If we didn't get an AssemblyBuilder (happens when there was nothing to build),
|
|
// get a default one.
|
|
if (_assemblyBuilder == null) {
|
|
_assemblyBuilder = CompilerType.GetDefaultAssemblyBuilder(
|
|
CompConfig, _referencedAssemblies, _configPath,
|
|
_generatedFilesDir, null /*outputAssemblyName*/);
|
|
}
|
|
|
|
codeDomProviderType = _assemblyBuilder.CodeDomProviderType;
|
|
compilerParameters = _assemblyBuilder.GetCompilerParameters();
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* This class handles the batch compilation of one directory. It may
|
|
* produce several assemblies out of them, based on dependencies and language
|
|
* differences. All the BuildProvider's are expected to share the same
|
|
* configuration (i.e. they live in the same directory).
|
|
*/
|
|
internal class WebDirectoryBatchCompiler {
|
|
|
|
private DateTime _utcStart;
|
|
|
|
// The set of assemblies that we will link with
|
|
private ICollection _referencedAssemblies;
|
|
|
|
// The <compilation> config section for the set of build providers that we handle
|
|
private CompilationSection _compConfig;
|
|
|
|
// [VirtualPathString,InternalBuildProvider]
|
|
private IDictionary _buildProviders = new Hashtable(
|
|
StringComparer.OrdinalIgnoreCase);
|
|
|
|
private VirtualDirectory _vdir;
|
|
private ArrayList[] _nonDependentBuckets;
|
|
|
|
private bool _ignoreProvidersWithErrors;
|
|
|
|
// The set of parser errors detected during parsing.
|
|
private ParserErrorCollection _parserErrors;
|
|
|
|
// The first parse exceptions thrown during parsing.
|
|
private HttpParseException _firstException;
|
|
|
|
internal WebDirectoryBatchCompiler(VirtualDirectory vdir) {
|
|
_vdir = vdir;
|
|
|
|
_utcStart = DateTime.UtcNow;
|
|
|
|
_compConfig = MTConfigUtil.GetCompilationConfig(_vdir.VirtualPath);
|
|
|
|
_referencedAssemblies = BuildManager.GetReferencedAssemblies(_compConfig);
|
|
}
|
|
|
|
internal void SetIgnoreErrors() {
|
|
_ignoreProvidersWithErrors = true;
|
|
}
|
|
|
|
internal void Process() {
|
|
|
|
AddBuildProviders(true /*retryIfDeletionHappens*/);
|
|
|
|
// If there are no BuildProvider's, we're done
|
|
if (_buildProviders.Count == 0)
|
|
return;
|
|
|
|
BuildManager.ReportDirectoryCompilationProgress(_vdir.VirtualPathObject);
|
|
|
|
GetBuildResultDependencies();
|
|
ProcessDependencies();
|
|
|
|
foreach (ICollection buildProviders in _nonDependentBuckets) {
|
|
if (!CompileNonDependentBuildProviders(buildProviders))
|
|
break;
|
|
}
|
|
|
|
// Report all parse exceptions
|
|
if (_parserErrors != null && _parserErrors.Count > 0) {
|
|
Debug.Assert(!_ignoreProvidersWithErrors);
|
|
|
|
// Throw the first exception as inner exception along with the parse errors.
|
|
HttpParseException newException =
|
|
new HttpParseException(_firstException.Message, _firstException, _firstException.VirtualPath,
|
|
_firstException.Source, _firstException.Line);
|
|
|
|
// Add the rest of the parser errors to the exception.
|
|
// The first one is already added.
|
|
for (int i = 1; i < _parserErrors.Count; i++) {
|
|
newException.ParserErrors.Add(_parserErrors[i]);
|
|
}
|
|
|
|
// rethrow the new exception
|
|
throw newException;
|
|
}
|
|
}
|
|
|
|
private void AddBuildProviders(bool retryIfDeletionHappens) {
|
|
|
|
DiskBuildResultCache.ResetAssemblyDeleted();
|
|
|
|
foreach (VirtualFile vfile in _vdir.Files) {
|
|
|
|
// If it's already built and up to date, skip it
|
|
BuildResult result = null;
|
|
try {
|
|
result = BuildManager.GetVPathBuildResultFromCache(vfile.VirtualPathObject);
|
|
}
|
|
catch {
|
|
// Ignore the cached error in batch compilation mode, since we want to compile
|
|
// as many files as possible.
|
|
// But don't ignore it in CBM or precompile cases, since we always want to try
|
|
// to compile everything that had failed before.
|
|
if (!BuildManager.PerformingPrecompilation) {
|
|
// Skip it if an exception occurs (e.g. if a compile error was cached for it)
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (result != null)
|
|
continue;
|
|
|
|
BuildProvider buildProvider = BuildManager.CreateBuildProvider(vfile.VirtualPathObject,
|
|
_compConfig, _referencedAssemblies, false /*failIfUnknown*/);
|
|
|
|
// Non-supported file type
|
|
if (buildProvider == null)
|
|
continue;
|
|
|
|
// IgnoreFileBuildProvider's should never be created
|
|
Debug.Assert(!(buildProvider is IgnoreFileBuildProvider));
|
|
|
|
_buildProviders[vfile.VirtualPath] = buildProvider;
|
|
}
|
|
|
|
// If an assembly had to be deleted/renamed as a result of calling GetVPathBuildResultFromCache,
|
|
// me way need to run the AddBuildProviders logic again. The reason is that as a result of
|
|
// deleting the assembly, we may have invalidated other BuildResult that we had earlier found
|
|
// to be up to date (VSWhidbey 269297)
|
|
if (DiskBuildResultCache.InUseAssemblyWasDeleted) {
|
|
Debug.Assert(retryIfDeletionHappens);
|
|
|
|
// Only retry if we're doing precompilation. For standard batching, we can live
|
|
// with the fact that not everything will be built after we're done (and we want to
|
|
// be done as quickly as possible since the user is waiting).
|
|
if (retryIfDeletionHappens && BuildManager.PerformingPrecompilation) {
|
|
Debug.Trace("WebDirectoryBatchCompiler", "Rerunning AddBuildProviders for '" +
|
|
_vdir.VirtualPath + "' because an assembly was out of date.");
|
|
|
|
// Pass false for retryIfDeletionHappens to make sure we don't get in an
|
|
// infinite recursion.
|
|
AddBuildProviders(false /*retryIfDeletionHappens*/);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void CacheAssemblyResults(AssemblyBuilder assemblyBuilder, CompilerResults results) {
|
|
|
|
foreach (BuildProvider buildProvider in assemblyBuilder.BuildProviders) {
|
|
|
|
BuildResult result = buildProvider.GetBuildResult(results);
|
|
|
|
// If the provider didn't produce anything, ignore it
|
|
if (result == null)
|
|
continue;
|
|
|
|
// If CacheVPathBuildResult returns false, something was found to be invalidated
|
|
// and we need to abort the caching (VSWhidbey 578372)
|
|
if (!BuildManager.CacheVPathBuildResult(buildProvider.VirtualPathObject, result, _utcStart))
|
|
break;
|
|
|
|
#if DBG
|
|
if (results != null) {
|
|
if (DelayLoadType.Enabled) {
|
|
Debug.Trace("BuildManager", buildProvider.VirtualPath + " Delay Load Assembly");
|
|
} else {
|
|
Debug.Trace("BuildManager", buildProvider.VirtualPath + results.CompiledAssembly.EscapedCodeBase);
|
|
}
|
|
}
|
|
else {
|
|
Debug.Trace("BuildManager", buildProvider.VirtualPath + ": no assembly");
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
// Cache the various compile errors found during batching
|
|
private void CacheCompileErrors(AssemblyBuilder assemblyBuilder, CompilerResults results) {
|
|
|
|
BuildProvider previous = null;
|
|
|
|
// Go through all the compile errors
|
|
foreach (CompilerError error in results.Errors) {
|
|
|
|
// Skip warnings
|
|
if (error.IsWarning)
|
|
continue;
|
|
|
|
// Try to map the error back to a BuildProvider. If we can't, skip the error.
|
|
BuildProvider buildProvider = assemblyBuilder.GetBuildProviderFromLinePragma(error.FileName);
|
|
if (buildProvider == null)
|
|
continue;
|
|
|
|
// Only cache the error for template controls. Otherwise, for file types like
|
|
// asmx/ashx, it's too likely that two of them define the same class.
|
|
if (!(buildProvider is BaseTemplateBuildProvider))
|
|
continue;
|
|
|
|
// If the error is for the same page as the previous one, ignore it
|
|
if (buildProvider == previous)
|
|
continue;
|
|
previous = buildProvider;
|
|
|
|
// Create a new CompilerResults for this error
|
|
CompilerResults newResults = new CompilerResults(null /*tempFiles*/);
|
|
|
|
// Copy all the output to the new result. Note that this will include all the
|
|
// error lines, not just the ones for this BuildProvider. But that's not a big deal,
|
|
// and we can't easily filter the output here.
|
|
foreach (string s in results.Output)
|
|
newResults.Output.Add(s);
|
|
|
|
// Copy various other fields to the new CompilerResults object
|
|
newResults.PathToAssembly = results.PathToAssembly;
|
|
newResults.NativeCompilerReturnValue = results.NativeCompilerReturnValue;
|
|
|
|
// Add this error. It will be the only one in the CompilerResults object.
|
|
newResults.Errors.Add(error);
|
|
|
|
// Create a new HttpCompileException & BuildResultCompileError to wrap this error
|
|
HttpCompileException e = new HttpCompileException(newResults,
|
|
assemblyBuilder.GetGeneratedSourceFromBuildProvider(buildProvider));
|
|
BuildResult result = new BuildResultCompileError(buildProvider.VirtualPathObject, e);
|
|
|
|
// Add the dependencies to the compile error build provider, so that
|
|
// we will retry compilation when a dependency changes
|
|
buildProvider.SetBuildResultDependencies(result);
|
|
|
|
// Cache it
|
|
BuildManager.CacheVPathBuildResult(buildProvider.VirtualPathObject, result, _utcStart);
|
|
}
|
|
|
|
}
|
|
|
|
private void GetBuildResultDependencies() {
|
|
foreach (BuildProvider buildProvider in _buildProviders.Values) {
|
|
ICollection virtualPathDependencies = buildProvider.GetBuildResultVirtualPathDependencies();
|
|
if (virtualPathDependencies == null)
|
|
continue;
|
|
|
|
foreach (string virtualPathDependency in virtualPathDependencies) {
|
|
BuildProvider dependentBuildProvider = (BuildProvider) _buildProviders[virtualPathDependency];
|
|
|
|
if (dependentBuildProvider != null)
|
|
buildProvider.AddBuildProviderDependency(dependentBuildProvider);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Split the providers into non dependent buckets
|
|
private void ProcessDependencies() {
|
|
// First phase: compute levels in the dependency tree
|
|
|
|
int totaldepth = 0;
|
|
Hashtable depth = new Hashtable();
|
|
Stack stack = new Stack();
|
|
|
|
// compute depths
|
|
foreach (BuildProvider buildProvider in _buildProviders.Values) {
|
|
stack.Push(buildProvider);
|
|
|
|
while (stack.Count > 0) {
|
|
BuildProvider curnode = (BuildProvider)stack.Peek();
|
|
|
|
bool recurse = false;
|
|
int maxdepth = 0;
|
|
|
|
if (curnode.BuildProviderDependencies != null) {
|
|
foreach (BuildProvider child in curnode.BuildProviderDependencies) {
|
|
|
|
if (depth.ContainsKey(child)) {
|
|
if (maxdepth <= (int)depth[child])
|
|
maxdepth = (int)depth[child] + 1;
|
|
else if ((int)depth[child] == -1)
|
|
throw new HttpException(SR.GetString(SR.File_Circular_Reference, child.VirtualPath));
|
|
}
|
|
else {
|
|
recurse = true;
|
|
stack.Push(child);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (recurse)
|
|
depth[curnode] = -1; // being computed;
|
|
else {
|
|
stack.Pop();
|
|
depth[curnode] = maxdepth;
|
|
if (totaldepth <= maxdepth)
|
|
totaldepth = maxdepth + 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// drop into buckets by depth
|
|
_nonDependentBuckets = new ArrayList[totaldepth];
|
|
|
|
for (IDictionaryEnumerator en = (IDictionaryEnumerator)depth.GetEnumerator(); en.MoveNext();) {
|
|
int level = (int)en.Value;
|
|
|
|
if (_nonDependentBuckets[level] == null)
|
|
_nonDependentBuckets[level] = new ArrayList();
|
|
|
|
_nonDependentBuckets[level].Add(en.Key);
|
|
}
|
|
|
|
#if DBG
|
|
int i = 0;
|
|
foreach (ICollection buildProviders in _nonDependentBuckets) {
|
|
Debug.Trace("BuildManager", String.Empty);
|
|
Debug.Trace("BuildManager", "Bucket " + i + " contains " + buildProviders.Count + " files");
|
|
|
|
foreach (BuildProvider buildProvider in buildProviders)
|
|
Debug.Trace("BuildManager", buildProvider.VirtualPath);
|
|
i++;
|
|
}
|
|
#endif
|
|
|
|
}
|
|
|
|
private bool IsBuildProviderSkipable(BuildProvider buildProvider) {
|
|
|
|
// If another build provider depends on it, we should not skip it
|
|
if (buildProvider.IsDependedOn) return false;
|
|
|
|
// No one depends on it (at least in this directory)
|
|
|
|
// If it's a source file, skip it. We need to do this for v1 compatibility,
|
|
// since v1 VS projects contain many source files which have already been
|
|
// precompiled into bin, and that should not be compiled dynamically
|
|
if (buildProvider is SourceFileBuildProvider)
|
|
return true;
|
|
|
|
// For the same reason, skip resources
|
|
if (buildProvider is ResXBuildProvider)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
private bool CompileNonDependentBuildProviders(ICollection buildProviders) {
|
|
|
|
// Key: CompilerType, Value: AssemblyBuilder
|
|
IDictionary assemblyBuilders = new Hashtable();
|
|
|
|
// List of InternalBuildProvider's that don't ask for a specific language
|
|
ArrayList languageFreeBuildProviders = null;
|
|
|
|
// AssemblyBuilder used for providers that don't need a specific language
|
|
AssemblyBuilder defaultAssemblyBuilder = null;
|
|
|
|
bool hasParserErrors = false;
|
|
|
|
foreach (BuildProvider buildProvider in buildProviders) {
|
|
|
|
if (IsBuildProviderSkipable(buildProvider))
|
|
continue;
|
|
|
|
// Instruct the internal build providers to continue processing for more parse errors.
|
|
if (!BuildManager.ThrowOnFirstParseError) {
|
|
InternalBuildProvider provider = buildProvider as InternalBuildProvider;
|
|
if (provider != null) {
|
|
provider.ThrowOnFirstParseError = false;
|
|
}
|
|
}
|
|
|
|
CompilerType compilerType = null;
|
|
|
|
// Get the language
|
|
try {
|
|
compilerType = BuildProvider.GetCompilerTypeFromBuildProvider(
|
|
buildProvider);
|
|
}
|
|
catch (HttpParseException ex) {
|
|
// Ignore the error if we are in that mode.
|
|
if (_ignoreProvidersWithErrors) {
|
|
continue;
|
|
}
|
|
|
|
hasParserErrors = true;
|
|
|
|
// Remember the first parse exception
|
|
if (_firstException == null) {
|
|
_firstException = ex;
|
|
}
|
|
|
|
if (_parserErrors == null) {
|
|
_parserErrors = new ParserErrorCollection();
|
|
}
|
|
|
|
_parserErrors.AddRange(ex.ParserErrors);
|
|
|
|
continue;
|
|
}
|
|
catch {
|
|
// Ignore the error if we are in that mode.
|
|
if (_ignoreProvidersWithErrors) {
|
|
continue;
|
|
}
|
|
|
|
throw;
|
|
}
|
|
|
|
AssemblyBuilder assemblyBuilder = defaultAssemblyBuilder;
|
|
ICollection typeNames = buildProvider.GetGeneratedTypeNames();
|
|
|
|
// Is it asking for a specific language?
|
|
if (compilerType == null) {
|
|
// If this provider doesn't need a specific language, and we haven't yet created
|
|
// a default builder that is capable of building this, just keep track of it
|
|
if (defaultAssemblyBuilder == null || defaultAssemblyBuilder.IsBatchFull ||
|
|
defaultAssemblyBuilder.ContainsTypeNames(typeNames)) {
|
|
if (languageFreeBuildProviders == null) {
|
|
languageFreeBuildProviders = new ArrayList();
|
|
}
|
|
|
|
languageFreeBuildProviders.Add(buildProvider);
|
|
continue;
|
|
}
|
|
}
|
|
else {
|
|
// Check if we already have an assembly builder of the right type
|
|
assemblyBuilder = (AssemblyBuilder)assemblyBuilders[compilerType];
|
|
}
|
|
|
|
// Starts a new assemblyBuilder if the old one already contains another buildprovider
|
|
// that uses the same type name
|
|
if (assemblyBuilder == null || assemblyBuilder.IsBatchFull ||
|
|
assemblyBuilder.ContainsTypeNames(typeNames)) {
|
|
|
|
// If the assemblyBuilder is full, compile it.
|
|
if (assemblyBuilder != null) {
|
|
CompileAssemblyBuilder(assemblyBuilder);
|
|
}
|
|
|
|
AssemblyBuilder newBuilder = compilerType.CreateAssemblyBuilder(
|
|
_compConfig, _referencedAssemblies);
|
|
|
|
assemblyBuilders[compilerType] = newBuilder;
|
|
|
|
// Remember it as the default if we don't already have one,
|
|
// or if the default is already full, switch the default to the new one.
|
|
if (defaultAssemblyBuilder == null ||
|
|
defaultAssemblyBuilder == assemblyBuilder) {
|
|
|
|
defaultAssemblyBuilder = newBuilder;
|
|
}
|
|
|
|
assemblyBuilder = newBuilder;
|
|
}
|
|
|
|
assemblyBuilder.AddTypeNames(typeNames);
|
|
assemblyBuilder.AddBuildProvider(buildProvider);
|
|
}
|
|
|
|
// Don't try to compile providers, otherwise compile exceptions will be bubbled up,
|
|
// and we lose the parse errors.
|
|
if (hasParserErrors) {
|
|
return false;
|
|
}
|
|
|
|
// Handle all the left over language free providers
|
|
if (languageFreeBuildProviders != null) {
|
|
|
|
// Indicates whether the default assembly builder is not a language specific builder.
|
|
bool newDefaultAssemblyBuilder = (defaultAssemblyBuilder == null);
|
|
|
|
// Add language independent providers to the default assembly builder.
|
|
foreach (BuildProvider languageFreeBuildProvider in languageFreeBuildProviders) {
|
|
|
|
ICollection typeNames = languageFreeBuildProvider.GetGeneratedTypeNames();
|
|
|
|
// If we don't have a default language assembly builder, get one or
|
|
// starts a new assemblyBuilder if the old one already contains another buildprovider
|
|
// that uses the same type name
|
|
if (defaultAssemblyBuilder == null || defaultAssemblyBuilder.IsBatchFull ||
|
|
defaultAssemblyBuilder.ContainsTypeNames(typeNames)) {
|
|
|
|
// If the default assemblyBuilder is full, compile it.
|
|
if (defaultAssemblyBuilder != null) {
|
|
CompileAssemblyBuilder(defaultAssemblyBuilder);
|
|
}
|
|
|
|
defaultAssemblyBuilder = CompilerType.GetDefaultAssemblyBuilder(
|
|
_compConfig, _referencedAssemblies, _vdir.VirtualPathObject /*configPath*/,
|
|
null /*outputAssemblyName*/);
|
|
|
|
// the default assembly builder needs to be compiled separately.
|
|
newDefaultAssemblyBuilder = true;
|
|
}
|
|
|
|
defaultAssemblyBuilder.AddTypeNames(typeNames);
|
|
defaultAssemblyBuilder.AddBuildProvider(languageFreeBuildProvider);
|
|
}
|
|
|
|
// Only compile the default assembly builder if it's not part of language specific
|
|
// assembly builder (which will be compiled separately)
|
|
if (newDefaultAssemblyBuilder) {
|
|
// Compile the default assembly builder.
|
|
CompileAssemblyBuilder(defaultAssemblyBuilder);
|
|
}
|
|
}
|
|
|
|
CompileAssemblyBuilderParallel(assemblyBuilders.Values);
|
|
|
|
return true;
|
|
}
|
|
|
|
private void CompileAssemblyBuilderParallel(ICollection assemblyBuilders) {
|
|
|
|
int maxConcurrent = Math.Min(assemblyBuilders.Count, CompilationUtil.MaxConcurrentCompilations);
|
|
|
|
if (maxConcurrent < 2) {
|
|
// Not using Parallel.ForEach to avoid performance penalty
|
|
foreach (AssemblyBuilder assemblyBuilder in assemblyBuilders) {
|
|
CompileAssemblyBuilder(assemblyBuilder);
|
|
}
|
|
}
|
|
|
|
else {
|
|
// devdiv bug 666936: ASP.NET compilation related deadlock in Antares scenario.
|
|
// The main (current) thread holds a global compilation lock. CacheAssemblyResults and CacheCompileErrors may
|
|
// also require the global compilation lock in case of removing old data and thus may lead to deadlock.
|
|
// Fix: using dictionaries to collect the build results from parallel threads and do caching in the main thread.
|
|
ConcurrentDictionary<AssemblyBuilder, CompilerResults> buildResults = new ConcurrentDictionary<AssemblyBuilder, CompilerResults>();
|
|
ConcurrentDictionary<AssemblyBuilder, CompilerResults> buildErrors = new ConcurrentDictionary<AssemblyBuilder, CompilerResults>();
|
|
|
|
try {
|
|
Parallel.ForEach(assemblyBuilders.Cast<AssemblyBuilder>(),
|
|
new ParallelOptions { MaxDegreeOfParallelism = maxConcurrent },
|
|
builder =>
|
|
{
|
|
CompilerResults results;
|
|
try {
|
|
results = builder.Compile();
|
|
}
|
|
catch (HttpCompileException e) {
|
|
buildErrors[builder] = e.Results;
|
|
throw;
|
|
}
|
|
buildResults[builder] = results;
|
|
});
|
|
}
|
|
catch (AggregateException e) {
|
|
ExceptionDispatchInfo.Capture(e.GetBaseException()).Throw();
|
|
}
|
|
finally {
|
|
// Before throwing the aggregated compilation exception, cache the build results first
|
|
// This follows the execution order for the single thread case
|
|
foreach (var pair in buildErrors) {
|
|
CacheCompileErrors(pair.Key, pair.Value);
|
|
}
|
|
foreach (var pair in buildResults) {
|
|
CacheAssemblyResults(pair.Key, pair.Value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void CompileAssemblyBuilder(AssemblyBuilder builder) {
|
|
|
|
CompilerResults results;
|
|
|
|
try {
|
|
results = builder.Compile();
|
|
}
|
|
catch (HttpCompileException e) {
|
|
CacheCompileErrors(builder, e.Results);
|
|
throw;
|
|
}
|
|
|
|
CacheAssemblyResults(builder, results);
|
|
}
|
|
}
|
|
|
|
}
|