diff --git a/src/api/wix/WixToolset.Extensibility/BaseOptimizerExtension.cs b/src/api/wix/WixToolset.Extensibility/BaseOptimizerExtension.cs
new file mode 100644
index 000000000..5bb892723
--- /dev/null
+++ b/src/api/wix/WixToolset.Extensibility/BaseOptimizerExtension.cs
@@ -0,0 +1,26 @@
+// 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.Extensibility
+{
+ using WixToolset.Extensibility.Data;
+
+ ///
+ /// Base class for creating an optimizer extension.
+ ///
+ public abstract class BaseOptimizerExtension : IOptimizerExtension
+ {
+ ///
+ /// Called after all files have been compiled, before built-in optimizations, and before all sections are linked into a single section.
+ ///
+ public virtual void PreOptimize(IOptimizeContext context)
+ {
+ }
+
+ ///
+ /// Called after all files have been compiled, after built-in optimizations, and before all sections are linked into a single section.
+ ///
+ public virtual void PostOptimize(IOptimizeContext context)
+ {
+ }
+ }
+}
diff --git a/src/api/wix/WixToolset.Extensibility/Data/ICompileContext.cs b/src/api/wix/WixToolset.Extensibility/Data/ICompileContext.cs
index 7006fde84..cfccd0a90 100644
--- a/src/api/wix/WixToolset.Extensibility/Data/ICompileContext.cs
+++ b/src/api/wix/WixToolset.Extensibility/Data/ICompileContext.cs
@@ -55,7 +55,7 @@ public interface ICompileContext
XDocument Source { get; set; }
///
- /// Cancellation token to abort cancellation.
+ /// Cancellation token.
///
CancellationToken CancellationToken { get; set; }
}
diff --git a/src/api/wix/WixToolset.Extensibility/Data/IOptimizeContext.cs b/src/api/wix/WixToolset.Extensibility/Data/IOptimizeContext.cs
new file mode 100644
index 000000000..5684ebcd2
--- /dev/null
+++ b/src/api/wix/WixToolset.Extensibility/Data/IOptimizeContext.cs
@@ -0,0 +1,61 @@
+// 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.Extensibility.Data
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Threading;
+ using WixToolset.Data;
+
+ ///
+ /// Context provided to the optimizer.
+ ///
+ public interface IOptimizeContext
+ {
+ ///
+ /// Service provider made available to the optimizer and its extensions.
+ ///
+ IServiceProvider ServiceProvider { get; }
+
+ ///
+ /// Set of extensions provided to the optimizer.
+ ///
+ IReadOnlyCollection Extensions { get; set; }
+
+ ///
+ /// Intermediate folder.
+ ///
+ string IntermediateFolder { get; set; }
+
+ ///
+ /// Collection of bindpaths used to bind files.
+ ///
+ IReadOnlyCollection BindPaths { get; set; }
+
+ ///
+ /// Bind variables used during optimization.
+ ///
+ IDictionary BindVariables { get; set; }
+
+ ///
+ /// Gets or sets the platform which the optimizer will use when defaulting 64-bit symbol properties.
+ ///
+ /// The platform which the optimizer will use when defaulting 64-bit symbol properties.
+ Platform Platform { get; set; }
+
+ ///
+ /// Collection of intermediates to optimize.
+ ///
+ IReadOnlyCollection Intermediates { get; set; }
+
+ ///
+ /// Collection of localization files to use in the optimizer.
+ ///
+ IReadOnlyCollection Localizations { get; set; }
+
+ ///
+ /// Cancellation token.
+ ///
+ CancellationToken CancellationToken { get; set; }
+ }
+}
diff --git a/src/api/wix/WixToolset.Extensibility/IOptimizerExtension.cs b/src/api/wix/WixToolset.Extensibility/IOptimizerExtension.cs
new file mode 100644
index 000000000..589e7ecc7
--- /dev/null
+++ b/src/api/wix/WixToolset.Extensibility/IOptimizerExtension.cs
@@ -0,0 +1,22 @@
+// 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.Extensibility
+{
+ using WixToolset.Extensibility.Data;
+
+ ///
+ /// Interface that all optimizer extensions implement.
+ ///
+ public interface IOptimizerExtension
+ {
+ ///
+ /// Called after all files have been compiled, before built-in optimizations, and before all sections are linked into a single section.
+ ///
+ void PreOptimize(IOptimizeContext context);
+
+ ///
+ /// Called after all files have been compiled, after built-in optimizations, and before all sections are linked into a single section.
+ ///
+ void PostOptimize(IOptimizeContext context);
+ }
+}
diff --git a/src/wix/WixToolset.Core/CommandLine/BuildCommand.cs b/src/wix/WixToolset.Core/CommandLine/BuildCommand.cs
index 68dbd768f..2ad180739 100644
--- a/src/wix/WixToolset.Core/CommandLine/BuildCommand.cs
+++ b/src/wix/WixToolset.Core/CommandLine/BuildCommand.cs
@@ -10,7 +10,6 @@ namespace WixToolset.Core.CommandLine
using System.Threading.Tasks;
using System.Xml.Linq;
using WixToolset.Data;
- using WixToolset.Data.Bind;
using WixToolset.Extensibility;
using WixToolset.Extensibility.Data;
using WixToolset.Extensibility.Services;
@@ -112,6 +111,8 @@ public override Task ExecuteAsync(CancellationToken cancellationToken)
return Task.FromResult(this.Messaging.LastErrorNumber);
}
+ this.OptimizePhase(wixobjs, wxls, this.commandLine.BindPaths, this.commandLine.BindVariables, cancellationToken);
+
if (inputsOutputs.OutputType == OutputType.Library)
{
using (new IntermediateFieldContext("wix.lib"))
@@ -207,6 +208,22 @@ private IReadOnlyList CompilePhase(IDictionary pre
return intermediates;
}
+ private void OptimizePhase(IReadOnlyCollection intermediates, IReadOnlyCollection localizations, IReadOnlyCollection bindPaths, Dictionary bindVariables, CancellationToken cancellationToken)
+ {
+ var context = this.ServiceProvider.GetService();
+ context.Extensions = this.ExtensionManager.GetServices();
+ context.IntermediateFolder = this.IntermediateFolder;
+ context.BindPaths = bindPaths;
+ context.BindVariables = bindVariables;
+ context.Platform = this.Platform;
+ context.Intermediates = intermediates;
+ context.Localizations = localizations;
+ context.CancellationToken = cancellationToken;
+
+ var optimizer = this.ServiceProvider.GetService();
+ optimizer.Optimize(context);
+ }
+
private void LibraryPhase(IReadOnlyCollection intermediates, IReadOnlyCollection localizations, IEnumerable libraryFiles, ISymbolDefinitionCreator creator, bool bindFiles, IReadOnlyCollection bindPaths, Dictionary bindVariables, string outputPath, CancellationToken cancellationToken)
{
var libraries = this.LoadLibraries(libraryFiles, creator);
diff --git a/src/wix/WixToolset.Core/IOptimizer.cs b/src/wix/WixToolset.Core/IOptimizer.cs
new file mode 100644
index 000000000..85c90b809
--- /dev/null
+++ b/src/wix/WixToolset.Core/IOptimizer.cs
@@ -0,0 +1,17 @@
+// 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 WixToolset.Extensibility.Data;
+
+ ///
+ /// Interface for built-in optimizer.
+ ///
+ public interface IOptimizer
+ {
+ ///
+ /// Called after all files have been compiled and before all sections are linked into a single section.
+ ///
+ void Optimize(IOptimizeContext context);
+ }
+}
diff --git a/src/wix/WixToolset.Core/OptimizeContext.cs b/src/wix/WixToolset.Core/OptimizeContext.cs
new file mode 100644
index 000000000..93b6c3542
--- /dev/null
+++ b/src/wix/WixToolset.Core/OptimizeContext.cs
@@ -0,0 +1,39 @@
+// 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;
+ using System.Collections.Generic;
+ using System.Threading;
+ using WixToolset.Data;
+ using WixToolset.Extensibility;
+ using WixToolset.Extensibility.Data;
+
+ internal class OptimizeContext : IOptimizeContext
+ {
+ internal OptimizeContext(IServiceProvider serviceProvider)
+ {
+ this.ServiceProvider = serviceProvider;
+ }
+
+ public IServiceProvider ServiceProvider { get; }
+
+ public IReadOnlyCollection Extensions { get; set; }
+
+ public string IntermediateFolder { get; set; }
+
+ public IReadOnlyCollection BindPaths { get; set; }
+
+ public IDictionary BindVariables { get; set; }
+
+ public Platform Platform { get; set; }
+
+ public bool IsCurrentPlatform64Bit => this.Platform == Platform.ARM64 || this.Platform == Platform.X64;
+
+ public IReadOnlyCollection Intermediates { get; set; }
+
+ public IReadOnlyCollection Localizations { get; set; }
+
+ public CancellationToken CancellationToken { get; set; }
+ }
+}
diff --git a/src/wix/WixToolset.Core/Optimizer.cs b/src/wix/WixToolset.Core/Optimizer.cs
new file mode 100644
index 000000000..5864121ee
--- /dev/null
+++ b/src/wix/WixToolset.Core/Optimizer.cs
@@ -0,0 +1,36 @@
+// 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;
+ using WixToolset.Extensibility.Data;
+ using WixToolset.Extensibility.Services;
+
+ internal class Optimizer : IOptimizer
+ {
+ internal Optimizer(IServiceProvider serviceProvider)
+ {
+ this.ServiceProvider = serviceProvider;
+ this.Messaging = this.ServiceProvider.GetService();
+ }
+
+ private IServiceProvider ServiceProvider { get; }
+
+ private IMessaging Messaging { get; }
+
+ public void Optimize(IOptimizeContext context)
+ {
+ foreach (var extension in context.Extensions)
+ {
+ extension.PreOptimize(context);
+ }
+
+ // TODO: Fill with useful optimization features.
+
+ foreach (var extension in context.Extensions)
+ {
+ extension.PostOptimize(context);
+ }
+ }
+ }
+}
diff --git a/src/wix/WixToolset.Core/WixToolsetServiceProvider.cs b/src/wix/WixToolset.Core/WixToolsetServiceProvider.cs
index 5620bcd2d..cb8dbd870 100644
--- a/src/wix/WixToolset.Core/WixToolsetServiceProvider.cs
+++ b/src/wix/WixToolset.Core/WixToolsetServiceProvider.cs
@@ -37,6 +37,7 @@ public WixToolsetServiceProvider()
this.AddService((provider, singletons) => new CommandLine.CommandLine(provider));
this.AddService((provider, singletons) => new PreprocessContext(provider));
this.AddService((provider, singletons) => new CompileContext(provider));
+ this.AddService((provider, singletons) => new OptimizeContext(provider));
this.AddService((provider, singletons) => new LibraryContext(provider));
this.AddService((provider, singletons) => new LibraryResult());
this.AddService((provider, singletons) => new LinkContext(provider));
@@ -58,6 +59,7 @@ public WixToolsetServiceProvider()
this.AddService((provider, singletons) => new Binder(provider));
this.AddService((provider, singletons) => new Compiler(provider));
+ this.AddService((provider, singletons) => new Optimizer(provider));
this.AddService((provider, singletons) => new LayoutCreator(provider));
this.AddService((provider, singletons) => new Preprocessor(provider));
this.AddService((provider, singletons) => new Librarian(provider));
diff --git a/src/wix/test/Example.Extension/ExampleExtensionFactory.cs b/src/wix/test/Example.Extension/ExampleExtensionFactory.cs
index 0b0fb8360..16eea6dcf 100644
--- a/src/wix/test/Example.Extension/ExampleExtensionFactory.cs
+++ b/src/wix/test/Example.Extension/ExampleExtensionFactory.cs
@@ -35,6 +35,10 @@ public bool TryCreateExtension(Type extensionType, out object extension)
{
extension = new ExampleCompilerExtension();
}
+ else if (extensionType == typeof(IOptimizerExtension))
+ {
+ extension = new ExampleOptimizerExtension();
+ }
else if (extensionType == typeof(IExtensionData))
{
extension = new ExampleExtensionData();
diff --git a/src/wix/test/Example.Extension/ExampleOptimizerExtension.cs b/src/wix/test/Example.Extension/ExampleOptimizerExtension.cs
new file mode 100644
index 000000000..6a2cb91e1
--- /dev/null
+++ b/src/wix/test/Example.Extension/ExampleOptimizerExtension.cs
@@ -0,0 +1,22 @@
+// 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 Example.Extension
+{
+ using System.Linq;
+ using WixToolset.Extensibility;
+ using WixToolset.Extensibility.Data;
+
+ internal class ExampleOptimizerExtension : BaseOptimizerExtension
+ {
+ public override void PostOptimize(IOptimizeContext context)
+ {
+ foreach (var intermediate in context.Intermediates)
+ {
+ foreach (var symbol in intermediate.Sections.SelectMany(s=>s.Symbols).OfType())
+ {
+ symbol.Value = $"{symbol.Value} ";
+ }
+ }
+ }
+ }
+}
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/ExtensionFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/ExtensionFixture.cs
index 710f3b8df..429226f53 100644
--- a/src/wix/test/WixToolsetTest.CoreIntegration/ExtensionFixture.cs
+++ b/src/wix/test/WixToolsetTest.CoreIntegration/ExtensionFixture.cs
@@ -24,7 +24,7 @@ public void CanBuildAndQuery()
var results = build.BuildAndQuery(Build, "Wix4Example");
WixAssert.CompareLineByLine(new[]
{
- "Wix4Example:Foo\tfilF5_pLhBuF5b4N9XEo52g_hUM5Lo\tBar"
+ "Wix4Example:Foo\tfilF5_pLhBuF5b4N9XEo52g_hUM5Lo\tBar "
}, results);
}
@@ -92,7 +92,7 @@ public void CanBuildWithExampleExtension()
var example = section.Symbols.Where(t => t.Definition.Type == SymbolDefinitionType.MustBeFromAnExtension).Single();
WixAssert.StringEqual("Foo", example.Id?.Id);
WixAssert.StringEqual("filF5_pLhBuF5b4N9XEo52g_hUM5Lo", example[0].AsString());
- WixAssert.StringEqual("Bar", example[1].AsString());
+ WixAssert.StringEqual("Bar ", example[1].AsString());
}
}
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Decompiled-Expected.xml b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Decompiled-Expected.xml
index 42ececb1f..0375b3b61 100644
--- a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Decompiled-Expected.xml
+++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Decompiled-Expected.xml
@@ -5,7 +5,7 @@
-
+
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/WixlibFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/WixlibFixture.cs
index 76326741a..0cd8a5426 100644
--- a/src/wix/test/WixToolsetTest.CoreIntegration/WixlibFixture.cs
+++ b/src/wix/test/WixToolsetTest.CoreIntegration/WixlibFixture.cs
@@ -288,7 +288,7 @@ public void CanBuildWithExtensionUsingWixlib()
var example = section.Symbols.Where(t => t.Definition.Type == SymbolDefinitionType.MustBeFromAnExtension).Single();
WixAssert.StringEqual("Foo", example.Id?.Id);
- WixAssert.StringEqual("Bar", example[1].AsString());
+ WixAssert.StringEqual("Bar ", example[1].AsString());
}
}
@@ -351,7 +351,7 @@ public void CanBuildWithExtensionUsingMultipleWixlibs()
var examples = section.Symbols.Where(t => t.Definition.Type == SymbolDefinitionType.MustBeFromAnExtension).ToArray();
WixAssert.CompareLineByLine(new[] { "Foo", "Other" }, examples.Select(t => t.Id?.Id).ToArray());
WixAssert.CompareLineByLine(new[] { "filF5_pLhBuF5b4N9XEo52g_hUM5Lo", "filvxdStJhRE_M5kbpLsTZJXbs34Sg" }, examples.Select(t => t[0].AsString()).ToArray());
- WixAssert.CompareLineByLine(new[] { "Bar", "Value" }, examples.Select(t => t[1].AsString()).ToArray());
+ WixAssert.CompareLineByLine(new[] { "Bar ", "Value " }, examples.Select(t => t[1].AsString()).ToArray());
}
}
}