From a6013a643208a8d1fc2d1136ef8d3a6c3e909522 Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Sun, 28 Feb 2021 21:04:16 -0600 Subject: Refactor payload compiling and harvesting. --- src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs | 8 +- .../Bundles/ProcessPayloadsCommand.cs | 49 +-- .../ExtensibilityServices/PayloadHarvester.cs | 68 ++++ .../Interfaces/IPayloadHarvester.cs | 23 ++ .../WixToolsetCoreServiceProviderExtensions.cs | 2 + src/WixToolset.Core/Compile/CompilerPayload.cs | 247 +++++++++++ src/WixToolset.Core/CompilerCore.cs | 40 ++ src/WixToolset.Core/Compiler_Bundle.cs | 452 +++++++-------------- 8 files changed, 546 insertions(+), 343 deletions(-) create mode 100644 src/WixToolset.Core.Burn/ExtensibilityServices/PayloadHarvester.cs create mode 100644 src/WixToolset.Core.Burn/Interfaces/IPayloadHarvester.cs create mode 100644 src/WixToolset.Core/Compile/CompilerPayload.cs diff --git a/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs b/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs index c9a111c6..724dd7ff 100644 --- a/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs +++ b/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs @@ -11,6 +11,7 @@ namespace WixToolset.Core.Burn using WixToolset.Core.Bind; using WixToolset.Core.Burn.Bind; using WixToolset.Core.Burn.Bundles; + using WixToolset.Core.Burn.Interfaces; using WixToolset.Data; using WixToolset.Data.Burn; using WixToolset.Data.Symbols; @@ -31,6 +32,7 @@ namespace WixToolset.Core.Burn this.BackendHelper = context.ServiceProvider.GetService(); this.InternalBurnBackendHelper = context.ServiceProvider.GetService(); + this.PayloadHarvester = context.ServiceProvider.GetService(); this.DefaultCompressionLevel = context.DefaultCompressionLevel; this.DelayedFields = context.DelayedFields; @@ -52,6 +54,8 @@ namespace WixToolset.Core.Burn private IInternalBurnBackendHelper InternalBurnBackendHelper { get; } + private IPayloadHarvester PayloadHarvester { get; } + private CompressionLevel? DefaultCompressionLevel { get; } public IEnumerable DelayedFields { get; } @@ -165,7 +169,7 @@ namespace WixToolset.Core.Burn // Process the explicitly authored payloads. ISet processedPayloads; { - var command = new ProcessPayloadsCommand(this.ServiceProvider, this.BackendHelper, payloadSymbols.Values, bundleSymbol.DefaultPackagingType, layoutDirectory); + var command = new ProcessPayloadsCommand(this.ServiceProvider, this.BackendHelper, this.PayloadHarvester, payloadSymbols.Values, bundleSymbol.DefaultPackagingType, layoutDirectory); command.Execute(); fileTransfers.AddRange(command.FileTransfers); @@ -247,7 +251,7 @@ namespace WixToolset.Core.Burn { var toProcess = payloadSymbols.Values.Where(r => !processedPayloads.Contains(r.Id.Id)).ToList(); - var command = new ProcessPayloadsCommand(this.ServiceProvider, this.BackendHelper, toProcess, bundleSymbol.DefaultPackagingType, layoutDirectory); + var command = new ProcessPayloadsCommand(this.ServiceProvider, this.BackendHelper, this.PayloadHarvester, toProcess, bundleSymbol.DefaultPackagingType, layoutDirectory); command.Execute(); fileTransfers.AddRange(command.FileTransfers); diff --git a/src/WixToolset.Core.Burn/Bundles/ProcessPayloadsCommand.cs b/src/WixToolset.Core.Burn/Bundles/ProcessPayloadsCommand.cs index db5b03fb..dea5b336 100644 --- a/src/WixToolset.Core.Burn/Bundles/ProcessPayloadsCommand.cs +++ b/src/WixToolset.Core.Burn/Bundles/ProcessPayloadsCommand.cs @@ -6,6 +6,7 @@ namespace WixToolset.Core.Burn.Bundles using System.Collections.Generic; using System.Diagnostics; using System.IO; + using WixToolset.Core.Burn.Interfaces; using WixToolset.Data; using WixToolset.Data.Burn; using WixToolset.Data.Symbols; @@ -14,13 +15,12 @@ namespace WixToolset.Core.Burn.Bundles internal class ProcessPayloadsCommand { - private static readonly Version EmptyVersion = new Version(0, 0, 0, 0); - - public ProcessPayloadsCommand(IWixToolsetServiceProvider serviceProvider, IBackendHelper backendHelper, IEnumerable payloads, PackagingType defaultPackaging, string layoutDirectory) + public ProcessPayloadsCommand(IWixToolsetServiceProvider serviceProvider, IBackendHelper backendHelper, IPayloadHarvester payloadHarvester, IEnumerable payloads, PackagingType defaultPackaging, string layoutDirectory) { this.Messaging = serviceProvider.GetService(); this.BackendHelper = backendHelper; + this.PayloadHarvester = payloadHarvester; this.Payloads = payloads; this.DefaultPackaging = defaultPackaging; this.LayoutDirectory = layoutDirectory; @@ -34,6 +34,8 @@ namespace WixToolset.Core.Burn.Bundles private IBackendHelper BackendHelper { get; } + private IPayloadHarvester PayloadHarvester { get; } + private IEnumerable Payloads { get; } private PackagingType DefaultPackaging { get; } @@ -56,17 +58,13 @@ namespace WixToolset.Core.Burn.Bundles this.UpdatePayloadPackagingType(payload); - if (String.IsNullOrEmpty(sourceFile?.Path)) + if (!this.PayloadHarvester.HarvestStandardInformation(payload)) { // Remote payloads obviously cannot be embedded. Debug.Assert(PackagingType.Embedded != payload.Packaging); } else // not a remote payload so we have a lot more to update. { - this.UpdatePayloadFileInformation(payload, sourceFile); - - this.UpdatePayloadVersionInformation(payload, sourceFile); - // External payloads need to be transfered. if (PackagingType.External == payload.Packaging) { @@ -110,40 +108,5 @@ namespace WixToolset.Core.Burn.Bundles payload.ContainerRef = BurnConstants.BurnDefaultAttachedContainerName; } } - - private void UpdatePayloadFileInformation(WixBundlePayloadSymbol payload, IntermediateFieldPathValue sourceFile) - { - var fileInfo = new FileInfo(sourceFile.Path); - - if (null != fileInfo) - { - payload.FileSize = fileInfo.Length; - - payload.Hash = BundleHashAlgorithm.Hash(fileInfo); - } - else - { - payload.FileSize = 0; - } - } - - private void UpdatePayloadVersionInformation(WixBundlePayloadSymbol payload, IntermediateFieldPathValue sourceFile) - { - var versionInfo = FileVersionInfo.GetVersionInfo(sourceFile.Path); - - if (null != versionInfo) - { - // Use the fixed version info block for the file since the resource text may not be a dotted quad. - var version = new Version(versionInfo.ProductMajorPart, versionInfo.ProductMinorPart, versionInfo.ProductBuildPart, versionInfo.ProductPrivatePart); - - if (ProcessPayloadsCommand.EmptyVersion != version) - { - payload.Version = version.ToString(); - } - - payload.Description = versionInfo.FileDescription; - payload.DisplayName = versionInfo.ProductName; - } - } } } diff --git a/src/WixToolset.Core.Burn/ExtensibilityServices/PayloadHarvester.cs b/src/WixToolset.Core.Burn/ExtensibilityServices/PayloadHarvester.cs new file mode 100644 index 00000000..9ef91028 --- /dev/null +++ b/src/WixToolset.Core.Burn/ExtensibilityServices/PayloadHarvester.cs @@ -0,0 +1,68 @@ +// 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.Burn.ExtensibilityServices +{ + using System; + using System.Diagnostics; + using System.IO; + using WixToolset.Core.Burn.Bundles; + using WixToolset.Core.Burn.Interfaces; + using WixToolset.Data.Symbols; + + internal class PayloadHarvester : IPayloadHarvester + { + private static readonly Version EmptyVersion = new Version(0, 0, 0, 0); + + /// + public bool HarvestStandardInformation(WixBundlePayloadSymbol payload) + { + var filePath = payload.SourceFile?.Path; + + if (String.IsNullOrEmpty(filePath)) + { + return false; + } + + this.UpdatePayloadFileInformation(payload, filePath); + + this.UpdatePayloadVersionInformation(payload, filePath); + + return true; + } + + private void UpdatePayloadFileInformation(WixBundlePayloadSymbol payload, string filePath) + { + var fileInfo = new FileInfo(filePath); + + if (null != fileInfo) + { + payload.FileSize = fileInfo.Length; + + payload.Hash = BundleHashAlgorithm.Hash(fileInfo); + } + else + { + payload.FileSize = 0; + } + } + + private void UpdatePayloadVersionInformation(WixBundlePayloadSymbol payload, string filePath) + { + var versionInfo = FileVersionInfo.GetVersionInfo(filePath); + + if (null != versionInfo) + { + // Use the fixed version info block for the file since the resource text may not be a dotted quad. + var version = new Version(versionInfo.ProductMajorPart, versionInfo.ProductMinorPart, versionInfo.ProductBuildPart, versionInfo.ProductPrivatePart); + + if (PayloadHarvester.EmptyVersion != version) + { + payload.Version = version.ToString(); + } + + payload.Description = versionInfo.FileDescription; + payload.DisplayName = versionInfo.ProductName; + } + } + } +} diff --git a/src/WixToolset.Core.Burn/Interfaces/IPayloadHarvester.cs b/src/WixToolset.Core.Burn/Interfaces/IPayloadHarvester.cs new file mode 100644 index 00000000..1bafa46e --- /dev/null +++ b/src/WixToolset.Core.Burn/Interfaces/IPayloadHarvester.cs @@ -0,0 +1,23 @@ +// 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.Burn.Interfaces +{ + using System.Diagnostics; + using WixToolset.Data.Symbols; + + /// + /// Service for harvesting payload information. + /// + public interface IPayloadHarvester + { + /// + /// Uses to: + /// update from file contents, + /// update from file size, and + /// update , , and from . + /// + /// The symbol to update. + /// Whether the symbol had a source file specified. + bool HarvestStandardInformation(WixBundlePayloadSymbol payload); + } +} diff --git a/src/WixToolset.Core.Burn/WixToolsetCoreServiceProviderExtensions.cs b/src/WixToolset.Core.Burn/WixToolsetCoreServiceProviderExtensions.cs index b0401b4a..58076d5e 100644 --- a/src/WixToolset.Core.Burn/WixToolsetCoreServiceProviderExtensions.cs +++ b/src/WixToolset.Core.Burn/WixToolsetCoreServiceProviderExtensions.cs @@ -5,6 +5,7 @@ namespace WixToolset.Core.Burn using System; using System.Collections.Generic; using WixToolset.Core.Burn.ExtensibilityServices; + using WixToolset.Core.Burn.Interfaces; using WixToolset.Extensibility.Services; /// @@ -31,6 +32,7 @@ namespace WixToolset.Core.Burn { // Singletons. coreProvider.AddService((provider, singletons) => AddSingleton(singletons, new BurnBackendHelper(provider))); + coreProvider.AddService((provider, singletons) => AddSingleton(singletons, new PayloadHarvester())); coreProvider.AddService((provider, singletons) => AddSingleton(singletons, provider.GetService())); } diff --git a/src/WixToolset.Core/Compile/CompilerPayload.cs b/src/WixToolset.Core/Compile/CompilerPayload.cs new file mode 100644 index 00000000..4eda56f8 --- /dev/null +++ b/src/WixToolset.Core/Compile/CompilerPayload.cs @@ -0,0 +1,247 @@ +// 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.IO; + using System.Xml.Linq; + using WixToolset.Data; + using WixToolset.Data.Burn; + using WixToolset.Data.Symbols; + + internal class CompilerPayload + { + public YesNoDefaultType Compressed { get; set; } = YesNoDefaultType.Default; + + public string Description { get; set; } + + public string DisplayName { get; set; } + + public string DownloadUrl { get; set; } + + public string Hash { get; set; } + + public Identifier Id { get; set; } + + public bool IsRequired { get; set; } = true; + + public string Name { get; set; } + + public string ProductName { get; set; } + + public long? Size { get; set; } + + public string SourceFile { get; set; } + + public string Version { get; set; } + + public CompilerPayload(CompilerCore core, SourceLineNumber sourceLineNumbers, XElement element) + { + this.Core = core; + this.Element = element; + this.SourceLineNumbers = sourceLineNumbers; + } + + private CompilerCore Core { get; } + + private XElement Element { get; } + + private SourceLineNumber SourceLineNumbers { get; } + + private void CalculateAndVerifyFields(CompilerPayload remotePayload = null) + { + if (String.IsNullOrEmpty(this.SourceFile)) + { + if (String.IsNullOrEmpty(this.Name)) + { + if (this.IsRequired) + { + this.Core.Write(ErrorMessages.ExpectedAttributesWithOtherAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Name", "SourceFile")); + } + } + else if (remotePayload == null) + { + this.SourceFile = Path.Combine("SourceDir", this.Name); + } + } + else if (remotePayload != null) + { + this.Core.Write(ErrorMessages.UnexpectedElementWithAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "RemotePayload", "SourceFile")); + } + else if (this.SourceFile.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)) + { + if (String.IsNullOrEmpty(this.Name)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Name", "SourceFile", this.SourceFile)); + } + else + { + this.SourceFile = Path.Combine(this.SourceFile, Path.GetFileName(this.Name)); + } + } + + if (remotePayload != null) + { + if (this.DownloadUrl == null) + { + this.Core.Write(ErrorMessages.ExpectedAttributeWithElement(this.SourceLineNumbers, this.Element.Name.LocalName, "DownloadUrl", "RemotePayload")); + } + + if (YesNoDefaultType.No != this.Compressed) + { + this.Compressed = YesNoDefaultType.No; + this.Core.Write(WarningMessages.RemotePayloadsMustNotAlsoBeCompressed(this.SourceLineNumbers, this.Element.Name.LocalName)); + } + + this.Description = remotePayload.Description; + this.DisplayName = remotePayload.DisplayName; + this.Hash = remotePayload.Hash; + this.Size = remotePayload.Size; + this.Version = remotePayload.Version; + } + } + + public WixBundlePayloadSymbol CreatePayloadSymbol(ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType = ComplexReferenceChildType.Unknown, string previousId = null) + { + WixBundlePayloadSymbol symbol = null; + + if (parentType == ComplexReferenceParentType.Container && parentId == BurnConstants.BurnUXContainerName) + { + if (this.Compressed == YesNoDefaultType.No) + { + this.Core.Write(WarningMessages.UxPayloadsOnlySupportEmbedding(this.SourceLineNumbers, this.SourceFile)); + } + + if (!String.IsNullOrEmpty(this.DownloadUrl)) + { + this.Core.Write(WarningMessages.DownloadUrlNotSupportedForEmbeddedPayloads(this.SourceLineNumbers, this.Id.Id)); + } + + this.Compressed = YesNoDefaultType.Yes; + this.DownloadUrl = null; + } + + if (!this.Core.EncounteredError) + { + symbol = this.Core.AddSymbol(new WixBundlePayloadSymbol(this.SourceLineNumbers, this.Id) + { + Name = String.IsNullOrEmpty(this.Name) ? Path.GetFileName(this.SourceFile) : this.Name, + SourceFile = new IntermediateFieldPathValue { Path = this.SourceFile }, + DownloadUrl = this.DownloadUrl, + Compressed = (this.Compressed == YesNoDefaultType.Yes) ? true : (this.Compressed == YesNoDefaultType.No) ? (bool?)false : null, + UnresolvedSourceFile = this.SourceFile, // duplicate of sourceFile but in a string column so it won't get resolved to a full path during binding. + DisplayName = this.DisplayName ?? this.ProductName, + Description = this.Description, + Hash = this.Hash, + FileSize = this.Size, + Version = this.Version, + }); + + this.Core.CreateGroupAndOrderingRows(this.SourceLineNumbers, parentType, parentId, ComplexReferenceChildType.Payload, symbol.Id.Id, previousType, previousId); + } + + return symbol; + } + + public void FinishCompilingPackage(CompilerPayload remotePayload) + { + this.CalculateAndVerifyFields(remotePayload); + this.GenerateIdFromFilename(); + + if (this.Id == null) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Id")); + this.Id = Identifier.Invalid; + } + } + + public void FinishCompilingPayload() + { + this.CalculateAndVerifyFields(); + this.GenerateIdFromPrefix("pay"); + } + + private void GenerateIdFromFilename() + { + if (this.Id == null) + { + if (!String.IsNullOrEmpty(this.Name)) + { + this.Id = this.Core.CreateIdentifierFromFilename(Path.GetFileName(this.Name)); + } + else if (!String.IsNullOrEmpty(this.SourceFile)) + { + this.Id = this.Core.CreateIdentifierFromFilename(Path.GetFileName(this.SourceFile)); + } + } + } + + private void GenerateIdFromPrefix(string prefix) + { + if (this.Id == null) + { + this.Id = this.Core.CreateIdentifier(prefix, this.SourceFile?.ToUpperInvariant() ?? String.Empty); + } + } + + public void ParseCompressed(XAttribute attrib) + { + this.Compressed = this.Core.GetAttributeYesNoDefaultValue(this.SourceLineNumbers, attrib); + } + + public void ParseDescription(XAttribute attrib) + { + this.Description = this.Core.GetAttributeValue(this.SourceLineNumbers, attrib); + } + + public void ParseDisplayName(XAttribute attrib) + { + this.DisplayName = this.Core.GetAttributeValue(this.SourceLineNumbers, attrib); + } + + public void ParseDownloadUrl(XAttribute attrib) + { + this.DownloadUrl = this.Core.GetAttributeValue(this.SourceLineNumbers, attrib); + } + + public void ParseHash(XAttribute attrib) + { + this.Hash = this.Core.GetAttributeValue(this.SourceLineNumbers, attrib); + } + + public void ParseId(XAttribute attrib) + { + this.Id = this.Core.GetAttributeIdentifier(this.SourceLineNumbers, attrib); + } + + public void ParseName(XAttribute attrib) + { + this.Name = this.Core.GetAttributeLongFilename(this.SourceLineNumbers, attrib, false, true); + if (!this.Core.IsValidLongFilename(this.Name, false, true)) + { + this.Core.Write(ErrorMessages.IllegalLongFilename(this.SourceLineNumbers, this.Element.Name.LocalName, "Name", this.Name)); + } + } + + public void ParseProductName(XAttribute attrib) + { + this.ProductName = this.Core.GetAttributeValue(this.SourceLineNumbers, attrib); + } + + public void ParseSize(XAttribute attrib) + { + this.Size = this.Core.GetAttributeLongValue(this.SourceLineNumbers, attrib, 1, Int64.MaxValue); + } + + public void ParseSourceFile(XAttribute attrib) + { + this.SourceFile = this.Core.GetAttributeValue(this.SourceLineNumbers, attrib); + } + + public void ParseVersion(XAttribute attrib) + { + this.Version = this.Core.GetAttributeValue(this.SourceLineNumbers, attrib); + } + + } +} diff --git a/src/WixToolset.Core/CompilerCore.cs b/src/WixToolset.Core/CompilerCore.cs index 53e0f3fc..cfe3082e 100644 --- a/src/WixToolset.Core/CompilerCore.cs +++ b/src/WixToolset.Core/CompilerCore.cs @@ -340,6 +340,46 @@ namespace WixToolset.Core return Common.IsValidModuleOrBundleVersion(version); } + /// + /// Creates group and ordering information. + /// + /// Source line numbers. + /// Type of parent group, if known. + /// Identifier of parent group, if known. + /// Type of this item. + /// Identifier for this item. + /// Type of previous item, if known. + /// Identifier of previous item, if known + public void CreateGroupAndOrderingRows(SourceLineNumber sourceLineNumbers, + ComplexReferenceParentType parentType, string parentId, + ComplexReferenceChildType type, string id, + ComplexReferenceChildType previousType, string previousId) + { + if (this.EncounteredError) + { + return; + } + + if (parentType != ComplexReferenceParentType.Unknown && parentId != null) + { + this.CreateWixGroupRow(sourceLineNumbers, parentType, parentId, type, id); + } + + if (previousType != ComplexReferenceChildType.Unknown && previousId != null) + { + // TODO: Should we define our own enum for this, just to ensure there's no "cross-contamination"? + // TODO: Also, we could potentially include an 'Attributes' field to track things like + // 'before' vs. 'after', and explicit vs. inferred dependencies. + this.AddSymbol(new WixOrderingSymbol(sourceLineNumbers) + { + ItemType = type, + ItemIdRef = id, + DependsOnType = previousType, + DependsOnIdRef = previousId, + }); + } + } + /// /// Creates a version 3 name-based UUID. /// diff --git a/src/WixToolset.Core/Compiler_Bundle.cs b/src/WixToolset.Core/Compiler_Bundle.cs index 189ac9b5..64fe2acc 100644 --- a/src/WixToolset.Core/Compiler_Bundle.cs +++ b/src/WixToolset.Core/Compiler_Bundle.cs @@ -651,7 +651,7 @@ namespace WixToolset.Core switch (child.Name.LocalName) { case "BootstrapperApplicationDll": - previousId = this.ParseBootstrapperApplicationDllElement(child, previousType, previousId); + previousId = this.ParseBootstrapperApplicationDllElement(child, id, previousType, previousId); previousType = ComplexReferenceChildType.Payload; break; case "Payload": @@ -683,15 +683,21 @@ namespace WixToolset.Core /// Parse the BoostrapperApplication element. /// /// Element to parse + /// /// /// - private Identifier ParseBootstrapperApplicationDllElement(XElement node, ComplexReferenceChildType previousType, Identifier previousId) + private Identifier ParseBootstrapperApplicationDllElement(XElement node, Identifier defaultId, ComplexReferenceChildType previousType, Identifier previousId) { var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var compilerPayload = new CompilerPayload(this.Core, sourceLineNumbers, node) + { + Id = defaultId, + }; var dpiAwareness = WixBootstrapperApplicationDpiAwarenessType.PerMonitorV2; - // The BootstrapperApplicationDll element acts like a Payload element so delegate to the "Payload" attribute parsing code to parse and create a Payload entry. - this.ParsePayloadElementContent(node, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId, true, out var id); + // This list lets us evaluate extension attributes *after* all core attributes + // have been parsed and dealt with, regardless of authoring order. + var extensionAttributes = new List(); foreach (var attrib in node.Attributes()) { @@ -699,6 +705,15 @@ namespace WixToolset.Core { switch (attrib.Name.LocalName) { + case "Id": + compilerPayload.ParseId(attrib); + break; + case "Name": + compilerPayload.ParseName(attrib); + break; + case "SourceFile": + compilerPayload.ParseSourceFile(attrib); + break; case "DpiAwareness": var dpiAwarenessValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); switch (dpiAwarenessValue) @@ -723,8 +738,28 @@ namespace WixToolset.Core break; } break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; } } + else + { + extensionAttributes.Add(attrib); + } + } + + compilerPayload.FinishCompilingPayload(); + + // Now that the Id is known, we can parse the extension attributes. + var context = new Dictionary + { + ["Id"] = compilerPayload.Id.Id, + }; + + foreach (var extensionAttribute in extensionAttributes) + { + this.Core.ParseExtensionAttribute(node, extensionAttribute, context); } foreach (var child in node.Elements()) @@ -746,19 +781,20 @@ namespace WixToolset.Core if (!this.Core.EncounteredError) { + compilerPayload.CreatePayloadSymbol(ComplexReferenceParentType.Container, Compiler.BurnUXContainerId.Id, previousType, previousId?.Id); this.Core.AddSymbol(new WixBundleContainerSymbol(sourceLineNumbers, Compiler.BurnUXContainerId) { Name = "bundle-ux.cab", Type = ContainerType.Attached }); - this.Core.AddSymbol(new WixBootstrapperApplicationDllSymbol(sourceLineNumbers, id) + this.Core.AddSymbol(new WixBootstrapperApplicationDllSymbol(sourceLineNumbers, compilerPayload.Id) { DpiAwareness = dpiAwareness, }); } - return id; + return compilerPayload.Id; } /// @@ -1134,16 +1170,57 @@ namespace WixToolset.Core private void ParseBundleExtensionElement(XElement node) { var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var compilerPayload = new CompilerPayload(this.Core, sourceLineNumbers, node); Identifier previousId = null; var previousType = ComplexReferenceChildType.Unknown; - // The BundleExtension element acts like a Payload element so delegate to the "Payload" attribute parsing code to parse and create a Payload entry. - if (this.ParsePayloadElementContent(node, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId, true, out var id)) + // This list lets us evaluate extension attributes *after* all core attributes + // have been parsed and dealt with, regardless of authoring order. + var extensionAttributes = new List(); + + foreach (var attrib in node.Attributes()) { - previousId = id; - previousType = ComplexReferenceChildType.Payload; + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + compilerPayload.ParseId(attrib); + break; + case "Name": + compilerPayload.ParseName(attrib); + break; + case "SourceFile": + compilerPayload.ParseSourceFile(attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + extensionAttributes.Add(attrib); + } } + compilerPayload.FinishCompilingPayload(); + + // Now that the Id is known, we can parse the extension attributes. + var context = new Dictionary + { + ["Id"] = compilerPayload.Id.Id, + }; + + foreach (var extensionAttribute in extensionAttributes) + { + this.Core.ParseExtensionAttribute(node, extensionAttribute, context); + } + + compilerPayload.CreatePayloadSymbol(ComplexReferenceParentType.Container, Compiler.BurnUXContainerId.Id, previousType, previousId?.Id); + previousId = compilerPayload.Id; + previousType = ComplexReferenceChildType.Payload; + foreach (var child in node.Elements()) { if (CompilerCore.WixNamespace == child.Name.Namespace) @@ -1172,9 +1249,9 @@ namespace WixToolset.Core // Add the BundleExtension. if (!this.Core.EncounteredError) { - this.Core.AddSymbol(new WixBundleExtensionSymbol(sourceLineNumbers, id) + this.Core.AddSymbol(new WixBundleExtensionSymbol(sourceLineNumbers, compilerPayload.Id) { - PayloadRef = id.Id, + PayloadRef = compilerPayload.Id.Id, }); } } @@ -1294,53 +1371,8 @@ namespace WixToolset.Core Debug.Assert(ComplexReferenceParentType.PayloadGroup == parentType || ComplexReferenceParentType.Package == parentType || ComplexReferenceParentType.Container == parentType); Debug.Assert(ComplexReferenceChildType.Unknown == previousType || ComplexReferenceChildType.PayloadGroup == previousType || ComplexReferenceChildType.Payload == previousType); - this.ParsePayloadElementContent(node, parentType, parentId, previousType, previousId, true, out var id); - var context = new Dictionary - { - ["Id"] = id?.Id - }; - - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - this.Core.ParseExtensionElement(node, child, context); - } - } - - return id; - } - - /// - /// Parse the attributes of the Payload element. - /// - /// Element to parse - /// ComplexReferenceParentType of parent element. - /// Identifier of parent element. - /// - /// - /// - /// - /// Whether SourceFile was specified. - private bool ParsePayloadElementContent(XElement node, ComplexReferenceParentType parentType, Identifier parentId, ComplexReferenceChildType previousType, Identifier previousId, bool required, out Identifier id) - { - Debug.Assert(ComplexReferenceParentType.PayloadGroup == parentType || ComplexReferenceParentType.Package == parentType || ComplexReferenceParentType.Container == parentType); - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - var compressed = YesNoDefaultType.Default; - id = null; - string name = null; - string sourceFile = null; - string downloadUrl = null; + var compilerPayload = new CompilerPayload(this.Core, sourceLineNumbers, node); // This list lets us evaluate extension attributes *after* all core attributes // have been parsed and dealt with, regardless of authoring order. @@ -1350,32 +1382,32 @@ namespace WixToolset.Core { if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) { + var allowed = true; switch (attrib.Name.LocalName) { - case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); - break; - case "Compressed": - compressed = this.Core.GetAttributeYesNoDefaultValue(sourceLineNumbers, attrib); - break; - case "Name": - name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false, true); - break; - case "SourceFile": - sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "DownloadUrl": - downloadUrl = this.Core.GetAttributeValue(sourceLineNumbers, attrib); - break; - case "DpiAwareness": - if (node.Name.LocalName != "BootstrapperApplicationDll") - { - this.Core.UnexpectedAttribute(node, attrib); - } - break; - default: + case "Id": + compilerPayload.ParseId(attrib); + break; + case "Compressed": + compilerPayload.ParseCompressed(attrib); + break; + case "Name": + compilerPayload.ParseName(attrib); + break; + case "SourceFile": + compilerPayload.ParseSourceFile(attrib); + break; + case "DownloadUrl": + compilerPayload.ParseDownloadUrl(attrib); + break; + default: + allowed = false; + break; + } + + if (!allowed) + { this.Core.UnexpectedAttribute(node, attrib); - break; } } else @@ -1384,15 +1416,12 @@ namespace WixToolset.Core } } - if (null == id) - { - id = this.Core.CreateIdentifier("pay", sourceFile?.ToUpperInvariant() ?? String.Empty); - } + compilerPayload.FinishCompilingPayload(); // Now that the PayloadId is known, we can parse the extension attributes. var context = new Dictionary { - ["Id"] = id.Id + ["Id"] = compilerPayload.Id.Id, }; foreach (var extensionAttribute in extensionAttributes) @@ -1400,36 +1429,32 @@ namespace WixToolset.Core this.Core.ParseExtensionAttribute(node, extensionAttribute, context); } - // Let caller handle the children. - - if (Compiler.BurnUXContainerId == parentId) + foreach (var child in node.Elements()) { - if (compressed == YesNoDefaultType.No) + if (CompilerCore.WixNamespace == child.Name.Namespace) { - this.Core.Write(WarningMessages.UxPayloadsOnlySupportEmbedding(sourceLineNumbers, sourceFile)); + switch (child.Name.LocalName) + { + default: + this.Core.UnexpectedElement(node, child); + break; + } } - - compressed = YesNoDefaultType.Yes; - } - - if (sourceFile == null) - { - if (required) + else { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile")); + this.Core.ParseExtensionElement(node, child, context); } - return false; } - this.CreatePayloadRow(sourceLineNumbers, id, name, sourceFile, downloadUrl, parentType, parentId, previousType, previousId, compressed, null, null, null); + compilerPayload.CreatePayloadSymbol(parentType, parentId?.Id, previousType, previousId?.Id); - return true; + return compilerPayload.Id; } - private RemotePayload ParseRemotePayloadElement(XElement node) + private CompilerPayload ParseRemotePayloadElement(XElement node) { var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - var remotePayload = new RemotePayload(); + var remotePayload = new CompilerPayload(this.Core, sourceLineNumbers, node); foreach (var attrib in node.Attributes()) { @@ -1438,19 +1463,19 @@ namespace WixToolset.Core switch (attrib.Name.LocalName) { case "Description": - remotePayload.Description = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + remotePayload.ParseDescription(attrib); break; case "Hash": - remotePayload.Hash = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + remotePayload.ParseHash(attrib); break; case "ProductName": - remotePayload.ProductName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + remotePayload.ParseProductName(attrib); break; case "Size": - remotePayload.Size = this.Core.GetAttributeLongValue(sourceLineNumbers, attrib, 0, Int64.MaxValue); + remotePayload.ParseSize(attrib); break; case "Version": - remotePayload.Version = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + remotePayload.ParseVersion(attrib); break; default: this.Core.UnexpectedAttribute(node, attrib); @@ -1491,57 +1516,6 @@ namespace WixToolset.Core return remotePayload; } - /// - /// Creates the row for a Payload. - /// - /// - /// - /// - /// - /// - /// ComplexReferenceParentType of parent element - /// Identifier of parent element. - /// - /// - /// - /// - /// - /// - /// - private WixBundlePayloadSymbol CreatePayloadRow(SourceLineNumber sourceLineNumbers, Identifier id, string name, string sourceFile, string downloadUrl, ComplexReferenceParentType parentType, - Identifier parentId, ComplexReferenceChildType previousType, Identifier previousId, YesNoDefaultType compressed, string displayName, string description, - RemotePayload remotePayload) - { - WixBundlePayloadSymbol symbol = null; - - if (!this.Core.EncounteredError) - { - symbol = this.Core.AddSymbol(new WixBundlePayloadSymbol(sourceLineNumbers, id) - { - Name = String.IsNullOrEmpty(name) ? Path.GetFileName(sourceFile) : name, - SourceFile = new IntermediateFieldPathValue { Path = sourceFile }, - DownloadUrl = downloadUrl, - Compressed = (compressed == YesNoDefaultType.Yes) ? true : (compressed == YesNoDefaultType.No) ? (bool?)false : null, - UnresolvedSourceFile = sourceFile, // duplicate of sourceFile but in a string column so it won't get resolved to a full path during binding. - DisplayName = displayName, - Description = description, - }); - - if (null != remotePayload) - { - symbol.Description = remotePayload.Description; - symbol.DisplayName = remotePayload.ProductName; - symbol.Hash = remotePayload.Hash; - symbol.FileSize = remotePayload.Size; - symbol.Version = remotePayload.Version; - } - - this.CreateGroupAndOrderingRows(sourceLineNumbers, parentType, parentId.Id, ComplexReferenceChildType.Payload, id.Id, previousType, previousId?.Id); - } - - return symbol; - } - /// /// Parse PayloadGroup element. /// @@ -1613,7 +1587,7 @@ namespace WixToolset.Core { this.Core.AddSymbol(new WixBundlePayloadGroupSymbol(sourceLineNumbers, id)); - this.CreateGroupAndOrderingRows(sourceLineNumbers, parentType, parentId?.Id, ComplexReferenceChildType.PayloadGroup, id.Id, ComplexReferenceChildType.Unknown, null); + this.Core.CreateGroupAndOrderingRows(sourceLineNumbers, parentType, parentId?.Id, ComplexReferenceChildType.PayloadGroup, id.Id, ComplexReferenceChildType.Unknown, null); } } @@ -1661,51 +1635,11 @@ namespace WixToolset.Core this.Core.ParseForExtensionElements(node); - this.CreateGroupAndOrderingRows(sourceLineNumbers, parentType, parentId?.Id, ComplexReferenceChildType.PayloadGroup, id?.Id, previousType, previousId?.Id); + this.Core.CreateGroupAndOrderingRows(sourceLineNumbers, parentType, parentId?.Id, ComplexReferenceChildType.PayloadGroup, id?.Id, previousType, previousId?.Id); return id; } - /// - /// Creates group and ordering information. - /// - /// Source line numbers. - /// Type of parent group, if known. - /// Identifier of parent group, if known. - /// Type of this item. - /// Identifier for this item. - /// Type of previous item, if known. - /// Identifier of previous item, if known - private void CreateGroupAndOrderingRows(SourceLineNumber sourceLineNumbers, - ComplexReferenceParentType parentType, string parentId, - ComplexReferenceChildType type, string id, - ComplexReferenceChildType previousType, string previousId) - { - if (this.Core.EncounteredError) - { - return; - } - - if (ComplexReferenceParentType.Unknown != parentType && null != parentId) - { - this.Core.CreateWixGroupRow(sourceLineNumbers, parentType, parentId, type, id); - } - - if (ComplexReferenceChildType.Unknown != previousType && null != previousId) - { - // TODO: Should we define our own enum for this, just to ensure there's no "cross-contamination"? - // TODO: Also, we could potentially include an 'Attributes' field to track things like - // 'before' vs. 'after', and explicit vs. inferred dependencies. - this.Core.AddSymbol(new WixOrderingSymbol(sourceLineNumbers) - { - ItemType = type, - ItemIdRef = id, - DependsOnType = previousType, - DependsOnIdRef = previousId - }); - } - } - /// /// Parse ExitCode element. /// @@ -2050,17 +1984,15 @@ namespace WixToolset.Core Debug.Assert(ComplexReferenceParentType.PackageGroup == parentType); Debug.Assert(ComplexReferenceChildType.Unknown == previousType || ComplexReferenceChildType.PackageGroup == previousType || ComplexReferenceChildType.Package == previousType); - var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); - Identifier id = null; - string name = null; - string sourceFile = null; - string downloadUrl = null; + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);; + var compilerPayload = new CompilerPayload(this.Core, sourceLineNumbers, node) + { + IsRequired = false, + }; string after = null; string installCondition = null; var cache = YesNoAlwaysType.Yes; // the default is to cache everything in tradeoff for stability over disk space. string cacheId = null; - string description = null; - string displayName = null; var logPathVariable = (packageType == WixBundlePackageType.Msu) ? String.Empty : null; var rollbackPathVariable = (packageType == WixBundlePackageType.Msu) ? String.Empty : null; var permanent = YesNoType.NotSet; @@ -2074,10 +2006,9 @@ namespace WixToolset.Core string protocol = null; var installSize = CompilerConstants.IntegerNotSet; string msuKB = null; - var compressed = YesNoDefaultType.Default; var enableFeatureSelection = YesNoType.NotSet; var forcePerMachine = YesNoType.NotSet; - RemotePayload remotePayload = null; + CompilerPayload remotePayload = null; var slipstream = YesNoType.NotSet; var expectedNetFx4Args = new string[] { "/q", "/norestart", "/chainingpackage" }; @@ -2094,20 +2025,16 @@ namespace WixToolset.Core switch (attrib.Name.LocalName) { case "Id": - id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + compilerPayload.ParseId(attrib); break; case "Name": - name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false, true); - if (!this.Core.IsValidLongFilename(name, false, true)) - { - this.Core.Write(ErrorMessages.IllegalLongFilename(sourceLineNumbers, node.Name.LocalName, "Name", name)); - } + compilerPayload.ParseName(attrib); break; case "SourceFile": - sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + compilerPayload.ParseSourceFile(attrib); break; case "DownloadUrl": - downloadUrl = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + compilerPayload.ParseDownloadUrl(attrib); break; case "After": after = this.Core.GetAttributeValue(sourceLineNumbers, attrib); @@ -2139,10 +2066,10 @@ namespace WixToolset.Core cacheId = this.Core.GetAttributeValue(sourceLineNumbers, attrib); break; case "Description": - description = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + compilerPayload.ParseDescription(attrib); break; case "DisplayName": - displayName = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + compilerPayload.ParseDisplayName(attrib); break; case "EnableFeatureSelection": enableFeatureSelection = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); @@ -2200,7 +2127,7 @@ namespace WixToolset.Core allowed = (packageType == WixBundlePackageType.Msu); break; case "Compressed": - compressed = this.Core.GetAttributeYesNoDefaultValue(sourceLineNumbers, attrib); + compilerPayload.ParseCompressed(attrib); break; case "Slipstream": slipstream = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib); @@ -2242,65 +2169,8 @@ namespace WixToolset.Core remotePayload = this.ParseRemotePayloadElement(child); } - if (String.IsNullOrEmpty(sourceFile)) - { - if (String.IsNullOrEmpty(name)) - { - this.Core.Write(ErrorMessages.ExpectedAttributesWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Name", "SourceFile")); - } - else if (null == remotePayload) - { - sourceFile = Path.Combine("SourceDir", name); - } - } - else if (null != remotePayload) - { - this.Core.Write(ErrorMessages.UnexpectedElementWithAttribute(sourceLineNumbers, node.Name.LocalName, "RemotePayload", "SourceFile")); - } - else if (sourceFile.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)) - { - if (String.IsNullOrEmpty(name)) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name", "SourceFile", sourceFile)); - } - else - { - sourceFile = Path.Combine(sourceFile, Path.GetFileName(name)); - } - } - - if (null == downloadUrl && null != remotePayload) - { - this.Core.Write(ErrorMessages.ExpectedAttributeWithElement(sourceLineNumbers, node.Name.LocalName, "DownloadUrl", "RemotePayload")); - } - - if (YesNoDefaultType.No != compressed && null != remotePayload) - { - compressed = YesNoDefaultType.No; - this.Core.Write(WarningMessages.RemotePayloadsMustNotAlsoBeCompressed(sourceLineNumbers, node.Name.LocalName)); - } - - if (null == id) - { - if (!String.IsNullOrEmpty(name)) - { - id = this.Core.CreateIdentifierFromFilename(Path.GetFileName(name)); - } - else if (!String.IsNullOrEmpty(sourceFile)) - { - id = this.Core.CreateIdentifierFromFilename(Path.GetFileName(sourceFile)); - } - - if (null == id) - { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); - id = Identifier.Invalid; - } - else if (!Common.IsIdentifier(id.Id)) - { - this.Core.Write(ErrorMessages.IllegalIdentifier(sourceLineNumbers, node.Name.LocalName, "Id", id.Id)); - } - } + compilerPayload.FinishCompilingPackage(remotePayload); + var id = compilerPayload.Id; if (null == logPathVariable) { @@ -2424,7 +2294,7 @@ namespace WixToolset.Core } else { - var context = new Dictionary() { { "Id", id?.Id } }; + var context = new Dictionary() { { "Id", id.Id } }; this.Core.ParseExtensionElement(node, child, context); } } @@ -2432,8 +2302,7 @@ namespace WixToolset.Core if (!this.Core.EncounteredError) { // We create the package contents as a payload with this package as the parent - this.CreatePayloadRow(sourceLineNumbers, id, name, sourceFile, downloadUrl, ComplexReferenceParentType.Package, id, - ComplexReferenceChildType.Unknown, null, compressed, displayName, description, remotePayload); + compilerPayload.CreatePayloadSymbol(ComplexReferenceParentType.Package, id.Id); this.Core.AddSymbol(new WixChainItemSymbol(sourceLineNumbers, id)); @@ -2801,7 +2670,7 @@ namespace WixToolset.Core previousId = afterId; } - this.CreateGroupAndOrderingRows(sourceLineNumbers, parentType, parentId, type, id, previousType, previousId); + this.Core.CreateGroupAndOrderingRows(sourceLineNumbers, parentType, parentId, type, id, previousType, previousId); } /// @@ -3251,18 +3120,5 @@ namespace WixToolset.Core return WixBundleVariableType.String; } - - private class RemotePayload - { - public string Description { get; set; } - - public string Hash { get; set; } - - public string ProductName { get; set; } - - public long Size { get; set; } - - public string Version { get; set; } - } } } -- cgit v1.2.3-55-g6feb