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(); //
+ }
+ }
+ }
+}