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()); } } }