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.Diagnostics; 8 using System.Globalization; 9 using System.IO; 10 using Microsoft.Build.Framework; 11 using Microsoft.Build.Tasks.Deployment.ManifestUtilities; 12 using Microsoft.Build.Shared; 13 using Microsoft.Build.Utilities; 14 15 namespace Microsoft.Build.Tasks 16 { 17 /// <summary> 18 /// This task resolves items in the build process (built, dependencies, satellites, 19 /// content, debug symbols, documentation, etc.) to files for manifest generation. 20 /// </Summary> 21 /// <comment> 22 /// This task executes following steps: 23 /// (1) Filter out Framework assemblies 24 /// (2) Filter out non-existant files 25 /// (3) Build list of Dependencies from built items with CopyLocal=True 26 /// (4) Build list of Prerequisites from built items with CopyLocal=False 27 /// (5) Build list of Satellites from built items based on TargetCulture 28 /// (6) Build list of Files from Files and ExtraFiles inputs, using next step 29 /// (7) For each PublishFile item... 30 /// If item is on Dependencies list then move it to Prerequisites list 31 /// If item is on Content list then add it to File list unless it is excluded 32 /// If item is on Extra list then add it to File list only if it is included 33 /// Apply Group and Optional attributes from PublishFile items to built items 34 /// (8) Insure all output items have a TargetPath, and if in a Group that IsOptional is set 35 /// </comment> 36 public sealed class ResolveManifestFiles : TaskExtension 37 { 38 #region Fields 39 private ITaskItem _deploymentManifestEntryPoint = null; 40 private ITaskItem _entryPoint; 41 private ITaskItem[] _extraFiles; 42 private ITaskItem[] _files; 43 private ITaskItem[] _managedAssemblies; 44 private ITaskItem[] _nativeAssemblies; 45 private ITaskItem[] _outputAssemblies; 46 private ITaskItem _outputDeploymentManifestEntryPoint = null; 47 private ITaskItem _outputEntryPoint = null; 48 private ITaskItem[] _outputFiles; 49 private ITaskItem[] _publishFiles; 50 private ITaskItem[] _satelliteAssemblies; 51 private string _specifiedTargetCulture; 52 private CultureInfo _targetCulture = null; 53 private bool _includeAllSatellites = false; 54 private bool _signingManifests = false; 55 private string _targetFrameworkVersion; 56 // if signing manifests is on and not all app files are included, then the project can't be published. 57 private bool _canPublish; 58 #endregion 59 60 #region Properties 61 62 public ITaskItem DeploymentManifestEntryPoint 63 { 64 get { return _deploymentManifestEntryPoint; } 65 set { _deploymentManifestEntryPoint = value; } 66 } 67 68 public ITaskItem EntryPoint 69 { 70 get { return _entryPoint; } 71 set { _entryPoint = value; } 72 } 73 74 public ITaskItem[] ExtraFiles 75 { 76 get { return _extraFiles; } 77 set { _extraFiles = Util.SortItems(value); } 78 } 79 80 public ITaskItem[] Files 81 { 82 get { return _files; } 83 set { _files = Util.SortItems(value); } 84 } 85 86 public ITaskItem[] ManagedAssemblies 87 { 88 get { return _managedAssemblies; } 89 set { _managedAssemblies = Util.SortItems(value); } 90 } 91 92 public ITaskItem[] NativeAssemblies 93 { 94 get { return _nativeAssemblies; } 95 set { _nativeAssemblies = Util.SortItems(value); } 96 } 97 98 [Output] 99 public ITaskItem[] OutputAssemblies 100 { 101 get { return _outputAssemblies; } 102 set { _outputAssemblies = value; } 103 } 104 105 [Output] 106 public ITaskItem OutputDeploymentManifestEntryPoint 107 { 108 get { return _outputDeploymentManifestEntryPoint; } 109 set { _outputDeploymentManifestEntryPoint = value; } 110 } 111 112 [Output] 113 public ITaskItem OutputEntryPoint 114 { 115 get { return _outputEntryPoint; } 116 set { _outputEntryPoint = value; } 117 } 118 119 [Output] 120 public ITaskItem[] OutputFiles 121 { 122 get { return _outputFiles; } 123 set { _outputFiles = value; } 124 } 125 126 public ITaskItem[] PublishFiles 127 { 128 get { return _publishFiles; } 129 set { _publishFiles = Util.SortItems(value); } 130 } 131 132 public ITaskItem[] SatelliteAssemblies 133 { 134 get { return _satelliteAssemblies; } 135 set { _satelliteAssemblies = Util.SortItems(value); } 136 } 137 138 public string TargetCulture 139 { 140 get { return _specifiedTargetCulture; } 141 set { _specifiedTargetCulture = value; } 142 } 143 144 public bool SigningManifests 145 { 146 get { return _signingManifests; } 147 set { _signingManifests = value; } 148 } 149 150 public string TargetFrameworkVersion 151 { 152 get 153 { 154 if (string.IsNullOrEmpty(_targetFrameworkVersion)) 155 return Constants.TargetFrameworkVersion35; 156 return _targetFrameworkVersion; 157 } 158 set { _targetFrameworkVersion = value; } 159 } 160 161 #endregion 162 ResolveManifestFiles()163 public ResolveManifestFiles() 164 { 165 } 166 Execute()167 public override bool Execute() 168 { 169 if (!ValidateInputs()) 170 return false; 171 172 // if signing manifests is on and not all app files are included, then the project can't be published. 173 _canPublish = true; 174 bool is35Project = (CompareFrameworkVersions(TargetFrameworkVersion, Constants.TargetFrameworkVersion35) >= 0); 175 176 PublishInfo[] assemblyPublishInfoList; 177 PublishInfo[] filePublishInfoList; 178 PublishInfo[] satellitePublishInfoList; 179 PublishInfo[] manifestEntryPointList; 180 GetPublishInfo(out assemblyPublishInfoList, out filePublishInfoList, out satellitePublishInfoList, out manifestEntryPointList); 181 182 _outputAssemblies = GetOutputAssembliesAndSatellites(assemblyPublishInfoList, satellitePublishInfoList); 183 184 if (!_canPublish && is35Project) 185 { 186 Log.LogErrorWithCodeFromResources("GenerateManifest.ManifestsSignedHashExcluded"); 187 return false; 188 } 189 190 _outputFiles = GetOutputFiles(filePublishInfoList); 191 192 if (!_canPublish && is35Project) 193 { 194 Log.LogErrorWithCodeFromResources("GenerateManifest.ManifestsSignedHashExcluded"); 195 return false; 196 } 197 198 _outputEntryPoint = GetOutputEntryPoint(_entryPoint, manifestEntryPointList); 199 200 if (!_canPublish && is35Project) 201 { 202 Log.LogErrorWithCodeFromResources("GenerateManifest.ManifestsSignedHashExcluded"); 203 return false; 204 } 205 206 _outputDeploymentManifestEntryPoint = GetOutputEntryPoint(_deploymentManifestEntryPoint, manifestEntryPointList); 207 208 if (!_canPublish && is35Project) 209 { 210 Log.LogErrorWithCodeFromResources("GenerateManifest.ManifestsSignedHashExcluded"); 211 return false; 212 } 213 214 return true; 215 } 216 ConvertFrameworkVersionToString(string version)217 private Version ConvertFrameworkVersionToString(string version) 218 { 219 if (version.StartsWith("v", StringComparison.OrdinalIgnoreCase)) 220 { 221 return new Version(version.Substring(1)); 222 } 223 return new Version(version); 224 } 225 CompareFrameworkVersions(string versionA, string versionB)226 private int CompareFrameworkVersions(string versionA, string versionB) 227 { 228 Version version1 = ConvertFrameworkVersionToString(versionA); 229 Version version2 = ConvertFrameworkVersionToString(versionB); 230 return version1.CompareTo(version2); 231 } 232 ValidateInputs()233 private bool ValidateInputs() 234 { 235 if (!String.IsNullOrEmpty(_specifiedTargetCulture)) 236 { 237 if (String.Equals(_specifiedTargetCulture, "*", StringComparison.Ordinal)) 238 { 239 _includeAllSatellites = true; 240 } 241 else if (!String.Equals(_specifiedTargetCulture, "neutral", StringComparison.Ordinal)) 242 { 243 try 244 { 245 _targetCulture = new CultureInfo(_specifiedTargetCulture); 246 } 247 catch (ArgumentException) 248 { 249 Log.LogErrorWithCodeFromResources("General.InvalidValue", "TargetCulture", "ResolveManifestFiles"); 250 return false; 251 } 252 } 253 } 254 return true; 255 } 256 257 258 #region Helpers 259 260 // Creates an output item for a an assembly, with optional Group attribute. CreateAssemblyItem(ITaskItem item, string group, string targetPath, string includeHash)261 private static ITaskItem CreateAssemblyItem(ITaskItem item, string group, string targetPath, string includeHash) 262 { 263 ITaskItem outputItem = new TaskItem(item.ItemSpec); 264 item.CopyMetadataTo(outputItem); 265 outputItem.SetMetadata("DependencyType", "Install"); 266 if (String.IsNullOrEmpty(targetPath)) 267 targetPath = GetItemTargetPath(outputItem); 268 outputItem.SetMetadata(ItemMetadataNames.targetPath, targetPath); 269 if (!String.IsNullOrEmpty(group)) 270 outputItem.SetMetadata("Group", group); 271 if (!String.IsNullOrEmpty(includeHash)) 272 outputItem.SetMetadata("IncludeHash", includeHash); 273 return outputItem; 274 } 275 276 // Creates an output item for a file, with optional Group and IsData attributes. CreateFileItem(ITaskItem item, string group, string targetPath, string includeHash, bool isDataFile)277 private static ITaskItem CreateFileItem(ITaskItem item, string group, string targetPath, string includeHash, bool isDataFile) 278 { 279 ITaskItem outputItem = new TaskItem(item.ItemSpec); 280 item.CopyMetadataTo(outputItem); 281 if (String.IsNullOrEmpty(targetPath)) 282 targetPath = GetItemTargetPath(outputItem); 283 outputItem.SetMetadata(ItemMetadataNames.targetPath, targetPath); 284 if (!String.IsNullOrEmpty(group) && !isDataFile) 285 outputItem.SetMetadata("Group", group); 286 if (!String.IsNullOrEmpty(includeHash)) 287 outputItem.SetMetadata("IncludeHash", includeHash); 288 289 outputItem.SetMetadata("IsDataFile", isDataFile.ToString().ToLowerInvariant()); 290 return outputItem; 291 } 292 293 // Creates an output item for a prerequisite. CreatePrerequisiteItem(ITaskItem item)294 private static ITaskItem CreatePrerequisiteItem(ITaskItem item) 295 { 296 ITaskItem outputItem = new TaskItem(item.ItemSpec); 297 item.CopyMetadataTo(outputItem); 298 outputItem.SetMetadata("DependencyType", "Prerequisite"); 299 return outputItem; 300 } 301 GetItemCopyLocal(ITaskItem item)302 private static bool GetItemCopyLocal(ITaskItem item) 303 { 304 string copyLocal = item.GetMetadata(ItemMetadataNames.copyLocal); 305 if (!String.IsNullOrEmpty(copyLocal)) 306 return ConvertUtil.ToBoolean(copyLocal); 307 else 308 return true; // always return true if item does not have a CopyLocal attribute 309 } 310 311 // Returns the culture for the specified item, first by looking for an attribute and if not found 312 // attempts to infer from the disk path. GetItemCulture(ITaskItem item)313 private static CultureInfo GetItemCulture(ITaskItem item) 314 { 315 string itemCulture = item.GetMetadata("Culture"); 316 if (String.IsNullOrEmpty(itemCulture)) 317 { 318 // Infer culture from path (i.e. "obj\debug\fr\WindowsApplication1.resources.dll" -> "fr") 319 string[] pathSegments = PathUtil.GetPathSegments(item.ItemSpec); 320 itemCulture = pathSegments.Length > 1 ? pathSegments[pathSegments.Length - 2] : null; 321 Debug.Assert(!String.IsNullOrEmpty(itemCulture), String.Format(CultureInfo.CurrentCulture, "Satellite item '{0}' is missing expected attribute '{1}'", item.ItemSpec, "Culture")); 322 item.SetMetadata("Culture", itemCulture); 323 } 324 return new CultureInfo(itemCulture); 325 } 326 GetItemTargetPath(ITaskItem item)327 private static string GetItemTargetPath(ITaskItem item) 328 { 329 string targetPath = item.GetMetadata(ItemMetadataNames.targetPath); 330 if (String.IsNullOrEmpty(targetPath)) 331 { 332 targetPath = Path.GetFileName(item.ItemSpec); 333 // If item is a satellite then make sure the culture is part of the path... 334 string assemblyType = item.GetMetadata("AssemblyType"); 335 if (String.Equals(assemblyType, "Satellite", StringComparison.Ordinal)) 336 { 337 CultureInfo itemCulture = GetItemCulture(item); 338 if (itemCulture != null) 339 targetPath = Path.Combine(itemCulture.ToString(), targetPath); 340 } 341 } 342 return targetPath; 343 } 344 GetOutputAssemblies(PublishInfo[] publishInfos, ref List<ITaskItem> assemblyList)345 private void GetOutputAssemblies(PublishInfo[] publishInfos, ref List<ITaskItem> assemblyList) 346 { 347 AssemblyMap assemblyMap = new AssemblyMap(); 348 349 // Add all managed assemblies to the AssemblyMap, except assemblies that are part of the .NET Framework... 350 if (_managedAssemblies != null) 351 foreach (ITaskItem item in _managedAssemblies) 352 if (!IsFiltered(item)) 353 { 354 item.SetMetadata("AssemblyType", "Managed"); 355 assemblyMap.Add(item); 356 } 357 358 if (_nativeAssemblies != null) 359 foreach (ITaskItem item in _nativeAssemblies) 360 if (!IsFiltered(item)) 361 { 362 item.SetMetadata("AssemblyType", "Native"); 363 assemblyMap.Add(item); 364 } 365 366 // Apply PublishInfo state from PublishFile items... 367 foreach (PublishInfo publishInfo in publishInfos) 368 { 369 MapEntry entry = assemblyMap[publishInfo.key]; 370 if (entry != null) 371 entry.publishInfo = publishInfo; 372 else 373 Log.LogWarningWithCodeFromResources("ResolveManifestFiles.PublishFileNotFound", publishInfo.key); 374 } 375 376 // Go through the AssemblyMap and determine which items get added to ouput AssemblyList based 377 // on computed PublishFlags for each item... 378 foreach (MapEntry entry in assemblyMap) 379 { 380 // If PublishInfo didn't come from a PublishFile item, then construct PublishInfo from the item 381 if (entry.publishInfo == null) 382 entry.publishInfo = new PublishInfo(); 383 384 // If state is auto then also need to look on the item to see whether the dependency type 385 // has alread been specified upstream (i.e. from ResolveNativeReference task)... 386 if (entry.publishInfo.state == PublishState.Auto) 387 { 388 string dependencyType = entry.item.GetMetadata("DependencyType"); 389 if (String.Equals(dependencyType, "Prerequisite", StringComparison.Ordinal)) 390 entry.publishInfo.state = PublishState.Prerequisite; 391 else if (String.Equals(dependencyType, "Install", StringComparison.Ordinal)) 392 entry.publishInfo.state = PublishState.Include; 393 } 394 395 bool copyLocal = GetItemCopyLocal(entry.item); 396 PublishFlags flags = PublishFlags.GetAssemblyFlags(entry.publishInfo.state, copyLocal); 397 398 if (flags.IsPublished && 399 string.Equals(entry.publishInfo.includeHash, "false", StringComparison.OrdinalIgnoreCase) && 400 SigningManifests == true) 401 _canPublish = false; 402 403 if (flags.IsPublished) 404 assemblyList.Add(CreateAssemblyItem(entry.item, entry.publishInfo.group, entry.publishInfo.targetPath, entry.publishInfo.includeHash)); 405 else if (flags.IsPrerequisite) 406 assemblyList.Add(CreatePrerequisiteItem(entry.item)); 407 } 408 } 409 GetOutputAssembliesAndSatellites(PublishInfo[] assemblyPublishInfos, PublishInfo[] satellitePublishInfos)410 private ITaskItem[] GetOutputAssembliesAndSatellites(PublishInfo[] assemblyPublishInfos, PublishInfo[] satellitePublishInfos) 411 { 412 List<ITaskItem> assemblyList = new List<ITaskItem>(); 413 GetOutputAssemblies(assemblyPublishInfos, ref assemblyList); 414 GetOutputSatellites(satellitePublishInfos, ref assemblyList); 415 return assemblyList.ToArray(); 416 } 417 GetOutputFiles(PublishInfo[] publishInfos)418 private ITaskItem[] GetOutputFiles(PublishInfo[] publishInfos) 419 { 420 List<ITaskItem> fileList = new List<ITaskItem>(); 421 FileMap fileMap = new FileMap(); 422 423 // Add all input Files to the FileMap, flagging them to be published by default... 424 if (Files != null) 425 foreach (ITaskItem item in Files) 426 fileMap.Add(item, true); 427 428 // Add all input ExtraFiles to the FileMap, flagging them to NOT be published by default... 429 if (ExtraFiles != null) 430 foreach (ITaskItem item in ExtraFiles) 431 fileMap.Add(item, false); 432 433 // Apply PublishInfo state from PublishFile items... 434 foreach (PublishInfo publishInfo in publishInfos) 435 { 436 MapEntry entry = fileMap[publishInfo.key]; 437 if (entry != null) 438 entry.publishInfo = publishInfo; 439 else 440 Log.LogWarningWithCodeFromResources("ResolveManifestFiles.PublishFileNotFound", publishInfo.key); 441 } 442 443 // Go through the FileMap and determine which items get added to ouput FileList based 444 // on computed PublishFlags for each item... 445 foreach (MapEntry entry in fileMap) 446 { 447 // If PublishInfo didn't come from a PublishFile item, then construct PublishInfo from the item 448 if (entry.publishInfo == null) 449 entry.publishInfo = new PublishInfo(); 450 451 string fileExtension = Path.GetExtension(entry.item.ItemSpec); 452 PublishFlags flags = PublishFlags.GetFileFlags(entry.publishInfo.state, fileExtension, entry.includedByDefault); 453 454 if (flags.IsPublished && 455 string.Equals(entry.publishInfo.includeHash, "false", StringComparison.OrdinalIgnoreCase) && 456 SigningManifests == true) 457 _canPublish = false; 458 459 if (flags.IsPublished) 460 fileList.Add(CreateFileItem(entry.item, entry.publishInfo.group, entry.publishInfo.targetPath, entry.publishInfo.includeHash, flags.IsDataFile)); 461 } 462 463 return fileList.ToArray(); 464 } 465 GetOutputSatellites(PublishInfo[] publishInfos, ref List<ITaskItem> assemblyList)466 private void GetOutputSatellites(PublishInfo[] publishInfos, ref List<ITaskItem> assemblyList) 467 { 468 FileMap satelliteMap = new FileMap(); 469 470 if (_satelliteAssemblies != null) 471 foreach (ITaskItem item in _satelliteAssemblies) 472 { 473 item.SetMetadata("AssemblyType", "Satellite"); 474 satelliteMap.Add(item, true); 475 } 476 477 // Apply PublishInfo state from PublishFile items... 478 foreach (PublishInfo publishInfo in publishInfos) 479 { 480 string key = publishInfo.key + ".dll"; 481 MapEntry entry = satelliteMap[key]; 482 if (entry != null) 483 entry.publishInfo = publishInfo; 484 else 485 Log.LogWarningWithCodeFromResources("ResolveManifestFiles.PublishFileNotFound", publishInfo.key); 486 } 487 488 // Go through the AssemblyMap and determine which items get added to ouput SatelliteList based 489 // on computed PublishFlags for each item... 490 foreach (MapEntry entry in satelliteMap) 491 { 492 // If PublishInfo didn't come from a PublishFile item, then construct PublishInfo from the item 493 if (entry.publishInfo == null) 494 { 495 entry.publishInfo = new PublishInfo(); 496 } 497 498 CultureInfo satelliteCulture = GetItemCulture(entry.item); 499 PublishFlags flags = PublishFlags.GetSatelliteFlags(entry.publishInfo.state, satelliteCulture, _targetCulture, _includeAllSatellites); 500 501 if (flags.IsPublished && 502 string.Equals(entry.publishInfo.includeHash, "false", StringComparison.OrdinalIgnoreCase) && 503 SigningManifests == true) 504 _canPublish = false; 505 506 if (flags.IsPublished) 507 { 508 assemblyList.Add(CreateAssemblyItem(entry.item, entry.publishInfo.group, entry.publishInfo.targetPath, entry.publishInfo.includeHash)); 509 } 510 else if (flags.IsPrerequisite) 511 { 512 assemblyList.Add(CreatePrerequisiteItem(entry.item)); 513 } 514 } 515 } 516 GetOutputEntryPoint(ITaskItem entryPoint, PublishInfo[] manifestEntryPointList)517 private ITaskItem GetOutputEntryPoint(ITaskItem entryPoint, PublishInfo[] manifestEntryPointList) 518 { 519 if (entryPoint == null) 520 { 521 return null; 522 } 523 TaskItem outputEntryPoint = new TaskItem(entryPoint.ItemSpec); 524 entryPoint.CopyMetadataTo(outputEntryPoint); 525 string targetPath = entryPoint.GetMetadata("TargetPath"); 526 if (!string.IsNullOrEmpty(targetPath)) 527 { 528 for (int i = 0; i < manifestEntryPointList.Length; i++) 529 { 530 if (String.Equals(targetPath, manifestEntryPointList[i].key, StringComparison.OrdinalIgnoreCase)) 531 { 532 if (!string.IsNullOrEmpty(manifestEntryPointList[i].includeHash)) 533 { 534 if (manifestEntryPointList[i].state != PublishState.Exclude && 535 string.Equals(manifestEntryPointList[i].includeHash, "false", StringComparison.OrdinalIgnoreCase) && 536 SigningManifests == true) 537 _canPublish = false; 538 outputEntryPoint.SetMetadata("IncludeHash", manifestEntryPointList[i].includeHash); 539 } 540 return outputEntryPoint; 541 } 542 } 543 } 544 545 return outputEntryPoint; 546 } 547 548 // Returns PublishFile items seperated into seperate arrays by FileType attribute. GetPublishInfo( out PublishInfo[] assemblyPublishInfos, out PublishInfo[] filePublishInfos, out PublishInfo[] satellitePublishInfos, out PublishInfo[] manifestEntryPointPublishInfos)549 private void GetPublishInfo( 550 out PublishInfo[] assemblyPublishInfos, 551 out PublishInfo[] filePublishInfos, 552 out PublishInfo[] satellitePublishInfos, 553 out PublishInfo[] manifestEntryPointPublishInfos) 554 { 555 List<PublishInfo> assemblyList = new List<PublishInfo>(); 556 List<PublishInfo> fileList = new List<PublishInfo>(); 557 List<PublishInfo> satelliteList = new List<PublishInfo>(); 558 List<PublishInfo> manifestEntryPointList = new List<PublishInfo>(); 559 560 if (PublishFiles != null) 561 foreach (ITaskItem item in PublishFiles) 562 { 563 PublishInfo publishInfo = new PublishInfo(item); 564 string fileType = item.GetMetadata("FileType"); 565 switch (fileType) 566 { 567 case "Assembly": 568 assemblyList.Add(publishInfo); 569 break; 570 case "File": 571 fileList.Add(publishInfo); 572 break; 573 case "Satellite": 574 satelliteList.Add(publishInfo); 575 break; 576 case "ManifestEntryPoint": 577 manifestEntryPointList.Add(publishInfo); 578 break; 579 default: 580 Log.LogWarningWithCodeFromResources("GenerateManifest.InvalidItemValue", "FileType", item.ItemSpec); 581 continue; 582 } 583 } 584 585 assemblyPublishInfos = assemblyList.ToArray(); 586 filePublishInfos = fileList.ToArray(); 587 satellitePublishInfos = satelliteList.ToArray(); 588 manifestEntryPointPublishInfos = manifestEntryPointList.ToArray(); 589 } 590 IsFiltered(ITaskItem item)591 private bool IsFiltered(ITaskItem item) 592 { 593 // If assembly is part of the FX then it should be filtered out... 594 // System.Reflection.AssemblyName.GetAssemblyName throws if file is not an assembly. 595 // We're using AssemblyIdentity.FromManagedAssembly here because it just does an 596 // OpenScope and returns null if not an assembly, which is much faster. 597 598 AssemblyIdentity identity = AssemblyIdentity.FromManagedAssembly(item.ItemSpec); 599 if (identity != null && identity.IsInFramework(Constants.DotNetFrameworkIdentifier, TargetFrameworkVersion)) 600 { 601 return true; 602 } 603 604 // If assembly is not a "Redist Root" then it should be filtered out... 605 string str = item.GetMetadata("IsRedistRoot"); 606 if (!String.IsNullOrEmpty(str)) 607 { 608 bool isRedistRoot; 609 if (Boolean.TryParse(str, out isRedistRoot)) 610 { 611 return !isRedistRoot; 612 } 613 } 614 return false; 615 } 616 617 #endregion 618 619 #region PublishInfo 620 private class PublishInfo 621 { 622 public readonly string key = null; 623 public readonly string group = null; 624 public readonly string targetPath = null; 625 public readonly string includeHash = null; 626 public PublishState state = PublishState.Auto; PublishInfo()627 public PublishInfo() 628 { 629 } PublishInfo(ITaskItem item)630 public PublishInfo(ITaskItem item) 631 { 632 this.key = item.ItemSpec != null ? item.ItemSpec.ToLowerInvariant() : null; 633 this.group = item.GetMetadata("Group"); 634 this.state = StringToPublishState(item.GetMetadata("PublishState")); 635 this.includeHash = item.GetMetadata("IncludeHash"); 636 this.targetPath = item.GetMetadata(ItemMetadataNames.targetPath); 637 } 638 } 639 #endregion 640 641 #region MapEntry 642 private class MapEntry 643 { 644 public readonly ITaskItem item; 645 public readonly bool includedByDefault; 646 public PublishInfo publishInfo = null; MapEntry(ITaskItem item, bool includedByDefault)647 public MapEntry(ITaskItem item, bool includedByDefault) 648 { 649 this.item = item; 650 this.includedByDefault = includedByDefault; 651 } 652 } 653 #endregion 654 655 #region AssemblyMap 656 private class AssemblyMap : IEnumerable 657 { 658 private readonly Dictionary<string, MapEntry> _dictionary = new Dictionary<string, MapEntry>(); 659 private readonly Dictionary<string, MapEntry> _simpleNameDictionary = new Dictionary<string, MapEntry>(); 660 661 public MapEntry this[string fusionName] 662 { 663 get 664 { 665 MapEntry entry = null; 666 string key = fusionName.ToLowerInvariant(); 667 if (!_dictionary.TryGetValue(key, out entry)) 668 _simpleNameDictionary.TryGetValue(key, out entry); 669 return entry; 670 } 671 } 672 Add(ITaskItem item)673 public void Add(ITaskItem item) 674 { 675 MapEntry entry = new MapEntry(item, true); 676 string key; 677 string fusionName = item.GetMetadata(ItemMetadataNames.fusionName); 678 if (String.IsNullOrEmpty(fusionName)) 679 fusionName = Path.GetFileNameWithoutExtension(item.ItemSpec); 680 681 // Add to map with full name, for SpecificVersion=true case 682 key = fusionName.ToLowerInvariant(); 683 Debug.Assert(!_dictionary.ContainsKey(key), String.Format(CultureInfo.CurrentCulture, "Two or more items with same key '{0}' detected", key)); 684 if (!_dictionary.ContainsKey(key)) 685 _dictionary.Add(key, entry); 686 687 // Also add to map with simple name, for SpecificVersion=false case 688 int i = fusionName.IndexOf(','); 689 if (i > 0) 690 { 691 string simpleName = fusionName.Substring(0, i); //example: "ClassLibrary1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" -> "ClassLibrary1" 692 key = simpleName.ToLowerInvariant(); 693 // If there are multiple with same simple name then we'll take the first one and ignore the rest, which is not an unreasonable thing to do 694 if (!_simpleNameDictionary.ContainsKey(key)) 695 _simpleNameDictionary.Add(key, entry); 696 } 697 } 698 IEnumerable.GetEnumerator()699 IEnumerator IEnumerable.GetEnumerator() 700 { 701 return _dictionary.Values.GetEnumerator(); 702 } 703 } 704 #endregion 705 706 #region FileMap 707 private class FileMap : IEnumerable 708 { 709 private readonly Dictionary<string, MapEntry> _dictionary = new Dictionary<string, MapEntry>(); 710 711 public MapEntry this[string targetPath] 712 { 713 get 714 { 715 MapEntry entry = null; 716 string key = targetPath.ToLowerInvariant(); 717 _dictionary.TryGetValue(key, out entry); 718 return entry; 719 } 720 } 721 Add(ITaskItem item, bool includedByDefault)722 public void Add(ITaskItem item, bool includedByDefault) 723 { 724 string targetPath = GetItemTargetPath(item); 725 Debug.Assert(!String.IsNullOrEmpty(targetPath)); 726 if (String.IsNullOrEmpty(targetPath)) return; 727 string key = targetPath.ToLowerInvariant(); 728 Debug.Assert(!_dictionary.ContainsKey(key), String.Format(CultureInfo.CurrentCulture, "Two or more items with same '{0}' attribute detected", ItemMetadataNames.targetPath)); 729 MapEntry entry = new MapEntry(item, includedByDefault); 730 if (!_dictionary.ContainsKey(key)) 731 _dictionary.Add(key, entry); 732 } 733 IEnumerable.GetEnumerator()734 IEnumerator IEnumerable.GetEnumerator() 735 { 736 return _dictionary.Values.GetEnumerator(); 737 } 738 } 739 #endregion 740 741 #region PublishFlags 742 private enum PublishState 743 { 744 Auto, 745 Include, 746 Exclude, 747 DataFile, 748 Prerequisite 749 } 750 StringToPublishState(string value)751 private static PublishState StringToPublishState(string value) 752 { 753 if (!String.IsNullOrEmpty(value)) 754 { 755 try 756 { 757 return (PublishState)Enum.Parse(typeof(PublishState), value, false); 758 } 759 catch (FormatException) 760 { 761 Debug.Fail(String.Format(CultureInfo.CurrentCulture, "Invalid value '{0}' for {1}", value, "PublishState")); 762 } 763 catch (ArgumentException) 764 { 765 Debug.Fail(String.Format(CultureInfo.CurrentCulture, "Invalid value '{0}' for {1}", value, "PublishState")); 766 } 767 } 768 return PublishState.Auto; 769 } 770 771 private class PublishFlags 772 { 773 private bool _isDataFile = false; 774 private bool _isPrerequisite = false; 775 private bool _isPublished = false; 776 PublishFlags(bool isDataFile, bool isPrerequisite, bool isPublished)777 private PublishFlags(bool isDataFile, bool isPrerequisite, bool isPublished) 778 { 779 _isDataFile = isDataFile; 780 _isPrerequisite = isPrerequisite; 781 _isPublished = isPublished; 782 } 783 GetAssemblyFlags(PublishState state, bool copyLocal)784 public static PublishFlags GetAssemblyFlags(PublishState state, bool copyLocal) 785 { 786 bool isDataFile = false; 787 bool isPrerequisite = false; 788 bool isPublished = false; 789 switch (state) 790 { 791 case PublishState.Auto: 792 isPrerequisite = !copyLocal; 793 isPublished = copyLocal; 794 break; 795 case PublishState.Include: 796 isPrerequisite = false; 797 isPublished = true; 798 break; 799 case PublishState.Exclude: 800 isPrerequisite = false; 801 isPublished = false; 802 break; 803 case PublishState.DataFile: 804 Debug.Fail(String.Format(CultureInfo.CurrentCulture, "PublishState.DataFile is invalid for an assembly")); 805 break; 806 case PublishState.Prerequisite: 807 isPrerequisite = true; 808 isPublished = false; 809 break; 810 default: 811 Debug.Fail(String.Format(CultureInfo.CurrentCulture, "Unhandled value PublishFlags.{0}", state.ToString())); 812 break; 813 } 814 return new PublishFlags(isDataFile, isPrerequisite, isPublished); 815 } 816 GetFileFlags(PublishState state, string fileExtension, bool includedByDefault)817 public static PublishFlags GetFileFlags(PublishState state, string fileExtension, bool includedByDefault) 818 { 819 bool isDataFile = false; 820 bool isPrerequisite = false; 821 bool isPublished = false; 822 switch (state) 823 { 824 case PublishState.Auto: 825 isDataFile = includedByDefault && PathUtil.IsDataFile(fileExtension); 826 isPublished = includedByDefault; 827 break; 828 case PublishState.Include: 829 isDataFile = false; 830 isPublished = true; 831 break; 832 case PublishState.Exclude: 833 isDataFile = false; 834 isPublished = false; 835 break; 836 case PublishState.DataFile: 837 isDataFile = true; 838 isPublished = true; 839 break; 840 case PublishState.Prerequisite: 841 Debug.Fail(String.Format(CultureInfo.CurrentCulture, "PublishState.Prerequisite is invalid for a file")); 842 break; 843 default: 844 Debug.Fail(String.Format(CultureInfo.CurrentCulture, "Unhandled value PublishFlags.{0}", state.ToString())); 845 break; 846 } 847 return new PublishFlags(isDataFile, isPrerequisite, isPublished); 848 } 849 GetSatelliteFlags(PublishState state, CultureInfo satelliteCulture, CultureInfo targetCulture, bool includeAllSatellites)850 public static PublishFlags GetSatelliteFlags(PublishState state, CultureInfo satelliteCulture, CultureInfo targetCulture, bool includeAllSatellites) 851 { 852 bool includedByDefault = IsSatelliteIncludedByDefault(satelliteCulture, targetCulture, includeAllSatellites); 853 bool isDataFile = false; 854 bool isPrerequisite = false; 855 bool isPublished = false; 856 switch (state) 857 { 858 case PublishState.Auto: 859 isPrerequisite = false; 860 isPublished = includedByDefault; 861 break; 862 case PublishState.Include: 863 isPrerequisite = false; 864 isPublished = true; 865 break; 866 case PublishState.Exclude: 867 isPrerequisite = false; 868 isPublished = false; 869 break; 870 case PublishState.DataFile: 871 Debug.Fail(String.Format(CultureInfo.CurrentCulture, "PublishState.DataFile is invalid for an assembly")); 872 break; 873 case PublishState.Prerequisite: 874 isPrerequisite = true; 875 isPublished = false; 876 break; 877 default: 878 Debug.Fail(String.Format(CultureInfo.CurrentCulture, "Unhandled value PublishFlags.{0}", state.ToString())); 879 break; 880 } 881 return new PublishFlags(isDataFile, isPrerequisite, isPublished); 882 } 883 884 public bool IsDataFile 885 { 886 get { return _isDataFile; } 887 } 888 889 public bool IsPrerequisite 890 { 891 get { return _isPrerequisite; } 892 } 893 894 public bool IsPublished 895 { 896 get { return _isPublished; } 897 } 898 IsSatelliteIncludedByDefault(CultureInfo satelliteCulture, CultureInfo targetCulture, bool includeAllSatellites)899 private static bool IsSatelliteIncludedByDefault(CultureInfo satelliteCulture, CultureInfo targetCulture, bool includeAllSatellites) 900 { 901 // If target culture not specified then satellite is not included by default... 902 if (targetCulture == null) 903 return includeAllSatellites; 904 905 // If satellite culture matches target culture then satellite is included by default... 906 if (targetCulture.Equals(satelliteCulture)) 907 return true; 908 909 // If satellite culture matches target culture's neutral culture then satellite is included by default... 910 // For example, if target culture is "fr-FR" then target culture's neutral culture is "fr", 911 // so if satellite culture is also "fr" then it will be included as well. 912 if (!targetCulture.IsNeutralCulture && targetCulture.Parent.Equals(satelliteCulture)) 913 return true; 914 915 // Otherwise satellite is not included by default... 916 return includeAllSatellites; 917 } 918 } 919 #endregion 920 } 921 } 922