1 //------------------------------------------------------------
2 // Copyright (c) Microsoft Corporation. All rights reserved.
3 //------------------------------------------------------------
5 namespace Microsoft.Build.Tasks.Xaml
10 using System.CodeDom.Compiler;
11 using System.Collections.Generic;
15 using System.Xaml.Schema;
17 using System.Reflection;
18 using System.Globalization;
19 using System.Runtime.Remoting.Lifetime;
20 using Microsoft.Build.Utilities;
22 using Microsoft.Build.Framework;
24 internal class PartialClassGenerationTaskInternal : MarshalByRefObject
26 const string UnknownExceptionErrorCode = "XC1000";
28 IList<ITaskItem> applicationMarkup;
29 IList<string> generatedResources;
30 IList<string> generatedCodeFiles;
31 IList<ITaskItem> references;
32 IList<string> sourceCodeFiles;
33 HashSet<string> sourceFilePaths;
34 IList<LogData> logData;
35 IList<Assembly> assemblyNames;
36 XamlSchemaContext schemaContext;
37 HashSet<string> markupFileNames;
38 IEnumerable<IXamlBuildTypeGenerationExtension> xamlBuildTypeGenerationExtensions;
39 XamlBuildTypeGenerationExtensionContext buildContextForExtensions;
41 // Set the lease lifetime according to the environment variable with the name defined by RemotingLeaseLifetimeInMinutesEnvironmentVariableName
42 public override object InitializeLifetimeService()
44 ILease lease = (ILease)base.InitializeLifetimeService();
45 XamlBuildTaskLeaseLifetimeHelper.SetLeaseLifetimeFromEnvironmentVariable(lease);
49 public IList<ITaskItem> ApplicationMarkup
53 if (this.applicationMarkup == null)
55 this.applicationMarkup = new List<ITaskItem>();
57 return this.applicationMarkup;
61 this.applicationMarkup = value;
65 public string AssemblyName
68 public TaskLoggingHelper BuildLogger
71 public XamlBuildTypeGenerationExtensionContext BuildContextForExtensions
75 if (this.buildContextForExtensions == null)
77 XamlBuildTypeGenerationExtensionContext local = new XamlBuildTypeGenerationExtensionContext();
78 local.AssemblyName = this.AssemblyName;
79 local.IsInProcessXamlMarkupCompile = this.IsInProcessXamlMarkupCompile;
80 local.Language = this.Language;
81 local.OutputPath = this.OutputPath;
82 local.RootNamespace = this.RootNamespace;
83 local.AddSourceCodeFiles(this.SourceCodeFiles);
84 local.AddReferences(XamlBuildTaskServices.GetReferences(this.references));
85 local.XamlBuildLogger = this.BuildLogger;
87 this.buildContextForExtensions = local;
89 return this.buildContextForExtensions;
93 public IList<string> GeneratedResources
97 if (this.generatedResources == null)
99 this.generatedResources = new List<string>();
101 return this.generatedResources;
105 public IList<string> GeneratedCodeFiles
109 if (this.generatedCodeFiles == null)
111 this.generatedCodeFiles = new List<string>();
113 return generatedCodeFiles;
117 public string GeneratedSourceExtension
120 public string Language
123 public IList<LogData> LogData
127 if (this.logData == null)
129 this.logData = new List<LogData>();
135 public string OutputPath
138 public IList<ITaskItem> References
142 if (this.references == null)
144 this.references = new List<ITaskItem>();
146 return this.references;
150 this.references = value;
154 public IList<Assembly> LoadedAssemblyList
158 if (this.assemblyNames == null)
160 if (IsInProcessXamlMarkupCompile)
162 this.assemblyNames = cachedAssemblyList;
166 this.assemblyNames = new List<Assembly>();
169 return this.assemblyNames;
173 this.assemblyNames = value;
174 if (IsInProcessXamlMarkupCompile)
176 cachedAssemblyList = value;
181 public string MSBuildProjectDirectory
184 private static IList<Assembly> cachedAssemblyList = null;
186 public bool IsInProcessXamlMarkupCompile
189 public string RootNamespace
192 public IList<string> SourceCodeFiles
196 if (this.sourceCodeFiles == null)
198 this.sourceCodeFiles = new List<string>();
200 return this.sourceCodeFiles;
204 this.sourceCodeFiles = value;
208 public bool RequiresCompilationPass2
211 public bool SupportExtensions
214 HashSet<string> SourceFilePaths
218 if (sourceFilePaths == null)
220 sourceFilePaths = new HashSet<string>();
221 if (SourceCodeFiles != null)
223 foreach (string sourceCodeFile in SourceCodeFiles)
225 sourceFilePaths.Add(sourceCodeFile);
229 return sourceFilePaths;
233 this.sourceFilePaths = value;
237 public XamlSchemaContext SchemaContext
241 if (schemaContext == null)
243 if (LoadedAssemblyList.Count > 0)
245 schemaContext = new XamlSchemaContext(LoadedAssemblyList);
249 schemaContext = new XamlSchemaContext();
252 return schemaContext;
256 public string HelperClassFullName
259 public IList<Tuple<string, string, string>> XamlBuildTaskTypeGenerationExtensionNames
265 public bool MarkupCompilePass2ExtensionsPresent
271 public bool Execute()
275 if (this.ApplicationMarkup == null || this.ApplicationMarkup.Count == 0)
279 if (!CodeDomProvider.IsDefinedLanguage(this.Language))
281 throw FxTrace.Exception.Argument("Language", SR.UnrecognizedLanguage(this.Language));
284 if (this.SupportExtensions)
286 this.xamlBuildTypeGenerationExtensions = XamlBuildTaskServices.GetXamlBuildTaskExtensions<IXamlBuildTypeGenerationExtension>(
287 this.XamlBuildTaskTypeGenerationExtensionNames,
289 this.MSBuildProjectDirectory);
292 AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += new ResolveEventHandler(XamlBuildTaskServices.ReflectionOnlyAssemblyResolve);
294 // We load the assemblies for the real builds
295 // For intellisense builds, we load them the first time only
296 if (!IsInProcessXamlMarkupCompile || this.LoadedAssemblyList == null)
298 if (this.References != null)
302 this.LoadedAssemblyList = XamlBuildTaskServices.Load(this.References, IsInProcessXamlMarkupCompile);
304 catch (FileNotFoundException e)
306 XamlBuildTaskServices.LogException(this.BuildLogger, e.Message, e.FileName, 0, 0);
312 CodeDomProvider codeDomProvider = CodeDomProvider.CreateProvider(this.Language);
314 ProcessHelperClassGeneration(codeDomProvider);
315 foreach (ITaskItem app in ApplicationMarkup)
317 string inputMarkupFile = app.ItemSpec;
320 retVal &= ProcessMarkupItem(app, codeDomProvider);
322 catch (LoggableException e)
328 XamlBuildTaskServices.LogException(this.BuildLogger, e.Message, e.Source, e.LineNumber, e.LinePosition);
331 catch (FileLoadException e)
338 XamlBuildTaskServices.LogException(this.BuildLogger, SR.AssemblyCannotBeResolved(XamlBuildTaskServices.FileNotLoaded), inputMarkupFile, 0, 0);
347 XamlBuildTaskServices.LogException(this.BuildLogger, e.Message, inputMarkupFile, 0, 0);
352 // Add the files generated from extensions
353 if (this.SupportExtensions)
357 foreach (string fileName in this.BuildContextForExtensions.GeneratedFiles)
359 this.GeneratedCodeFiles.Add(fileName);
362 foreach (string fileName in this.BuildContextForExtensions.GeneratedResourceFiles)
364 this.GeneratedResources.Add(fileName);
378 // Log unknown errors that do not originate from the task.
379 // Assumes that all known errors are logged when the exception is thrown.
380 if (!(e is LoggableException))
382 XamlBuildTaskServices.LogException(this.BuildLogger, e.Message);
388 void ProcessHelperClassGeneration(CodeDomProvider codeDomProvider)
390 string codeFileName = "_" + this.AssemblyName + GetGeneratedSourceExtension(codeDomProvider);
391 codeFileName = Path.Combine(this.OutputPath, codeFileName);
394 string namespaceName = "XamlStaticHelperNamespace";
395 string className = "_XamlStaticHelper";
397 // Generate code file
398 CodeCompileUnit codeUnit = new ClassGenerator(this.BuildLogger, codeDomProvider, this.Language).GenerateHelperClass(namespaceName, className, this.LoadedAssemblyList);
399 WriteCode(codeDomProvider, codeUnit, codeFileName);
400 this.GeneratedCodeFiles.Add(codeFileName);
402 this.HelperClassFullName = namespaceName + "." + className;
405 bool ProcessMarkupItem(ITaskItem markupItem, CodeDomProvider codeDomProvider)
407 string markupItemFileName = markupItem.ItemSpec;
408 XamlBuildTaskServices.PopulateModifiers(codeDomProvider);
410 XamlNodeList xamlNodes = ReadXamlNodes(markupItemFileName);
411 if (xamlNodes == null)
416 ClassData classData = ReadClassData(xamlNodes, markupItemFileName);
418 string outputFileName = GetFileName(markupItemFileName);
419 string codeFileName = Path.ChangeExtension(outputFileName, GetGeneratedSourceExtension(codeDomProvider));
420 string markupFileName = Path.ChangeExtension(outputFileName, GeneratedSourceExtension + XamlBuildTaskServices.XamlExtension);
421 classData.EmbeddedResourceFileName = Path.GetFileName(markupFileName);
422 classData.HelperClassFullName = this.HelperClassFullName;
424 // Check if code file with partial class exists
425 classData.SourceFileExists = UserProvidedFileExists(markupItemFileName, codeDomProvider);
427 // Store the full type name as metadata on the markup item
428 string rootNamespacePrefix = null;
429 string namespacePrefix = null;
430 string typeFullName = null;
431 if (this.Language.Equals("VB") && !String.IsNullOrWhiteSpace(classData.RootNamespace))
433 rootNamespacePrefix = classData.RootNamespace + ".";
436 if (!String.IsNullOrWhiteSpace(classData.Namespace))
438 namespacePrefix = classData.Namespace + ".";
441 if (rootNamespacePrefix != null)
443 if (namespacePrefix != null)
445 typeFullName = rootNamespacePrefix + namespacePrefix + classData.Name;
449 typeFullName = rootNamespacePrefix + classData.Name;
454 if (namespacePrefix != null)
456 typeFullName = namespacePrefix + classData.Name;
460 typeFullName = classData.Name;
464 markupItem.SetMetadata("typeName", typeFullName);
466 // Execute extensions here to give them a chance to mutate the ClassData before we generate code.
467 if (this.SupportExtensions)
469 if (!ExecuteExtensions(classData, markupItem))
475 // Generate code file
476 CodeCompileUnit codeUnit = new ClassGenerator(this.BuildLogger, codeDomProvider, this.Language).Generate(classData);
477 WriteCode(codeDomProvider, codeUnit, codeFileName);
478 this.GeneratedCodeFiles.Add(codeFileName);
480 // Generate resource file
481 if (!string.IsNullOrEmpty(this.AssemblyName))
483 // Generate xaml "implementation" file
484 XmlWriterSettings xmlSettings = new XmlWriterSettings { Indent = true, IndentChars = " ", CloseOutput = true };
485 using (XmlWriter xmlWriter = XmlWriter.Create(File.Open(markupFileName, FileMode.Create), xmlSettings))
487 XamlXmlWriterSettings xamlSettings = new XamlXmlWriterSettings() { CloseOutput = true };
489 // Process EmbeddedResourceXaml to remove xml:space="preserve"
495 RemoveXamlSpaceAttribute(classData);
497 using (XamlReader reader = classData.EmbeddedResourceXaml.GetReader())
499 using (XamlXmlWriter xamlWriter = new XamlXmlWriter(xmlWriter, reader.SchemaContext, xamlSettings))
501 XamlServices.Transform(reader, xamlWriter);
505 this.GeneratedResources.Add(markupFileName);
508 if (classData.RequiresCompilationPass2)
510 this.RequiresCompilationPass2 = true;
514 if (!this.SupportExtensions)
516 if (!ValidateXaml(xamlNodes, markupItemFileName))
518 this.RequiresCompilationPass2 = true;
523 // skip validation if we are doing in-proc compile
524 // OR if we have pass 2 extensions hooked up
525 // as we anyway need to run pass 2 in that case
526 if (!this.IsInProcessXamlMarkupCompile && !this.MarkupCompilePass2ExtensionsPresent)
528 if (!ValidateXaml(xamlNodes, markupItemFileName))
530 this.RequiresCompilationPass2 = true;
538 bool ExecuteExtensions(ClassData classData, ITaskItem markupItem)
540 // Execute pass1 extensions only
541 // we skip pass1 extensions if we are doing in-proc compile
542 if (!this.IsInProcessXamlMarkupCompile)
544 bool extensionExecutedSuccessfully = true;
545 foreach (IXamlBuildTypeGenerationExtension extension in this.xamlBuildTypeGenerationExtensions)
547 if (extension == null)
552 this.BuildContextForExtensions.InputTaskItem = markupItem;
555 extensionExecutedSuccessfully = extension.Execute(classData, this.BuildContextForExtensions) && extensionExecutedSuccessfully;
563 throw FxTrace.Exception.AsError(new LoggableException(SR.ExceptionThrownInExtension(extension.ToString(), e.GetType().ToString(), e.Message)));
567 if (this.BuildLogger.HasLoggedErrors || !extensionExecutedSuccessfully)
576 string GetFileName(string markupItem)
578 if (markupFileNames == null)
580 markupFileNames = new HashSet<string>();
583 string originalMarkupItemName = Path.GetFileNameWithoutExtension(markupItem);
584 string markupItemName = originalMarkupItemName;
586 while (this.markupFileNames.Contains(markupItemName))
588 markupItemName = originalMarkupItemName + "." + (++i).ToString(CultureInfo.InvariantCulture);
590 this.markupFileNames.Add(markupItemName);
591 markupItemName = markupItemName + Path.GetExtension(markupItem);
592 if (this.OutputPath == null)
594 throw FxTrace.Exception.AsError(
595 new InvalidOperationException(SR.OutputPathCannotBeNull));
597 return Path.Combine(this.OutputPath, markupItemName);
600 XamlNodeList ReadXamlNodes(string xamlFileName)
602 XamlNodeList nodeList = new XamlNodeList(this.SchemaContext);
606 XamlXmlReaderSettings settings = new XamlXmlReaderSettings
608 AllowProtectedMembersOnRoot = true,
609 ProvideLineInfo = true
612 using (StreamReader streamReader = new StreamReader(xamlFileName))
614 XamlReader reader = new XamlXmlReader(XmlReader.Create(streamReader), this.SchemaContext, settings);
615 XamlServices.Transform(reader, nodeList.Writer);
618 catch (XmlException e)
620 XamlBuildTaskServices.LogException(this.BuildLogger, e.Message, xamlFileName, e.LineNumber, e.LinePosition);
623 catch (XamlException e)
625 XamlBuildTaskServices.LogException(this.BuildLogger, e.Message, xamlFileName, e.LineNumber, e.LinePosition);
629 if (nodeList.Count > 0)
639 ClassData ReadClassData(XamlNodeList xamlNodes, string xamlFileName)
641 ClassImporter importer = new ClassImporter(xamlFileName, this.AssemblyName, this.Language.Equals("VB") ? this.RootNamespace : null);
642 ClassData classData = importer.ReadFromXaml(xamlNodes);
646 void RemoveXamlSpaceAttribute(ClassData classData)
648 using (XamlReader reader = classData.EmbeddedResourceXaml.GetReader())
650 XamlNodeList newList = new XamlNodeList(reader.SchemaContext);
651 using (XamlWriter writer = newList.Writer)
653 bool nodesAvailable = reader.Read();
654 while (nodesAvailable)
656 if (reader.NodeType == XamlNodeType.StartMember && reader.Member == XamlLanguage.Space)
662 writer.WriteNode(reader);
663 nodesAvailable = reader.Read();
667 classData.EmbeddedResourceXaml = newList;
671 bool ValidateXaml(XamlNodeList xamlNodeList, string xamlFileName)
673 using (XamlReader xamlReader = xamlNodeList.GetReader())
675 IList<LogData> validationErrors = null;
676 ClassValidator validator = new ClassValidator(xamlFileName, null, null);
677 return validator.ValidateXaml(xamlReader, true, this.AssemblyName, out validationErrors);
681 void WriteCode(CodeDomProvider provider, CodeCompileUnit codeUnit, string fileName)
683 using (StreamWriter fileStream = new StreamWriter(fileName))
685 using (IndentedTextWriter tw = new IndentedTextWriter(fileStream))
687 provider.GenerateCodeFromCompileUnit(codeUnit, tw, new CodeGeneratorOptions());
692 string GetGeneratedSourceExtension(CodeDomProvider codeDomProvider)
694 string result = null;
695 if (!string.IsNullOrEmpty(this.GeneratedSourceExtension))
697 result = this.GeneratedSourceExtension;
698 if (!result.StartsWith(".", StringComparison.Ordinal))
700 result = "." + result;
703 return result + "." + codeDomProvider.FileExtension;
706 bool UserProvidedFileExists(string markupItemPath, CodeDomProvider codeDomProvider)
708 string desiredSourceFilePath = Path.ChangeExtension(markupItemPath, "xaml." + codeDomProvider.FileExtension);
709 return SourceFilePaths.Contains(desiredSourceFilePath);