diff --git a/global.json b/global.json index 77a8132..8b25293 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,5 @@ { "msbuild-sdks": { - "WixToolset.Sdk": "4.0.0-build-0170" + "WixToolset.Sdk": "4.0.0-build-0191" } } diff --git a/src/test/WixToolsetTest.Tag/BundleTagExtensionFixture.cs b/src/test/WixToolsetTest.Tag/BundleTagExtensionFixture.cs new file mode 100644 index 0000000..a34a393 --- /dev/null +++ b/src/test/WixToolsetTest.Tag/BundleTagExtensionFixture.cs @@ -0,0 +1,75 @@ +// 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.Tag +{ + using System; + using System.IO; + using System.Xml.Linq; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using WixToolset.Tag; + using Xunit; + + public class BundleTagExtensionFixture + { + private static readonly XNamespace SwidTagNamespace = "http://standards.iso.org/iso/19770/-2/2009/schema.xsd"; + + [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 extPath = Path.GetFullPath(new Uri(typeof(TagExtensionFactory).Assembly.CodeBase).LocalPath); + + 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"), + "-ext", extPath, + "-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"), + "-ext", extPath, + "-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-badata.xml"); + var doc = XDocument.Load(badata); + + var swidTag = doc.Root.Element("WixSoftwareTag").Value; + var docTag = XDocument.Parse(swidTag); + + var title = docTag.Root.Element(SwidTagNamespace + "product_title").Value; + var version = docTag.Root.Element(SwidTagNamespace + "product_version").Element(SwidTagNamespace + "name").Value; + Assert.Equal("~TagTestBundle", title); + Assert.Equal("4.3.2.1", version); + } + } + + } +} diff --git a/src/test/WixToolsetTest.Tag/TagExtensionFixture.cs b/src/test/WixToolsetTest.Tag/PackageTagExtensionFixture.cs similarity index 53% rename from src/test/WixToolsetTest.Tag/TagExtensionFixture.cs rename to src/test/WixToolsetTest.Tag/PackageTagExtensionFixture.cs index eb8ed6c..e3d7224 100644 --- a/src/test/WixToolsetTest.Tag/TagExtensionFixture.cs +++ b/src/test/WixToolsetTest.Tag/PackageTagExtensionFixture.cs @@ -8,18 +8,20 @@ namespace WixToolsetTest.Tag using WixToolset.Tag; using Xunit; - public class TagExtensionFixture + public class PackageTagExtensionFixture { - [Fact(Skip = "Currently fails")] - public void CanBuildUsingProductTag() + [Fact] + public void CanBuildPackageWithTag() { var folder = TestData.Get(@"TestData\ProductTag"); var build = new Builder(folder, typeof(TagExtensionFactory), new[] { folder }); - var results = build.BuildAndQuery(Build, "Property"); - Assert.Equal(new[] + var results = build.BuildAndQuery(Build, "File", "SoftwareIdentificationTag"); + WixAssert.CompareLineByLine(new[] { - "Property:", + "File:filF5_pLhBuF5b4N9XEo52g_hUM5Lo\tfilF5_pLhBuF5b4N9XEo52g_hUM5Lo\texample.txt\t20\t\t\t512\t1", + "File:tagkVqdRrA2JRwvrWCPR6pTv7eOzoE\ttagkVqdRrA2JRwvrWCPR6pTv7eOzoE\tth_a8yhh|regid.2008-09.org.wixtoolset ~TagTestPackage.swidtag\t910\t\t\t1\t2", + "SoftwareIdentificationTag:tagkVqdRrA2JRwvrWCPR6pTv7eOzoE\tregid.2008-09.org.wixtoolset\t8738B0C5-C4AA-4634-8C03-11EAA2F1E15D\tComponent" }, results.ToArray()); } diff --git a/src/test/WixToolsetTest.Tag/TestData/BundleTag/BundleWithTag.wxs b/src/test/WixToolsetTest.Tag/TestData/BundleTag/BundleWithTag.wxs new file mode 100644 index 0000000..9b06c37 --- /dev/null +++ b/src/test/WixToolsetTest.Tag/TestData/BundleTag/BundleWithTag.wxs @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/test/WixToolsetTest.Tag/TestData/BundleTag/fakeba.dll b/src/test/WixToolsetTest.Tag/TestData/BundleTag/fakeba.dll new file mode 100644 index 0000000..64061ea --- /dev/null +++ b/src/test/WixToolsetTest.Tag/TestData/BundleTag/fakeba.dll @@ -0,0 +1 @@ +This is fakeba.dll. \ No newline at end of file diff --git a/src/test/WixToolsetTest.Tag/TestData/ProductTag/Package.wxs b/src/test/WixToolsetTest.Tag/TestData/ProductTag/PackageWithTag.wxs similarity index 74% rename from src/test/WixToolsetTest.Tag/TestData/ProductTag/Package.wxs rename to src/test/WixToolsetTest.Tag/TestData/ProductTag/PackageWithTag.wxs index e96f49f..024c2cd 100644 --- a/src/test/WixToolsetTest.Tag/TestData/ProductTag/Package.wxs +++ b/src/test/WixToolsetTest.Tag/TestData/ProductTag/PackageWithTag.wxs @@ -1,5 +1,5 @@ - + diff --git a/src/test/WixToolsetTest.Tag/WixToolsetTest.Tag.csproj b/src/test/WixToolsetTest.Tag/WixToolsetTest.Tag.csproj index 6a8cdb5..5d9f5a1 100644 --- a/src/test/WixToolsetTest.Tag/WixToolsetTest.Tag.csproj +++ b/src/test/WixToolsetTest.Tag/WixToolsetTest.Tag.csproj @@ -12,10 +12,7 @@ - - - - + diff --git a/src/wixext/TagBurnBackendBinderExtension.cs b/src/wixext/TagBurnBackendBinderExtension.cs new file mode 100644 index 0000000..2f76415 --- /dev/null +++ b/src/wixext/TagBurnBackendBinderExtension.cs @@ -0,0 +1,167 @@ +// 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.Tag +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Text; + using System.Xml.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Dtf.WindowsInstaller; + using WixToolset.Extensibility; + using WixToolset.Tag.Symbols; + using WixToolset.Tag.Writer; + + /// + /// The Binder for the WiX Toolset Software Id Tag Extension. + /// + public sealed class TagBurnBackendBinderExtension : BaseBurnBackendBinderExtension + { + /// + /// Called for each extension symbol that hasn't been handled yet. + /// + /// The linked section. + /// The current symbol. + /// True if the symbol is a Bundle tag symbol. + public override bool TryProcessSymbol(IntermediateSection section, IntermediateSymbol symbol) + { + if (symbol is WixBundleTagSymbol tagSymbol) + { + tagSymbol.Xml = CalculateTagXml(section, tagSymbol); + + var fragment = new XDocument( + new XElement("WixSoftwareTag", + new XAttribute("Filename", tagSymbol.Filename), + new XAttribute("Regid", tagSymbol.Regid), + new XCData(tagSymbol.Xml) + ) + ); + + this.BackendHelper.AddBootstrapperApplicationData(fragment.Root.ToString(SaveOptions.DisableFormatting | SaveOptions.OmitDuplicateNamespaces)); + return true; + } + + return false; + } + + private static string CalculateTagXml(IntermediateSection section, WixBundleTagSymbol tagSymbol) + { + var bundleSymbol = section.Symbols.OfType().Single(); + var uniqueId = Guid.Parse(bundleSymbol.BundleId).ToString("D"); + var bundleVersion = TagWriter.CreateFourPartVersion(bundleSymbol.Version); + + var packageTags = CollectPackageTags(section); + + var licensed = (tagSymbol.Attributes == 1); + if (!Enum.TryParse(tagSymbol.Type, out TagType type)) + { + type = TagType.Application; + } + + var containedTags = CalculateContainedTagsAndType(packageTags, ref type); + + using (var ms = new MemoryStream()) + { + TagWriter.CreateTagFile(ms, tagSymbol.Regid, uniqueId.ToUpperInvariant(), bundleSymbol.Name, bundleVersion, bundleSymbol.Manufacturer, licensed, type, containedTags); + + // Use StreamReader to "eat" the BOM if present. + ms.Position = 0; + using (var streamReader = new StreamReader(ms, Encoding.UTF8)) + { + return streamReader.ReadToEnd(); + } + } + } + + private static IList 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`, `UniqueId`, `Type` FROM `SoftwareIdentificationTag`")) + { + view.Execute(); + while (true) + { + using (var record = view.Fetch()) + { + if (null == record) + { + break; + } + + if (!Enum.TryParse(record.GetString(3), out TagType type)) + { + type = TagType.Unknown; + } + + tags.Add(new SoftwareTag() { Regid = record.GetString(1), Id = record.GetString(2), Type = type }); + } + } + } + } + } + } + + return tags; + } + + private static IEnumerable CalculateContainedTagsAndType(IEnumerable allTags, ref TagType type) + { + var containedTags = new List(); + + foreach (var tag in allTags) + { + // If this tag type is an Application or Group then try to coerce our type to a Group. + if (TagType.Application == tag.Type || TagType.Group == tag.Type) + { + // If the type is still unknown, change our tag type and clear any contained tags that might have already + // been colllected. + if (TagType.Unknown == type) + { + type = TagType.Group; + containedTags = new List(); + } + + // If we are a Group then we can add this as a contained tag, otherwise skip it. + if (TagType.Group == type) + { + containedTags.Add(tag); + } + + // TODO: should we warn if this bundle is typed as a non-Group software id tag but is actually + // carrying Application or Group software tags? + } + else if (TagType.Component == tag.Type || TagType.Feature == tag.Type) + { + // We collect Component and Feature tags only if the our tag is an Application or might still default to an Application. + if (TagType.Application == type || TagType.Unknown == type) + { + containedTags.Add(tag); + } + } + } + + // If our type was not set by now, we'll default to an Application. + if (TagType.Unknown == type) + { + type = TagType.Application; + } + + return containedTags; + } + } +} diff --git a/src/wixext/TagCompiler.cs b/src/wixext/TagCompiler.cs index eea38b2..f4d33c9 100644 --- a/src/wixext/TagCompiler.cs +++ b/src/wixext/TagCompiler.cs @@ -256,6 +256,7 @@ namespace WixToolset.Tag this.ParseHelper.EnsureTable(section, sourceLineNumbers, TagTableDefinitions.SoftwareIdentificationTag); var symbol = section.AddSymbol(new WixProductTagSymbol(sourceLineNumbers, fileId) { + FileRef = fileId.Id, Regid = regid, Name = name, Type = type, diff --git a/src/wixext/TagErrors.cs b/src/wixext/TagErrors.cs index 72da684..de74651 100644 --- a/src/wixext/TagErrors.cs +++ b/src/wixext/TagErrors.cs @@ -12,6 +12,11 @@ namespace WixToolset.Data 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 SingleRegIdPerProduct(SourceLineNumber sourceLineNumbers, string regid, string firstRegid, SourceLineNumber firstSourceLineNumbers) + { + return Message(sourceLineNumbers, Ids.SingleRegIdPerProduct, "All of the Tag/@Regid attribute values in a package must match. The RegId '{0}' does not match the first RegId '{1}' found at: {2}.", regid, firstRegid, firstSourceLineNumbers.ToString()); + } + private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args) { return new Message(sourceLineNumber, MessageLevel.Error, (int)id, format, args); @@ -25,6 +30,7 @@ namespace WixToolset.Data public enum Ids { IllegalName = 6601, + SingleRegIdPerProduct = 6602, } } } diff --git a/src/wixext/TagExtensionFactory.cs b/src/wixext/TagExtensionFactory.cs index 0853cca..982d662 100644 --- a/src/wixext/TagExtensionFactory.cs +++ b/src/wixext/TagExtensionFactory.cs @@ -12,6 +12,7 @@ namespace WixToolset.Tag { typeof(TagCompiler), typeof(TagExtensionData), + typeof(TagBurnBackendBinderExtension), typeof(TagWindowsInstallerBackendBinderExtension), }; } diff --git a/src/wixext/TagWindowsInstallerBackendBinderExtension.cs b/src/wixext/TagWindowsInstallerBackendBinderExtension.cs index 8e47a78..111a703 100644 --- a/src/wixext/TagWindowsInstallerBackendBinderExtension.cs +++ b/src/wixext/TagWindowsInstallerBackendBinderExtension.cs @@ -6,417 +6,213 @@ namespace WixToolset.Tag 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.Data.WindowsInstaller; - using WixToolset.Dtf.WindowsInstaller; using WixToolset.Extensibility; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + using WixToolset.Tag.Symbols; + using WixToolset.Tag.Writer; /// - /// The Binder for the WiX Toolset Software Id Tag Extension. + /// The Windows Installer backend enhancements for the WiX Toolset Software Id Tag Extension. /// - public sealed class TagWindowsInstallerBackendBinderExtension : BaseWindowsInstallerBackendBinderExtension + public sealed class TagWindowsInstallerBackendBinderExtension : BaseWindowsInstallerBackendBinderExtension, IResolverExtension { public override IEnumerable TableDefinitions => TagTableDefinitions.All; -#if TODO_TAG_BINDER_EXTENSION - private string overallRegid; - private RowDictionary swidRows = new RowDictionary(); + private IBackendHelper backendHelper; + private string workingFolder; /// - /// Called before database binding occurs. + /// Ensures the first draft of the SWID Tag exists on disk when being included + /// in MSI packages. /// - public override void Initialize(Output output) + /// Resolve context. + public void PreResolve(IResolveContext context) { + var section = context.IntermediateRepresentation.Sections.FirstOrDefault(); + // Only process MSI packages. - if (OutputType.Product != output.Type) + if (SectionType.Product != section?.Type) { return; } - this.overallRegid = null; // always reset overall regid on initialize. + this.backendHelper = context.ServiceProvider.GetService(); - // Ensure the tag files are generated to be imported by the MSI. - this.CreateProductTagFiles(output); + this.workingFolder = CalculateWorkingFolder(context.IntermediateFolder); + + // Ensure any tag files are generated to be imported by the MSI. + var tagSymbols = this.CreateProductTagFiles(section); + + var tagSymbol = tagSymbols.FirstOrDefault(); + if (tagSymbol != null) + { + // If we created any tag files, add a WixVariable to map to the regid. + section.AddSymbol(new WixVariableSymbol(tagSymbol.SourceLineNumbers, new Identifier(AccessModifier.Private, "WixTagRegid")) + { + Value = tagSymbol.Regid, + Overridable = false + }); + } + } + + /// + /// Unused. + /// + public IResolveFileResult ResolveFile(string source, IntermediateSymbolDefinition symbolDefinition, SourceLineNumber sourceLineNumbers, BindStage bindStage) => null; + + /// + /// Unused. + /// + public void PostResolve(IResolveResult result) { } + + /// + /// Called before database binding occurs. + /// + public override void PreBackendBind(IBindContext context) + { + base.PreBackendBind(context); + + this.backendHelper = base.BackendHelper; + + this.workingFolder = CalculateWorkingFolder(context.IntermediateFolder); } /// /// Called after database variable resolution occurs. /// - public override void AfterResolvedFields(Output output) + public override void SymbolsFinalized(IntermediateSection section) { // Only process MSI packages. - if (OutputType.Product != output.Type) + if (section.Type != SectionType.Product) { return; } - Table wixBindUpdateFilesTable = output.Tables["WixBindUpdatedFiles"]; + var tagSymbols = this.CreateProductTagFiles(section); - // We'll end up re-writing the tag files but this time we may have the ProductCode - // now to use as the unique id. - List updatedFileRows = this.CreateProductTagFiles(output); - foreach (WixFileRow updateFileRow in updatedFileRows) + foreach (var tagSymbol in tagSymbols) { - Row row = wixBindUpdateFilesTable.CreateRow(updateFileRow.SourceLineNumbers); - row[0] = updateFileRow.File; + section.Symbols.Add(tagSymbol); } } - /// - /// Called after all output changes occur and right before the output is bound into its final format. - /// - public override void Finish(Output output) + private IEnumerable CreateProductTagFiles(IntermediateSection section) { - // Only finish bundles. - if (OutputType.Bundle != output.Type) + var productTags = section.Symbols.OfType().ToList(); + if (!productTags.Any()) { - return; + return null; } - this.overallRegid = null; // always reset overall regid on initialize. + string productCode = null; + string productName = null; + string productVersion = null; + string manufacturer = null; - Table tagTable = output.Tables["WixBundleTag"]; - if (null != tagTable) + foreach (var property in section.Symbols.OfType()) { - Table table = output.Tables["WixBundle"]; - WixBundleRow bundleInfo = (WixBundleRow)table.Rows[0]; - Version bundleVersion = TagBinder.CreateFourPartVersion(bundleInfo.Version); - - // Try to collect all the software id tags from all the child packages. - IList allTags = TagBinder.CollectPackageTags(output); - - foreach (Row tagRow in tagTable.Rows) + switch (property.Id.Id) { - string regid = (string)tagRow[1]; - string name = (string)tagRow[2]; - bool licensed = (null != tagRow[3] && 0 != (int)tagRow[3]); - string typeString = (string)tagRow[5]; - - TagType type = String.IsNullOrEmpty(typeString) ? TagType.Unknown : (TagType)Enum.Parse(typeof(TagType), typeString); - IList containedTags = TagBinder.CalculateContainedTagsAndType(allTags, ref type); - - using (MemoryStream ms = new MemoryStream()) - { - TagBinder.CreateTagFile(ms, regid, bundleInfo.BundleId.ToString("D").ToUpperInvariant(), bundleInfo.Name, bundleVersion, bundleInfo.Publisher, licensed, type, containedTags); - tagRow[4] = Encoding.UTF8.GetString(ms.ToArray()); - } + case "ProductCode": + productCode = property.Value; + break; + case "ProductName": + productName = property.Value; + break; + case "ProductVersion": + productVersion = TagWriter.CreateFourPartVersion(property.Value); + break; + case "Manufacturer": + manufacturer = property.Value; + break; } } - } - private List CreateProductTagFiles(Output output) - { - List updatedFileRows = new List(); - SourceLineNumber sourceLineNumbers = null; - - Table tagTable = output.Tables["WixProductTag"]; - if (null != tagTable) + // If the ProductCode is available, only keep it if it is a GUID. + if (!String.IsNullOrEmpty(productCode)) { - string productCode = null; - string productName = null; - Version productVersion = null; - string manufacturer = null; - - Table properties = output.Tables["Property"]; - foreach (Row property in properties.Rows) + if (productCode.Equals("*")) { - switch ((string)property[0]) - { - case "ProductCode": - productCode = (string)property[1]; - break; - case "ProductName": - productName = (string)property[1]; - break; - case "ProductVersion": - productVersion = TagBinder.CreateFourPartVersion((string)property[1]); - break; - case "Manufacturer": - manufacturer = (string)property[1]; - break; - } + productCode = null; + } + else if (Guid.TryParse(productCode, out var guid)) + { + productCode = guid.ToString("D").ToUpperInvariant(); + } + else // not a GUID, erase it. + { + productCode = null; + } + } + + Directory.CreateDirectory(this.workingFolder); + + var fileSymbols = section.Symbols.OfType().ToList(); + var swidSymbols = new Dictionary(); + + foreach (var tagSymbol in productTags) + { + var tagFileRef = tagSymbol.FileRef; + var licensed = (tagSymbol.Attributes == 1); + var uniqueId = String.IsNullOrEmpty(productCode) ? tagSymbol.Name.Replace(" ", "-") : productCode; + + if (!Enum.TryParse(tagSymbol.Type, out TagType type)) + { + type = TagType.Application; } - // If the ProductCode is available, only keep it if it is a GUID. - if (!String.IsNullOrEmpty(productCode)) + // Ensure all tag symbols in this product share a regid. + var firstTagSymbol = swidSymbols.Values.FirstOrDefault(); + if (firstTagSymbol != null && firstTagSymbol.Regid != tagSymbol.Regid) { - if (productCode.Equals("*")) + this.Messaging.Write(TagErrors.SingleRegIdPerProduct(tagSymbol.SourceLineNumbers, tagSymbol.Regid, firstTagSymbol.Regid, firstTagSymbol.SourceLineNumbers)); + continue; + } + + // Find the FileSymbol that is referenced by this WixProductTag. + var fileSymbol = fileSymbols.FirstOrDefault(f => f.Id.Id == tagFileRef); + if (fileSymbol != null) + { + var fileName = String.IsNullOrEmpty(fileSymbol.Name) ? Path.GetFileName(fileSymbol.Source.Path) : fileSymbol.Name; + + // Write the tag file. + fileSymbol.Source = new IntermediateFieldPathValue { - productCode = null; + Path = Path.Combine(this.workingFolder, fileName) + }; + + this.backendHelper.TrackFile(fileSymbol.Source.Path, TrackedFileType.Temporary, fileSymbol.SourceLineNumbers); + + using (var fs = new FileStream(fileSymbol.Source.Path, FileMode.Create)) + { + TagWriter.CreateTagFile(fs, tagSymbol.Regid, uniqueId, productName, productVersion, manufacturer, licensed, type, null); } - else + + // Ensure the matching "SoftwareIdentificationTag" symbol exists and is populated correctly. + if (!swidSymbols.TryGetValue(tagFileRef, out var swidRow)) { - try + swidRow = new SoftwareIdentificationTagSymbol(fileSymbol.SourceLineNumbers) { - Guid guid = new Guid(productCode); - productCode = guid.ToString("D").ToUpperInvariant(); - } - catch // not a GUID, erase it. - { - productCode = null; - } - } - } + FileRef = tagFileRef, + Regid = tagSymbol.Regid + }; - Table wixFileTable = output.Tables["WixFile"]; - foreach (Row tagRow in tagTable.Rows) - { - string fileId = (string)tagRow[0]; - string regid = (string)tagRow[1]; - string name = (string)tagRow[2]; - bool licensed = (null != tagRow[3] && 1 == (int)tagRow[3]); - string typeString = (string)tagRow[4]; - - TagType type = String.IsNullOrEmpty(typeString) ? TagType.Application : (TagType)Enum.Parse(typeof(TagType), typeString); - string uniqueId = String.IsNullOrEmpty(productCode) ? name.Replace(" ", "-") : productCode; - - if (String.IsNullOrEmpty(this.overallRegid)) - { - this.overallRegid = regid; - sourceLineNumbers = tagRow.SourceLineNumbers; - } - else if (!this.overallRegid.Equals(regid, StringComparison.Ordinal)) - { - // TODO: display error that only one regid supported. + swidSymbols.Add(tagFileRef, swidRow); } - // Find the WixFileRow that matches for this WixProductTag. - foreach (WixFileRow wixFileRow in wixFileTable.Rows) - { - if (fileId == wixFileRow.File) - { - // Write the tag file. - wixFileRow.Source = Path.GetTempFileName(); - using (FileStream fs = new FileStream(wixFileRow.Source, FileMode.Create)) - { - TagBinder.CreateTagFile(fs, regid, uniqueId, productName, productVersion, manufacturer, licensed, type, null); - } - - updatedFileRows.Add(wixFileRow); // remember that we modified this file. - - // Ensure the matching "SoftwareIdentificationTag" row exists and - // is populated correctly. - Row swidRow; - if (!this.swidRows.TryGetValue(fileId, out swidRow)) - { - Table swid = output.Tables["SoftwareIdentificationTag"]; - swidRow = swid.CreateRow(wixFileRow.SourceLineNumbers); - swidRow[0] = fileId; - swidRow[1] = this.overallRegid; - - this.swidRows.Add(swidRow); - } - - // Always rewrite. - swidRow[2] = uniqueId; - swidRow[3] = type.ToString(); - - break; - } - } + // Always rewrite. + swidRow.TagId = uniqueId; + swidRow.Type = type.ToString(); } } - // If we remembered the source line number for the regid, then add - // a WixVariable to map to the regid. - if (null != sourceLineNumbers) - { - Table wixVariableTable = output.EnsureTable(this.Core.TableDefinitions["WixVariable"]); - WixVariableRow wixVariableRow = (WixVariableRow)wixVariableTable.CreateRow(sourceLineNumbers); - wixVariableRow.Id = "WixTagRegid"; - wixVariableRow.Value = this.overallRegid; - wixVariableRow.Overridable = false; - } - - return updatedFileRows; + return swidSymbols.Values; } - private static Version CreateFourPartVersion(string versionString) - { - Version version = new Version(versionString); - return new Version(version.Major, - -1 < version.Minor ? version.Minor : 0, - -1 < version.Build ? version.Build : 0, - -1 < version.Revision ? version.Revision : 0); - } - - private static IList CollectPackageTags(Output bundle) - { - List tags = new List(); - Table packageTable = bundle.Tables["WixBundlePackage"]; - if (null != packageTable) - { - Table payloadTable = bundle.Tables["WixBundlePayload"]; - RowDictionary payloads = new RowDictionary(payloadTable); - - foreach (WixBundlePackageRow row in packageTable.RowsAs()) - { - if (WixBundlePackageType.Msi == row.Type) - { - string packagePayloadId = row.PackagePayload; - WixBundlePayloadRow payload = payloads.Get(packagePayloadId); - - using (Database db = new Database(payload.FullFileName)) - { - if (db.Tables.Contains("SoftwareIdentificationTag")) - { - using (View view = db.OpenView("SELECT `Regid`, `UniqueId`, `Type` FROM `SoftwareIdentificationTag`")) - { - view.Execute(); - while (true) - { - using (Record record = view.Fetch()) - { - if (null == record) - { - break; - } - - TagType type = String.IsNullOrEmpty(record.GetString(3)) ? TagType.Unknown : (TagType)Enum.Parse(typeof(TagType), record.GetString(3)); - tags.Add(new SoftwareTag() { Regid = record.GetString(1), Id = record.GetString(2), Type = type }); - } - } - } - } - } - } - } - } - - return tags; - } - - private static IList CalculateContainedTagsAndType(IEnumerable allTags, ref TagType type) - { - List containedTags = new List(); - foreach (SoftwareTag tag in allTags) - { - // If this tag type is an Application or Group then try to coerce our type to a Group. - if (TagType.Application == tag.Type || TagType.Group == tag.Type) - { - // If the type is still unknown, change our tag type and clear any contained tags that might have already - // been colllected. - if (TagType.Unknown == type) - { - type = TagType.Group; - containedTags = new List(); - } - - // If we are a Group then we can add this as a contained tag, otherwise skip it. - if (TagType.Group == type) - { - containedTags.Add(tag); - } - - // TODO: should we warn if this bundle is typed as a non-Group software id tag but is actually - // carrying Application or Group software tags? - } - else if (TagType.Component == tag.Type || TagType.Feature == tag.Type) - { - // We collect Component and Feature tags only if the our tag is an Application or might still default to an Application. - if (TagType.Application == type || TagType.Unknown == type) - { - containedTags.Add(tag); - } - } - } - - // If our type was not set by now, we'll default to an Application. - if (TagType.Unknown == type) - { - type = TagType.Application; - } - - return containedTags; - } - - private static void CreateTagFile(Stream stream, string regid, string uniqueId, string name, Version version, string manufacturer, bool licensed, TagType tagType, IList containedTags) - { - using (XmlTextWriter writer = new XmlTextWriter(stream, Encoding.UTF8)) - { - writer.WriteStartDocument(); - writer.WriteStartElement("software_identification_tag", "http://standards.iso.org/iso/19770/-2/2009/schema.xsd"); - writer.WriteElementString("entitlement_required_indicator", licensed ? "true" : "false"); - - writer.WriteElementString("product_title", name); - - writer.WriteStartElement("product_version"); - writer.WriteElementString("name", version.ToString()); - writer.WriteStartElement("numeric"); - writer.WriteElementString("major", version.Major.ToString()); - writer.WriteElementString("minor", version.Minor.ToString()); - writer.WriteElementString("build", version.Build.ToString()); - writer.WriteElementString("review", version.Revision.ToString()); - writer.WriteEndElement(); - writer.WriteEndElement(); - - writer.WriteStartElement("software_creator"); - writer.WriteElementString("name", manufacturer); - writer.WriteElementString("regid", regid); - writer.WriteEndElement(); - - if (licensed) - { - writer.WriteStartElement("software_licensor"); - writer.WriteElementString("name", manufacturer); - writer.WriteElementString("regid", regid); - writer.WriteEndElement(); - } - - writer.WriteStartElement("software_id"); - writer.WriteElementString("unique_id", uniqueId); - writer.WriteElementString("tag_creator_regid", regid); - writer.WriteEndElement(); - - writer.WriteStartElement("tag_creator"); - writer.WriteElementString("name", manufacturer); - writer.WriteElementString("regid", regid); - writer.WriteEndElement(); - - if (null != containedTags && 0 < containedTags.Count) - { - writer.WriteStartElement("complex_of"); - foreach (SoftwareTag tag in containedTags) - { - writer.WriteStartElement("software_id"); - writer.WriteElementString("unique_id", tag.Id); - writer.WriteElementString("tag_creator_regid", tag.Regid); - writer.WriteEndElement(); // - } - writer.WriteEndElement(); // - } - - if (TagType.Unknown != tagType) - { - writer.WriteStartElement("extended_information"); - writer.WriteStartElement("tag_type", "http://www.tagvault.org/tv_extensions.xsd"); - writer.WriteValue(tagType.ToString()); - writer.WriteEndElement(); // - writer.WriteEndElement(); // - } - - writer.WriteEndElement(); // - } - } - - private enum TagType - { - Unknown, - Application, - Component, - Feature, - Group, - Patch, - } - - private class SoftwareTag - { - public string Regid { get; set; } - public string Id { get; set; } - public TagType Type { get; set; } - } -#endif + private static string CalculateWorkingFolder(string intermediateFolder) => Path.Combine(intermediateFolder, "_swidtag"); } } diff --git a/src/wixext/Writer/SoftwareTag.cs b/src/wixext/Writer/SoftwareTag.cs new file mode 100644 index 0000000..a4879e7 --- /dev/null +++ b/src/wixext/Writer/SoftwareTag.cs @@ -0,0 +1,13 @@ +// 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.Tag.Writer +{ + public class SoftwareTag + { + public string Regid { get; set; } + + public string Id { get; set; } + + public TagType Type { get; set; } + } +} diff --git a/src/wixext/Writer/TagType.cs b/src/wixext/Writer/TagType.cs new file mode 100644 index 0000000..7edf101 --- /dev/null +++ b/src/wixext/Writer/TagType.cs @@ -0,0 +1,14 @@ +// 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.Tag.Writer +{ + public enum TagType + { + Unknown, + Application, + Component, + Feature, + Group, + Patch, + } +} diff --git a/src/wixext/Writer/TagWriter.cs b/src/wixext/Writer/TagWriter.cs new file mode 100644 index 0000000..b72eafa --- /dev/null +++ b/src/wixext/Writer/TagWriter.cs @@ -0,0 +1,105 @@ +// 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.Tag.Writer +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Text; + using System.Xml; + + public static class TagWriter + { + private static readonly XmlWriterSettings WriterSettings = new XmlWriterSettings + { + CloseOutput = false, + Encoding = Encoding.UTF8 + }; + + public static string CreateFourPartVersion(string versionString) + { + if (Version.TryParse(versionString, out var version)) + { + versionString = new Version(version.Major, + -1 < version.Minor ? version.Minor : 0, + -1 < version.Build ? version.Build : 0, + -1 < version.Revision ? version.Revision : 0).ToString(); + } + + return versionString; + } + + public static void CreateTagFile(Stream stream, string regid, string uniqueId, string name, string version, string manufacturer, bool licensed, TagType tagType, IEnumerable containedTags) + { + using (var writer = XmlWriter.Create(stream, WriterSettings)) + { + writer.WriteStartDocument(); + writer.WriteStartElement("software_identification_tag", "http://standards.iso.org/iso/19770/-2/2009/schema.xsd"); + writer.WriteElementString("entitlement_required_indicator", licensed ? "true" : "false"); + + writer.WriteElementString("product_title", name); + + writer.WriteStartElement("product_version"); + writer.WriteElementString("name", version.ToString()); + if (Version.TryParse(version, out var parsedVersion)) + { + writer.WriteStartElement("numeric"); + writer.WriteElementString("major", parsedVersion.Major.ToString()); + writer.WriteElementString("minor", parsedVersion.Minor.ToString()); + writer.WriteElementString("build", parsedVersion.Build.ToString()); + writer.WriteElementString("review", parsedVersion.Revision.ToString()); + writer.WriteEndElement(); + } + writer.WriteEndElement(); + + writer.WriteStartElement("software_creator"); + writer.WriteElementString("name", manufacturer); + writer.WriteElementString("regid", regid); + writer.WriteEndElement(); + + if (licensed) + { + writer.WriteStartElement("software_licensor"); + writer.WriteElementString("name", manufacturer); + writer.WriteElementString("regid", regid); + writer.WriteEndElement(); + } + + writer.WriteStartElement("software_id"); + writer.WriteElementString("unique_id", uniqueId); + writer.WriteElementString("tag_creator_regid", regid); + writer.WriteEndElement(); + + writer.WriteStartElement("tag_creator"); + writer.WriteElementString("name", manufacturer); + writer.WriteElementString("regid", regid); + writer.WriteEndElement(); + + if (containedTags?.Any() == true) + { + writer.WriteStartElement("complex_of"); + foreach (var tag in containedTags) + { + writer.WriteStartElement("software_id"); + writer.WriteElementString("unique_id", tag.Id); + writer.WriteElementString("tag_creator_regid", tag.Regid); + writer.WriteEndElement(); // + } + writer.WriteEndElement(); // + } + + if (TagType.Unknown != tagType) + { + writer.WriteStartElement("extended_information"); + writer.WriteStartElement("tag_type", "http://www.tagvault.org/tv_extensions.xsd"); + writer.WriteValue(tagType.ToString()); + writer.WriteEndElement(); // + writer.WriteEndElement(); // + } + + writer.WriteEndElement(); // + } + } + } +}