Implement TagExtension as Binder extension
This commit is contained in:
Родитель
e82ac3e6e4
Коммит
b253e5f9f8
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"msbuild-sdks": {
|
||||
"WixToolset.Sdk": "4.0.0-build-0170"
|
||||
"WixToolset.Sdk": "4.0.0-build-0191"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" xmlns:swid="http://wixtoolset.org/schemas/v4/wxs/tag">
|
||||
<Bundle Name="~TagTestBundle" Version="4.3.2.1" Manufacturer="Example Corporation" UpgradeCode="047730A5-30FE-4A62-A520-DA9381B8226A">
|
||||
<BootstrapperApplication>
|
||||
<BootstrapperApplicationDll SourceFile="fakeba.dll" />
|
||||
</BootstrapperApplication>
|
||||
|
||||
<swid:Tag Regid="regid.2008-09.org.wixtoolset" />
|
||||
|
||||
<Chain>
|
||||
<MsiPackage SourceFile="test.msi">
|
||||
<MsiProperty Name="TEST" Value="1" />
|
||||
</MsiPackage>
|
||||
</Chain>
|
||||
</Bundle>
|
||||
</Wix>
|
|
@ -0,0 +1 @@
|
|||
This is fakeba.dll.
|
|
@ -1,5 +1,5 @@
|
|||
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" xmlns:swid="http://wixtoolset.org/schemas/v4/wxs/tag">
|
||||
<Package Name="MsiPackage" Language="1033" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a">
|
||||
<Package ProductCode="8738B0C5-C4AA-4634-8C03-11EAA2F1E15D" Name="~TagTestPackage" Language="1033" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a">
|
||||
|
||||
<MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" />
|
||||
|
|
@ -12,10 +12,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="TestData\ProductTag\example.txt" CopyToOutputDirectory="PreserveNewest" />
|
||||
<Content Include="TestData\ProductTag\Package.en-us.wxl" CopyToOutputDirectory="PreserveNewest" />
|
||||
<Content Include="TestData\ProductTag\Package.wxs" CopyToOutputDirectory="PreserveNewest" />
|
||||
<Content Include="TestData\ProductTag\PackageComponents.wxs" CopyToOutputDirectory="PreserveNewest" />
|
||||
<Content Include="TestData\**" CopyToOutputDirectory="PreserveNewest" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// The Binder for the WiX Toolset Software Id Tag Extension.
|
||||
/// </summary>
|
||||
public sealed class TagBurnBackendBinderExtension : BaseBurnBackendBinderExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// Called for each extension symbol that hasn't been handled yet.
|
||||
/// </summary>
|
||||
/// <param name="section">The linked section.</param>
|
||||
/// <param name="symbol">The current symbol.</param>
|
||||
/// <returns>True if the symbol is a Bundle tag symbol.</returns>
|
||||
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<WixBundleSymbol>().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<SoftwareTag> CollectPackageTags(IntermediateSection section)
|
||||
{
|
||||
var tags = new List<SoftwareTag>();
|
||||
|
||||
var msiPackages = section.Symbols.OfType<WixBundlePackageSymbol>().Where(s => s.Type == WixBundlePackageType.Msi).ToList();
|
||||
if (msiPackages.Any())
|
||||
{
|
||||
var payloadSymbolsById = section.Symbols.OfType<WixBundlePayloadSymbol>().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<SoftwareTag> CalculateContainedTagsAndType(IEnumerable<SoftwareTag> allTags, ref TagType type)
|
||||
{
|
||||
var containedTags = new List<SoftwareTag>();
|
||||
|
||||
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<SoftwareTag>();
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ namespace WixToolset.Tag
|
|||
{
|
||||
typeof(TagCompiler),
|
||||
typeof(TagExtensionData),
|
||||
typeof(TagBurnBackendBinderExtension),
|
||||
typeof(TagWindowsInstallerBackendBinderExtension),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// The Binder for the WiX Toolset Software Id Tag Extension.
|
||||
/// The Windows Installer backend enhancements for the WiX Toolset Software Id Tag Extension.
|
||||
/// </summary>
|
||||
public sealed class TagWindowsInstallerBackendBinderExtension : BaseWindowsInstallerBackendBinderExtension
|
||||
public sealed class TagWindowsInstallerBackendBinderExtension : BaseWindowsInstallerBackendBinderExtension, IResolverExtension
|
||||
{
|
||||
public override IEnumerable<TableDefinition> TableDefinitions => TagTableDefinitions.All;
|
||||
|
||||
#if TODO_TAG_BINDER_EXTENSION
|
||||
private string overallRegid;
|
||||
private RowDictionary<Row> swidRows = new RowDictionary<Row>();
|
||||
private IBackendHelper backendHelper;
|
||||
private string workingFolder;
|
||||
|
||||
/// <summary>
|
||||
/// Called before database binding occurs.
|
||||
/// Ensures the first draft of the SWID Tag exists on disk when being included
|
||||
/// in MSI packages.
|
||||
/// </summary>
|
||||
public override void Initialize(Output output)
|
||||
/// <param name="context">Resolve context.</param>
|
||||
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<IBackendHelper>();
|
||||
|
||||
// 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
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unused.
|
||||
/// </summary>
|
||||
public IResolveFileResult ResolveFile(string source, IntermediateSymbolDefinition symbolDefinition, SourceLineNumber sourceLineNumbers, BindStage bindStage) => null;
|
||||
|
||||
/// <summary>
|
||||
/// Unused.
|
||||
/// </summary>
|
||||
public void PostResolve(IResolveResult result) { }
|
||||
|
||||
/// <summary>
|
||||
/// Called before database binding occurs.
|
||||
/// </summary>
|
||||
public override void PreBackendBind(IBindContext context)
|
||||
{
|
||||
base.PreBackendBind(context);
|
||||
|
||||
this.backendHelper = base.BackendHelper;
|
||||
|
||||
this.workingFolder = CalculateWorkingFolder(context.IntermediateFolder);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called after database variable resolution occurs.
|
||||
/// </summary>
|
||||
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<WixFileRow> 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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called after all output changes occur and right before the output is bound into its final format.
|
||||
/// </summary>
|
||||
public override void Finish(Output output)
|
||||
private IEnumerable<SoftwareIdentificationTagSymbol> CreateProductTagFiles(IntermediateSection section)
|
||||
{
|
||||
// Only finish bundles.
|
||||
if (OutputType.Bundle != output.Type)
|
||||
var productTags = section.Symbols.OfType<WixProductTagSymbol>().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<PropertySymbol>())
|
||||
{
|
||||
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<SoftwareTag> 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<SoftwareTag> 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<WixFileRow> CreateProductTagFiles(Output output)
|
||||
{
|
||||
List<WixFileRow> updatedFileRows = new List<WixFileRow>();
|
||||
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<FileSymbol>().ToList();
|
||||
var swidSymbols = new Dictionary<string, SoftwareIdentificationTagSymbol>();
|
||||
|
||||
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<SoftwareTag> CollectPackageTags(Output bundle)
|
||||
{
|
||||
List<SoftwareTag> tags = new List<SoftwareTag>();
|
||||
Table packageTable = bundle.Tables["WixBundlePackage"];
|
||||
if (null != packageTable)
|
||||
{
|
||||
Table payloadTable = bundle.Tables["WixBundlePayload"];
|
||||
RowDictionary<WixBundlePayloadRow> payloads = new RowDictionary<WixBundlePayloadRow>(payloadTable);
|
||||
|
||||
foreach (WixBundlePackageRow row in packageTable.RowsAs<WixBundlePackageRow>())
|
||||
{
|
||||
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<SoftwareTag> CalculateContainedTagsAndType(IEnumerable<SoftwareTag> allTags, ref TagType type)
|
||||
{
|
||||
List<SoftwareTag> containedTags = new List<SoftwareTag>();
|
||||
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<SoftwareTag>();
|
||||
}
|
||||
|
||||
// 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<SoftwareTag> 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(); // </software_id>
|
||||
}
|
||||
writer.WriteEndElement(); // </complex_of>
|
||||
}
|
||||
|
||||
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(); // </tag_type>
|
||||
writer.WriteEndElement(); // </extended_information>
|
||||
}
|
||||
|
||||
writer.WriteEndElement(); // </software_identification_tag>
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
}
|
|
@ -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<SoftwareTag> 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(); // </software_id>
|
||||
}
|
||||
writer.WriteEndElement(); // </complex_of>
|
||||
}
|
||||
|
||||
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(); // </tag_type>
|
||||
writer.WriteEndElement(); // </extended_information>
|
||||
}
|
||||
|
||||
writer.WriteEndElement(); // </software_identification_tag>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче