1 //------------------------------------------------------------
2 // Copyright (c) Microsoft Corporation. All rights reserved.
3 //------------------------------------------------------------
5 namespace Microsoft.Build.Tasks.Xaml
8 using System.Collections;
9 using System.Collections.Generic;
12 using System.Reflection;
14 using System.Threading;
16 using Microsoft.Build.Framework;
17 using Microsoft.Build.Utilities;
18 using Microsoft.VisualStudio.Activities;
20 [Fx.Tag.XamlVisible(true)]
21 public class PartialClassGenerationTask : Task
23 const string DefaultGeneratedSourceExtension = "g";
24 List<ITaskItem> generatedResources = new List<ITaskItem>();
25 List<ITaskItem> generatedCodeFiles = new List<ITaskItem>();
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;
32 public PartialClassGenerationTask()
34 this.GeneratedSourceExtension = DefaultGeneratedSourceExtension;
37 [Fx.Tag.KnownXamlExternal]
39 public ITaskItem[] ApplicationMarkup { get; set; }
41 public string AssemblyName
44 public string[] KnownReferencePaths { get; set; }
46 [Fx.Tag.KnownXamlExternal]
48 public ITaskItem[] GeneratedResources
52 return generatedResources.ToArray();
57 generatedResources = new List<ITaskItem>(value);
61 [Fx.Tag.KnownXamlExternal]
63 public ITaskItem[] GeneratedCodeFiles
67 return generatedCodeFiles.ToArray();
72 generatedCodeFiles = new List<ITaskItem>(value);
76 public string GeneratedSourceExtension
80 public string Language
84 public string OutputPath
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
92 return this.msBuildProjectDirectory;
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;
104 [Fx.Tag.KnownXamlExternal]
105 public ITaskItem[] References
108 public string RootNamespace
111 [Fx.Tag.KnownXamlExternal]
112 public ITaskItem[] SourceCodeFiles
116 public bool RequiresCompilationPass2
119 public string BuildTaskPath
122 public bool IsInProcessXamlMarkupCompile
125 public ITaskItem[] XamlBuildTypeGenerationExtensionNames
128 public ITaskItem[] XamlBuildTypeInspectionExtensionNames
131 private static AppDomain inProcessAppDomain;
132 private static Dictionary<string, DateTime> referencesTimeStampCache;
133 private Object referencesCacheLock = new Object();
135 public override bool Execute()
137 VSDesignerPerfEventProvider perfEventProvider = new VSDesignerPerfEventProvider();
138 perfEventProvider.WriteEvent(VSDesignerPerfEvents.XamlBuildTaskExecuteStart);
142 if (IsInProcessXamlMarkupCompile)
144 bool acquiredLock = false;
147 Monitor.TryEnter(referencesCacheLock, ref acquiredLock);
150 return ReuseAppDomainAndExecute();
154 return GetAppDomainAndExecute();
161 Monitor.Exit(referencesCacheLock);
167 return GetAppDomainAndExecute();
172 perfEventProvider.WriteEvent(VSDesignerPerfEvents.XamlBuildTaskExecuteEnd);
176 bool ReuseAppDomainAndExecute()
178 AppDomain appDomain = null;
179 bool createdNewAppDomain = false;
184 appDomain = GetInProcessAppDomain(out createdNewAppDomain);
185 bool ret = ExecuteInternal(appDomain);
194 if (createdNewAppDomain)
196 XamlBuildTaskServices.LogException(this.Log, e.Message);
201 AppDomain.Unload(inProcessAppDomain);
202 inProcessAppDomain = null;
203 return GetAppDomainAndExecute();
211 Log.MarkAsInactive();
216 bool GetAppDomainAndExecute()
218 AppDomain appDomain = null;
221 appDomain = CreateNewAppDomain();
222 bool ret = ExecuteInternal(appDomain);
232 XamlBuildTaskServices.LogException(this.Log, e.Message);
237 if (appDomain != null)
239 AppDomain.Unload(appDomain);
244 bool ExecuteInternal(AppDomain appDomain)
246 PartialClassGenerationTaskInternal wrapper = (PartialClassGenerationTaskInternal)appDomain.CreateInstanceAndUnwrap(
247 Assembly.GetExecutingAssembly().FullName,
248 typeof(PartialClassGenerationTaskInternal).FullName);
250 PopulateBuildArtifacts(wrapper);
252 bool ret = wrapper.Execute();
256 ExtractBuiltArtifacts(wrapper);
261 AppDomain CreateNewAppDomain()
263 return XamlBuildTaskServices.CreateAppDomain("PartialClassAppDomain_" + Guid.NewGuid(), BuildTaskPath);
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)
272 newAppDomain = false;
273 if (inProcessAppDomain == null)
275 inProcessAppDomain = CreateNewAppDomain();
277 UpdateReferenceCache();
279 else if (AreReferencesChanged())
281 AppDomain.Unload(inProcessAppDomain);
282 inProcessAppDomain = CreateNewAppDomain();
284 UpdateReferenceCache();
286 return inProcessAppDomain;
289 // This method accesses the static referencesTimeStampCache.
290 // To ensure thread safety, this method should be called inside a lock/monitor
291 bool AreReferencesChanged()
293 bool refsChanged = false;
294 if (referencesTimeStampCache == null || referencesTimeStampCache.Count != References.Length)
300 foreach (var reference in References)
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)
316 // This method accesses the static referencesTimeStampCache.
317 // To ensure thread safety, this method should be called inside a lock/monitor
318 void UpdateReferenceCache()
320 referencesTimeStampCache = new Dictionary<string, DateTime>();
321 foreach (var reference in References)
323 string fullPath = Path.GetFullPath(reference.ItemSpec);
324 DateTime timeStamp = File.GetLastWriteTimeUtc(fullPath);
325 referencesTimeStampCache.Add(fullPath, timeStamp);
329 void PopulateBuildArtifacts(PartialClassGenerationTaskInternal wrapper)
331 IList<ITaskItem> applicationMarkup = null;
332 if (this.ApplicationMarkup != null)
334 applicationMarkup = this.ApplicationMarkup
335 .Select(i => new DelegatingTaskItem(i) as ITaskItem).ToList();
337 wrapper.ApplicationMarkup = applicationMarkup;
339 wrapper.BuildLogger = this.Log;
341 wrapper.References = this.References
342 .Select(i => new DelegatingTaskItem(i) as ITaskItem).ToList();
344 IList<string> sourceCodeFiles = null;
345 if (this.SourceCodeFiles != null)
347 sourceCodeFiles = new List<string>(this.SourceCodeFiles.Length);
348 foreach (ITaskItem taskItem in this.SourceCodeFiles)
350 sourceCodeFiles.Add(taskItem.ItemSpec);
353 wrapper.SourceCodeFiles = sourceCodeFiles;
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)
365 wrapper.MarkupCompilePass2ExtensionsPresent = true;
368 wrapper.SupportExtensions = this.supportExtensions;
371 void ExtractBuiltArtifacts(PartialClassGenerationTaskInternal wrapper)
373 foreach (string resource in wrapper.GeneratedResources)
375 this.generatedResources.Add(new TaskItem(resource));
378 foreach (string code in wrapper.GeneratedCodeFiles)
380 this.generatedCodeFiles.Add(new TaskItem(code));
383 this.RequiresCompilationPass2 = wrapper.RequiresCompilationPass2 ||
384 (this.XamlBuildTypeInspectionExtensionNames != null && this.XamlBuildTypeInspectionExtensionNames.Length > 0);