Skip to content

Commit

Permalink
Support naked files.
Browse files Browse the repository at this point in the history
Implements wixtoolset/issues#7696.

`File` elements can appear where `Component` elements do in WiX v4. The
compiler generates an appropriate per-file component. Naked files under
`Directory`, `DirectoryRef`, `Fragment`, `StandardDirectory`, or
`Package` elements are included in a package via the [default-feature
feature](wixtoolset/issues#7581). Naked files
appearing under `ComponentGroup`, `Feature`, `FeatureRef`, and
`FeatureGroup` generate the component and the reference to the parent
element.

Components and naked Files default to being installed to INSTALLFOLDER
(including a default INSTALLFOLDER if one isn't otherwise authored).
  • Loading branch information
barnson committed Jan 8, 2024
1 parent 44a13ff commit f676cee
Show file tree
Hide file tree
Showing 27 changed files with 751 additions and 120 deletions.
5 changes: 5 additions & 0 deletions src/api/wix/WixToolset.Data/ErrorMessages.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2256,6 +2256,11 @@ public static Message IllegalInnerText(SourceLineNumber sourceLineNumbers, strin
return Message(sourceLineNumbers, Ids.IllegalInnerText, "The {0} element contains inner text which is obsolete. Use the {1} attribute instead.", elementName, attributeName);
}

public static Message IllegalAttributeWhenNested(SourceLineNumber sourceLineNumbers, string attributeName)
{
return Message(sourceLineNumbers, Ids.IllegalAttributeWhenNested, "The File element contains an attribute '{0}' that cannot be used in a File element that is a child of a Component element.", attributeName);
}

private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args)
{
return new Message(sourceLineNumber, MessageLevel.Error, (int)id, format, args);
Expand Down
354 changes: 276 additions & 78 deletions src/wix/WixToolset.Core/Compiler.cs

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions src/wix/WixToolset.Core/Compiler_Module.cs
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,9 @@ private void ParseModuleElement(XElement node)
case "Exclusion":
this.ParseExclusionElement(child);
break;
case "File":
this.ParseNakedFileElement(child, ComplexReferenceParentType.Module, this.activeName, null, null);
break;
case "Icon":
this.ParseIconElement(child);
break;
Expand Down
3 changes: 3 additions & 0 deletions src/wix/WixToolset.Core/Compiler_Package.cs
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,9 @@ private void ParsePackageElement(XElement node)
case "FeatureGroupRef":
this.ParseFeatureGroupRefElement(child, ComplexReferenceParentType.Product, productCode);
break;
case "File":
this.ParseNakedFileElement(child, ComplexReferenceParentType.Product, productCode, null, null);
break;
case "Icon":
this.ParseIconElement(child);
break;
Expand Down
4 changes: 4 additions & 0 deletions src/wix/WixToolset.Core/Link/AddDefaultSymbolsCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public void Execute()
return;
}

// If a directory with id INSTALLFOLDER hasn't been authored, provide a default one.
if (!this.Find.SymbolsByName.ContainsKey(WixStandardInstallFolderReference))
{
var sourceLineNumber = new SourceLineNumber("DefaultInstallFolder");
Expand All @@ -51,6 +52,9 @@ public void Execute()
);
}

// If an upgrade hasn't been authored and the upgrade strategy is MajorUpgrade,
// conjure a default major upgrade with the stdlib localization string for the
// downgrade error message.
var symbols = this.Sections.SelectMany(section => section.Symbols);
var upgradeSymbols = symbols.OfType<UpgradeSymbol>();
if (!upgradeSymbols.Any())
Expand Down
2 changes: 2 additions & 0 deletions src/wix/WixToolset.Core/Link/SymbolWithSection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ namespace WixToolset.Core.Link
{
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using WixToolset.Data;
using WixToolset.Data.Symbols;

/// <summary>
/// Symbol with section representing a single unique symbol.
/// </summary>
[DebuggerDisplay("{Symbol.DebuggerDisplay}")]
internal class SymbolWithSection
{
private List<WixSimpleReferenceSymbol> directReferences;
Expand Down
29 changes: 0 additions & 29 deletions src/wix/test/WixToolsetTest.CoreIntegration/ComponentFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,34 +41,5 @@ public void CanDetectDuplicateComponentGuids()
}, errors.Select(e => e.Id).ToArray());
}
}

[Fact]
public void CannotBuildMissingDirectoryAttributeWithSubdirectory()
{
var folder = TestData.Get(@"TestData");

using (var fs = new DisposableFileSystem())
{
var baseFolder = fs.GetFolder();
var intermediateFolder = Path.Combine(baseFolder, "obj");
var msiPath = Path.Combine(baseFolder, "bin", "test.msi");

var result = WixRunner.Execute(new[]
{
"build",
Path.Combine(folder, "Component", "MissingDirectoryWithSubdirectory.wxs"),
Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"),
"-bindpath", Path.Combine(folder, "SingleFile", "data"),
"-intermediateFolder", intermediateFolder,
"-o", msiPath
});

var errors = result.Messages.Select(m => m.ToString()).ToArray();
WixAssert.CompareLineByLine(new[]
{
"The Component/@Directory attribute was not found; it is required."
}, errors);
}
}
}
}
247 changes: 247 additions & 0 deletions src/wix/test/WixToolsetTest.CoreIntegration/NakedFileFixture.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.

namespace WixToolsetTest.CoreIntegration
{
using System.Data;
using System.IO;
using System.Linq;
using WixInternal.Core.TestPackage;
using WixInternal.TestSupport;
using WixToolset.Data.WindowsInstaller;
using Xunit;

public class NakedFileFixture
{
[Fact]
public void CanBuildNakedFilesInComponentGroup()
{
var rows = BuildAndQueryComponentAndFileTables("ComponentGroup.wxs");

AssertFileComponentIds(2, rows);
}

[Fact]
public void CanBuildNakedFilesInFeature()
{
var rows = BuildAndQueryComponentAndFileTables("Feature.wxs");

AssertFileComponentIds(2, rows);
}

[Fact]
public void CanBuildNakedFilesInDirectory()
{
var rows = BuildAndQueryComponentAndFileTables("Directory.wxs");

AssertFileComponentIds(2, rows);
}

[Fact]
public void CanBuildNakedFilesInDirectoryRef()
{
var rows = BuildAndQueryComponentAndFileTables("DirectoryRef.wxs");

AssertFileComponentIds(2, rows);
}

[Fact]
public void CanBuildNakedFilesInFeatureRef()
{
var rows = BuildAndQueryComponentAndFileTables("FeatureRef.wxs");

AssertFileComponentIds(2, rows);
}

[Fact]
public void CanBuildNakedFilesInFeatureGroup()
{
var rows = BuildAndQueryComponentAndFileTables("FeatureGroup.wxs");

AssertFileComponentIds(2, rows);
}

[Fact]
public void CanBuildNakedFilesInFragments()
{
var rows = BuildAndQueryComponentAndFileTables("Fragment.wxs");

AssertFileComponentIds(2, rows);
}

[Fact]
public void CanBuildNakedFilesInStandardDirectory()
{
var rows = BuildAndQueryComponentAndFileTables("StandardDirectory.wxs");

AssertFileComponentIds(2, rows);
}

[Fact]
public void CanBuildNakedFilesInModule()
{
var rows = BuildAndQueryComponentAndFileTables("Module.wxs", isPackage: false);

AssertFileComponentIds(2, rows);
}

[Fact]
public void CanBuildNakedFilesWithConditions()
{
var rows = BuildAndQueryComponentAndFileTables("Condition.wxs");
var componentRows = rows.Where(row => row.StartsWith("Component:")).ToArray();

// Coincidentally, the files' ids are the same as the component conditions.
foreach (var componentRow in componentRows)
{
var columns = componentRow.Split(':', '\t');
Assert.Equal(columns[1], columns[5]);
}
}

[Fact]
public void CanBuildNakedFilesUnderPackage()
{
var rows = BuildAndQueryComponentAndFileTables("Package.wxs");
AssertFileComponentIds(4, rows);
}

[Fact]
public void CanBuildNakedFilesUnderPackageWithDefaultInstallFolder()
{
var rows = BuildAndQueryComponentAndFileTables("PackageWithDefaultInstallFolder.wxs");
AssertFileComponentIds(4, rows);
}

[Fact]
public void NakedFilesUnderPackageWithAuthoredFeatureAreOrphaned()
{
var messages = BuildAndQueryComponentAndFileTables("PackageWithoutDefaultFeature.wxs", isPackage: true, 267);
Assert.Equal(new[]
{
"267",
"267",
}, messages);
}

[Fact]
public void IllegalAttributesWhenNonNakedFailTheBuild()
{
var messages = BuildAndQueryComponentAndFileTables("BadAttributes.wxs", isPackage: true, 62);
Assert.Equal(new[]
{
"62",
"62",
"62",
"62",
}, messages);
}

[Fact]
public void CanBuildNakedFileFromWixlibComponentGroup()
{
var rows = BuildPackageWithWixlib("WixlibComponentGroup.wxs", "WixlibComponentGroupPackage.wxs");

AssertFileComponentIds(2, rows);
}

private static string[] BuildPackageWithWixlib(string wixlibSourcePath, string msiSourcePath)
{
var folder = TestData.Get("TestData", "NakedFile");

using (var fs = new DisposableFileSystem())
{
var baseFolder = fs.GetFolder();
var intermediateFolder = Path.Combine(baseFolder, "obj");
var binFolder = Path.Combine(baseFolder, "bin");
var wixlibPath = Path.Combine(binFolder, Path.ChangeExtension(wixlibSourcePath, ".wixlib"));

var result = WixRunner.Execute(new[]
{
"build",
Path.Combine(folder, wixlibSourcePath),
"-intermediateFolder", intermediateFolder,
"-bindpath", folder,
"-o", wixlibPath,
});

result.AssertSuccess();

var msiPath = Path.Combine(binFolder, "test.msi");

result = WixRunner.Execute(new[]
{
"build",
Path.Combine(folder, msiSourcePath),
wixlibPath,
"-intermediateFolder", intermediateFolder,
"-bindpath", folder,
"-o", msiPath,
});
result.AssertSuccess();

return Query.QueryDatabase(msiPath, new[] { "Component", "File" })
.OrderBy(s => s)
.ToArray();
}
}

private static string[] BuildAndQueryComponentAndFileTables(string file, bool isPackage = true, int? exitCode = null)
{
var folder = TestData.Get("TestData", "NakedFile");

using (var fs = new DisposableFileSystem())
{
var baseFolder = fs.GetFolder();
var intermediateFolder = Path.Combine(baseFolder, "obj");
var binFolder = Path.Combine(baseFolder, "bin");
var msiPath = Path.Combine(binFolder, isPackage ? "test.msi" : "test.msm");

var result = WixRunner.Execute(new[]
{
"build",
Path.Combine(folder, file),
"-intermediateFolder", intermediateFolder,
"-bindpath", folder,
"-o", msiPath,
});

if (exitCode.HasValue)
{
Assert.Equal(exitCode.Value, result.ExitCode);

return result.Messages.Select(m => m.Id.ToString()).ToArray();
}
else
{
result.AssertSuccess();

return Query.QueryDatabase(msiPath, new[] { "Component", "File" })
.OrderBy(s => s)
.ToArray();
}
}
}

private static void AssertFileComponentIds(int fileCount, string[] rows)
{
var componentRows = rows.Where(row => row.StartsWith("Component:")).ToArray();
var fileRows = rows.Where(row => row.StartsWith("File:")).ToArray();

Assert.Equal(fileCount, componentRows.Length);
Assert.Equal(componentRows.Length, fileRows.Length);

// Component id == Component keypath == File id
foreach (var componentRow in componentRows)
{
var columns = componentRow.Split(':', '\t');
Assert.Equal(columns[1], columns[6]);
}

foreach (var fileRow in fileRows)
{
var columns = fileRow.Split(':', '\t');
Assert.Equal(columns[1], columns[2]);
}
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
<Package Name="MsiPackage" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a">
<MajorUpgrade DowngradeErrorMessage="Downgrade error message." />

<Feature Id="ProductFeature">
<Component Directory="ProgramFilesFolder" Subdirectory="MsiPackage">
<File Source="test.txt" Bitness="always64" Condition="BLAHBLAHBLAH" Directory="ProgramFilesFolder" Subdirectory="MsiPackage" />
</Component>
</Feature>
</Package>
</Wix>
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
<Package Name="MsiPackage" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a">
<MajorUpgrade DowngradeErrorMessage="Downgrade error message." />

<Feature Id="ProductFeature">
<ComponentGroupRef Id="Files" />
</Feature>

<ComponentGroup Id="Files" Directory="ProgramFilesFolder" Subdirectory="MsiPackage">
<File Source="test.txt" />
<File Source="test.txt" Name="test2.txt" />
</ComponentGroup>
</Package>
</Wix>
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
<Package Name="MsiPackage" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a">
<MajorUpgrade DowngradeErrorMessage="Downgrade error message." />

<Feature Id="ProductFeature">
<File Id="FILE1" Condition="FILE1" Directory="ProgramFilesFolder" Subdirectory="MsiPackage" Source="test.txt" />
<File Id="FILE2" Condition="FILE2" Directory="ProgramFilesFolder" Subdirectory="MsiPackage" Source="test.txt" Name="test2.txt" />
</Feature>
</Package>
</Wix>
Loading

0 comments on commit f676cee

Please sign in to comment.