// 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.IO; using System.Linq; using WixToolset.Core.Bind; using WixToolset.Data; using WixToolset.Extensibility.Data; using WixToolset.Extensibility.Services; /// /// Layout for the WiX toolset. /// internal class LayoutCreator : ILayoutCreator { internal LayoutCreator(IServiceProvider serviceProvider) { this.ServiceProvider = serviceProvider; this.Messaging = serviceProvider.GetService(); } private IServiceProvider ServiceProvider { get; } private IMessaging Messaging { get; } public void Layout(ILayoutContext context) { // Pre-layout. // foreach (var extension in context.Extensions) { extension.PreLayout(context); } try { // Final step in binding that transfers (moves/copies) all files generated into the appropriate // location in the source image. if (context.FileTransfers?.Any() == true) { this.Messaging.Write(VerboseMessages.LayingOutMedia()); var command = new TransferFilesCommand(this.Messaging, context.Extensions, context.FileTransfers, context.SuppressAclReset); command.Execute(); } if (context.TrackedFiles != null) { this.CleanTempFiles(context.IntermediateFolder, context.TrackedFiles); } } finally { if (context.TrackedFiles != null) { if (!String.IsNullOrEmpty(context.ContentsFile)) { this.CreateContentsFile(context.ContentsFile, context.TrackedFiles); } if (!String.IsNullOrEmpty(context.OutputsFile)) { this.CreateOutputsFile(context.OutputsFile, context.TrackedFiles); } if (!String.IsNullOrEmpty(context.BuiltOutputsFile)) { this.CreateBuiltOutputsFile(context.BuiltOutputsFile, context.TrackedFiles); } } } // Post-layout. foreach (var extension in context.Extensions) { extension.PostLayout(); } } /// /// Writes the paths to the content files to a text file. /// /// Path to write file. /// Collection of paths to content files that will be written to file. private void CreateContentsFile(string path, IEnumerable trackedFiles) { var uniqueInputFilePaths = new SortedSet(trackedFiles.Where(t => t.Type == TrackedFileType.Input).Select(t => t.Path), StringComparer.OrdinalIgnoreCase); if (!uniqueInputFilePaths.Any()) { return; } var directory = Path.GetDirectoryName(path); Directory.CreateDirectory(directory); using (var contents = new StreamWriter(path, false)) { foreach (var inputPath in uniqueInputFilePaths) { contents.WriteLine(inputPath); } } } /// /// Writes the paths to the output files to a text file. /// /// Path to write file. /// Collection of files that were transferred to the output directory. private void CreateOutputsFile(string path, IEnumerable trackedFiles) { var uniqueOutputPaths = new SortedSet(trackedFiles.Where(t => t.Clean).Select(t => t.Path), StringComparer.OrdinalIgnoreCase); if (!uniqueOutputPaths.Any()) { return; } var directory = Path.GetDirectoryName(path); Directory.CreateDirectory(directory); using (var outputs = new StreamWriter(path, false)) { //// Don't list files where the source is the same as the destination since //// that might be the only place the file exists. The outputs file is often //// used to delete stuff and losing the original source would be bad. //var uniqueOutputPaths = new SortedSet(fileTransfers.Where(ft => !ft.Redundant).Select(ft => ft.Destination), StringComparer.OrdinalIgnoreCase); foreach (var outputPath in uniqueOutputPaths) { outputs.WriteLine(outputPath); } } } /// /// Writes the paths to the built output files to a text file. /// /// Path to write file. /// Collection of files that were transferred to the output directory. private void CreateBuiltOutputsFile(string path, IEnumerable trackedFiles) { var uniqueBuiltPaths = new SortedSet(trackedFiles.Where(t => t.Type == TrackedFileType.Final).Select(t => t.Path), StringComparer.OrdinalIgnoreCase); if (!uniqueBuiltPaths.Any()) { return; } var directory = Path.GetDirectoryName(path); Directory.CreateDirectory(directory); using (var outputs = new StreamWriter(path, false)) { foreach (var builtPath in uniqueBuiltPaths) { outputs.WriteLine(builtPath); } } } private void CleanTempFiles(string intermediateFolder, IEnumerable trackedFiles) { var uniqueTempPaths = new SortedSet(trackedFiles.Where(t => t.Type == TrackedFileType.Temporary).Select(t => t.Path), StringComparer.OrdinalIgnoreCase); if (!uniqueTempPaths.Any()) { return; } var uniqueFolders = new SortedSet(StringComparer.OrdinalIgnoreCase) { intermediateFolder }; // Clean up temp files. foreach (var tempPath in uniqueTempPaths) { try { this.SplitUniqueFolders(intermediateFolder, tempPath, uniqueFolders); File.Delete(tempPath); } catch // delete is best effort. { } } // Clean up empty temp folders. foreach (var folder in uniqueFolders.Reverse()) { try { Directory.Delete(folder); } catch // delete is best effort. { } } } private void SplitUniqueFolders(string intermediateFolder, string tempPath, SortedSet uniqueFolders) { if (tempPath.StartsWith(intermediateFolder, StringComparison.OrdinalIgnoreCase)) { var folder = Path.GetDirectoryName(tempPath).Substring(intermediateFolder.Length); var parts = folder.Split(new[] { '\\', '/' }, StringSplitOptions.RemoveEmptyEntries); folder = intermediateFolder; foreach (var part in parts) { folder = Path.Combine(folder, part); uniqueFolders.Add(folder); } } } } }