aboutsummaryrefslogtreecommitdiff
path: root/src/tools/heat/UtilMutator.cs
diff options
context:
space:
mode:
authorRob Mensching <rob@firegiant.com>2022-07-26 17:20:39 -0700
committerRob Mensching <rob@firegiant.com>2022-08-01 20:25:19 -0700
commita627ca9b720047e633a8fe72003ab9bee31006c5 (patch)
tree2bc8a924bb4141ab718e74d08f6459a0ffe8d573 /src/tools/heat/UtilMutator.cs
parent521eb3c9cf38823a2c4019abb85dc0b3200b92cb (diff)
downloadwix-a627ca9b720047e633a8fe72003ab9bee31006c5.tar.gz
wix-a627ca9b720047e633a8fe72003ab9bee31006c5.tar.bz2
wix-a627ca9b720047e633a8fe72003ab9bee31006c5.zip
Create WixToolset.Heat.nupkg to distribute heat.exe and Heat targets
Moves Heat functionality to the "tools" layer and packages it all up in WixToolset.Heat.nupkg for distribution in WiX v4. Completes 6838
Diffstat (limited to 'src/tools/heat/UtilMutator.cs')
-rw-r--r--src/tools/heat/UtilMutator.cs633
1 files changed, 633 insertions, 0 deletions
diff --git a/src/tools/heat/UtilMutator.cs b/src/tools/heat/UtilMutator.cs
new file mode 100644
index 00000000..0bc15cd6
--- /dev/null
+++ b/src/tools/heat/UtilMutator.cs
@@ -0,0 +1,633 @@
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
3namespace WixToolset.Harvesters
4{
5 using System;
6 using System.Collections;
7 using System.Diagnostics;
8 using System.Globalization;
9 using System.IO;
10 using WixToolset.Harvesters.Extensibility;
11 using Wix = WixToolset.Harvesters.Serialize;
12
13 /// <summary>
14 /// The template type.
15 /// </summary>
16 public enum TemplateType
17 {
18 /// <summary>
19 /// A fragment template.
20 /// </summary>
21 Fragment,
22
23 /// <summary>
24 /// A module template.
25 /// </summary>
26 Module,
27
28 /// <summary>
29 /// A product template.
30 /// </summary>
31 Package
32 }
33
34 /// <summary>
35 /// The mutator for the WiX Toolset Internet Information Services Extension.
36 /// </summary>
37 public sealed class UtilMutator : BaseMutatorExtension
38 {
39 private ArrayList components;
40 private ArrayList componentGroups;
41 private string componentGroupName;
42 private bool createFragments;
43 private ArrayList directories;
44 private ArrayList directoryRefs;
45 private ArrayList files;
46 private ArrayList features;
47 private SortedList fragments;
48 private bool autogenerateGuids;
49 private bool generateGuids;
50 private string guidFormat = "B"; // Defaults to guid in {}
51 private Wix.IParentElement rootElement;
52 private bool setUniqueIdentifiers;
53 private TemplateType templateType;
54
55 /// <summary>
56 /// Instantiate a new UtilMutator.
57 /// </summary>
58 public UtilMutator()
59 {
60 this.components = new ArrayList();
61 this.componentGroups = new ArrayList();
62 this.directories = new ArrayList();
63 this.directoryRefs = new ArrayList();
64 this.features = new ArrayList();
65 this.files = new ArrayList();
66 this.fragments = new SortedList();
67 }
68
69 /// <summary>
70 /// Gets or sets the value of the component group name.
71 /// </summary>
72 /// <value>The component group name.</value>
73 public string ComponentGroupName
74 {
75 get { return this.componentGroupName; }
76 set { this.componentGroupName = value; }
77 }
78
79 /// <summary>
80 /// Gets or sets the option to create fragments.
81 /// </summary>
82 /// <value>The option to create fragments.</value>
83 public bool CreateFragments
84 {
85 get { return this.createFragments; }
86 set { this.createFragments = value; }
87 }
88
89 /// <summary>
90 /// Gets or sets the option to autogenerate component guids at compile time.
91 /// </summary>
92 /// <value>The option to autogenerate component guids.</value>
93 public bool AutogenerateGuids
94 {
95 get { return this.autogenerateGuids; }
96 set { this.autogenerateGuids = value; }
97 }
98
99 /// <summary>
100 /// Gets or sets the option to generate missing guids.
101 /// </summary>
102 /// <value>The option to generate missing guids.</value>
103 public bool GenerateGuids
104 {
105 get { return this.generateGuids; }
106 set { this.generateGuids = value; }
107 }
108
109 /// <summary>
110 /// Gets or sets the option to set the format of guids.
111 /// D - 32 digits separated by hyphens:
112 /// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
113 /// B - 32 digits separated by hyphens, enclosed in brackets:
114 /// {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
115 /// </summary>
116 /// <value>Guid format either B or D.</value>
117 public string GuidFormat
118 {
119 get { return this.guidFormat; }
120 set { this.guidFormat = value; }
121 }
122
123 /// <summary>
124 /// Gets the sequence of the extension.
125 /// </summary>
126 /// <value>The sequence of the extension.</value>
127 public override int Sequence
128 {
129 get { return 1000; }
130 }
131
132 /// <summary>
133 /// Gets of sets the option to set unique identifiers.
134 /// </summary>
135 /// <value>The option to set unique identifiers.</value>
136 public bool SetUniqueIdentifiers
137 {
138 get { return this.setUniqueIdentifiers; }
139 set { this.setUniqueIdentifiers = value; }
140 }
141
142 /// <summary>
143 /// Gets or sets the template type.
144 /// </summary>
145 /// <value>The template type.</value>
146 public TemplateType TemplateType
147 {
148 get { return this.templateType; }
149 set { this.templateType = value; }
150 }
151
152 /// <summary>
153 /// Mutate a WiX document.
154 /// </summary>
155 /// <param name="wix">The Wix document element.</param>
156 public override void Mutate(Wix.Wix wix)
157 {
158 this.components.Clear();
159 this.directories.Clear();
160 this.directoryRefs.Clear();
161 this.features.Clear();
162 this.files.Clear();
163 this.fragments.Clear();
164 this.rootElement = null;
165
166 // index elements in this wix document
167 this.IndexElement(wix);
168
169 this.MutateWix(wix);
170
171 this.MutateFiles();
172
173 this.MutateDirectories();
174
175 this.MutateComponents();
176
177 if (null != this.componentGroupName)
178 {
179 this.CreateComponentGroup(wix);
180 }
181
182 // add the components to the product feature after all the identifiers have been set
183 if (TemplateType.Package == this.templateType)
184 {
185 Wix.Feature feature = (Wix.Feature)this.features[0];
186
187 foreach (Wix.ComponentGroup group in this.componentGroups)
188 {
189 Wix.ComponentGroupRef componentGroupRef = new Wix.ComponentGroupRef();
190 componentGroupRef.Id = group.Id;
191
192 feature.AddChild(componentGroupRef);
193 }
194 }
195 else if (TemplateType.Module == this.templateType)
196 {
197 foreach (Wix.ISchemaElement element in wix.Children)
198 {
199 if (element is Wix.Module)
200 {
201 foreach (Wix.ComponentGroup group in this.componentGroups)
202 {
203 Wix.ComponentGroupRef componentGroupRef = new Wix.ComponentGroupRef();
204 componentGroupRef.Id = group.Id;
205
206 ((Wix.IParentElement)element).AddChild(componentGroupRef);
207 }
208 break;
209 }
210 }
211 }
212
213 //if(!this.createFragments && TemplateType.Package
214 foreach (Wix.Fragment fragment in this.fragments.Values)
215 {
216 wix.AddChild(fragment);
217 }
218 }
219
220 /// <summary>
221 /// Creates a component group with a given name.
222 /// </summary>
223 /// <param name="wix">The Wix document element.</param>
224 private void CreateComponentGroup(Wix.Wix wix)
225 {
226 Wix.ComponentGroup componentGroup = new Wix.ComponentGroup();
227 componentGroup.Id = this.componentGroupName;
228 this.componentGroups.Add(componentGroup);
229
230 Wix.Fragment cgFragment = new Wix.Fragment();
231 cgFragment.AddChild(componentGroup);
232 wix.AddChild(cgFragment);
233
234 int componentCount = 0;
235 for (; componentCount < this.components.Count; componentCount++)
236 {
237 Wix.Component c = this.components[componentCount] as Wix.Component;
238
239 if (this.createFragments)
240 {
241 if (c.ParentElement is Wix.Directory)
242 {
243 Wix.Directory parentDirectory = c.ParentElement as Wix.Directory;
244
245 componentGroup.AddChild(c);
246 c.Directory = parentDirectory.Id;
247 parentDirectory.RemoveChild(c);
248 }
249 else if (c.ParentElement is Wix.DirectoryRef)
250 {
251 Wix.DirectoryRef parentDirectory = c.ParentElement as Wix.DirectoryRef;
252
253 componentGroup.AddChild(c);
254 c.Directory = parentDirectory.Id;
255 parentDirectory.RemoveChild(c);
256
257 // Remove whole fragment if moving the component to the component group just leaves an empty DirectoryRef
258 if (0 < this.fragments.Count && parentDirectory.ParentElement is Wix.Fragment)
259 {
260 Wix.Fragment parentFragment = parentDirectory.ParentElement as Wix.Fragment;
261 int childCount = 0;
262 foreach (Wix.ISchemaElement element in parentFragment.Children)
263 {
264 childCount++;
265 }
266
267 // Component should always have an Id but the SortedList creation allows for null and bases the name on the fragment count which we cannot reverse engineer here.
268 if (1 == childCount && !String.IsNullOrEmpty(c.Id))
269 {
270 int removeIndex = this.fragments.IndexOfKey(String.Concat("Component:", c.Id));
271 if (0 <= removeIndex)
272 {
273 this.fragments.RemoveAt(removeIndex);
274 }
275 }
276 }
277 }
278 }
279 else
280 {
281 Wix.ComponentRef componentRef = new Wix.ComponentRef();
282 componentRef.Id = c.Id;
283 componentGroup.AddChild(componentRef);
284 }
285 }
286 }
287
288 /// <summary>
289 /// Index an element.
290 /// </summary>
291 /// <param name="element">The element to index.</param>
292 private void IndexElement(Wix.ISchemaElement element)
293 {
294 if (element is Wix.Component)
295 {
296 this.components.Add(element);
297 }
298 else if (element is Wix.ComponentGroup)
299 {
300 this.componentGroups.Add(element);
301 }
302 else if (element is Wix.Directory)
303 {
304 this.directories.Add(element);
305 }
306 else if (element is Wix.DirectoryRef)
307 {
308 this.directoryRefs.Add(element);
309 }
310 else if (element is Wix.Feature)
311 {
312 this.features.Add(element);
313 }
314 else if (element is Wix.File)
315 {
316 this.files.Add(element);
317 }
318 else if (element is Wix.Module || element is Wix.PatchCreation || element is Wix.Package)
319 {
320 Debug.Assert(null == this.rootElement);
321 this.rootElement = (Wix.IParentElement)element;
322 }
323
324 // index the child elements
325 if (element is Wix.IParentElement)
326 {
327 foreach (Wix.ISchemaElement childElement in ((Wix.IParentElement)element).Children)
328 {
329 this.IndexElement(childElement);
330 }
331 }
332 }
333
334 /// <summary>
335 /// Mutate the components.
336 /// </summary>
337 private void MutateComponents()
338 {
339 IdentifierGenerator identifierGenerator = new IdentifierGenerator("Component", this.Core);
340 if (TemplateType.Module == this.templateType)
341 {
342 identifierGenerator.MaxIdentifierLength = IdentifierGenerator.MaxModuleIdentifierLength;
343 }
344
345 foreach (Wix.Component component in this.components)
346 {
347 if (null == component.Id)
348 {
349 string firstFileId = string.Empty;
350
351 // attempt to create a possible identifier from the first file identifier in the component
352 foreach (Wix.File file in component[typeof(Wix.File)])
353 {
354 firstFileId = file.Id;
355 break;
356 }
357
358 if (string.IsNullOrEmpty(firstFileId))
359 {
360 firstFileId = this.GetGuid();
361 }
362
363 component.Id = identifierGenerator.GetIdentifier(firstFileId);
364 }
365
366 if (null == component.Guid)
367 {
368 if (this.AutogenerateGuids)
369 {
370 component.Guid = "*";
371 }
372 else
373 {
374 component.Guid = this.GetGuid();
375 }
376 }
377
378 if (this.createFragments && component.ParentElement is Wix.Directory)
379 {
380 Wix.Directory directory = (Wix.Directory)component.ParentElement;
381
382 // parent directory must have an identifier to create a reference to it
383 if (null == directory.Id)
384 {
385 break;
386 }
387
388 if (this.rootElement is Wix.Module)
389 {
390 // add a ComponentRef for the Component
391 Wix.ComponentRef componentRef = new Wix.ComponentRef();
392 componentRef.Id = component.Id;
393 this.rootElement.AddChild(componentRef);
394 }
395
396 // create a new Fragment
397 Wix.Fragment fragment = new Wix.Fragment();
398 this.fragments.Add(String.Concat("Component:", (null != component.Id ? component.Id : this.fragments.Count.ToString())), fragment);
399
400 // create a new DirectoryRef
401 Wix.DirectoryRef directoryRef = new Wix.DirectoryRef();
402 directoryRef.Id = directory.Id;
403 fragment.AddChild(directoryRef);
404
405 // move the Component from the the Directory to the DirectoryRef
406 directory.RemoveChild(component);
407 directoryRef.AddChild(component);
408 }
409 }
410 }
411
412 /// <summary>
413 /// Mutate the directories.
414 /// </summary>
415 private void MutateDirectories()
416 {
417 if (!this.setUniqueIdentifiers)
418 {
419 // assign all identifiers before fragmenting (because fragmenting requires them all to be present)
420 IdentifierGenerator identifierGenerator = new IdentifierGenerator("Directory", this.Core);
421 if (TemplateType.Module == this.templateType)
422 {
423 identifierGenerator.MaxIdentifierLength = IdentifierGenerator.MaxModuleIdentifierLength;
424 }
425
426 foreach (Wix.Directory directory in this.directories)
427 {
428 if (null == directory.Id)
429 {
430 directory.Id = identifierGenerator.GetIdentifier(directory.Name);
431 }
432 }
433 }
434
435 if (this.createFragments)
436 {
437 foreach (Wix.Directory directory in this.directories)
438 {
439 if (directory.ParentElement is Wix.Directory)
440 {
441 Wix.Directory parentDirectory = (Wix.Directory)directory.ParentElement;
442
443 // parent directory must have an identifier to create a reference to it
444 if (null == parentDirectory.Id)
445 {
446 return;
447 }
448
449 // create a new Fragment
450 Wix.Fragment fragment = new Wix.Fragment();
451 this.fragments.Add(String.Concat("Directory:", ("TARGETDIR" == directory.Id ? null : (null != directory.Id ? directory.Id : this.fragments.Count.ToString()))), fragment);
452
453 // create a new DirectoryRef
454 Wix.DirectoryRef directoryRef = new Wix.DirectoryRef();
455 directoryRef.Id = parentDirectory.Id;
456 fragment.AddChild(directoryRef);
457
458 // move the Directory from the parent Directory to DirectoryRef
459 parentDirectory.RemoveChild(directory);
460 directoryRef.AddChild(directory);
461 }
462 else if (directory.ParentElement is Wix.Fragment)
463 {
464 // When creating fragments, remove any top-level Directory elements;
465 // the fragments should be pulled in by their DirectoryRefs instead.
466 Wix.Fragment parent = (Wix.Fragment)directory.ParentElement;
467 parent.RemoveChild(directory);
468
469 // Remove the fragment if it is empty.
470 if (parent.Children.GetEnumerator().Current == null && parent.ParentElement != null)
471 {
472 ((Wix.IParentElement)parent.ParentElement).RemoveChild(parent);
473 }
474 }
475 else if (directory.ParentElement == this.rootElement)
476 {
477 // create a new Fragment
478 Wix.Fragment fragment = new Wix.Fragment();
479 this.fragments.Add(String.Concat("Directory:", ("TARGETDIR" == directory.Id ? null : (null != directory.Id ? directory.Id : this.fragments.Count.ToString()))), fragment);
480
481 // move the Directory from the root element to the Fragment
482 this.rootElement.RemoveChild(directory);
483 fragment.AddChild(directory);
484 }
485 }
486 }
487 }
488
489 /// <summary>
490 /// Mutate the files.
491 /// </summary>
492 private void MutateFiles()
493 {
494 IdentifierGenerator identifierGenerator = new IdentifierGenerator("File", this.Core);
495 if (TemplateType.Module == this.templateType)
496 {
497 identifierGenerator.MaxIdentifierLength = IdentifierGenerator.MaxModuleIdentifierLength;
498 }
499
500 foreach (Wix.File file in this.files)
501 {
502 if (null == file.Id)
503 {
504 file.Id = identifierGenerator.GetIdentifier(Path.GetFileName(file.Source));
505 }
506 }
507 }
508
509 /// <summary>
510 /// Mutate a Wix element.
511 /// </summary>
512 /// <param name="wix">The Wix element to mutate.</param>
513 private void MutateWix(Wix.Wix wix)
514 {
515 if (TemplateType.Fragment != this.templateType)
516 {
517 if (null != this.rootElement || 0 != this.features.Count)
518 {
519 throw new Exception("The template option cannot be used with Feature, Package, or Module elements present.");
520 }
521
522 // create a package element although it won't always be used
523 Wix.SummaryInformation package = new Wix.SummaryInformation();
524 if (TemplateType.Module == this.templateType)
525 {
526 package.Id = this.GetGuid();
527 }
528 else
529 {
530 package.Compressed = Wix.YesNoType.yes;
531 }
532
533 package.InstallerVersion = 200;
534
535 Wix.Directory targetDir = new Wix.Directory();
536 targetDir.Id = "TARGETDIR";
537 targetDir.Name = "SourceDir";
538
539 foreach (Wix.DirectoryRef directoryRef in this.directoryRefs)
540 {
541 if (String.Equals(directoryRef.Id, "TARGETDIR", StringComparison.OrdinalIgnoreCase))
542 {
543 Wix.IParentElement parent = directoryRef.ParentElement as Wix.IParentElement;
544
545 foreach (Wix.ISchemaElement element in directoryRef.Children)
546 {
547 targetDir.AddChild(element);
548 }
549
550 parent.RemoveChild(directoryRef);
551
552 if (null != ((Wix.ISchemaElement)parent).ParentElement)
553 {
554 int i = 0;
555
556 foreach (Wix.ISchemaElement element in parent.Children)
557 {
558 i++;
559 }
560
561 if (0 == i)
562 {
563 Wix.IParentElement supParent = (Wix.IParentElement)((Wix.ISchemaElement)parent).ParentElement;
564 supParent.RemoveChild((Wix.ISchemaElement)parent);
565 }
566 }
567
568 break;
569 }
570 }
571
572 if (TemplateType.Module == this.templateType)
573 {
574 Wix.Module module = new Wix.Module();
575 module.Id = "PUT-MODULE-NAME-HERE";
576 module.Language = "1033";
577 module.Version = "1.0.0.0";
578
579 package.Manufacturer = "PUT-COMPANY-NAME-HERE";
580 module.AddChild(package);
581 module.AddChild(targetDir);
582
583 wix.AddChild(module);
584 this.rootElement = module;
585 }
586 else // product
587 {
588 Wix.Package product = new Wix.Package();
589 product.Id = this.GetGuid();
590 product.Language = "1033";
591 product.Manufacturer = "PUT-COMPANY-NAME-HERE";
592 product.Name = "PUT-PRODUCT-NAME-HERE";
593 product.UpgradeCode = this.GetGuid();
594 product.Version = "1.0.0.0";
595 product.AddChild(package);
596 product.AddChild(targetDir);
597
598 Wix.Media media = new Wix.Media();
599 media.Id = "1";
600 media.Cabinet = "product.cab";
601 media.EmbedCab = Wix.YesNoType.yes;
602 product.AddChild(media);
603
604 Wix.Feature feature = new Wix.Feature();
605 feature.Id = "ProductFeature";
606 feature.Title = "PUT-FEATURE-TITLE-HERE";
607 feature.Level = 1;
608 product.AddChild(feature);
609 this.features.Add(feature);
610
611 wix.AddChild(product);
612 this.rootElement = product;
613 }
614 }
615 }
616
617 /// <summary>
618 /// Get a generated guid or a placeholder for a guid.
619 /// </summary>
620 /// <returns>A generated guid or placeholder.</returns>
621 private string GetGuid()
622 {
623 if (this.generateGuids)
624 {
625 return Guid.NewGuid().ToString(this.guidFormat, CultureInfo.InvariantCulture).ToUpper(CultureInfo.InvariantCulture);
626 }
627 else
628 {
629 return "PUT-GUID-HERE";
630 }
631 }
632 }
633}