From b84202794f5fd4f21e0fb1fc74c2f5d531035db3 Mon Sep 17 00:00:00 2001 From: Bob Arnson Date: Mon, 23 Oct 2023 00:17:18 -0400 Subject: [PATCH] Introduce a new phase in the build pipeline. A useful point in the build pipeline is after all the files in the project have been compiled but before they've been linked. The WiX core and extensions can operate on symbols across the project but without operating at the source-code level. This phase is currently named "optimize," after a moderately-similar phase in other compiler architectures. The name is, for now, a stake in the ground and a better alternate is welcome. --- .../BaseOptimizerExtension.cs | 26 ++++++++ .../Data/ICompileContext.cs | 2 +- .../Data/IOptimizeContext.cs | 61 +++++++++++++++++++ .../IOptimizerExtension.cs | 22 +++++++ .../CommandLine/BuildCommand.cs | 19 +++++- src/wix/WixToolset.Core/IOptimizer.cs | 17 ++++++ src/wix/WixToolset.Core/OptimizeContext.cs | 39 ++++++++++++ src/wix/WixToolset.Core/Optimizer.cs | 36 +++++++++++ .../WixToolsetServiceProvider.cs | 2 + .../ExampleExtensionFactory.cs | 4 ++ .../ExampleOptimizerExtension.cs | 22 +++++++ .../ExtensionFixture.cs | 4 +- .../ExampleExtension/Decompiled-Expected.xml | 2 +- .../WixlibFixture.cs | 4 +- 14 files changed, 253 insertions(+), 7 deletions(-) create mode 100644 src/api/wix/WixToolset.Extensibility/BaseOptimizerExtension.cs create mode 100644 src/api/wix/WixToolset.Extensibility/Data/IOptimizeContext.cs create mode 100644 src/api/wix/WixToolset.Extensibility/IOptimizerExtension.cs create mode 100644 src/wix/WixToolset.Core/IOptimizer.cs create mode 100644 src/wix/WixToolset.Core/OptimizeContext.cs create mode 100644 src/wix/WixToolset.Core/Optimizer.cs create mode 100644 src/wix/test/Example.Extension/ExampleOptimizerExtension.cs 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()); } } }