423b3f8e05068664b416005ad1050fef5c63f662
[mono.git] / mcs / class / referencesource / XamlBuildTask / Microsoft / Build / Tasks / Xaml / PartialClassGenerationTaskInternal.cs
1 //------------------------------------------------------------
2 // Copyright (c) Microsoft Corporation.  All rights reserved.
3 //------------------------------------------------------------
4
5 namespace Microsoft.Build.Tasks.Xaml
6 {
7
8     using System;
9     using System.CodeDom;
10     using System.CodeDom.Compiler;
11     using System.Collections.Generic;
12     using System.IO;
13     using System.Runtime;
14     using System.Xaml;
15     using System.Xaml.Schema;
16     using System.Xml;
17     using System.Reflection;
18     using System.Globalization;
19     using System.Runtime.Remoting.Lifetime;
20     using Microsoft.Build.Utilities;
21     using XamlBuildTask;
22     using Microsoft.Build.Framework;
23
24     internal class PartialClassGenerationTaskInternal : MarshalByRefObject
25     {
26         const string UnknownExceptionErrorCode = "XC1000";
27
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;
40
41         // Set the lease lifetime according to the environment variable with the name defined by RemotingLeaseLifetimeInMinutesEnvironmentVariableName
42         public override object InitializeLifetimeService()
43         {
44             ILease lease = (ILease)base.InitializeLifetimeService();
45             XamlBuildTaskLeaseLifetimeHelper.SetLeaseLifetimeFromEnvironmentVariable(lease);
46             return lease;
47         }
48
49         public IList<ITaskItem> ApplicationMarkup
50         {
51             get
52             {
53                 if (this.applicationMarkup == null)
54                 {
55                     this.applicationMarkup = new List<ITaskItem>();
56                 }
57                 return this.applicationMarkup;
58             }
59             set
60             {
61                 this.applicationMarkup = value;
62             }
63         }
64
65         public string AssemblyName
66         { get; set; }
67
68         public TaskLoggingHelper BuildLogger
69         { get; set; }
70
71         public XamlBuildTypeGenerationExtensionContext BuildContextForExtensions
72         {
73             get
74             {
75                 if (this.buildContextForExtensions == null)
76                 {
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;
86
87                     this.buildContextForExtensions = local;
88                 }
89                 return this.buildContextForExtensions;
90             }
91         }
92
93         public IList<string> GeneratedResources
94         {
95             get
96             {
97                 if (this.generatedResources == null)
98                 {
99                     this.generatedResources = new List<string>();
100                 }
101                 return this.generatedResources;
102             }
103         }
104
105         public IList<string> GeneratedCodeFiles
106         {
107             get
108             {
109                 if (this.generatedCodeFiles == null)
110                 {
111                     this.generatedCodeFiles = new List<string>();
112                 }
113                 return generatedCodeFiles;
114             }
115         }
116         
117         public string GeneratedSourceExtension
118         { get; set; }
119
120         public string Language
121         { get; set; }
122
123         public IList<LogData> LogData
124         {
125             get
126             {
127                 if (this.logData == null)
128                 {
129                     this.logData = new List<LogData>();
130                 }
131                 return this.logData;
132             }
133         }
134
135         public string OutputPath
136         { get; set; }
137
138         public IList<ITaskItem> References
139         {
140             get
141             {
142                 if (this.references == null)
143                 {
144                     this.references = new List<ITaskItem>();
145                 }
146                 return this.references;
147             }
148             set
149             {
150                 this.references = value;
151             }
152         }
153
154         public IList<Assembly> LoadedAssemblyList
155         {
156             get
157             {
158                 if (this.assemblyNames == null)
159                 {
160                     if (IsInProcessXamlMarkupCompile)
161                     {
162                         this.assemblyNames = cachedAssemblyList;
163                     }
164                     else
165                     {
166                         this.assemblyNames = new List<Assembly>();
167                     }
168                 }
169                 return this.assemblyNames;
170             }
171             set
172             {
173                 this.assemblyNames = value;
174                 if (IsInProcessXamlMarkupCompile)
175                 {
176                     cachedAssemblyList = value;
177                 }
178             }
179         }
180
181         public string MSBuildProjectDirectory
182         { get; set; }
183
184         private static IList<Assembly> cachedAssemblyList = null;
185
186         public bool IsInProcessXamlMarkupCompile
187         { get; set; }
188
189         public string RootNamespace
190         { get; set; }
191
192         public IList<string> SourceCodeFiles
193         {
194             get
195             {
196                 if (this.sourceCodeFiles == null)
197                 {
198                     this.sourceCodeFiles = new List<string>();
199                 }
200                 return this.sourceCodeFiles;
201             }
202             set
203             {
204                 this.sourceCodeFiles = value;
205             }
206         }
207
208         public bool RequiresCompilationPass2
209         { get; set; }
210
211         public bool SupportExtensions
212         { get; set; }
213
214         HashSet<string> SourceFilePaths
215         {
216             get
217             {
218                 if (sourceFilePaths == null)
219                 {
220                     sourceFilePaths = new HashSet<string>();
221                     if (SourceCodeFiles != null)
222                     {
223                         foreach (string sourceCodeFile in SourceCodeFiles)
224                         {
225                             sourceFilePaths.Add(sourceCodeFile);
226                         }
227                     }
228                 }
229                 return sourceFilePaths;
230             }
231             set
232             {
233                 this.sourceFilePaths = value;
234             }
235         }
236
237         public XamlSchemaContext SchemaContext
238         {
239             get
240             {
241                 if (schemaContext == null)
242                 {
243                     if (LoadedAssemblyList.Count > 0)
244                     {
245                         schemaContext = new XamlSchemaContext(LoadedAssemblyList);
246                     }
247                     else
248                     {
249                         schemaContext = new XamlSchemaContext();
250                     }
251                 }
252                 return schemaContext;
253             }
254         }
255
256         public string HelperClassFullName
257         { get; set; }
258
259         public IList<Tuple<string, string, string>> XamlBuildTaskTypeGenerationExtensionNames
260         {
261             get;
262             set;
263         }
264
265         public bool MarkupCompilePass2ExtensionsPresent
266         {
267             get;
268             set;
269         } 
270
271         public bool Execute()
272         {
273             try
274             {
275                 if (this.ApplicationMarkup == null || this.ApplicationMarkup.Count == 0)
276                 {
277                     return true;
278                 }
279                 if (!CodeDomProvider.IsDefinedLanguage(this.Language))
280                 {
281                     throw FxTrace.Exception.Argument("Language", SR.UnrecognizedLanguage(this.Language));
282                 }
283
284                 if (this.SupportExtensions)
285                 {
286                     this.xamlBuildTypeGenerationExtensions = XamlBuildTaskServices.GetXamlBuildTaskExtensions<IXamlBuildTypeGenerationExtension>(
287                         this.XamlBuildTaskTypeGenerationExtensionNames,
288                         this.BuildLogger,
289                         this.MSBuildProjectDirectory);
290                 }
291
292                 AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += new ResolveEventHandler(XamlBuildTaskServices.ReflectionOnlyAssemblyResolve);
293                 bool retVal = true;
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)
297                 {
298                     if (this.References != null)
299                     {
300                         try
301                         {
302                             this.LoadedAssemblyList = XamlBuildTaskServices.Load(this.References, IsInProcessXamlMarkupCompile);
303                         }
304                         catch (FileNotFoundException e)
305                         {
306                             XamlBuildTaskServices.LogException(this.BuildLogger, e.Message, e.FileName, 0, 0);
307                             retVal = false;
308                         }
309                     }
310                 }
311
312                 CodeDomProvider codeDomProvider = CodeDomProvider.CreateProvider(this.Language);
313
314                 ProcessHelperClassGeneration(codeDomProvider); 
315                 foreach (ITaskItem app in ApplicationMarkup)
316                 {
317                     string inputMarkupFile = app.ItemSpec;
318                     try
319                     {
320                         retVal &= ProcessMarkupItem(app, codeDomProvider);
321                     }
322                     catch (LoggableException e)
323                     {
324                         if (Fx.IsFatal(e))
325                         {
326                             throw;
327                         }
328                         XamlBuildTaskServices.LogException(this.BuildLogger, e.Message, e.Source, e.LineNumber, e.LinePosition);
329                         retVal = false;
330                     }
331                     catch (FileLoadException e)
332                     {
333                         if (Fx.IsFatal(e))
334                         {
335                             throw;
336                         }
337
338                         XamlBuildTaskServices.LogException(this.BuildLogger, SR.AssemblyCannotBeResolved(XamlBuildTaskServices.FileNotLoaded), inputMarkupFile, 0, 0);
339                         retVal = false;
340                     }
341                     catch (Exception e)
342                     {
343                         if (Fx.IsFatal(e))
344                         {
345                             throw;
346                         }
347                         XamlBuildTaskServices.LogException(this.BuildLogger, e.Message, inputMarkupFile, 0, 0);
348                         retVal = false;
349                     }
350                 }
351
352                 // Add the files generated from extensions
353                 if (this.SupportExtensions)
354                 {
355                     if (retVal)
356                     {
357                         foreach (string fileName in this.BuildContextForExtensions.GeneratedFiles)
358                         {
359                             this.GeneratedCodeFiles.Add(fileName);
360                         }
361
362                         foreach (string fileName in this.BuildContextForExtensions.GeneratedResourceFiles)
363                         {
364                             this.GeneratedResources.Add(fileName);
365                         }
366                     }
367                 }
368
369                 return retVal;
370             }
371             catch (Exception e)
372             {
373                 if (Fx.IsFatal(e))
374                 {
375                     throw;
376                 }
377
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))
381                 {
382                     XamlBuildTaskServices.LogException(this.BuildLogger, e.Message);
383                 }
384                 return false;
385             }
386         }
387
388         void ProcessHelperClassGeneration(CodeDomProvider codeDomProvider)
389         {
390             string codeFileName = "_" + this.AssemblyName + GetGeneratedSourceExtension(codeDomProvider);
391             codeFileName = Path.Combine(this.OutputPath, codeFileName);
392
393
394             string namespaceName = "XamlStaticHelperNamespace";
395             string className = "_XamlStaticHelper";
396
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);
401
402             this.HelperClassFullName = namespaceName + "." + className;
403         }
404
405         bool ProcessMarkupItem(ITaskItem markupItem, CodeDomProvider codeDomProvider)
406         {
407             string markupItemFileName = markupItem.ItemSpec;
408             XamlBuildTaskServices.PopulateModifiers(codeDomProvider);
409
410             XamlNodeList xamlNodes = ReadXamlNodes(markupItemFileName);
411             if (xamlNodes == null)
412             {
413                 return false;
414             }
415
416             ClassData classData = ReadClassData(xamlNodes, markupItemFileName);
417
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;
423
424             // Check if code file with partial class exists
425             classData.SourceFileExists = UserProvidedFileExists(markupItemFileName, codeDomProvider);
426
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))
432             {
433                 rootNamespacePrefix = classData.RootNamespace + ".";
434             }
435
436             if (!String.IsNullOrWhiteSpace(classData.Namespace))
437             {
438                 namespacePrefix = classData.Namespace + ".";
439             }
440
441             if (rootNamespacePrefix != null)
442             {
443                 if (namespacePrefix != null)
444                 {
445                     typeFullName = rootNamespacePrefix + namespacePrefix + classData.Name;
446                 }
447                 else
448                 {
449                     typeFullName = rootNamespacePrefix + classData.Name;
450                 }
451             }
452             else
453             {
454                 if (namespacePrefix != null)
455                 {
456                     typeFullName = namespacePrefix + classData.Name;
457                 }
458                 else
459                 {
460                     typeFullName = classData.Name;
461                 }
462             }
463
464             markupItem.SetMetadata("typeName", typeFullName);
465
466             // Execute extensions here to give them a chance to mutate the ClassData before we generate code.
467             if (this.SupportExtensions)
468             {
469                 if (!ExecuteExtensions(classData, markupItem))
470                 {
471                     return false;
472                 }
473             }
474
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);
479
480             // Generate resource file
481             if (!string.IsNullOrEmpty(this.AssemblyName))
482             {
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))
486                 {
487                     XamlXmlWriterSettings xamlSettings = new XamlXmlWriterSettings() { CloseOutput = true };
488                     
489                     // Process EmbeddedResourceXaml to remove xml:space="preserve"
490                     // due to a 
491
492
493
494
495                     RemoveXamlSpaceAttribute(classData);
496
497                     using (XamlReader reader = classData.EmbeddedResourceXaml.GetReader())
498                     {
499                         using (XamlXmlWriter xamlWriter = new XamlXmlWriter(xmlWriter, reader.SchemaContext, xamlSettings))
500                         {
501                             XamlServices.Transform(reader, xamlWriter);                                                     
502                         }
503                     }
504                 }
505                 this.GeneratedResources.Add(markupFileName);
506             }
507
508             if (classData.RequiresCompilationPass2)
509             {
510                 this.RequiresCompilationPass2 = true;
511             }
512             else
513             {
514                 if (!this.SupportExtensions)
515                 {
516                     if (!ValidateXaml(xamlNodes, markupItemFileName))
517                     {
518                         this.RequiresCompilationPass2 = true;
519                     }
520                 }
521                 else
522                 {
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)
527                     {
528                         if (!ValidateXaml(xamlNodes, markupItemFileName))
529                         {
530                             this.RequiresCompilationPass2 = true;
531                         }
532                     }
533                 }
534             }
535             return true;
536         }
537
538         bool ExecuteExtensions(ClassData classData, ITaskItem markupItem)
539         {
540             // Execute pass1 extensions only 
541             // we skip pass1 extensions if we are doing in-proc compile
542             if (!this.IsInProcessXamlMarkupCompile)
543             {
544                 bool extensionExecutedSuccessfully = true;
545                 foreach (IXamlBuildTypeGenerationExtension extension in this.xamlBuildTypeGenerationExtensions)
546                 {
547                     if (extension == null)
548                     {
549                         continue;
550                     }
551
552                     this.BuildContextForExtensions.InputTaskItem = markupItem;
553                     try
554                     {
555                         extensionExecutedSuccessfully = extension.Execute(classData, this.BuildContextForExtensions) && extensionExecutedSuccessfully;
556                     }
557                     catch (Exception e)
558                     {
559                         if (Fx.IsFatal(e))
560                         {
561                             throw;
562                         }
563                         throw FxTrace.Exception.AsError(new LoggableException(SR.ExceptionThrownInExtension(extension.ToString(), e.GetType().ToString(), e.Message)));
564                     }
565                 }
566
567                 if (this.BuildLogger.HasLoggedErrors || !extensionExecutedSuccessfully)
568                 {
569                     return false;
570                 }
571             }
572
573             return true;
574         }
575
576         string GetFileName(string markupItem)
577         {
578             if (markupFileNames == null)
579             {
580                 markupFileNames = new HashSet<string>();
581             }
582
583             string originalMarkupItemName = Path.GetFileNameWithoutExtension(markupItem);
584             string markupItemName = originalMarkupItemName;
585             int i = 0;
586             while (this.markupFileNames.Contains(markupItemName))
587             {
588                 markupItemName = originalMarkupItemName + "." + (++i).ToString(CultureInfo.InvariantCulture);
589             }
590             this.markupFileNames.Add(markupItemName);
591             markupItemName = markupItemName + Path.GetExtension(markupItem);
592             if (this.OutputPath == null)
593             {
594                 throw FxTrace.Exception.AsError(
595                         new InvalidOperationException(SR.OutputPathCannotBeNull));
596             }
597             return Path.Combine(this.OutputPath, markupItemName);
598         }
599
600         XamlNodeList ReadXamlNodes(string xamlFileName)
601         {
602             XamlNodeList nodeList = new XamlNodeList(this.SchemaContext);
603
604             try
605             {
606                 XamlXmlReaderSettings settings = new XamlXmlReaderSettings
607                 {
608                     AllowProtectedMembersOnRoot = true,
609                     ProvideLineInfo = true
610                 };
611
612                 using (StreamReader streamReader = new StreamReader(xamlFileName))
613                 {
614                     XamlReader reader = new XamlXmlReader(XmlReader.Create(streamReader), this.SchemaContext, settings);
615                     XamlServices.Transform(reader, nodeList.Writer);
616                 }
617             }
618             catch (XmlException e)
619             {
620                 XamlBuildTaskServices.LogException(this.BuildLogger, e.Message, xamlFileName, e.LineNumber, e.LinePosition); 
621                 return null;
622             }
623             catch (XamlException e)
624             {
625                 XamlBuildTaskServices.LogException(this.BuildLogger, e.Message, xamlFileName, e.LineNumber, e.LinePosition);
626                 return null;
627             }
628             
629             if (nodeList.Count > 0)
630             {
631                 return nodeList;
632             }
633             else
634             {
635                 return null;
636             }
637         }
638
639         ClassData ReadClassData(XamlNodeList xamlNodes, string xamlFileName)
640         {
641             ClassImporter importer = new ClassImporter(xamlFileName, this.AssemblyName, this.Language.Equals("VB") ? this.RootNamespace : null);
642             ClassData classData = importer.ReadFromXaml(xamlNodes);
643             return classData;
644         }
645
646         void RemoveXamlSpaceAttribute(ClassData classData)
647         {
648             using (XamlReader reader = classData.EmbeddedResourceXaml.GetReader())
649             {
650                 XamlNodeList newList = new XamlNodeList(reader.SchemaContext);
651                 using (XamlWriter writer = newList.Writer)
652                 {
653                     bool nodesAvailable = reader.Read();
654                     while (nodesAvailable)
655                     {                        
656                         if (reader.NodeType == XamlNodeType.StartMember && reader.Member == XamlLanguage.Space)
657                         {
658                             reader.Skip();
659                         }
660                         else
661                         {
662                             writer.WriteNode(reader);
663                             nodesAvailable = reader.Read();
664                         }
665                     }
666                 }
667                 classData.EmbeddedResourceXaml = newList;
668             }
669         }
670
671         bool ValidateXaml(XamlNodeList xamlNodeList, string xamlFileName)
672         {
673             using (XamlReader xamlReader = xamlNodeList.GetReader())
674             {
675                 IList<LogData> validationErrors = null;
676                 ClassValidator validator = new ClassValidator(xamlFileName, null, null);
677                 return validator.ValidateXaml(xamlReader, true, this.AssemblyName, out validationErrors);
678             }
679         }
680
681         void WriteCode(CodeDomProvider provider, CodeCompileUnit codeUnit, string fileName)
682         {
683             using (StreamWriter fileStream = new StreamWriter(fileName))
684             {
685                 using (IndentedTextWriter tw = new IndentedTextWriter(fileStream))
686                 {
687                     provider.GenerateCodeFromCompileUnit(codeUnit, tw, new CodeGeneratorOptions());
688                 }
689             }
690         }
691
692         string GetGeneratedSourceExtension(CodeDomProvider codeDomProvider)
693         {
694             string result = null;
695             if (!string.IsNullOrEmpty(this.GeneratedSourceExtension))
696             {
697                 result = this.GeneratedSourceExtension;
698                 if (!result.StartsWith(".", StringComparison.Ordinal))
699                 {
700                     result = "." + result;
701                 }
702             }
703             return result + "." + codeDomProvider.FileExtension;
704         }
705
706         bool UserProvidedFileExists(string markupItemPath, CodeDomProvider codeDomProvider)
707         {
708             string desiredSourceFilePath = Path.ChangeExtension(markupItemPath, "xaml." + codeDomProvider.FileExtension);
709             return SourceFilePaths.Contains(desiredSourceFilePath);
710         }
711     }
712 }