File: Microsoft\Build\Tasks\Xaml\PartialClassGenerationTask.cs
Project: ndp\cdf\src\NetFx40\XamlBuildTask\XamlBuildTask.csproj (XamlBuildTask)
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
//------------------------------------------------------------
 
namespace Microsoft.Build.Tasks.Xaml
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Reflection;
    using System.Runtime;
    using System.Threading;
    using System.Xaml;
    using Microsoft.Build.Framework;
    using Microsoft.Build.Utilities;
    using Microsoft.VisualStudio.Activities;
 
    [Fx.Tag.XamlVisible(true)]
    public class PartialClassGenerationTask : Task
    {
        const string DefaultGeneratedSourceExtension = "g";
        List<ITaskItem> generatedResources = new List<ITaskItem>();
        List<ITaskItem> generatedCodeFiles = new List<ITaskItem>();
 
        // We will do Dev10 behavior if the new required property MSBuildProjectDirectory is NOT specified. This can happen
        // if a Dev10 version of the Microsoft.Xaml.Targets file is being used with Dev11 installed.
        bool supportExtensions = false;
        string msBuildProjectDirectory;
 
        public PartialClassGenerationTask()
        {
            this.GeneratedSourceExtension = DefaultGeneratedSourceExtension;
        }
 
        [Fx.Tag.KnownXamlExternal]
        [Output]
        public ITaskItem[] ApplicationMarkup { get; set; }
 
        public string AssemblyName
        { get; set; }
 
        public string[] KnownReferencePaths { get; set; }
 
        [Fx.Tag.KnownXamlExternal]
        [Output]
        public ITaskItem[] GeneratedResources
        {
            get
            {
                return generatedResources.ToArray();
            }
 
            set
            {
                generatedResources = new List<ITaskItem>(value);
            }
        }
        
        [Fx.Tag.KnownXamlExternal]
        [Output]
        public ITaskItem[] GeneratedCodeFiles
        {
            get
            {
                return generatedCodeFiles.ToArray();
            }
 
            set
            {
                generatedCodeFiles = new List<ITaskItem>(value);
            }
        }       
 
        public string GeneratedSourceExtension
        { get; set; }
 
        [Required]
        public string Language
        { get; set; }
 
        [Required]
        public string OutputPath
        { get; set; }
 
        // This is Required for Dev11, but to allow things to work with a Dev10 targets file, we are not marking it required.
        public string MSBuildProjectDirectory
        {
            get
            {
                return this.msBuildProjectDirectory;
            }
 
            set
            {
                this.msBuildProjectDirectory = value;
                // The fact that this property is being set indicates that a Dev11 version of the targets
                // file is being used, so we should not do Dev10 behavior.
                this.supportExtensions = true;
            }
        }
 
        [Fx.Tag.KnownXamlExternal]
        public ITaskItem[] References
        { get; set; }
 
        public string RootNamespace
        { get; set; }
 
        [Fx.Tag.KnownXamlExternal]
        public ITaskItem[] SourceCodeFiles
        { get; set; }
 
        [Output]
        public bool RequiresCompilationPass2
        { get; set; }
 
        public string BuildTaskPath
        { get; set; }
 
        public bool IsInProcessXamlMarkupCompile
        { get; set; }
 
        public ITaskItem[] XamlBuildTypeGenerationExtensionNames
        { get; set; }
 
        public ITaskItem[] XamlBuildTypeInspectionExtensionNames
        { get; set; }
 
        private static AppDomain inProcessAppDomain;
        private static Dictionary<string, DateTime> referencesTimeStampCache;
        private Object referencesCacheLock = new Object();
 
        public override bool Execute()
        {
            VSDesignerPerfEventProvider perfEventProvider = new VSDesignerPerfEventProvider();
            perfEventProvider.WriteEvent(VSDesignerPerfEvents.XamlBuildTaskExecuteStart);
 
            try
            {
                if (IsInProcessXamlMarkupCompile)
                {
                    bool acquiredLock = false;
                    try
                    {
                        Monitor.TryEnter(referencesCacheLock, ref acquiredLock);
                        if (acquiredLock)
                        {
                            return ReuseAppDomainAndExecute();
                        }
                        else
                        {
                            return GetAppDomainAndExecute();
                        }
                    }
                    finally
                    {
                        if (acquiredLock)
                        {
                            Monitor.Exit(referencesCacheLock);
                        }
                    }
                }
                else
                {
                    return GetAppDomainAndExecute();
                }
            }
            finally
            {
                perfEventProvider.WriteEvent(VSDesignerPerfEvents.XamlBuildTaskExecuteEnd);
            }
        }
            
        bool ReuseAppDomainAndExecute()
        {
            AppDomain appDomain = null;
            bool createdNewAppDomain = false;
            try
            {
                try
                {
                    appDomain = GetInProcessAppDomain(out createdNewAppDomain);
                    bool ret = ExecuteInternal(appDomain);
                    return ret;
                }
                catch (Exception e)
                {
                    if (Fx.IsFatal(e))
                    {
                        throw;
                    }
                    if (createdNewAppDomain)
                    {
                        XamlBuildTaskServices.LogException(this.Log, e.Message);
                        return false;
                    }
                    else
                    {
                        if (inProcessAppDomain != null)
                        {
                            AppDomain.Unload(inProcessAppDomain);
                            inProcessAppDomain = null;
                        }
                        return GetAppDomainAndExecute();
                    }
                }
            }
            finally
            {
                if (Log != null)
                {
                    Log.MarkAsInactive();
                }
            }
        }
 
        bool GetAppDomainAndExecute()
        {
            AppDomain appDomain = null;
            try
            {
                appDomain = CreateNewAppDomain();
                bool ret = ExecuteInternal(appDomain);
                return ret;
            }
            catch (Exception e)
            {
                if (Fx.IsFatal(e))
                {
                    throw;
                }
 
                XamlBuildTaskServices.LogException(this.Log, e.Message);
                return false;
            }
            finally
            {
                if (appDomain != null)
                {
                    AppDomain.Unload(appDomain);
                }
            }
        }
 
        bool ExecuteInternal(AppDomain appDomain)
        {
            PartialClassGenerationTaskInternal wrapper = (PartialClassGenerationTaskInternal)appDomain.CreateInstanceAndUnwrap(
                                                        Assembly.GetExecutingAssembly().FullName,
                                                        typeof(PartialClassGenerationTaskInternal).FullName);
 
            PopulateBuildArtifacts(wrapper);
 
            bool ret = wrapper.Execute();
 
            if (ret)
            {
                ExtractBuiltArtifacts(wrapper);
            }
            return ret;
        }
 
        AppDomain CreateNewAppDomain()
        {
            return XamlBuildTaskServices.CreateAppDomain("PartialClassAppDomain_" + Guid.NewGuid(), BuildTaskPath);
        }
        
        // For Intellisense builds, we re-use the AppDomain for successive builds instead of creating a new one every time, 
        // if the references have not changed (there are no new references and they have not been updated since the last build)
        // This method accesses the static referencesTimeStampCache (indirectly). 
        // To ensure thread safety, this method should be called inside a lock/monitor
        AppDomain GetInProcessAppDomain(out bool newAppDomain)
        {
            newAppDomain = false;
            if (inProcessAppDomain == null)
            {
                inProcessAppDomain = CreateNewAppDomain();
                newAppDomain = true;
                UpdateReferenceCache();
            }
            else if (AreReferencesChanged())
            {
                AppDomain.Unload(inProcessAppDomain);
                inProcessAppDomain = CreateNewAppDomain();
                newAppDomain = true;
                UpdateReferenceCache();
            }
            return inProcessAppDomain;
        }
 
        // This method accesses the static referencesTimeStampCache.
        // To ensure thread safety, this method should be called inside a lock/monitor
        bool AreReferencesChanged()
        {
            bool refsChanged = false;
            if (referencesTimeStampCache == null || referencesTimeStampCache.Count != References.Length)
            {
                refsChanged = true;
            }
            else
            {
                foreach (var reference in References)
                {
                    string fullPath = Path.GetFullPath(reference.ItemSpec);
                    DateTime timeStamp = File.GetLastWriteTimeUtc(fullPath);
                    if (!referencesTimeStampCache.ContainsKey(fullPath)
                        || timeStamp > referencesTimeStampCache[fullPath]
                        || timeStamp == DateTime.MinValue)
                    {
                        refsChanged = true;
                        break;
                    }
                }
            }
            return refsChanged;
        }
 
        // This method accesses the static referencesTimeStampCache.
        // To ensure thread safety, this method should be called inside a lock/monitor
        void UpdateReferenceCache()
        {
            referencesTimeStampCache = new Dictionary<string, DateTime>();
            foreach (var reference in References)
            {
                string fullPath = Path.GetFullPath(reference.ItemSpec);
                DateTime timeStamp = File.GetLastWriteTimeUtc(fullPath);
                referencesTimeStampCache.Add(fullPath, timeStamp);
            }
        }
 
        void PopulateBuildArtifacts(PartialClassGenerationTaskInternal wrapper)
        {
            IList<ITaskItem> applicationMarkup = null;
            if (this.ApplicationMarkup != null)
            {
                applicationMarkup = this.ApplicationMarkup
                    .Select(i => new DelegatingTaskItem(i) as ITaskItem).ToList();
            }
            wrapper.ApplicationMarkup = applicationMarkup;
 
            wrapper.BuildLogger = this.Log;
 
            wrapper.References = this.References
                .Select(i => new DelegatingTaskItem(i) as ITaskItem).ToList();
 
            IList<string> sourceCodeFiles = null;
            if (this.SourceCodeFiles != null)
            {
                sourceCodeFiles = new List<string>(this.SourceCodeFiles.Length);
                foreach (ITaskItem taskItem in this.SourceCodeFiles)
                {
                    sourceCodeFiles.Add(taskItem.ItemSpec);
                }
            }
            wrapper.SourceCodeFiles = sourceCodeFiles;
 
            wrapper.Language = this.Language;
            wrapper.AssemblyName = this.AssemblyName;
            wrapper.OutputPath = this.OutputPath;
            wrapper.RootNamespace = this.RootNamespace;
            wrapper.GeneratedSourceExtension = this.GeneratedSourceExtension;
            wrapper.IsInProcessXamlMarkupCompile = this.IsInProcessXamlMarkupCompile;
            wrapper.MSBuildProjectDirectory = this.MSBuildProjectDirectory;
            wrapper.XamlBuildTaskTypeGenerationExtensionNames = XamlBuildTaskServices.GetXamlBuildTaskExtensionNames(this.XamlBuildTypeGenerationExtensionNames);
            if (this.XamlBuildTypeInspectionExtensionNames != null && this.XamlBuildTypeInspectionExtensionNames.Length > 0)
            {
                wrapper.MarkupCompilePass2ExtensionsPresent = true;
            }
 
            wrapper.SupportExtensions = this.supportExtensions;
        }        
 
        void ExtractBuiltArtifacts(PartialClassGenerationTaskInternal wrapper)
        {
            foreach (string resource in wrapper.GeneratedResources)
            {
                this.generatedResources.Add(new TaskItem(resource));
            }
 
            foreach (string code in wrapper.GeneratedCodeFiles)
            {
                this.generatedCodeFiles.Add(new TaskItem(code));
            }
 
            this.RequiresCompilationPass2 = wrapper.RequiresCompilationPass2 ||
                (this.XamlBuildTypeInspectionExtensionNames != null && this.XamlBuildTypeInspectionExtensionNames.Length > 0);
        }
    }
}