1 // Copyright (c) Microsoft. All rights reserved.
2 // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 
4 using System;
5 using System.Collections;
6 using System.Collections.Generic;
7 using System.IO;
8 using System.Linq;
9 using System.Text;
10 using System.Resources;
11 using System.Reflection;
12 using System.Diagnostics;
13 using System.Diagnostics.CodeAnalysis;
14 using System.Globalization;
15 #if FEATURE_RESGENCACHE
16 using System.Runtime.Serialization;
17 using System.Runtime.Serialization.Formatters.Binary;
18 #endif
19 using Microsoft.Build.Framework;
20 using Microsoft.Build.Shared;
21 using System.CodeDom;
22 using System.CodeDom.Compiler;
23 using System.Xml;
24 using System.Runtime.InteropServices;
25 #if FEATURE_SYSTEM_CONFIGURATION
26 using System.Configuration;
27 #endif
28 using System.Security;
29 #if FEATURE_RESX_RESOURCE_READER
30 using System.ComponentModel.Design;
31 #endif
32 #if FEATURE_APPDOMAIN
33 using System.Runtime.Remoting;
34 #endif
35 
36 #if (!STANDALONEBUILD)
37 using Microsoft.Internal.Performance;
38 #endif
39 using System.Runtime.Versioning;
40 
41 using Microsoft.Build.Utilities;
42 using System.Xml.Linq;
43 
44 namespace Microsoft.Build.Tasks
45 {
46     /// <summary>
47     /// This class defines the "GenerateResource" MSBuild task, which enables using resource APIs
48     /// to transform resource files.
49     /// </summary>
50     [RequiredRuntime("v2.0")]
51     public sealed partial class GenerateResource : TaskExtension
52     {
53 
54 #if !FEATURE_CODEDOM
55         private readonly string CSharpLanguageName = "CSharp";
56         private readonly string VisualBasicLanguageName = "VisualBasic";
57 #endif
58 
59 
60         #region Fields
61 
62 #if FEATURE_RESGENCACHE
63         // This cache helps us track the linked resource files listed inside of a resx resource file
64         private ResGenDependencies _cache;
65 #endif
66 
67         // This is where we store the list of input files/sources
68         private ITaskItem[] _sources = null;
69 
70         // Indicates whether the resource reader should use the source file's
71         // directory to resolve relative file paths.
72         private bool _useSourcePath = false;
73 
74         // This is needed for the actual items from the project
75         private ITaskItem[] _references = null;
76 
77         // Any additional inputs to dependency checking.
78         private ITaskItem[] _additionalInputs = null;
79 
80         // This is the path/name of the dependency cache file
81         private ITaskItem _stateFile = null;
82 
83         // This list is all of the resource file(s) generated by the task
84         private ITaskItem[] _outputResources = null;
85 
86         // List of those output resources that were not actually created, due to an error
87         private ArrayList _unsuccessfullyCreatedOutFiles = new ArrayList();
88 
89         // Storage for names of *all files* written to disk.
90         private ArrayList _filesWritten = new ArrayList();
91 
92         // StronglyTypedLanguage
93         private string _stronglyTypedLanguage = null;
94 
95         // StronglyTypedNamespace
96         private string _stronglyTypedNamespace = null;
97 
98         // StronglyTypedManifestPrefix
99         private string _stronglyTypedManifestPrefix = null;
100 
101         // StronglyTypedClassName
102         private string _stronglyTypedClassName = null;
103 
104         // StronglyTypedFileName
105         private string _stronglyTypedFileName = null;
106 
107         // Whether the STR class should have public members; default is false
108         private bool _publicClass = false;
109 
110         // Did the CodeDOM succeed when creating any Strongly Typed Resource class?
111         private bool _stronglyTypedResourceSuccessfullyCreated = false;
112 
113         // When true, a separate AppDomain is always created.
114         private bool _neverLockTypeAssemblies = false;
115 
116         private bool _foundNewestUncorrelatedInputWriteTime = false;
117 
118         private DateTime _newestUncorrelatedInputWriteTime;
119 
120         // The targets may pass in the path to the SDKToolsPath. If so this should be used to generate the commandline
121         // for logging purposes.  Also, when ExecuteAsTool is true, it determines where the system goes looking for resgen.exe
122         private string _sdkToolsPath;
123 
124         // True if the resource generation should be sent out-of-proc to resgen.exe; false otherwise.  Defaults to true
125         // because we want to execute out-of-proc when ToolsVersion is < 4.0, and the earlier targets files don't know
126         // about this property.
127         private bool _executeAsTool = true;
128 
129         // Path to resgen.exe
130         private string _resgenPath;
131 
132         // table of already seen types by their typename
133         // note the use of the ordinal comparer that matches the case sensitive Type.GetType usage
134         private Dictionary<string, Type> _typeTable = new Dictionary<string, Type>(StringComparer.Ordinal);
135 
136         /// <summary>
137         /// Table of aliases for types defined in resx / resw files
138         /// Ordinal comparer matches ResXResourceReader's use of a HashTable.
139         /// </summary>
140         private Dictionary<string, string> _aliases = new Dictionary<string, string>(StringComparer.Ordinal);
141 
142         // Our calculation is not quite correct. Using a number substantially less than 32768 in order to
143         // be sure we don't exceed it.
144         private static int s_maximumCommandLength = 28000;
145 
146         // Contains the list of paths from which inputs will not be taken into account during up-to-date check.
147         private ITaskItem[] _excludedInputPaths;
148 
149 #if FEATURE_APPDOMAIN
150         /// <summary>
151         /// The task items that we remoted across the appdomain boundary
152         /// we use this list to disconnect the task items once we're done.
153         /// </summary>
154         private List<ITaskItem> _remotedTaskItems;
155 #endif
156 
157         /// <summary>
158         /// Satellite input assemblies.
159         /// </summary>
160         private List<ITaskItem> _satelliteInputs;
161 
162         #endregion  // fields
163 
164         #region Properties
165 
166         /// <summary>
167         /// The names of the items to be converted. The extension must be one of the
168         /// following: .txt, .resx or .resources.
169         /// </summary>
170         [Required]
171         [Output]
172         public ITaskItem[] Sources
173         {
174             set { _sources = value; }
175             get { return _sources; }
176         }
177 
178         /// <summary>
179         /// Indicates whether the resource reader should use the source file's directory to
180         /// resolve relative file paths.
181         /// </summary>
182         public bool UseSourcePath
183         {
184             set { _useSourcePath = value; }
185             get { return _useSourcePath; }
186         }
187 
188         /// <summary>
189         /// Resolves types in ResX files (XML resources) for Strongly Typed Resources
190         /// </summary>
191         public ITaskItem[] References
192         {
193             set { _references = value; }
194             get { return _references; }
195         }
196 
197         /// <summary>
198         /// Additional inputs to the dependency checking done by this task. For example,
199         /// the project and targets files typically should be inputs, so that if they are updated,
200         /// all resources are regenerated.
201         /// </summary>
202         public ITaskItem[] AdditionalInputs
203         {
204             set { _additionalInputs = value; }
205             get { return _additionalInputs; }
206         }
207 
208         /// <summary>
209         /// This is the path/name of the file containing the dependency cache
210         /// </summary>
211         public ITaskItem StateFile
212         {
213             set { _stateFile = value; }
214             get { return _stateFile; }
215         }
216 
217         /// <summary>
218         /// The name(s) of the resource file to create. If the user does not specify this
219         /// attribute, the task will append a .resources extension to each input filename
220         /// argument and write the file to the directory that contains the input file.
221         /// Includes any output files that were already up to date, but not any output files
222         /// that failed to be written due to an error.
223         /// </summary>
224         [Output]
225         public ITaskItem[] OutputResources
226         {
227             set { _outputResources = value; }
228             get { return _outputResources; }
229         }
230 
231         /// <summary>
232         /// Storage for names of *all files* written to disk.  This is part of the implementation
233         /// for Clean, and contains the OutputResources items and the StateFile item.
234         /// Includes any output files that were already up to date, but not any output files
235         /// that failed to be written due to an error.
236         /// </summary>
237         [Output]
238         public ITaskItem[] FilesWritten
239         {
240             get
241             {
242                 return (ITaskItem[])_filesWritten.ToArray(typeof(ITaskItem));
243             }
244         }
245 
246         /// <summary>
247         /// The language to use when generating the class source for the strongly typed resource.
248         /// This parameter must match exactly one of the languages used by the CodeDomProvider.
249         /// </summary>
250         public string StronglyTypedLanguage
251         {
252             set
253             {
254                 // Since this string is passed directly into the framework, we don't want to
255                 // try to validate it -- that might prevent future expansion of supported languages.
256                 _stronglyTypedLanguage = value;
257             }
258             get { return _stronglyTypedLanguage; }
259         }
260 
261         /// <summary>
262         /// Specifies the namespace to use for the generated class source for the
263         /// strongly typed resource. If left blank, no namespace is used.
264         /// </summary>
265         public string StronglyTypedNamespace
266         {
267             set { _stronglyTypedNamespace = value; }
268             get { return _stronglyTypedNamespace; }
269         }
270 
271         /// <summary>
272         /// Specifies the resource namespace or manifest prefix to use in the generated
273         /// class source for the strongly typed resource.
274         /// </summary>
275         public string StronglyTypedManifestPrefix
276         {
277             set { _stronglyTypedManifestPrefix = value; }
278             get { return _stronglyTypedManifestPrefix; }
279         }
280 
281         /// <summary>
282         /// Specifies the class name for the strongly typed resource class.  If left blank, the base
283         /// name of the resource file is used.
284         /// </summary>
285         [Output]
286         public string StronglyTypedClassName
287         {
288             set { _stronglyTypedClassName = value; }
289             get { return _stronglyTypedClassName; }
290         }
291 
292         /// <summary>
293         /// Specifies the filename for the source file.  If left blank, the name of the class is
294         /// used as the base filename, with the extension dependent on the language.
295         /// </summary>
296         [Output]
297         public string StronglyTypedFileName
298         {
299             set { _stronglyTypedFileName = value; }
300             get { return _stronglyTypedFileName; }
301         }
302 
303         /// <summary>
304         /// Specifies whether the strongly typed class should be created public (with public methods)
305         /// instead of the default internal. Analogous to resgen.exe's /publicClass switch.
306         /// </summary>
307         public bool PublicClass
308         {
309             set { _publicClass = value; }
310             get { return _publicClass; }
311         }
312 
313         /// <summary>
314         /// Whether this rule is generating .resources files or extracting .ResW files from assemblies.
315         /// Requires some additional input filtering.
316         /// </summary>
317         public bool ExtractResWFiles
318         {
319             get;
320             set;
321         }
322 
323         /// <summary>
324         /// (default = false)
325         /// When true, a new AppDomain is always created to evaluate the .resx files.
326         /// When false, a new AppDomain is created only when it looks like a user's
327         ///  assembly is referenced by the .resx.
328         /// </summary>
329         public bool NeverLockTypeAssemblies
330         {
331             set { _neverLockTypeAssemblies = value; }
332             get { return _neverLockTypeAssemblies; }
333         }
334 
335         /// <summary>
336         /// Even though the generate resource task will do the processing in process, a logging message is still generated. This logging message
337         /// will include the path to the windows SDK. Since the targets now will pass in the Windows SDK path we should use this for logging.
338         /// </summary>
339         public string SdkToolsPath
340         {
341             get { return _sdkToolsPath; }
342             set { _sdkToolsPath = value; }
343         }
344 
345         /// <summary>
346         /// Property to allow multitargeting of ResolveComReferences:  If true, tlbimp.exe and
347         /// aximp.exe from the appropriate target framework will be run out-of-proc to generate
348         /// the necessary wrapper assemblies.
349         /// </summary>
350         public bool ExecuteAsTool
351         {
352             set { _executeAsTool = value; }
353             get { return _executeAsTool; }
354         }
355 
356         /// <summary>
357         /// Array of equals-separated pairs of environment
358         /// variables that should be passed to the spawned resgen.exe,
359         /// in addition to (or selectively overriding) the regular environment block.
360         /// These aren't currently used when resgen is run in-process.
361         /// </summary>
362         public string[] EnvironmentVariables
363         {
364             get;
365             set;
366         }
367 
368         /// <summary>
369         /// That set of paths from which tracked inputs will be ignored during
370         /// Up to date checking
371         /// </summary>
372         public ITaskItem[] ExcludedInputPaths
373         {
374             get { return _excludedInputPaths; }
375             set { _excludedInputPaths = value; }
376         }
377 
378         /// <summary>
379         /// Property used to set whether tracked incremental build will be used. If true,
380         /// incremental build is turned on; otherwise, a rebuild will be forced.
381         /// </summary>
382         public bool MinimalRebuildFromTracking
383         {
384             get
385             {
386                 // not using tracking anymore
387                 return false;
388             }
389 
390             set
391             {
392                 // do nothing
393             }
394         }
395 
396         /// <summary>
397         /// True if we should be tracking file access patterns - necessary for incremental
398         /// build support.
399         /// </summary>
400         public bool TrackFileAccess
401         {
402             get
403             {
404                 // not using tracking anymore
405                 return false;
406             }
407             set
408             {
409                 // do nothing
410             }
411         }
412 
413         /// <summary>
414         /// Names of the read tracking logs.
415         /// </summary>
416         [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "TLog", Justification = "Has now shipped as public API; plus it's unclear whether 'Tlog' or 'TLog' is the preferred casing")]
417         public ITaskItem[] TLogReadFiles
418         {
419             get
420             {
421                 return Array.Empty<ITaskItem>();
422             }
423         }
424 
425         /// <summary>
426         /// Names of the write tracking logs.
427         /// </summary>
428         [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "TLog", Justification = "Has now shipped as public API; plus it's unclear whether 'Tlog' or 'TLog' is the preferred casing")]
429         public ITaskItem[] TLogWriteFiles
430         {
431             get
432             {
433                 return Array.Empty<ITaskItem>();
434             }
435         }
436 
437         /// <summary>
438         /// Intermediate directory into which the tracking logs from running this task will be placed.
439         /// </summary>
440         public string TrackerLogDirectory
441         {
442             get
443             {
444                 return String.Empty;
445             }
446             set
447             {
448                 // do nothing
449             }
450         }
451 
452         /// <summary>
453         /// Microsoft.Build.Utilities.ExecutableType of ResGen.exe.  Used to determine whether or not
454         /// Tracker.exe needs to be used to spawn ResGen.exe.  If empty, uses a heuristic to determine
455         /// a default architecture.
456         /// </summary>
457         public string ToolArchitecture
458         {
459             get
460             {
461                 return String.Empty;
462             }
463 
464             set
465             {
466                 // do nothing
467             }
468         }
469 
470         /// <summary>
471         /// Path to the appropriate .NET Framework location that contains FileTracker.dll.  If set, the user
472         /// takes responsibility for making sure that the bitness of the FileTracker.dll that they pass matches
473         /// the bitness of the ResGen.exe that they intend to use. If not set, the task decides the appropriate
474         /// location based on the current .NET Framework version.
475         /// </summary>
476         /// <comments>
477         /// Should only need to be used in partial or full checked in toolset scenarios.
478         /// </comments>
479         public string TrackerFrameworkPath
480         {
481             get
482             {
483                 return String.Empty;
484             }
485 
486             set
487             {
488                 // do nothing
489             }
490         }
491 
492         /// <summary>
493         /// Path to the appropriate Windows SDK location that contains Tracker.exe.  If set, the user takes
494         /// responsibility for making sure that the bitness of the Tracker.exe that they pass matches the
495         /// bitness of the ResGen.exe that they intend to use. If not set, the task decides the appropriate
496         /// location based on the current Windows SDK.
497         /// </summary>
498         /// <comments>
499         /// Should only need to be used in partial or full checked in toolset scenarios.
500         /// </comments>
501         public string TrackerSdkPath
502         {
503             get
504             {
505                 return String.Empty;
506             }
507 
508             set
509             {
510                 // do nothing
511             }
512         }
513 
514         /// <summary>
515         /// Where to extract ResW files.  (Could be the intermediate directory.)
516         /// </summary>
517         public string OutputDirectory
518         {
519             get;
520             set;
521         }
522 
523         #endregion // properties
524 
525         /// <summary>
526         /// Simple public constructor.
527         /// </summary>
GenerateResource()528         public GenerateResource()
529         {
530             // do nothing
531         }
532 
533         /// <summary>
534         /// Logs a Resgen.exe command line that indicates what parameters were
535         /// passed to this task. Since this task is replacing Resgen, and we used
536         /// to log the Resgen.exe command line, we need to continue logging an
537         /// equivalent command line.
538         /// </summary>
539         /// <param name="inputFiles"></param>
540         /// <param name="outputFiles"></param>
LogResgenCommandLine(List<ITaskItem> inputFiles, List<ITaskItem> outputFiles)541         private void LogResgenCommandLine(List<ITaskItem> inputFiles, List<ITaskItem> outputFiles)
542         {
543             CommandLineBuilderExtension commandLineBuilder = new CommandLineBuilderExtension();
544 
545             // start the command line with the path to Resgen.exe
546             commandLineBuilder.AppendFileNameIfNotNull(Path.Combine(_resgenPath, "resgen.exe"));
547 
548             GenerateResGenCommandLineWithoutResources(commandLineBuilder);
549 
550             if (StronglyTypedLanguage == null)
551             {
552                 // append the resources to compile
553                 for (int i = 0; i < inputFiles.Count; ++i)
554                 {
555                     if (!ExtractResWFiles)
556                     {
557                         commandLineBuilder.AppendFileNamesIfNotNull
558                         (
559                             new string[] { inputFiles[i].ItemSpec, outputFiles[i].ItemSpec },
560                             ","
561                         );
562                     }
563                     else
564                     {
565                         commandLineBuilder.AppendFileNameIfNotNull(inputFiles[i].ItemSpec);
566                     }
567                 }
568             }
569             else
570             {
571                 // append the resource to compile
572                 commandLineBuilder.AppendFileNamesIfNotNull(inputFiles.ToArray(), " ");
573                 commandLineBuilder.AppendFileNamesIfNotNull(outputFiles.ToArray(), " ");
574 
575                 // append the strongly-typed resource details
576                 commandLineBuilder.AppendSwitchIfNotNull
577                 (
578                     "/str:",
579                     new string[] { StronglyTypedLanguage, StronglyTypedNamespace, StronglyTypedClassName, StronglyTypedFileName },
580                     ","
581                 );
582             }
583 
584             Log.LogCommandLine(MessageImportance.Low, commandLineBuilder.ToString());
585         }
586 
587         /// <summary>
588         /// Generate the parts of the resgen command line that are don't involve resgen.exe itself or the
589         /// resources to be generated.
590         /// </summary>
591         /// <comments>
592         /// Expects resGenCommand to be non-null -- otherwise, it doesn't get passed back to the caller, so it's
593         /// useless anyway.
594         /// </comments>
595         /// <param name="resGenCommand"></param>
GenerateResGenCommandLineWithoutResources(CommandLineBuilderExtension resGenCommand)596         private void GenerateResGenCommandLineWithoutResources(CommandLineBuilderExtension resGenCommand)
597         {
598             // Throw an internal error, since this method should only ever get called by other aspects of this task, not
599             // anything that the user touches.
600             ErrorUtilities.VerifyThrowInternalNull(resGenCommand, "resGenCommand");
601 
602             // append the /useSourcePath flag if requested.
603             if (UseSourcePath)
604             {
605                 resGenCommand.AppendSwitch("/useSourcePath");
606             }
607 
608             // append the /publicClass flag if requested
609             if (PublicClass)
610             {
611                 resGenCommand.AppendSwitch("/publicClass");
612             }
613 
614             // append the references, if any
615             if (References != null)
616             {
617                 foreach (ITaskItem reference in References)
618                 {
619                     resGenCommand.AppendSwitchIfNotNull("/r:", reference);
620                 }
621             }
622 
623             // append /compile switch if not creating strongly typed class
624             if (String.IsNullOrEmpty(StronglyTypedLanguage))
625             {
626                 resGenCommand.AppendSwitch("/compile");
627             }
628         }
629 
630         /// <summary>
631         /// This is the main entry point for the GenerateResource task.
632         /// </summary>
633         /// <returns>true, if task executes successfully</returns>
Execute()634         public override bool Execute()
635         {
636             bool outOfProcExecutionSucceeded = true;
637 #if (!STANDALONEBUILD)
638             using (new CodeMarkerStartEnd(CodeMarkerEvent.perfMSBuildGenerateResourceBegin, CodeMarkerEvent.perfMSBuildGenerateResourceEnd))
639 #endif
640             {
641                 // If we're extracting ResW files from assemblies (instead of building resources),
642                 // our Sources can contain PDB's, pictures, and other non-DLL's.  Prune that list.
643                 // .NET Framework assemblies are not included.  However, other Microsoft ones
644                 // such as MSTestFramework may be included (resolved from GetSDKReferenceFiles).
645                 if (ExtractResWFiles && Sources != null)
646                 {
647                     _satelliteInputs = new List<ITaskItem>();
648 
649                     List<ITaskItem> newSources = new List<ITaskItem>();
650                     foreach (ITaskItem item in Sources)
651                     {
652                         if (item.ItemSpec.EndsWith(".dll", StringComparison.OrdinalIgnoreCase))
653                         {
654                             if (item.ItemSpec.EndsWith(".resources.dll", StringComparison.OrdinalIgnoreCase))
655                             {
656                                 _satelliteInputs.Add(item);
657                             }
658                             else
659                             {
660                                 newSources.Add(item);
661                             }
662                         }
663                     }
664                     Sources = newSources.ToArray();
665                 }
666 
667 
668                 // If there are no sources to process, just return (with success) and report the condition.
669                 if ((Sources == null) || (Sources.Length == 0))
670                 {
671                     Log.LogMessageFromResources(MessageImportance.Low, "GenerateResource.NoSources");
672                     // Indicate we generated nothing
673                     OutputResources = null;
674                     return true;
675                 }
676 
677                 if (!ValidateParameters())
678                 {
679                     // Indicate we generated nothing
680                     OutputResources = null;
681                     return false;
682                 }
683 
684                 // In the case that OutputResources wasn't set, build up the outputs by transforming the Sources
685                 // However if we are extracting ResW files, we cannot easily tell which files we'll produce up front,
686                 // without loading each assembly.
687                 if (!ExtractResWFiles && !CreateOutputResourcesNames())
688                 {
689                     // Indicate we generated nothing
690                     OutputResources = null;
691                     return false;
692                 }
693 
694                 List<ITaskItem> inputsToProcess;
695                 List<ITaskItem> outputsToProcess;
696                 List<ITaskItem> cachedOutputFiles;  // For incremental builds, this is the set of already-existing, up to date files.
697 
698                 GetResourcesToProcess(out inputsToProcess, out outputsToProcess, out cachedOutputFiles);
699 
700                 if (inputsToProcess.Count == 0 && !Log.HasLoggedErrors)
701                 {
702                     if (cachedOutputFiles.Count > 0)
703                     {
704                         OutputResources = cachedOutputFiles.ToArray();
705                     }
706 
707                     Log.LogMessageFromResources("GenerateResource.NothingOutOfDate");
708                 }
709                 else
710                 {
711                     if (!ComputePathToResGen())
712                     {
713                         // unable to compute the path to resgen.exe and that is necessary to
714                         // continue forward, so return now.
715                         return false;
716                     }
717 
718                     if (ExecuteAsTool)
719                     {
720                         outOfProcExecutionSucceeded = GenerateResourcesUsingResGen(inputsToProcess, outputsToProcess);
721                     }
722                     else // Execute in-proc (or in a separate appdomain if necessary)
723                     {
724                         // Log equivalent command line as this is a convenient way to log all the references, etc
725                         // even though we're not actually running resgen.exe
726                         LogResgenCommandLine(inputsToProcess, outputsToProcess);
727 
728 #if FEATURE_APPDOMAIN
729                         // Figure out whether a separate AppDomain is required because an assembly would be locked.
730                         bool needSeparateAppDomain = NeedSeparateAppDomain();
731 
732                         AppDomain appDomain = null;
733 #endif
734                         ProcessResourceFiles process = null;
735 
736                         try
737                         {
738 #if FEATURE_APPDOMAIN
739                             if (needSeparateAppDomain)
740                             {
741                                 // we're going to be remoting across the appdomain boundary, so
742                                 // create the list that we'll use to disconnect the taskitems once we're done
743                                 _remotedTaskItems = new List<ITaskItem>();
744 
745                                 appDomain = AppDomain.CreateDomain
746                                 (
747                                     "generateResourceAppDomain",
748                                     null,
749                                     AppDomain.CurrentDomain.SetupInformation
750                                 );
751 
752                                 object obj = appDomain.CreateInstanceFromAndUnwrap
753                                    (
754                                        typeof(ProcessResourceFiles).Module.FullyQualifiedName,
755                                        typeof(ProcessResourceFiles).FullName
756                                    );
757 
758                                 Type processType = obj.GetType();
759                                 ErrorUtilities.VerifyThrow(processType == typeof(ProcessResourceFiles), "Somehow got a wrong and possibly incompatible type for ProcessResourceFiles.");
760 
761                                 process = (ProcessResourceFiles)obj;
762 
763                                 RecordItemsForDisconnectIfNecessary(_references);
764                                 RecordItemsForDisconnectIfNecessary(inputsToProcess);
765                                 RecordItemsForDisconnectIfNecessary(outputsToProcess);
766                             }
767                             else
768 #endif
769                             {
770                                 process = new ProcessResourceFiles();
771                             }
772 
773                             process.Run(Log, _references, inputsToProcess, _satelliteInputs, outputsToProcess, UseSourcePath,
774                                 StronglyTypedLanguage, _stronglyTypedNamespace, _stronglyTypedManifestPrefix,
775                                 StronglyTypedFileName, StronglyTypedClassName, PublicClass,
776                                 ExtractResWFiles, OutputDirectory);
777 
778                             this.StronglyTypedClassName = process.StronglyTypedClassName; // in case a default was chosen
779                             this.StronglyTypedFileName = process.StronglyTypedFilename;   // in case a default was chosen
780                             _stronglyTypedResourceSuccessfullyCreated = process.StronglyTypedResourceSuccessfullyCreated;
781                             if (null != process.UnsuccessfullyCreatedOutFiles)
782                             {
783                                 foreach (string item in process.UnsuccessfullyCreatedOutFiles)
784                                 {
785                                     _unsuccessfullyCreatedOutFiles.Add(item);
786                                 }
787                             }
788 
789                             if (ExtractResWFiles)
790                             {
791                                 ITaskItem[] outputResources = process.ExtractedResWFiles.ToArray();
792 #if FEATURE_APPDOMAIN
793                                 if (needSeparateAppDomain)
794                                 {
795                                     // Ensure we can unload the other AppDomain, yet still use the
796                                     // ITaskItems we got back.  Clone them.
797                                     outputResources = CloneValuesInThisAppDomain(outputResources);
798                                 }
799 #endif
800 
801                                 if (cachedOutputFiles.Count > 0)
802                                 {
803                                     OutputResources = new ITaskItem[outputResources.Length + cachedOutputFiles.Count];
804                                     outputResources.CopyTo(OutputResources, 0);
805                                     cachedOutputFiles.CopyTo(OutputResources, outputResources.Length);
806                                 }
807                                 else
808                                 {
809                                     OutputResources = outputResources;
810                                 }
811 
812 #if FEATURE_RESGENCACHE
813                                 // Get portable library cache info (and if needed, marshal it to this AD).
814                                 List<ResGenDependencies.PortableLibraryFile> portableLibraryCacheInfo = process.PortableLibraryCacheInfo;
815                                 for (int i = 0; i < portableLibraryCacheInfo.Count; i++)
816                                 {
817                                     _cache.UpdatePortableLibrary(portableLibraryCacheInfo[i]);
818                                 }
819 #endif
820                             }
821 
822                             process = null;
823                         }
824                         finally
825                         {
826 #if FEATURE_APPDOMAIN
827                             if (needSeparateAppDomain && appDomain != null)
828                             {
829                                 Log.MarkAsInactive();
830 
831                                 AppDomain.Unload(appDomain);
832                                 process = null;
833                                 appDomain = null;
834 
835                                 // if we've been asked to remote these items then
836                                 // we need to disconnect them from .NET Remoting now we're all done with them
837                                 if (_remotedTaskItems != null)
838                                 {
839                                     foreach (ITaskItem item in _remotedTaskItems)
840                                     {
841                                         if (item is MarshalByRefObject)
842                                         {
843                                             // Tell remoting to forget connections to the taskitem
844                                             RemotingServices.Disconnect((MarshalByRefObject)item);
845                                         }
846                                     }
847                                 }
848 
849                                 _remotedTaskItems = null;
850                             }
851 #endif
852                         }
853                     }
854                 }
855 
856 #if FEATURE_RESGENCACHE
857                 // And now we serialize the cache to save our resgen linked file resolution for later use.
858                 WriteStateFile();
859 #endif
860 
861                 RemoveUnsuccessfullyCreatedResourcesFromOutputResources();
862 
863                 RecordFilesWritten();
864             }
865 
866             return !Log.HasLoggedErrors && outOfProcExecutionSucceeded;
867         }
868 
869         /// <summary>
870         /// For setting OutputResources and ensuring it can be read after the second AppDomain has been unloaded.
871         /// </summary>
872         /// <param name="remoteValues">ITaskItems in another AppDomain</param>
873         /// <returns></returns>
CloneValuesInThisAppDomain(IList<ITaskItem> remoteValues)874         private static ITaskItem[] CloneValuesInThisAppDomain(IList<ITaskItem> remoteValues)
875         {
876             ITaskItem[] clonedOutput = new ITaskItem[remoteValues.Count];
877             for (int i = 0; i < remoteValues.Count; i++)
878             {
879                 clonedOutput[i] = new TaskItem(remoteValues[i]);
880             }
881 
882             return clonedOutput;
883         }
884 
885 #if FEATURE_APPDOMAIN
886         /// <summary>
887         /// Remember this TaskItem so that we can disconnect it when this Task has finished executing
888         /// Only if we're passing TaskItems to another AppDomain is this necessary. This call
889         /// Will make that determination for you.
890         /// </summary>
RecordItemsForDisconnectIfNecessary(IEnumerable<ITaskItem> items)891         private void RecordItemsForDisconnectIfNecessary(IEnumerable<ITaskItem> items)
892         {
893             if (_remotedTaskItems != null && items != null)
894             {
895                 // remember that we need to disconnect these items
896                 _remotedTaskItems.AddRange(items);
897             }
898         }
899 #endif
900 
901         /// <summary>
902         /// Computes the path to ResGen.exe for use in logging and for passing to the
903         /// nested ResGen task.
904         /// </summary>
905         /// <returns>True if the path is found (or it doesn't matter because we're executing in memory), false otherwise</returns>
ComputePathToResGen()906         private bool ComputePathToResGen()
907         {
908 #if FEATURE_RESGEN
909             _resgenPath = null;
910 
911             if (String.IsNullOrEmpty(_sdkToolsPath))
912             {
913                 _resgenPath = ToolLocationHelper.GetPathToDotNetFrameworkSdkFile("resgen.exe", TargetDotNetFrameworkVersion.Version35);
914 
915                 if (null == _resgenPath && ExecuteAsTool)
916                 {
917                     Log.LogErrorWithCodeFromResources("General.PlatformSDKFileNotFound", "resgen.exe",
918                         ToolLocationHelper.GetDotNetFrameworkSdkInstallKeyValue(TargetDotNetFrameworkVersion.Version35),
919                         ToolLocationHelper.GetDotNetFrameworkSdkRootRegistryKey(TargetDotNetFrameworkVersion.Version35));
920                 }
921             }
922             else
923             {
924                 _resgenPath = SdkToolsPathUtility.GeneratePathToTool(
925                     SdkToolsPathUtility.FileInfoExists,
926                     Microsoft.Build.Utilities.ProcessorArchitecture.CurrentProcessArchitecture,
927                     SdkToolsPath,
928                     "resgen.exe",
929                     Log,
930                     ExecuteAsTool);
931             }
932 
933             if (null == _resgenPath && !ExecuteAsTool)
934             {
935                 // if Resgen.exe is not installed, just use the filename
936                 _resgenPath = String.Empty;
937                 return true;
938             }
939 
940             // We may be passing this to the ResGen task (wrapper around resgen.exe), in which case
941             // we want to pass just the path -- ResGen will attach the "resgen.exe" onto the end
942             // itself.
943             if (_resgenPath != null)
944             {
945                 _resgenPath = Path.GetDirectoryName(_resgenPath);
946             }
947 
948             return _resgenPath != null;
949 #else
950             _resgenPath = string.Empty;
951             return true;
952 #endif
953         }
954 
955         /// <summary>
956         /// Wrapper around the call to the ResGen task that handles setting up the
957         /// task to run properly.
958         /// </summary>
959         /// <param name="inputsToProcess">Array of names of inputs to be processed</param>
960         /// <param name="outputsToProcess">Array of output names corresponding to the inputs</param>
GenerateResourcesUsingResGen(List<ITaskItem> inputsToProcess, List<ITaskItem> outputsToProcess)961         private bool GenerateResourcesUsingResGen(List<ITaskItem> inputsToProcess, List<ITaskItem> outputsToProcess)
962         {
963 #if FEATURE_RESGEN
964             bool resGenSucceeded = false;
965 
966             if (StronglyTypedLanguage != null)
967             {
968                 resGenSucceeded = GenerateStronglyTypedResourceUsingResGen(inputsToProcess, outputsToProcess);
969             }
970             else
971             {
972                 resGenSucceeded = TransformResourceFilesUsingResGen(inputsToProcess, outputsToProcess);
973             }
974 
975             return resGenSucceeded;
976 #else
977             Log.LogError("ResGen.exe not supported on .NET Core MSBuild");
978             return false;
979 #endif
980         }
981 
982 
983 #if FEATURE_RESGEN
984         /// <summary>
985         /// Given an instance of the ResGen task with everything but the strongly typed
986         /// resource-related parameters filled out, execute the task and return the result
987         /// </summary>
988         /// <param name="resGen">The task to execute.</param>
TransformResourceFilesUsingResGen(List<ITaskItem> inputsToProcess, List<ITaskItem> outputsToProcess)989         private bool TransformResourceFilesUsingResGen(List<ITaskItem> inputsToProcess, List<ITaskItem> outputsToProcess)
990         {
991             ErrorUtilities.VerifyThrow(inputsToProcess.Count != 0, "There should be resource files to process");
992             ErrorUtilities.VerifyThrow(inputsToProcess.Count == outputsToProcess.Count, "The number of inputs and outputs should be equal");
993 
994             bool succeeded = true;
995 
996             // We need to do a whole lot of work to make sure that we're not overrunning the command line ... UNLESS
997             // we're running ResGen 4.0 or later, which supports response files.
998             if (!_resgenPath.Equals(Path.GetDirectoryName(NativeMethodsShared.GetLongFilePath(ToolLocationHelper.GetPathToDotNetFrameworkSdkFile("resgen.exe", TargetDotNetFrameworkVersion.Version35))), StringComparison.OrdinalIgnoreCase))
999             {
1000                 ResGen resGen = CreateResGenTaskWithDefaultParameters();
1001 
1002                 resGen.InputFiles = inputsToProcess.ToArray();
1003                 resGen.OutputFiles = outputsToProcess.ToArray();
1004 
1005                 ITaskItem[] outputFiles = resGen.OutputFiles;
1006 
1007                 succeeded = resGen.Execute();
1008 
1009                 if (!succeeded)
1010                 {
1011                     foreach (ITaskItem outputFile in outputFiles)
1012                     {
1013                         if (!File.Exists(outputFile.ItemSpec))
1014                         {
1015                             _unsuccessfullyCreatedOutFiles.Add(outputFile.ItemSpec);
1016                         }
1017                     }
1018                 }
1019             }
1020             else
1021             {
1022                 int initialResourceIndex = 0;
1023                 int numberOfResourcesToAdd = 0;
1024                 bool doneProcessingResources = false;
1025                 CommandLineBuilderExtension resourcelessCommandBuilder = new CommandLineBuilderExtension();
1026                 string resourcelessCommand = null;
1027 
1028                 GenerateResGenCommandLineWithoutResources(resourcelessCommandBuilder);
1029 
1030                 if (resourcelessCommandBuilder.Length > 0)
1031                 {
1032                     resourcelessCommand = resourcelessCommandBuilder.ToString();
1033                 }
1034 
1035                 while (!doneProcessingResources)
1036                 {
1037                     numberOfResourcesToAdd = CalculateResourceBatchSize(inputsToProcess, outputsToProcess, resourcelessCommand, initialResourceIndex);
1038                     ResGen resGen = CreateResGenTaskWithDefaultParameters();
1039 
1040                     resGen.InputFiles = inputsToProcess.GetRange(initialResourceIndex, numberOfResourcesToAdd).ToArray();
1041                     resGen.OutputFiles = outputsToProcess.GetRange(initialResourceIndex, numberOfResourcesToAdd).ToArray();
1042 
1043                     ITaskItem[] outputFiles = resGen.OutputFiles;
1044 
1045                     bool thisBatchSucceeded = resGen.Execute();
1046 
1047                     // This batch failed, so add the failing resources from this batch to the list of failures
1048                     if (!thisBatchSucceeded)
1049                     {
1050                         foreach (ITaskItem outputFile in outputFiles)
1051                         {
1052                             if (!File.Exists(outputFile.ItemSpec))
1053                             {
1054                                 _unsuccessfullyCreatedOutFiles.Add(outputFile.ItemSpec);
1055                             }
1056                         }
1057                     }
1058 
1059                     initialResourceIndex += numberOfResourcesToAdd;
1060                     doneProcessingResources = initialResourceIndex == inputsToProcess.Count;
1061                     succeeded = succeeded && thisBatchSucceeded;
1062                 }
1063             }
1064 
1065             return succeeded;
1066         }
1067 #endif
1068 
1069 
1070         /// <summary>
1071         /// Given the list of inputs and outputs, returns the number of resources (starting at the provided initial index)
1072         /// that can fit onto the commandline without exceeding MaximumCommandLength.
1073         /// </summary>
CalculateResourceBatchSize(List<ITaskItem> inputsToProcess, List<ITaskItem> outputsToProcess, string resourcelessCommand, int initialResourceIndex)1074         private int CalculateResourceBatchSize(List<ITaskItem> inputsToProcess, List<ITaskItem> outputsToProcess, string resourcelessCommand, int initialResourceIndex)
1075         {
1076             CommandLineBuilderExtension currentCommand = new CommandLineBuilderExtension();
1077 
1078             if (!String.IsNullOrEmpty(resourcelessCommand))
1079             {
1080                 currentCommand.AppendTextUnquoted(resourcelessCommand);
1081             }
1082 
1083             int i = initialResourceIndex;
1084             while (currentCommand.Length < s_maximumCommandLength && i < inputsToProcess.Count)
1085             {
1086                 currentCommand.AppendFileNamesIfNotNull
1087                     (
1088                         new ITaskItem[] { inputsToProcess[i], outputsToProcess[i] },
1089                         ","
1090                     );
1091                 i++;
1092             }
1093 
1094             int numberOfResourcesToAdd = 0;
1095             if (currentCommand.Length <= s_maximumCommandLength)
1096             {
1097                 // We've successfully added all the rest.
1098                 numberOfResourcesToAdd = i - initialResourceIndex;
1099             }
1100             else
1101             {
1102                 // The last one added tossed us over the edge.
1103                 numberOfResourcesToAdd = i - initialResourceIndex - 1;
1104             }
1105 
1106             return numberOfResourcesToAdd;
1107         }
1108 
1109 
1110 #if FEATURE_RESGEN
1111         /// <summary>
1112         /// Given an instance of the ResGen task with everything but the strongly typed
1113         /// resource-related parameters filled out, execute the task and return the result
1114         /// </summary>
1115         /// <param name="resGen">The task to execute.</param>
GenerateStronglyTypedResourceUsingResGen(List<ITaskItem> inputsToProcess, List<ITaskItem> outputsToProcess)1116         private bool GenerateStronglyTypedResourceUsingResGen(List<ITaskItem> inputsToProcess, List<ITaskItem> outputsToProcess)
1117         {
1118             ErrorUtilities.VerifyThrow(inputsToProcess.Count == 1 && outputsToProcess.Count == 1, "For STR, there should only be one input and one output.");
1119 
1120             ResGen resGen = CreateResGenTaskWithDefaultParameters();
1121 
1122             resGen.InputFiles = inputsToProcess.ToArray();
1123             resGen.OutputFiles = outputsToProcess.ToArray();
1124 
1125             resGen.StronglyTypedLanguage = StronglyTypedLanguage;
1126             resGen.StronglyTypedNamespace = StronglyTypedNamespace;
1127             resGen.StronglyTypedClassName = StronglyTypedClassName;
1128             resGen.StronglyTypedFileName = StronglyTypedFileName;
1129 
1130             // Save the output file name -- ResGen will delete failing files
1131             ITaskItem outputFile = resGen.OutputFiles[0];
1132 
1133             _stronglyTypedResourceSuccessfullyCreated = resGen.Execute();
1134 
1135             if (!_stronglyTypedResourceSuccessfullyCreated && (resGen.OutputFiles == null || resGen.OutputFiles.Length == 0))
1136             {
1137                 _unsuccessfullyCreatedOutFiles.Add(outputFile.ItemSpec);
1138             }
1139 
1140             // now need to set the defaults (if defaults were chosen) so that they can be
1141             // consumed by outside users
1142             StronglyTypedClassName = resGen.StronglyTypedClassName;
1143             StronglyTypedFileName = resGen.StronglyTypedFileName;
1144 
1145             return _stronglyTypedResourceSuccessfullyCreated;
1146         }
1147 
1148         /// <summary>
1149         /// Factoring out the setting of the default parameters to the
1150         /// ResGen task.
1151         /// </summary>
1152         /// <param name="resGen"></param>
CreateResGenTaskWithDefaultParameters()1153         private ResGen CreateResGenTaskWithDefaultParameters()
1154         {
1155             ResGen resGen = new ResGen();
1156 
1157             resGen.BuildEngine = BuildEngine;
1158             resGen.SdkToolsPath = _resgenPath;
1159             resGen.PublicClass = PublicClass;
1160             resGen.References = References;
1161             resGen.UseSourcePath = UseSourcePath;
1162             resGen.EnvironmentVariables = EnvironmentVariables;
1163 
1164             return resGen;
1165         }
1166 #endif
1167 
1168         /// <summary>
1169         /// Check for parameter errors.
1170         /// </summary>
1171         /// <returns>true if parameters are valid</returns>
ValidateParameters()1172         private bool ValidateParameters()
1173         {
1174             // make sure that if the output resources were set, they exactly match the number of input sources
1175             if ((OutputResources != null) && (OutputResources.Length != Sources.Length))
1176             {
1177                 Log.LogErrorWithCodeFromResources("General.TwoVectorsMustHaveSameLength", Sources.Length, OutputResources.Length, "Sources", "OutputResources");
1178                 return false;
1179             }
1180 
1181             // Creating an STR is triggered merely by setting the language
1182             if (_stronglyTypedLanguage != null)
1183             {
1184                 // Like Resgen.exe, only a single Sources is allowed if you are generating STR.
1185                 // Otherwise, each STR class overwrites the previous one. In theory we could generate separate
1186                 // STR classes for each input, but then the class name and file name parameters would have to be vectors.
1187                 if (Sources.Length != 1)
1188                 {
1189                     Log.LogErrorWithCodeFromResources("GenerateResource.STRLanguageButNotExactlyOneSourceFile");
1190                     return false;
1191                 }
1192             }
1193             else
1194             {
1195                 if (StronglyTypedClassName != null || StronglyTypedNamespace != null || StronglyTypedFileName != null || StronglyTypedManifestPrefix != null)
1196                 {
1197                     // We have no language to generate a STR, but nevertheless the user passed us a class,
1198                     // namespace, and/or filename. Let them know that they probably wanted to pass a language too.
1199                     Log.LogErrorWithCodeFromResources("GenerateResource.STRClassNamespaceOrFilenameWithoutLanguage");
1200                     return false;
1201                 }
1202             }
1203 
1204             if (ExtractResWFiles && ExecuteAsTool)
1205             {
1206                 // This combination isn't currently supported, because ResGen may produce some not-easily-predictable
1207                 // set of ResW files and we don't have any logic to get that set of files back into GenerateResource
1208                 // at the moment.  This could be solved fixed with some engineering effort.
1209                 Log.LogErrorWithCodeFromResources("GenerateResource.ExecuteAsToolAndExtractResWNotSupported");
1210                 return false;
1211             }
1212 
1213             return true;
1214         }
1215 
1216         /// <summary>
1217         /// Returns true if everything is up to date and we don't need to do any work.
1218         /// </summary>
1219         /// <returns></returns>
GetResourcesToProcess(out List<ITaskItem> inputsToProcess, out List<ITaskItem> outputsToProcess, out List<ITaskItem> cachedOutputFiles)1220         private void GetResourcesToProcess(out List<ITaskItem> inputsToProcess, out List<ITaskItem> outputsToProcess, out List<ITaskItem> cachedOutputFiles)
1221         {
1222 #if FEATURE_RESGENCACHE
1223             // First we look to see if we have a resgen linked files cache.  If so, then we can use that
1224             // cache to speed up processing.
1225             ReadStateFile();
1226 #endif
1227 
1228             bool nothingOutOfDate = true;
1229             inputsToProcess = new List<ITaskItem>();
1230             outputsToProcess = new List<ITaskItem>();
1231             cachedOutputFiles = new List<ITaskItem>();
1232 
1233             // decide what sources we need to build
1234             for (int i = 0; i < Sources.Length; ++i)
1235             {
1236                 if (ExtractResWFiles)
1237                 {
1238 #if FEATURE_RESGENCACHE
1239                     // We can't cheaply predict the output files, since that would require
1240                     // loading each assembly.  So don't even try guessing what they will be.
1241                     // However, our cache will sometimes record all the info we need (for incremental builds).
1242                     string sourceFileName = Sources[i].ItemSpec;
1243                     ResGenDependencies.PortableLibraryFile library = _cache.TryGetPortableLibraryInfo(sourceFileName);
1244                     if (library != null && library.AllOutputFilesAreUpToDate())
1245                     {
1246                         AppendCachedOutputTaskItems(library, cachedOutputFiles);
1247                     }
1248                     else
1249                     {
1250 #endif
1251                         inputsToProcess.Add(Sources[i]);
1252 #if FEATURE_RESGENCACHE
1253                     }
1254 #endif
1255 
1256                     continue;
1257                 }
1258 
1259                 // Attributes from input items are forwarded to output items.
1260                 Sources[i].CopyMetadataTo(OutputResources[i]);
1261                 Sources[i].SetMetadata("OutputResource", OutputResources[i].ItemSpec);
1262 
1263                 if (!File.Exists(Sources[i].ItemSpec))
1264                 {
1265                     // Error but continue with the files that do exist
1266                     Log.LogErrorWithCodeFromResources("GenerateResource.ResourceNotFound", Sources[i].ItemSpec);
1267                     _unsuccessfullyCreatedOutFiles.Add(OutputResources[i].ItemSpec);
1268                 }
1269                 else
1270                 {
1271                     // check to see if the output resources file (and, if it is a .resx, any linked files)
1272                     // is up to date compared to the input file
1273                     if (ShouldRebuildResgenOutputFile(Sources[i].ItemSpec, OutputResources[i].ItemSpec))
1274                     {
1275                         nothingOutOfDate = false;
1276                         inputsToProcess.Add(Sources[i]);
1277                         outputsToProcess.Add(OutputResources[i]);
1278                     }
1279                 }
1280             }
1281 
1282             // If the STR class file is out of date (for example, it's missing) we don't want to skip
1283             // resource generation.
1284             if (_stronglyTypedLanguage != null)
1285             {
1286                 // We're generating a STR class file, so there must be exactly one input resource file.
1287                 // If that resource file itself is out of date, the STR class file is going to get generated anyway.
1288                 if (nothingOutOfDate && File.Exists(Sources[0].ItemSpec))
1289                 {
1290                     GetStronglyTypedResourceToProcess(ref inputsToProcess, ref outputsToProcess);
1291                 }
1292             }
1293         }
1294 
1295 #if FEATURE_RESGENCACHE
1296         /// <summary>
1297         /// Given a cached portable library that is up to date, create ITaskItems to represent the output of the task, as if we did real work.
1298         /// </summary>
1299         /// <param name="library">The portable library cache entry to extract output files & metadata from.</param>
1300         /// <param name="cachedOutputFiles">List of output files produced from the cache.</param>
AppendCachedOutputTaskItems(ResGenDependencies.PortableLibraryFile library, List<ITaskItem> cachedOutputFiles)1301         private void AppendCachedOutputTaskItems(ResGenDependencies.PortableLibraryFile library, List<ITaskItem> cachedOutputFiles)
1302         {
1303             foreach (string outputFileName in library.OutputFiles)
1304             {
1305                 ITaskItem item = new TaskItem(outputFileName);
1306                 item.SetMetadata("ResourceIndexName", library.AssemblySimpleName);
1307                 if (library.NeutralResourceLanguage != null)
1308                 {
1309                     item.SetMetadata("NeutralResourceLanguage", library.NeutralResourceLanguage);
1310                 }
1311 
1312                 cachedOutputFiles.Add(item);
1313             }
1314         }
1315 #endif
1316 
1317         /// <summary>
1318         /// Checks if this list contain any duplicates.  Do this so we don't have any races where we have two
1319         /// threads trying to write to the same file simultaneously.
1320         /// </summary>
1321         /// <param name="originalList">A list that may have duplicates</param>
1322         /// <returns>Were there duplicates?</returns>
ContainsDuplicates(IList<ITaskItem> originalList)1323         private bool ContainsDuplicates(IList<ITaskItem> originalList)
1324         {
1325             HashSet<string> set = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
1326             foreach (ITaskItem item in originalList)
1327             {
1328                 try
1329                 {
1330                     // Get the fully qualified path, to ensure two file names don't end up pointing to the same directory.
1331                     if (!set.Add(item.GetMetadata("FullPath")))
1332                     {
1333                         Log.LogErrorWithCodeFromResources("GenerateResource.DuplicateOutputFilenames", item.ItemSpec);
1334                         return true;
1335                     }
1336                 }
1337                 catch (InvalidOperationException e)
1338                 {
1339                     Log.LogErrorWithCodeFromResources("GenerateResource.InvalidFilename", item.ItemSpec, e.Message);
1340                     // Returning true causes us to not continue executing.
1341                     return true;
1342                 }
1343             }
1344 
1345             return false;
1346         }
1347 
1348         /// <summary>
1349         /// Determines if the given output file is up to date with respect to the
1350         /// the given input file by comparing timestamps of the two files as well as
1351         /// (if the source is a .resx) the linked files inside the .resx file itself
1352         /// </summary>
1353         /// <param name="sourceFilePath"></param>
1354         /// <param name="outputFilePath"></param>
1355         /// <returns></returns>
ShouldRebuildResgenOutputFile(string sourceFilePath, string outputFilePath)1356         private bool ShouldRebuildResgenOutputFile(string sourceFilePath, string outputFilePath)
1357         {
1358             // See if any uncorrelated inputs are missing before checking source and output file timestamps.
1359             // Don't consult the uncorrelated input file times if we haven't already got them:
1360             // typically, it's the .resx's that are out of date so we want to check those first.
1361             if (_foundNewestUncorrelatedInputWriteTime && GetNewestUncorrelatedInputWriteTime() == DateTime.MaxValue)
1362             {
1363                 // An uncorrelated input is missing; need to build
1364                 return true;
1365             }
1366 
1367             DateTime outputTime = NativeMethodsShared.GetLastWriteFileUtcTime(outputFilePath);
1368 
1369             // Quick check to see if any uncorrelated input is newer in which case we can avoid checking source file timestamp
1370             if (_foundNewestUncorrelatedInputWriteTime && GetNewestUncorrelatedInputWriteTime() > outputTime)
1371             {
1372                 // An uncorrelated input is newer, need to build
1373                 return true;
1374             }
1375 
1376             DateTime sourceTime = NativeMethodsShared.GetLastWriteFileUtcTime(sourceFilePath);
1377 
1378             if (!String.Equals(Path.GetExtension(sourceFilePath), ".resx", StringComparison.OrdinalIgnoreCase) &&
1379                 !String.Equals(Path.GetExtension(sourceFilePath), ".resw", StringComparison.OrdinalIgnoreCase))
1380             {
1381                 // If source file is NOT a .resx, for example a .restext file,
1382                 // timestamp checking is simple, because there's no linked files to examine, and no references.
1383                 return NeedToRebuildSourceFile(sourceTime, outputTime);
1384             }
1385 
1386 #if FEATURE_RESGENCACHE
1387             // OK, we have a .resx file
1388 
1389             // PERF: Regardless of whether the outputFile exists, if the source file is a .resx
1390             // go ahead and retrieve it from the cache. This is because we want the cache
1391             // to be populated so that incremental builds can be fast.
1392             // Note that this is a trade-off: clean builds will be slightly slower. However,
1393             // for clean builds we're about to read in this very same .resx file so reading
1394             // it now will page it in. The second read should be cheap.
1395             ResGenDependencies.ResXFile resxFileInfo = null;
1396             try
1397             {
1398                 resxFileInfo = _cache.GetResXFileInfo(sourceFilePath);
1399             }
1400             catch (Exception e)  // Catching Exception, but rethrowing unless it's a well-known exception.
1401             {
1402                 if (ExceptionHandling.NotExpectedIoOrXmlException(e))
1403                 {
1404                     throw;
1405                 }
1406 
1407                 // Return true, so that resource processing will display the error
1408                 // No point logging a duplicate error here as well
1409                 return true;
1410             }
1411 
1412             // if the .resources file is out of date even just with respect to the .resx or
1413             // the additional inputs, we don't need to go to the point of checking the linked files.
1414             if (NeedToRebuildSourceFile(sourceTime, outputTime))
1415             {
1416                 return true;
1417             }
1418 
1419             // The .resources is up to date with respect to the .resx file -
1420             // we need to compare timestamps for each linked file inside
1421             // the .resx file itself
1422             if (resxFileInfo.LinkedFiles != null)
1423             {
1424                 foreach (string linkedFilePath in resxFileInfo.LinkedFiles)
1425                 {
1426                     DateTime linkedFileTime = NativeMethodsShared.GetLastWriteFileUtcTime(linkedFilePath);
1427 
1428                     if (linkedFileTime == DateTime.MinValue)
1429                     {
1430                         // Linked file is missing - force a build, so that resource generation
1431                         // will produce a nice error message
1432                         return true;
1433                     }
1434 
1435                     if (linkedFileTime > outputTime)
1436                     {
1437                         // Linked file is newer, need to build
1438                         return true;
1439                     }
1440                 }
1441             }
1442 
1443             return false;
1444 #else
1445 #if FEATURE_RESX_RESOURCE_READER
1446             Compile error: if we get Resx reading before binary serialization
1447             it might be interesting to get caching working some other way.
1448 #endif
1449             // On .NET Core, we don't have binary serialization to maintain
1450             // the cache, but conveniently .NET Core assemblies (the only
1451             // supported target of .NET Core MSBuild) cannot use linked
1452             // resources. So the only relevant comparison is input/output.
1453             // See https://github.com/Microsoft/msbuild/issues/1197
1454             return NeedToRebuildSourceFile(sourceTime, outputTime);
1455 #endif
1456         }
1457 
1458         /// <summary>
1459         /// Returns true if the output does not exist, if the provided source is newer than the output,
1460         /// or if any of the set of additional inputs is newer than the output.  Otherwise, returns false.
1461         /// </summary>
NeedToRebuildSourceFile(DateTime sourceTime, DateTime outputTime)1462         private bool NeedToRebuildSourceFile(DateTime sourceTime, DateTime outputTime)
1463         {
1464             if (sourceTime > outputTime)
1465             {
1466                 // Source file is newer, need to build
1467                 return true;
1468             }
1469 
1470             if (GetNewestUncorrelatedInputWriteTime() > outputTime)
1471             {
1472                 // An uncorrelated input is newer, need to build
1473                 return true;
1474             }
1475 
1476             return false;
1477         }
1478 
1479         /// <summary>
1480         /// Add the strongly typed resource to the set of resources to process if it is out of date.
1481         /// </summary>
GetStronglyTypedResourceToProcess(ref List<ITaskItem> inputsToProcess, ref List<ITaskItem> outputsToProcess)1482         private void GetStronglyTypedResourceToProcess(ref List<ITaskItem> inputsToProcess, ref List<ITaskItem> outputsToProcess)
1483         {
1484             bool needToRebuildSTR = false;
1485 
1486             // The resource file isn't out of date. So check whether the STR class file is.
1487             try
1488             {
1489                 if (StronglyTypedFileName == null)
1490                 {
1491 #if FEATURE_CODEDOM
1492                     CodeDomProvider provider = null;
1493 
1494                     if (ProcessResourceFiles.TryCreateCodeDomProvider(Log, StronglyTypedLanguage, out provider))
1495                     {
1496                         StronglyTypedFileName = ProcessResourceFiles.GenerateDefaultStronglyTypedFilename(provider, OutputResources[0].ItemSpec);
1497                     }
1498 #else
1499                     StronglyTypedFileName = TryGenerateDefaultStronglyTypedFilename();
1500 #endif
1501                 }
1502             }
1503             catch (Exception e) when (ExceptionHandling.IsIoRelatedException(e))
1504             {
1505                 Log.LogErrorWithCodeFromResources("GenerateResource.CannotWriteSTRFile", StronglyTypedFileName, e.Message);
1506                 // Now that we've logged an error, we know we're not going to do anything more, so
1507                 // don't bother to correctly populate the inputs / outputs.
1508                 _unsuccessfullyCreatedOutFiles.Add(OutputResources[0].ItemSpec);
1509                 _stronglyTypedResourceSuccessfullyCreated = false;
1510                 return;
1511             }
1512 
1513             // Now we have the filename, check if it's up to date
1514             DateTime sourceTime = NativeMethodsShared.GetLastWriteFileUtcTime(Sources[0].ItemSpec);
1515             DateTime outputTime = NativeMethodsShared.GetLastWriteFileUtcTime(StronglyTypedFileName);
1516 
1517             if (sourceTime == DateTime.MinValue || outputTime == DateTime.MinValue)
1518             {
1519                 // Source file is missing - force a build, so that resource generation
1520                 // will produce a nice error message; or output file is missing,
1521                 // need to build it
1522                 needToRebuildSTR = true;
1523             }
1524             else if (sourceTime > outputTime)
1525             {
1526                 // Source file is newer, need to build
1527                 needToRebuildSTR = true;
1528             }
1529 
1530             if (needToRebuildSTR)
1531             {
1532                 // We know there's only one input, just make sure it gets processed and that will cause
1533                 // the STR class file to get updated
1534                 if (inputsToProcess.Count == 0)
1535                 {
1536                     inputsToProcess.Add(Sources[0]);
1537                     outputsToProcess.Add(OutputResources[0]);
1538                 }
1539             }
1540             else
1541             {
1542                 // If the STR class file is up to date and was skipped then we
1543                 // should consider the file written and add it to FilesWritten output
1544                 _stronglyTypedResourceSuccessfullyCreated = true;
1545             }
1546         }
1547 
1548         /// <summary>
1549         /// Returns the newest last write time among the references and additional inputs.
1550         /// If any do not exist, returns DateTime.MaxValue so that resource generation produces a nice error.
1551         /// </summary>
GetNewestUncorrelatedInputWriteTime()1552         private DateTime GetNewestUncorrelatedInputWriteTime()
1553         {
1554             if (!_foundNewestUncorrelatedInputWriteTime)
1555             {
1556                 _newestUncorrelatedInputWriteTime = DateTime.MinValue;
1557 
1558                 // Check the timestamp of each of the passed-in references to find the newest
1559                 if (this.References != null)
1560                 {
1561                     foreach (ITaskItem reference in this.References)
1562                     {
1563                         DateTime referenceTime = NativeMethodsShared.GetLastWriteFileUtcTime(reference.ItemSpec);
1564 
1565                         if (referenceTime == DateTime.MinValue)
1566                         {
1567                             // File does not exist: force a build to produce an error message
1568                             _foundNewestUncorrelatedInputWriteTime = true;
1569                             return DateTime.MaxValue;
1570                         }
1571 
1572                         if (referenceTime > _newestUncorrelatedInputWriteTime)
1573                         {
1574                             _newestUncorrelatedInputWriteTime = referenceTime;
1575                         }
1576                     }
1577                 }
1578 
1579                 // Check the timestamp of each of the additional inputs to see if one's even newer
1580                 if (this.AdditionalInputs != null)
1581                 {
1582                     foreach (ITaskItem additionalInput in this.AdditionalInputs)
1583                     {
1584                         DateTime additionalInputTime = NativeMethodsShared.GetLastWriteFileUtcTime(additionalInput.ItemSpec);
1585 
1586                         if (additionalInputTime == DateTime.MinValue)
1587                         {
1588                             // File does not exist: force a build to produce an error message
1589                             _foundNewestUncorrelatedInputWriteTime = true;
1590                             return DateTime.MaxValue;
1591                         }
1592 
1593                         if (additionalInputTime > _newestUncorrelatedInputWriteTime)
1594                         {
1595                             _newestUncorrelatedInputWriteTime = additionalInputTime;
1596                         }
1597                     }
1598                 }
1599 
1600                 _foundNewestUncorrelatedInputWriteTime = true;
1601             }
1602 
1603             return _newestUncorrelatedInputWriteTime;
1604         }
1605 
1606 #if FEATURE_APPDOMAIN
1607         /// <summary>
1608         /// Make the decision about whether a separate AppDomain is needed.
1609         /// If this algorithm is unsure about whether a separate AppDomain is
1610         /// needed, it should always err on the side of returning 'true'. This
1611         /// is because a separate AppDomain, while slow to create, is always safe.
1612         /// </summary>
1613         /// <param name="sources">The list of .resx files.</param>
1614         /// <returns></returns>
NeedSeparateAppDomain()1615         private bool NeedSeparateAppDomain()
1616         {
1617             if (NeverLockTypeAssemblies)
1618             {
1619                 Log.LogMessageFromResources(MessageImportance.Low, "GenerateResource.SeparateAppDomainBecauseNeverLockTypeAssembliesTrue");
1620                 return true;
1621             }
1622 
1623             foreach (ITaskItem source in _sources)
1624             {
1625                 string extension = Path.GetExtension(source.ItemSpec);
1626 
1627                 if (String.Compare(extension, ".resources.dll", StringComparison.OrdinalIgnoreCase) == 0 ||
1628                     String.Compare(extension, ".dll", StringComparison.OrdinalIgnoreCase) == 0 ||
1629                     String.Compare(extension, ".exe", StringComparison.OrdinalIgnoreCase) == 0)
1630                 {
1631                     return true;
1632                 }
1633 
1634                 if (String.Compare(extension, ".resx", StringComparison.OrdinalIgnoreCase) == 0 ||
1635                     String.Compare(extension, ".resw", StringComparison.OrdinalIgnoreCase) == 0)
1636                 {
1637                     XmlReader reader = null;
1638                     string name = null;
1639 
1640                     try
1641                     {
1642                         XmlReaderSettings readerSettings = new XmlReaderSettings();
1643                         readerSettings.DtdProcessing = DtdProcessing.Ignore;
1644                         reader = XmlReader.Create(source.ItemSpec, readerSettings);
1645 
1646                         while (reader.Read())
1647                         {
1648                             // Look for the <data> section
1649                             if (reader.NodeType == XmlNodeType.Element)
1650                             {
1651                                 if (String.Equals(reader.Name, "data", StringComparison.OrdinalIgnoreCase))
1652                                 {
1653                                     // Is there an attribute called type?
1654                                     string typeName = reader.GetAttribute("type");
1655                                     name = reader.GetAttribute("name");
1656 
1657                                     if (typeName != null)
1658                                     {
1659                                         Type type;
1660 
1661                                         // It is likely that we've seen this type before
1662                                         // we'll try our table of previously seen types
1663                                         // since it is *much* faster to do that than
1664                                         // call Type.GetType needlessly!
1665                                         if (!_typeTable.TryGetValue(typeName, out type))
1666                                         {
1667                                             string resolvedTypeName = typeName;
1668 
1669                                             // This type name might be an alias, so first resolve that if any.
1670                                             int indexOfSeperator = typeName.IndexOf(",", StringComparison.Ordinal);
1671 
1672                                             if (indexOfSeperator != -1)
1673                                             {
1674                                                 string typeFromTypeName = typeName.Substring(0, indexOfSeperator);
1675                                                 string maybeAliasFromTypeName = typeName.Substring(indexOfSeperator + 1);
1676 
1677                                                 if (!String.IsNullOrWhiteSpace(maybeAliasFromTypeName))
1678                                                 {
1679                                                     maybeAliasFromTypeName = maybeAliasFromTypeName.Trim();
1680 
1681                                                     string fullName = null;
1682                                                     if (_aliases.TryGetValue(maybeAliasFromTypeName, out fullName))
1683                                                     {
1684                                                         resolvedTypeName = typeFromTypeName + ", " + fullName;
1685                                                     }
1686                                                 }
1687                                             }
1688 
1689                                             // Can this type be found in the GAC?
1690                                             type = Type.GetType(resolvedTypeName, throwOnError: false, ignoreCase: false);
1691 
1692                                             // Remember our resolved type
1693                                             _typeTable[typeName] = type;
1694                                         }
1695 
1696                                         if (type == null)
1697                                         {
1698                                             // If the type could not be found in the GAC, then we're going to need
1699                                             // to load the referenced assemblies (those passed in through the
1700                                             // "References" parameter during the building of this .RESX.  Therefore,
1701                                             // we should create a separate app-domain, so that those assemblies
1702                                             // can be unlocked when the task is finished.
1703                                             // The type didn't start with "System." so return true.
1704                                             Log.LogMessageFromResources
1705                                             (
1706                                                 MessageImportance.Low,
1707                                                 "GenerateResource.SeparateAppDomainBecauseOfType",
1708                                                 (name == null) ? String.Empty : name,
1709                                                 typeName,
1710                                                 source.ItemSpec,
1711                                                 ((IXmlLineInfo)reader).LineNumber
1712                                             );
1713 
1714                                             return true;
1715                                         }
1716 
1717                                         // If there's a type, we don't need to look at any mimetype
1718                                         continue;
1719                                     }
1720 
1721                                     // DDB #9825.
1722                                     // There's no type attribute on this <data> -- if there's a MimeType, it's a serialized
1723                                     // object of unknown type, and we have to assume it will need a new app domain.
1724                                     // The possible mimetypes ResXResourceReader understands are:
1725                                     //
1726                                     // application/x-microsoft.net.object.binary.base64
1727                                     // application/x-microsoft.net.object.bytearray.base64
1728                                     // application/x-microsoft.net.object.binary.base64
1729                                     // application/x-microsoft.net.object.soap.base64
1730                                     // text/microsoft-urt/binary-serialized/base64
1731                                     // text/microsoft-urt/psuedoml-serialized/base64
1732                                     // text/microsoft-urt/soap-serialized/base64
1733                                     //
1734                                     // Of these, application/x-microsoft.net.object.bytearray.base64 usually has a type attribute
1735                                     // as well; ResxResourceReader will use that Type, which may not need a new app domain. So
1736                                     // if there's a type attribute, we don't look at mimetype.
1737                                     //
1738                                     // If there is a mimetype and no type, we can't tell the type without deserializing and loading it,
1739                                     // so we assume a new appdomain is needed.
1740                                     //
1741                                     // Actually, if application/x-microsoft.net.object.bytearray.base64 doesn't have a Type attribute,
1742                                     // ResxResourceReader assumes System.String, but for safety we don't assume that here.
1743 
1744                                     string mimeType = reader.GetAttribute("mimetype");
1745 
1746                                     if (mimeType != null)
1747                                     {
1748                                         if (NeedSeparateAppDomainBasedOnSerializedType(reader))
1749                                         {
1750                                             Log.LogMessageFromResources
1751                                             (
1752                                                 MessageImportance.Low,
1753                                                 "GenerateResource.SeparateAppDomainBecauseOfMimeType",
1754                                                 (name == null) ? String.Empty : name,
1755                                                 mimeType,
1756                                                 source.ItemSpec,
1757                                                 ((IXmlLineInfo)reader).LineNumber
1758                                             );
1759 
1760                                             return true;
1761                                         }
1762                                     }
1763                                 }
1764                                 else if (String.Equals(reader.Name, "assembly", StringComparison.OrdinalIgnoreCase))
1765                                 {
1766                                     string alias = reader.GetAttribute("alias");
1767                                     string fullName = reader.GetAttribute("name");
1768 
1769                                     if (!String.IsNullOrWhiteSpace(alias) && !String.IsNullOrWhiteSpace(fullName))
1770                                     {
1771                                         alias = alias.Trim();
1772                                         fullName = fullName.Trim();
1773 
1774                                         _aliases[alias] = fullName;
1775                                     }
1776                                 }
1777                             }
1778                         }
1779                     }
1780                     catch (XmlException e)
1781                     {
1782                         Log.LogMessageFromResources
1783                                     (
1784                                         MessageImportance.Low,
1785                                         "GenerateResource.SeparateAppDomainBecauseOfExceptionLineNumber",
1786                                         source.ItemSpec,
1787                                         ((IXmlLineInfo)reader).LineNumber,
1788                                         e.Message
1789                                     );
1790 
1791                         return true;
1792                     }
1793 #if FEATURE_RESGENCACHE
1794                     catch (SerializationException e)
1795                     {
1796                         Log.LogMessageFromResources
1797                                     (
1798                                         MessageImportance.Low,
1799                                         "GenerateResource.SeparateAppDomainBecauseOfErrorDeserializingLineNumber",
1800                                         source.ItemSpec,
1801                                         (name == null) ? String.Empty : name,
1802                                         ((IXmlLineInfo)reader).LineNumber,
1803                                         e.Message
1804                                     );
1805 
1806                         return true;
1807                     }
1808 #endif
1809                     catch (Exception e)
1810                     {
1811                         // DDB#9819
1812                         // Customers have reported the following exceptions coming out of this method's call to GetType():
1813                         //      System.Runtime.InteropServices.COMException (0x8000000A): The data necessary to complete this operation is not yet available. (Exception from HRESULT: 0x8000000A)
1814                         //      System.NullReferenceException: Object reference not set to an instance of an object.
1815                         //      System.InvalidOperationException: Collection was modified; enumeration operation may not execute.
1816                         // We don't have reproes, but probably the right thing to do is to assume a new app domain is needed on almost any exception.
1817                         // Any problem loading the type will get logged later when the resource reader tries it.
1818                         //
1819                         // XmlException or an IO exception is also possible from an invalid input file.
1820                         if (ExceptionHandling.IsCriticalException(e))
1821                             throw;
1822 
1823                         // If there was any problem parsing the .resx then log a message and
1824                         // fall back to using a separate AppDomain.
1825                         Log.LogMessageFromResources
1826                                     (
1827                                         MessageImportance.Low,
1828                                         "GenerateResource.SeparateAppDomainBecauseOfException",
1829                                         source.ItemSpec,
1830                                         e.Message
1831                                     );
1832 
1833                         // In case we need more information from the customer (given this has been heavily reported
1834                         // and we don't understand it properly) let the usual debug switch dump the stack.
1835                         if (Environment.GetEnvironmentVariable("MSBUILDDEBUG") == "1")
1836                         {
1837                             Log.LogErrorFromException(e, /* stack */ true, /* inner exceptions */ true, null);
1838                         }
1839 
1840                         return true;
1841                     }
1842                     finally
1843                     {
1844                         reader?.Close();
1845                     }
1846                 }
1847             }
1848 
1849             return false;
1850         }
1851 
1852         /// <summary>
1853         /// Finds the "value" element expected to be the next element read from the supplied reader.
1854         /// Deserializes the data content in order to figure out whether it implies a new app domain
1855         /// should be used to process resources.
1856         /// </summary>
NeedSeparateAppDomainBasedOnSerializedType(XmlReader reader)1857         private bool NeedSeparateAppDomainBasedOnSerializedType(XmlReader reader)
1858         {
1859 #if FEATURE_RESGENCACHE
1860             while (reader.Read())
1861             {
1862                 if (reader.NodeType == XmlNodeType.Element)
1863                 {
1864                     if (!String.Equals(reader.Name, "value", StringComparison.OrdinalIgnoreCase))
1865                     {
1866                         // <data> claimed it was serialized, but didn't have a <value>;
1867                         // return true to err on side of caution
1868                         return true;
1869                     }
1870 
1871                     // Found "value" element
1872                     string data = reader.ReadElementContentAsString();
1873 
1874                     bool isSerializedObjectLoadable = DetermineWhetherSerializedObjectLoads(data);
1875 
1876                     // If it's not loadable, it's presumably a user type, so create a new app domain
1877                     return !isSerializedObjectLoadable;
1878                 }
1879             }
1880 
1881             // We didn't find any element at all -- the .resx is malformed.
1882             // Return true to err on the side of caution. Error will appear later.
1883 #endif
1884             return true;
1885         }
1886 #endif
1887 
1888 #if FEATURE_RESGENCACHE
1889         /// <summary>
1890         /// Deserializes a base64 block from a resx in order to figure out if its type is in the GAC.
1891         /// Because we're not providing any assembly resolution callback, deserialization
1892         /// will attempt to load the object's type using fusion rules, which essentially means
1893         /// the GAC. So, if the object is null, it's definitely not in the GAC.
1894         /// </summary>
DetermineWhetherSerializedObjectLoads(string data)1895         private bool DetermineWhetherSerializedObjectLoads(string data)
1896         {
1897             byte[] serializedData = ByteArrayFromBase64WrappedString(data);
1898 
1899             BinaryFormatter binaryFormatter = new BinaryFormatter();
1900 
1901             using (MemoryStream memoryStream = new MemoryStream(serializedData))
1902             {
1903                 object result = binaryFormatter.Deserialize(memoryStream);
1904 
1905                 return (result != null);
1906             }
1907         }
1908 #endif
1909 
1910         /// <summary>
1911         /// Chars that should be ignored in the nicely justified block of base64
1912         /// </summary>
1913         private static readonly char[] s_specialChars = new char[] { ' ', '\r', '\n' };
1914 
1915         /// <summary>
1916         /// Turns the nicely justified block of base64 found in a resx into a byte array.
1917         /// Copied from fx\src\winforms\managed\system\winforms\control.cs
1918         /// </summary>
ByteArrayFromBase64WrappedString(string text)1919         private static byte[] ByteArrayFromBase64WrappedString(string text)
1920         {
1921             if (text.IndexOfAny(s_specialChars) != -1)
1922             {
1923                 StringBuilder sb = new StringBuilder(text.Length);
1924                 for (int i = 0; i < text.Length; i++)
1925                 {
1926                     switch (text[i])
1927                     {
1928                         case ' ':
1929                         case '\r':
1930                         case '\n':
1931                             break;
1932                         default:
1933                             sb.Append(text[i]);
1934                             break;
1935                     }
1936                 }
1937                 return Convert.FromBase64String(sb.ToString());
1938             }
1939             else
1940             {
1941                 return Convert.FromBase64String(text);
1942             }
1943         }
1944 
1945         /// <summary>
1946         /// Make sure that OutputResources has 1 file name for each name in Sources.
1947         /// </summary>
CreateOutputResourcesNames()1948         private bool CreateOutputResourcesNames()
1949         {
1950             if (OutputResources == null)
1951             {
1952                 OutputResources = new ITaskItem[Sources.Length];
1953                 int i = 0;
1954                 try
1955                 {
1956                     for (i = 0; i < Sources.Length; ++i)
1957                     {
1958                         OutputResources[i] = new TaskItem(Path.ChangeExtension(Sources[i].ItemSpec, ".resources"));
1959                     }
1960                 }
1961                 catch (ArgumentException e)
1962                 {
1963                     Log.LogErrorWithCodeFromResources("GenerateResource.InvalidFilename", Sources[i].ItemSpec, e.Message);
1964                     return false;
1965                 }
1966             }
1967 
1968             // Now check for duplicates.
1969             if (ContainsDuplicates(OutputResources))
1970             {
1971                 return false;
1972             }
1973 
1974             return true;
1975         }
1976 
1977         /// <summary>
1978         /// Remove any output resources that we didn't successfully create (due to error) from the
1979         /// OutputResources list. Keeps the ordering of OutputResources the same.
1980         /// </summary>
1981         /// <remarks>
1982         /// Q: Why didn't we keep a "successfully created" list instead, like in the Copy task does, which
1983         /// would save us doing the removal algorithm below?
1984         /// A: Because we want the ordering of OutputResources to be the same as the ordering passed in.
1985         /// Some items (the up to date ones) would be added to the successful output list first, and the other items
1986         /// are added during processing, so the ordering would change. We could fix that up, but it's better to do
1987         /// the fix up only in the rarer error case. If there were no errors, the algorithm below skips.</remarks>
RemoveUnsuccessfullyCreatedResourcesFromOutputResources()1988         private void RemoveUnsuccessfullyCreatedResourcesFromOutputResources()
1989         {
1990             // Normally, there aren't any unsuccessful conversions.
1991             if (_unsuccessfullyCreatedOutFiles == null ||
1992                 _unsuccessfullyCreatedOutFiles.Count == 0)
1993             {
1994                 return;
1995             }
1996 
1997             ErrorUtilities.VerifyThrow(OutputResources != null && OutputResources.Length != 0, "Should be at least one output resource");
1998 
1999             // We only get here if there was at least one resource generation error.
2000             ITaskItem[] temp = new ITaskItem[OutputResources.Length - _unsuccessfullyCreatedOutFiles.Count];
2001             int copied = 0;
2002             int removed = 0;
2003             for (int i = 0; i < Sources.Length; i++)
2004             {
2005                 // Check whether this one is in the bad list.
2006                 if (removed < _unsuccessfullyCreatedOutFiles.Count &&
2007                     _unsuccessfullyCreatedOutFiles.Contains(OutputResources[i].ItemSpec))
2008                 {
2009                     removed++;
2010                     Sources[i].SetMetadata("OutputResource", String.Empty);
2011                 }
2012                 else
2013                 {
2014                     // Copy it to the okay list.
2015                     temp[copied] = OutputResources[i];
2016                     copied++;
2017                 }
2018             }
2019             OutputResources = temp;
2020         }
2021 
2022         /// <summary>
2023         /// Record the list of file that will be written to disk.
2024         /// </summary>
RecordFilesWritten()2025         private void RecordFilesWritten()
2026         {
2027             // Add any output resources that were successfully created,
2028             // or would have been if they weren't already up to date (important for Clean)
2029             if (this.OutputResources != null)
2030             {
2031                 foreach (ITaskItem item in this.OutputResources)
2032                 {
2033                     _filesWritten.Add(item);
2034                 }
2035             }
2036 
2037             // Add any state file
2038             if (StateFile != null && StateFile.ItemSpec.Length > 0)
2039             {
2040                 // It's possible the file wasn't actually written (eg the path was invalid)
2041                 // We can't easily tell whether that happened here, and I think it's fine to add it anyway.
2042                 _filesWritten.Add(StateFile);
2043             }
2044 
2045             // Only add the STR class file if the CodeDOM succeeded, or if it exists and is up to date.
2046             // Otherwise, we probably didn't write it successfully.
2047             if (_stronglyTypedResourceSuccessfullyCreated)
2048             {
2049                 if (StronglyTypedFileName == null)
2050                 {
2051 #if FEATURE_CODEDOM
2052                     CodeDomProvider provider = null;
2053 
2054                     if (ProcessResourceFiles.TryCreateCodeDomProvider(Log, StronglyTypedLanguage, out provider))
2055                     {
2056                         StronglyTypedFileName = ProcessResourceFiles.GenerateDefaultStronglyTypedFilename(
2057                             provider, OutputResources[0].ItemSpec);
2058                     }
2059 #else
2060                     StronglyTypedFileName = TryGenerateDefaultStronglyTypedFilename();
2061 #endif
2062                 }
2063 
2064                 _filesWritten.Add(new TaskItem(this.StronglyTypedFileName));
2065             }
2066         }
2067 
2068 #if !FEATURE_CODEDOM
TryGenerateDefaultStronglyTypedFilename()2069         private string TryGenerateDefaultStronglyTypedFilename()
2070         {
2071             string extension = null;
2072             if (StronglyTypedLanguage == CSharpLanguageName)
2073             {
2074                 extension = ".cs";
2075             }
2076             else if (StronglyTypedLanguage == VisualBasicLanguageName)
2077             {
2078                 extension = ".vb";
2079             }
2080             if (extension != null)
2081             {
2082                 return Path.ChangeExtension(OutputResources[0].ItemSpec, extension);
2083             }
2084             return null;
2085         }
2086 #endif
2087 
2088 #if FEATURE_RESGENCACHE
2089         /// <summary>
2090         /// Read the state file if able.
2091         /// </summary>
ReadStateFile()2092         private void ReadStateFile()
2093         {
2094             // First we look to see if we have a resgen linked files cache.  If so, then we can use that
2095             // cache to speed up processing.  If there's a problem reading the cache file (or it
2096             // just doesn't exist, then this method will return a brand new cache object.
2097 
2098             // This method eats IO Exceptions
2099             _cache = ResGenDependencies.DeserializeCache((StateFile == null) ? null : StateFile.ItemSpec, UseSourcePath, Log);
2100             ErrorUtilities.VerifyThrow(_cache != null, "We did not create a cache!");
2101         }
2102 
2103         /// <summary>
2104         /// Write the state file if there is one to be written.
2105         /// </summary>
WriteStateFile()2106         private void WriteStateFile()
2107         {
2108             if (_cache.IsDirty)
2109             {
2110                 // And now we serialize the cache to save our resgen linked file resolution for later use.
2111                 _cache.SerializeCache((StateFile == null) ? null : StateFile.ItemSpec, Log);
2112             }
2113         }
2114 #endif
2115     }
2116 
2117     /// <summary>
2118     /// This class handles the processing of source resource files into compiled resource files.
2119     /// Its designed to be called from a separate AppDomain so that any files locked by ResXResourceReader
2120     /// can be released.
2121     /// </summary>
2122     internal sealed class ProcessResourceFiles
2123 #if FEATURE_APPDOMAIN
2124         : MarshalByRefObject
2125 #endif
2126     {
2127         #region fields
2128         /// <summary>
2129         /// List of readers used for input.
2130         /// </summary>
2131         private List<ReaderInfo> _readers = new List<ReaderInfo>();
2132 
2133         /// <summary>
2134         /// Logger for any messages or errors
2135         /// </summary>
2136         private TaskLoggingHelper _logger = null;
2137 
2138         /// <summary>
2139         /// Language for the strongly typed resources.
2140         /// </summary>
2141         private string _stronglyTypedLanguage;
2142 
2143         /// <summary>
2144         /// Filename for the strongly typed resources.
2145         /// Getter provided since the processor may choose a default.
2146         /// </summary>
2147         internal string StronglyTypedFilename
2148         {
2149             get
2150             {
2151                 return _stronglyTypedFilename;
2152             }
2153         }
2154         private string _stronglyTypedFilename;
2155 
2156         /// <summary>
2157         /// Namespace for the strongly typed resources.
2158         /// </summary>
2159         private string _stronglyTypedNamespace;
2160 
2161         /// <summary>
2162         /// ResourceNamespace for the strongly typed resources.
2163         /// </summary>
2164         private string _stronglyTypedResourcesNamespace;
2165 
2166         /// <summary>
2167         /// Class name for the strongly typed resources.
2168         /// Getter provided since the processor may choose a default.
2169         /// </summary>
2170         internal string StronglyTypedClassName
2171         {
2172             get
2173             {
2174                 return _stronglyTypedClassName;
2175             }
2176         }
2177         private string _stronglyTypedClassName;
2178 
2179         /// <summary>
2180         /// Whether the fields in the STR class should be public, rather than internal
2181         /// </summary>
2182         private bool _stronglyTypedClassIsPublic;
2183 
2184 #if FEATURE_ASSEMBLY_LOADFROM
2185         /// <summary>
2186         /// Class that gets called by the ResxResourceReader to resolve references
2187         /// to assemblies within the .RESX.
2188         /// </summary>
2189         private AssemblyNamesTypeResolutionService _typeResolver = null;
2190 
2191         /// <summary>
2192         /// Handles assembly resolution events.
2193         /// </summary>
2194         private ResolveEventHandler _eventHandler;
2195 #endif
2196 
2197         /// <summary>
2198         /// The referenced assemblies
2199         /// </summary>
2200         private ITaskItem[] _assemblyFiles;
2201 
2202 #if FEATURE_ASSEMBLY_LOADFROM
2203         /// <summary>
2204         /// The AssemblyNameExtensions for each of the referenced assemblies in "assemblyFiles".
2205         /// This is populated lazily.
2206         /// </summary>
2207         private AssemblyNameExtension[] _assemblyNames;
2208 #endif
2209 
2210         /// <summary>
2211         /// List of input files to process.
2212         /// </summary>
2213         private List<ITaskItem> _inFiles;
2214 
2215         /// <summary>
2216         /// List of satellite input files to process.
2217         /// </summary>
2218         private List<ITaskItem> _satelliteInFiles;
2219 
2220         /// <summary>
2221         /// List of output files to process.
2222         /// </summary>
2223         private List<ITaskItem> _outFiles;
2224 
2225         /// <summary>
2226         /// Whether we are extracting ResW files from an assembly, instead of creating .resources files.
2227         /// </summary>
2228         private bool _extractResWFiles;
2229 
2230         /// <summary>
2231         /// Where to write extracted ResW files.
2232         /// </summary>
2233         private string _resWOutputDirectory;
2234 
2235         internal List<ITaskItem> ExtractedResWFiles
2236         {
2237             get
2238             {
2239                 if (_extractedResWFiles == null)
2240                 {
2241                     _extractedResWFiles = new List<ITaskItem>();
2242                 }
2243                 return _extractedResWFiles;
2244             }
2245         }
2246         private List<ITaskItem> _extractedResWFiles;
2247 
2248 #if FEATURE_RESGENCACHE
2249         /// <summary>
2250         /// Record all the information about outputs here to avoid future incremental builds.
2251         /// </summary>
2252         internal List<ResGenDependencies.PortableLibraryFile> PortableLibraryCacheInfo
2253         {
2254             get { return _portableLibraryCacheInfo; }
2255         }
2256         private List<ResGenDependencies.PortableLibraryFile> _portableLibraryCacheInfo;
2257 #endif
2258 
2259         /// <summary>
2260         /// List of output files that we failed to create due to an error.
2261         /// See note in RemoveUnsuccessfullyCreatedResourcesFromOutputResources()
2262         /// </summary>
2263         internal ArrayList UnsuccessfullyCreatedOutFiles
2264         {
2265             get
2266             {
2267                 if (null == _unsuccessfullyCreatedOutFiles)
2268                 {
2269                     _unsuccessfullyCreatedOutFiles = new ArrayList();
2270                 }
2271                 return _unsuccessfullyCreatedOutFiles;
2272             }
2273         }
2274         private ArrayList _unsuccessfullyCreatedOutFiles;
2275 
2276         /// <summary>
2277         /// Whether we successfully created the STR class
2278         /// </summary>
2279         internal bool StronglyTypedResourceSuccessfullyCreated
2280         {
2281             get
2282             {
2283                 return _stronglyTypedResourceSuccessfullyCreated;
2284             }
2285         }
2286         private bool _stronglyTypedResourceSuccessfullyCreated = false;
2287 
2288         /// <summary>
2289         /// Indicates whether the resource reader should use the source file's
2290         /// directory to resolve relative file paths.
2291         /// </summary>
2292         private bool _useSourcePath = false;
2293 
2294 #endregion
2295 
2296         /// <summary>
2297         /// Process all files.
2298         /// </summary>
Run(TaskLoggingHelper log, ITaskItem[] assemblyFilesList, List<ITaskItem> inputs, List<ITaskItem> satelliteInputs, List<ITaskItem> outputs, bool sourcePath, string language, string namespacename, string resourcesNamespace, string filename, string classname, bool publicClass, bool extractingResWFiles, string resWOutputDirectory)2299         internal void Run(TaskLoggingHelper log, ITaskItem[] assemblyFilesList, List<ITaskItem> inputs, List<ITaskItem> satelliteInputs, List<ITaskItem> outputs, bool sourcePath,
2300                           string language, string namespacename, string resourcesNamespace, string filename, string classname, bool publicClass,
2301                           bool extractingResWFiles, string resWOutputDirectory)
2302         {
2303             _logger = log;
2304             _assemblyFiles = assemblyFilesList;
2305             _inFiles = inputs;
2306             _satelliteInFiles = satelliteInputs;
2307             _outFiles = outputs;
2308             _useSourcePath = sourcePath;
2309             _stronglyTypedLanguage = language;
2310             _stronglyTypedNamespace = namespacename;
2311             _stronglyTypedResourcesNamespace = resourcesNamespace;
2312             _stronglyTypedFilename = filename;
2313             _stronglyTypedClassName = classname;
2314             _stronglyTypedClassIsPublic = publicClass;
2315             _readers = new List<ReaderInfo>();
2316             _extractResWFiles = extractingResWFiles;
2317             _resWOutputDirectory = resWOutputDirectory;
2318 #if FEATURE_RESGENCACHE
2319             _portableLibraryCacheInfo = new List<ResGenDependencies.PortableLibraryFile>();
2320 #endif
2321 
2322 #if FEATURE_ASSEMBLY_LOADFROM
2323             // If references were passed in, we will have to give the ResxResourceReader an object
2324             // by which it can resolve types that are referenced from within the .RESX.
2325             if ((_assemblyFiles != null) && (_assemblyFiles.Length > 0))
2326             {
2327                 _typeResolver = new AssemblyNamesTypeResolutionService(_assemblyFiles);
2328             }
2329 #endif
2330 
2331             try
2332             {
2333 #if FEATURE_ASSEMBLY_LOADFROM
2334                 // Install assembly resolution event handler.
2335                 _eventHandler = new ResolveEventHandler(ResolveAssembly);
2336                 AppDomain.CurrentDomain.AssemblyResolve += _eventHandler;
2337 #endif
2338 
2339                 for (int i = 0; i < _inFiles.Count; ++i)
2340                 {
2341                     string outputSpec = _extractResWFiles ? resWOutputDirectory : _outFiles[i].ItemSpec;
2342                     if (!ProcessFile(_inFiles[i].ItemSpec, outputSpec))
2343                     {
2344                         // Since we failed, remove items from OutputResources.  Note when extracting ResW
2345                         // files, we won't have added anything to OutputResources up front though.
2346                         if (!_extractResWFiles)
2347                         {
2348                             UnsuccessfullyCreatedOutFiles.Add(outputSpec);
2349                         }
2350                     }
2351                 }
2352             }
2353             finally
2354             {
2355 #if FEATURE_ASSEMBLY_LOADFROM
2356                 // Remove the event handler.
2357                 AppDomain.CurrentDomain.AssemblyResolve -= _eventHandler;
2358                 _eventHandler = null;
2359 #endif
2360             }
2361         }
2362 
2363 #if FEATURE_ASSEMBLY_LOADFROM
2364         /// <summary>
2365         /// Callback to resolve assembly names to assemblies.
2366         /// </summary>
2367         /// <param name="sender"></param>
2368         /// <param name="args"></param>
2369         /// <returns></returns>
ResolveAssembly(object sender, ResolveEventArgs args)2370         internal Assembly ResolveAssembly(object sender, ResolveEventArgs args)
2371         {
2372             AssemblyNameExtension requestedAssemblyName = new AssemblyNameExtension(args.Name);
2373 
2374             if (_assemblyFiles != null)
2375             {
2376                 // Populate the list of assembly names for all passed-in references if it hasn't
2377                 // been populated already.
2378                 if (_assemblyNames == null)
2379                 {
2380                     _assemblyNames = new AssemblyNameExtension[_assemblyFiles.Length];
2381                     for (int i = 0; i < _assemblyFiles.Length; i++)
2382                     {
2383                         ITaskItem assemblyFile = _assemblyFiles[i];
2384                         _assemblyNames[i] = null;
2385 
2386                         if (assemblyFile.ItemSpec != null && File.Exists(assemblyFile.ItemSpec))
2387                         {
2388                             string fusionName = assemblyFile.GetMetadata(ItemMetadataNames.fusionName);
2389                             if (!String.IsNullOrEmpty(fusionName))
2390                             {
2391                                 _assemblyNames[i] = new AssemblyNameExtension(fusionName);
2392                             }
2393                             else
2394                             {
2395                                 // whoever passed us this reference wasn't polite enough to also
2396                                 // give us a metadata with the fusion name.  Trying to load up every
2397                                 // assembly here would take a lot of time, so just stick the assembly
2398                                 // file name (which we assume generally maps to the simple name) into
2399                                 // the list instead. If there's a fusion name that matches, we'll get
2400                                 // that first; otherwise there's a good chance that if the simple name
2401                                 // matches the file name, it's a good match.
2402                                 _assemblyNames[i] = new AssemblyNameExtension(Path.GetFileNameWithoutExtension(assemblyFile.ItemSpec));
2403                             }
2404                         }
2405                     }
2406                 }
2407 
2408                 // Loop through all the references passed in, and see if any of them have an assembly
2409                 // name that exactly matches the requested one.
2410                 for (int i = 0; i < _assemblyNames.Length; i++)
2411                 {
2412                     AssemblyNameExtension candidateAssemblyName = _assemblyNames[i];
2413 
2414                     if (candidateAssemblyName != null)
2415                     {
2416                         if (candidateAssemblyName.CompareTo(requestedAssemblyName) == 0)
2417                         {
2418                             return Assembly.UnsafeLoadFrom(_assemblyFiles[i].ItemSpec);
2419                         }
2420                     }
2421                 }
2422 
2423                 // If none of the referenced assembly names matches exactly, try to find one that
2424                 // has the same base name.  This is here to fix bug where the
2425                 // serialized data inside the .RESX referred to the assembly just by the base name,
2426                 // omitting the version, culture, publickeytoken information.
2427                 for (int i = 0; i < _assemblyNames.Length; i++)
2428                 {
2429                     AssemblyNameExtension candidateAssemblyName = _assemblyNames[i];
2430 
2431                     if (candidateAssemblyName != null)
2432                     {
2433                         if (String.Compare(requestedAssemblyName.Name, candidateAssemblyName.Name, StringComparison.CurrentCultureIgnoreCase) == 0)
2434                         {
2435                             return Assembly.UnsafeLoadFrom(_assemblyFiles[i].ItemSpec);
2436                         }
2437                     }
2438                 }
2439             }
2440 
2441             return null;
2442         }
2443 #endif
2444 
2445 #region Code from ResGen.EXE
2446 
2447         /// <summary>
2448         /// Read all resources from a file and write to a new file in the chosen format
2449         /// </summary>
2450         /// <remarks>Uses the input and output file extensions to determine their format</remarks>
2451         /// <param name="inFile">Input resources file</param>
2452         /// <param name="outFile">Output resources file</param>
2453         /// <returns>True if conversion was successful, otherwise false</returns>
ProcessFile(string inFile, string outFileOrDir)2454         private bool ProcessFile(string inFile, string outFileOrDir)
2455         {
2456             Format inFileFormat = GetFormat(inFile);
2457             if (inFileFormat == Format.Error)
2458             {
2459                 // GetFormat would have logged an error.
2460                 return false;
2461             }
2462             if (inFileFormat != Format.Assembly) // outFileOrDir is a directory when the input file is an assembly
2463             {
2464                 Format outFileFormat = GetFormat(outFileOrDir);
2465                 if (outFileFormat == Format.Assembly)
2466                 {
2467                     _logger.LogErrorFromResources("GenerateResource.CannotWriteAssembly", outFileOrDir);
2468                     return false;
2469                 }
2470                 else if (outFileFormat == Format.Error)
2471                 {
2472                     return false;
2473                 }
2474             }
2475 
2476             if (!_extractResWFiles)
2477             {
2478                 _logger.LogMessageFromResources("GenerateResource.ProcessingFile", inFile, outFileOrDir);
2479             }
2480 
2481             // Reset state
2482             _readers = new List<ReaderInfo>();
2483 
2484             try
2485             {
2486                 ReadResources(inFile, _useSourcePath, outFileOrDir);
2487             }
2488             catch (ArgumentException ae)
2489             {
2490                 if (ae.InnerException is XmlException)
2491                 {
2492                     XmlException xe = (XmlException) ae.InnerException;
2493                     _logger.LogErrorWithCodeFromResources(null, FileUtilities.GetFullPathNoThrow(inFile), xe.LineNumber,
2494                         xe.LinePosition, 0, 0, "General.InvalidResxFile", xe.Message);
2495                 }
2496                 else
2497                 {
2498                     _logger.LogErrorWithCodeFromResources(null, FileUtilities.GetFullPathNoThrow(inFile), 0, 0, 0, 0,
2499                         "General.InvalidResxFile", ae.Message);
2500                 }
2501                 return false;
2502             }
2503             catch (TextFileException tfe)
2504             {
2505                 // Used to pass back error context from ReadTextResources to here.
2506                 _logger.LogErrorWithCodeFromResources(null, tfe.FileName, tfe.LineNumber, tfe.LinePosition, 1, 1,
2507                     "GenerateResource.MessageTunnel", tfe.Message);
2508                 return false;
2509             }
2510             catch (XmlException xe)
2511             {
2512                 _logger.LogErrorWithCodeFromResources(null, FileUtilities.GetFullPathNoThrow(inFile), xe.LineNumber,
2513                     xe.LinePosition, 0, 0, "General.InvalidResxFile", xe.Message);
2514                 return false;
2515             }
2516             catch (Exception e) when (
2517 #if FEATURE_RESGENCACHE
2518                                       e is SerializationException ||
2519 #endif
2520                                       e is TargetInvocationException)
2521             {
2522                 // DDB #9819
2523                 // SerializationException and TargetInvocationException can occur when trying to deserialize a type from a resource format (typically with other exceptions inside)
2524                 // This is a bug in the type being serialized, so the best we can do is dump diagnostic information and move on to the next input resource file.
2525                 _logger.LogErrorWithCodeFromResources(null, FileUtilities.GetFullPathNoThrow(inFile), 0, 0, 0, 0,
2526                     "General.InvalidResxFile", e.Message);
2527 
2528                 // Log the stack, so the problem with the type in the .resx is diagnosable by the customer
2529                 _logger.LogErrorFromException(e, /* stack */ true, /* inner exceptions */ true,
2530                     FileUtilities.GetFullPathNoThrow(inFile));
2531                 return false;
2532             }
2533             catch (Exception e) when (ExceptionHandling.IsIoRelatedException(e))
2534             {
2535                 // Regular IO error
2536                 _logger.LogErrorWithCodeFromResources(null, FileUtilities.GetFullPathNoThrow(inFile), 0, 0, 0, 0,
2537                     "General.InvalidResxFile", e.Message);
2538                 return false;
2539             }
2540 
2541             string currentOutputFile = null;
2542             string currentOutputDirectory = null;
2543             string currentOutputSourceCodeFile = null;
2544             bool currentOutputDirectoryAlreadyExisted = true;
2545 
2546             try
2547             {
2548                 if (GetFormat(inFile) == Format.Assembly)
2549                 {
2550 #if FEATURE_RESGENCACHE
2551                     // Prepare cache data
2552                     ResGenDependencies.PortableLibraryFile library = new ResGenDependencies.PortableLibraryFile(inFile);
2553 #endif
2554                     List<string> resWFilesForThisAssembly = new List<string>();
2555 
2556                     foreach (ReaderInfo reader in _readers)
2557                     {
2558                         String currentOutputFileNoPath = reader.outputFileName + ".resw";
2559                         currentOutputFile = null;
2560                         currentOutputDirectoryAlreadyExisted = true;
2561                         string priDirectory = Path.Combine(outFileOrDir ?? String.Empty,
2562                             reader.assemblySimpleName);
2563                         currentOutputDirectory = Path.Combine(priDirectory,
2564                             reader.cultureName ?? String.Empty);
2565 
2566                         if (!Directory.Exists(currentOutputDirectory))
2567                         {
2568                             currentOutputDirectoryAlreadyExisted = false;
2569                             Directory.CreateDirectory(currentOutputDirectory);
2570                         }
2571                         currentOutputFile = Path.Combine(currentOutputDirectory, currentOutputFileNoPath);
2572 
2573                         // For very long resource names, this directory structure may be too deep.
2574                         // If so, assume that the name is so long it will already uniquely distinguish itself.
2575                         // However for shorter names we'd still prefer to use the assembly simple name
2576                         // in the path to avoid conflicts.
2577                         currentOutputFile = EnsurePathIsShortEnough(currentOutputFile, currentOutputFileNoPath,
2578                             outFileOrDir, reader.cultureName);
2579 
2580                         if (currentOutputFile == null)
2581                         {
2582                             // We couldn't generate a file name short enough to handle this.  Fail but continue.
2583                             continue;
2584                         }
2585 
2586                         // Always write the output file here - other logic prevents us from processing this
2587                         // file for incremental builds if everything was up to date.
2588                         WriteResources(reader, currentOutputFile);
2589 
2590                         string escapedOutputFile = EscapingUtilities.Escape(currentOutputFile);
2591                         ITaskItem newOutputFile = new TaskItem(escapedOutputFile);
2592                         resWFilesForThisAssembly.Add(escapedOutputFile);
2593                         newOutputFile.SetMetadata("ResourceIndexName", reader.assemblySimpleName);
2594 #if FEATURE_RESGENCACHE
2595                         library.AssemblySimpleName = reader.assemblySimpleName;
2596 #endif
2597                         if (reader.fromNeutralResources)
2598                         {
2599                             newOutputFile.SetMetadata("NeutralResourceLanguage", reader.cultureName);
2600 #if FEATURE_RESGENCACHE
2601                             library.NeutralResourceLanguage = reader.cultureName;
2602 #endif
2603                         }
2604                         ExtractedResWFiles.Add(newOutputFile);
2605                     }
2606 
2607 #if FEATURE_RESGENCACHE
2608                     library.OutputFiles = resWFilesForThisAssembly.ToArray();
2609                     _portableLibraryCacheInfo.Add(library);
2610 #endif
2611                 }
2612                 else
2613                 {
2614                     currentOutputFile = outFileOrDir;
2615                     ErrorUtilities.VerifyThrow(_readers.Count == 1,
2616                         "We have no readers, or we have multiple readers & are ignoring subsequent ones.  Num readers: {0}",
2617                         _readers.Count);
2618                     WriteResources(_readers[0], outFileOrDir);
2619                 }
2620 
2621                 if (_stronglyTypedLanguage != null)
2622                 {
2623                     try
2624                     {
2625                         ErrorUtilities.VerifyThrow(_readers.Count == 1,
2626                             "We have no readers, or we have multiple readers & are ignoring subsequent ones.  Num readers: {0}",
2627                             _readers.Count);
2628                         CreateStronglyTypedResources(_readers[0], outFileOrDir, inFile, out currentOutputSourceCodeFile);
2629                     }
2630                     catch (Exception e) when (ExceptionHandling.IsIoRelatedException(e))
2631                     {
2632                         // IO Error
2633                         _logger.LogErrorWithCodeFromResources("GenerateResource.CannotWriteSTRFile",
2634                             _stronglyTypedFilename, e.Message);
2635 
2636                         if (File.Exists(outFileOrDir)
2637                             && GetFormat(inFile) != Format.Assembly
2638                             // outFileOrDir is a directory when the input file is an assembly
2639                             && GetFormat(outFileOrDir) != Format.Assembly)
2640                             // Never delete an assembly since we don't ever actually write to assemblies.
2641                         {
2642                             RemoveCorruptedFile(outFileOrDir);
2643                         }
2644                         if (currentOutputSourceCodeFile != null)
2645                         {
2646                             RemoveCorruptedFile(currentOutputSourceCodeFile);
2647                         }
2648                         return false;
2649                     }
2650                 }
2651             }
2652             catch (IOException io)
2653             {
2654                 if (currentOutputFile != null)
2655                 {
2656                     _logger.LogErrorWithCodeFromResources("GenerateResource.CannotWriteOutput",
2657                         FileUtilities.GetFullPathNoThrow(currentOutputFile), io.Message);
2658                     if (File.Exists(currentOutputFile))
2659                     {
2660                         if (GetFormat(currentOutputFile) != Format.Assembly)
2661                             // Never delete an assembly since we don't ever actually write to assemblies.
2662                         {
2663                             RemoveCorruptedFile(currentOutputFile);
2664                         }
2665                     }
2666                 }
2667 
2668                 if (currentOutputDirectory != null &&
2669                     currentOutputDirectoryAlreadyExisted == false)
2670                 {
2671                     // Do not annoy the user by removing an empty directory we did not create.
2672                     try
2673                     {
2674                         Directory.Delete(currentOutputDirectory); // Remove output directory if empty
2675                     }
2676                     catch (Exception e)
2677                     {
2678                         // Fail silently (we are not even checking if the call to File.Delete succeeded)
2679                         if (ExceptionHandling.IsCriticalException(e))
2680                         {
2681                             throw;
2682                         }
2683                     }
2684                 }
2685                 return false;
2686             }
2687             catch (Exception e) when (
2688 #if FEATURE_RESGENCACHE
2689                                       e is SerializationException ||
2690 #endif
2691                                       e is TargetInvocationException)
2692             {
2693                 // DDB #9819
2694                 // SerializationException and TargetInvocationException can occur when trying to serialize a type into a resource format (typically with other exceptions inside)
2695                 // This is a bug in the type being serialized, so the best we can do is dump diagnostic information and move on to the next input resource file.
2696                 _logger.LogErrorWithCodeFromResources("GenerateResource.CannotWriteOutput",
2697                     FileUtilities.GetFullPathNoThrow(inFile), e.Message); // Input file is more useful to log
2698 
2699                 // Log the stack, so the problem with the type in the .resx is diagnosable by the customer
2700                 _logger.LogErrorFromException(e, /* stack */ true, /* inner exceptions */ true,
2701                     FileUtilities.GetFullPathNoThrow(inFile));
2702                 return false;
2703             }
2704             catch (Exception e) when (ExceptionHandling.IsIoRelatedException(e))
2705             {
2706                 // Regular IO error
2707                 _logger.LogErrorWithCodeFromResources("GenerateResource.CannotWriteOutput",
2708                     FileUtilities.GetFullPathNoThrow(currentOutputFile), e.Message);
2709                 return false;
2710             }
2711 
2712             return true;
2713         }
2714 
2715         /// <summary>
2716         /// For very long resource names, the directory structure we generate may be too deep.
2717         /// If so, assume that the name is so long it will already uniquely distinguish itself.
2718         /// However for shorter names we'd still prefer to use the assembly simple name
2719         /// in the path to avoid conflicts.
2720         /// </summary>
2721         /// <param name="currentOutputFile">The current path name</param>
2722         /// <param name="currentOutputFileNoPath">The current file name without a path.</param>
2723         /// <param name="outputDirectory">Output directory path</param>
2724         /// <param name="cultureName">culture for this resource</param>
2725         /// <returns>The current path or a shorter one.</returns>
EnsurePathIsShortEnough(string currentOutputFile, string currentOutputFileNoPath, string outputDirectory, string cultureName)2726         private string EnsurePathIsShortEnough(string currentOutputFile, string currentOutputFileNoPath, string outputDirectory, string cultureName)
2727         {
2728             // File names >= 260 characters won't work.  File names of exactly 259 characters are odd though.
2729             // They seem to work with Notepad and Windows Explorer, but not with MakePri.  They don't work
2730             // reliably with cmd's dir command either (depending on whether you use absolute or relative paths
2731             // and whether there are quotes around the name).
2732             const int EffectiveMaxPath = 258;   // Everything <= EffectiveMaxPath should work well.
2733             bool success = false;
2734             try
2735             {
2736                 currentOutputFile = Path.GetFullPath(currentOutputFile);
2737                 success = currentOutputFile.Length <= EffectiveMaxPath;
2738             }
2739             catch (PathTooLongException)
2740             {
2741                 success = false;
2742             }
2743 
2744             if (!success)
2745             {
2746                 string shorterPath = Path.Combine(outputDirectory ?? String.Empty, cultureName ?? String.Empty);
2747                 if (!Directory.Exists(shorterPath))
2748                 {
2749                     Directory.CreateDirectory(shorterPath);
2750                 }
2751                 currentOutputFile = Path.Combine(shorterPath, currentOutputFileNoPath);
2752 
2753                 // Try again
2754                 try
2755                 {
2756                     currentOutputFile = Path.GetFullPath(currentOutputFile);
2757                     success = currentOutputFile.Length <= EffectiveMaxPath;
2758                 }
2759                 catch (PathTooLongException)
2760                 {
2761                     success = false;
2762                 }
2763 
2764                 // Can't do anything more without violating correctness.
2765                 if (!success)
2766                 {
2767                     _logger.LogErrorWithCodeFromResources("GenerateResource.PathTooLong", currentOutputFile);
2768                     currentOutputFile = null;
2769                     // We've logged an error message.  This MSBuild task will fail, but can continue processing other input (to find other errors).
2770                 }
2771             }
2772             return currentOutputFile;
2773         }
2774 
2775         /// <summary>
2776         /// Remove a corrupted file, with error handling and a warning if we fail.
2777         /// </summary>
2778         /// <param name="filename">Full path to file to delete</param>
RemoveCorruptedFile(string filename)2779         private void RemoveCorruptedFile(string filename)
2780         {
2781             _logger.LogWarningWithCodeFromResources("GenerateResource.CorruptOutput", FileUtilities.GetFullPathNoThrow(filename));
2782             try
2783             {
2784                 File.Delete(filename);
2785             }
2786             catch (Exception deleteException)
2787             {
2788                 _logger.LogWarningWithCodeFromResources("GenerateResource.DeleteCorruptOutputFailed", FileUtilities.GetFullPathNoThrow(filename), deleteException.Message);
2789 
2790                 if (ExceptionHandling.NotExpectedException(deleteException))
2791                 {
2792                     throw;
2793                 }
2794             }
2795         }
2796 
2797         /// <summary>
2798         /// Figure out the format of an input resources file from the extension
2799         /// </summary>
2800         /// <param name="filename">Input resources file</param>
2801         /// <returns>Resources format</returns>
GetFormat(string filename)2802         private Format GetFormat(string filename)
2803         {
2804             string extension = String.Empty;
2805 
2806             try
2807             {
2808                 extension = Path.GetExtension(filename);
2809             }
2810             catch (ArgumentException ex)
2811             {
2812                 _logger.LogErrorWithCodeFromResources("GenerateResource.InvalidFilename", filename, ex.Message);
2813                 return Format.Error;
2814             }
2815 
2816             if (String.Compare(extension, ".txt", StringComparison.OrdinalIgnoreCase) == 0 ||
2817                 String.Compare(extension, ".restext", StringComparison.OrdinalIgnoreCase) == 0)
2818             {
2819                 return Format.Text;
2820             }
2821             else if (String.Compare(extension, ".resx", StringComparison.OrdinalIgnoreCase) == 0 ||
2822                      String.Compare(extension, ".resw", StringComparison.OrdinalIgnoreCase) == 0)
2823             {
2824                 return Format.XML;
2825             }
2826             else if (String.Compare(extension, ".resources.dll", StringComparison.OrdinalIgnoreCase) == 0 ||
2827                      String.Compare(extension, ".dll", StringComparison.OrdinalIgnoreCase) == 0 ||
2828                      String.Compare(extension, ".exe", StringComparison.OrdinalIgnoreCase) == 0)
2829             {
2830                 return Format.Assembly;
2831             }
2832             else if (String.Compare(extension, ".resources", StringComparison.OrdinalIgnoreCase) == 0)
2833             {
2834                 return Format.Binary;
2835             }
2836             else
2837             {
2838                 _logger.LogErrorWithCodeFromResources("GenerateResource.UnknownFileExtension", Path.GetExtension(filename), filename);
2839                 return Format.Error;
2840             }
2841         }
2842 
2843         /// <summary>
2844         /// Text files are just name/value pairs.  ResText is the same format
2845         /// with a unique extension to work around some ambiguities with MSBuild
2846         /// ResX is our existing XML format from V1.
2847         /// </summary>
2848         private enum Format
2849         {
2850             Text, // .txt or .restext
2851             XML, // .resx
2852             Binary, // .resources
2853             Assembly, // .dll, .exe or .resources.dll
2854             Error, // anything else
2855         }
2856 
2857         /// <summary>
2858         /// Reads the resources out of the specified file and populates the
2859         /// resources hashtable.
2860         /// </summary>
2861         /// <param name="filename">Filename to load</param>
2862         /// <param name="shouldUseSourcePath">Whether to resolve paths in the
2863         /// resources file relative to the resources file location</param>
2864         /// <param name="outFileOrDir"> Output file or directory. </param>
ReadResources(String filename, bool shouldUseSourcePath, String outFileOrDir)2865         private void ReadResources(String filename, bool shouldUseSourcePath, String outFileOrDir)
2866         {
2867             Format format = GetFormat(filename);
2868 
2869             if (format == Format.Assembly) // Multiple input .resources files within one assembly
2870             {
2871 #if FEATURE_ASSEMBLY_LOADFROM
2872                 ReadAssemblyResources(filename, outFileOrDir);
2873 #else
2874                 _logger.LogError("Reading resources from Assembly not supported on .NET Core MSBuild");
2875 #endif
2876             }
2877             else
2878             {
2879                 ReaderInfo reader = new ReaderInfo();
2880                 _readers.Add(reader);
2881                 switch (format)
2882                 {
2883                     case Format.Text:
2884                         ReadTextResources(reader, filename);
2885                         break;
2886 
2887                     case Format.XML:
2888 #if FEATURE_RESX_RESOURCE_READER
2889                         ResXResourceReader resXReader = null;
2890                         if (_typeResolver != null)
2891                         {
2892                             resXReader = new ResXResourceReader(filename, _typeResolver);
2893                         }
2894                         else
2895                         {
2896                             resXReader = new ResXResourceReader(filename);
2897                         }
2898 
2899                         if (shouldUseSourcePath)
2900                         {
2901                             String fullPath = Path.GetFullPath(filename);
2902                             resXReader.BasePath = Path.GetDirectoryName(fullPath);
2903                         }
2904                         // ReadResources closes the reader for us
2905                         ReadResources(reader, resXReader, filename);
2906                         break;
2907 #else
2908 
2909                         using (var xmlReader = new XmlTextReader(filename))
2910                         {
2911                             xmlReader.WhitespaceHandling = WhitespaceHandling.None;
2912                             XDocument doc = XDocument.Load(xmlReader, LoadOptions.PreserveWhitespace);
2913                             foreach (XElement dataElem in doc.Element("root").Elements("data"))
2914                             {
2915                                 string name = dataElem.Attribute("name").Value;
2916                                 string value = dataElem.Element("value").Value;
2917                                 AddResource(reader, name, value, filename);
2918                             }
2919                         }
2920                         break;
2921 #endif
2922                     case Format.Binary:
2923 #if FEATURE_RESX_RESOURCE_READER
2924                         ReadResources(reader, new ResourceReader(filename), filename); // closes reader for us
2925 #else
2926                         _logger.LogError("ResGen.exe not supported on .NET Core MSBuild");
2927 #endif
2928                         break;
2929 
2930                     default:
2931                         // We should never get here, we've already checked the format
2932                         Debug.Fail("Unknown format " + format.ToString());
2933                         return;
2934                 }
2935                 _logger.LogMessageFromResources(MessageImportance.Low, "GenerateResource.ReadResourceMessage", reader.resources.Count, filename);
2936             }
2937         }
2938 
2939 #if FEATURE_ASSEMBLY_LOADFROM
2940         /// <summary>
2941         /// Reads resources from an assembly.
2942         /// </summary>
2943         /// <param name="name"></param>
2944         /// <param name="outFileOrDir"> Output file or directory. </param>
2945         /// <remarks> This should not run for Framework assemblies. </remarks>
ReadAssemblyResources(String name, String outFileOrDir)2946         internal void ReadAssemblyResources(String name, String outFileOrDir)
2947         {
2948             // If something else in the solution failed to build...
2949             if (!File.Exists(name))
2950             {
2951                 _logger.LogErrorWithCodeFromResources("GenerateResource.MissingFile", name);
2952                 return;
2953             }
2954 
2955             Assembly a = null;
2956             bool mainAssembly = false;
2957             bool failedLoadingCultureInfo = false;
2958             NeutralResourcesLanguageAttribute neutralResourcesLanguageAttribute = null;
2959             AssemblyName assemblyName = null;
2960 
2961             try
2962             {
2963                 a = Assembly.UnsafeLoadFrom(name);
2964                 assemblyName = a.GetName();
2965 
2966                 if (_extractResWFiles)
2967                 {
2968                     var targetFrameworkAttribute = a.GetCustomAttribute<TargetFrameworkAttribute>();
2969                     if (
2970                             targetFrameworkAttribute != null &&
2971                             (
2972                                 targetFrameworkAttribute.FrameworkName.StartsWith("Silverlight,", StringComparison.OrdinalIgnoreCase) ||
2973                                 targetFrameworkAttribute.FrameworkName.StartsWith("WindowsPhone,", StringComparison.OrdinalIgnoreCase)
2974                             )
2975                         )
2976                     {
2977                         // Skip Silverlight assemblies.
2978                         _logger.LogMessageFromResources("GenerateResource.SkippingExtractingFromNonSupportedFramework", name, targetFrameworkAttribute.FrameworkName);
2979                         return;
2980                     }
2981 
2982                     _logger.LogMessageFromResources("GenerateResource.ExtractingResWFiles", name, outFileOrDir);
2983                 }
2984 
2985                 CultureInfo ci = null;
2986                 try
2987                 {
2988                     ci = assemblyName.CultureInfo;
2989                 }
2990                 catch (ArgumentException e)
2991                 {
2992                     _logger.LogWarningWithCodeFromResources(null, name, 0, 0, 0, 0, "GenerateResource.CreatingCultureInfoFailed", e.GetType().Name, e.Message, assemblyName.ToString());
2993                     failedLoadingCultureInfo = true;
2994                 }
2995 
2996                 if (!failedLoadingCultureInfo)
2997                 {
2998                     mainAssembly = ci.Equals(CultureInfo.InvariantCulture);
2999                     neutralResourcesLanguageAttribute = CheckAssemblyCultureInfo(name, assemblyName, ci, a, mainAssembly);
3000                 }  // if (!failedLoadingCultureInfo)
3001             }
3002             catch (BadImageFormatException)
3003             {
3004                 // If we're extracting ResW files, this task is being run on all DLL's referred to by the project.
3005                 // That may potentially include C++ libraries & immersive (non-portable) class libraries, which don't have resources.
3006                 // We can't easily filter those.  We can simply skip them.
3007                 return;
3008             }
3009             catch (Exception e)
3010             {
3011                 if (ExceptionHandling.IsCriticalException(e))
3012                     throw;
3013                 _logger.LogErrorWithCodeFromResources("GenerateResource.CannotLoadAssemblyLoadFromFailed", name, e);
3014             }
3015 
3016             if (a != null)
3017             {
3018                 String[] resources = a.GetManifestResourceNames();
3019                 CultureInfo satCulture = null;
3020                 String expectedExt = null;
3021                 if (!failedLoadingCultureInfo)
3022                 {
3023                     satCulture = assemblyName.CultureInfo;
3024                     if (!satCulture.Equals(CultureInfo.InvariantCulture))
3025                         expectedExt = '.' + satCulture.Name + ".resources";
3026                 }
3027 
3028                 foreach (String resName in resources)
3029                 {
3030                     if (!resName.EndsWith(".resources", StringComparison.OrdinalIgnoreCase)) // Skip non-.resources assembly blobs
3031                         continue;
3032 
3033                     if (mainAssembly)
3034                     {
3035                         if (CultureInfo.InvariantCulture.CompareInfo.IsSuffix(resName, ".en-US.resources", CompareOptions.IgnoreCase))
3036                         {
3037                             _logger.LogErrorFromResources("GenerateResource.ImproperlyBuiltMainAssembly", resName, name);
3038                             continue;
3039                         }
3040 
3041                         if (neutralResourcesLanguageAttribute == null)
3042                         {
3043                             _logger.LogWarningWithCodeFromResources(null, name, 0, 0, 0, 0, "GenerateResource.MainAssemblyMissingNeutralResourcesLanguage", name);
3044                             break;
3045                         }
3046                     }
3047                     else if (!failedLoadingCultureInfo && !CultureInfo.InvariantCulture.CompareInfo.IsSuffix(resName, expectedExt, CompareOptions.IgnoreCase))
3048                     {
3049                         _logger.LogErrorFromResources("GenerateResource.ImproperlyBuiltSatelliteAssembly", resName, expectedExt, name);
3050                         continue;
3051                     }
3052 
3053                     try
3054                     {
3055                         Stream s = a.GetManifestResourceStream(resName);
3056                         using (IResourceReader rr = new ResourceReader(s))
3057                         {
3058                             ReaderInfo reader = new ReaderInfo();
3059                             if (mainAssembly)
3060                             {
3061                                 reader.fromNeutralResources = true;
3062                                 reader.assemblySimpleName = assemblyName.Name;
3063                             }
3064                             else
3065                             {
3066                                 Debug.Assert(assemblyName.Name.EndsWith(".resources", StringComparison.OrdinalIgnoreCase));
3067                                 reader.assemblySimpleName = assemblyName.Name.Remove(assemblyName.Name.Length - 10);  // Remove .resources from satellite assembly name
3068                             }
3069                             reader.outputFileName = resName.Remove(resName.Length - 10); // Remove the .resources extension
3070                             if (satCulture != null && !String.IsNullOrEmpty(satCulture.Name))
3071                             {
3072                                 reader.cultureName = satCulture.Name;
3073                             }
3074                             else if (neutralResourcesLanguageAttribute != null && !String.IsNullOrEmpty(neutralResourcesLanguageAttribute.CultureName))
3075                             {
3076                                 reader.cultureName = neutralResourcesLanguageAttribute.CultureName;
3077                             }
3078 
3079                             if (reader.cultureName != null)
3080                             {
3081                                 // Remove the culture from the filename
3082                                 if (reader.outputFileName.EndsWith("." + reader.cultureName, StringComparison.OrdinalIgnoreCase))
3083                                     reader.outputFileName = reader.outputFileName.Remove(reader.outputFileName.Length - (reader.cultureName.Length + 1));
3084                             }
3085                             _readers.Add(reader);
3086 
3087                             foreach (DictionaryEntry pair in rr)
3088                             {
3089                                 AddResource(reader, (string)pair.Key, pair.Value, resName);
3090                             }
3091                         }
3092                     }
3093                     catch (FileNotFoundException)
3094                     {
3095                         _logger.LogErrorWithCodeFromResources(null, name, 0, 0, 0, 0, "GenerateResource.NoResourcesFileInAssembly", resName);
3096                     }
3097                 }
3098             }
3099 
3100             var satelliteAssemblies = _satelliteInFiles.Where(ti => ti.GetMetadata("OriginalItemSpec").Equals(name, StringComparison.OrdinalIgnoreCase));
3101 
3102             foreach (var satelliteAssembly in satelliteAssemblies)
3103             {
3104                 ReadAssemblyResources(satelliteAssembly.ItemSpec, outFileOrDir);
3105             }
3106         }
3107 
3108         /// <summary>
3109         /// Checks the consistency of the CultureInfo and NeutralResourcesLanguageAttribute settings.
3110         /// </summary>
3111         /// <param name="name">Assembly's file name</param>
3112         /// <param name="assemblyName">AssemblyName of this assembly</param>
3113         /// <param name="culture">Assembly's CultureInfo</param>
CheckAssemblyCultureInfo(String name, AssemblyName assemblyName, CultureInfo culture, Assembly a, bool mainAssembly)3114         private NeutralResourcesLanguageAttribute CheckAssemblyCultureInfo(String name, AssemblyName assemblyName, CultureInfo culture, Assembly a, bool mainAssembly)
3115         {
3116             NeutralResourcesLanguageAttribute neutralResourcesLanguageAttribute = null;
3117             if (mainAssembly)
3118             {
3119                 Object[] attrs = a.GetCustomAttributes(typeof(NeutralResourcesLanguageAttribute), false);
3120                 if (attrs.Length != 0)
3121                 {
3122                     neutralResourcesLanguageAttribute = (NeutralResourcesLanguageAttribute)attrs[0];
3123                     bool fallbackToSatellite = neutralResourcesLanguageAttribute.Location == UltimateResourceFallbackLocation.Satellite;
3124                     if (!fallbackToSatellite && neutralResourcesLanguageAttribute.Location != UltimateResourceFallbackLocation.MainAssembly)
3125                         _logger.LogWarningWithCodeFromResources(null, name, 0, 0, 0, 0, "GenerateResource.UnrecognizedUltimateResourceFallbackLocation", neutralResourcesLanguageAttribute.Location, name);
3126                     // This MSBuild task needs to not report an error for main assemblies that don't have managed resources.
3127                 }
3128             }
3129             else
3130             {  // Satellite assembly, or a mal-formed main assembly
3131                 // Additional error checking from ResView.
3132                 if (!assemblyName.Name.EndsWith(".resources", StringComparison.OrdinalIgnoreCase))
3133                 {
3134                     _logger.LogWarningWithCodeFromResources(null, name, 0, 0, 0, 0, "GenerateResource.SatelliteOrMalformedAssembly", name, culture.Name, assemblyName.Name);
3135                     return null;
3136                 }
3137                 Type[] types = a.GetTypes();
3138                 if (types.Length > 0)
3139                 {
3140                     _logger.LogWarningWithCodeFromResources("GenerateResource.SatelliteAssemblyContainsCode", name);
3141                 }
3142 
3143                 if (!ContainsProperlyNamedResourcesFiles(a, false))
3144                     _logger.LogWarningWithCodeFromResources("GenerateResource.SatelliteAssemblyContainsNoResourcesFile", assemblyName.CultureInfo.Name);
3145             }
3146             return neutralResourcesLanguageAttribute;
3147         }
3148 
ContainsProperlyNamedResourcesFiles(Assembly a, bool mainAssembly)3149         private static bool ContainsProperlyNamedResourcesFiles(Assembly a, bool mainAssembly)
3150         {
3151             String postfix = mainAssembly ? ".resources" : a.GetName().CultureInfo.Name + ".resources";
3152             foreach (String manifestResourceName in a.GetManifestResourceNames())
3153                 if (manifestResourceName.EndsWith(postfix, StringComparison.OrdinalIgnoreCase))
3154                     return true;
3155             return false;
3156         }
3157 #endif
3158 
3159         /// <summary>
3160         /// Write resources from the resources ArrayList to the specified output file
3161         /// </summary>
3162         /// <param name="filename">Output resources file</param>
WriteResources(ReaderInfo reader, String filename)3163         private void WriteResources(ReaderInfo reader, String filename)
3164         {
3165             Format format = GetFormat(filename);
3166             switch (format)
3167             {
3168                 case Format.Text:
3169                     WriteTextResources(reader, filename);
3170                     break;
3171 
3172                 case Format.XML:
3173 #if FEATURE_RESX_RESOURCE_READER
3174                     WriteResources(reader, new ResXResourceWriter(filename)); // closes writer for us
3175 #else
3176                     _logger.LogError(format.ToString() + " not supported on .NET Core MSBuild");
3177 #endif
3178                     break;
3179 
3180 
3181                 case Format.Assembly:
3182                     _logger.LogErrorFromResources("GenerateResource.CannotWriteAssembly", filename);
3183                     break;
3184 
3185                 case Format.Binary:
3186 
3187                     WriteResources(reader, new ResourceWriter(File.OpenWrite(filename))); // closes writer for us
3188                     break;
3189 
3190 
3191                 default:
3192                     // We should never get here, we've already checked the format
3193                     Debug.Fail("Unknown format " + format.ToString());
3194                     break;
3195             }
3196         }
3197 
3198 
3199         /// <summary>
3200         /// Create a strongly typed resource class
3201         /// </summary>
3202         /// <param name="outFile">Output resource filename, for defaulting the class filename</param>
3203         /// <param name="inputFileName">Input resource filename, for error messages</param>
CreateStronglyTypedResources(ReaderInfo reader, String outFile, String inputFileName, out String sourceFile)3204         private void CreateStronglyTypedResources(ReaderInfo reader, String outFile, String inputFileName, out String sourceFile)
3205         {
3206 #if FEATURE_CODEDOM
3207             CodeDomProvider provider = null;
3208 
3209             if (!TryCreateCodeDomProvider(_logger, _stronglyTypedLanguage, out provider))
3210             {
3211                 sourceFile = null;
3212                 return;
3213             }
3214 
3215             // Default the class name if we need to
3216             if (_stronglyTypedClassName == null)
3217             {
3218                 _stronglyTypedClassName = Path.GetFileNameWithoutExtension(outFile);
3219             }
3220 
3221             // Default the filename if we need to
3222             if (_stronglyTypedFilename == null)
3223             {
3224                 _stronglyTypedFilename = GenerateDefaultStronglyTypedFilename(provider, outFile);
3225             }
3226             sourceFile = this.StronglyTypedFilename;
3227 
3228             _logger.LogMessageFromResources("GenerateResource.CreatingSTR", _stronglyTypedFilename);
3229 
3230             // Generate the STR class
3231             String[] errors;
3232             bool generateInternalClass = !_stronglyTypedClassIsPublic;
3233             //StronglyTypedResourcesNamespace can be null and this is ok.
3234             // If it is null then the default namespace (=stronglyTypedNamespace) is used.
3235             CodeCompileUnit ccu = StronglyTypedResourceBuilder.Create(
3236                     reader.resourcesHashTable,
3237                     _stronglyTypedClassName,
3238                     _stronglyTypedNamespace,
3239                     _stronglyTypedResourcesNamespace,
3240                     provider,
3241                     generateInternalClass,
3242                     out errors
3243                     );
3244 
3245             CodeGeneratorOptions codeGenOptions = new CodeGeneratorOptions();
3246             using (TextWriter output = new StreamWriter(_stronglyTypedFilename))
3247             {
3248                 provider.GenerateCodeFromCompileUnit(ccu, output, codeGenOptions);
3249             }
3250 
3251             if (errors.Length > 0)
3252             {
3253                 _logger.LogErrorWithCodeFromResources("GenerateResource.ErrorFromCodeDom", inputFileName);
3254                 foreach (String error in errors)
3255                 {
3256                     _logger.LogErrorWithCodeFromResources("GenerateResource.CodeDomError", error);
3257                 }
3258             }
3259             else
3260             {
3261                 // No errors, and no exceptions - we presumably did create the STR class file
3262                 // and it should get added to FilesWritten. So set a flag to indicate this.
3263                 _stronglyTypedResourceSuccessfullyCreated = true;
3264             }
3265 #else
3266             sourceFile = null;
3267             _logger.LogError("Generating strongly typed resource files not currently supported on .NET Core MSBuild");
3268 #endif
3269         }
3270 
3271 #if FEATURE_CODEDOM
3272         /// <summary>
3273         /// If no strongly typed resource class filename was specified, we come up with a default based on the
3274         /// input file name and the default language extension.
3275         /// </summary>
3276         /// <comments>
3277         /// Broken out here so it can be called from GenerateResource class.
3278         /// </comments>
3279         /// <param name="provider">A CodeDomProvider for the language</param>
3280         /// <param name="outputResourcesFile">Name of the output resources file</param>
3281         /// <returns>Filename for strongly typed resource class</returns>
GenerateDefaultStronglyTypedFilename(CodeDomProvider provider, string outputResourcesFile)3282         public static string GenerateDefaultStronglyTypedFilename(CodeDomProvider provider, string outputResourcesFile)
3283         {
3284             return Path.ChangeExtension(outputResourcesFile, provider.FileExtension);
3285         }
3286 
3287         /// <summary>
3288         /// Tries to create a CodeDom provider for the specified strongly typed language.  If successful, returns true,
3289         /// otherwise returns false.
3290         /// </summary>
3291         /// <comments>
3292         /// Broken out here so it can be called from GenerateResource class.
3293         /// Not a true "TryXXX" method, as it still throws if it encounters an exception it doesn't expect.
3294         /// </comments>
3295         /// <param name="stronglyTypedLanguage">The language to create a provider for.</param>
3296         /// <param name="provider">The provider in question, if one is successfully created.</param>
3297         /// <returns>True if the provider was successfully created, false otherwise.</returns>
TryCreateCodeDomProvider(TaskLoggingHelper logger, string stronglyTypedLanguage, out CodeDomProvider provider)3298         public static bool TryCreateCodeDomProvider(TaskLoggingHelper logger, string stronglyTypedLanguage, out CodeDomProvider provider)
3299         {
3300             provider = null;
3301 
3302             try
3303             {
3304                 provider = CodeDomProvider.CreateProvider(stronglyTypedLanguage);
3305             }
3306 #if FEATURE_SYSTEM_CONFIGURATION
3307             catch (ConfigurationException e)
3308             {
3309                 logger.LogErrorWithCodeFromResources("GenerateResource.STRCodeDomProviderFailed", stronglyTypedLanguage, e.Message);
3310                 return false;
3311             }
3312 #endif
3313             catch (SecurityException e)
3314             {
3315                 logger.LogErrorWithCodeFromResources("GenerateResource.STRCodeDomProviderFailed", stronglyTypedLanguage, e.Message);
3316                 return false;
3317             }
3318 
3319             return provider != null;
3320         }
3321 #endif
3322 
3323 #if FEATURE_RESX_RESOURCE_READER
3324         /// <summary>
3325         /// Read resources from an XML or binary format file
3326         /// </summary>
3327         /// <param name="reader">Appropriate IResourceReader</param>
3328         /// <param name="fileName">Filename, for error messages</param>
ReadResources(ReaderInfo readerInfo, IResourceReader reader, String fileName)3329         private void ReadResources(ReaderInfo readerInfo, IResourceReader reader, String fileName)
3330         {
3331             using (reader)
3332             {
3333                 IDictionaryEnumerator resEnum = reader.GetEnumerator();
3334                 while (resEnum.MoveNext())
3335                 {
3336                     string name = (string)resEnum.Key;
3337                     object value = resEnum.Value;
3338                     AddResource(readerInfo, name, value, fileName);
3339                 }
3340             }
3341         }
3342 #endif
3343 
3344         /// <summary>
3345         /// Read resources from a text format file
3346         /// </summary>
3347         /// <param name="fileName">Input resources filename</param>
ReadTextResources(ReaderInfo reader, String fileName)3348         private void ReadTextResources(ReaderInfo reader, String fileName)
3349         {
3350             // Check for byte order marks in the beginning of the input file, but
3351             // default to UTF-8.
3352             using (LineNumberStreamReader sr = new LineNumberStreamReader(fileName, new UTF8Encoding(true), true))
3353             {
3354                 StringBuilder name = new StringBuilder(255);
3355                 StringBuilder value = new StringBuilder(2048);
3356 
3357                 int ch = sr.Read();
3358                 while (ch != -1)
3359                 {
3360                     if (ch == '\n' || ch == '\r')
3361                     {
3362                         ch = sr.Read();
3363                         continue;
3364                     }
3365 
3366                     // Skip over commented lines or ones starting with whitespace.
3367                     // Support LocStudio INF format's comment char, ';'
3368                     if (ch == '#' || ch == '\t' || ch == ' ' || ch == ';')
3369                     {
3370                         // comment char (or blank line) - skip line.
3371                         sr.ReadLine();
3372                         ch = sr.Read();
3373                         continue;
3374                     }
3375                     // Note that in Beta of version 1 we recommended users should put a [strings]
3376                     // section in their file.  Now it's completely unnecessary and can
3377                     // only cause bugs.  We will not parse anything using '[' stuff now
3378                     // and we should give a warning about seeing [strings] stuff.
3379                     // In V1.1 or V2, we can rip this out completely, I hope.
3380                     if (ch == '[')
3381                     {
3382                         String skip = sr.ReadLine();
3383                         if (skip.Equals("strings]"))
3384                             _logger.LogWarningWithCodeFromResources(null, fileName, sr.LineNumber - 1, 1, 0, 0, "GenerateResource.ObsoleteStringsTag");
3385                         else
3386                         {
3387                             throw new TextFileException(_logger.FormatResourceString("GenerateResource.UnexpectedInfBracket", "[" + skip), fileName, sr.LineNumber - 1, 1);
3388                         }
3389                         ch = sr.Read();
3390                         continue;
3391                     }
3392 
3393                     // Read in name
3394                     name.Length = 0;
3395                     while (ch != '=')
3396                     {
3397                         if (ch == '\r' || ch == '\n')
3398                             throw new TextFileException(_logger.FormatResourceString("GenerateResource.NoEqualsInLine", name), fileName, sr.LineNumber, sr.LinePosition);
3399 
3400                         name.Append((char)ch);
3401                         ch = sr.Read();
3402                         if (ch == -1)
3403                             break;
3404                     }
3405                     if (name.Length == 0)
3406                         throw new TextFileException(_logger.FormatResourceString("GenerateResource.NoNameInLine"), fileName, sr.LineNumber, sr.LinePosition);
3407 
3408                     // For the INF file, we must allow a space on both sides of the equals
3409                     // sign.  Deal with it.
3410                     if (name[name.Length - 1] == ' ')
3411                     {
3412                         name.Length = name.Length - 1;
3413                     }
3414                     ch = sr.Read(); // move past =
3415                     // If it exists, move past the first space after the equals sign.
3416                     if (ch == ' ')
3417                         ch = sr.Read();
3418 
3419                     // Read in value
3420                     value.Length = 0;
3421 
3422                     while (ch != -1)
3423                     {
3424                         // Did we read @"\r" or @"\n"?
3425                         bool quotedNewLine = false;
3426                         if (ch == '\\')
3427                         {
3428                             ch = sr.Read();
3429                             switch (ch)
3430                             {
3431                                 case '\\':
3432                                     // nothing needed
3433                                     break;
3434                                 case 'n':
3435                                     ch = '\n';
3436                                     quotedNewLine = true;
3437                                     break;
3438                                 case 'r':
3439                                     ch = '\r';
3440                                     quotedNewLine = true;
3441                                     break;
3442                                 case 't':
3443                                     ch = '\t';
3444                                     break;
3445                                 case '"':
3446                                     ch = '\"';
3447                                     break;
3448                                 case 'u':
3449                                     char[] hex = new char[4];
3450                                     int numChars = 4;
3451                                     int index = 0;
3452                                     while (numChars > 0)
3453                                     {
3454                                         int n = sr.Read(hex, index, numChars);
3455                                         if (n == 0)
3456                                             throw new TextFileException(_logger.FormatResourceString("GenerateResource.InvalidEscape", name.ToString(), (char)ch), fileName, sr.LineNumber, sr.LinePosition);
3457                                         index += n;
3458                                         numChars -= n;
3459                                     }
3460                                     try
3461                                     {
3462                                         ch = (char)UInt16.Parse(new String(hex), NumberStyles.HexNumber, CultureInfo.CurrentCulture);
3463                                     }
3464                                     catch (FormatException)
3465                                     {
3466                                         // We know about this one...
3467                                         throw new TextFileException(_logger.FormatResourceString("GenerateResource.InvalidHexEscapeValue", name.ToString(), new String(hex)), fileName, sr.LineNumber, sr.LinePosition);
3468                                     }
3469                                     catch (OverflowException)
3470                                     {
3471                                         // We know about this one, too...
3472                                         throw new TextFileException(_logger.FormatResourceString("GenerateResource.InvalidHexEscapeValue", name.ToString(), new String(hex)), fileName, sr.LineNumber, sr.LinePosition);
3473                                     }
3474                                     quotedNewLine = (ch == '\n' || ch == '\r');
3475                                     break;
3476 
3477                                 default:
3478                                     throw new TextFileException(_logger.FormatResourceString("GenerateResource.InvalidEscape", name.ToString(), (char)ch), fileName, sr.LineNumber, sr.LinePosition);
3479                             }
3480                         }
3481 
3482                         // Consume endline...
3483                         //   Endline can be \r\n or \n.  But do not treat a
3484                         //   quoted newline (ie, @"\r" or @"\n" in text) as a
3485                         //   real new line.  They aren't the end of a line.
3486                         if (!quotedNewLine)
3487                         {
3488                             if (ch == '\r')
3489                             {
3490                                 ch = sr.Read();
3491                                 if (ch == -1)
3492                                 {
3493                                     break;
3494                                 }
3495                                 else if (ch == '\n')
3496                                 {
3497                                     ch = sr.Read();
3498                                     break;
3499                                 }
3500                             }
3501                             else if (ch == '\n')
3502                             {
3503                                 ch = sr.Read();
3504                                 break;
3505                             }
3506                         }
3507 
3508                         value.Append((char)ch);
3509                         ch = sr.Read();
3510                     }
3511 
3512                     // Note that value can be an empty string
3513                     AddResource(reader, name.ToString(), value.ToString(), fileName, sr.LineNumber, sr.LinePosition);
3514                 }
3515             }
3516         }
3517 
3518 
3519         /// <summary>
3520         /// Write resources to an XML or binary format resources file.
3521         /// </summary>
3522         /// <remarks>Closes writer automatically</remarks>
3523         /// <param name="writer">Appropriate IResourceWriter</param>
WriteResources(ReaderInfo reader, IResourceWriter writer)3524         private void WriteResources(ReaderInfo reader,
3525 #if FEATURE_RESX_RESOURCE_READER
3526             IResourceWriter writer)
3527 #else
3528             ResourceWriter writer)
3529 #endif
3530         {
3531             Exception capturedException = null;
3532             try
3533             {
3534                 foreach (Entry entry in reader.resources)
3535                 {
3536                     string key = entry.name;
3537                     object value = entry.value;
3538 #if FEATURE_RESX_RESOURCE_READER
3539                     writer.AddResource(key, value);
3540 #else
3541                     writer.AddResource(key, (string) value);
3542 #endif
3543                 }
3544             }
3545             catch (Exception e)
3546             {
3547                 capturedException = e; // Rethrow this after catching exceptions thrown by Close().
3548             }
3549             finally
3550             {
3551                 if (capturedException == null)
3552                 {
3553                     writer.Dispose(); // If this throws, exceptions will be caught upstream.
3554                 }
3555                 else
3556                 {
3557                     // It doesn't hurt to call Close() twice. In the event of a full disk, we *need* to call Close() twice.
3558                     // In that case, the first time we catch an exception indicating that the XML written to disk is malformed,
3559                     // specifically an InvalidOperationException: "Token EndElement in state Error would result in an invalid XML document."
3560                     try { writer.Dispose(); }
3561                     catch (Exception) { } // We agressively catch all exception types since we already have one we will throw.
3562                     // The second time we catch the out of disk space exception.
3563                     try { writer.Dispose(); }
3564                     catch (Exception) { } // We agressively catch all exception types since we already have one we will throw.
3565                     throw capturedException; // In the event of a full disk, this is an out of disk space IOException.
3566                 }
3567             }
3568         }
3569 
3570         /// <summary>
3571         /// Write resources to a text format resources file
3572         /// </summary>
3573         /// <param name="fileName">Output resources file</param>
WriteTextResources(ReaderInfo reader, String fileName)3574         private void WriteTextResources(ReaderInfo reader, String fileName)
3575         {
3576             using (StreamWriter writer = FileUtilities.OpenWrite(fileName, false, Encoding.UTF8))
3577             {
3578                 foreach (Entry entry in reader.resources)
3579                 {
3580                     String key = entry.name;
3581                     Object v = entry.value;
3582                     String value = v as String;
3583                     if (value == null)
3584                     {
3585                         _logger.LogErrorWithCodeFromResources(null, fileName, 0, 0, 0, 0, "GenerateResource.OnlyStringsSupported", key, v.GetType().FullName);
3586                     }
3587                     else
3588                     {
3589                         // Escape any special characters in the String.
3590                         value = value.Replace("\\", "\\\\");
3591                         value = value.Replace("\n", "\\n");
3592                         value = value.Replace("\r", "\\r");
3593                         value = value.Replace("\t", "\\t");
3594 
3595                         writer.WriteLine("{0}={1}", key, value);
3596                     }
3597                 }
3598             }
3599         }
3600 
3601         /// <summary>
3602         /// Add a resource from a text file to the internal data structures
3603         /// </summary>
3604         /// <param name="name">Resource name</param>
3605         /// <param name="value">Resource value</param>
3606         /// <param name="inputFileName">Input file for messages</param>
3607         /// <param name="lineNumber">Line number for messages</param>
3608         /// <param name="linePosition">Column number for messages</param>
AddResource(ReaderInfo reader, string name, object value, String inputFileName, int lineNumber, int linePosition)3609         private void AddResource(ReaderInfo reader, string name, object value, String inputFileName, int lineNumber, int linePosition)
3610         {
3611             Entry entry = new Entry(name, value);
3612 
3613             if (reader.resourcesHashTable.ContainsKey(name))
3614             {
3615                 _logger.LogWarningWithCodeFromResources(null, inputFileName, lineNumber, linePosition, 0, 0, "GenerateResource.DuplicateResourceName", name);
3616                 return;
3617             }
3618 
3619             reader.resources.Add(entry);
3620             reader.resourcesHashTable.Add(name, value);
3621         }
3622 
3623         /// <summary>
3624         /// Add a resource from an XML or binary format file to the internal data structures
3625         /// </summary>
3626         /// <param name="name">Resource name</param>
3627         /// <param name="value">Resource value</param>
3628         /// <param name="inputFileName">Input file for messages</param>
AddResource(ReaderInfo reader, string name, object value, String inputFileName)3629         private void AddResource(ReaderInfo reader, string name, object value, String inputFileName)
3630         {
3631             AddResource(reader, name, value, inputFileName, 0, 0);
3632         }
3633 
3634         internal sealed class ReaderInfo
3635         {
3636             public String outputFileName;
3637             public String cultureName;
3638             // We use a list to preserve the resource ordering (primarily for easier testing),
3639             // but also use a hash table to check for duplicate names.
3640             public ArrayList resources;
3641             public Hashtable resourcesHashTable;
3642             public String assemblySimpleName;  // The main assembly's simple name (ie, no .resources)
3643             public bool fromNeutralResources;  // Was this from the main assembly (or if the NRLA specified fallback to satellite, that satellite?)
3644 
ReaderInfo()3645             public ReaderInfo()
3646             {
3647                 resources = new ArrayList();
3648                 resourcesHashTable = new Hashtable(StringComparer.OrdinalIgnoreCase);
3649             }
3650         }
3651 
3652         /// <summary>
3653         /// Custom StreamReader that provides detailed position information,
3654         /// used when reading text format resources
3655         /// </summary>
3656         internal sealed class LineNumberStreamReader : StreamReader
3657         {
3658             // Line numbers start from 1, as well as line position.
3659             // For better error reporting, set line number to 1 and col to 0.
3660             private int _lineNumber;
3661             private int _col;
3662 
LineNumberStreamReader(String fileName, Encoding encoding, bool detectEncoding)3663             internal LineNumberStreamReader(String fileName, Encoding encoding, bool detectEncoding)
3664                 : base(File.Open(fileName, FileMode.Open, FileAccess.Read), encoding, detectEncoding)
3665             {
3666                 _lineNumber = 1;
3667                 _col = 0;
3668             }
3669 
LineNumberStreamReader(Stream stream)3670             internal LineNumberStreamReader(Stream stream)
3671                 : base(stream)
3672             {
3673                 _lineNumber = 1;
3674                 _col = 0;
3675             }
3676 
Read()3677             public override int Read()
3678             {
3679                 int ch = base.Read();
3680                 if (ch != -1)
3681                 {
3682                     _col++;
3683                     if (ch == '\n')
3684                     {
3685                         _lineNumber++;
3686                         _col = 0;
3687                     }
3688                 }
3689                 return ch;
3690             }
3691 
Read([In, Out] char[] chars, int index, int count)3692             public override int Read([In, Out] char[] chars, int index, int count)
3693             {
3694                 int r = base.Read(chars, index, count);
3695                 for (int i = 0; i < r; i++)
3696                 {
3697                     if (chars[i + index] == '\n')
3698                     {
3699                         _lineNumber++;
3700                         _col = 0;
3701                     }
3702                     else
3703                         _col++;
3704                 }
3705                 return r;
3706             }
3707 
ReadLine()3708             public override String ReadLine()
3709             {
3710                 String s = base.ReadLine();
3711                 if (s != null)
3712                 {
3713                     _lineNumber++;
3714                     _col = 0;
3715                 }
3716                 return s;
3717             }
3718 
ReadToEnd()3719             public override String ReadToEnd()
3720             {
3721                 throw new NotImplementedException("NYI");
3722             }
3723 
3724             internal int LineNumber
3725             {
3726                 get { return _lineNumber; }
3727             }
3728 
3729             internal int LinePosition
3730             {
3731                 get { return _col; }
3732             }
3733         }
3734 
3735         /// <summary>
3736         /// For flow of control & passing sufficient error context back
3737         /// from ReadTextResources
3738         /// </summary>
3739         [Serializable]
3740         internal sealed class TextFileException : Exception
3741         {
3742             private String fileName;
3743             private int lineNumber;
3744             private int column;
3745 
3746 #if FEATURE_RESGENCACHE
3747             /// <summary>
3748             /// Fxcop want to have the correct basic exception constructors implemented
3749             /// </summary>
TextFileException(SerializationInfo info, StreamingContext context)3750             private TextFileException(SerializationInfo info, StreamingContext context)
3751                 : base(info, context)
3752             {
3753             }
3754 #endif
3755 
TextFileException(String message, String fileName, int lineNumber, int linePosition)3756             internal TextFileException(String message, String fileName, int lineNumber, int linePosition)
3757                 : base(message)
3758             {
3759                 this.fileName = fileName;
3760                 this.lineNumber = lineNumber;
3761                 column = linePosition;
3762             }
3763 
3764             internal String FileName
3765             {
3766                 get { return fileName; }
3767             }
3768 
3769             internal int LineNumber
3770             {
3771                 get { return lineNumber; }
3772             }
3773 
3774             internal int LinePosition
3775             {
3776                 get { return column; }
3777             }
3778         }
3779 
3780         /// <summary>
3781         /// Name value resource pair to go in resources list
3782         /// </summary>
3783         private class Entry
3784         {
Entry(string name, object value)3785             public Entry(string name, object value)
3786             {
3787                 this.name = name;
3788                 this.value = value;
3789             }
3790 
3791             public string name;
3792             public object value;
3793         }
3794 #endregion // Code from ResGen.EXE
3795     }
3796 
3797 #if FEATURE_ASSEMBLY_LOADFROM
3798     /// <summary>
3799     /// This implemention of ITypeResolutionService is passed into the ResxResourceReader
3800     /// class, which calls back into the methods on this class in order to resolve types
3801     /// and assemblies that are referenced inside of the .RESX.
3802     /// </summary>
3803     internal class AssemblyNamesTypeResolutionService : ITypeResolutionService
3804     {
3805         private Hashtable _cachedAssemblies;
3806         private ITaskItem[] _referencePaths;
3807         private Hashtable _cachedTypes = new Hashtable();
3808 
3809         /// <summary>
3810         /// Constructor, initialized with the set of resolved reference paths passed
3811         /// into the GenerateResource task.
3812         /// </summary>
3813         /// <param name="referencePaths"></param>
AssemblyNamesTypeResolutionService(ITaskItem[] referencePaths)3814         internal AssemblyNamesTypeResolutionService(ITaskItem[] referencePaths)
3815         {
3816             _referencePaths = referencePaths;
3817         }
3818 
3819         /// <summary>
3820         /// Not implemented.  Not called by the ResxResourceReader.
3821         /// </summary>
3822         /// <param name="name"></param>
3823         /// <returns></returns>
GetAssembly(AssemblyName name)3824         public Assembly GetAssembly(AssemblyName name)
3825         {
3826             throw new NotSupportedException();
3827         }
3828 
3829         /// <summary>
3830         /// Not implemented.  Not called by the ResxResourceReader.
3831         /// </summary>
3832         /// <param name="name"></param>
3833         /// <returns></returns>
GetAssembly(AssemblyName name, bool throwOnError)3834         public Assembly GetAssembly(AssemblyName name, bool throwOnError)
3835         {
3836             throw new NotSupportedException();
3837         }
3838 
3839         /// <summary>
3840         /// Given a path to an assembly, load the assembly if it's not already loaded.
3841         /// </summary>
3842         /// <param name="pathToAssembly"></param>
3843         /// <param name="throwOnError"></param>
3844         /// <returns></returns>
GetAssemblyByPath(string pathToAssembly, bool throwOnError)3845         private Assembly GetAssemblyByPath(string pathToAssembly, bool throwOnError)
3846         {
3847             if (_cachedAssemblies == null)
3848             {
3849                 _cachedAssemblies = new Hashtable();
3850             }
3851 
3852             if (!_cachedAssemblies.Contains(pathToAssembly))
3853             {
3854                 try
3855                 {
3856                     _cachedAssemblies[pathToAssembly] = Assembly.UnsafeLoadFrom(pathToAssembly);
3857                 }
3858                 catch
3859                 {
3860                     if (throwOnError)
3861                     {
3862                         throw;
3863                     }
3864                 }
3865             }
3866 
3867             return (Assembly)_cachedAssemblies[pathToAssembly];
3868         }
3869 
3870         /// <summary>
3871         /// Not implemented.  Not called by the ResxResourceReader.
3872         /// </summary>
3873         /// <param name="name"></param>
3874         /// <returns></returns>
GetPathOfAssembly(AssemblyName name)3875         public string GetPathOfAssembly(AssemblyName name)
3876         {
3877             throw new NotSupportedException();
3878         }
3879 
3880         /// <summary>
3881         /// Returns the type with the specified name.  Searches for the type in all
3882         /// of the assemblies passed into the References parameter of the GenerateResource
3883         /// task.
3884         /// </summary>
3885         /// <param name="name"></param>
3886         /// <returns></returns>
GetType(string name)3887         public Type GetType(string name)
3888         {
3889             return GetType(name, true);
3890         }
3891 
3892         /// <summary>
3893         /// Returns the type with the specified name.  Searches for the type in all
3894         /// of the assemblies passed into the References parameter of the GenerateResource
3895         /// task.
3896         /// </summary>
3897         /// <param name="name"></param>
3898         /// <param name="throwOnError"></param>
3899         /// <returns></returns>
GetType(string name, bool throwOnError)3900         public Type GetType(string name, bool throwOnError)
3901         {
3902             return GetType(name, throwOnError, false);
3903         }
3904 
3905         /// <summary>
3906         /// Returns the type with the specified name.  Searches for the type in all
3907         /// of the assemblies passed into the References parameter of the GenerateResource
3908         /// task.
3909         /// </summary>
3910         /// <param name="name"></param>
3911         /// <param name="throwOnError"></param>
3912         /// <param name="ignoreCase"></param>
3913         /// <returns></returns>
GetType(string name, bool throwOnError, bool ignoreCase)3914         public Type GetType(string name, bool throwOnError, bool ignoreCase)
3915         {
3916             Type resultFromCache = (Type)_cachedTypes[name];
3917 
3918             if (!_cachedTypes.Contains(name))
3919             {
3920                 // first try to resolve in the GAC
3921                 Type result = Type.GetType(name, false, ignoreCase);
3922 
3923                 // did not find it in the GAC, check each assembly
3924                 if ((result == null) && (_referencePaths != null))
3925                 {
3926                     foreach (ITaskItem referencePath in _referencePaths)
3927                     {
3928                         Assembly a = this.GetAssemblyByPath(referencePath.ItemSpec, throwOnError);
3929                         if (a != null)
3930                         {
3931                             result = a.GetType(name, false, ignoreCase);
3932                             if (result == null)
3933                             {
3934                                 int indexOfComma = name.IndexOf(",", StringComparison.Ordinal);
3935                                 if (indexOfComma != -1)
3936                                 {
3937                                     string shortName = name.Substring(0, indexOfComma);
3938                                     result = a.GetType(shortName, false, ignoreCase);
3939                                 }
3940                             }
3941 
3942                             if (result != null)
3943                             {
3944                                 break;
3945                             }
3946                         }
3947                     }
3948                 }
3949 
3950                 if (result == null && throwOnError)
3951                 {
3952                     ErrorUtilities.VerifyThrowArgument(false, "GenerateResource.CouldNotLoadType", name);
3953                 }
3954 
3955                 _cachedTypes[name] = result;
3956                 resultFromCache = result;
3957             }
3958 
3959             return resultFromCache;
3960         }
3961 
3962         /// <summary>
3963         /// Not implemented.  Not called by the ResxResourceReader.
3964         /// </summary>
3965         /// <param name="name"></param>
3966         /// <returns></returns>
ReferenceAssembly(AssemblyName name)3967         public void ReferenceAssembly(AssemblyName name)
3968         {
3969             throw new NotSupportedException();
3970         }
3971     }
3972 #endif
3973 }
3974