[coop] Temporarily restore MonoThreadInfo when TLS destructor runs. Fixes #43099
[mono.git] / mcs / class / referencesource / XamlBuildTask / Microsoft / Build / Tasks / Xaml / PartialClassGenerationTask.cs
1 //------------------------------------------------------------
2 // Copyright (c) Microsoft Corporation.  All rights reserved.
3 //------------------------------------------------------------
4
5 namespace Microsoft.Build.Tasks.Xaml
6 {
7     using System;
8     using System.Collections;
9     using System.Collections.Generic;
10     using System.IO;
11     using System.Linq;
12     using System.Reflection;
13     using System.Runtime;
14     using System.Threading;
15     using System.Xaml;
16     using Microsoft.Build.Framework;
17     using Microsoft.Build.Utilities;
18     using Microsoft.VisualStudio.Activities;
19
20     [Fx.Tag.XamlVisible(true)]
21     public class PartialClassGenerationTask : Task
22     {
23         const string DefaultGeneratedSourceExtension = "g";
24         List<ITaskItem> generatedResources = new List<ITaskItem>();
25         List<ITaskItem> generatedCodeFiles = new List<ITaskItem>();
26
27         // We will do Dev10 behavior if the new required property MSBuildProjectDirectory is NOT specified. This can happen
28         // if a Dev10 version of the Microsoft.Xaml.Targets file is being used with Dev11 installed.
29         bool supportExtensions = false;
30         string msBuildProjectDirectory;
31
32         public PartialClassGenerationTask()
33         {
34             this.GeneratedSourceExtension = DefaultGeneratedSourceExtension;
35         }
36
37         [Fx.Tag.KnownXamlExternal]
38         [Output]
39         public ITaskItem[] ApplicationMarkup { get; set; }
40
41         public string AssemblyName
42         { get; set; }
43
44         public string[] KnownReferencePaths { get; set; }
45
46         [Fx.Tag.KnownXamlExternal]
47         [Output]
48         public ITaskItem[] GeneratedResources
49         {
50             get
51             {
52                 return generatedResources.ToArray();
53             }
54
55             set
56             {
57                 generatedResources = new List<ITaskItem>(value);
58             }
59         }
60         
61         [Fx.Tag.KnownXamlExternal]
62         [Output]
63         public ITaskItem[] GeneratedCodeFiles
64         {
65             get
66             {
67                 return generatedCodeFiles.ToArray();
68             }
69
70             set
71             {
72                 generatedCodeFiles = new List<ITaskItem>(value);
73             }
74         }       
75
76         public string GeneratedSourceExtension
77         { get; set; }
78
79         [Required]
80         public string Language
81         { get; set; }
82
83         [Required]
84         public string OutputPath
85         { get; set; }
86
87         // This is Required for Dev11, but to allow things to work with a Dev10 targets file, we are not marking it required.
88         public string MSBuildProjectDirectory
89         {
90             get
91             {
92                 return this.msBuildProjectDirectory;
93             }
94
95             set
96             {
97                 this.msBuildProjectDirectory = value;
98                 // The fact that this property is being set indicates that a Dev11 version of the targets
99                 // file is being used, so we should not do Dev10 behavior.
100                 this.supportExtensions = true;
101             }
102         }
103
104         [Fx.Tag.KnownXamlExternal]
105         public ITaskItem[] References
106         { get; set; }
107
108         public string RootNamespace
109         { get; set; }
110
111         [Fx.Tag.KnownXamlExternal]
112         public ITaskItem[] SourceCodeFiles
113         { get; set; }
114
115         [Output]
116         public bool RequiresCompilationPass2
117         { get; set; }
118
119         public string BuildTaskPath
120         { get; set; }
121
122         public bool IsInProcessXamlMarkupCompile
123         { get; set; }
124
125         public ITaskItem[] XamlBuildTypeGenerationExtensionNames
126         { get; set; }
127
128         public ITaskItem[] XamlBuildTypeInspectionExtensionNames
129         { get; set; }
130
131         private static AppDomain inProcessAppDomain;
132         private static Dictionary<string, DateTime> referencesTimeStampCache;
133         private Object referencesCacheLock = new Object();
134
135         public override bool Execute()
136         {
137             VSDesignerPerfEventProvider perfEventProvider = new VSDesignerPerfEventProvider();
138             perfEventProvider.WriteEvent(VSDesignerPerfEvents.XamlBuildTaskExecuteStart);
139
140             try
141             {
142                 if (IsInProcessXamlMarkupCompile)
143                 {
144                     bool acquiredLock = false;
145                     try
146                     {
147                         Monitor.TryEnter(referencesCacheLock, ref acquiredLock);
148                         if (acquiredLock)
149                         {
150                             return ReuseAppDomainAndExecute();
151                         }
152                         else
153                         {
154                             return GetAppDomainAndExecute();
155                         }
156                     }
157                     finally
158                     {
159                         if (acquiredLock)
160                         {
161                             Monitor.Exit(referencesCacheLock);
162                         }
163                     }
164                 }
165                 else
166                 {
167                     return GetAppDomainAndExecute();
168                 }
169             }
170             finally
171             {
172                 perfEventProvider.WriteEvent(VSDesignerPerfEvents.XamlBuildTaskExecuteEnd);
173             }
174         }
175             
176         bool ReuseAppDomainAndExecute()
177         {
178             AppDomain appDomain = null;
179             bool createdNewAppDomain = false;
180             try
181             {
182                 try
183                 {
184                     appDomain = GetInProcessAppDomain(out createdNewAppDomain);
185                     bool ret = ExecuteInternal(appDomain);
186                     return ret;
187                 }
188                 catch (Exception e)
189                 {
190                     if (Fx.IsFatal(e))
191                     {
192                         throw;
193                     }
194                     if (createdNewAppDomain)
195                     {
196                         XamlBuildTaskServices.LogException(this.Log, e.Message);
197                         return false;
198                     }
199                     else
200                     {
201                         AppDomain.Unload(inProcessAppDomain);
202                         inProcessAppDomain = null;
203                         return GetAppDomainAndExecute();
204                     }
205                 }
206             }
207             finally
208             {
209                 if (Log != null)
210                 {
211                     Log.MarkAsInactive();
212                 }
213             }
214         }
215
216         bool GetAppDomainAndExecute()
217         {
218             AppDomain appDomain = null;
219             try
220             {
221                 appDomain = CreateNewAppDomain();
222                 bool ret = ExecuteInternal(appDomain);
223                 return ret;
224             }
225             catch (Exception e)
226             {
227                 if (Fx.IsFatal(e))
228                 {
229                     throw;
230                 }
231
232                 XamlBuildTaskServices.LogException(this.Log, e.Message);
233                 return false;
234             }
235             finally
236             {
237                 if (appDomain != null)
238                 {
239                     AppDomain.Unload(appDomain);
240                 }
241             }
242         }
243
244         bool ExecuteInternal(AppDomain appDomain)
245         {
246             PartialClassGenerationTaskInternal wrapper = (PartialClassGenerationTaskInternal)appDomain.CreateInstanceAndUnwrap(
247                                                         Assembly.GetExecutingAssembly().FullName,
248                                                         typeof(PartialClassGenerationTaskInternal).FullName);
249
250             PopulateBuildArtifacts(wrapper);
251
252             bool ret = wrapper.Execute();
253
254             if (ret)
255             {
256                 ExtractBuiltArtifacts(wrapper);
257             }
258             return ret;
259         }
260
261         AppDomain CreateNewAppDomain()
262         {
263             return XamlBuildTaskServices.CreateAppDomain("PartialClassAppDomain_" + Guid.NewGuid(), BuildTaskPath);
264         }
265         
266         // For Intellisense builds, we re-use the AppDomain for successive builds instead of creating a new one every time, 
267         // if the references have not changed (there are no new references and they have not been updated since the last build)
268         // This method accesses the static referencesTimeStampCache (indirectly). 
269         // To ensure thread safety, this method should be called inside a lock/monitor
270         AppDomain GetInProcessAppDomain(out bool newAppDomain)
271         {
272             newAppDomain = false;
273             if (inProcessAppDomain == null)
274             {
275                 inProcessAppDomain = CreateNewAppDomain();
276                 newAppDomain = true;
277                 UpdateReferenceCache();
278             }
279             else if (AreReferencesChanged())
280             {
281                 AppDomain.Unload(inProcessAppDomain);
282                 inProcessAppDomain = CreateNewAppDomain();
283                 newAppDomain = true;
284                 UpdateReferenceCache();
285             }
286             return inProcessAppDomain;
287         }
288
289         // This method accesses the static referencesTimeStampCache.
290         // To ensure thread safety, this method should be called inside a lock/monitor
291         bool AreReferencesChanged()
292         {
293             bool refsChanged = false;
294             if (referencesTimeStampCache == null || referencesTimeStampCache.Count != References.Length)
295             {
296                 refsChanged = true;
297             }
298             else
299             {
300                 foreach (var reference in References)
301                 {
302                     string fullPath = Path.GetFullPath(reference.ItemSpec);
303                     DateTime timeStamp = File.GetLastWriteTimeUtc(fullPath);
304                     if (!referencesTimeStampCache.ContainsKey(fullPath)
305                         || timeStamp > referencesTimeStampCache[fullPath]
306                         || timeStamp == DateTime.MinValue)
307                     {
308                         refsChanged = true;
309                         break;
310                     }
311                 }
312             }
313             return refsChanged;
314         }
315
316         // This method accesses the static referencesTimeStampCache.
317         // To ensure thread safety, this method should be called inside a lock/monitor
318         void UpdateReferenceCache()
319         {
320             referencesTimeStampCache = new Dictionary<string, DateTime>();
321             foreach (var reference in References)
322             {
323                 string fullPath = Path.GetFullPath(reference.ItemSpec);
324                 DateTime timeStamp = File.GetLastWriteTimeUtc(fullPath);
325                 referencesTimeStampCache.Add(fullPath, timeStamp);
326             }
327         }
328
329         void PopulateBuildArtifacts(PartialClassGenerationTaskInternal wrapper)
330         {
331             IList<ITaskItem> applicationMarkup = null;
332             if (this.ApplicationMarkup != null)
333             {
334                 applicationMarkup = this.ApplicationMarkup
335                     .Select(i => new DelegatingTaskItem(i) as ITaskItem).ToList();
336             }
337             wrapper.ApplicationMarkup = applicationMarkup;
338
339             wrapper.BuildLogger = this.Log;
340
341             wrapper.References = this.References
342                 .Select(i => new DelegatingTaskItem(i) as ITaskItem).ToList();
343
344             IList<string> sourceCodeFiles = null;
345             if (this.SourceCodeFiles != null)
346             {
347                 sourceCodeFiles = new List<string>(this.SourceCodeFiles.Length);
348                 foreach (ITaskItem taskItem in this.SourceCodeFiles)
349                 {
350                     sourceCodeFiles.Add(taskItem.ItemSpec);
351                 }
352             }
353             wrapper.SourceCodeFiles = sourceCodeFiles;
354
355             wrapper.Language = this.Language;
356             wrapper.AssemblyName = this.AssemblyName;
357             wrapper.OutputPath = this.OutputPath;
358             wrapper.RootNamespace = this.RootNamespace;
359             wrapper.GeneratedSourceExtension = this.GeneratedSourceExtension;
360             wrapper.IsInProcessXamlMarkupCompile = this.IsInProcessXamlMarkupCompile;
361             wrapper.MSBuildProjectDirectory = this.MSBuildProjectDirectory;
362             wrapper.XamlBuildTaskTypeGenerationExtensionNames = XamlBuildTaskServices.GetXamlBuildTaskExtensionNames(this.XamlBuildTypeGenerationExtensionNames);
363             if (this.XamlBuildTypeInspectionExtensionNames != null && this.XamlBuildTypeInspectionExtensionNames.Length > 0)
364             {
365                 wrapper.MarkupCompilePass2ExtensionsPresent = true;
366             }
367
368             wrapper.SupportExtensions = this.supportExtensions;
369         }        
370
371         void ExtractBuiltArtifacts(PartialClassGenerationTaskInternal wrapper)
372         {
373             foreach (string resource in wrapper.GeneratedResources)
374             {
375                 this.generatedResources.Add(new TaskItem(resource));
376             }
377
378             foreach (string code in wrapper.GeneratedCodeFiles)
379             {
380                 this.generatedCodeFiles.Add(new TaskItem(code));
381             }
382
383             this.RequiresCompilationPass2 = wrapper.RequiresCompilationPass2 ||
384                 (this.XamlBuildTypeInspectionExtensionNames != null && this.XamlBuildTypeInspectionExtensionNames.Length > 0);
385         }
386     }
387 }