// 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. namespace WixToolset.Core { using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Linq; using WixToolset.Core.Link; using WixToolset.Data; using WixToolset.Data.Symbols; using WixToolset.Data.WindowsInstaller; using WixToolset.Extensibility.Data; using WixToolset.Extensibility.Services; /// /// Linker core of the WiX toolset. /// internal class Linker : ILinker { private static readonly string EmptyGuid = Guid.Empty.ToString("B"); private readonly bool sectionIdOnRows; /// /// Creates a linker. /// internal Linker(IServiceProvider serviceProvider) { this.ServiceProvider = serviceProvider; this.Messaging = this.ServiceProvider.GetService(); this.sectionIdOnRows = true; // TODO: what is the correct value for this? } private IServiceProvider ServiceProvider { get; } private IMessaging Messaging { get; } private ILinkContext Context { get; set; } /// /// Gets or sets the path to output unreferenced symbols to. If null or empty, there is no output. /// /// The path to output the xml file. public string UnreferencedSymbolsFile { get; set; } /// /// Gets or sets the option to show pedantic messages. /// /// The option to show pedantic messages. public bool ShowPedanticMessages { get; set; } /// /// Links a collection of sections into an output. /// /// Output intermediate from the linking. public Intermediate Link(ILinkContext context) { this.Context = context; if (this.Context.SymbolDefinitionCreator == null) { this.Context.SymbolDefinitionCreator = this.ServiceProvider.GetService(); } foreach (var extension in this.Context.Extensions) { extension.PreLink(this.Context); } var invalidIntermediates = this.Context.Intermediates.Where(i => !i.HasLevel(Data.IntermediateLevels.Compiled)); if (invalidIntermediates.Any()) { this.Messaging.Write(ErrorMessages.IntermediatesMustBeCompiled(String.Join(", ", invalidIntermediates.Select(i => i.Id)))); } Intermediate intermediate = null; try { var sections = this.Context.Intermediates.SelectMany(i => i.Sections).ToList(); var localizations = this.Context.Intermediates.SelectMany(i => i.Localizations).ToList(); // Add sections from the extensions with data. foreach (var data in this.Context.ExtensionData) { var library = data.GetLibrary(this.Context.SymbolDefinitionCreator); if (library != null) { sections.AddRange(library.Sections); } } //this.activeOutput = null; var multipleFeatureComponents = new Hashtable(); var wixVariables = new Dictionary(); // First find the entry section and while processing all sections load all the symbols from all of the sections. var find = new FindEntrySectionAndLoadSymbolsCommand(this.Messaging, sections, this.Context.ExpectedOutputType); find.Execute(); // Must have found the entry section by now. if (null == find.EntrySection) { if (this.Context.ExpectedOutputType == OutputType.IntermediatePostLink || this.Context.ExpectedOutputType == OutputType.Unknown) { throw new WixException(ErrorMessages.MissingEntrySection()); } else { throw new WixException(ErrorMessages.MissingEntrySection(this.Context.ExpectedOutputType.ToString())); } } // Add the missing standard action and directory symbols. this.LoadStandardSymbols(find.SymbolsByName); // Resolve the symbol references to find the set of sections we care about for linking. // Of course, we start with the entry section (that's how it got its name after all). var resolve = new ResolveReferencesCommand(this.Messaging, find.EntrySection, find.SymbolsByName); resolve.Execute(); if (this.Messaging.EncounteredError) { return null; } // Reset the sections to only those that were resolved then flatten the complex // references that particpate in groups. sections = resolve.ResolvedSections.ToList(); // TODO: consider filtering "localizations" down to only those localizations from // intermediates in the sections. this.FlattenSectionsComplexReferences(sections); if (this.Messaging.EncounteredError) { return null; } // The hard part in linking is processing the complex references. var referencedComponents = new HashSet(); var componentsToFeatures = new ConnectToFeatureCollection(); var featuresToFeatures = new ConnectToFeatureCollection(); var modulesToFeatures = new ConnectToFeatureCollection(); this.ProcessComplexReferences(find.EntrySection, sections, referencedComponents, componentsToFeatures, featuresToFeatures, modulesToFeatures); if (this.Messaging.EncounteredError) { return null; } // Display an error message for Components that were not referenced by a Feature. foreach (var symbolWithSection in resolve.ReferencedSymbolWithSections.Where(s => s.Symbol.Definition.Type == SymbolDefinitionType.Component)) { if (!referencedComponents.Contains(symbolWithSection.Name)) { this.Messaging.Write(ErrorMessages.OrphanedComponent(symbolWithSection.Symbol.SourceLineNumbers, symbolWithSection.Symbol.Id.Id)); } } // Report duplicates that would ultimately end up being primary key collisions. { var reportDupes = new ReportConflictingSymbolsCommand(this.Messaging, find.PossibleConflicts, resolve.ResolvedSections); reportDupes.Execute(); } if (this.Messaging.EncounteredError) { return null; } // resolve the feature to feature connects this.ResolveFeatureToFeatureConnects(featuresToFeatures, find.SymbolsByName); // Create a new section to hold the linked content. Start with the entry section's // metadata. var resolvedSection = new IntermediateSection(find.EntrySection.Id, find.EntrySection.Type); var sectionCount = 0; foreach (var section in sections) { sectionCount++; var sectionId = section.Id; if (null == sectionId && this.sectionIdOnRows) { sectionId = "wix.section." + sectionCount.ToString(CultureInfo.InvariantCulture); } foreach (var symbol in section.Symbols) { if (find.RedundantSymbols.Contains(symbol)) { continue; } var copySymbol = true; // by default, copy symbols. // handle special tables switch (symbol.Definition.Type) { case SymbolDefinitionType.Class: if (SectionType.Product == resolvedSection.Type) { this.ResolveFeatures(symbol, (int)ClassSymbolFields.ComponentRef, (int)ClassSymbolFields.FeatureRef, componentsToFeatures, multipleFeatureComponents); } break; case SymbolDefinitionType.Extension: if (SectionType.Product == resolvedSection.Type) { this.ResolveFeatures(symbol, (int)ExtensionSymbolFields.ComponentRef, (int)ExtensionSymbolFields.FeatureRef, componentsToFeatures, multipleFeatureComponents); } break; case SymbolDefinitionType.Assembly: if (SectionType.Product == resolvedSection.Type) { this.ResolveFeatures(symbol, (int)AssemblySymbolFields.ComponentRef, (int)AssemblySymbolFields.FeatureRef, componentsToFeatures, multipleFeatureComponents); } break; case SymbolDefinitionType.PublishComponent: if (SectionType.Product == resolvedSection.Type) { this.ResolveFeatures(symbol, (int)PublishComponentSymbolFields.ComponentRef, (int)PublishComponentSymbolFields.FeatureRef, componentsToFeatures, multipleFeatureComponents); } break; case SymbolDefinitionType.Shortcut: if (SectionType.Product == resolvedSection.Type) { this.ResolveFeatures(symbol, (int)ShortcutSymbolFields.ComponentRef, (int)ShortcutSymbolFields.Target, componentsToFeatures, multipleFeatureComponents); } break; case SymbolDefinitionType.TypeLib: if (SectionType.Product == resolvedSection.Type) { this.ResolveFeatures(symbol, (int)TypeLibSymbolFields.ComponentRef, (int)TypeLibSymbolFields.FeatureRef, componentsToFeatures, multipleFeatureComponents); } break; case SymbolDefinitionType.WixMerge: if (SectionType.Product == resolvedSection.Type) { this.ResolveFeatures(symbol, -1, (int)WixMergeSymbolFields.FeatureRef, modulesToFeatures, null); } break; case SymbolDefinitionType.WixComplexReference: copySymbol = false; break; case SymbolDefinitionType.WixSimpleReference: copySymbol = false; break; case SymbolDefinitionType.WixVariable: this.AddWixVariable(wixVariables, (WixVariableSymbol)symbol); copySymbol = false; // Do not copy the symbol, it will be added later after all overriding has been handled. break; } if (copySymbol) { resolvedSection.AddSymbol(symbol); } } } // Copy the module to feature connections into the output. foreach (ConnectToFeature connectToFeature in modulesToFeatures) { foreach (var feature in connectToFeature.ConnectFeatures) { resolvedSection.AddSymbol(new WixFeatureModulesSymbol { FeatureRef = feature, WixMergeRef = connectToFeature.ChildId }); } } // Correct the section Id in FeatureComponents table. if (this.sectionIdOnRows) { #if TODO_DO_SYMBOLS_NEED_SECTIONIDS var componentSectionIds = resolvedSection.Symbols.OfType().ToDictionary(c => c.Id.Id, c => c.SectionId); foreach (var featureComponentSymbol in resolvedSection.Symbols.OfType()) { if (componentSectionIds.TryGetValue(featureComponentSymbol.ComponentRef, out var componentSectionId)) { featureComponentSymbol.SectionId = componentSectionId; } } #endif } // Copy the wix variable rows to the output now that all overriding has been accounted for. foreach (var symbol in wixVariables.Values) { resolvedSection.AddSymbol(symbol); } // Bundles have groups of data that must be flattened in a way different from other types. this.FlattenBundleTables(resolvedSection); if (this.Messaging.EncounteredError) { return null; } var collate = new CollateLocalizationsCommand(this.Messaging, localizations); var localizationsByCulture = collate.Execute(); intermediate = new Intermediate(resolvedSection.Id, Data.IntermediateLevels.Linked, new[] { resolvedSection }, localizationsByCulture); } finally { foreach (var extension in this.Context.Extensions) { extension.PostLink(intermediate); } } return this.Messaging.EncounteredError ? null : intermediate; } /// /// Check for colliding values and collect the wix variable rows. /// /// Collection of WixVariableSymbols by id. /// WixVariableSymbol to add, if not overridden. private void AddWixVariable(Dictionary wixVariables, WixVariableSymbol symbol) { var id = symbol.Id.Id; if (wixVariables.TryGetValue(id, out var collidingSymbol)) { if (collidingSymbol.Overridable && !symbol.Overridable) { wixVariables[id] = symbol; } else if (!symbol.Overridable || (collidingSymbol.Overridable && symbol.Overridable)) { this.Messaging.Write(ErrorMessages.WixVariableCollision(symbol.SourceLineNumbers, id)); } } else { wixVariables.Add(id, symbol); } } /// /// Load the standard action and directory symbols. /// /// Collection of symbols. private void LoadStandardSymbols(IDictionary symbolsByName) { foreach (var actionSymbol in WindowsInstallerStandard.StandardActions()) { var symbolWithSection = new SymbolWithSection(null, actionSymbol); // If the action's symbol has not already been defined (i.e. overriden by the user), add it now. if (!symbolsByName.ContainsKey(symbolWithSection.Name)) { symbolsByName.Add(symbolWithSection.Name, symbolWithSection); } } foreach (var directorySymbol in WindowsInstallerStandard.StandardDirectories()) { var symbolWithSection = new SymbolWithSection(null, directorySymbol); // If the directory's symbol has not already been defined (i.e. overriden by the user), add it now. if (!symbolsByName.ContainsKey(symbolWithSection.Name)) { symbolsByName.Add(symbolWithSection.Name, symbolWithSection); } } } /// /// Process the complex references. /// /// Active section to add symbols to. /// Sections that are referenced during the link process. /// Collection of all components referenced by complex reference. /// Component to feature complex references. /// Feature to feature complex references. /// Module to feature complex references. private void ProcessComplexReferences(IntermediateSection resolvedSection, IEnumerable sections, ISet referencedComponents, ConnectToFeatureCollection componentsToFeatures, ConnectToFeatureCollection featuresToFeatures, ConnectToFeatureCollection modulesToFeatures) { var componentsToModules = new Hashtable(); foreach (var section in sections) { // Need ToList since we might want to add symbols while processing. foreach (var wixComplexReferenceRow in section.Symbols.OfType().ToList()) { ConnectToFeature connection; switch (wixComplexReferenceRow.ParentType) { case ComplexReferenceParentType.Feature: switch (wixComplexReferenceRow.ChildType) { case ComplexReferenceChildType.Component: connection = componentsToFeatures[wixComplexReferenceRow.Child]; if (null == connection) { componentsToFeatures.Add(new ConnectToFeature(section, wixComplexReferenceRow.Child, wixComplexReferenceRow.Parent, wixComplexReferenceRow.IsPrimary)); } else if (wixComplexReferenceRow.IsPrimary) { if (connection.IsExplicitPrimaryFeature) { this.Messaging.Write(ErrorMessages.MultiplePrimaryReferences(wixComplexReferenceRow.SourceLineNumbers, wixComplexReferenceRow.ChildType.ToString(), wixComplexReferenceRow.Child, wixComplexReferenceRow.ParentType.ToString(), wixComplexReferenceRow.Parent, (null != connection.PrimaryFeature ? "Feature" : "Package"), connection.PrimaryFeature ?? resolvedSection.Id)); continue; } else { connection.ConnectFeatures.Add(connection.PrimaryFeature); // move the guessed primary feature to the list of connects connection.PrimaryFeature = wixComplexReferenceRow.Parent; // set the new primary feature connection.IsExplicitPrimaryFeature = true; // and make sure we remember that we set it so we can fail if we try to set it again } } else { connection.ConnectFeatures.Add(wixComplexReferenceRow.Parent); } // add a row to the FeatureComponents table section.AddSymbol(new FeatureComponentsSymbol { FeatureRef = wixComplexReferenceRow.Parent, ComponentRef = wixComplexReferenceRow.Child, }); // index the component for finding orphaned records var symbolName = String.Concat("Component:", wixComplexReferenceRow.Child); referencedComponents.Add(symbolName); break; case ComplexReferenceChildType.Feature: connection = featuresToFeatures[wixComplexReferenceRow.Child]; if (null != connection) { this.Messaging.Write(ErrorMessages.MultiplePrimaryReferences(wixComplexReferenceRow.SourceLineNumbers, wixComplexReferenceRow.ChildType.ToString(), wixComplexReferenceRow.Child, wixComplexReferenceRow.ParentType.ToString(), wixComplexReferenceRow.Parent, (null != connection.PrimaryFeature ? "Feature" : "Package"), (null != connection.PrimaryFeature ? connection.PrimaryFeature : resolvedSection.Id))); continue; } featuresToFeatures.Add(new ConnectToFeature(section, wixComplexReferenceRow.Child, wixComplexReferenceRow.Parent, wixComplexReferenceRow.IsPrimary)); break; case ComplexReferenceChildType.Module: connection = modulesToFeatures[wixComplexReferenceRow.Child]; if (null == connection) { modulesToFeatures.Add(new ConnectToFeature(section, wixComplexReferenceRow.Child, wixComplexReferenceRow.Parent, wixComplexReferenceRow.IsPrimary)); } else if (wixComplexReferenceRow.IsPrimary) { if (connection.IsExplicitPrimaryFeature) { this.Messaging.Write(ErrorMessages.MultiplePrimaryReferences(wixComplexReferenceRow.SourceLineNumbers, wixComplexReferenceRow.ChildType.ToString(), wixComplexReferenceRow.Child, wixComplexReferenceRow.ParentType.ToString(), wixComplexReferenceRow.Parent, (null != connection.PrimaryFeature ? "Feature" : "Package"), (null != connection.PrimaryFeature ? connection.PrimaryFeature : resolvedSection.Id))); continue; } else { connection.ConnectFeatures.Add(connection.PrimaryFeature); // move the guessed primary feature to the list of connects connection.PrimaryFeature = wixComplexReferenceRow.Parent; // set the new primary feature connection.IsExplicitPrimaryFeature = true; // and make sure we remember that we set it so we can fail if we try to set it again } } else { connection.ConnectFeatures.Add(wixComplexReferenceRow.Parent); } break; default: throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, "Unexpected complex reference child type: {0}", Enum.GetName(typeof(ComplexReferenceChildType), wixComplexReferenceRow.ChildType))); } break; case ComplexReferenceParentType.Module: switch (wixComplexReferenceRow.ChildType) { case ComplexReferenceChildType.Component: if (componentsToModules.ContainsKey(wixComplexReferenceRow.Child)) { this.Messaging.Write(ErrorMessages.ComponentReferencedTwice(wixComplexReferenceRow.SourceLineNumbers, wixComplexReferenceRow.Child)); continue; } else { componentsToModules.Add(wixComplexReferenceRow.Child, wixComplexReferenceRow); // should always be new // add a row to the ModuleComponents table section.AddSymbol(new ModuleComponentsSymbol { Component = wixComplexReferenceRow.Child, ModuleID = wixComplexReferenceRow.Parent, Language = Convert.ToInt32(wixComplexReferenceRow.ParentLanguage), }); } // index the component for finding orphaned records var componentSymbolName = String.Concat("Component:", wixComplexReferenceRow.Child); referencedComponents.Add(componentSymbolName); break; default: throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, "Unexpected complex reference child type: {0}", Enum.GetName(typeof(ComplexReferenceChildType), wixComplexReferenceRow.ChildType))); } break; case ComplexReferenceParentType.Patch: switch (wixComplexReferenceRow.ChildType) { case ComplexReferenceChildType.PatchFamily: case ComplexReferenceChildType.PatchFamilyGroup: break; default: throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, "Unexpected complex reference child type: {0}", Enum.GetName(typeof(ComplexReferenceChildType), wixComplexReferenceRow.ChildType))); } break; case ComplexReferenceParentType.Product: switch (wixComplexReferenceRow.ChildType) { case ComplexReferenceChildType.Feature: connection = featuresToFeatures[wixComplexReferenceRow.Child]; if (null != connection) { this.Messaging.Write(ErrorMessages.MultiplePrimaryReferences(wixComplexReferenceRow.SourceLineNumbers, wixComplexReferenceRow.ChildType.ToString(), wixComplexReferenceRow.Child, wixComplexReferenceRow.ParentType.ToString(), wixComplexReferenceRow.Parent, (null != connection.PrimaryFeature ? "Feature" : "Package"), (null != connection.PrimaryFeature ? connection.PrimaryFeature : resolvedSection.Id))); continue; } featuresToFeatures.Add(new ConnectToFeature(section, wixComplexReferenceRow.Child, null, wixComplexReferenceRow.IsPrimary)); break; default: throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, "Unexpected complex reference child type: {0}", Enum.GetName(typeof(ComplexReferenceChildType), wixComplexReferenceRow.ChildType))); } break; default: // Note: Groups have been processed before getting here so they are not handled by any case above. throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, "Unexpected complex reference child type: {0}", Enum.GetName(typeof(ComplexReferenceParentType), wixComplexReferenceRow.ParentType))); } } } } /// /// Flattens all complex references in all sections in the collection. /// /// Sections that are referenced during the link process. private void FlattenSectionsComplexReferences(IEnumerable sections) { var parentGroups = new Dictionary>(); var parentGroupsSections = new Dictionary(); var parentGroupsNeedingProcessing = new Dictionary(); // DisplaySectionComplexReferences("--- section's complex references before flattening ---", sections); // Step 1: Gather all of the complex references that are going to participate // in the flatting process. This means complex references that have "grouping // parents" of Features, Modules, and, of course, Groups. These references // that participate in a "grouping parent" will be removed from their section // now and after processing added back in Step 3 below. foreach (var section in sections) { var removeSymbols = new List(); foreach (var symbol in section.Symbols) { // Only process the "grouping parents" such as FeatureGroup, ComponentGroup, Feature, // and Module. Non-grouping complex references are simple and // resolved during normal complex reference resolutions. if (symbol is WixComplexReferenceSymbol wixComplexReferenceRow && (ComplexReferenceParentType.FeatureGroup == wixComplexReferenceRow.ParentType || ComplexReferenceParentType.ComponentGroup == wixComplexReferenceRow.ParentType || ComplexReferenceParentType.Feature == wixComplexReferenceRow.ParentType || ComplexReferenceParentType.Module == wixComplexReferenceRow.ParentType || ComplexReferenceParentType.PatchFamilyGroup == wixComplexReferenceRow.ParentType || ComplexReferenceParentType.Product == wixComplexReferenceRow.ParentType)) { var parentTypeAndId = this.CombineTypeAndId(wixComplexReferenceRow.ParentType, wixComplexReferenceRow.Parent); // Group all complex references with a common parent // together so we can find them quickly while processing in // Step 2. if (!parentGroups.TryGetValue(parentTypeAndId, out var childrenComplexRefs)) { childrenComplexRefs = new List(); parentGroups.Add(parentTypeAndId, childrenComplexRefs); } childrenComplexRefs.Add(wixComplexReferenceRow); removeSymbols.Add(wixComplexReferenceRow); // Remember the mapping from set of complex references with a common // parent to their section. We'll need this to add them back to the // correct section in Step 3. if (!parentGroupsSections.TryGetValue(parentTypeAndId, out var parentSection)) { parentGroupsSections.Add(parentTypeAndId, section); } // If the child of the complex reference is another group, then in Step 2 // we're going to have to process this complex reference again to copy // the child group's references into the parent group. if ((ComplexReferenceChildType.ComponentGroup == wixComplexReferenceRow.ChildType) || (ComplexReferenceChildType.FeatureGroup == wixComplexReferenceRow.ChildType) || (ComplexReferenceChildType.PatchFamilyGroup == wixComplexReferenceRow.ChildType)) { if (!parentGroupsNeedingProcessing.ContainsKey(parentTypeAndId)) { parentGroupsNeedingProcessing.Add(parentTypeAndId, section); } } } } foreach (var removeSymbol in removeSymbols) { section.RemoveSymbol(removeSymbol); } } Debug.Assert(parentGroups.Count == parentGroupsSections.Count); Debug.Assert(parentGroupsNeedingProcessing.Count <= parentGroups.Count); // DisplaySectionComplexReferences("\r\n\r\n--- section's complex references middle of flattening ---", sections); // Step 2: Loop through the parent groups that have nested groups removing // them from the hash table as they are processed. At the end of this the // complex references should all be flattened. var keys = parentGroupsNeedingProcessing.Keys.ToList(); foreach (var key in keys) { if (parentGroupsNeedingProcessing.ContainsKey(key)) { var loopDetector = new Stack(); this.FlattenGroup(key, loopDetector, parentGroups, parentGroupsNeedingProcessing); } else { // the group must have allready been procesed and removed from the hash table } } Debug.Assert(0 == parentGroupsNeedingProcessing.Count); // Step 3: Finally, ensure that all of the groups that were removed // in Step 1 and flattened in Step 2 are added to their appropriate // section. This is where we will toss out the final no-longer-needed // groups. foreach (var parentGroup in parentGroups.Keys) { var section = parentGroupsSections[parentGroup]; foreach (var wixComplexReferenceRow in parentGroups[parentGroup]) { if ((ComplexReferenceParentType.FeatureGroup != wixComplexReferenceRow.ParentType) && (ComplexReferenceParentType.ComponentGroup != wixComplexReferenceRow.ParentType) && (ComplexReferenceParentType.PatchFamilyGroup != wixComplexReferenceRow.ParentType)) { section.AddSymbol(wixComplexReferenceRow); } } } // DisplaySectionComplexReferences("\r\n\r\n--- section's complex references after flattening ---", sections); } private string CombineTypeAndId(ComplexReferenceParentType type, string id) { return String.Concat(type.ToString(), ":", id); } private string CombineTypeAndId(ComplexReferenceChildType type, string id) { return String.Concat(type.ToString(), ":", id); } /// /// Recursively processes the group. /// /// String combination type and id of group to process next. /// Stack of groups processed thus far. Used to detect loops. /// Hash table of complex references grouped by parent id. /// Hash table of parent groups that still have nested groups that need to be flattened. private void FlattenGroup(string parentTypeAndId, Stack loopDetector, Dictionary> parentGroups, Dictionary parentGroupsNeedingProcessing) { Debug.Assert(parentGroupsNeedingProcessing.ContainsKey(parentTypeAndId)); loopDetector.Push(parentTypeAndId); // push this complex reference parent identfier into the stack for loop verifying var allNewChildComplexReferences = new List(); var referencesToParent = parentGroups[parentTypeAndId]; foreach (var wixComplexReferenceRow in referencesToParent) { Debug.Assert(ComplexReferenceParentType.ComponentGroup == wixComplexReferenceRow.ParentType || ComplexReferenceParentType.FeatureGroup == wixComplexReferenceRow.ParentType || ComplexReferenceParentType.Feature == wixComplexReferenceRow.ParentType || ComplexReferenceParentType.Module == wixComplexReferenceRow.ParentType || ComplexReferenceParentType.Product == wixComplexReferenceRow.ParentType || ComplexReferenceParentType.PatchFamilyGroup == wixComplexReferenceRow.ParentType || ComplexReferenceParentType.Patch == wixComplexReferenceRow.ParentType); Debug.Assert(parentTypeAndId == this.CombineTypeAndId(wixComplexReferenceRow.ParentType, wixComplexReferenceRow.Parent)); // We are only interested processing when the child is a group. if ((ComplexReferenceChildType.ComponentGroup == wixComplexReferenceRow.ChildType) || (ComplexReferenceChildType.FeatureGroup == wixComplexReferenceRow.ChildType) || (ComplexReferenceChildType.PatchFamilyGroup == wixComplexReferenceRow.ChildType)) { var childTypeAndId = this.CombineTypeAndId(wixComplexReferenceRow.ChildType, wixComplexReferenceRow.Child); if (loopDetector.Contains(childTypeAndId)) { // Create a comma delimited list of the references that participate in the // loop for the error message. Start at the bottom of the stack and work the // way up to present the loop as a directed graph. var loop = String.Join(" -> ", loopDetector); this.Messaging.Write(ErrorMessages.ReferenceLoopDetected(wixComplexReferenceRow?.SourceLineNumbers, loop)); // Cleanup the parentGroupsNeedingProcessing and the loopDetector just like the // exit of this method does at the end because we are exiting early. loopDetector.Pop(); parentGroupsNeedingProcessing.Remove(parentTypeAndId); return; // bail } // Check to see if the child group still needs to be processed. If so, // go do that so that we'll get all of that children's (and children's // children) complex references correctly merged into our parent group. if (parentGroupsNeedingProcessing.ContainsKey(childTypeAndId)) { this.FlattenGroup(childTypeAndId, loopDetector, parentGroups, parentGroupsNeedingProcessing); } // If the child is a parent to anything (i.e. the parent has grandchildren) // clone each of the children's complex references, repoint them to the parent // complex reference (because we're moving references up the tree), and finally // add the cloned child's complex reference to the list of complex references // that we'll eventually add to the parent group. if (parentGroups.TryGetValue(childTypeAndId, out var referencesToChild)) { foreach (var crefChild in referencesToChild) { // Only merge up the non-group items since groups are purged // after this part of the processing anyway (cloning them would // be a complete waste of time). if ((ComplexReferenceChildType.FeatureGroup != crefChild.ChildType) || (ComplexReferenceChildType.ComponentGroup != crefChild.ChildType) || (ComplexReferenceChildType.PatchFamilyGroup != crefChild.ChildType)) { var crefChildClone = crefChild.Clone(); Debug.Assert(crefChildClone.Parent == wixComplexReferenceRow.Child); crefChildClone.Reparent(wixComplexReferenceRow); allNewChildComplexReferences.Add(crefChildClone); } } } } } // Add the children group's complex references to the parent // group. Clean out any left over groups and quietly remove any // duplicate complex references that occurred during the merge. referencesToParent.AddRange(allNewChildComplexReferences); referencesToParent.Sort(ComplexReferenceComparision); for (var i = referencesToParent.Count - 1; i >= 0; --i) { var wixComplexReferenceRow = referencesToParent[i]; if ((ComplexReferenceChildType.FeatureGroup == wixComplexReferenceRow.ChildType) || (ComplexReferenceChildType.ComponentGroup == wixComplexReferenceRow.ChildType) || (ComplexReferenceChildType.PatchFamilyGroup == wixComplexReferenceRow.ChildType)) { referencesToParent.RemoveAt(i); } else if (i > 0) { // Since the list is already sorted, we can find duplicates by simply // looking at the next sibling in the list and tossing out one if they // match. var crefCompare = referencesToParent[i - 1]; if (0 == wixComplexReferenceRow.CompareToWithoutConsideringPrimary(crefCompare)) { referencesToParent.RemoveAt(i); } } } int ComplexReferenceComparision(WixComplexReferenceSymbol x, WixComplexReferenceSymbol y) { var comparison = x.ChildType - y.ChildType; if (0 == comparison) { comparison = String.Compare(x.Child, y.Child, StringComparison.Ordinal); if (0 == comparison) { comparison = x.ParentType - y.ParentType; if (0 == comparison) { comparison = String.Compare(x.ParentLanguage ?? String.Empty, y.ParentLanguage ?? String.Empty, StringComparison.Ordinal); if (0 == comparison) { comparison = String.Compare(x.Parent, y.Parent, StringComparison.Ordinal); } } } } return comparison; } loopDetector.Pop(); // pop this complex reference off the stack since we're done verify the loop here parentGroupsNeedingProcessing.Remove(parentTypeAndId); // remove the newly processed complex reference } /* /// /// Debugging method for displaying the section complex references. /// /// The header. /// The sections to display. private void DisplaySectionComplexReferences(string header, SectionCollection sections) { Console.WriteLine(header); foreach (Section section in sections) { Table wixComplexReferenceTable = section.Tables["WixComplexReference"]; foreach (WixComplexReferenceRow cref in wixComplexReferenceTable.Rows) { Console.WriteLine("Section: {0} Parent: {1} Type: {2} Child: {3} Primary: {4}", section.Id, cref.ParentId, cref.ParentType, cref.ChildId, cref.IsPrimary); } } } */ /// /// Flattens the tables used in a Bundle. /// /// Output containing the tables to process. private void FlattenBundleTables(IntermediateSection entrySection) { if (SectionType.Bundle != entrySection.Type) { return; } // We need to flatten the nested PayloadGroups and PackageGroups under // UX, Chain, and any Containers. When we're done, the WixGroups table // will hold Payloads under UX, ChainPackages (references?) under Chain, // and ChainPackages/Payloads under the attached and any detatched // Containers. var groups = new WixGroupingOrdering(entrySection, this.Messaging); // Create UX payloads and Package payloads groups.UseTypes(new[] { ComplexReferenceParentType.Container, ComplexReferenceParentType.Layout, ComplexReferenceParentType.PackageGroup, ComplexReferenceParentType.PayloadGroup, ComplexReferenceParentType.Package }, new[] { ComplexReferenceChildType.PackageGroup, ComplexReferenceChildType.Package, ComplexReferenceChildType.PackagePayload, ComplexReferenceChildType.PayloadGroup, ComplexReferenceChildType.Payload }); groups.FlattenAndRewriteGroups(ComplexReferenceParentType.Package, false); groups.FlattenAndRewriteGroups(ComplexReferenceParentType.Container, false); groups.FlattenAndRewriteGroups(ComplexReferenceParentType.Layout, false); // Create Chain packages... groups.UseTypes(new[] { ComplexReferenceParentType.PackageGroup }, new[] { ComplexReferenceChildType.Package, ComplexReferenceChildType.PackageGroup }); groups.FlattenAndRewriteRows(ComplexReferenceChildType.PackageGroup, "WixChain", false); groups.RemoveUsedGroupRows(); } /// /// Resolves the features connected to other features in the active output. /// /// Feature to feature complex references. /// All symbols loaded from the sections. private void ResolveFeatureToFeatureConnects(ConnectToFeatureCollection featuresToFeatures, IDictionary allSymbols) { foreach (ConnectToFeature connection in featuresToFeatures) { var wixSimpleReferenceRow = new WixSimpleReferenceSymbol { Table = "Feature", PrimaryKeys = connection.ChildId }; if (allSymbols.TryGetValue(wixSimpleReferenceRow.SymbolicName, out var symbol)) { var featureSymbol = (FeatureSymbol)symbol.Symbol; featureSymbol.ParentFeatureRef = connection.PrimaryFeature; } } } /// /// Resolve features for columns that have null guid placeholders. /// /// Symbol to resolve. /// Number of the column containing the connection identifier. /// Number of the column containing the feature. /// Connect to feature complex references. /// Hashtable of known components under multiple features. private void ResolveFeatures(IntermediateSymbol symbol, int connectionColumn, int featureColumn, ConnectToFeatureCollection connectToFeatures, Hashtable multipleFeatureComponents) { var connectionId = connectionColumn < 0 ? symbol.Id.Id : symbol.AsString(connectionColumn); var featureId = symbol.AsString(featureColumn); if (EmptyGuid == featureId) { var connection = connectToFeatures[connectionId]; if (null == connection) { // display an error for the component or merge module as appropriate if (null != multipleFeatureComponents) { this.Messaging.Write(ErrorMessages.ComponentExpectedFeature(symbol.SourceLineNumbers, connectionId, symbol.Definition.Name, symbol.Id.Id)); } else { this.Messaging.Write(ErrorMessages.MergeModuleExpectedFeature(symbol.SourceLineNumbers, connectionId)); } } else { // check for unique, implicit, primary feature parents with multiple possible parent features if (this.ShowPedanticMessages && !connection.IsExplicitPrimaryFeature && 0 < connection.ConnectFeatures.Count) { // display a warning for the component or merge module as approrpriate if (null != multipleFeatureComponents) { if (!multipleFeatureComponents.Contains(connectionId)) { this.Messaging.Write(WarningMessages.ImplicitComponentPrimaryFeature(connectionId)); // remember this component so only one warning is generated for it multipleFeatureComponents[connectionId] = null; } } else { this.Messaging.Write(WarningMessages.ImplicitMergeModulePrimaryFeature(connectionId)); } } // set the feature symbol.Set(featureColumn, connection.PrimaryFeature); } } } } }