File: Compilation\CodeDirectoryCompiler.cs
Project: ndp\fx\src\xsp\system\Web\System.Web.csproj (System.Web)
//------------------------------------------------------------------------------
// <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);
        }
    }
}
 
}