diff options
| author | Rob Mensching <rob@firegiant.com> | 2017-09-17 15:35:20 -0700 |
|---|---|---|
| committer | Rob Mensching <rob@firegiant.com> | 2017-09-17 16:00:11 -0700 |
| commit | d3d3649a68cb1fa589fdd987a6690dbd5d671f0d (patch) | |
| tree | 44fe37ee352b7e3a355cc1e08b1d7d5988c14cc0 /src/WixToolset.Core/Link | |
| parent | a62610d23d6feb98be3b1e529a4e81b19d77d9d8 (diff) | |
| download | wix-d3d3649a68cb1fa589fdd987a6690dbd5d671f0d.tar.gz wix-d3d3649a68cb1fa589fdd987a6690dbd5d671f0d.tar.bz2 wix-d3d3649a68cb1fa589fdd987a6690dbd5d671f0d.zip | |
Initial code commit
Diffstat (limited to 'src/WixToolset.Core/Link')
| -rw-r--r-- | src/WixToolset.Core/Link/ConnectToFeature.cs | 95 | ||||
| -rw-r--r-- | src/WixToolset.Core/Link/ConnectToFeatureCollection.cs | 92 | ||||
| -rw-r--r-- | src/WixToolset.Core/Link/ConnectToModule.cs | 54 | ||||
| -rw-r--r-- | src/WixToolset.Core/Link/ConnectToModuleCollection.cs | 92 | ||||
| -rw-r--r-- | src/WixToolset.Core/Link/FindEntrySectionAndLoadSymbolsCommand.cs | 109 | ||||
| -rw-r--r-- | src/WixToolset.Core/Link/ReportConflictingSymbolsCommand.cs | 49 | ||||
| -rw-r--r-- | src/WixToolset.Core/Link/ResolveReferencesCommand.cs | 178 | ||||
| -rw-r--r-- | src/WixToolset.Core/Link/WixGroupingOrdering.cs | 726 |
8 files changed, 1395 insertions, 0 deletions
diff --git a/src/WixToolset.Core/Link/ConnectToFeature.cs b/src/WixToolset.Core/Link/ConnectToFeature.cs new file mode 100644 index 00000000..6e046b89 --- /dev/null +++ b/src/WixToolset.Core/Link/ConnectToFeature.cs | |||
| @@ -0,0 +1,95 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixToolset.Link | ||
| 4 | { | ||
| 5 | using System.Collections.Specialized; | ||
| 6 | using WixToolset.Data; | ||
| 7 | |||
| 8 | /// <summary> | ||
| 9 | /// Object that connects things (components/modules) to features. | ||
| 10 | /// </summary> | ||
| 11 | public sealed class ConnectToFeature | ||
| 12 | { | ||
| 13 | private Section section; | ||
| 14 | private string childId; | ||
| 15 | |||
| 16 | private string primaryFeature; | ||
| 17 | private bool explicitPrimaryFeature; | ||
| 18 | private StringCollection connectFeatures; | ||
| 19 | |||
| 20 | /// <summary> | ||
| 21 | /// Creates a new connect to feature. | ||
| 22 | /// </summary> | ||
| 23 | /// <param name="section">Section this connect belongs to.</param> | ||
| 24 | /// <param name="childId">Id of the child.</param> | ||
| 25 | public ConnectToFeature(Section section, string childId) : | ||
| 26 | this(section, childId, null, false) | ||
| 27 | { | ||
| 28 | } | ||
| 29 | |||
| 30 | /// <summary> | ||
| 31 | /// Creates a new connect to feature. | ||
| 32 | /// </summary> | ||
| 33 | /// <param name="section">Section this connect belongs to.</param> | ||
| 34 | /// <param name="childId">Id of the child.</param> | ||
| 35 | /// <param name="primaryFeature">Sets the primary feature for the connection.</param> | ||
| 36 | /// <param name="explicitPrimaryFeature">Sets if this is explicit primary.</param> | ||
| 37 | public ConnectToFeature(Section section, string childId, string primaryFeature, bool explicitPrimaryFeature) | ||
| 38 | { | ||
| 39 | this.section = section; | ||
| 40 | this.childId = childId; | ||
| 41 | |||
| 42 | this.primaryFeature = primaryFeature; | ||
| 43 | this.explicitPrimaryFeature = explicitPrimaryFeature; | ||
| 44 | |||
| 45 | this.connectFeatures = new StringCollection(); | ||
| 46 | } | ||
| 47 | |||
| 48 | /// <summary> | ||
| 49 | /// Gets the section. | ||
| 50 | /// </summary> | ||
| 51 | /// <value>Section.</value> | ||
| 52 | public Section Section | ||
| 53 | { | ||
| 54 | get { return this.section; } | ||
| 55 | } | ||
| 56 | |||
| 57 | /// <summary> | ||
| 58 | /// Gets the child identifier. | ||
| 59 | /// </summary> | ||
| 60 | /// <value>The child identifier.</value> | ||
| 61 | public string ChildId | ||
| 62 | { | ||
| 63 | get { return this.childId; } | ||
| 64 | } | ||
| 65 | |||
| 66 | /// <summary> | ||
| 67 | /// Gets or sets if the flag for if the primary feature was set explicitly. | ||
| 68 | /// </summary> | ||
| 69 | /// <value>The flag for if the primary feature was set explicitly.</value> | ||
| 70 | public bool IsExplicitPrimaryFeature | ||
| 71 | { | ||
| 72 | get { return this.explicitPrimaryFeature; } | ||
| 73 | set { this.explicitPrimaryFeature = value; } | ||
| 74 | } | ||
| 75 | |||
| 76 | /// <summary> | ||
| 77 | /// Gets or sets the primary feature. | ||
| 78 | /// </summary> | ||
| 79 | /// <value>The primary feature.</value> | ||
| 80 | public string PrimaryFeature | ||
| 81 | { | ||
| 82 | get { return this.primaryFeature; } | ||
| 83 | set { this.primaryFeature = value; } | ||
| 84 | } | ||
| 85 | |||
| 86 | /// <summary> | ||
| 87 | /// Gets the features connected to. | ||
| 88 | /// </summary> | ||
| 89 | /// <value>Features connected to.</value> | ||
| 90 | public StringCollection ConnectFeatures | ||
| 91 | { | ||
| 92 | get { return this.connectFeatures; } | ||
| 93 | } | ||
| 94 | } | ||
| 95 | } | ||
diff --git a/src/WixToolset.Core/Link/ConnectToFeatureCollection.cs b/src/WixToolset.Core/Link/ConnectToFeatureCollection.cs new file mode 100644 index 00000000..8dd0d22c --- /dev/null +++ b/src/WixToolset.Core/Link/ConnectToFeatureCollection.cs | |||
| @@ -0,0 +1,92 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixToolset.Link | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections; | ||
| 7 | |||
| 8 | /// <summary> | ||
| 9 | /// Hash collection of connect to feature objects. | ||
| 10 | /// </summary> | ||
| 11 | public sealed class ConnectToFeatureCollection : ICollection | ||
| 12 | { | ||
| 13 | private Hashtable collection; | ||
| 14 | |||
| 15 | /// <summary> | ||
| 16 | /// Instantiate a new ConnectToFeatureCollection class. | ||
| 17 | /// </summary> | ||
| 18 | public ConnectToFeatureCollection() | ||
| 19 | { | ||
| 20 | this.collection = new Hashtable(); | ||
| 21 | } | ||
| 22 | |||
| 23 | /// <summary> | ||
| 24 | /// Gets the number of items in the collection. | ||
| 25 | /// </summary> | ||
| 26 | /// <value>Number of items in collection.</value> | ||
| 27 | public int Count | ||
| 28 | { | ||
| 29 | get { return this.collection.Count; } | ||
| 30 | } | ||
| 31 | |||
| 32 | /// <summary> | ||
| 33 | /// Gets if the collection has been synchronized. | ||
| 34 | /// </summary> | ||
| 35 | /// <value>True if the collection has been synchronized.</value> | ||
| 36 | public bool IsSynchronized | ||
| 37 | { | ||
| 38 | get { return this.collection.IsSynchronized; } | ||
| 39 | } | ||
| 40 | |||
| 41 | /// <summary> | ||
| 42 | /// Gets the object used to synchronize the collection. | ||
| 43 | /// </summary> | ||
| 44 | /// <value>Oject used the synchronize the collection.</value> | ||
| 45 | public object SyncRoot | ||
| 46 | { | ||
| 47 | get { return this.collection.SyncRoot; } | ||
| 48 | } | ||
| 49 | |||
| 50 | /// <summary> | ||
| 51 | /// Gets a feature connection by child id. | ||
| 52 | /// </summary> | ||
| 53 | /// <param name="childId">Identifier of child to locate.</param> | ||
| 54 | public ConnectToFeature this[string childId] | ||
| 55 | { | ||
| 56 | get { return (ConnectToFeature)this.collection[childId]; } | ||
| 57 | } | ||
| 58 | |||
| 59 | /// <summary> | ||
| 60 | /// Adds a feature connection to the collection. | ||
| 61 | /// </summary> | ||
| 62 | /// <param name="connection">Feature connection to add.</param> | ||
| 63 | public void Add(ConnectToFeature connection) | ||
| 64 | { | ||
| 65 | if (null == connection) | ||
| 66 | { | ||
| 67 | throw new ArgumentNullException("connection"); | ||
| 68 | } | ||
| 69 | |||
| 70 | this.collection.Add(connection.ChildId, connection); | ||
| 71 | } | ||
| 72 | |||
| 73 | /// <summary> | ||
| 74 | /// Copies the collection into an array. | ||
| 75 | /// </summary> | ||
| 76 | /// <param name="array">Array to copy the collection into.</param> | ||
| 77 | /// <param name="index">Index to start copying from.</param> | ||
| 78 | public void CopyTo(System.Array array, int index) | ||
| 79 | { | ||
| 80 | this.collection.CopyTo(array, index); | ||
| 81 | } | ||
| 82 | |||
| 83 | /// <summary> | ||
| 84 | /// Gets enumerator for the collection. | ||
| 85 | /// </summary> | ||
| 86 | /// <returns>Enumerator for the collection.</returns> | ||
| 87 | public IEnumerator GetEnumerator() | ||
| 88 | { | ||
| 89 | return this.collection.Values.GetEnumerator(); | ||
| 90 | } | ||
| 91 | } | ||
| 92 | } | ||
diff --git a/src/WixToolset.Core/Link/ConnectToModule.cs b/src/WixToolset.Core/Link/ConnectToModule.cs new file mode 100644 index 00000000..d6a8338e --- /dev/null +++ b/src/WixToolset.Core/Link/ConnectToModule.cs | |||
| @@ -0,0 +1,54 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixToolset.Link | ||
| 4 | { | ||
| 5 | /// <summary> | ||
| 6 | /// Object that connects things to modules. | ||
| 7 | /// </summary> | ||
| 8 | public sealed class ConnectToModule | ||
| 9 | { | ||
| 10 | private string childId; | ||
| 11 | private string module; | ||
| 12 | private string moduleLanguage; | ||
| 13 | |||
| 14 | /// <summary> | ||
| 15 | /// Creates a new connect to module. | ||
| 16 | /// </summary> | ||
| 17 | /// <param name="childId">Id of the child.</param> | ||
| 18 | /// <param name="module">Id of the module.</param> | ||
| 19 | /// <param name="moduleLanguage">Language of the module.</param> | ||
| 20 | public ConnectToModule(string childId, string module, string moduleLanguage) | ||
| 21 | { | ||
| 22 | this.childId = childId; | ||
| 23 | this.module = module; | ||
| 24 | this.moduleLanguage = moduleLanguage; | ||
| 25 | } | ||
| 26 | |||
| 27 | /// <summary> | ||
| 28 | /// Gets the id of the child. | ||
| 29 | /// </summary> | ||
| 30 | /// <value>Child identifier.</value> | ||
| 31 | public string ChildId | ||
| 32 | { | ||
| 33 | get { return this.childId; } | ||
| 34 | } | ||
| 35 | |||
| 36 | /// <summary> | ||
| 37 | /// Gets the id of the module. | ||
| 38 | /// </summary> | ||
| 39 | /// <value>The id of the module.</value> | ||
| 40 | public string Module | ||
| 41 | { | ||
| 42 | get { return this.module; } | ||
| 43 | } | ||
| 44 | |||
| 45 | /// <summary> | ||
| 46 | /// Gets the language of the module. | ||
| 47 | /// </summary> | ||
| 48 | /// <value>The language of the module.</value> | ||
| 49 | public string ModuleLanguage | ||
| 50 | { | ||
| 51 | get { return this.moduleLanguage; } | ||
| 52 | } | ||
| 53 | } | ||
| 54 | } | ||
diff --git a/src/WixToolset.Core/Link/ConnectToModuleCollection.cs b/src/WixToolset.Core/Link/ConnectToModuleCollection.cs new file mode 100644 index 00000000..6595487f --- /dev/null +++ b/src/WixToolset.Core/Link/ConnectToModuleCollection.cs | |||
| @@ -0,0 +1,92 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixToolset.Link | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections; | ||
| 7 | |||
| 8 | /// <summary> | ||
| 9 | /// Hash collection of connect to module objects. | ||
| 10 | /// </summary> | ||
| 11 | public sealed class ConnectToModuleCollection : ICollection | ||
| 12 | { | ||
| 13 | private Hashtable collection; | ||
| 14 | |||
| 15 | /// <summary> | ||
| 16 | /// Instantiate a new ConnectToModuleCollection class. | ||
| 17 | /// </summary> | ||
| 18 | public ConnectToModuleCollection() | ||
| 19 | { | ||
| 20 | this.collection = new Hashtable(); | ||
| 21 | } | ||
| 22 | |||
| 23 | /// <summary> | ||
| 24 | /// Gets the number of elements actually contained in the ConnectToModuleCollection. | ||
| 25 | /// </summary> | ||
| 26 | /// <value>The number of elements actually contained in the ConnectToModuleCollection.</value> | ||
| 27 | public int Count | ||
| 28 | { | ||
| 29 | get { return this.collection.Count; } | ||
| 30 | } | ||
| 31 | |||
| 32 | /// <summary> | ||
| 33 | /// Gets a value indicating whether access to the ConnectToModuleCollection is synchronized (thread-safe). | ||
| 34 | /// </summary> | ||
| 35 | /// <value>true if access to the ConnectToModuleCollection is synchronized (thread-safe); otherwise, false. The default is false.</value> | ||
| 36 | public bool IsSynchronized | ||
| 37 | { | ||
| 38 | get { return this.collection.IsSynchronized; } | ||
| 39 | } | ||
| 40 | |||
| 41 | /// <summary> | ||
| 42 | /// Gets an object that can be used to synchronize access to the ConnectToModuleCollection. | ||
| 43 | /// </summary> | ||
| 44 | /// <value>An object that can be used to synchronize access to the ConnectToModuleCollection.</value> | ||
| 45 | public object SyncRoot | ||
| 46 | { | ||
| 47 | get { return this.collection.SyncRoot; } | ||
| 48 | } | ||
| 49 | |||
| 50 | /// <summary> | ||
| 51 | /// Gets a module connection by child id. | ||
| 52 | /// </summary> | ||
| 53 | /// <param name="childId">Identifier of child to locate.</param> | ||
| 54 | public ConnectToModule this[string childId] | ||
| 55 | { | ||
| 56 | get { return (ConnectToModule)this.collection[childId]; } | ||
| 57 | } | ||
| 58 | |||
| 59 | /// <summary> | ||
| 60 | /// Adds a module connection to the collection. | ||
| 61 | /// </summary> | ||
| 62 | /// <param name="connection">Module connection to add.</param> | ||
| 63 | public void Add(ConnectToModule connection) | ||
| 64 | { | ||
| 65 | if (null == connection) | ||
| 66 | { | ||
| 67 | throw new ArgumentNullException("connection"); | ||
| 68 | } | ||
| 69 | |||
| 70 | this.collection.Add(connection.ChildId, connection); | ||
| 71 | } | ||
| 72 | |||
| 73 | /// <summary> | ||
| 74 | /// Copies the entire ConnectToModuleCollection to a compatible one-dimensional Array, starting at the specified index of the target array. | ||
| 75 | /// </summary> | ||
| 76 | /// <param name="array">The one-dimensional Array that is the destination of the elements copied from this ConnectToModuleCollection. The Array must have zero-based indexing.</param> | ||
| 77 | /// <param name="index">The zero-based index in array at which copying begins.</param> | ||
| 78 | public void CopyTo(System.Array array, int index) | ||
| 79 | { | ||
| 80 | this.collection.Keys.CopyTo(array, index); | ||
| 81 | } | ||
| 82 | |||
| 83 | /// <summary> | ||
| 84 | /// Returns an enumerator for the entire ConnectToModuleCollection. | ||
| 85 | /// </summary> | ||
| 86 | /// <returns>An IEnumerator for the entire ConnectToModuleCollection.</returns> | ||
| 87 | public IEnumerator GetEnumerator() | ||
| 88 | { | ||
| 89 | return this.collection.Keys.GetEnumerator(); | ||
| 90 | } | ||
| 91 | } | ||
| 92 | } | ||
diff --git a/src/WixToolset.Core/Link/FindEntrySectionAndLoadSymbolsCommand.cs b/src/WixToolset.Core/Link/FindEntrySectionAndLoadSymbolsCommand.cs new file mode 100644 index 00000000..effb06e4 --- /dev/null +++ b/src/WixToolset.Core/Link/FindEntrySectionAndLoadSymbolsCommand.cs | |||
| @@ -0,0 +1,109 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixToolset.Link | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Linq; | ||
| 8 | using WixToolset.Data; | ||
| 9 | |||
| 10 | internal class FindEntrySectionAndLoadSymbolsCommand : ICommand | ||
| 11 | { | ||
| 12 | private IEnumerable<Section> sections; | ||
| 13 | |||
| 14 | public FindEntrySectionAndLoadSymbolsCommand(IEnumerable<Section> sections) | ||
| 15 | { | ||
| 16 | this.sections = sections; | ||
| 17 | } | ||
| 18 | |||
| 19 | /// <summary> | ||
| 20 | /// Sets the expected entry output type, based on output file extension provided to the linker. | ||
| 21 | /// </summary> | ||
| 22 | public OutputType ExpectedOutputType { private get; set; } | ||
| 23 | |||
| 24 | /// <summary> | ||
| 25 | /// Gets the located entry section after the command is executed. | ||
| 26 | /// </summary> | ||
| 27 | public Section EntrySection { get; private set; } | ||
| 28 | |||
| 29 | /// <summary> | ||
| 30 | /// Gets the collection of loaded symbols. | ||
| 31 | /// </summary> | ||
| 32 | public IDictionary<string, Symbol> Symbols { get; private set; } | ||
| 33 | |||
| 34 | public IEnumerable<Symbol> PossiblyConflictingSymbols { get; private set; } | ||
| 35 | |||
| 36 | public void Execute() | ||
| 37 | { | ||
| 38 | Dictionary<string, Symbol> symbols = new Dictionary<string, Symbol>(); | ||
| 39 | HashSet<Symbol> possibleConflicts = new HashSet<Symbol>(); | ||
| 40 | |||
| 41 | SectionType expectedEntrySectionType; | ||
| 42 | if (!Enum.TryParse<SectionType>(this.ExpectedOutputType.ToString(), out expectedEntrySectionType)) | ||
| 43 | { | ||
| 44 | expectedEntrySectionType = SectionType.Unknown; | ||
| 45 | } | ||
| 46 | |||
| 47 | foreach (Section section in this.sections) | ||
| 48 | { | ||
| 49 | // Try to find the one and only entry section. | ||
| 50 | if (SectionType.Product == section.Type || SectionType.Module == section.Type || SectionType.PatchCreation == section.Type || SectionType.Patch == section.Type || SectionType.Bundle == section.Type) | ||
| 51 | { | ||
| 52 | if (SectionType.Unknown != expectedEntrySectionType && section.Type != expectedEntrySectionType) | ||
| 53 | { | ||
| 54 | string outputExtension = Output.GetExtension(this.ExpectedOutputType); | ||
| 55 | Messaging.Instance.OnMessage(WixWarnings.UnexpectedEntrySection(section.SourceLineNumbers, section.Type.ToString(), expectedEntrySectionType.ToString(), outputExtension)); | ||
| 56 | } | ||
| 57 | |||
| 58 | if (null == this.EntrySection) | ||
| 59 | { | ||
| 60 | this.EntrySection = section; | ||
| 61 | } | ||
| 62 | else | ||
| 63 | { | ||
| 64 | Messaging.Instance.OnMessage(WixErrors.MultipleEntrySections(this.EntrySection.SourceLineNumbers, this.EntrySection.Id, section.Id)); | ||
| 65 | Messaging.Instance.OnMessage(WixErrors.MultipleEntrySections2(section.SourceLineNumbers)); | ||
| 66 | } | ||
| 67 | } | ||
| 68 | |||
| 69 | // Load all the symbols from the section's tables that create symbols. | ||
| 70 | foreach (Table table in section.Tables.Where(t => t.Definition.CreateSymbols)) | ||
| 71 | { | ||
| 72 | foreach (Row row in table.Rows) | ||
| 73 | { | ||
| 74 | Symbol symbol = new Symbol(row); | ||
| 75 | |||
| 76 | Symbol existingSymbol; | ||
| 77 | if (!symbols.TryGetValue(symbol.Name, out existingSymbol)) | ||
| 78 | { | ||
| 79 | symbols.Add(symbol.Name, symbol); | ||
| 80 | } | ||
| 81 | else // uh-oh, duplicate symbols. | ||
| 82 | { | ||
| 83 | // If the duplicate symbols are both private directories, there is a chance that they | ||
| 84 | // point to identical rows. Identical directory rows are redundant and will not cause | ||
| 85 | // conflicts. | ||
| 86 | if (AccessModifier.Private == existingSymbol.Access && AccessModifier.Private == symbol.Access && | ||
| 87 | "Directory" == existingSymbol.Row.Table.Name && existingSymbol.Row.IsIdentical(symbol.Row)) | ||
| 88 | { | ||
| 89 | // Ensure identical symbol's row is marked redundant to ensure (should the row be | ||
| 90 | // referenced into the final output) it will not add duplicate primary keys during | ||
| 91 | // the .IDT importing. | ||
| 92 | symbol.Row.Redundant = true; | ||
| 93 | existingSymbol.AddRedundant(symbol); | ||
| 94 | } | ||
| 95 | else | ||
| 96 | { | ||
| 97 | existingSymbol.AddPossibleConflict(symbol); | ||
| 98 | possibleConflicts.Add(existingSymbol); | ||
| 99 | } | ||
| 100 | } | ||
| 101 | } | ||
| 102 | } | ||
| 103 | } | ||
| 104 | |||
| 105 | this.Symbols = symbols; | ||
| 106 | this.PossiblyConflictingSymbols = possibleConflicts; | ||
| 107 | } | ||
| 108 | } | ||
| 109 | } | ||
diff --git a/src/WixToolset.Core/Link/ReportConflictingSymbolsCommand.cs b/src/WixToolset.Core/Link/ReportConflictingSymbolsCommand.cs new file mode 100644 index 00000000..39c3a5c2 --- /dev/null +++ b/src/WixToolset.Core/Link/ReportConflictingSymbolsCommand.cs | |||
| @@ -0,0 +1,49 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixToolset.Link | ||
| 4 | { | ||
| 5 | using System.Collections.Generic; | ||
| 6 | using System.Linq; | ||
| 7 | using WixToolset.Data; | ||
| 8 | |||
| 9 | public class ReportConflictingSymbolsCommand : ICommand | ||
| 10 | { | ||
| 11 | private IEnumerable<Symbol> possibleConflicts; | ||
| 12 | private IEnumerable<Section> resolvedSections; | ||
| 13 | |||
| 14 | public ReportConflictingSymbolsCommand(IEnumerable<Symbol> possibleConflicts, IEnumerable<Section> resolvedSections) | ||
| 15 | { | ||
| 16 | this.possibleConflicts = possibleConflicts; | ||
| 17 | this.resolvedSections = resolvedSections; | ||
| 18 | } | ||
| 19 | |||
| 20 | public void Execute() | ||
| 21 | { | ||
| 22 | // Do a quick check if there are any possibly conflicting symbols that don't come from tables that allow | ||
| 23 | // overriding. Hopefully the symbols with possible conflicts list is usually very short list (empty should | ||
| 24 | // be the most common). If we find any matches, we'll do a more costly check to see if the possible conflicting | ||
| 25 | // symbols are in sections we actually referenced. From the resulting set, show an error for each duplicate | ||
| 26 | // (aka: conflicting) symbol. This should catch any rows with colliding primary keys (since symbols are based | ||
| 27 | // on the primary keys of rows). | ||
| 28 | List<Symbol> illegalDuplicates = possibleConflicts.Where(s => "WixAction" != s.Row.Table.Name && "WixVariable" != s.Row.Table.Name).ToList(); | ||
| 29 | if (0 < illegalDuplicates.Count) | ||
| 30 | { | ||
| 31 | HashSet<Section> referencedSections = new HashSet<Section>(resolvedSections); | ||
| 32 | foreach (Symbol referencedDuplicateSymbol in illegalDuplicates.Where(s => referencedSections.Contains(s.Section))) | ||
| 33 | { | ||
| 34 | List<Symbol> actuallyReferencedDuplicateSymbols = referencedDuplicateSymbol.PossiblyConflictingSymbols.Where(s => referencedSections.Contains(s.Section)).ToList(); | ||
| 35 | |||
| 36 | if (actuallyReferencedDuplicateSymbols.Any()) | ||
| 37 | { | ||
| 38 | Messaging.Instance.OnMessage(WixErrors.DuplicateSymbol(referencedDuplicateSymbol.Row.SourceLineNumbers, referencedDuplicateSymbol.Name)); | ||
| 39 | |||
| 40 | foreach (Symbol duplicate in actuallyReferencedDuplicateSymbols) | ||
| 41 | { | ||
| 42 | Messaging.Instance.OnMessage(WixErrors.DuplicateSymbol2(duplicate.Row.SourceLineNumbers)); | ||
| 43 | } | ||
| 44 | } | ||
| 45 | } | ||
| 46 | } | ||
| 47 | } | ||
| 48 | } | ||
| 49 | } | ||
diff --git a/src/WixToolset.Core/Link/ResolveReferencesCommand.cs b/src/WixToolset.Core/Link/ResolveReferencesCommand.cs new file mode 100644 index 00000000..5a985f3f --- /dev/null +++ b/src/WixToolset.Core/Link/ResolveReferencesCommand.cs | |||
| @@ -0,0 +1,178 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixToolset.Link | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Diagnostics; | ||
| 8 | using System.Linq; | ||
| 9 | using WixToolset.Data; | ||
| 10 | using WixToolset.Data.Rows; | ||
| 11 | |||
| 12 | /// <summary> | ||
| 13 | /// Resolves all the simple references in a section. | ||
| 14 | /// </summary> | ||
| 15 | internal class ResolveReferencesCommand : ICommand | ||
| 16 | { | ||
| 17 | private Section entrySection; | ||
| 18 | private IDictionary<string, Symbol> symbols; | ||
| 19 | private HashSet<Symbol> referencedSymbols; | ||
| 20 | private HashSet<Section> resolvedSections; | ||
| 21 | |||
| 22 | public ResolveReferencesCommand(Section entrySection, IDictionary<string, Symbol> symbols) | ||
| 23 | { | ||
| 24 | this.entrySection = entrySection; | ||
| 25 | this.symbols = symbols; | ||
| 26 | } | ||
| 27 | |||
| 28 | public bool BuildingMergeModule { private get; set; } | ||
| 29 | |||
| 30 | public IEnumerable<Symbol> ReferencedSymbols { get { return this.referencedSymbols; } } | ||
| 31 | |||
| 32 | public IEnumerable<Section> ResolvedSections { get { return this.resolvedSections; } } | ||
| 33 | |||
| 34 | /// <summary> | ||
| 35 | /// Resolves all the simple references in a section. | ||
| 36 | /// </summary> | ||
| 37 | public void Execute() | ||
| 38 | { | ||
| 39 | this.resolvedSections = new HashSet<Section>(); | ||
| 40 | this.referencedSymbols = new HashSet<Symbol>(); | ||
| 41 | |||
| 42 | this.RecursivelyResolveReferences(this.entrySection); | ||
| 43 | } | ||
| 44 | |||
| 45 | /// <summary> | ||
| 46 | /// Recursive helper function to resolve all references of passed in section. | ||
| 47 | /// </summary> | ||
| 48 | /// <param name="section">Section with references to resolve.</param> | ||
| 49 | /// <remarks>Note: recursive function.</remarks> | ||
| 50 | private void RecursivelyResolveReferences(Section section) | ||
| 51 | { | ||
| 52 | // If we already resolved this section, move on to the next. | ||
| 53 | if (!this.resolvedSections.Add(section)) | ||
| 54 | { | ||
| 55 | return; | ||
| 56 | } | ||
| 57 | |||
| 58 | // Process all of the references contained in this section using the collection of | ||
| 59 | // symbols provided. Then recursively call this method to process the | ||
| 60 | // located symbol's section. All in all this is a very simple depth-first | ||
| 61 | // search of the references per-section. | ||
| 62 | Table wixSimpleReferenceTable; | ||
| 63 | if (section.Tables.TryGetTable("WixSimpleReference", out wixSimpleReferenceTable)) | ||
| 64 | { | ||
| 65 | foreach (WixSimpleReferenceRow wixSimpleReferenceRow in wixSimpleReferenceTable.Rows) | ||
| 66 | { | ||
| 67 | Debug.Assert(wixSimpleReferenceRow.Section == section); | ||
| 68 | |||
| 69 | // If we're building a Merge Module, ignore all references to the Media table | ||
| 70 | // because Merge Modules don't have Media tables. | ||
| 71 | if (this.BuildingMergeModule && "Media" == wixSimpleReferenceRow.TableName) | ||
| 72 | { | ||
| 73 | continue; | ||
| 74 | } | ||
| 75 | |||
| 76 | Symbol symbol; | ||
| 77 | if (!this.symbols.TryGetValue(wixSimpleReferenceRow.SymbolicName, out symbol)) | ||
| 78 | { | ||
| 79 | Messaging.Instance.OnMessage(WixErrors.UnresolvedReference(wixSimpleReferenceRow.SourceLineNumbers, wixSimpleReferenceRow.SymbolicName)); | ||
| 80 | } | ||
| 81 | else // see if the symbol (and any of its duplicates) are appropriately accessible. | ||
| 82 | { | ||
| 83 | IList<Symbol> accessible = DetermineAccessibleSymbols(section, symbol); | ||
| 84 | if (!accessible.Any()) | ||
| 85 | { | ||
| 86 | Messaging.Instance.OnMessage(WixErrors.UnresolvedReference(wixSimpleReferenceRow.SourceLineNumbers, wixSimpleReferenceRow.SymbolicName, symbol.Access)); | ||
| 87 | } | ||
| 88 | else if (1 == accessible.Count) | ||
| 89 | { | ||
| 90 | Symbol accessibleSymbol = accessible[0]; | ||
| 91 | this.referencedSymbols.Add(accessibleSymbol); | ||
| 92 | |||
| 93 | if (null != accessibleSymbol.Section) | ||
| 94 | { | ||
| 95 | RecursivelyResolveReferences(accessibleSymbol.Section); | ||
| 96 | } | ||
| 97 | } | ||
| 98 | else // display errors for the duplicate symbols. | ||
| 99 | { | ||
| 100 | Symbol accessibleSymbol = accessible[0]; | ||
| 101 | string referencingSourceLineNumber = wixSimpleReferenceRow.SourceLineNumbers.ToString(); | ||
| 102 | if (String.IsNullOrEmpty(referencingSourceLineNumber)) | ||
| 103 | { | ||
| 104 | Messaging.Instance.OnMessage(WixErrors.DuplicateSymbol(accessibleSymbol.Row.SourceLineNumbers, accessibleSymbol.Name)); | ||
| 105 | } | ||
| 106 | else | ||
| 107 | { | ||
| 108 | Messaging.Instance.OnMessage(WixErrors.DuplicateSymbol(accessibleSymbol.Row.SourceLineNumbers, accessibleSymbol.Name, referencingSourceLineNumber)); | ||
| 109 | } | ||
| 110 | |||
| 111 | foreach (Symbol accessibleDuplicate in accessible.Skip(1)) | ||
| 112 | { | ||
| 113 | Messaging.Instance.OnMessage(WixErrors.DuplicateSymbol2(accessibleDuplicate.Row.SourceLineNumbers)); | ||
| 114 | } | ||
| 115 | } | ||
| 116 | } | ||
| 117 | } | ||
| 118 | } | ||
| 119 | } | ||
| 120 | |||
| 121 | /// <summary> | ||
| 122 | /// Determine if the symbol and any of its duplicates are accessbile by referencing section. | ||
| 123 | /// </summary> | ||
| 124 | /// <param name="referencingSection">Section referencing the symbol.</param> | ||
| 125 | /// <param name="symbol">Symbol being referenced.</param> | ||
| 126 | /// <returns>List of symbols accessible by referencing section.</returns> | ||
| 127 | private IList<Symbol> DetermineAccessibleSymbols(Section referencingSection, Symbol symbol) | ||
| 128 | { | ||
| 129 | List<Symbol> symbols = new List<Symbol>(); | ||
| 130 | |||
| 131 | if (AccessibleSymbol(referencingSection, symbol)) | ||
| 132 | { | ||
| 133 | symbols.Add(symbol); | ||
| 134 | } | ||
| 135 | |||
| 136 | foreach (Symbol dupe in symbol.PossiblyConflictingSymbols) | ||
| 137 | { | ||
| 138 | if (AccessibleSymbol(referencingSection, dupe)) | ||
| 139 | { | ||
| 140 | symbols.Add(dupe); | ||
| 141 | } | ||
| 142 | } | ||
| 143 | |||
| 144 | foreach (Symbol dupe in symbol.RedundantSymbols) | ||
| 145 | { | ||
| 146 | if (AccessibleSymbol(referencingSection, dupe)) | ||
| 147 | { | ||
| 148 | symbols.Add(dupe); | ||
| 149 | } | ||
| 150 | } | ||
| 151 | |||
| 152 | return symbols; | ||
| 153 | } | ||
| 154 | |||
| 155 | /// <summary> | ||
| 156 | /// Determine if a single symbol is accessible by the referencing section. | ||
| 157 | /// </summary> | ||
| 158 | /// <param name="referencingSection">Section referencing the symbol.</param> | ||
| 159 | /// <param name="symbol">Symbol being referenced.</param> | ||
| 160 | /// <returns>True if symbol is accessible.</returns> | ||
| 161 | private bool AccessibleSymbol(Section referencingSection, Symbol symbol) | ||
| 162 | { | ||
| 163 | switch (symbol.Access) | ||
| 164 | { | ||
| 165 | case AccessModifier.Public: | ||
| 166 | return true; | ||
| 167 | case AccessModifier.Internal: | ||
| 168 | return symbol.Row.Section.IntermediateId.Equals(referencingSection.IntermediateId) || (null != symbol.Row.Section.LibraryId && symbol.Row.Section.LibraryId.Equals(referencingSection.LibraryId)); | ||
| 169 | case AccessModifier.Protected: | ||
| 170 | return symbol.Row.Section.IntermediateId.Equals(referencingSection.IntermediateId); | ||
| 171 | case AccessModifier.Private: | ||
| 172 | return referencingSection == symbol.Section; | ||
| 173 | default: | ||
| 174 | throw new InvalidOperationException(); | ||
| 175 | } | ||
| 176 | } | ||
| 177 | } | ||
| 178 | } | ||
diff --git a/src/WixToolset.Core/Link/WixGroupingOrdering.cs b/src/WixToolset.Core/Link/WixGroupingOrdering.cs new file mode 100644 index 00000000..fc0ce43b --- /dev/null +++ b/src/WixToolset.Core/Link/WixGroupingOrdering.cs | |||
| @@ -0,0 +1,726 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixToolset.Link | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections; | ||
| 7 | using System.Collections.ObjectModel; | ||
| 8 | using System.Collections.Generic; | ||
| 9 | using System.Diagnostics; | ||
| 10 | using System.Globalization; | ||
| 11 | using System.Linq; | ||
| 12 | using System.Text; | ||
| 13 | using WixToolset.Extensibility; | ||
| 14 | using WixToolset.Data; | ||
| 15 | |||
| 16 | /// <summary> | ||
| 17 | /// Grouping and Ordering class of the WiX toolset. | ||
| 18 | /// </summary> | ||
| 19 | internal sealed class WixGroupingOrdering : IMessageHandler | ||
| 20 | { | ||
| 21 | private Output output; | ||
| 22 | private IMessageHandler messageHandler; | ||
| 23 | private List<string> groupTypes; | ||
| 24 | private List<string> itemTypes; | ||
| 25 | private ItemCollection items; | ||
| 26 | private List<int> rowsUsed; | ||
| 27 | private bool loaded; | ||
| 28 | private bool encounteredError; | ||
| 29 | |||
| 30 | /// <summary> | ||
| 31 | /// Creates a WixGroupingOrdering object. | ||
| 32 | /// </summary> | ||
| 33 | /// <param name="output">Output from which to read the group and order information.</param> | ||
| 34 | /// <param name="messageHandler">Handler for any error messages.</param> | ||
| 35 | /// <param name="groupTypes">Group types to include.</param> | ||
| 36 | /// <param name="itemTypes">Item types to include.</param> | ||
| 37 | public WixGroupingOrdering(Output output, IMessageHandler messageHandler) | ||
| 38 | { | ||
| 39 | this.output = output; | ||
| 40 | this.messageHandler = messageHandler; | ||
| 41 | |||
| 42 | this.rowsUsed = new List<int>(); | ||
| 43 | this.loaded = false; | ||
| 44 | this.encounteredError = false; | ||
| 45 | } | ||
| 46 | |||
| 47 | /// <summary> | ||
| 48 | /// Switches a WixGroupingOrdering object to operate on a new set of groups/items. | ||
| 49 | /// </summary> | ||
| 50 | /// <param name="groupTypes">Group types to include.</param> | ||
| 51 | /// <param name="itemTypes">Item types to include.</param> | ||
| 52 | public void UseTypes(IEnumerable<string> groupTypes, IEnumerable<string> itemTypes) | ||
| 53 | { | ||
| 54 | this.groupTypes = new List<string>(groupTypes); | ||
| 55 | this.itemTypes = new List<string>(itemTypes); | ||
| 56 | |||
| 57 | this.items = new ItemCollection(); | ||
| 58 | this.loaded = false; | ||
| 59 | } | ||
| 60 | |||
| 61 | /// <summary> | ||
| 62 | /// Sends a message to the message handler if there is one. | ||
| 63 | /// </summary> | ||
| 64 | /// <param name="mea">Message event arguments.</param> | ||
| 65 | public void OnMessage(MessageEventArgs e) | ||
| 66 | { | ||
| 67 | WixErrorEventArgs errorEventArgs = e as WixErrorEventArgs; | ||
| 68 | |||
| 69 | if (null != errorEventArgs || MessageLevel.Error == e.Level) | ||
| 70 | { | ||
| 71 | this.encounteredError = true; | ||
| 72 | } | ||
| 73 | |||
| 74 | if (null != this.messageHandler) | ||
| 75 | { | ||
| 76 | this.messageHandler.OnMessage(e); | ||
| 77 | } | ||
| 78 | else if (null != errorEventArgs) | ||
| 79 | { | ||
| 80 | throw new WixException(errorEventArgs); | ||
| 81 | } | ||
| 82 | } | ||
| 83 | |||
| 84 | /// <summary> | ||
| 85 | /// Finds all nested items under a parent group and creates new WixGroup data for them. | ||
| 86 | /// </summary> | ||
| 87 | /// <param name="parentType">The group type for the parent group to flatten.</param> | ||
| 88 | /// <param name="parentId">The identifier of the parent group to flatten.</param> | ||
| 89 | /// <param name="removeUsedRows">Whether to remove used group rows before returning.</param> | ||
| 90 | public void FlattenAndRewriteRows(string parentType, string parentId, bool removeUsedRows) | ||
| 91 | { | ||
| 92 | Debug.Assert(this.groupTypes.Contains(parentType)); | ||
| 93 | |||
| 94 | List<Item> orderedItems; | ||
| 95 | this.CreateOrderedList(parentType, parentId, out orderedItems); | ||
| 96 | if (this.encounteredError) | ||
| 97 | { | ||
| 98 | return; | ||
| 99 | } | ||
| 100 | |||
| 101 | this.CreateNewGroupRows(parentType, parentId, orderedItems); | ||
| 102 | |||
| 103 | if (removeUsedRows) | ||
| 104 | { | ||
| 105 | this.RemoveUsedGroupRows(); | ||
| 106 | } | ||
| 107 | } | ||
| 108 | |||
| 109 | /// <summary> | ||
| 110 | /// Finds all items under a parent group type and creates new WixGroup data for them. | ||
| 111 | /// </summary> | ||
| 112 | /// <param name="parentType">The type of the parent group to flatten.</param> | ||
| 113 | /// <param name="removeUsedRows">Whether to remove used group rows before returning.</param> | ||
| 114 | public void FlattenAndRewriteGroups(string parentType, bool removeUsedRows) | ||
| 115 | { | ||
| 116 | Debug.Assert(this.groupTypes.Contains(parentType)); | ||
| 117 | |||
| 118 | this.LoadFlattenOrderGroups(); | ||
| 119 | if (this.encounteredError) | ||
| 120 | { | ||
| 121 | return; | ||
| 122 | } | ||
| 123 | |||
| 124 | foreach (Item item in this.items) | ||
| 125 | { | ||
| 126 | if (parentType == item.Type) | ||
| 127 | { | ||
| 128 | List<Item> orderedItems; | ||
| 129 | this.CreateOrderedList(item.Type, item.Id, out orderedItems); | ||
| 130 | this.CreateNewGroupRows(item.Type, item.Id, orderedItems); | ||
| 131 | } | ||
| 132 | } | ||
| 133 | |||
| 134 | if (removeUsedRows) | ||
| 135 | { | ||
| 136 | this.RemoveUsedGroupRows(); | ||
| 137 | } | ||
| 138 | } | ||
| 139 | |||
| 140 | |||
| 141 | /// <summary> | ||
| 142 | /// Creates a flattened and ordered list of items for the given parent group. | ||
| 143 | /// </summary> | ||
| 144 | /// <param name="parentType">The group type for the parent group to flatten.</param> | ||
| 145 | /// <param name="parentId">The identifier of the parent group to flatten.</param> | ||
| 146 | /// <param name="orderedItems">The returned list of ordered items.</param> | ||
| 147 | private void CreateOrderedList(string parentType, string parentId, out List<Item> orderedItems) | ||
| 148 | { | ||
| 149 | orderedItems = null; | ||
| 150 | |||
| 151 | this.LoadFlattenOrderGroups(); | ||
| 152 | if (this.encounteredError) | ||
| 153 | { | ||
| 154 | return; | ||
| 155 | } | ||
| 156 | |||
| 157 | Item parentItem; | ||
| 158 | if (!this.items.TryGetValue(parentType, parentId, out parentItem)) | ||
| 159 | { | ||
| 160 | this.OnMessage(WixErrors.IdentifierNotFound(parentType, parentId)); | ||
| 161 | return; | ||
| 162 | } | ||
| 163 | |||
| 164 | orderedItems = new List<Item>(parentItem.ChildItems); | ||
| 165 | orderedItems.Sort(new Item.AfterItemComparer()); | ||
| 166 | } | ||
| 167 | |||
| 168 | /// <summary> | ||
| 169 | /// Removes rows from WixGroup that have been used by this object. | ||
| 170 | /// </summary> | ||
| 171 | public void RemoveUsedGroupRows() | ||
| 172 | { | ||
| 173 | List<int> sortedIndexes = this.rowsUsed.Distinct().OrderByDescending(i => i).ToList(); | ||
| 174 | |||
| 175 | Table wixGroupTable = this.output.Tables["WixGroup"]; | ||
| 176 | Debug.Assert(null != wixGroupTable); | ||
| 177 | Debug.Assert(sortedIndexes[0] < wixGroupTable.Rows.Count); | ||
| 178 | |||
| 179 | foreach (int rowIndex in sortedIndexes) | ||
| 180 | { | ||
| 181 | wixGroupTable.Rows.RemoveAt(rowIndex); | ||
| 182 | } | ||
| 183 | } | ||
| 184 | |||
| 185 | /// <summary> | ||
| 186 | /// Creates new WixGroup rows for a list of items. | ||
| 187 | /// </summary> | ||
| 188 | /// <param name="parentType">The group type for the parent group in the new rows.</param> | ||
| 189 | /// <param name="parentId">The identifier of the parent group in the new rows.</param> | ||
| 190 | /// <param name="orderedItems">The list of new items.</param> | ||
| 191 | private void CreateNewGroupRows(string parentType, string parentId, List<Item> orderedItems) | ||
| 192 | { | ||
| 193 | // TODO: MSIs don't guarantee that rows stay in the same order, and technically, neither | ||
| 194 | // does WiX (although they do, currently). We probably want to "upgrade" this to a new | ||
| 195 | // table that includes a sequence number, and then change the code that uses ordered | ||
| 196 | // groups to read from that table instead. | ||
| 197 | Table wixGroupTable = this.output.Tables["WixGroup"]; | ||
| 198 | Debug.Assert(null != wixGroupTable); | ||
| 199 | |||
| 200 | foreach (Item item in orderedItems) | ||
| 201 | { | ||
| 202 | Row row = wixGroupTable.CreateRow(item.Row.SourceLineNumbers); | ||
| 203 | row[0] = parentId; | ||
| 204 | row[1] = parentType; | ||
| 205 | row[2] = item.Id; | ||
| 206 | row[3] = item.Type; | ||
| 207 | } | ||
| 208 | } | ||
| 209 | |||
| 210 | // Group/Ordering Flattening Logic | ||
| 211 | // | ||
| 212 | // What follows is potentially convoluted logic. Two somewhat orthogonal concepts are in | ||
| 213 | // play: grouping (parent/child relationships) and ordering (before/after relationships). | ||
| 214 | // Dealing with just one or the other is straghtforward. Groups can be flattened | ||
| 215 | // recursively. Ordering can be propagated in either direction. When the ordering also | ||
| 216 | // participates in the grouping constructions, however, things get trickier. For the | ||
| 217 | // purposes of this discussion, we're dealing with "items" and "groups", and an instance | ||
| 218 | // of either of them can be marked as coming "after" some other instance. | ||
| 219 | // | ||
| 220 | // For simple item-to-item ordering, the "after" values simply propagate: if A is after B, | ||
| 221 | // and B is after C, then we can say that A is after *both* B and C. If a group is involved, | ||
| 222 | // it acts as a proxy for all of its included items and any sub-groups. | ||
| 223 | |||
| 224 | /// <summary> | ||
| 225 | /// Internal workhorse for ensuring that group and ordering information has | ||
| 226 | /// been loaded and applied. | ||
| 227 | /// </summary> | ||
| 228 | private void LoadFlattenOrderGroups() | ||
| 229 | { | ||
| 230 | if (!this.loaded) | ||
| 231 | { | ||
| 232 | this.LoadGroups(); | ||
| 233 | this.LoadOrdering(); | ||
| 234 | |||
| 235 | // It would be really nice to have a "find circular after dependencies" | ||
| 236 | // function, but it gets much more complicated because of the way that | ||
| 237 | // the dependencies are propagated across group boundaries. For now, we | ||
| 238 | // just live with the dependency loop detection as we flatten the | ||
| 239 | // dependencies. Group references, however, we can check directly. | ||
| 240 | this.FindCircularGroupReferences(); | ||
| 241 | |||
| 242 | if (!this.encounteredError) | ||
| 243 | { | ||
| 244 | this.FlattenGroups(); | ||
| 245 | this.FlattenOrdering(); | ||
| 246 | } | ||
| 247 | |||
| 248 | this.loaded = true; | ||
| 249 | } | ||
| 250 | } | ||
| 251 | |||
| 252 | /// <summary> | ||
| 253 | /// Loads data from the WixGroup table. | ||
| 254 | /// </summary> | ||
| 255 | private void LoadGroups() | ||
| 256 | { | ||
| 257 | Table wixGroupTable = this.output.Tables["WixGroup"]; | ||
| 258 | if (null == wixGroupTable || 0 == wixGroupTable.Rows.Count) | ||
| 259 | { | ||
| 260 | // TODO: Change message name to make it *not* Bundle specific? | ||
| 261 | this.OnMessage(WixErrors.MissingBundleInformation("WixGroup")); | ||
| 262 | } | ||
| 263 | |||
| 264 | // Collect all of the groups | ||
| 265 | for (int rowIndex = 0; rowIndex < wixGroupTable.Rows.Count; ++rowIndex) | ||
| 266 | { | ||
| 267 | Row row = wixGroupTable.Rows[rowIndex]; | ||
| 268 | string rowParentName = (string)row[0]; | ||
| 269 | string rowParentType = (string)row[1]; | ||
| 270 | string rowChildName = (string)row[2]; | ||
| 271 | string rowChildType = (string)row[3]; | ||
| 272 | |||
| 273 | // If this row specifies a parent or child type that's not in our | ||
| 274 | // lists, we assume it's not a row that we're concerned about. | ||
| 275 | if (!this.groupTypes.Contains(rowParentType) || | ||
| 276 | !this.itemTypes.Contains(rowChildType)) | ||
| 277 | { | ||
| 278 | continue; | ||
| 279 | } | ||
| 280 | |||
| 281 | this.rowsUsed.Add(rowIndex); | ||
| 282 | |||
| 283 | Item parentItem; | ||
| 284 | if (!this.items.TryGetValue(rowParentType, rowParentName, out parentItem)) | ||
| 285 | { | ||
| 286 | parentItem = new Item(row, rowParentType, rowParentName); | ||
| 287 | this.items.Add(parentItem); | ||
| 288 | } | ||
| 289 | |||
| 290 | Item childItem; | ||
| 291 | if (!this.items.TryGetValue(rowChildType, rowChildName, out childItem)) | ||
| 292 | { | ||
| 293 | childItem = new Item(row, rowChildType, rowChildName); | ||
| 294 | this.items.Add(childItem); | ||
| 295 | } | ||
| 296 | |||
| 297 | parentItem.ChildItems.Add(childItem); | ||
| 298 | } | ||
| 299 | } | ||
| 300 | |||
| 301 | /// <summary> | ||
| 302 | /// Flattens group/item information. | ||
| 303 | /// </summary> | ||
| 304 | private void FlattenGroups() | ||
| 305 | { | ||
| 306 | foreach (Item item in this.items) | ||
| 307 | { | ||
| 308 | item.FlattenChildItems(); | ||
| 309 | } | ||
| 310 | } | ||
| 311 | |||
| 312 | /// <summary> | ||
| 313 | /// Finds and reports circular references in the group/item data. | ||
| 314 | /// </summary> | ||
| 315 | private void FindCircularGroupReferences() | ||
| 316 | { | ||
| 317 | ItemCollection itemsInKnownLoops = new ItemCollection(); | ||
| 318 | foreach (Item item in this.items) | ||
| 319 | { | ||
| 320 | if (itemsInKnownLoops.Contains(item)) | ||
| 321 | { | ||
| 322 | continue; | ||
| 323 | } | ||
| 324 | |||
| 325 | ItemCollection itemsSeen = new ItemCollection(); | ||
| 326 | string circularReference; | ||
| 327 | if (this.FindCircularGroupReference(item, item, itemsSeen, out circularReference)) | ||
| 328 | { | ||
| 329 | itemsInKnownLoops.Add(itemsSeen); | ||
| 330 | this.OnMessage(WixErrors.ReferenceLoopDetected(item.Row.SourceLineNumbers, circularReference)); | ||
| 331 | } | ||
| 332 | } | ||
| 333 | } | ||
| 334 | |||
| 335 | /// <summary> | ||
| 336 | /// Recursive worker to find and report circular references in group/item data. | ||
| 337 | /// </summary> | ||
| 338 | /// <param name="checkItem">The sentinal item being checked.</param> | ||
| 339 | /// <param name="currentItem">The current item in the recursion.</param> | ||
| 340 | /// <param name="itemsSeen">A list of all items already visited (for performance).</param> | ||
| 341 | /// <param name="circularReference">A list of items in the current circular reference, if one was found; null otherwise.</param> | ||
| 342 | /// <returns>True if a circular reference was found; false otherwise.</returns> | ||
| 343 | private bool FindCircularGroupReference(Item checkItem, Item currentItem, ItemCollection itemsSeen, out string circularReference) | ||
| 344 | { | ||
| 345 | circularReference = null; | ||
| 346 | foreach (Item subitem in currentItem.ChildItems) | ||
| 347 | { | ||
| 348 | if (checkItem == subitem) | ||
| 349 | { | ||
| 350 | // TODO: Even better would be to include the source lines for each reference! | ||
| 351 | circularReference = String.Format(CultureInfo.InvariantCulture, "{0}:{1} -> {2}:{3}", | ||
| 352 | currentItem.Type, currentItem.Id, subitem.Type, subitem.Id); | ||
| 353 | return true; | ||
| 354 | } | ||
| 355 | |||
| 356 | if (!itemsSeen.Contains(subitem)) | ||
| 357 | { | ||
| 358 | itemsSeen.Add(subitem); | ||
| 359 | if (this.FindCircularGroupReference(checkItem, subitem, itemsSeen, out circularReference)) | ||
| 360 | { | ||
| 361 | // TODO: Even better would be to include the source lines for each reference! | ||
| 362 | circularReference = String.Format(CultureInfo.InvariantCulture, "{0}:{1} -> {2}", | ||
| 363 | currentItem.Type, currentItem.Id, circularReference); | ||
| 364 | return true; | ||
| 365 | } | ||
| 366 | } | ||
| 367 | } | ||
| 368 | |||
| 369 | return false; | ||
| 370 | } | ||
| 371 | |||
| 372 | /// <summary> | ||
| 373 | /// Loads ordering dependency data from the WixOrdering table. | ||
| 374 | /// </summary> | ||
| 375 | private void LoadOrdering() | ||
| 376 | { | ||
| 377 | Table wixOrderingTable = output.Tables["WixOrdering"]; | ||
| 378 | if (null == wixOrderingTable || 0 == wixOrderingTable.Rows.Count) | ||
| 379 | { | ||
| 380 | // TODO: Do we need a message here? | ||
| 381 | return; | ||
| 382 | } | ||
| 383 | |||
| 384 | foreach (Row row in wixOrderingTable.Rows) | ||
| 385 | { | ||
| 386 | string rowItemType = (string)row[0]; | ||
| 387 | string rowItemName = (string)row[1]; | ||
| 388 | string rowDependsOnType = (string)row[2]; | ||
| 389 | string rowDependsOnName = (string)row[3]; | ||
| 390 | |||
| 391 | // If this row specifies some other (unknown) type in either | ||
| 392 | // position, we assume it's not a row that we're concerned about. | ||
| 393 | // For ordering, we allow group and item in either position. | ||
| 394 | if (!(this.groupTypes.Contains(rowItemType) || this.itemTypes.Contains(rowItemType)) || | ||
| 395 | !(this.groupTypes.Contains(rowDependsOnType) || this.itemTypes.Contains(rowDependsOnType))) | ||
| 396 | { | ||
| 397 | continue; | ||
| 398 | } | ||
| 399 | |||
| 400 | Item item = null; | ||
| 401 | Item dependsOn = null; | ||
| 402 | |||
| 403 | if (!this.items.TryGetValue(rowItemType, rowItemName, out item)) | ||
| 404 | { | ||
| 405 | this.OnMessage(WixErrors.IdentifierNotFound(rowItemType, rowItemName)); | ||
| 406 | } | ||
| 407 | |||
| 408 | if (!this.items.TryGetValue(rowDependsOnType, rowDependsOnName, out dependsOn)) | ||
| 409 | { | ||
| 410 | this.OnMessage(WixErrors.IdentifierNotFound(rowDependsOnType, rowDependsOnName)); | ||
| 411 | } | ||
| 412 | |||
| 413 | if (null == item || null == dependsOn) | ||
| 414 | { | ||
| 415 | continue; | ||
| 416 | } | ||
| 417 | |||
| 418 | item.AddAfter(dependsOn, this); | ||
| 419 | } | ||
| 420 | } | ||
| 421 | |||
| 422 | /// <summary> | ||
| 423 | /// Flattens the ordering dependencies in the groups/items. | ||
| 424 | /// </summary> | ||
| 425 | private void FlattenOrdering() | ||
| 426 | { | ||
| 427 | // Because items don't know about their parent groups (and can, in fact, be | ||
| 428 | // in more than one group at a time), we need to pre-propagate the 'afters' | ||
| 429 | // from each parent item to its children before we attempt to flatten the | ||
| 430 | // ordering. | ||
| 431 | foreach (Item item in this.items) | ||
| 432 | { | ||
| 433 | item.PropagateAfterToChildItems(this); | ||
| 434 | } | ||
| 435 | |||
| 436 | foreach (Item item in this.items) | ||
| 437 | { | ||
| 438 | item.FlattenAfters(this); | ||
| 439 | } | ||
| 440 | } | ||
| 441 | |||
| 442 | /// <summary> | ||
| 443 | /// A variant of KeyedCollection that doesn't throw when an item is re-added. | ||
| 444 | /// </summary> | ||
| 445 | /// <typeparam name="TKey">Key type for the collection.</typeparam> | ||
| 446 | /// <typeparam name="TItem">Item type for the colelction.</typeparam> | ||
| 447 | internal abstract class EnhancedKeyCollection<TKey, TItem> : KeyedCollection<TKey, TItem> | ||
| 448 | { | ||
| 449 | new public void Add(TItem item) | ||
| 450 | { | ||
| 451 | if (!this.Contains(item)) | ||
| 452 | { | ||
| 453 | base.Add(item); | ||
| 454 | } | ||
| 455 | } | ||
| 456 | |||
| 457 | public void Add(Collection<TItem> list) | ||
| 458 | { | ||
| 459 | foreach (TItem item in list) | ||
| 460 | { | ||
| 461 | this.Add(item); | ||
| 462 | } | ||
| 463 | } | ||
| 464 | |||
| 465 | public void Remove(Collection<TItem> list) | ||
| 466 | { | ||
| 467 | foreach (TItem item in list) | ||
| 468 | { | ||
| 469 | this.Remove(item); | ||
| 470 | } | ||
| 471 | } | ||
| 472 | |||
| 473 | public bool TryGetValue(TKey key, out TItem item) | ||
| 474 | { | ||
| 475 | // KeyedCollection doesn't implement the TryGetValue() method, but it's | ||
| 476 | // a useful concept. We can't just always pass this to the enclosed | ||
| 477 | // Dictionary, however, because it doesn't always exist! If it does, we | ||
| 478 | // can delegate to it as one would expect. If it doesn't, we have to | ||
| 479 | // implement everything ourselves in terms of Contains(). | ||
| 480 | |||
| 481 | if (null != this.Dictionary) | ||
| 482 | { | ||
| 483 | return this.Dictionary.TryGetValue(key, out item); | ||
| 484 | } | ||
| 485 | |||
| 486 | if (this.Contains(key)) | ||
| 487 | { | ||
| 488 | item = this[key]; | ||
| 489 | return true; | ||
| 490 | } | ||
| 491 | |||
| 492 | item = default(TItem); | ||
| 493 | return false; | ||
| 494 | } | ||
| 495 | |||
| 496 | #if DEBUG | ||
| 497 | // This just makes debugging easier... | ||
| 498 | public override string ToString() | ||
| 499 | { | ||
| 500 | StringBuilder sb = new StringBuilder(); | ||
| 501 | foreach (TItem item in this) | ||
| 502 | { | ||
| 503 | sb.AppendFormat("{0}, ", item); | ||
| 504 | } | ||
| 505 | sb.Length -= 2; | ||
| 506 | return sb.ToString(); | ||
| 507 | } | ||
| 508 | #endif // DEBUG | ||
| 509 | } | ||
| 510 | |||
| 511 | /// <summary> | ||
| 512 | /// A specialized EnhancedKeyCollection, typed to Items. | ||
| 513 | /// </summary> | ||
| 514 | internal class ItemCollection : EnhancedKeyCollection<string, Item> | ||
| 515 | { | ||
| 516 | protected override string GetKeyForItem(Item item) | ||
| 517 | { | ||
| 518 | return item.Key; | ||
| 519 | } | ||
| 520 | |||
| 521 | public bool TryGetValue(string type, string id, out Item item) | ||
| 522 | { | ||
| 523 | return this.TryGetValue(CreateKeyFromTypeId(type, id), out item); | ||
| 524 | } | ||
| 525 | |||
| 526 | public static string CreateKeyFromTypeId(string type, string id) | ||
| 527 | { | ||
| 528 | return String.Format(CultureInfo.InvariantCulture, "{0}_{1}", type, id); | ||
| 529 | } | ||
| 530 | } | ||
| 531 | |||
| 532 | /// <summary> | ||
| 533 | /// An item (or group) in the grouping/ordering engine. | ||
| 534 | /// </summary> | ||
| 535 | /// <remarks>Encapsulates nested group membership and also before/after | ||
| 536 | /// ordering dependencies.</remarks> | ||
| 537 | internal class Item | ||
| 538 | { | ||
| 539 | private ItemCollection afterItems; | ||
| 540 | private ItemCollection beforeItems; // for checking for circular references | ||
| 541 | private bool flattenedAfterItems; | ||
| 542 | |||
| 543 | public Item(Row row, string type, string id) | ||
| 544 | { | ||
| 545 | this.Row = row; | ||
| 546 | this.Type = type; | ||
| 547 | this.Id = id; | ||
| 548 | |||
| 549 | this.Key = ItemCollection.CreateKeyFromTypeId(type, id); | ||
| 550 | |||
| 551 | afterItems = new ItemCollection(); | ||
| 552 | beforeItems = new ItemCollection(); | ||
| 553 | flattenedAfterItems = false; | ||
| 554 | } | ||
| 555 | |||
| 556 | public Row Row { get; private set; } | ||
| 557 | public string Type { get; private set; } | ||
| 558 | public string Id { get; private set; } | ||
| 559 | public string Key { get; private set; } | ||
| 560 | |||
| 561 | #if DEBUG | ||
| 562 | // Makes debugging easier... | ||
| 563 | public override string ToString() | ||
| 564 | { | ||
| 565 | return this.Key; | ||
| 566 | } | ||
| 567 | #endif // DEBUG | ||
| 568 | |||
| 569 | private ItemCollection childItems = new ItemCollection(); | ||
| 570 | public ItemCollection ChildItems { get { return childItems; } } | ||
| 571 | |||
| 572 | /// <summary> | ||
| 573 | /// Removes any nested groups under this item and replaces | ||
| 574 | /// them with their child items. | ||
| 575 | /// </summary> | ||
| 576 | public void FlattenChildItems() | ||
| 577 | { | ||
| 578 | ItemCollection flattenedChildItems = new ItemCollection(); | ||
| 579 | |||
| 580 | foreach (Item childItem in this.ChildItems) | ||
| 581 | { | ||
| 582 | if (0 == childItem.ChildItems.Count) | ||
| 583 | { | ||
| 584 | flattenedChildItems.Add(childItem); | ||
| 585 | } | ||
| 586 | else | ||
| 587 | { | ||
| 588 | childItem.FlattenChildItems(); | ||
| 589 | flattenedChildItems.Add(childItem.ChildItems); | ||
| 590 | } | ||
| 591 | } | ||
| 592 | |||
| 593 | this.ChildItems.Clear(); | ||
| 594 | this.ChildItems.Add(flattenedChildItems); | ||
| 595 | } | ||
| 596 | |||
| 597 | /// <summary> | ||
| 598 | /// Adds a list of items to the 'after' ordering collection. | ||
| 599 | /// </summary> | ||
| 600 | /// <param name="items">List of items to add.</param> | ||
| 601 | /// <param name="messageHandler">Message handler in case a circular ordering reference is found.</param> | ||
| 602 | public void AddAfter(ItemCollection items, IMessageHandler messageHandler) | ||
| 603 | { | ||
| 604 | foreach (Item item in items) | ||
| 605 | { | ||
| 606 | this.AddAfter(item, messageHandler); | ||
| 607 | } | ||
| 608 | } | ||
| 609 | |||
| 610 | /// <summary> | ||
| 611 | /// Adds an item to the 'after' ordering collection. | ||
| 612 | /// </summary> | ||
| 613 | /// <param name="item">Items to add.</param> | ||
| 614 | /// <param name="messageHandler">Message handler in case a circular ordering reference is found.</param> | ||
| 615 | public void AddAfter(Item after, IMessageHandler messageHandler) | ||
| 616 | { | ||
| 617 | if (this.beforeItems.Contains(after)) | ||
| 618 | { | ||
| 619 | // We could try to chain this up (the way that group circular dependencies | ||
| 620 | // are reported), but since we're in the process of flattening, we may already | ||
| 621 | // have lost some distinction between authored and propagated ordering. | ||
| 622 | string circularReference = String.Format(CultureInfo.InvariantCulture, "{0}:{1} -> {2}:{3} -> {0}:{1}", | ||
| 623 | this.Type, this.Id, after.Type, after.Id); | ||
| 624 | messageHandler.OnMessage(WixErrors.OrderingReferenceLoopDetected(after.Row.SourceLineNumbers, circularReference)); | ||
| 625 | return; | ||
| 626 | } | ||
| 627 | |||
| 628 | this.afterItems.Add(after); | ||
| 629 | after.beforeItems.Add(this); | ||
| 630 | } | ||
| 631 | |||
| 632 | /// <summary> | ||
| 633 | /// Propagates 'after' dependencies from an item to its child items. | ||
| 634 | /// </summary> | ||
| 635 | /// <param name="messageHandler">Message handler in case a circular ordering reference is found.</param> | ||
| 636 | /// <remarks>Because items don't know about their parent groups (and can, in fact, be in more | ||
| 637 | /// than one group at a time), we need to propagate the 'afters' from each parent item to its children | ||
| 638 | /// before we attempt to flatten the ordering.</remarks> | ||
| 639 | public void PropagateAfterToChildItems(IMessageHandler messageHandler) | ||
| 640 | { | ||
| 641 | if (this.ShouldItemPropagateChildOrdering()) | ||
| 642 | { | ||
| 643 | foreach (Item childItem in this.childItems) | ||
| 644 | { | ||
| 645 | childItem.AddAfter(this.afterItems, messageHandler); | ||
| 646 | } | ||
| 647 | } | ||
| 648 | } | ||
| 649 | |||
| 650 | /// <summary> | ||
| 651 | /// Flattens the ordering dependency for this item. | ||
| 652 | /// </summary> | ||
| 653 | /// <param name="messageHandler">Message handler in case a circular ordering reference is found.</param> | ||
| 654 | public void FlattenAfters(IMessageHandler messageHandler) | ||
| 655 | { | ||
| 656 | if (this.flattenedAfterItems) | ||
| 657 | { | ||
| 658 | return; | ||
| 659 | } | ||
| 660 | |||
| 661 | this.flattenedAfterItems = true; | ||
| 662 | |||
| 663 | // Ensure that if we're after something (A), and *it's* after something (B), | ||
| 664 | // that we list ourselved as after both (A) *and* (B). | ||
| 665 | ItemCollection nestedAfterItems = new ItemCollection(); | ||
| 666 | |||
| 667 | foreach (Item afterItem in this.afterItems) | ||
| 668 | { | ||
| 669 | afterItem.FlattenAfters(messageHandler); | ||
| 670 | nestedAfterItems.Add(afterItem.afterItems); | ||
| 671 | |||
| 672 | if (afterItem.ShouldItemPropagateChildOrdering()) | ||
| 673 | { | ||
| 674 | // If we are after a group, it really means | ||
| 675 | // we are after all of the group's children. | ||
| 676 | foreach (Item childItem in afterItem.ChildItems) | ||
| 677 | { | ||
| 678 | childItem.FlattenAfters(messageHandler); | ||
| 679 | nestedAfterItems.Add(childItem.afterItems); | ||
| 680 | nestedAfterItems.Add(childItem); | ||
| 681 | } | ||
| 682 | } | ||
| 683 | } | ||
| 684 | |||
| 685 | this.AddAfter(nestedAfterItems, messageHandler); | ||
| 686 | } | ||
| 687 | |||
| 688 | // We *don't* propagate ordering information from Packages or | ||
| 689 | // Containers to their children, because ordering doesn't matter | ||
| 690 | // for them, and a Payload in two Packages (or Containers) can | ||
| 691 | // cause a circular reference to occur. We do, however, need to | ||
| 692 | // track the ordering in the UX Container, because we need the | ||
| 693 | // first payload to be the entrypoint. | ||
| 694 | private bool ShouldItemPropagateChildOrdering() | ||
| 695 | { | ||
| 696 | if (String.Equals("Package", this.Type, StringComparison.Ordinal) || | ||
| 697 | (String.Equals("Container", this.Type, StringComparison.Ordinal) && | ||
| 698 | !String.Equals(Compiler.BurnUXContainerId, this.Id, StringComparison.Ordinal))) | ||
| 699 | { | ||
| 700 | return false; | ||
| 701 | } | ||
| 702 | return true; | ||
| 703 | } | ||
| 704 | |||
| 705 | /// <summary> | ||
| 706 | /// Helper IComparer class to make ordering easier. | ||
| 707 | /// </summary> | ||
| 708 | internal sealed class AfterItemComparer : IComparer<Item> | ||
| 709 | { | ||
| 710 | public int Compare(Item x, Item y) | ||
| 711 | { | ||
| 712 | if (x.afterItems.Contains(y)) | ||
| 713 | { | ||
| 714 | return 1; | ||
| 715 | } | ||
| 716 | else if (y.afterItems.Contains(x)) | ||
| 717 | { | ||
| 718 | return -1; | ||
| 719 | } | ||
| 720 | |||
| 721 | return string.CompareOrdinal(x.Id, y.Id); | ||
| 722 | } | ||
| 723 | } | ||
| 724 | } | ||
| 725 | } | ||
| 726 | } | ||
