main
cs 927 lines 49.8 KB
Raw
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.Core
4 {
5 using System;
6 using System.Collections;
7 using System.Collections.Generic;
8 using System.Diagnostics;
9 using System.Globalization;
10 using System.Linq;
11 using WixToolset.Core.Link;
12 using WixToolset.Data;
13 using WixToolset.Data.Symbols;
14 using WixToolset.Extensibility.Data;
15 using WixToolset.Extensibility.Services;
16
17 /// <summary>
18 /// Linker core of the WiX toolset.
19 /// </summary>
20 internal class Linker : ILinker
21 {
22 private static readonly string EmptyGuid = Guid.Empty.ToString("B");
23
24 /// <summary>
25 /// Creates a linker.
26 /// </summary>
27 internal Linker(IServiceProvider serviceProvider)
28 {
29 this.ServiceProvider = serviceProvider;
30 this.Messaging = this.ServiceProvider.GetService<IMessaging>();
31 }
32
33 private IServiceProvider ServiceProvider { get; }
34
35 private IMessaging Messaging { get; }
36
37 private ILinkContext Context { get; set; }
38
39 /// <summary>
40 /// Gets or sets the path to output unreferenced symbols to. If null or empty, there is no output.
41 /// </summary>
42 /// <value>The path to output the xml file.</value>
43 public string UnreferencedSymbolsFile { get; set; }
44
45 /// <summary>
46 /// Gets or sets the option to show pedantic messages.
47 /// </summary>
48 /// <value>The option to show pedantic messages.</value>
49 public bool ShowPedanticMessages { get; set; }
50
51 /// <summary>
52 /// Links a collection of sections into an output.
53 /// </summary>
54 /// <returns>Output intermediate from the linking.</returns>
55 public Intermediate Link(ILinkContext context)
56 {
57 this.Context = context;
58
59 if (this.Context.SymbolDefinitionCreator == null)
60 {
61 this.Context.SymbolDefinitionCreator = this.ServiceProvider.GetService<ISymbolDefinitionCreator>();
62 }
63
64 foreach (var extension in this.Context.Extensions)
65 {
66 extension.PreLink(this.Context);
67 }
68
69 var invalidIntermediates = this.Context.Intermediates.Where(i => !i.HasLevel(Data.IntermediateLevels.Compiled));
70 if (invalidIntermediates.Any())
71 {
72 this.Messaging.Write(ErrorMessages.IntermediatesMustBeCompiled(String.Join(", ", invalidIntermediates.Select(i => i.Id))));
73 }
74
75 Intermediate intermediate = null;
76 try
77 {
78 var sections = this.Context.Intermediates.SelectMany(i => i.Sections).ToList();
79 var localizations = this.Context.Intermediates.SelectMany(i => i.Localizations).ToList();
80
81 // Add sections from the extensions with data.
82 foreach (var data in this.Context.ExtensionData)
83 {
84 var library = data.GetLibrary(this.Context.SymbolDefinitionCreator);
85
86 if (library != null)
87 {
88 sections.AddRange(library.Sections);
89
90 if (library.Localizations?.Count > 0)
91 {
92 // Include localizations from the extension data and be sure to note that the localization came from
93 // an extension. It is important to remember which localization came from an extension when filtering
94 // localizations during the resolve process later.
95 localizations.AddRange(library.Localizations.Select(l => l.UpdateLocation(LocalizationLocation.Extension)));
96 }
97 }
98 }
99
100 // Load the standard wixlib.
101 if (!this.Context.SkipStdWixlib)
102 {
103 var stdlib = WixStandardLibrary.Build(this.Context.Platform);
104
105 sections.AddRange(stdlib.Sections);
106
107 if (stdlib.Localizations?.Count > 0)
108 {
109 localizations.AddRange(stdlib.Localizations);
110 }
111 }
112
113 var multipleFeatureComponents = new Hashtable();
114
115 var wixVariables = new Dictionary<string, WixVariableSymbol>();
116
117 // First find the entry section and while processing all sections load all the symbols from all of the sections.
118 var find = new FindEntrySectionAndLoadSymbolsCommand(this.Messaging, sections, this.Context.ExpectedOutputType);
119 find.Execute();
120
121 // Must have found the entry section by now.
122 if (null == find.EntrySection)
123 {
124 if (this.Context.ExpectedOutputType == OutputType.IntermediatePostLink || this.Context.ExpectedOutputType == OutputType.Unknown)
125 {
126 throw new WixException(ErrorMessages.MissingEntrySection());
127 }
128 else
129 {
130 throw new WixException(ErrorMessages.MissingEntrySection(this.Context.ExpectedOutputType.ToString()));
131 }
132 }
133
134 // Add default symbols that need a bit more intelligence than just being
135 // included in the standard library.
136 {
137 var command = new AddDefaultSymbolsCommand(find, sections);
138 command.Execute();
139 }
140
141 // If there are no authored features, create a default feature and assign
142 // the components to it.
143 {
144 var command = new AssignDefaultFeatureCommand(find, sections);
145 command.Execute();
146 }
147
148 // Resolve the symbol references to find the set of sections we care about for linking.
149 // Of course, we start with the entry section (that's how it got its name after all).
150 var resolve = new ResolveReferencesCommand(this.Messaging, find.EntrySection, find.SymbolsByName);
151 resolve.Execute();
152
153 if (this.Messaging.EncounteredError)
154 {
155 return null;
156 }
157
158 // Reset the sections to only those that were resolved then flatten the complex
159 // references that particpate in groups.
160 sections = resolve.ResolvedSections.ToList();
161
162 // TODO: consider filtering "localizations" down to only those localizations from
163 // intermediates in the sections.
164
165 this.FlattenSectionsComplexReferences(sections);
166
167 if (this.Messaging.EncounteredError)
168 {
169 return null;
170 }
171
172 // The hard part in linking is processing the complex references.
173 var referencedComponents = new HashSet<string>();
174 var componentsToFeatures = new ConnectToFeatureCollection();
175 var featuresToFeatures = new ConnectToFeatureCollection();
176 var modulesToFeatures = new ConnectToFeatureCollection();
177 this.ProcessComplexReferences(find.EntrySection, sections, referencedComponents, componentsToFeatures, featuresToFeatures, modulesToFeatures);
178
179 if (this.Messaging.EncounteredError)
180 {
181 return null;
182 }
183
184 // If there are authored features, error for any referenced components that aren't assigned to a feature.
185 foreach (var component in sections.SelectMany(s => s.Symbols.Where(y => y.Definition.Type == SymbolDefinitionType.Component)))
186 {
187 if (!referencedComponents.Contains(component.Id.Id))
188 {
189 this.Messaging.Write(ErrorMessages.OrphanedComponent(component.SourceLineNumbers, component.Id.Id));
190 }
191 }
192
193 // Process conflicts that may be overridden virtual symbols (that's okay) or end up as primary key collisions (those need to be reported as errors).
194 ISet<IntermediateSymbol> overriddenSymbols;
195 {
196 var reportDupes = new ProcessConflictingSymbolsCommand(this.Messaging, find.PossibleConflicts, find.OverrideSymbols, resolve.ResolvedSections);
197 reportDupes.Execute();
198
199 overriddenSymbols = reportDupes.OverriddenSymbols;
200 }
201
202 if (this.Messaging.EncounteredError)
203 {
204 return null;
205 }
206
207 // resolve the feature to feature connects
208 this.ResolveFeatureToFeatureConnects(featuresToFeatures, find.SymbolsByName);
209
210 // Create a new section to hold the linked content. Start with the entry section's
211 // metadata.
212 var resolvedSection = new IntermediateSection(find.EntrySection.Id, find.EntrySection.Type);
213 var identicalDirectoryIds = new HashSet<string>(StringComparer.Ordinal);
214
215 foreach (var section in sections)
216 {
217 foreach (var symbol in section.Symbols)
218 {
219 // If this symbol is an identical directory, ensure we only visit
220 // one (and skip the other identicals with the same id).
221 if (find.IdenticalDirectorySymbols.Contains(symbol))
222 {
223 if (!identicalDirectoryIds.Add(symbol.Id.Id))
224 {
225 continue;
226 }
227 }
228 else if (overriddenSymbols.Contains(symbol))
229 {
230 // Skip the symbols that were overridden.
231 continue;
232 }
233
234 var copySymbol = true; // by default, copy symbols.
235
236 // handle special tables
237 switch (symbol.Definition.Type)
238 {
239 case SymbolDefinitionType.Class:
240 if (SectionType.Package == resolvedSection.Type)
241 {
242 this.ResolveFeatures(symbol, (int)ClassSymbolFields.ComponentRef, (int)ClassSymbolFields.FeatureRef, componentsToFeatures, multipleFeatureComponents);
243 }
244 break;
245
246 case SymbolDefinitionType.Extension:
247 if (SectionType.Package == resolvedSection.Type)
248 {
249 this.ResolveFeatures(symbol, (int)ExtensionSymbolFields.ComponentRef, (int)ExtensionSymbolFields.FeatureRef, componentsToFeatures, multipleFeatureComponents);
250 }
251 break;
252
253 case SymbolDefinitionType.Assembly:
254 if (SectionType.Package == resolvedSection.Type)
255 {
256 this.ResolveFeatures(symbol, (int)AssemblySymbolFields.ComponentRef, (int)AssemblySymbolFields.FeatureRef, componentsToFeatures, multipleFeatureComponents);
257 }
258 break;
259
260 case SymbolDefinitionType.PublishComponent:
261 if (SectionType.Package == resolvedSection.Type)
262 {
263 this.ResolveFeatures(symbol, (int)PublishComponentSymbolFields.ComponentRef, (int)PublishComponentSymbolFields.FeatureRef, componentsToFeatures, multipleFeatureComponents);
264 }
265 break;
266
267 case SymbolDefinitionType.Shortcut:
268 if (SectionType.Package == resolvedSection.Type)
269 {
270 this.ResolveFeatures(symbol, (int)ShortcutSymbolFields.ComponentRef, (int)ShortcutSymbolFields.Target, componentsToFeatures, multipleFeatureComponents);
271 }
272 break;
273
274 case SymbolDefinitionType.TypeLib:
275 if (SectionType.Package == resolvedSection.Type)
276 {
277 this.ResolveFeatures(symbol, (int)TypeLibSymbolFields.ComponentRef, (int)TypeLibSymbolFields.FeatureRef, componentsToFeatures, multipleFeatureComponents);
278 }
279 break;
280
281 case SymbolDefinitionType.WixMerge:
282 if (SectionType.Package == resolvedSection.Type)
283 {
284 this.ResolveFeatures(symbol, -1, (int)WixMergeSymbolFields.FeatureRef, modulesToFeatures, null);
285 }
286 break;
287
288 case SymbolDefinitionType.WixSimpleReference:
289 case SymbolDefinitionType.WixComplexReference:
290 copySymbol = false;
291 break;
292
293 case SymbolDefinitionType.WixVariable:
294 this.AddWixVariable(wixVariables, (WixVariableSymbol)symbol);
295 copySymbol = false; // Do not copy the symbol, it will be added later after all overriding has been handled.
296 break;
297 }
298
299 if (copySymbol)
300 {
301 resolvedSection.AddSymbol(symbol);
302 }
303 }
304 }
305
306 // Copy the module to feature connections into the output.
307 foreach (ConnectToFeature connectToFeature in modulesToFeatures)
308 {
309 foreach (var feature in connectToFeature.ConnectFeatures)
310 {
311 resolvedSection.AddSymbol(new WixFeatureModulesSymbol
312 {
313 FeatureRef = feature,
314 WixMergeRef = connectToFeature.ChildId
315 });
316 }
317 }
318
319 // Copy the wix variable rows to the output now that all overriding has been accounted for.
320 foreach (var symbol in wixVariables.Values)
321 {
322 resolvedSection.AddSymbol(symbol);
323 }
324
325 // Bundles have groups of data that must be flattened in a way different from other types.
326 if (resolvedSection.Type == SectionType.Bundle)
327 {
328 var command = new FlattenAndProcessBundleTablesCommand(resolvedSection, this.Messaging);
329 command.Execute();
330 }
331
332 if (this.Messaging.EncounteredError)
333 {
334 return null;
335 }
336
337 var collate = new CollateLocalizationsCommand(this.Messaging, localizations);
338 var localizationsByCulture = collate.Execute();
339
340 intermediate = new Intermediate(resolvedSection.Id, Data.IntermediateLevels.Linked, new[] { resolvedSection }, localizationsByCulture);
341 }
342 finally
343 {
344 foreach (var extension in this.Context.Extensions)
345 {
346 extension.PostLink(intermediate);
347 }
348 }
349
350 return this.Messaging.EncounteredError ? null : intermediate;
351 }
352
353 /// <summary>
354 /// Check for colliding values and collect the wix variable rows.
355 /// </summary>
356 /// <param name="wixVariables">Collection of WixVariableSymbols by id.</param>
357 /// <param name="symbol">WixVariableSymbol to add, if not overridden.</param>
358 private void AddWixVariable(Dictionary<string, WixVariableSymbol> wixVariables, WixVariableSymbol symbol)
359 {
360 var id = symbol.Id.Id;
361
362 if (wixVariables.TryGetValue(id, out var collidingSymbol))
363 {
364 if (collidingSymbol.Overridable && !symbol.Overridable)
365 {
366 wixVariables[id] = symbol;
367 }
368 else if (!symbol.Overridable || (collidingSymbol.Overridable && symbol.Overridable))
369 {
370 this.Messaging.Write(ErrorMessages.BindVariableCollision(symbol.SourceLineNumbers, id));
371 }
372 }
373 else
374 {
375 wixVariables.Add(id, symbol);
376 }
377 }
378
379 /// <summary>
380 /// Process the complex references.
381 /// </summary>
382 /// <param name="resolvedSection">Active section to add symbols to.</param>
383 /// <param name="sections">Sections that are referenced during the link process.</param>
384 /// <param name="referencedComponents">Collection of all components referenced by complex reference.</param>
385 /// <param name="componentsToFeatures">Component to feature complex references.</param>
386 /// <param name="featuresToFeatures">Feature to feature complex references.</param>
387 /// <param name="modulesToFeatures">Module to feature complex references.</param>
388 private void ProcessComplexReferences(IntermediateSection resolvedSection, IEnumerable<IntermediateSection> sections, ISet<string> referencedComponents, ConnectToFeatureCollection componentsToFeatures, ConnectToFeatureCollection featuresToFeatures, ConnectToFeatureCollection modulesToFeatures)
389 {
390 var componentsToModules = new Hashtable();
391
392 foreach (var section in sections)
393 {
394 // Need ToList since we might want to add symbols while processing.
395 var wixComplexReferences = section.Symbols.OfType<WixComplexReferenceSymbol>().ToList();
396 foreach (var wixComplexReferenceRow in wixComplexReferences)
397 {
398 ConnectToFeature connection;
399 switch (wixComplexReferenceRow.ParentType)
400 {
401 case ComplexReferenceParentType.Feature:
402 switch (wixComplexReferenceRow.ChildType)
403 {
404 case ComplexReferenceChildType.Component:
405 connection = componentsToFeatures[wixComplexReferenceRow.Child];
406 if (null == connection)
407 {
408 componentsToFeatures.Add(new ConnectToFeature(section, wixComplexReferenceRow.Child, wixComplexReferenceRow.Parent, wixComplexReferenceRow.IsPrimary));
409 }
410 else if (wixComplexReferenceRow.IsPrimary)
411 {
412 if (connection.IsExplicitPrimaryFeature)
413 {
414 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));
415 continue;
416 }
417 else
418 {
419 connection.ConnectFeatures.Add(connection.PrimaryFeature); // move the guessed primary feature to the list of connects
420 connection.PrimaryFeature = wixComplexReferenceRow.Parent; // set the new primary feature
421 connection.IsExplicitPrimaryFeature = true; // and make sure we remember that we set it so we can fail if we try to set it again
422 }
423 }
424 else
425 {
426 connection.ConnectFeatures.Add(wixComplexReferenceRow.Parent);
427 }
428
429 // add a row to the FeatureComponents table
430 section.AddSymbol(new FeatureComponentsSymbol
431 {
432 FeatureRef = wixComplexReferenceRow.Parent,
433 ComponentRef = wixComplexReferenceRow.Child,
434 });
435
436 // index the component for finding orphaned records
437 referencedComponents.Add(wixComplexReferenceRow.Child);
438
439 break;
440
441 case ComplexReferenceChildType.Feature:
442 connection = featuresToFeatures[wixComplexReferenceRow.Child];
443 if (null != connection)
444 {
445 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));
446 continue;
447 }
448
449 featuresToFeatures.Add(new ConnectToFeature(section, wixComplexReferenceRow.Child, wixComplexReferenceRow.Parent, wixComplexReferenceRow.IsPrimary));
450 break;
451
452 case ComplexReferenceChildType.Module:
453 connection = modulesToFeatures[wixComplexReferenceRow.Child];
454 if (null == connection)
455 {
456 modulesToFeatures.Add(new ConnectToFeature(section, wixComplexReferenceRow.Child, wixComplexReferenceRow.Parent, wixComplexReferenceRow.IsPrimary));
457 }
458 else if (wixComplexReferenceRow.IsPrimary)
459 {
460 if (connection.IsExplicitPrimaryFeature)
461 {
462 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));
463 continue;
464 }
465 else
466 {
467 connection.ConnectFeatures.Add(connection.PrimaryFeature); // move the guessed primary feature to the list of connects
468 connection.PrimaryFeature = wixComplexReferenceRow.Parent; // set the new primary feature
469 connection.IsExplicitPrimaryFeature = true; // and make sure we remember that we set it so we can fail if we try to set it again
470 }
471 }
472 else
473 {
474 connection.ConnectFeatures.Add(wixComplexReferenceRow.Parent);
475 }
476 break;
477
478 default:
479 throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, "Unexpected complex reference child type: {0}", Enum.GetName(typeof(ComplexReferenceChildType), wixComplexReferenceRow.ChildType)));
480 }
481 break;
482
483 case ComplexReferenceParentType.Module:
484 switch (wixComplexReferenceRow.ChildType)
485 {
486 case ComplexReferenceChildType.Component:
487 if (componentsToModules.ContainsKey(wixComplexReferenceRow.Child))
488 {
489 this.Messaging.Write(ErrorMessages.ComponentReferencedTwice(wixComplexReferenceRow.SourceLineNumbers, wixComplexReferenceRow.Child));
490 continue;
491 }
492 else
493 {
494 componentsToModules.Add(wixComplexReferenceRow.Child, wixComplexReferenceRow); // should always be new
495
496 // add a row to the ModuleComponents table
497 section.AddSymbol(new ModuleComponentsSymbol
498 {
499 Component = wixComplexReferenceRow.Child,
500 ModuleID = wixComplexReferenceRow.Parent,
501 Language = Convert.ToInt32(wixComplexReferenceRow.ParentLanguage),
502 });
503 }
504
505 // index the component for finding orphaned records
506 referencedComponents.Add(wixComplexReferenceRow.Child);
507
508 break;
509
510 default:
511 throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, "Unexpected complex reference child type: {0}", Enum.GetName(typeof(ComplexReferenceChildType), wixComplexReferenceRow.ChildType)));
512 }
513 break;
514
515 case ComplexReferenceParentType.Patch:
516 switch (wixComplexReferenceRow.ChildType)
517 {
518 case ComplexReferenceChildType.PatchFamily:
519 case ComplexReferenceChildType.PatchFamilyGroup:
520 break;
521
522 default:
523 throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, "Unexpected complex reference child type: {0}", Enum.GetName(typeof(ComplexReferenceChildType), wixComplexReferenceRow.ChildType)));
524 }
525 break;
526
527 case ComplexReferenceParentType.Product:
528 switch (wixComplexReferenceRow.ChildType)
529 {
530 case ComplexReferenceChildType.Feature:
531 connection = featuresToFeatures[wixComplexReferenceRow.Child];
532 if (null != connection)
533 {
534 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));
535 continue;
536 }
537
538 featuresToFeatures.Add(new ConnectToFeature(section, wixComplexReferenceRow.Child, null, wixComplexReferenceRow.IsPrimary));
539 break;
540
541 case ComplexReferenceChildType.Component:
542 case ComplexReferenceChildType.ComponentGroup:
543 break;
544
545 default:
546 throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, "Unexpected complex reference child type: {0}", Enum.GetName(typeof(ComplexReferenceChildType), wixComplexReferenceRow.ChildType)));
547 }
548 break;
549
550 default:
551 // Note: Groups have been processed before getting here so they are not handled by any case above.
552 throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, "Unexpected complex reference child type: {0}", Enum.GetName(typeof(ComplexReferenceParentType), wixComplexReferenceRow.ParentType)));
553 }
554 }
555 }
556 }
557
558 /// <summary>
559 /// Flattens all complex references in all sections in the collection.
560 /// </summary>
561 /// <param name="sections">Sections that are referenced during the link process.</param>
562 private void FlattenSectionsComplexReferences(IEnumerable<IntermediateSection> sections)
563 {
564 var parentGroups = new Dictionary<string, List<WixComplexReferenceSymbol>>();
565 var parentGroupsSections = new Dictionary<string, IntermediateSection>();
566 var parentGroupsNeedingProcessing = new Dictionary<string, IntermediateSection>();
567
568 // DisplaySectionComplexReferences("--- section's complex references before flattening ---", sections);
569
570 // Step 1: Gather all of the complex references that are going to participate
571 // in the flatting process. This means complex references that have "grouping
572 // parents" of Features, Modules, and, of course, Groups. These references
573 // that participate in a "grouping parent" will be removed from their section
574 // now and after processing added back in Step 3 below.
575 foreach (var section in sections)
576 {
577 var removeSymbols = new List<IntermediateSymbol>();
578
579 foreach (var symbol in section.Symbols)
580 {
581 // Only process the "grouping parents" such as FeatureGroup, ComponentGroup, Feature,
582 // and Module. Non-grouping complex references are simple and
583 // resolved during normal complex reference resolutions.
584 if (symbol is WixComplexReferenceSymbol wixComplexReferenceRow &&
585 (ComplexReferenceParentType.FeatureGroup == wixComplexReferenceRow.ParentType ||
586 ComplexReferenceParentType.ComponentGroup == wixComplexReferenceRow.ParentType ||
587 ComplexReferenceParentType.Feature == wixComplexReferenceRow.ParentType ||
588 ComplexReferenceParentType.Module == wixComplexReferenceRow.ParentType ||
589 ComplexReferenceParentType.PatchFamilyGroup == wixComplexReferenceRow.ParentType ||
590 ComplexReferenceParentType.Product == wixComplexReferenceRow.ParentType))
591 {
592 var parentTypeAndId = this.CombineTypeAndId(wixComplexReferenceRow.ParentType, wixComplexReferenceRow.Parent);
593
594 // Group all complex references with a common parent
595 // together so we can find them quickly while processing in
596 // Step 2.
597 if (!parentGroups.TryGetValue(parentTypeAndId, out var childrenComplexRefs))
598 {
599 childrenComplexRefs = new List<WixComplexReferenceSymbol>();
600 parentGroups.Add(parentTypeAndId, childrenComplexRefs);
601 }
602
603 childrenComplexRefs.Add(wixComplexReferenceRow);
604 removeSymbols.Add(wixComplexReferenceRow);
605
606 // Remember the mapping from set of complex references with a common
607 // parent to their section. We'll need this to add them back to the
608 // correct section in Step 3.
609 if (!parentGroupsSections.TryGetValue(parentTypeAndId, out var parentSection))
610 {
611 parentGroupsSections.Add(parentTypeAndId, section);
612 }
613
614 // If the child of the complex reference is another group, then in Step 2
615 // we're going to have to process this complex reference again to copy
616 // the child group's references into the parent group.
617 if ((ComplexReferenceChildType.ComponentGroup == wixComplexReferenceRow.ChildType) ||
618 (ComplexReferenceChildType.FeatureGroup == wixComplexReferenceRow.ChildType) ||
619 (ComplexReferenceChildType.PatchFamilyGroup == wixComplexReferenceRow.ChildType))
620 {
621 if (!parentGroupsNeedingProcessing.ContainsKey(parentTypeAndId))
622 {
623 parentGroupsNeedingProcessing.Add(parentTypeAndId, section);
624 }
625 }
626 }
627 }
628
629 foreach (var removeSymbol in removeSymbols)
630 {
631 section.RemoveSymbol(removeSymbol);
632 }
633 }
634
635 Debug.Assert(parentGroups.Count == parentGroupsSections.Count);
636 Debug.Assert(parentGroupsNeedingProcessing.Count <= parentGroups.Count);
637
638 // DisplaySectionComplexReferences("\r\n\r\n--- section's complex references middle of flattening ---", sections);
639
640 // Step 2: Loop through the parent groups that have nested groups removing
641 // them from the hash table as they are processed. At the end of this the
642 // complex references should all be flattened.
643 var keys = parentGroupsNeedingProcessing.Keys.ToList();
644
645 foreach (var key in keys)
646 {
647 if (parentGroupsNeedingProcessing.ContainsKey(key))
648 {
649 var loopDetector = new Stack<string>();
650 this.FlattenGroup(key, loopDetector, parentGroups, parentGroupsNeedingProcessing);
651 }
652 else
653 {
654 // the group must have allready been procesed and removed from the hash table
655 }
656 }
657 Debug.Assert(0 == parentGroupsNeedingProcessing.Count);
658
659 // Step 3: Finally, ensure that all of the groups that were removed
660 // in Step 1 and flattened in Step 2 are added to their appropriate
661 // section. This is where we will toss out the final no-longer-needed
662 // groups.
663 foreach (var parentGroup in parentGroups.Keys)
664 {
665 var section = parentGroupsSections[parentGroup];
666
667 foreach (var wixComplexReferenceRow in parentGroups[parentGroup])
668 {
669 if ((ComplexReferenceParentType.FeatureGroup != wixComplexReferenceRow.ParentType) &&
670 (ComplexReferenceParentType.ComponentGroup != wixComplexReferenceRow.ParentType) &&
671 (ComplexReferenceParentType.PatchFamilyGroup != wixComplexReferenceRow.ParentType))
672 {
673 section.AddSymbol(wixComplexReferenceRow);
674 }
675 }
676 }
677
678 // DisplaySectionComplexReferences("\r\n\r\n--- section's complex references after flattening ---", sections);
679 }
680
681 private string CombineTypeAndId(ComplexReferenceParentType type, string id)
682 {
683 return String.Concat(type.ToString(), ":", id);
684 }
685
686 private string CombineTypeAndId(ComplexReferenceChildType type, string id)
687 {
688 return String.Concat(type.ToString(), ":", id);
689 }
690
691 /// <summary>
692 /// Recursively processes the group.
693 /// </summary>
694 /// <param name="parentTypeAndId">String combination type and id of group to process next.</param>
695 /// <param name="loopDetector">Stack of groups processed thus far. Used to detect loops.</param>
696 /// <param name="parentGroups">Hash table of complex references grouped by parent id.</param>
697 /// <param name="parentGroupsNeedingProcessing">Hash table of parent groups that still have nested groups that need to be flattened.</param>
698 private void FlattenGroup(string parentTypeAndId, Stack<string> loopDetector, Dictionary<string, List<WixComplexReferenceSymbol>> parentGroups, Dictionary<string, IntermediateSection> parentGroupsNeedingProcessing)
699 {
700 Debug.Assert(parentGroupsNeedingProcessing.ContainsKey(parentTypeAndId));
701 loopDetector.Push(parentTypeAndId); // push this complex reference parent identfier into the stack for loop verifying
702
703 var allNewChildComplexReferences = new List<WixComplexReferenceSymbol>();
704
705 var referencesToParent = parentGroups[parentTypeAndId];
706 foreach (var wixComplexReferenceRow in referencesToParent)
707 {
708 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);
709 Debug.Assert(parentTypeAndId == this.CombineTypeAndId(wixComplexReferenceRow.ParentType, wixComplexReferenceRow.Parent));
710
711 // We are only interested processing when the child is a group.
712 if ((ComplexReferenceChildType.ComponentGroup == wixComplexReferenceRow.ChildType) ||
713 (ComplexReferenceChildType.FeatureGroup == wixComplexReferenceRow.ChildType) ||
714 (ComplexReferenceChildType.PatchFamilyGroup == wixComplexReferenceRow.ChildType))
715 {
716 var childTypeAndId = this.CombineTypeAndId(wixComplexReferenceRow.ChildType, wixComplexReferenceRow.Child);
717 if (loopDetector.Contains(childTypeAndId))
718 {
719 // Create a comma delimited list of the references that participate in the
720 // loop for the error message. Start at the bottom of the stack and work the
721 // way up to present the loop as a directed graph.
722 var loop = String.Join(" -> ", loopDetector);
723
724 this.Messaging.Write(ErrorMessages.ReferenceLoopDetected(wixComplexReferenceRow?.SourceLineNumbers, loop));
725
726 // Cleanup the parentGroupsNeedingProcessing and the loopDetector just like the
727 // exit of this method does at the end because we are exiting early.
728 loopDetector.Pop();
729 parentGroupsNeedingProcessing.Remove(parentTypeAndId);
730
731 return; // bail
732 }
733
734 // Check to see if the child group still needs to be processed. If so,
735 // go do that so that we'll get all of that children's (and children's
736 // children) complex references correctly merged into our parent group.
737 if (parentGroupsNeedingProcessing.ContainsKey(childTypeAndId))
738 {
739 this.FlattenGroup(childTypeAndId, loopDetector, parentGroups, parentGroupsNeedingProcessing);
740 }
741
742 // If the child is a parent to anything (i.e. the parent has grandchildren)
743 // clone each of the children's complex references, repoint them to the parent
744 // complex reference (because we're moving references up the tree), and finally
745 // add the cloned child's complex reference to the list of complex references
746 // that we'll eventually add to the parent group.
747 if (parentGroups.TryGetValue(childTypeAndId, out var referencesToChild))
748 {
749 foreach (var crefChild in referencesToChild)
750 {
751 // Only merge up the non-group items since groups are purged
752 // after this part of the processing anyway (cloning them would
753 // be a complete waste of time).
754 if ((ComplexReferenceChildType.FeatureGroup != crefChild.ChildType) ||
755 (ComplexReferenceChildType.ComponentGroup != crefChild.ChildType) ||
756 (ComplexReferenceChildType.PatchFamilyGroup != crefChild.ChildType))
757 {
758 var crefChildClone = crefChild.Clone();
759 Debug.Assert(crefChildClone.Parent == wixComplexReferenceRow.Child);
760
761 crefChildClone.Reparent(wixComplexReferenceRow);
762 allNewChildComplexReferences.Add(crefChildClone);
763 }
764 }
765 }
766 }
767 }
768
769 // Add the children group's complex references to the parent
770 // group. Clean out any left over groups and quietly remove any
771 // duplicate complex references that occurred during the merge.
772 referencesToParent.AddRange(allNewChildComplexReferences);
773 referencesToParent.Sort(ComplexReferenceComparision);
774 for (var i = referencesToParent.Count - 1; i >= 0; --i)
775 {
776 var wixComplexReferenceRow = referencesToParent[i];
777
778 if ((ComplexReferenceChildType.FeatureGroup == wixComplexReferenceRow.ChildType) ||
779 (ComplexReferenceChildType.ComponentGroup == wixComplexReferenceRow.ChildType) ||
780 (ComplexReferenceChildType.PatchFamilyGroup == wixComplexReferenceRow.ChildType))
781 {
782 referencesToParent.RemoveAt(i);
783 }
784 else if (i > 0)
785 {
786 // Since the list is already sorted, we can find duplicates by simply
787 // looking at the next sibling in the list and tossing out one if they
788 // match.
789 var crefCompare = referencesToParent[i - 1];
790 if (0 == wixComplexReferenceRow.CompareToWithoutConsideringPrimary(crefCompare))
791 {
792 referencesToParent.RemoveAt(i);
793 }
794 }
795 }
796
797 int ComplexReferenceComparision(WixComplexReferenceSymbol x, WixComplexReferenceSymbol y)
798 {
799 var comparison = x.ChildType - y.ChildType;
800 if (0 == comparison)
801 {
802 comparison = String.Compare(x.Child, y.Child, StringComparison.Ordinal);
803 if (0 == comparison)
804 {
805 comparison = x.ParentType - y.ParentType;
806 if (0 == comparison)
807 {
808 comparison = String.Compare(x.ParentLanguage ?? String.Empty, y.ParentLanguage ?? String.Empty, StringComparison.Ordinal);
809 if (0 == comparison)
810 {
811 comparison = String.Compare(x.Parent, y.Parent, StringComparison.Ordinal);
812 }
813 }
814 }
815 }
816
817 return comparison;
818 }
819
820 loopDetector.Pop(); // pop this complex reference off the stack since we're done verify the loop here
821 parentGroupsNeedingProcessing.Remove(parentTypeAndId); // remove the newly processed complex reference
822 }
823
824 /*
825 /// <summary>
826 /// Debugging method for displaying the section complex references.
827 /// </summary>
828 /// <param name="header">The header.</param>
829 /// <param name="sections">The sections to display.</param>
830 private void DisplaySectionComplexReferences(string header, SectionCollection sections)
831 {
832 Console.WriteLine(header);
833 foreach (Section section in sections)
834 {
835 Table wixComplexReferenceTable = section.Tables["WixComplexReference"];
836
837 foreach (WixComplexReferenceRow cref in wixComplexReferenceTable.Rows)
838 {
839 Console.WriteLine("Section: {0} Parent: {1} Type: {2} Child: {3} Primary: {4}", section.Id, cref.ParentId, cref.ParentType, cref.ChildId, cref.IsPrimary);
840 }
841 }
842 }
843 */
844
845 /// <summary>
846 /// Resolves the features connected to other features in the active output.
847 /// </summary>
848 /// <param name="featuresToFeatures">Feature to feature complex references.</param>
849 /// <param name="allSymbols">All symbols loaded from the sections.</param>
850 private void ResolveFeatureToFeatureConnects(ConnectToFeatureCollection featuresToFeatures, IDictionary<string, SymbolWithSection> allSymbols)
851 {
852 foreach (ConnectToFeature connection in featuresToFeatures)
853 {
854 var wixSimpleReferenceRow = new WixSimpleReferenceSymbol
855 {
856 Table = "Feature",
857 PrimaryKeys = connection.ChildId
858 };
859
860 if (allSymbols.TryGetValue(wixSimpleReferenceRow.SymbolicName, out var symbol))
861 {
862 var featureSymbol = (FeatureSymbol)symbol.Symbol;
863 featureSymbol.ParentFeatureRef = connection.PrimaryFeature;
864 }
865 }
866 }
867
868 /// <summary>
869 /// Resolve features for columns that have null guid placeholders.
870 /// </summary>
871 /// <param name="symbol">Symbol to resolve.</param>
872 /// <param name="connectionColumn">Number of the column containing the connection identifier.</param>
873 /// <param name="featureColumn">Number of the column containing the feature.</param>
874 /// <param name="connectToFeatures">Connect to feature complex references.</param>
875 /// <param name="multipleFeatureComponents">Hashtable of known components under multiple features.</param>
876 private void ResolveFeatures(IntermediateSymbol symbol, int connectionColumn, int featureColumn, ConnectToFeatureCollection connectToFeatures, Hashtable multipleFeatureComponents)
877 {
878 var connectionId = connectionColumn < 0 ? symbol.Id.Id : symbol.AsString(connectionColumn);
879 var featureId = symbol.AsString(featureColumn);
880
881 if (EmptyGuid == featureId)
882 {
883 var connection = connectToFeatures[connectionId];
884
885 if (null == connection)
886 {
887 // display an error for the component or merge module as appropriate
888 if (null != multipleFeatureComponents)
889 {
890 this.Messaging.Write(ErrorMessages.ComponentExpectedFeature(symbol.SourceLineNumbers, connectionId, symbol.Definition.Name, symbol.Id.Id));
891 }
892 else
893 {
894 this.Messaging.Write(ErrorMessages.MergeModuleExpectedFeature(symbol.SourceLineNumbers, connectionId));
895 }
896 }
897 else
898 {
899 // check for unique, implicit, primary feature parents with multiple possible parent features
900 if (this.ShowPedanticMessages &&
901 !connection.IsExplicitPrimaryFeature &&
902 0 < connection.ConnectFeatures.Count)
903 {
904 // display a warning for the component or merge module as approrpriate
905 if (null != multipleFeatureComponents)
906 {
907 if (!multipleFeatureComponents.Contains(connectionId))
908 {
909 this.Messaging.Write(WarningMessages.ImplicitComponentPrimaryFeature(connectionId));
910
911 // remember this component so only one warning is generated for it
912 multipleFeatureComponents[connectionId] = null;
913 }
914 }
915 else
916 {
917 this.Messaging.Write(WarningMessages.ImplicitMergeModulePrimaryFeature(connectionId));
918 }
919 }
920
921 // set the feature
922 symbol.Set(featureColumn, connection.PrimaryFeature);
923 }
924 }
925 }
926 }
927 }