e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
379 lines
16 KiB
C#
379 lines
16 KiB
C#
//------------------------------------------------------------------------------
|
|
// <copyright file="CodeDirectoryCompiler.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.CodeDom.Compiler;
|
|
using System.Configuration;
|
|
using System.Globalization;
|
|
using System.Web.Configuration;
|
|
using System.Reflection;
|
|
using System.Web.Hosting;
|
|
using System.Web.Util;
|
|
using System.Web.UI;
|
|
|
|
// The different types of directory that we treat as 'Code' (with minor differences)
|
|
internal enum CodeDirectoryType {
|
|
MainCode, // The main /code directory
|
|
SubCode, // Code subdirectories registered to be compiled separately
|
|
AppResources, // The /Resources directory
|
|
LocalResources, // A /LocalResources directory (at any level)
|
|
WebReferences // The /WebReferences directory
|
|
}
|
|
|
|
internal class CodeDirectoryCompiler {
|
|
|
|
private VirtualPath _virtualDir;
|
|
private CodeDirectoryType _dirType;
|
|
private StringSet _excludedSubdirectories;
|
|
|
|
private BuildProvidersCompiler _bpc;
|
|
private BuildProviderSet _buildProviders = new BuildProviderSet();
|
|
|
|
private bool _onlyBuildLocalizedResources;
|
|
|
|
static internal BuildResultMainCodeAssembly _mainCodeBuildResult;
|
|
|
|
internal static bool IsResourceCodeDirectoryType(CodeDirectoryType dirType) {
|
|
return dirType == CodeDirectoryType.AppResources || dirType == CodeDirectoryType.LocalResources;
|
|
}
|
|
|
|
internal static Assembly GetCodeDirectoryAssembly(VirtualPath virtualDir,
|
|
CodeDirectoryType dirType, string assemblyName,
|
|
StringSet excludedSubdirectories, bool isDirectoryAllowed) {
|
|
|
|
string physicalDir = virtualDir.MapPath();
|
|
|
|
if (!isDirectoryAllowed) {
|
|
|
|
// The directory should never exist in a precompiled app
|
|
if (Directory.Exists(physicalDir)) {
|
|
throw new HttpException(SR.GetString(SR.Bar_dir_in_precompiled_app, virtualDir));
|
|
}
|
|
}
|
|
|
|
bool supportLocalization = IsResourceCodeDirectoryType(dirType);
|
|
|
|
// Determine the proper cache key based on the type of directory we're processing
|
|
string cacheKey = assemblyName;
|
|
|
|
// Try the cache first
|
|
BuildResult result = BuildManager.GetBuildResultFromCache(cacheKey);
|
|
Assembly resultAssembly = null;
|
|
|
|
// If it's cached, just return it
|
|
if (result != null) {
|
|
|
|
// It should always be a BuildResultCompiledAssembly, though if there is
|
|
// a VirtualPathProvider doing very bad things, it may not (VSWhidbey 341701)
|
|
Debug.Assert(result is BuildResultCompiledAssembly);
|
|
if (result is BuildResultCompiledAssembly) {
|
|
|
|
// If it's the main code assembly, keep track of it so we can later call
|
|
// the AppInitialize method
|
|
if (result is BuildResultMainCodeAssembly) {
|
|
Debug.Assert(dirType == CodeDirectoryType.MainCode);
|
|
Debug.Assert(_mainCodeBuildResult == null);
|
|
_mainCodeBuildResult = (BuildResultMainCodeAssembly) result;
|
|
}
|
|
|
|
resultAssembly = ((BuildResultCompiledAssembly)result).ResultAssembly;
|
|
|
|
if (!supportLocalization)
|
|
return resultAssembly;
|
|
|
|
// We found a preserved resource assembly. However, we may not be done,
|
|
// as the culture specific files may have changed.
|
|
|
|
// But don't make any further checks if the directory is not allowed (precomp secenario).
|
|
// In that case, we should always return the assembly (VSWhidbey 533498)
|
|
if (!isDirectoryAllowed)
|
|
return resultAssembly;
|
|
|
|
BuildResultResourceAssembly buildResultResAssembly = (BuildResultResourceAssembly)result;
|
|
|
|
string newResourcesDependenciesHash = HashCodeCombiner.GetDirectoryHash(virtualDir);
|
|
|
|
// If the resources hash (which includes satellites) is up to date, we're done
|
|
if (newResourcesDependenciesHash == buildResultResAssembly.ResourcesDependenciesHash)
|
|
return resultAssembly;
|
|
}
|
|
}
|
|
|
|
// If app was precompiled, don't attempt compilation
|
|
if (!isDirectoryAllowed)
|
|
return null;
|
|
|
|
// Check whether the virtual dir is mapped to a different application,
|
|
// which we don't support (VSWhidbey 218603). But don't do this for LocalResource (VSWhidbey 237935)
|
|
if (dirType != CodeDirectoryType.LocalResources && !StringUtil.StringStartsWithIgnoreCase(physicalDir, HttpRuntime.AppDomainAppPathInternal)) {
|
|
throw new HttpException(SR.GetString(SR.Virtual_codedir, virtualDir.VirtualPathString));
|
|
}
|
|
|
|
// If the directory doesn't exist, we may be done
|
|
if (!Directory.Exists(physicalDir)) {
|
|
|
|
// We're definitely done if it's not the main code dir
|
|
if (dirType != CodeDirectoryType.MainCode)
|
|
return null;
|
|
|
|
// If it is the main code dir, we're only done is there is no profile to compile
|
|
// since the profice gets built as part of the main assembly.
|
|
if (!ProfileBuildProvider.HasCompilableProfile)
|
|
return null;
|
|
}
|
|
|
|
|
|
// Otherwise, compile it
|
|
|
|
BuildManager.ReportDirectoryCompilationProgress(virtualDir);
|
|
|
|
DateTime utcStart = DateTime.UtcNow;
|
|
|
|
CodeDirectoryCompiler cdc = new CodeDirectoryCompiler(virtualDir,
|
|
dirType, excludedSubdirectories);
|
|
|
|
string outputAssemblyName = null;
|
|
|
|
if (resultAssembly != null) {
|
|
// If resultAssembly is not null, we are in the case where we just need to build
|
|
// the localized resx file in a resources dir (local or global)
|
|
Debug.Assert(supportLocalization);
|
|
outputAssemblyName = resultAssembly.GetName().Name;
|
|
cdc._onlyBuildLocalizedResources = true;
|
|
}
|
|
else {
|
|
outputAssemblyName = BuildManager.GenerateRandomAssemblyName(assemblyName);
|
|
}
|
|
|
|
BuildProvidersCompiler bpc =
|
|
new BuildProvidersCompiler(virtualDir, supportLocalization, outputAssemblyName);
|
|
|
|
cdc._bpc = bpc;
|
|
|
|
// Find all the build provider we want to compile from the code directory
|
|
cdc.FindBuildProviders();
|
|
|
|
// Give them to the BuildProvidersCompiler
|
|
bpc.SetBuildProviders(cdc._buildProviders);
|
|
|
|
// Compile them into an assembly
|
|
CompilerResults results = bpc.PerformBuild();
|
|
|
|
// Did we just compile something?
|
|
if (results != null) {
|
|
Debug.Assert(result == null);
|
|
Debug.Assert(resultAssembly == null);
|
|
|
|
// If there is already a loaded module with the same path, try to wait for it to be unloaded.
|
|
// Otherwise, we would end up loading this old assembly instead of the new one (VSWhidbey 554697)
|
|
DateTime waitLimit = DateTime.UtcNow.AddMilliseconds(3000);
|
|
for (;;) {
|
|
IntPtr hModule = UnsafeNativeMethods.GetModuleHandle(results.PathToAssembly);
|
|
if (hModule == IntPtr.Zero)
|
|
break;
|
|
|
|
Debug.Trace("CodeDirectoryCompiler", results.PathToAssembly + " is already loaded. Waiting a bit");
|
|
|
|
System.Threading.Thread.Sleep(250);
|
|
|
|
// Stop trying if the timeout was reached
|
|
if (DateTime.UtcNow > waitLimit) {
|
|
Debug.Trace("CodeDirectoryCompiler", "Timeout waiting for old assembly to unload: " + results.PathToAssembly);
|
|
throw new HttpException(SR.GetString(SR.Assembly_already_loaded, results.PathToAssembly));
|
|
}
|
|
}
|
|
|
|
resultAssembly = results.CompiledAssembly;
|
|
}
|
|
|
|
// It is possible that there was nothing to compile (and we're not in the
|
|
// satellite resources case)
|
|
if (resultAssembly == null)
|
|
return null;
|
|
|
|
// For the main code directory, use a special BuildResult that takes care of
|
|
// calling AppInitialize if it finds one
|
|
if (dirType == CodeDirectoryType.MainCode) {
|
|
// Keep track of it so we can later call the AppInitialize method
|
|
_mainCodeBuildResult = new BuildResultMainCodeAssembly(resultAssembly);
|
|
|
|
result = _mainCodeBuildResult;
|
|
}
|
|
else if (supportLocalization) {
|
|
result = new BuildResultResourceAssembly(resultAssembly);
|
|
}
|
|
else {
|
|
result = new BuildResultCompiledAssembly(resultAssembly);
|
|
}
|
|
|
|
result.VirtualPath = virtualDir;
|
|
|
|
// If compilations are optimized, we need to include the right dependencies, since we can no longer
|
|
// rely on everything getting wiped out when something in App_Code changes.
|
|
// But don't do this for local resources, since they have their own special way of
|
|
// dealing with dependencies (in BuildResultResourceAssembly.ComputeSourceDependenciesHashCode).
|
|
// It's crucial *not* to do it as it triggers a tricky infinite recursion due to the fact
|
|
// that GetBuildResultFromCacheInternal calls EnsureFirstTimeDirectoryInitForDependencies if
|
|
// there is at least one dependency
|
|
if (BuildManager.OptimizeCompilations && dirType != CodeDirectoryType.LocalResources) {
|
|
result.AddVirtualPathDependencies(new SingleObjectCollection(virtualDir.AppRelativeVirtualPathString));
|
|
}
|
|
|
|
// Top level assembly should not be cached to memory. But LocalResources are *not*
|
|
// top level files, and do benefit from memory caching
|
|
if (dirType != CodeDirectoryType.LocalResources)
|
|
result.CacheToMemory = false;
|
|
|
|
// Cache it for next time
|
|
BuildManager.CacheBuildResult(cacheKey, result, utcStart);
|
|
|
|
return resultAssembly;
|
|
}
|
|
|
|
// Call the AppInitialize method in the Code assembly if there is one
|
|
internal static void CallAppInitializeMethod() {
|
|
if (_mainCodeBuildResult != null)
|
|
_mainCodeBuildResult.CallAppInitializeMethod();
|
|
}
|
|
|
|
internal const string sourcesDirectoryPrefix = "Sources_";
|
|
|
|
internal static void GetCodeDirectoryInformation(
|
|
VirtualPath virtualDir, CodeDirectoryType dirType, StringSet excludedSubdirectories, int index,
|
|
out Type codeDomProviderType, out CompilerParameters compilerParameters,
|
|
out string generatedFilesDir) {
|
|
|
|
// Compute the full path to the directory we'll use to generate all
|
|
// the code files
|
|
generatedFilesDir = HttpRuntime.CodegenDirInternal + "\\" +
|
|
sourcesDirectoryPrefix + virtualDir.FileName;
|
|
|
|
bool supportLocalization = IsResourceCodeDirectoryType(dirType);
|
|
|
|
// the index is used to retrieve the correct referenced assemblies
|
|
BuildProvidersCompiler bpc = new BuildProvidersCompiler(virtualDir, supportLocalization,
|
|
generatedFilesDir, index);
|
|
|
|
CodeDirectoryCompiler cdc = new CodeDirectoryCompiler(virtualDir,
|
|
dirType, excludedSubdirectories);
|
|
cdc._bpc = bpc;
|
|
|
|
// Find all the build provider we want to compile from the code directory
|
|
cdc.FindBuildProviders();
|
|
|
|
// Give them to the BuildProvidersCompiler
|
|
bpc.SetBuildProviders(cdc._buildProviders);
|
|
|
|
// Generate all the sources into the directory generatedFilesDir
|
|
bpc.GenerateSources(out codeDomProviderType, out compilerParameters);
|
|
}
|
|
|
|
private CodeDirectoryCompiler(VirtualPath virtualDir, CodeDirectoryType dirType,
|
|
StringSet excludedSubdirectories) {
|
|
|
|
_virtualDir = virtualDir;
|
|
_dirType = dirType;
|
|
_excludedSubdirectories = excludedSubdirectories;
|
|
}
|
|
|
|
private void FindBuildProviders() {
|
|
|
|
// If we need to build the profile, add its build provider
|
|
if (_dirType == CodeDirectoryType.MainCode && ProfileBuildProvider.HasCompilableProfile) {
|
|
_buildProviders.Add(ProfileBuildProvider.Create());
|
|
|
|
}
|
|
|
|
VirtualDirectory vdir = HostingEnvironment.VirtualPathProvider.GetDirectory(_virtualDir);
|
|
ProcessDirectoryRecursive(vdir, true /*topLevel*/);
|
|
}
|
|
|
|
private void AddFolderLevelBuildProviders(VirtualDirectory vdir, FolderLevelBuildProviderAppliesTo appliesTo) {
|
|
BuildManager.AddFolderLevelBuildProviders(_buildProviders, vdir.VirtualPathObject,
|
|
appliesTo, _bpc.CompConfig, _bpc.ReferencedAssemblies);
|
|
}
|
|
|
|
private void ProcessDirectoryRecursive(VirtualDirectory vdir, bool topLevel) {
|
|
|
|
// If it's a WebReferences directory, handle it using a single WebReferencesBuildProvider
|
|
// instead of creating a different BuildProvider for each file.
|
|
if (_dirType == CodeDirectoryType.WebReferences) {
|
|
// Create a build provider for the current directory
|
|
BuildProvider buildProvider = new WebReferencesBuildProvider(vdir);
|
|
buildProvider.SetVirtualPath(vdir.VirtualPathObject);
|
|
_buildProviders.Add(buildProvider);
|
|
|
|
AddFolderLevelBuildProviders(vdir, FolderLevelBuildProviderAppliesTo.WebReferences);
|
|
}
|
|
else if (_dirType == CodeDirectoryType.AppResources) {
|
|
AddFolderLevelBuildProviders(vdir, FolderLevelBuildProviderAppliesTo.GlobalResources);
|
|
}
|
|
else if (_dirType == CodeDirectoryType.LocalResources) {
|
|
AddFolderLevelBuildProviders(vdir, FolderLevelBuildProviderAppliesTo.LocalResources);
|
|
}
|
|
else if (_dirType == CodeDirectoryType.MainCode || _dirType == CodeDirectoryType.SubCode) {
|
|
AddFolderLevelBuildProviders(vdir, FolderLevelBuildProviderAppliesTo.Code);
|
|
}
|
|
|
|
// Go through all the files in the directory
|
|
foreach (VirtualFileBase child in vdir.Children) {
|
|
|
|
if (child.IsDirectory) {
|
|
|
|
// If we are at the top level of this code directory, and the current
|
|
// subdirectory is in the exclude list, skip it
|
|
if (topLevel && _excludedSubdirectories != null &&
|
|
_excludedSubdirectories.Contains(child.Name)) {
|
|
continue;
|
|
}
|
|
|
|
// Exclude the special FrontPage directory (VSWhidbey 116727)
|
|
if (child.Name == "_vti_cnf")
|
|
continue;
|
|
|
|
ProcessDirectoryRecursive(child as VirtualDirectory, false /*topLevel*/);
|
|
continue;
|
|
}
|
|
|
|
// Don't look at individual files for WebReferences directories
|
|
if (_dirType == CodeDirectoryType.WebReferences)
|
|
continue;
|
|
|
|
// Skip neutral files if _onlyBuildLocalizedResources is true
|
|
if (IsResourceCodeDirectoryType(_dirType)) {
|
|
if (_onlyBuildLocalizedResources && System.Web.UI.Util.GetCultureName(child.VirtualPath) == null) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
BuildProvider buildProvider = BuildManager.CreateBuildProvider(child.VirtualPathObject,
|
|
(IsResourceCodeDirectoryType(_dirType)) ?
|
|
BuildProviderAppliesTo.Resources : BuildProviderAppliesTo.Code,
|
|
_bpc.CompConfig,
|
|
_bpc.ReferencedAssemblies, false /*failIfUnknown*/);
|
|
|
|
// Non-supported file type
|
|
if (buildProvider == null)
|
|
continue;
|
|
|
|
// For Page resources, don't generate a strongly typed class
|
|
if (_dirType == CodeDirectoryType.LocalResources && buildProvider is BaseResourcesBuildProvider) {
|
|
((BaseResourcesBuildProvider)buildProvider).DontGenerateStronglyTypedClass();
|
|
}
|
|
|
|
_buildProviders.Add(buildProvider);
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|