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