From 760fb810ba5ecc3c6ce752a9bfa3755f7b7c0f6a Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Thu, 25 Feb 2021 09:58:20 -0800 Subject: Absorb Tag.wixext into Core as SoftwareTag element Resolves wixtoolset/issues#5949 --- src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs | 7 + .../Bind/GenerateManifestDataFromIRCommand.cs | 1 + .../Bind/ProcessBundleSoftwareTagsCommand.cs | 142 ++++++++++ .../Bundles/CreateBurnManifestCommand.cs | 12 +- .../Bind/BindDatabaseCommand.cs | 12 + .../Bind/CreateOutputFromIRCommand.cs | 3 +- .../Bind/ProcessPackageSoftwareTagsCommand.cs | 131 +++++++++ .../Bind/UpdateFileFacadesCommand.cs | 2 +- src/WixToolset.Core/Bind/FileFacade.cs | 2 +- src/WixToolset.Core/CompilerCore.cs | 1 - src/WixToolset.Core/CompilerErrors.cs | 30 ++ src/WixToolset.Core/Compiler_2.cs | 3 + src/WixToolset.Core/Compiler_Bundle.cs | 3 + src/WixToolset.Core/Compiler_Patch.cs | 3 + src/WixToolset.Core/Compiler_Tag.cs | 315 +++++++++++++++++++++ .../SoftwareTagFixture.cs | 100 +++++++ .../TestData/BundleTag/BundleWithTag.wxs | 15 + .../TestData/BundleTag/fakeba.dll | 1 + .../TestData/ProductTag/Package.en-us.wxl | 11 + .../TestData/ProductTag/PackageComponents.wxs | 10 + .../TestData/ProductTag/PackageWithTag.wxs | 20 ++ .../TestData/ProductTag/example.txt | 1 + 22 files changed, 814 insertions(+), 11 deletions(-) create mode 100644 src/WixToolset.Core.Burn/Bind/ProcessBundleSoftwareTagsCommand.cs create mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/ProcessPackageSoftwareTagsCommand.cs create mode 100644 src/WixToolset.Core/CompilerErrors.cs create mode 100644 src/WixToolset.Core/Compiler_Tag.cs create mode 100644 src/test/WixToolsetTest.CoreIntegration/SoftwareTagFixture.cs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/BundleWithTag.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/fakeba.dll create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/Package.en-us.wxl create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageComponents.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageWithTag.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/example.txt diff --git a/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs b/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs index 93620e1b..c9a111c6 100644 --- a/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs +++ b/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs @@ -380,6 +380,13 @@ namespace WixToolset.Core.Burn // Update the bundle per-machine/per-user scope based on the chained packages. this.ResolveBundleInstallScope(section, bundleSymbol, orderedFacades); + var softwareTags = section.Symbols.OfType().ToList(); + if (softwareTags.Any()) + { + var command = new ProcessBundleSoftwareTagsCommand(section, softwareTags); + command.Execute(); + } + // Give the extension one last hook before generating the output files. foreach (var extension in this.BackendExtensions) { diff --git a/src/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs b/src/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs index c51d380c..36ced6cf 100644 --- a/src/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs +++ b/src/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs @@ -82,6 +82,7 @@ namespace WixToolset.Core.Burn.Bind case SymbolDefinitionType.WixBundleRelatedPackage: case SymbolDefinitionType.WixBundleRollbackBoundary: case SymbolDefinitionType.WixBundleSlipstreamMsp: + case SymbolDefinitionType.WixBundleTag: case SymbolDefinitionType.WixBundleUpdate: case SymbolDefinitionType.WixBundleVariable: case SymbolDefinitionType.WixBuildInfo: diff --git a/src/WixToolset.Core.Burn/Bind/ProcessBundleSoftwareTagsCommand.cs b/src/WixToolset.Core.Burn/Bind/ProcessBundleSoftwareTagsCommand.cs new file mode 100644 index 00000000..8584d2a4 --- /dev/null +++ b/src/WixToolset.Core.Burn/Bind/ProcessBundleSoftwareTagsCommand.cs @@ -0,0 +1,142 @@ +// 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.Bind +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Text; + using System.Xml; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Dtf.WindowsInstaller; + + internal class ProcessBundleSoftwareTagsCommand + { + public ProcessBundleSoftwareTagsCommand(IntermediateSection section, IEnumerable softwareTags) + { + this.Section = section; + this.SoftwareTags = softwareTags; + } + + private IntermediateSection Section { get; } + + private IEnumerable SoftwareTags { get; } + + public void Execute() + { + var bundleInfo = this.Section.Symbols.OfType().FirstOrDefault(); + var bundleId = NormalizeGuid(bundleInfo.BundleId); + var upgradeCode = NormalizeGuid(bundleInfo.UpgradeCode); + + var uniqueId = String.Concat("wix:bundle/", bundleId); + var persistentId = String.Concat("wix:bundle.upgrade/", upgradeCode); + + // Try to collect all the software id tags from all the child packages. + var containedTags = CollectPackageTags(this.Section); + + foreach (var bundleTag in this.SoftwareTags) + { + using (var ms = new MemoryStream()) + { + CreateTagFile(ms, uniqueId, bundleInfo.Name, bundleInfo.Version, bundleTag.Regid, bundleInfo.Manufacturer, persistentId, containedTags); + bundleTag.Xml = Encoding.UTF8.GetString(ms.ToArray()); + } + } + } + + private static string NormalizeGuid(string guidString) + { + if (Guid.TryParse(guidString, out var guid)) + { + return guid.ToString("D").ToUpperInvariant(); + } + + return guidString; + } + + private static IEnumerable CollectPackageTags(IntermediateSection section) + { + var tags = new List(); + + var msiPackages = section.Symbols.OfType().Where(s => s.Type == WixBundlePackageType.Msi).ToList(); + if (msiPackages.Any()) + { + var payloadSymbolsById = section.Symbols.OfType().ToDictionary(s => s.Id.Id); + + foreach (var msiPackage in msiPackages) + { + var payload = payloadSymbolsById[msiPackage.PayloadRef]; + + using (var db = new Database(payload.SourceFile.Path)) + { + using (var view = db.OpenView("SELECT `Regid`, `TagId` FROM `SoftwareIdentificationTag`")) + { + view.Execute(); + while (true) + { + using (var record = view.Fetch()) + { + if (null == record) + { + break; + } + + tags.Add(new SoftwareTag { Regid = record.GetString(1), Id = record.GetString(2) }); + } + } + } + } + } + } + + return tags; + } + + private static void CreateTagFile(Stream stream, string uniqueId, string name, string version, string regid, string manufacturer, string persistendId, IEnumerable containedTags) + { + var versionScheme = Version.TryParse(version, out _) ? "multipartnumeric" : "alphanumeric"; + + using (var writer = XmlWriter.Create(stream, new XmlWriterSettings { Indent = true})) + { + writer.WriteStartDocument(); + writer.WriteStartElement("SoftwareIdentity", "http://standards.iso.org/iso/19770/-2/2015/schema.xsd"); + writer.WriteAttributeString("tagId", uniqueId); + writer.WriteAttributeString("name", name); + writer.WriteAttributeString("version", version); + writer.WriteAttributeString("versionScheme", versionScheme); + + writer.WriteStartElement("Entity"); + writer.WriteAttributeString("name", manufacturer); + writer.WriteAttributeString("regid", regid); + writer.WriteAttributeString("role", "softwareCreator tagCreator"); + writer.WriteEndElement(); // + + if (!String.IsNullOrEmpty(persistendId)) + { + writer.WriteStartElement("Meta"); + writer.WriteAttributeString("persistentId", persistendId); + writer.WriteEndElement(); // + } + + foreach (var containedTag in containedTags) + { + writer.WriteStartElement("Link"); + writer.WriteAttributeString("rel", "component"); + writer.WriteAttributeString("href", String.Concat("swid:", containedTag.Id)); + writer.WriteEndElement(); // + } + + writer.WriteEndElement(); // + } + } + + private class SoftwareTag + { + public string Regid { get; set; } + + public string Id { get; set; } + } + } +} diff --git a/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs b/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs index 71bc0229..3bc6bf1b 100644 --- a/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs +++ b/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs @@ -295,17 +295,15 @@ namespace WixToolset.Core.Burn.Bundles writer.WriteEndElement(); // } -#if TODO // Handle SWID Tags - var bundleTags = this.Output.Tables["WixBundleTag"].RowsAs(); - foreach (var row in bundleTags) + foreach (var bundleTagSymbol in this.Section.Symbols.OfType()) { writer.WriteStartElement("SoftwareTag"); - writer.WriteAttributeString("Filename", (string)row[0]); - writer.WriteAttributeString("Regid", (string)row[1]); - writer.WriteCData((string)row[4]); + writer.WriteAttributeString("Filename", bundleTagSymbol.Filename); + writer.WriteAttributeString("Regid", bundleTagSymbol.Regid); + writer.WriteAttributeString("Path", bundleTagSymbol.InstallPath); + writer.WriteCData(bundleTagSymbol.Xml); writer.WriteEndElement(); } -#endif writer.WriteEndElement(); // diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs index 012c7c4c..25a093fd 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs @@ -282,6 +282,18 @@ namespace WixToolset.Core.WindowsInstaller.Bind return null; } + // Process SoftwareTags in MSI packages. + if (SectionType.Product == section.Type) + { + var softwareTags = section.Symbols.OfType().ToList(); + + if (softwareTags.Any()) + { + var command = new ProcessPackageSoftwareTagsCommand(section, softwareTags, this.IntermediateFolder); + command.Execute(); + } + } + // Gather information about files that do not come from merge modules. { var command = new UpdateFileFacadesCommand(this.Messaging, section, fileFacades, fileFacades.Where(f => !f.FromModule), variableCache, overwriteHash: true); diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs index b52ff434..37383caa 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs @@ -231,6 +231,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind case SymbolDefinitionType.WixPatchRef: case SymbolDefinitionType.WixPatchTarget: case SymbolDefinitionType.WixProperty: + case SymbolDefinitionType.WixProductTag: case SymbolDefinitionType.WixSimpleReference: case SymbolDefinitionType.WixSuppressAction: case SymbolDefinitionType.WixSuppressModularization: @@ -456,7 +457,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind private void AddDirectorySymbol(DirectorySymbol symbol) { - if (String.IsNullOrEmpty(symbol.ShortName) && !symbol.Name.Equals(".") && !symbol.Name.Equals("SourceDir") && !Common.IsValidShortFilename(symbol.Name, false)) + if (String.IsNullOrEmpty(symbol.ShortName) && symbol.Name != null && !symbol.Name.Equals(".") && !symbol.Name.Equals("SourceDir") && !Common.IsValidShortFilename(symbol.Name, false)) { symbol.ShortName = CreateShortName(symbol.Name, false, false, "Directory", symbol.ParentDirectoryRef); } diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/ProcessPackageSoftwareTagsCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/ProcessPackageSoftwareTagsCommand.cs new file mode 100644 index 00000000..9a068603 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/ProcessPackageSoftwareTagsCommand.cs @@ -0,0 +1,131 @@ +// 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.WindowsInstaller.Bind +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Xml; + using WixToolset.Data; + using WixToolset.Data.Symbols; + + internal class ProcessPackageSoftwareTagsCommand + { + public ProcessPackageSoftwareTagsCommand(IntermediateSection section, IEnumerable softwareTags, string intermediateFolder) + { + this.Section = section; + this.SoftwareTags = softwareTags; + this.IntermediateFolder = intermediateFolder; + } + + private string IntermediateFolder { get; } + + private IntermediateSection Section { get; } + + private IEnumerable SoftwareTags { get; } + + public void Execute() + { + string productName = null; + string productVersion = null; + string manufacturer = null; + string upgradeCode = null; + + var summaryInfo = this.Section.Symbols.OfType().FirstOrDefault(s => s.PropertyId == SummaryInformationType.PackageCode); + var packageCode = NormalizeGuid(summaryInfo?.Value); + + foreach (var property in this.Section.Symbols.OfType()) + { + switch (property.Id.Id) + { + case "ProductName": + productName = property.Value; + break; + case "ProductVersion": + productVersion = property.Value; + break; + case "Manufacturer": + manufacturer = property.Value; + break; + case "UpgradeCode": + upgradeCode = NormalizeGuid(property.Value); + break; + } + } + + var fileSymbolsById = this.Section.Symbols.OfType().Where(f => f.Id != null).ToDictionary(f => f.Id.Id); + + var workingFolder = Path.Combine(this.IntermediateFolder, "_swidtag"); + + Directory.CreateDirectory(workingFolder); + + foreach (var tagRow in this.SoftwareTags) + { + if (fileSymbolsById.TryGetValue(tagRow.FileRef, out var fileSymbol)) + { + var uniqueId = String.Concat("msi:package/", packageCode); + var persistentId = String.IsNullOrEmpty(upgradeCode) ? null : String.Concat("msi:upgrade/", upgradeCode); + + // Write the tag file. + fileSymbol.Source = new IntermediateFieldPathValue { Path = Path.Combine(workingFolder, fileSymbol.Name) }; + + using (var fs = new FileStream(fileSymbol.Source.Path, FileMode.Create)) + { + CreateTagFile(fs, uniqueId, productName, productVersion, tagRow.Regid, manufacturer, persistentId); + } + + // Ensure the matching "SoftwareIdentificationTag" row exists and + // is populated correctly. + this.Section.AddSymbol(new SoftwareIdentificationTagSymbol(tagRow.SourceLineNumbers, tagRow.Id) + { + FileRef = fileSymbol.Id.Id, + Regid = tagRow.Regid, + TagId = uniqueId, + PersistentId = persistentId + }); + } + } + } + + private static string NormalizeGuid(string guidString) + { + if (Guid.TryParse(guidString, out var guid)) + { + return guid.ToString("D").ToUpperInvariant(); + } + + return guidString; + } + + private static void CreateTagFile(Stream stream, string uniqueId, string name, string version, string regid, string manufacturer, string persistendId) + { + var versionScheme = Version.TryParse(version, out _) ? "multipartnumeric" : "alphanumeric"; + + using (var writer = XmlWriter.Create(stream, new XmlWriterSettings { Indent = true })) + { + writer.WriteStartDocument(); + writer.WriteStartElement("SoftwareIdentity", "http://standards.iso.org/iso/19770/-2/2015/schema.xsd"); + writer.WriteAttributeString("tagId", uniqueId); + writer.WriteAttributeString("name", name); + writer.WriteAttributeString("version", version); + writer.WriteAttributeString("versionScheme", versionScheme); + + writer.WriteStartElement("Entity"); + writer.WriteAttributeString("name", manufacturer); + writer.WriteAttributeString("regid", regid); + writer.WriteAttributeString("role", "softwareCreator tagCreator"); + writer.WriteEndElement(); // + + if (!String.IsNullOrEmpty(persistendId)) + { + writer.WriteStartElement("Meta"); + writer.WriteAttributeString("persistentId", persistendId); + writer.WriteEndElement(); // + } + + writer.WriteEndElement(); // + } + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs index 938627ed..d5bdc797 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs @@ -45,7 +45,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind { var assemblyNameSymbols = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); - foreach (var file in this.UpdateFileFacades) + foreach (var file in this.UpdateFileFacades.Where(f => f.SourcePath != null)) { this.UpdateFileFacade(file, assemblyNameSymbols); } diff --git a/src/WixToolset.Core/Bind/FileFacade.cs b/src/WixToolset.Core/Bind/FileFacade.cs index ec4e9725..9705cd01 100644 --- a/src/WixToolset.Core/Bind/FileFacade.cs +++ b/src/WixToolset.Core/Bind/FileFacade.cs @@ -125,7 +125,7 @@ namespace WixToolset.Core.Bind public SourceLineNumber SourceLineNumber => this.FileRow == null ? this.FileSymbol.SourceLineNumbers : this.FileRow.SourceLineNumbers; - public string SourcePath => this.FileRow == null ? this.FileSymbol.Source.Path : this.FileRow.Source; + public string SourcePath => this.FileRow == null ? this.FileSymbol.Source?.Path : this.FileRow.Source; public bool Compressed => this.FileRow == null ? (this.FileSymbol.Attributes & FileSymbolAttributes.Compressed) == FileSymbolAttributes.Compressed : (this.FileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesCompressed) == WindowsInstallerConstants.MsidbFileAttributesCompressed; diff --git a/src/WixToolset.Core/CompilerCore.cs b/src/WixToolset.Core/CompilerCore.cs index 1f6d6329..53e0f3fc 100644 --- a/src/WixToolset.Core/CompilerCore.cs +++ b/src/WixToolset.Core/CompilerCore.cs @@ -6,7 +6,6 @@ namespace WixToolset.Core using System.Collections; using System.Collections.Generic; using System.Diagnostics; - using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Reflection; using System.Text; diff --git a/src/WixToolset.Core/CompilerErrors.cs b/src/WixToolset.Core/CompilerErrors.cs new file mode 100644 index 00000000..da64c376 --- /dev/null +++ b/src/WixToolset.Core/CompilerErrors.cs @@ -0,0 +1,30 @@ +// 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 WixToolset.Data; + + internal static class CompilerErrors + { + public static Message IllegalName(SourceLineNumber sourceLineNumbers, string parentElement, string name) + { + return Message(sourceLineNumbers, Ids.IllegalName, "The Tag/@Name attribute value, '{1}', contains invalid filename identifiers. The Tag/@Name may have defaulted from the {0}/@Name attrbute. If so, use the Tag/@Name attribute to provide a valid filename. Any character except for the follow may be used: \\ ? | > < : / * \".", parentElement, name); + } + + public static Message ExampleRegid(SourceLineNumber sourceLineNumbers, string regid) + { + return Message(sourceLineNumbers, Ids.ExampleRegid, "Regid '{0}' is a placeholder that must be replaced with an appropriate value for your installation. Use the simplified URI for your organization or project.", regid); + } + + private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args) + { + return new Message(sourceLineNumber, MessageLevel.Error, (int)id, format, args); + } + + public enum Ids + { + IllegalName = 6601, + ExampleRegid = 6602, + } + } +} diff --git a/src/WixToolset.Core/Compiler_2.cs b/src/WixToolset.Core/Compiler_2.cs index 09d56e49..295392c8 100644 --- a/src/WixToolset.Core/Compiler_2.cs +++ b/src/WixToolset.Core/Compiler_2.cs @@ -316,6 +316,9 @@ namespace WixToolset.Core string parentName = null; this.ParseSFPCatalogElement(child, ref parentName); break; + case "SoftwareTag": + this.ParsePackageTagElement(child); + break; case "SummaryInformation": this.ParseSummaryInformationElement(child, ref isCodepageSet, ref isPackageNameSet, ref isKeywordsSet, ref isPackageAuthorSet); break; diff --git a/src/WixToolset.Core/Compiler_Bundle.cs b/src/WixToolset.Core/Compiler_Bundle.cs index 7a386de7..1ee09166 100644 --- a/src/WixToolset.Core/Compiler_Bundle.cs +++ b/src/WixToolset.Core/Compiler_Bundle.cs @@ -346,6 +346,9 @@ namespace WixToolset.Core case "SetVariableRef": this.ParseSimpleRefElement(child, SymbolDefinitions.WixSetVariable); break; + case "SoftwareTag": + this.ParseBundleTagElement(child); + break; case "Update": this.ParseUpdateElement(child); break; diff --git a/src/WixToolset.Core/Compiler_Patch.cs b/src/WixToolset.Core/Compiler_Patch.cs index 2fb1affb..83737c43 100644 --- a/src/WixToolset.Core/Compiler_Patch.cs +++ b/src/WixToolset.Core/Compiler_Patch.cs @@ -410,6 +410,9 @@ namespace WixToolset.Core case "PropertyRef": this.ParsePatchChildRefElement(child, "Property"); break; + case "SoftwareTagRef": + this.ParseTagRefElement(child); + break; case "UIRef": this.ParsePatchChildRefElement(child, "WixUI"); break; diff --git a/src/WixToolset.Core/Compiler_Tag.cs b/src/WixToolset.Core/Compiler_Tag.cs new file mode 100644 index 00000000..2b3523c8 --- /dev/null +++ b/src/WixToolset.Core/Compiler_Tag.cs @@ -0,0 +1,315 @@ +// 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.Xml.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + + /// + /// Compiler of the WiX toolset. + /// + internal partial class Compiler : ICompiler + { + /// + /// Parses a Tag element for Software Id Tag registration under a Bundle element. + /// + /// The element to parse. + private void ParseBundleTagElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string name = null; + string regid = null; + string installPath = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Name": + name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false); + break; + case "Regid": + regid = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "InstallDirectory": + case "Bitness": + this.Core.Write(ErrorMessages.ExpectedParentWithAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Package")); + break; + case "InstallPath": + installPath = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + this.Core.ParseForExtensionElements(node); + + if (String.IsNullOrEmpty(name)) + { + name = node.Parent?.Attribute("Name")?.Value; + + if (String.IsNullOrEmpty(name)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); + } + } + + if (!String.IsNullOrEmpty(name) && !this.Core.IsValidLongFilename(name)) + { + this.Core.Write(CompilerErrors.IllegalName(sourceLineNumbers, node.Name.LocalName, name)); + } + + if (String.IsNullOrEmpty(regid)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Regid")); + } + else if (regid.Equals("example.com", StringComparison.OrdinalIgnoreCase)) + { + this.Core.Write(CompilerErrors.ExampleRegid(sourceLineNumbers, regid)); + } + + if (String.IsNullOrEmpty(installPath)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "InstallPath")); + } + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(new WixBundleTagSymbol(sourceLineNumbers) + { + Filename = String.Concat(name, ".swidtag"), + Regid = regid, + Name = name, + InstallPath = installPath + }); + } + } + + /// + /// Parses a Tag element for Software Id Tag registration under a Package element. + /// + /// The element to parse. + private void ParsePackageTagElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier id = null; + string name = null; + string regid = null; + string feature = null; + string installDirectory = null; + var win64 = this.Context.IsCurrentPlatform64Bit; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Name": + name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false); + break; + case "Regid": + regid = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Feature": + feature = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "InstallDirectory": + installDirectory = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + break; + case "InstallPath": + this.Core.Write(ErrorMessages.ExpectedParentWithAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Bundle")); + break; + case "Bitness": + var bitnessValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (bitnessValue) + { + case "always32": + win64 = false; + break; + case "always64": + win64 = true; + break; + case "default": + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, bitnessValue, "default", "always32", "always64")); + break; + } + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + this.Core.ParseForExtensionElements(node); + + if (String.IsNullOrEmpty(name)) + { + name = node.Parent?.Attribute("Name")?.Value; + + if (String.IsNullOrEmpty(name)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name")); + } + } + + if (!String.IsNullOrEmpty(name) && !this.Core.IsValidLongFilename(name)) + { + this.Core.Write(CompilerErrors.IllegalName(sourceLineNumbers, node.Name.LocalName, name)); + } + + if (String.IsNullOrEmpty(regid)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Regid")); + } + else if (regid.Equals("example.com", StringComparison.OrdinalIgnoreCase)) + { + this.Core.Write(CompilerErrors.ExampleRegid(sourceLineNumbers, regid)); + return; + } + else if (id == null) + { + id = this.CreateTagId(regid); + } + + if (String.IsNullOrEmpty(installDirectory)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "InstallDirectory")); + } + + if (!this.Core.EncounteredError) + { + var fileName = String.Concat(name, ".swidtag"); + + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, installDirectory); + this.Core.AddSymbol(new DirectorySymbol(sourceLineNumbers, id) + { + Name = "swidtag", + ParentDirectoryRef = installDirectory, + ComponentGuidGenerationSeed = "4BAD0C8B-3AF0-BFE3-CC83-094749A1C4B1" + }); + + this.Core.AddSymbol(new ComponentSymbol(sourceLineNumbers, id) + { + ComponentId = "*", + DirectoryRef = id.Id, + KeyPath = id.Id, + KeyPathType = ComponentKeyPathType.File, + Location = ComponentLocation.LocalOnly, + Win64 = win64 + }); + + this.Core.AddSymbol(new FileSymbol(sourceLineNumbers, id) + { + ComponentRef = id.Id, + Name = fileName, + DiskId = 1, + Attributes = FileSymbolAttributes.ReadOnly, + }); + + if (!String.IsNullOrEmpty(feature)) + { + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Feature, feature); + } + else + { + feature = "WixSwidTag"; + this.Core.AddSymbol(new FeatureSymbol(sourceLineNumbers, new Identifier(AccessModifier.Private, feature)) + { + Title = "ISO/IEC 19770-2", + Level = 1, + InstallDefault = FeatureInstallDefault.Local, + Display = 0, + DisallowAdvertise = true, + DisallowAbsent = true, + }); + } + this.Core.CreateComplexReference(sourceLineNumbers, ComplexReferenceParentType.Feature, feature, null, ComplexReferenceChildType.Component, id.Id, true); + + this.Core.EnsureTable(sourceLineNumbers, "SoftwareIdentificationTag"); + this.Core.AddSymbol(new WixProductTagSymbol(sourceLineNumbers, id) + { + FileRef = id.Id, + Regid = regid, + Name = name + }); + } + } + + /// + /// Parses a TagRef element for Software Id Tag registration under a PatchFamily element. + /// + /// The element to parse. + private void ParseTagRefElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + string regid = null; + + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Regid": + regid = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + default: + this.Core.UnexpectedAttribute(node, attrib); + break; + } + } + else + { + this.Core.ParseExtensionAttribute(node, attrib); + } + } + + this.Core.ParseForExtensionElements(node); + + if (String.IsNullOrEmpty(regid)) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Regid")); + } + else if (regid.Equals("example.com", StringComparison.OrdinalIgnoreCase)) + { + this.Core.Write(CompilerErrors.ExampleRegid(sourceLineNumbers, regid)); + } + + if (!this.Core.EncounteredError) + { + var id = this.CreateTagId(regid); + + this.Core.AddSymbol(new WixPatchRefSymbol(sourceLineNumbers, id) + { + Table = SymbolDefinitions.Component.Name, + PrimaryKeys = id.Id + }); + } + } + + private Identifier CreateTagId(string regid) => this.Core.CreateIdentifier("tag", regid, ".product.tag"); + } +} diff --git a/src/test/WixToolsetTest.CoreIntegration/SoftwareTagFixture.cs b/src/test/WixToolsetTest.CoreIntegration/SoftwareTagFixture.cs new file mode 100644 index 00000000..15276b18 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/SoftwareTagFixture.cs @@ -0,0 +1,100 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System.IO; + using System.Linq; + using System.Xml.Linq; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using Xunit; + + public class SoftwareTagFixture + { + private static readonly XNamespace BurnManifestNamespace = "http://wixtoolset.org/schemas/v4/2008/Burn"; + private static readonly XNamespace SwidTagNamespace = "http://standards.iso.org/iso/19770/-2/2009/schema.xsd"; + + [Fact] + public void CanBuildPackageWithTag() + { + var folder = TestData.Get(@"TestData\ProductTag"); + var build = new Builder(folder, null, new[] { folder }); + + var results = build.BuildAndQuery(Build, "File", "SoftwareIdentificationTag"); + + var replacePackageCodeStart = results[2].IndexOf("\tmsi:package/") + "\tmsi:package/".Length; + var replacePackageCodeEnd = results[2].IndexOf("\t", replacePackageCodeStart); + results[2] = results[2].Substring(0, replacePackageCodeStart) + "???" + results[2].Substring(replacePackageCodeEnd); + WixAssert.CompareLineByLine(new[] + { + "File:filF5_pLhBuF5b4N9XEo52g_hUM5Lo\tfilF5_pLhBuF5b4N9XEo52g_hUM5Lo\texample.txt\t20\t\t\t512\t1", + "File:tagEYRYWwOt95punO7qPPAQ9p1GBpY\ttagEYRYWwOt95punO7qPPAQ9p1GBpY\trdcfonyt.swi|~TagTestPackage.swidtag\t449\t\t\t1\t2", + "SoftwareIdentificationTag:tagEYRYWwOt95punO7qPPAQ9p1GBpY\twixtoolset.org\tmsi:package/???\tmsi:upgrade/047730A5-30FE-4A62-A520-DA9381B8226A\t" + }, results.ToArray()); + } + + [Fact] + public void CanBuildBundleWithTag() + { + var testDataFolder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(testDataFolder, "ProductTag", "PackageWithTag.wxs"), + Path.Combine(testDataFolder, "ProductTag", "PackageComponents.wxs"), + "-loc", Path.Combine(testDataFolder, "ProductTag", "Package.en-us.wxl"), + "-bindpath", Path.Combine(testDataFolder, "ProductTag"), + "-intermediateFolder", Path.Combine(intermediateFolder, "package"), + "-o", Path.Combine(baseFolder, "package", @"test.msi") + }); + + result.AssertSuccess(); + + result = WixRunner.Execute(new[] + { + "build", + Path.Combine(testDataFolder, "BundleTag", "BundleWithTag.wxs"), + "-bindpath", Path.Combine(testDataFolder, "BundleTag"), + "-bindpath", Path.Combine(baseFolder, "package"), + "-intermediateFolder", intermediateFolder, + "-o", Path.Combine(baseFolder, @"bin\test.exe") + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.exe"))); + Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb"))); + + using (var ouput = WixOutput.Read(Path.Combine(baseFolder, @"bin\test.wixpdb"))) + { + var badata = ouput.GetDataStream("wix-burndata.xml"); + var doc = XDocument.Load(badata); + + var swidTag = doc.Root.Element(BurnManifestNamespace + "Registration").Element(BurnManifestNamespace + "SoftwareTag").Value; + + var swidTagPath = Path.Combine(baseFolder, "test.swidtag"); + File.WriteAllText(swidTagPath, swidTag); + + var docTag = XDocument.Load(swidTagPath); + var title = docTag.Root.Attribute("name").Value; + var version = docTag.Root.Attribute("version").Value; + Assert.Equal("~TagTestBundle", title); + Assert.Equal("4.3.2.1", version); + } + } + } + + private static void Build(string[] args) + { + var result = WixRunner.Execute(args) + .AssertSuccess(); + } + } +} diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/BundleWithTag.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/BundleWithTag.wxs new file mode 100644 index 00000000..f44fb7bc --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/BundleWithTag.wxs @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/fakeba.dll b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/fakeba.dll new file mode 100644 index 00000000..64061ea0 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/fakeba.dll @@ -0,0 +1 @@ +This is fakeba.dll. \ No newline at end of file diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/Package.en-us.wxl b/src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageComponents.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageComponents.wxs new file mode 100644 index 00000000..37a2c462 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageComponents.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageWithTag.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageWithTag.wxs new file mode 100644 index 00000000..17543c1a --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageWithTag.wxs @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/example.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/example.txt new file mode 100644 index 00000000..1b4ffe8a --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/example.txt @@ -0,0 +1 @@ +This is example.txt. \ No newline at end of file -- cgit v1.2.3-55-g6feb