diff options
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 | } | ||