Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Naked files and default feature #458

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -2271,6 +2271,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
69 changes: 69 additions & 0 deletions src/wix/WixToolset.Core/AssignDefaultFeatureCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// 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.Core
{
using System.Collections.Generic;
using System.Linq;
using WixToolset.Core.Link;
using WixToolset.Data;
using WixToolset.Data.Symbols;
using WixToolset.Extensibility.Services;

internal class AssignDefaultFeatureCommand
{
private const string DefaultFeatureName = "WixDefaultFeature";

public AssignDefaultFeatureCommand(IMessaging messaging, IntermediateSection entrySection, IEnumerable<IntermediateSection> sections, HashSet<string> referencedComponents, Link.ConnectToFeatureCollection componentsToFeatures)
{
this.Messaging = messaging;
this.EntrySection = entrySection;
this.Sections = sections;
this.ReferencedComponents = referencedComponents;
this.ComponentsToFeatures = componentsToFeatures;
}

public IMessaging Messaging { get; }

public IntermediateSection EntrySection { get; }

public IEnumerable<IntermediateSection> Sections { get; }

public HashSet<string> ReferencedComponents { get; }

public ConnectToFeatureCollection ComponentsToFeatures { get; }

public void Execute()
{
var assignedComponents = false;

foreach (var section in this.Sections)
{
foreach (var component in section.Symbols.OfType<ComponentSymbol>().ToList())
{
if (!this.ReferencedComponents.Contains(component.Id.Id))
{
assignedComponents = true;

this.ComponentsToFeatures.Add(new ConnectToFeature(section, component.Id.Id, DefaultFeatureName, explicitPrimaryFeature: true));

section.AddSymbol(new FeatureComponentsSymbol
{
FeatureRef = DefaultFeatureName,
ComponentRef = component.Id.Id,
});
}
}
}

if (assignedComponents)
{
this.EntrySection.AddSymbol(new FeatureSymbol(null, new Identifier(AccessModifier.Global, DefaultFeatureName))
{
Level = 1,
Display = 0,
InstallDefault = FeatureInstallDefault.Local,
});
}
}
}
}
346 changes: 268 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
9 changes: 9 additions & 0 deletions src/wix/WixToolset.Core/Compiler_Package.cs
Original file line number Diff line number Diff line change
Expand Up @@ -242,9 +242,15 @@ private void ParsePackageElement(XElement node)
case "Component":
this.ParseComponentElement(child, ComplexReferenceParentType.Unknown, null, null, CompilerConstants.IntegerNotSet, null, null);
break;
case "ComponentRef":
this.ParseComponentRefElement(child, ComplexReferenceParentType.Product, null, null);
break;
case "ComponentGroup":
this.ParseComponentGroupElement(child, ComplexReferenceParentType.Unknown, null);
break;
case "ComponentGroupRef":
this.ParseComponentGroupRefElement(child, ComplexReferenceParentType.Product, null, null);
break;
case "CustomAction":
this.ParseCustomActionElement(child);
break;
Expand Down Expand Up @@ -281,6 +287,9 @@ private void ParsePackageElement(XElement node)
case "FeatureGroupRef":
this.ParseFeatureGroupRefElement(child, ComplexReferenceParentType.Product, productCode);
break;
case "File":
this.ParseNakedFileElement(child, ComplexReferenceParentType.Unknown, null, null, null);
break;
case "Icon":
this.ParseIconElement(child);
break;
Expand Down
21 changes: 17 additions & 4 deletions src/wix/WixToolset.Core/Linker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -150,14 +150,23 @@ public Intermediate Link(ILinkContext context)
return null;
}

// Display an error message for Components that were not referenced by a Feature.
foreach (var component in sections.SelectMany(s => s.Symbols.Where(y => y.Definition.Type == SymbolDefinitionType.Component)))
// If there are authored features, error for any referenced components that aren't assigned to a feature.
// If not, create a default feature and assign the components to it.
if (sections.SelectMany(s => s.Symbols).OfType<FeatureSymbol>().Any())
{
if (!referencedComponents.Contains(component.Id.Id))
foreach (var component in sections.SelectMany(s => s.Symbols.Where(y => y.Definition.Type == SymbolDefinitionType.Component)))
{
this.Messaging.Write(ErrorMessages.OrphanedComponent(component.SourceLineNumbers, component.Id.Id));
if (!referencedComponents.Contains(component.Id.Id))
{
this.Messaging.Write(ErrorMessages.OrphanedComponent(component.SourceLineNumbers, component.Id.Id));
}
}
}
else if (find.EntrySection.Type == SectionType.Package)
{
var command = new AssignDefaultFeatureCommand(this.Messaging, find.EntrySection, sections, referencedComponents, componentsToFeatures);
command.Execute();
}

// Report duplicates that would ultimately end up being primary key collisions.
{
Expand Down Expand Up @@ -532,6 +541,10 @@ private void ProcessComplexReferences(IntermediateSection resolvedSection, IEnum
featuresToFeatures.Add(new ConnectToFeature(section, wixComplexReferenceRow.Child, null, wixComplexReferenceRow.IsPrimary));
break;

case ComplexReferenceChildType.Component:
case ComplexReferenceChildType.ComponentGroup:
break;

default:
throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, "Unexpected complex reference child type: {0}", Enum.GetName(typeof(ComplexReferenceChildType), wixComplexReferenceRow.ChildType)));
}
Expand Down
71 changes: 71 additions & 0 deletions src/wix/test/WixToolsetTest.CoreIntegration/FeatureFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,77 @@ public void CanDetectMissingFeatureComponentMapping()
}
}

[Fact]
public void CanAutomaticallyCreateDefaultFeature()
{
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, "Feature", "PackageDefaultFeature.wxs"),
"-bindpath", Path.Combine(folder, "SingleFile", "data"),
"-intermediateFolder", intermediateFolder,
"-o", msiPath
});

Assert.Empty(result.Messages);

Assert.True(File.Exists(msiPath));
var results = Query.QueryDatabase(msiPath, new[] { "Feature", "FeatureComponents", "Shortcut" });
WixAssert.CompareLineByLine(new[]
{
"Feature:WixDefaultFeature\t\t\t\t0\t1\t\t0",
"FeatureComponents:WixDefaultFeature\tAnotherComponentInAFragment",
"FeatureComponents:WixDefaultFeature\tComponentInAFragment",
"FeatureComponents:WixDefaultFeature\tfil6J6CHYPBCOMYclNjnqn0afimmzM",
"FeatureComponents:WixDefaultFeature\tfilcV1yrx0x8wJWj4qMzcH21jwkPko",
"FeatureComponents:WixDefaultFeature\tfilj.cb0sFWqIPHPFSKJSEEaPDuAQ4",
"Shortcut:AdvertisedShortcut\tINSTALLFOLDER\tShortcut\tAnotherComponentInAFragment\tWixDefaultFeature\t\t\t\t\t\t\t\t\t\t\t",
}, results);
}
}

[Fact]
public void WontAutomaticallyCreateDefaultFeature()
{
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, "Feature", "PackageBadDefaultFeature.wxs"),
"-bindpath", Path.Combine(folder, "SingleFile", "data"),
"-intermediateFolder", intermediateFolder,
"-o", msiPath
});

var messages = result.Messages.Select(m => m.ToString()).ToList();
messages.Sort();

WixAssert.CompareLineByLine(new[]
{
"Found orphaned Component 'fil6J6CHYPBCOMYclNjnqn0afimmzM'. If this is a Package, every Component must have at least one parent Feature. To include a Component in a Module, you must include it directly as a Component element of the Module element or indirectly via ComponentRef, ComponentGroup, or ComponentGroupRef elements.",
"Found orphaned Component 'filcV1yrx0x8wJWj4qMzcH21jwkPko'. If this is a Package, every Component must have at least one parent Feature. To include a Component in a Module, you must include it directly as a Component element of the Module element or indirectly via ComponentRef, ComponentGroup, or ComponentGroupRef elements.",
"Found orphaned Component 'filj.cb0sFWqIPHPFSKJSEEaPDuAQ4'. If this is a Package, every Component must have at least one parent Feature. To include a Component in a Module, you must include it directly as a Component element of the Module element or indirectly via ComponentRef, ComponentGroup, or ComponentGroupRef elements.",
}, messages.ToArray());

Assert.Equal(267, result.ExitCode);
}
}

[Fact]
public void CannotBuildMsiWithTooLargeFeatureDepth()
{
Expand Down
Loading