Skip to content

Commit

Permalink
Add Files file harvesting.
Browse files Browse the repository at this point in the history
Implements wixtoolset/issues#7857.

Like [naked files](wixtoolset/issues#7696),
`Files` elements can appear where `Component` elements do in WiX v4. The
optimizer enumerates files and directories, generating single-file
components as it goes. MSBuild-like wildcards (including `**`) are
supported. `Excludes` child elements lets you exclude files that would
otherwise be captured by wildcards.
  • Loading branch information
barnson committed Feb 26, 2024
1 parent 4f1209d commit 11e7b98
Show file tree
Hide file tree
Showing 40 changed files with 1,146 additions and 9 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 @@ -368,6 +368,11 @@ public static Message ExpectedAttribute(SourceLineNumber sourceLineNumbers, stri
return Message(sourceLineNumbers, Ids.ExpectedAttribute, "The {0}/@{1} attribute was not found; it is required unless the attribute {2} has a value of '{3}'.", elementName, attributeName, otherAttributeName, otherAttributeValue, otherAttributeValueUnless);
}

public static Message ExpectedAttributeInElementOrParent(SourceLineNumber sourceLineNumbers, string elementName, string attributeName)
{
return Message(sourceLineNumbers, Ids.ExpectedAttributeInElementOrParent, "The {0}/@{1} attribute was not found or empty; it is required unless it is specified in the parent element.", elementName, attributeName);
}

public static Message ExpectedAttributeInElementOrParent(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string parentElementName)
{
return Message(sourceLineNumbers, Ids.ExpectedAttributeInElementOrParent, "The {0}/@{1} attribute was not found or empty; it is required, or it can be specified in the parent {2} element.", elementName, attributeName, parentElementName);
Expand Down
84 changes: 84 additions & 0 deletions src/api/wix/WixToolset.Data/Symbols/HarvestFilesSymbol.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// 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.Data
{
using WixToolset.Data.Symbols;

public static partial class SymbolDefinitions
{
public static readonly IntermediateSymbolDefinition HarvestFiles = new IntermediateSymbolDefinition(
SymbolDefinitionType.HarvestFiles,
new[]
{
new IntermediateFieldDefinition(nameof(HarvestFilesSymbolFields.DirectoryRef), IntermediateFieldType.String),
new IntermediateFieldDefinition(nameof(HarvestFilesSymbolFields.Inclusions), IntermediateFieldType.String),
new IntermediateFieldDefinition(nameof(HarvestFilesSymbolFields.Exclusions), IntermediateFieldType.String),
new IntermediateFieldDefinition(nameof(HarvestFilesSymbolFields.ComplexReferenceParentType), IntermediateFieldType.String),
new IntermediateFieldDefinition(nameof(HarvestFilesSymbolFields.ParentId), IntermediateFieldType.String),
new IntermediateFieldDefinition(nameof(HarvestFilesSymbolFields.SourcePath), IntermediateFieldType.String),
},
typeof(HarvestFilesSymbol));
}
}

namespace WixToolset.Data.Symbols
{
public enum HarvestFilesSymbolFields
{
DirectoryRef,
Inclusions,
Exclusions,
ComplexReferenceParentType,
ParentId,
SourcePath,
}

public class HarvestFilesSymbol : IntermediateSymbol
{
public HarvestFilesSymbol() : base(SymbolDefinitions.HarvestFiles, null, null)
{
}

public HarvestFilesSymbol(SourceLineNumber sourceLineNumber, Identifier id = null) : base(SymbolDefinitions.HarvestFiles, sourceLineNumber, id)
{
}

public IntermediateField this[HarvestFilesSymbolFields index] => this.Fields[(int)index];

public string DirectoryRef
{
get => (string)this.Fields[(int)HarvestFilesSymbolFields.DirectoryRef];
set => this.Set((int)HarvestFilesSymbolFields.DirectoryRef, value);
}

public string Inclusions
{
get => (string)this.Fields[(int)HarvestFilesSymbolFields.Inclusions];
set => this.Set((int)HarvestFilesSymbolFields.Inclusions, value);
}

public string Exclusions
{
get => (string)this.Fields[(int)HarvestFilesSymbolFields.Exclusions];
set => this.Set((int)HarvestFilesSymbolFields.Exclusions, value);
}

public string ComplexReferenceParentType
{
get => (string)this.Fields[(int)HarvestFilesSymbolFields.ComplexReferenceParentType];
set => this.Set((int)HarvestFilesSymbolFields.ComplexReferenceParentType, value);
}

public string ParentId
{
get => (string)this.Fields[(int)HarvestFilesSymbolFields.ParentId];
set => this.Set((int)HarvestFilesSymbolFields.ParentId, value);
}

public string SourcePath
{
get => (string)this.Fields[(int)HarvestFilesSymbolFields.SourcePath];
set => this.Set((int)HarvestFilesSymbolFields.SourcePath, value);
}
}
}
1 change: 1 addition & 0 deletions src/api/wix/WixToolset.Data/Symbols/SymbolDefinitions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public enum SymbolDefinitionType
FeatureComponents,
File,
FileSFPCatalog,
HarvestFiles,
Icon,
ImageFamilies,
IniFile,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace WixToolset.Extensibility.Services
using WixToolset.Extensibility.Data;

/// <summary>
/// Interface provided to help compiler extensions parse.
/// Interface provided to help compiler and optimizer extensions parse.
/// </summary>
public interface IParseHelper
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@ private void AddSectionToData()
break;

// Symbols used internally and are not added to the output.
case SymbolDefinitionType.HarvestFiles:
case SymbolDefinitionType.WixBuildInfo:
case SymbolDefinitionType.WixBindUpdatedFiles:
case SymbolDefinitionType.WixComponentGroup:
Expand Down
8 changes: 2 additions & 6 deletions src/wix/WixToolset.Core/CommandLine/BuildCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -125,12 +125,8 @@ public override Task<int> ExecuteAsync(CancellationToken cancellationToken)
{
using (new IntermediateFieldContext("wix.link"))
{
var wixipl = inputsOutputs.Wixipls.SingleOrDefault();

if (wixipl == null)
{
wixipl = this.LinkPhase(wixobjs, inputsOutputs, creator, cancellationToken);
}
var wixipl = inputsOutputs.Wixipls.SingleOrDefault()
?? this.LinkPhase(wixobjs, inputsOutputs, creator, cancellationToken);

if (!this.Messaging.EncounteredError)
{
Expand Down
149 changes: 148 additions & 1 deletion src/wix/WixToolset.Core/Compiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2655,6 +2655,9 @@ private void ParseComponentGroupElement(XElement node, ComplexReferenceParentTyp
case "File":
this.ParseNakedFileElement(child, ComplexReferenceParentType.ComponentGroup, id.Id, directoryId, source);
break;
case "Files":
this.ParseFilesElement(child, ComplexReferenceParentType.ComponentGroup, id.Id, directoryId, source);
break;
default:
this.Core.UnexpectedElement(node, child);
break;
Expand Down Expand Up @@ -3106,7 +3109,7 @@ private void ParseCopyFileElement(XElement node, string componentId, string file
this.Core.AddSymbol(new MoveFileSymbol(sourceLineNumbers, id)
{
ComponentRef = componentId,
SourceName = sourceName,
SourceName = sourceName,
DestinationName = destinationName,
DestinationShortName = destinationShortName,
SourceFolder = sourceDirectory ?? sourceProperty,
Expand Down Expand Up @@ -3881,6 +3884,9 @@ private void ParseDirectoryElement(XElement node, string parentId, int diskId, s
case "File":
this.ParseNakedFileElement(child, ComplexReferenceParentType.Unknown, null, id.Id, fileSource);
break;
case "Files":
this.ParseFilesElement(child, ComplexReferenceParentType.Unknown, null, id.Id, fileSource);
break;
case "Merge":
this.ParseMergeElement(child, id.Id, diskId);
break;
Expand Down Expand Up @@ -3996,6 +4002,9 @@ private void ParseDirectoryRefElement(XElement node)
case "File":
this.ParseNakedFileElement(child, ComplexReferenceParentType.Unknown, null, id, fileSource);
break;
case "Files":
this.ParseFilesElement(child, ComplexReferenceParentType.Unknown, null, id, fileSource);
break;
case "Merge":
this.ParseMergeElement(child, id, diskId);
break;
Expand Down Expand Up @@ -4436,6 +4445,9 @@ private void ParseFeatureElement(XElement node, ComplexReferenceParentType paren
case "File":
this.ParseNakedFileElement(child, ComplexReferenceParentType.Feature, id.Id, null, null);
break;
case "Files":
this.ParseFilesElement(child, ComplexReferenceParentType.Feature, id.Id, null, null);
break;
case "Level":
this.ParseLevelElement(child, id.Id);
break;
Expand Down Expand Up @@ -4579,6 +4591,9 @@ private void ParseFeatureRefElement(XElement node, ComplexReferenceParentType pa
case "File":
this.ParseNakedFileElement(child, ComplexReferenceParentType.Feature, id, null, null);
break;
case "Files":
this.ParseFilesElement(child, ComplexReferenceParentType.Feature, id, null, null);
break;
case "MergeRef":
this.ParseMergeRefElement(child, ComplexReferenceParentType.Feature, id);
break;
Expand Down Expand Up @@ -4667,6 +4682,9 @@ private void ParseFeatureGroupElement(XElement node, ComplexReferenceParentType
case "File":
this.ParseNakedFileElement(child, ComplexReferenceParentType.FeatureGroup, id.Id, null, null);
break;
case "Files":
this.ParseFilesElement(child, ComplexReferenceParentType.Feature, id.Id, null, null);
break;
case "MergeRef":
this.ParseMergeRefElement(child, ComplexReferenceParentType.FeatureGroup, id.Id);
break;
Expand Down Expand Up @@ -5677,6 +5695,129 @@ private void ParseNakedFileElement(XElement node, ComplexReferenceParentType par
}
}

/// <summary>
/// Parses a `Files` element.
/// </summary>
/// <param name="node">Files element to parse.</param>
/// <param name="parentType">Type of complex reference parent. Will be Unknown if there is no parent.</param>
/// <param name="parentId">Optional identifier for primary parent.</param>
/// <param name="directoryId">Ancestor's directory id.</param>
/// <param name="sourcePath">Default source path of parent directory.</param>
private void ParseFilesElement(XElement node, ComplexReferenceParentType parentType, string parentId, string directoryId, string sourcePath)
{
var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
var win64 = this.Context.IsCurrentPlatform64Bit;
string subdirectory = null;
var inclusions = new List<string>();
var exclusions = new List<string>();

foreach (var attrib in node.Attributes())
{
if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
{
switch (attrib.Name.LocalName)
{
case "Directory":
directoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, directoryId);
break;
case "Subdirectory":
subdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true);
break;
case "Include":
inclusions.AddRange(this.Core.GetAttributeValue(sourceLineNumbers, attrib).Split(';'));
break;
default:
this.Core.UnexpectedAttribute(node, attrib);
break;
}
}
else
{
var context = new Dictionary<string, string>() { { "Win64", win64.ToString() } };
this.Core.ParseExtensionAttribute(node, attrib, context);
}
}

foreach (var child in node.Elements())
{
if (CompilerCore.WixNamespace == child.Name.Namespace)
{
switch (child.Name.LocalName)
{
case "Exclude":
this.ParseFilesExcludeElement(child, exclusions);
break;
default:
this.Core.UnexpectedElement(node, child);
break;
}
}
else
{
var context = new Dictionary<string, string>() { { "Win64", win64.ToString() } };
this.Core.ParseExtensionElement(node, child, context);
}
}

if (String.IsNullOrEmpty(directoryId))
{
directoryId = "INSTALLFOLDER";
this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, directoryId);
}
else if (!String.IsNullOrEmpty(subdirectory))
{
directoryId = this.HandleSubdirectory(sourceLineNumbers, node, directoryId, subdirectory, "Directory", "Subdirectory");
}

if (!inclusions.Any())
{
this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Include"));
}

var inclusionsAsString = String.Join(";", inclusions);
var exclusionsAsString = String.Join(";", exclusions);

var id = this.Core.CreateIdentifier("hvf", directoryId, inclusionsAsString, exclusionsAsString);

this.Core.AddSymbol(new HarvestFilesSymbol(sourceLineNumbers, id)
{
DirectoryRef = directoryId,
Inclusions = inclusionsAsString,
Exclusions = exclusionsAsString,
ComplexReferenceParentType = parentType.ToString(),
ParentId = parentId,
SourcePath = sourcePath,
});
}

private void ParseFilesExcludeElement(XElement node, IList<string> paths)
{
var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);

foreach (var attrib in node.Attributes())
{
if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
{
switch (attrib.Name.LocalName)
{
case "Files":
paths.Add(this.Core.GetAttributeValue(sourceLineNumbers, attrib));
break;
default:
this.Core.UnexpectedAttribute(node, attrib);
break;
}
}
else
{
this.Core.ParseExtensionAttribute(node, attrib);
}
}

this.Core.ParseForExtensionElements(node);
}

/// <summary>
/// Parses a file search element.
/// </summary>
Expand Down Expand Up @@ -5997,6 +6138,9 @@ private void ParseFragmentElement(XElement node)
case "File":
this.ParseNakedFileElement(child, ComplexReferenceParentType.Unknown, null, null, null);
break;
case "Files":
this.ParseFilesElement(child, ComplexReferenceParentType.Unknown, null, null, null);
break;
case "Icon":
this.ParseIconElement(child);
break;
Expand Down Expand Up @@ -7347,6 +7491,9 @@ private void ParseStandardDirectoryElement(XElement node)
case "File":
this.ParseNakedFileElement(child, ComplexReferenceParentType.Unknown, null, id, null);
break;
case "Files":
this.ParseFilesElement(child, ComplexReferenceParentType.Unknown, null, id, null);
break;
case "Merge":
this.ParseMergeElement(child, id, diskId: CompilerConstants.IntegerNotSet);
break;
Expand Down
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 @@ -178,6 +178,9 @@ private void ParseModuleElement(XElement node)
case "File":
this.ParseNakedFileElement(child, ComplexReferenceParentType.Module, this.activeName, null, null);
break;
case "Files":
this.ParseFilesElement(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 @@ -306,6 +306,9 @@ private void ParsePackageElement(XElement node)
case "File":
this.ParseNakedFileElement(child, ComplexReferenceParentType.Product, productCode, null, null);
break;
case "Files":
this.ParseFilesElement(child, ComplexReferenceParentType.Unknown, null, null, null);
break;
case "Icon":
this.ParseIconElement(child);
break;
Expand Down
Loading

0 comments on commit 11e7b98

Please sign in to comment.