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