aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/WixToolset.BuildTasks/WixToolTask.cs3
-rw-r--r--src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs6
-rw-r--r--src/WixToolset.Core.Burn/BundleBackend.cs3
-rw-r--r--src/WixToolset.Core.Burn/Bundles/ProcessMsiPackageCommand.cs1
-rw-r--r--src/WixToolset.Core.Burn/Bundles/ProcessPayloadsCommand.cs1
-rw-r--r--src/WixToolset.Core.Burn/BurnBackendFactory.cs2
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs98
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs2
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/CalculateComponentGuids.cs3
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/CopyTransformDataCommand.cs2
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs2
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/PathResolver.cs105
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs11
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/ResolvedDirectory.cs (renamed from src/WixToolset.Core/Bind/ResolvedDirectory.cs)4
-rw-r--r--src/WixToolset.Core.WindowsInstaller/MsiBackend.cs8
-rw-r--r--src/WixToolset.Core.WindowsInstaller/MsmBackend.cs8
-rw-r--r--src/WixToolset.Core.WindowsInstaller/MspBackend.cs2
-rw-r--r--src/WixToolset.Core.WindowsInstaller/MstBackend.cs1
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Unbind/UnbindTranformCommand.cs2
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Validator.cs2
-rw-r--r--src/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendFactory.cs1
-rw-r--r--src/WixToolset.Core/Bind/FileResolver.cs92
-rw-r--r--src/WixToolset.Core/Bind/FileSystem.cs87
-rw-r--r--src/WixToolset.Core/Bind/ResolveFieldsCommand.cs17
-rw-r--r--src/WixToolset.Core/Bind/ResolveResult.cs14
-rw-r--r--src/WixToolset.Core/Bind/TransferFilesCommand.cs22
-rw-r--r--src/WixToolset.Core/BindContext.cs4
-rw-r--r--src/WixToolset.Core/Binder.cs451
-rw-r--r--src/WixToolset.Core/BinderFileManager.cs2
-rw-r--r--src/WixToolset.Core/BinderFileManagerCore.cs2
-rw-r--r--src/WixToolset.Core/CommandLine/BuildCommand.cs122
-rw-r--r--src/WixToolset.Core/CommandLine/CommandLine.cs4
-rw-r--r--src/WixToolset.Core/Compiler.cs53
-rw-r--r--src/WixToolset.Core/Layout.cs188
-rw-r--r--src/WixToolset.Core/LayoutContext.cs40
-rw-r--r--src/WixToolset.Core/Librarian.cs30
-rw-r--r--src/WixToolset.Core/LibraryContext.cs2
-rw-r--r--src/WixToolset.Core/Linker.cs6
-rw-r--r--src/WixToolset.Core/PreprocessContext.cs3
-rw-r--r--src/WixToolset.Core/ResolveContext.cs32
-rw-r--r--src/WixToolset.Core/Resolver.cs234
-rw-r--r--src/WixToolset.Core/WixToolsetServiceProvider.cs10
-rw-r--r--src/WixToolset.Core/WixVariableResolver.cs77
-rw-r--r--src/test/WixToolsetTest.CoreIntegration/ProgramFixture.cs45
44 files changed, 1005 insertions, 799 deletions
diff --git a/src/WixToolset.BuildTasks/WixToolTask.cs b/src/WixToolset.BuildTasks/WixToolTask.cs
index 69c837c3..7ccf0327 100644
--- a/src/WixToolset.BuildTasks/WixToolTask.cs
+++ b/src/WixToolset.BuildTasks/WixToolTask.cs
@@ -193,8 +193,7 @@ namespace WixToolset.BuildTasks
193 string responseFile = null; 193 string responseFile = null;
194 try 194 try
195 { 195 {
196 string responseFileSwitch; 196 responseFile = this.GetTemporaryResponseFile(responseFileCommands, out var responseFileSwitch);
197 responseFile = this.GetTemporaryResponseFile(responseFileCommands, out responseFileSwitch);
198 if (!String.IsNullOrEmpty(responseFileSwitch)) 197 if (!String.IsNullOrEmpty(responseFileSwitch))
199 { 198 {
200 commandLineCommands = commandLineCommands + " " + responseFileSwitch; 199 commandLineCommands = commandLineCommands + " " + responseFileSwitch;
diff --git a/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs b/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs
index 5e04e722..bc3ba8df 100644
--- a/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs
+++ b/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs
@@ -74,7 +74,9 @@ namespace WixToolset.Core.Burn
74 this.DelayedFields = context.DelayedFields; 74 this.DelayedFields = context.DelayedFields;
75 this.ExpectedEmbeddedFiles = context.ExpectedEmbeddedFiles; 75 this.ExpectedEmbeddedFiles = context.ExpectedEmbeddedFiles;
76 76
77 this.BackendExtensions = context.ExtensionManager.Create<IBurnBackendExtension>(); 77 var extensionManager = context.ServiceProvider.GetService<IExtensionManager>();
78
79 this.BackendExtensions = extensionManager.Create<IBurnBackendExtension>();
78 } 80 }
79 81
80 public CompressionLevel DefaultCompressionLevel { private get; set; } 82 public CompressionLevel DefaultCompressionLevel { private get; set; }
@@ -85,8 +87,6 @@ namespace WixToolset.Core.Burn
85 87
86 private IEnumerable<IBurnBackendExtension> BackendExtensions { get; } 88 private IEnumerable<IBurnBackendExtension> BackendExtensions { get; }
87 89
88 public IEnumerable<IBinderExtension> Extensions { private get; set; }
89
90 public Intermediate Output { private get; set; } 90 public Intermediate Output { private get; set; }
91 91
92 public string OutputPath { private get; set; } 92 public string OutputPath { private get; set; }
diff --git a/src/WixToolset.Core.Burn/BundleBackend.cs b/src/WixToolset.Core.Burn/BundleBackend.cs
index f66a3fbe..83d33c8a 100644
--- a/src/WixToolset.Core.Burn/BundleBackend.cs
+++ b/src/WixToolset.Core.Burn/BundleBackend.cs
@@ -9,7 +9,6 @@ namespace WixToolset.Core.Burn
9 using WixToolset.Data; 9 using WixToolset.Data;
10 using WixToolset.Data.Bind; 10 using WixToolset.Data.Bind;
11 using WixToolset.Extensibility; 11 using WixToolset.Extensibility;
12 using WixToolset.Extensibility.Services;
13 12
14 internal class BundleBackend : IBackend 13 internal class BundleBackend : IBackend
15 { 14 {
@@ -25,7 +24,7 @@ namespace WixToolset.Core.Burn
25 //command.WixVariableResolver = context.WixVariableResolver; 24 //command.WixVariableResolver = context.WixVariableResolver;
26 command.Execute(); 25 command.Execute();
27 26
28 return new BindResult(command.FileTransfers, command.ContentFilePaths); 27 return new BindResult { FileTransfers = command.FileTransfers, ContentFilePaths = command.ContentFilePaths };
29 } 28 }
30 29
31 public bool Inscribe(IInscribeContext context) 30 public bool Inscribe(IInscribeContext context)
diff --git a/src/WixToolset.Core.Burn/Bundles/ProcessMsiPackageCommand.cs b/src/WixToolset.Core.Burn/Bundles/ProcessMsiPackageCommand.cs
index 2ee58514..636cb1a0 100644
--- a/src/WixToolset.Core.Burn/Bundles/ProcessMsiPackageCommand.cs
+++ b/src/WixToolset.Core.Burn/Bundles/ProcessMsiPackageCommand.cs
@@ -14,7 +14,6 @@ namespace WixToolset.Core.Burn.Bundles
14 using WixToolset.Extensibility; 14 using WixToolset.Extensibility;
15 using WixToolset.Core.Native; 15 using WixToolset.Core.Native;
16 using Dtf = WixToolset.Dtf.WindowsInstaller; 16 using Dtf = WixToolset.Dtf.WindowsInstaller;
17 using WixToolset.Bind;
18 using WixToolset.Data.Bind; 17 using WixToolset.Data.Bind;
19 18
20 /// <summary> 19 /// <summary>
diff --git a/src/WixToolset.Core.Burn/Bundles/ProcessPayloadsCommand.cs b/src/WixToolset.Core.Burn/Bundles/ProcessPayloadsCommand.cs
index 7900fcd1..1d91ddf2 100644
--- a/src/WixToolset.Core.Burn/Bundles/ProcessPayloadsCommand.cs
+++ b/src/WixToolset.Core.Burn/Bundles/ProcessPayloadsCommand.cs
@@ -9,7 +9,6 @@ namespace WixToolset.Core.Burn.Bundles
9 using System.Security.Cryptography; 9 using System.Security.Cryptography;
10 using System.Security.Cryptography.X509Certificates; 10 using System.Security.Cryptography.X509Certificates;
11 using System.Text; 11 using System.Text;
12 using WixToolset.Bind;
13 using WixToolset.Data; 12 using WixToolset.Data;
14 using WixToolset.Data.Bind; 13 using WixToolset.Data.Bind;
15 using WixToolset.Data.Rows; 14 using WixToolset.Data.Rows;
diff --git a/src/WixToolset.Core.Burn/BurnBackendFactory.cs b/src/WixToolset.Core.Burn/BurnBackendFactory.cs
index 8e2b3ce2..5da3a0cb 100644
--- a/src/WixToolset.Core.Burn/BurnBackendFactory.cs
+++ b/src/WixToolset.Core.Burn/BurnBackendFactory.cs
@@ -9,7 +9,7 @@ namespace WixToolset.Core.Burn
9 9
10 internal class BurnBackendFactory : IBackendFactory 10 internal class BurnBackendFactory : IBackendFactory
11 { 11 {
12 public bool TryCreateBackend(string outputType, string outputFile, IBindContext context, out IBackend backend) 12 public bool TryCreateBackend(string outputType, string outputFile, WixToolset.Extensibility.IBindContext context, out IBackend backend)
13 { 13 {
14 if (String.IsNullOrEmpty(outputType)) 14 if (String.IsNullOrEmpty(outputType))
15 { 15 {
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs
index 410e462a..2f161305 100644
--- a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs
@@ -22,31 +22,27 @@ namespace WixToolset.Core.WindowsInstaller.Bind
22 // As outlined in RFC 4122, this is our namespace for generating name-based (version 3) UUIDs. 22 // As outlined in RFC 4122, this is our namespace for generating name-based (version 3) UUIDs.
23 internal static readonly Guid WixComponentGuidNamespace = new Guid("{3064E5C6-FB63-4FE9-AC49-E446A792EFA5}"); 23 internal static readonly Guid WixComponentGuidNamespace = new Guid("{3064E5C6-FB63-4FE9-AC49-E446A792EFA5}");
24 24
25 public BindDatabaseCommand(IBindContext context, IEnumerable<IWindowsInstallerBackendExtension> backendExtension, Validator validator) 25 public BindDatabaseCommand(WixToolset.Extensibility.IBindContext context, IEnumerable<IWindowsInstallerBackendExtension> backendExtension, Validator validator)
26 { 26 {
27 this.TableDefinitions = WindowsInstallerStandardInternal.GetTableDefinitions(); 27 this.TableDefinitions = WindowsInstallerStandardInternal.GetTableDefinitions();
28 28
29 this.BindPaths = context.BindPaths;
30 this.CabbingThreadCount = context.CabbingThreadCount; 29 this.CabbingThreadCount = context.CabbingThreadCount;
31 this.CabCachePath = context.CabCachePath; 30 this.CabCachePath = context.CabCachePath;
32 this.Codepage = context.Codepage; 31 this.Codepage = context.Codepage;
33 this.DefaultCompressionLevel = context.DefaultCompressionLevel; 32 this.DefaultCompressionLevel = context.DefaultCompressionLevel;
34 this.DelayedFields = context.DelayedFields; 33 this.DelayedFields = context.DelayedFields;
35 this.ExpectedEmbeddedFiles = context.ExpectedEmbeddedFiles; 34 this.ExpectedEmbeddedFiles = context.ExpectedEmbeddedFiles;
36 this.Extensions = context.Extensions; 35 this.FileSystemExtensions = context.FileSystemExtensions;
37 this.Intermediate = context.IntermediateRepresentation; 36 this.Intermediate = context.IntermediateRepresentation;
38 this.Messaging = context.Messaging; 37 this.Messaging = context.Messaging;
39 this.OutputPath = context.OutputPath; 38 this.OutputPath = context.OutputPath;
40 this.PdbFile = context.OutputPdbPath; 39 this.PdbFile = context.OutputPdbPath;
41 this.IntermediateFolder = context.IntermediateFolder; 40 this.IntermediateFolder = context.IntermediateFolder;
42 this.Validator = validator; 41 this.Validator = validator;
43 this.WixVariableResolver = context.WixVariableResolver; 42
44
45 this.BackendExtensions = backendExtension; 43 this.BackendExtensions = backendExtension;
46 } 44 }
47 45
48 private IEnumerable<BindPath> BindPaths { get; }
49
50 private int Codepage { get; } 46 private int Codepage { get; }
51 47
52 private int CabbingThreadCount { get; } 48 private int CabbingThreadCount { get; }
@@ -59,12 +55,12 @@ namespace WixToolset.Core.WindowsInstaller.Bind
59 55
60 public IEnumerable<IExpectedExtractFile> ExpectedEmbeddedFiles { get; } 56 public IEnumerable<IExpectedExtractFile> ExpectedEmbeddedFiles { get; }
61 57
58 public IEnumerable<IFileSystemExtension> FileSystemExtensions { get; }
59
62 public bool DeltaBinaryPatch { get; set; } 60 public bool DeltaBinaryPatch { get; set; }
63 61
64 private IEnumerable<IWindowsInstallerBackendExtension> BackendExtensions { get; } 62 private IEnumerable<IWindowsInstallerBackendExtension> BackendExtensions { get; }
65 63
66 private IEnumerable<IBinderExtension> Extensions { get; }
67
68 private string PdbFile { get; } 64 private string PdbFile { get; }
69 65
70 private Intermediate Intermediate { get; } 66 private Intermediate Intermediate { get; }
@@ -83,7 +79,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind
83 79
84 private Validator Validator { get; } 80 private Validator Validator { get; }
85 81
86 private IBindVariableResolver WixVariableResolver { get; }
87 82
88 public IEnumerable<FileTransfer> FileTransfers { get; private set; } 83 public IEnumerable<FileTransfer> FileTransfers { get; private set; }
89 84
@@ -100,8 +95,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind
100 // If there are any fields to resolve later, create the cache to populate during bind. 95 // If there are any fields to resolve later, create the cache to populate during bind.
101 var variableCache = this.DelayedFields.Any() ? new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase) : null; 96 var variableCache = this.DelayedFields.Any() ? new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase) : null;
102 97
103 this.LocalizeUI(section);
104
105 // Process the summary information table before the other tables. 98 // Process the summary information table before the other tables.
106 bool compressed; 99 bool compressed;
107 bool longNames; 100 bool longNames;
@@ -534,85 +527,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind
534 intermediate.Save(Path.ChangeExtension(this.OutputPath, "wir")); 527 intermediate.Save(Path.ChangeExtension(this.OutputPath, "wir"));
535 } 528 }
536 529
537 /// <summary>
538 /// Localize dialogs and controls.
539 /// </summary>
540 /// <param name="tables">The tables to localize.</param>
541 private void LocalizeUI(IntermediateSection section)
542 {
543 foreach (var row in section.Tuples.OfType<DialogTuple>())
544 {
545 string dialog = row.Dialog;
546
547 if (this.WixVariableResolver.TryGetLocalizedControl(dialog, null, out LocalizedControl localizedControl))
548 {
549 if (CompilerConstants.IntegerNotSet != localizedControl.X)
550 {
551 row.HCentering = localizedControl.X;
552 }
553
554 if (CompilerConstants.IntegerNotSet != localizedControl.Y)
555 {
556 row.VCentering = localizedControl.Y;
557 }
558
559 if (CompilerConstants.IntegerNotSet != localizedControl.Width)
560 {
561 row.Width = localizedControl.Width;
562 }
563
564 if (CompilerConstants.IntegerNotSet != localizedControl.Height)
565 {
566 row.Height = localizedControl.Height;
567 }
568
569 row.Attributes = row.Attributes | localizedControl.Attributes;
570
571 if (!String.IsNullOrEmpty(localizedControl.Text))
572 {
573 row.Title = localizedControl.Text;
574 }
575 }
576 }
577
578
579 foreach (var row in section.Tuples.OfType<ControlTuple>())
580 {
581 string dialog = row.Dialog_;
582 string control = row.Control;
583
584 if (this.WixVariableResolver.TryGetLocalizedControl(dialog, control, out LocalizedControl localizedControl))
585 {
586 if (CompilerConstants.IntegerNotSet != localizedControl.X)
587 {
588 row.X = localizedControl.X;
589 }
590
591 if (CompilerConstants.IntegerNotSet != localizedControl.Y)
592 {
593 row.Y = localizedControl.Y;
594 }
595
596 if (CompilerConstants.IntegerNotSet != localizedControl.Width)
597 {
598 row.Width = localizedControl.Width;
599 }
600
601 if (CompilerConstants.IntegerNotSet != localizedControl.Height)
602 {
603 row.Height = localizedControl.Height;
604 }
605
606 row.Attributes = row.Attributes | localizedControl.Attributes;
607
608 if (!String.IsNullOrEmpty(localizedControl.Text))
609 {
610 row.Text = localizedControl.Text;
611 }
612 }
613 }
614 }
615
616#if TODO_FINISH_PATCH 530#if TODO_FINISH_PATCH
617 /// <summary> 531 /// <summary>
618 /// Copy file data between transform substorages and the patch output object 532 /// Copy file data between transform substorages and the patch output object
@@ -984,7 +898,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
984 private void GenerateDatabase(Output output, string databaseFile, bool keepAddedColumns, bool useSubdirectory) 898 private void GenerateDatabase(Output output, string databaseFile, bool keepAddedColumns, bool useSubdirectory)
985 { 899 {
986 var command = new GenerateDatabaseCommand(); 900 var command = new GenerateDatabaseCommand();
987 command.Extensions = this.Extensions; 901 command.Extensions = this.FileSystemExtensions;
988 command.Output = output; 902 command.Output = output;
989 command.OutputPath = databaseFile; 903 command.OutputPath = databaseFile;
990 command.KeepAddedColumns = keepAddedColumns; 904 command.KeepAddedColumns = keepAddedColumns;
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs
index 800ebac0..8cb0e0de 100644
--- a/src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs
@@ -15,7 +15,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
15 15
16 internal class BindTransformCommand 16 internal class BindTransformCommand
17 { 17 {
18 public IEnumerable<IBinderExtension> Extensions { private get; set; } 18 public IEnumerable<IFileSystemExtension> Extensions { private get; set; }
19 19
20 public TableDefinitionCollection TableDefinitions { private get; set; } 20 public TableDefinitionCollection TableDefinitions { private get; set; }
21 21
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CalculateComponentGuids.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CalculateComponentGuids.cs
index 056f92a7..76747728 100644
--- a/src/WixToolset.Core.WindowsInstaller/Bind/CalculateComponentGuids.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/CalculateComponentGuids.cs
@@ -6,7 +6,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind
6 using System.Collections.Generic; 6 using System.Collections.Generic;
7 using System.IO; 7 using System.IO;
8 using System.Linq; 8 using System.Linq;
9 using WixToolset.Bind;
10 using WixToolset.Core.Native; 9 using WixToolset.Core.Native;
11 using WixToolset.Data; 10 using WixToolset.Data;
12 using WixToolset.Data.Tuples; 11 using WixToolset.Data.Tuples;
@@ -135,7 +134,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
135 if (fileRow.File == componentRow.KeyPath) 134 if (fileRow.File == componentRow.KeyPath)
136 { 135 {
137 // calculate the key file's canonical target path 136 // calculate the key file's canonical target path
138 string directoryPath = Binder.GetDirectoryPath(targetPathsByDirectoryId, componentIdGenSeeds, componentRow.Directory_, true); 137 string directoryPath = PathResolver.GetDirectoryPath(targetPathsByDirectoryId, componentIdGenSeeds, componentRow.Directory_, true);
139 string fileName = Common.GetName(fileRow.LongFileName, false, true).ToLowerInvariant(); 138 string fileName = Common.GetName(fileRow.LongFileName, false, true).ToLowerInvariant();
140 path = Path.Combine(directoryPath, fileName); 139 path = Path.Combine(directoryPath, fileName);
141 140
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CopyTransformDataCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CopyTransformDataCommand.cs
index 13408312..6ff03941 100644
--- a/src/WixToolset.Core.WindowsInstaller/Bind/CopyTransformDataCommand.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/CopyTransformDataCommand.cs
@@ -18,7 +18,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
18 { 18 {
19 public bool CopyOutFileRows { private get; set; } 19 public bool CopyOutFileRows { private get; set; }
20 20
21 public IEnumerable<IBinderExtension> Extensions { private get; set; } 21 public IEnumerable<IFileSystemExtension> Extensions { private get; set; }
22 22
23 public IMessaging Messaging { private get; set; } 23 public IMessaging Messaging { private get; set; }
24 24
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs
index ee7cc61b..cc920ac2 100644
--- a/src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs
@@ -19,7 +19,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
19 { 19 {
20 public int Codepage { private get; set; } 20 public int Codepage { private get; set; }
21 21
22 public IEnumerable<IBinderExtension> Extensions { private get; set; } 22 public IEnumerable<IFileSystemExtension> Extensions { private get; set; }
23 23
24 /// <summary> 24 /// <summary>
25 /// Whether to keep columns added in a transform. 25 /// Whether to keep columns added in a transform.
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/PathResolver.cs b/src/WixToolset.Core.WindowsInstaller/Bind/PathResolver.cs
new file mode 100644
index 00000000..492c9137
--- /dev/null
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/PathResolver.cs
@@ -0,0 +1,105 @@
1// 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.
2
3namespace WixToolset.Core.WindowsInstaller.Bind
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using WixToolset.Data;
9
10 internal static class PathResolver
11 {
12 /// <summary>
13 /// Get the source path of a directory.
14 /// </summary>
15 /// <param name="directories">All cached directories.</param>
16 /// <param name="componentIdGenSeeds">Hash table of Component GUID generation seeds indexed by directory id.</param>
17 /// <param name="directory">Directory identifier.</param>
18 /// <param name="canonicalize">Canonicalize the path for standard directories.</param>
19 /// <returns>Source path of a directory.</returns>
20 public static string GetDirectoryPath(Dictionary<string, ResolvedDirectory> directories, Dictionary<string, string> componentIdGenSeeds, string directory, bool canonicalize)
21 {
22 if (!directories.TryGetValue(directory, out var resolvedDirectory))
23 {
24 throw new WixException(ErrorMessages.ExpectedDirectory(directory));
25 }
26
27 if (null == resolvedDirectory.Path)
28 {
29 if (null != componentIdGenSeeds && componentIdGenSeeds.ContainsKey(directory))
30 {
31 resolvedDirectory.Path = (string)componentIdGenSeeds[directory];
32 }
33 else if (canonicalize && WindowsInstallerStandard.IsStandardDirectory(directory))
34 {
35 // when canonicalization is on, standard directories are treated equally
36 resolvedDirectory.Path = directory;
37 }
38 else
39 {
40 string name = resolvedDirectory.Name;
41
42 if (canonicalize)
43 {
44 name = name?.ToLowerInvariant();
45 }
46
47 if (String.IsNullOrEmpty(resolvedDirectory.DirectoryParent))
48 {
49 resolvedDirectory.Path = name;
50 }
51 else
52 {
53 string parentPath = GetDirectoryPath(directories, componentIdGenSeeds, resolvedDirectory.DirectoryParent, canonicalize);
54
55 if (null != resolvedDirectory.Name)
56 {
57 resolvedDirectory.Path = Path.Combine(parentPath, name);
58 }
59 else
60 {
61 resolvedDirectory.Path = parentPath;
62 }
63 }
64 }
65 }
66
67 return resolvedDirectory.Path;
68 }
69
70 /// <summary>
71 /// Gets the source path of a file.
72 /// </summary>
73 /// <param name="directories">All cached directories in <see cref="ResolvedDirectory"/>.</param>
74 /// <param name="directoryId">Parent directory identifier.</param>
75 /// <param name="fileName">File name (in long|source format).</param>
76 /// <param name="compressed">Specifies the package is compressed.</param>
77 /// <param name="useLongName">Specifies the package uses long file names.</param>
78 /// <returns>Source path of file relative to package directory.</returns>
79 public static string GetFileSourcePath(Dictionary<string, ResolvedDirectory> directories, string directoryId, string fileName, bool compressed, bool useLongName)
80 {
81 string fileSourcePath = Common.GetName(fileName, true, useLongName);
82
83 if (compressed)
84 {
85 // Use just the file name of the file since all uncompressed files must appear
86 // in the root of the image in a compressed package.
87 }
88 else
89 {
90 // Get the relative path of where we want the file to be layed out as specified
91 // in the Directory table.
92 string directoryPath = PathResolver.GetDirectoryPath(directories, null, directoryId, false);
93 fileSourcePath = Path.Combine(directoryPath, fileSourcePath);
94 }
95
96 // Strip off "SourceDir" if it's still on there.
97 if (fileSourcePath.StartsWith("SourceDir\\", StringComparison.Ordinal))
98 {
99 fileSourcePath = fileSourcePath.Substring(10);
100 }
101
102 return fileSourcePath;
103 }
104 }
105}
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs
index e1a26a67..39771508 100644
--- a/src/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs
@@ -5,14 +5,13 @@ namespace WixToolset.Core.WindowsInstaller.Bind
5 using System; 5 using System;
6 using System.Collections.Generic; 6 using System.Collections.Generic;
7 using System.IO; 7 using System.IO;
8 using WixToolset.Data; 8 using System.Linq;
9 using WixToolset.Msi;
10 using WixToolset.Core.Native;
11 using WixToolset.Bind;
12 using WixToolset.Core.Bind; 9 using WixToolset.Core.Bind;
10 using WixToolset.Core.Native;
11 using WixToolset.Data;
13 using WixToolset.Data.Bind; 12 using WixToolset.Data.Bind;
14 using WixToolset.Data.Tuples; 13 using WixToolset.Data.Tuples;
15 using System.Linq; 14 using WixToolset.Msi;
16 15
17 /// <summary> 16 /// <summary>
18 /// Defines the file transfers necessary to layout the uncompressed files. 17 /// Defines the file transfers necessary to layout the uncompressed files.
@@ -100,7 +99,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
100 throw new WixException(ErrorMessages.FileIdentifierNotFound(facade.File.SourceLineNumbers, facade.File.File)); 99 throw new WixException(ErrorMessages.FileIdentifierNotFound(facade.File.SourceLineNumbers, facade.File.File));
101 } 100 }
102 101
103 relativeFileLayoutPath = Binder.GetFileSourcePath(directories, fileRecord[1], fileRecord[2], this.Compressed, this.LongNamesInImage); 102 relativeFileLayoutPath = PathResolver.GetFileSourcePath(directories, fileRecord[1], fileRecord[2], this.Compressed, this.LongNamesInImage);
104 } 103 }
105 104
106 // finally put together the base media layout path and the relative file layout path 105 // finally put together the base media layout path and the relative file layout path
diff --git a/src/WixToolset.Core/Bind/ResolvedDirectory.cs b/src/WixToolset.Core.WindowsInstaller/Bind/ResolvedDirectory.cs
index 9d07fc93..e06321cf 100644
--- a/src/WixToolset.Core/Bind/ResolvedDirectory.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/ResolvedDirectory.cs
@@ -1,11 +1,11 @@
1// 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. 1// 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.
2 2
3namespace WixToolset.Bind 3namespace WixToolset.Core.WindowsInstaller.Bind
4{ 4{
5 /// <summary> 5 /// <summary>
6 /// Structure used for resolved directory information. 6 /// Structure used for resolved directory information.
7 /// </summary> 7 /// </summary>
8 public struct ResolvedDirectory 8 internal struct ResolvedDirectory
9 { 9 {
10 /// <summary> 10 /// <summary>
11 /// Constructor for ResolvedDirectory. 11 /// Constructor for ResolvedDirectory.
diff --git a/src/WixToolset.Core.WindowsInstaller/MsiBackend.cs b/src/WixToolset.Core.WindowsInstaller/MsiBackend.cs
index 4753677a..00f09db3 100644
--- a/src/WixToolset.Core.WindowsInstaller/MsiBackend.cs
+++ b/src/WixToolset.Core.WindowsInstaller/MsiBackend.cs
@@ -12,9 +12,11 @@ namespace WixToolset.Core.WindowsInstaller
12 12
13 internal class MsiBackend : IBackend 13 internal class MsiBackend : IBackend
14 { 14 {
15 public BindResult Bind(IBindContext context) 15 public BindResult Bind(WixToolset.Extensibility.IBindContext context)
16 { 16 {
17 var backendExtensions = context.ExtensionManager.Create<IWindowsInstallerBackendExtension>(); 17 var extensionManager = context.ServiceProvider.GetService<IExtensionManager>();
18
19 var backendExtensions = extensionManager.Create<IWindowsInstallerBackendExtension>();
18 20
19 foreach (var extension in backendExtensions) 21 foreach (var extension in backendExtensions)
20 { 22 {
@@ -26,7 +28,7 @@ namespace WixToolset.Core.WindowsInstaller
26 var command = new BindDatabaseCommand(context, backendExtensions, validator); 28 var command = new BindDatabaseCommand(context, backendExtensions, validator);
27 command.Execute(); 29 command.Execute();
28 30
29 var result = new BindResult(command.FileTransfers, command.ContentFilePaths); 31 var result = new BindResult { FileTransfers = command.FileTransfers, ContentFilePaths = command.ContentFilePaths };
30 32
31 foreach (var extension in backendExtensions) 33 foreach (var extension in backendExtensions)
32 { 34 {
diff --git a/src/WixToolset.Core.WindowsInstaller/MsmBackend.cs b/src/WixToolset.Core.WindowsInstaller/MsmBackend.cs
index 2323f8dd..9c70860e 100644
--- a/src/WixToolset.Core.WindowsInstaller/MsmBackend.cs
+++ b/src/WixToolset.Core.WindowsInstaller/MsmBackend.cs
@@ -11,9 +11,11 @@ namespace WixToolset.Core.WindowsInstaller
11 11
12 internal class MsmBackend : IBackend 12 internal class MsmBackend : IBackend
13 { 13 {
14 public BindResult Bind(IBindContext context) 14 public BindResult Bind(WixToolset.Extensibility.IBindContext context)
15 { 15 {
16 var backendExtensions = context.ExtensionManager.Create<IWindowsInstallerBackendExtension>(); 16 var extensionManager = context.ServiceProvider.GetService<IExtensionManager>();
17
18 var backendExtensions = extensionManager.Create<IWindowsInstallerBackendExtension>();
17 19
18 foreach (var extension in backendExtensions) 20 foreach (var extension in backendExtensions)
19 { 21 {
@@ -25,7 +27,7 @@ namespace WixToolset.Core.WindowsInstaller
25 var command = new BindDatabaseCommand(context, backendExtensions, validator); 27 var command = new BindDatabaseCommand(context, backendExtensions, validator);
26 command.Execute(); 28 command.Execute();
27 29
28 var result = new BindResult(command.FileTransfers, command.ContentFilePaths); 30 var result = new BindResult { FileTransfers = command.FileTransfers, ContentFilePaths = command.ContentFilePaths };
29 31
30 foreach (var extension in backendExtensions) 32 foreach (var extension in backendExtensions)
31 { 33 {
diff --git a/src/WixToolset.Core.WindowsInstaller/MspBackend.cs b/src/WixToolset.Core.WindowsInstaller/MspBackend.cs
index 8fb63665..5dbed241 100644
--- a/src/WixToolset.Core.WindowsInstaller/MspBackend.cs
+++ b/src/WixToolset.Core.WindowsInstaller/MspBackend.cs
@@ -16,7 +16,7 @@ namespace WixToolset.Core.WindowsInstaller
16 16
17 internal class MspBackend : IBackend 17 internal class MspBackend : IBackend
18 { 18 {
19 public BindResult Bind(IBindContext context) 19 public BindResult Bind(WixToolset.Extensibility.IBindContext context)
20 { 20 {
21 throw new NotImplementedException(); 21 throw new NotImplementedException();
22 } 22 }
diff --git a/src/WixToolset.Core.WindowsInstaller/MstBackend.cs b/src/WixToolset.Core.WindowsInstaller/MstBackend.cs
index 17617dbc..4eb0901c 100644
--- a/src/WixToolset.Core.WindowsInstaller/MstBackend.cs
+++ b/src/WixToolset.Core.WindowsInstaller/MstBackend.cs
@@ -7,7 +7,6 @@ namespace WixToolset.Core.WindowsInstaller
7 using WixToolset.Data; 7 using WixToolset.Data;
8 using WixToolset.Data.Bind; 8 using WixToolset.Data.Bind;
9 using WixToolset.Extensibility; 9 using WixToolset.Extensibility;
10 using WixToolset.Extensibility.Services;
11 10
12 internal class MstBackend : IBackend 11 internal class MstBackend : IBackend
13 { 12 {
diff --git a/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindTranformCommand.cs b/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindTranformCommand.cs
index 2b018013..00e5a755 100644
--- a/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindTranformCommand.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindTranformCommand.cs
@@ -244,7 +244,7 @@ namespace WixToolset.Core.WindowsInstaller.Unbind
244 private void GenerateDatabase(Output output, string databaseFile) 244 private void GenerateDatabase(Output output, string databaseFile)
245 { 245 {
246 var command = new GenerateDatabaseCommand(); 246 var command = new GenerateDatabaseCommand();
247 command.Extensions = Array.Empty<IBinderExtension>(); 247 command.Extensions = Array.Empty<IFileSystemExtension>();
248 command.Output = output; 248 command.Output = output;
249 command.OutputPath = databaseFile; 249 command.OutputPath = databaseFile;
250 command.KeepAddedColumns = true; 250 command.KeepAddedColumns = true;
diff --git a/src/WixToolset.Core.WindowsInstaller/Validator.cs b/src/WixToolset.Core.WindowsInstaller/Validator.cs
index d553cc71..5f41e88d 100644
--- a/src/WixToolset.Core.WindowsInstaller/Validator.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Validator.cs
@@ -324,7 +324,7 @@ namespace WixToolset.Core.WindowsInstaller
324 } 324 }
325 } 325 }
326 326
327 public static Validator CreateFromContext(IBindContext context, string cubeFilename) 327 public static Validator CreateFromContext(WixToolset.Extensibility.IBindContext context, string cubeFilename)
328 { 328 {
329 Validator validator = null; 329 Validator validator = null;
330 330
diff --git a/src/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendFactory.cs b/src/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendFactory.cs
index a7f58ed4..b66a4617 100644
--- a/src/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendFactory.cs
+++ b/src/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendFactory.cs
@@ -5,7 +5,6 @@ namespace WixToolset.Core.WindowsInstaller
5 using System; 5 using System;
6 using System.IO; 6 using System.IO;
7 using WixToolset.Extensibility; 7 using WixToolset.Extensibility;
8 using WixToolset.Extensibility.Services;
9 8
10 internal class WindowsInstallerBackendFactory : IBackendFactory 9 internal class WindowsInstallerBackendFactory : IBackendFactory
11 { 10 {
diff --git a/src/WixToolset.Core/Bind/FileResolver.cs b/src/WixToolset.Core/Bind/FileResolver.cs
index 2142d261..a20d3f34 100644
--- a/src/WixToolset.Core/Bind/FileResolver.cs
+++ b/src/WixToolset.Core/Bind/FileResolver.cs
@@ -6,7 +6,6 @@ namespace WixToolset.Core.Bind
6 using System.Collections.Generic; 6 using System.Collections.Generic;
7 using System.IO; 7 using System.IO;
8 using System.Linq; 8 using System.Linq;
9 using System.Runtime.InteropServices;
10 using WixToolset.Data; 9 using WixToolset.Data;
11 using WixToolset.Data.Bind; 10 using WixToolset.Data.Bind;
12 using WixToolset.Extensibility; 11 using WixToolset.Extensibility;
@@ -22,9 +21,9 @@ namespace WixToolset.Core.Bind
22 this.RebaseUpdated = this.BindPaths[BindStage.Updated].Any(); 21 this.RebaseUpdated = this.BindPaths[BindStage.Updated].Any();
23 } 22 }
24 23
25 public FileResolver(IEnumerable<BindPath> bindPaths, IEnumerable<IBinderExtension> extensions) : this(bindPaths) 24 public FileResolver(IEnumerable<BindPath> bindPaths, IEnumerable<IResolverExtension> extensions) : this(bindPaths)
26 { 25 {
27 this.BinderExtensions = extensions ?? Array.Empty<IBinderExtension>(); 26 this.ResolverExtensions = extensions ?? Array.Empty<IResolverExtension>();
28 } 27 }
29 28
30 public FileResolver(IEnumerable<BindPath> bindPaths, IEnumerable<ILibrarianExtension> extensions) : this(bindPaths) 29 public FileResolver(IEnumerable<BindPath> bindPaths, IEnumerable<ILibrarianExtension> extensions) : this(bindPaths)
@@ -38,79 +37,15 @@ namespace WixToolset.Core.Bind
38 37
39 public bool RebaseUpdated { get; } 38 public bool RebaseUpdated { get; }
40 39
41 private IEnumerable<IBinderExtension> BinderExtensions { get; } 40 private IEnumerable<IResolverExtension> ResolverExtensions { get; }
42 41
43 private IEnumerable<ILibrarianExtension> LibrarianExtensions { get; } 42 private IEnumerable<ILibrarianExtension> LibrarianExtensions { get; }
44 43
45 /// <summary> 44 public string Resolve(SourceLineNumber sourceLineNumbers, IntermediateTupleDefinition tupleDefinition, string source)
46 /// Copies a file.
47 /// </summary>
48 /// <param name="source">The file to copy.</param>
49 /// <param name="destination">The destination file.</param>
50 /// <param name="overwrite">true if the destination file can be overwritten; otherwise, false.</param>
51 public bool CopyFile(string source, string destination, bool overwrite)
52 {
53 foreach (var extension in this.BinderExtensions)
54 {
55 if (extension.CopyFile(source, destination, overwrite))
56 {
57 return true;
58 }
59 }
60
61 if (overwrite && File.Exists(destination))
62 {
63 File.Delete(destination);
64 }
65
66 if (!CreateHardLink(destination, source, IntPtr.Zero))
67 {
68#if DEBUG
69 int er = Marshal.GetLastWin32Error();
70#endif
71
72 File.Copy(source, destination, overwrite);
73 }
74
75 return true;
76 }
77
78 /// <summary>
79 /// Moves a file.
80 /// </summary>
81 /// <param name="source">The file to move.</param>
82 /// <param name="destination">The destination file.</param>
83 public bool MoveFile(string source, string destination, bool overwrite)
84 {
85 foreach (var extension in this.BinderExtensions)
86 {
87 if (extension.MoveFile(source, destination, overwrite))
88 {
89 return true;
90 }
91 }
92
93 if (overwrite && File.Exists(destination))
94 {
95 File.Delete(destination);
96 }
97
98 var directory = Path.GetDirectoryName(destination);
99 if (!String.IsNullOrEmpty(directory))
100 {
101 Directory.CreateDirectory(directory);
102 }
103
104 File.Move(source, destination);
105
106 return true;
107 }
108
109 public string Resolve(SourceLineNumber sourceLineNumbers, string table, string path)
110 { 45 {
111 foreach (var extension in this.LibrarianExtensions) 46 foreach (var extension in this.LibrarianExtensions)
112 { 47 {
113 var resolved = extension.Resolve(sourceLineNumbers, table, path); 48 var resolved = extension.Resolve(sourceLineNumbers, tupleDefinition, source);
114 49
115 if (null != resolved) 50 if (null != resolved)
116 { 51 {
@@ -118,7 +53,7 @@ namespace WixToolset.Core.Bind
118 } 53 }
119 } 54 }
120 55
121 return this.ResolveUsingBindPaths(path, table, sourceLineNumbers, BindStage.Normal); 56 return this.ResolveUsingBindPaths(source, tupleDefinition, sourceLineNumbers, BindStage.Normal);
122 } 57 }
123 58
124 /// <summary> 59 /// <summary>
@@ -129,11 +64,11 @@ namespace WixToolset.Core.Bind
129 /// <param name="sourceLineNumbers">Optional source line of source file being resolved.</param> 64 /// <param name="sourceLineNumbers">Optional source line of source file being resolved.</param>
130 /// <param name="bindStage">The binding stage used to determine what collection of bind paths will be used</param> 65 /// <param name="bindStage">The binding stage used to determine what collection of bind paths will be used</param>
131 /// <returns>Should return a valid path for the stream to be imported.</returns> 66 /// <returns>Should return a valid path for the stream to be imported.</returns>
132 public string ResolveFile(string source, string type, SourceLineNumber sourceLineNumbers, BindStage bindStage) 67 public string ResolveFile(string source, IntermediateTupleDefinition tupleDefinition, SourceLineNumber sourceLineNumbers, BindStage bindStage)
133 { 68 {
134 foreach (var extension in this.BinderExtensions) 69 foreach (var extension in this.ResolverExtensions)
135 { 70 {
136 var resolved = extension.ResolveFile(source, type, sourceLineNumbers, bindStage); 71 var resolved = extension.ResolveFile(source, tupleDefinition, sourceLineNumbers, bindStage);
137 72
138 if (null != resolved) 73 if (null != resolved)
139 { 74 {
@@ -141,10 +76,10 @@ namespace WixToolset.Core.Bind
141 } 76 }
142 } 77 }
143 78
144 return this.ResolveUsingBindPaths(source, type, sourceLineNumbers, bindStage); 79 return this.ResolveUsingBindPaths(source, tupleDefinition, sourceLineNumbers, bindStage);
145 } 80 }
146 81
147 private string ResolveUsingBindPaths(string source, string type, SourceLineNumber sourceLineNumbers, BindStage bindStage) 82 private string ResolveUsingBindPaths(string source, IntermediateTupleDefinition tupleDefinition, SourceLineNumber sourceLineNumbers, BindStage bindStage)
148 { 83 {
149 string resolved = null; 84 string resolved = null;
150 85
@@ -206,7 +141,7 @@ namespace WixToolset.Core.Bind
206 141
207 if (null == resolved) 142 if (null == resolved)
208 { 143 {
209 throw new WixFileNotFoundException(sourceLineNumbers, source, type); 144 throw new WixFileNotFoundException(sourceLineNumbers, source, tupleDefinition.Name);
210 } 145 }
211 146
212 // Didn't find the file. 147 // Didn't find the file.
@@ -224,8 +159,5 @@ namespace WixToolset.Core.Bind
224 throw new WixException(ErrorMessages.IllegalCharactersInPath(path)); 159 throw new WixException(ErrorMessages.IllegalCharactersInPath(path));
225 } 160 }
226 } 161 }
227
228 [DllImport("Kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
229 private static extern bool CreateHardLink(string lpFileName, string lpExistingFileName, IntPtr lpSecurityAttributes);
230 } 162 }
231} 163}
diff --git a/src/WixToolset.Core/Bind/FileSystem.cs b/src/WixToolset.Core/Bind/FileSystem.cs
new file mode 100644
index 00000000..7d1b223e
--- /dev/null
+++ b/src/WixToolset.Core/Bind/FileSystem.cs
@@ -0,0 +1,87 @@
1// 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.
2
3namespace WixToolset.Core.Bind
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using System.Runtime.InteropServices;
9 using WixToolset.Extensibility;
10
11 internal class FileSystem
12 {
13 public FileSystem(IEnumerable<IFileSystemExtension> extensions)
14 {
15 this.Extensions = extensions ?? Array.Empty<IFileSystemExtension>();
16 }
17
18 private IEnumerable<IFileSystemExtension> Extensions { get; }
19
20 /// <summary>
21 /// Copies a file.
22 /// </summary>
23 /// <param name="source">The file to copy.</param>
24 /// <param name="destination">The destination file.</param>
25 /// <param name="overwrite">true if the destination file can be overwritten; otherwise, false.</param>
26 public bool CopyFile(string source, string destination, bool overwrite)
27 {
28 foreach (var extension in this.Extensions)
29 {
30 if (extension.CopyFile(source, destination, overwrite))
31 {
32 return true;
33 }
34 }
35
36 if (overwrite && File.Exists(destination))
37 {
38 File.Delete(destination);
39 }
40
41 if (!CreateHardLink(destination, source, IntPtr.Zero))
42 {
43#if DEBUG
44 int er = Marshal.GetLastWin32Error();
45#endif
46
47 File.Copy(source, destination, overwrite);
48 }
49
50 return true;
51 }
52
53 /// <summary>
54 /// Moves a file.
55 /// </summary>
56 /// <param name="source">The file to move.</param>
57 /// <param name="destination">The destination file.</param>
58 public bool MoveFile(string source, string destination, bool overwrite)
59 {
60 foreach (var extension in this.Extensions)
61 {
62 if (extension.MoveFile(source, destination, overwrite))
63 {
64 return true;
65 }
66 }
67
68 if (overwrite && File.Exists(destination))
69 {
70 File.Delete(destination);
71 }
72
73 var directory = Path.GetDirectoryName(destination);
74 if (!String.IsNullOrEmpty(directory))
75 {
76 Directory.CreateDirectory(directory);
77 }
78
79 File.Move(source, destination);
80
81 return true;
82 }
83
84 [DllImport("Kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
85 private static extern bool CreateHardLink(string lpFileName, string lpExistingFileName, IntPtr lpSecurityAttributes);
86 }
87}
diff --git a/src/WixToolset.Core/Bind/ResolveFieldsCommand.cs b/src/WixToolset.Core/Bind/ResolveFieldsCommand.cs
index e8c90956..824eb9a5 100644
--- a/src/WixToolset.Core/Bind/ResolveFieldsCommand.cs
+++ b/src/WixToolset.Core/Bind/ResolveFieldsCommand.cs
@@ -22,7 +22,7 @@ namespace WixToolset.Core.Bind
22 22
23 public IEnumerable<BindPath> BindPaths { private get; set; } 23 public IEnumerable<BindPath> BindPaths { private get; set; }
24 24
25 public IEnumerable<IBinderExtension> Extensions { private get; set; } 25 public IEnumerable<IResolverExtension> Extensions { private get; set; }
26 26
27 public ExtractEmbeddedFiles FilesWithEmbeddedFiles { private get; set; } 27 public ExtractEmbeddedFiles FilesWithEmbeddedFiles { private get; set; }
28 28
@@ -52,7 +52,6 @@ namespace WixToolset.Core.Bind
52 } 52 }
53 53
54 var isDefault = true; 54 var isDefault = true;
55 var delayedResolve = false;
56 55
57 // Check to make sure we're in a scenario where we can handle variable resolution. 56 // Check to make sure we're in a scenario where we can handle variable resolution.
58 if (null != delayedFields) 57 if (null != delayedFields)
@@ -63,16 +62,18 @@ namespace WixToolset.Core.Bind
63 var original = field.AsString(); 62 var original = field.AsString();
64 if (!String.IsNullOrEmpty(original)) 63 if (!String.IsNullOrEmpty(original))
65 { 64 {
66 var value = this.BindVariableResolver.ResolveVariables(row.SourceLineNumbers, original, false, out isDefault, out delayedResolve); 65 var resolution = this.BindVariableResolver.ResolveVariables(row.SourceLineNumbers, original, false);
67 if (original != value) 66 if (resolution.UpdatedValue)
68 { 67 {
69 field.Set(value); 68 field.Set(resolution.Value);
70 } 69 }
71 70
72 if (delayedResolve) 71 if (resolution.DelayedResolve)
73 { 72 {
74 delayedFields.Add(new DelayedField(row, field)); 73 delayedFields.Add(new DelayedField(row, field));
75 } 74 }
75
76 isDefault = resolution.IsDefault;
76 } 77 }
77 } 78 }
78 } 79 }
@@ -119,13 +120,13 @@ namespace WixToolset.Core.Bind
119#endif 120#endif
120 121
121 // resolve the path to the file 122 // resolve the path to the file
122 var value = fileResolver.ResolveFile(objectField.Path, row.Definition.Name, row.SourceLineNumbers, BindStage.Normal); 123 var value = fileResolver.ResolveFile(objectField.Path, row.Definition, row.SourceLineNumbers, BindStage.Normal);
123 field.Set(value); 124 field.Set(value);
124 } 125 }
125 else if (!fileResolver.RebaseTarget && !fileResolver.RebaseUpdated) // Normal binding for Patch Scenario (normal patch, no re-basing logic) 126 else if (!fileResolver.RebaseTarget && !fileResolver.RebaseUpdated) // Normal binding for Patch Scenario (normal patch, no re-basing logic)
126 { 127 {
127 // resolve the path to the file 128 // resolve the path to the file
128 var value = fileResolver.ResolveFile(objectField.Path, row.Definition.Name, row.SourceLineNumbers, BindStage.Normal); 129 var value = fileResolver.ResolveFile(objectField.Path, row.Definition, row.SourceLineNumbers, BindStage.Normal);
129 field.Set(value); 130 field.Set(value);
130 } 131 }
131#if REVISIT_FOR_PATCHING 132#if REVISIT_FOR_PATCHING
diff --git a/src/WixToolset.Core/Bind/ResolveResult.cs b/src/WixToolset.Core/Bind/ResolveResult.cs
deleted file mode 100644
index 13f25054..00000000
--- a/src/WixToolset.Core/Bind/ResolveResult.cs
+++ /dev/null
@@ -1,14 +0,0 @@
1// 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.
2
3namespace WixToolset.Core.Bind
4{
5 using System.Collections.Generic;
6 using WixToolset.Extensibility;
7
8 public class ResolveResult
9 {
10 public IEnumerable<IExpectedExtractFile> ExpectedEmbeddedFiles { get; set; }
11
12 public IEnumerable<IDelayedField> DelayedFields { get; set; }
13 }
14} \ No newline at end of file
diff --git a/src/WixToolset.Core/Bind/TransferFilesCommand.cs b/src/WixToolset.Core/Bind/TransferFilesCommand.cs
index 68d8b129..d4e143c3 100644
--- a/src/WixToolset.Core/Bind/TransferFilesCommand.cs
+++ b/src/WixToolset.Core/Bind/TransferFilesCommand.cs
@@ -13,15 +13,15 @@ namespace WixToolset.Core.Bind
13 13
14 internal class TransferFilesCommand 14 internal class TransferFilesCommand
15 { 15 {
16 public TransferFilesCommand(IMessaging messaging, IEnumerable<BindPath> bindPaths, IEnumerable<IBinderExtension> extensions, IEnumerable<FileTransfer> fileTransfers, bool suppressAclReset) 16 public TransferFilesCommand(IMessaging messaging, IEnumerable<IFileSystemExtension> extensions, IEnumerable<FileTransfer> fileTransfers, bool suppressAclReset)
17 { 17 {
18 this.FileResolver = new FileResolver(bindPaths, extensions); 18 this.FileSystem = new FileSystem(extensions);
19 this.Messaging = messaging; 19 this.Messaging = messaging;
20 this.FileTransfers = fileTransfers; 20 this.FileTransfers = fileTransfers;
21 this.SuppressAclReset = suppressAclReset; 21 this.SuppressAclReset = suppressAclReset;
22 } 22 }
23 23
24 private FileResolver FileResolver { get; } 24 private FileSystem FileSystem { get; }
25 25
26 private IMessaging Messaging { get; } 26 private IMessaging Messaging { get; }
27 27
@@ -35,10 +35,8 @@ namespace WixToolset.Core.Bind
35 35
36 foreach (var fileTransfer in this.FileTransfers) 36 foreach (var fileTransfer in this.FileTransfers)
37 { 37 {
38 string fileSource = this.FileResolver.ResolveFile(fileTransfer.Source, fileTransfer.Type, fileTransfer.SourceLineNumbers, BindStage.Normal);
39
40 // If the source and destination are identical, then there's nothing to do here 38 // If the source and destination are identical, then there's nothing to do here
41 if (0 == String.Compare(fileSource, fileTransfer.Destination, StringComparison.OrdinalIgnoreCase)) 39 if (0 == String.Compare(fileTransfer.Source, fileTransfer.Destination, StringComparison.OrdinalIgnoreCase))
42 { 40 {
43 fileTransfer.Redundant = true; 41 fileTransfer.Redundant = true;
44 continue; 42 continue;
@@ -51,13 +49,13 @@ namespace WixToolset.Core.Bind
51 { 49 {
52 if (fileTransfer.Move) 50 if (fileTransfer.Move)
53 { 51 {
54 this.Messaging.Write(VerboseMessages.MoveFile(fileSource, fileTransfer.Destination)); 52 this.Messaging.Write(VerboseMessages.MoveFile(fileTransfer.Source, fileTransfer.Destination));
55 this.TransferFile(true, fileSource, fileTransfer.Destination); 53 this.TransferFile(true, fileTransfer.Source, fileTransfer.Destination);
56 } 54 }
57 else 55 else
58 { 56 {
59 this.Messaging.Write(VerboseMessages.CopyFile(fileSource, fileTransfer.Destination)); 57 this.Messaging.Write(VerboseMessages.CopyFile(fileTransfer.Source, fileTransfer.Destination));
60 this.TransferFile(false, fileSource, fileTransfer.Destination); 58 this.TransferFile(false, fileTransfer.Source, fileTransfer.Destination);
61 } 59 }
62 60
63 retry = false; 61 retry = false;
@@ -183,11 +181,11 @@ namespace WixToolset.Core.Bind
183 181
184 if (move) 182 if (move)
185 { 183 {
186 complete = this.FileResolver.MoveFile(source, destination, true); 184 complete = this.FileSystem.MoveFile(source, destination, true);
187 } 185 }
188 else 186 else
189 { 187 {
190 complete = this.FileResolver.CopyFile(source, destination, true); 188 complete = this.FileSystem.CopyFile(source, destination, true);
191 } 189 }
192 190
193 if (!complete) 191 if (!complete)
diff --git a/src/WixToolset.Core/BindContext.cs b/src/WixToolset.Core/BindContext.cs
index 41d0ddf9..8bdacf75 100644
--- a/src/WixToolset.Core/BindContext.cs
+++ b/src/WixToolset.Core/BindContext.cs
@@ -35,7 +35,7 @@ namespace WixToolset.Core
35 35
36 public IExtensionManager ExtensionManager { get; set; } 36 public IExtensionManager ExtensionManager { get; set; }
37 37
38 public IEnumerable<IBinderExtension> Extensions { get; set; } 38 public IEnumerable<IFileSystemExtension> FileSystemExtensions { get; set; }
39 39
40 public IEnumerable<string> Ices { get; set; } 40 public IEnumerable<string> Ices { get; set; }
41 41
@@ -47,8 +47,6 @@ namespace WixToolset.Core
47 47
48 public string OutputPdbPath { get; set; } 48 public string OutputPdbPath { get; set; }
49 49
50 public bool SuppressAclReset { get; set; }
51
52 public IEnumerable<string> SuppressIces { get; set; } 50 public IEnumerable<string> SuppressIces { get; set; }
53 51
54 public bool SuppressValidation { get; set; } 52 public bool SuppressValidation { get; set; }
diff --git a/src/WixToolset.Core/Binder.cs b/src/WixToolset.Core/Binder.cs
index 2369b600..9db27fec 100644
--- a/src/WixToolset.Core/Binder.cs
+++ b/src/WixToolset.Core/Binder.cs
@@ -3,14 +3,11 @@
3namespace WixToolset.Core 3namespace WixToolset.Core
4{ 4{
5 using System; 5 using System;
6 using System.Collections;
7 using System.Collections.Generic; 6 using System.Collections.Generic;
8 using System.Diagnostics; 7 using System.Diagnostics;
9 using System.Globalization;
10 using System.IO; 8 using System.IO;
11 using System.Linq; 9 using System.Linq;
12 using System.Reflection; 10 using System.Reflection;
13 using WixToolset.Bind;
14 using WixToolset.Core.Bind; 11 using WixToolset.Core.Bind;
15 using WixToolset.Data; 12 using WixToolset.Data;
16 using WixToolset.Data.Bind; 13 using WixToolset.Data.Bind;
@@ -148,130 +145,72 @@ namespace WixToolset.Core
148 /// <value>The Wix variable resolver.</value> 145 /// <value>The Wix variable resolver.</value>
149 internal WixVariableResolver WixVariableResolver { get; set; } 146 internal WixVariableResolver WixVariableResolver { get; set; }
150 147
151 /// <summary> 148 public BindResult Bind(IBindContext context)
152 /// Add a binder extension.
153 /// </summary>
154 /// <param name="extension">New extension.</param>
155 //public void AddExtension(IBinderExtension extension)
156 //{
157 // this.extensions.Add(extension);
158 //}
159
160 /// <summary>
161 /// Add a file manager extension.
162 /// </summary>
163 /// <param name="extension">New file manager.</param>
164 //public void AddExtension(IBinderFileManager extension)
165 //{
166 // this.fileManagers.Add(extension);
167 //}
168
169 public bool Bind(IBindContext context)
170 { 149 {
171 this.Context = context; 150 this.Context = context;
172 151
173 //if (!String.IsNullOrEmpty(this.Context.FileManagerCore.CabCachePath))
174 //{
175 // Directory.CreateDirectory(this.Context.FileManagerCore.CabCachePath);
176 //}
177
178 //this.core = new BinderCore();
179 //this.core.FileManagerCore = this.Context.FileManagerCore;
180
181 this.WriteBuildInfoTable(this.Context.IntermediateRepresentation, this.Context.OutputPath); 152 this.WriteBuildInfoTable(this.Context.IntermediateRepresentation, this.Context.OutputPath);
182 153
183 // Prebind.
184 //
185 this.Context.Extensions = this.Context.ExtensionManager.Create<IBinderExtension>();
186
187 foreach (IBinderExtension extension in this.Context.Extensions)
188 {
189 extension.PreBind(this.Context);
190 }
191
192 // Resolve.
193 //
194 var resolveResult = this.Resolve();
195
196 this.Context.DelayedFields = resolveResult.DelayedFields;
197
198 this.Context.ExpectedEmbeddedFiles = resolveResult.ExpectedEmbeddedFiles;
199
200 // Backend.
201 //
202 var bindResult = this.BackendBind(); 154 var bindResult = this.BackendBind();
203 155 return bindResult;
204 if (bindResult != null)
205 {
206 // Postbind.
207 //
208 foreach (IBinderExtension extension in this.Context.Extensions)
209 {
210 extension.PostBind(bindResult);
211 }
212
213 // Layout.
214 //
215 this.Layout(bindResult);
216 }
217
218 return this.Context.Messaging.EncounteredError;
219 } 156 }
220 157
221 private ResolveResult Resolve() 158//// private ResolveResult Resolve()
222 { 159//// {
223 var buildingPatch = this.Context.IntermediateRepresentation.Sections.Any(s => s.Type == SectionType.Patch); 160//// var buildingPatch = this.Context.IntermediateRepresentation.Sections.Any(s => s.Type == SectionType.Patch);
224 161
225 var filesWithEmbeddedFiles = new ExtractEmbeddedFiles(); 162//// var filesWithEmbeddedFiles = new ExtractEmbeddedFiles();
226 163
227 IEnumerable<DelayedField> delayedFields; 164//// IEnumerable<DelayedField> delayedFields;
228 { 165//// {
229 var command = new ResolveFieldsCommand(); 166//// var command = new ResolveFieldsCommand();
230 command.Messaging = this.Context.Messaging; 167//// command.Messaging = this.Context.Messaging;
231 command.BuildingPatch = buildingPatch; 168//// command.BuildingPatch = buildingPatch;
232 command.BindVariableResolver = this.Context.WixVariableResolver; 169//// command.BindVariableResolver = this.Context.WixVariableResolver;
233 command.BindPaths = this.Context.BindPaths; 170//// command.BindPaths = this.Context.BindPaths;
234 command.Extensions = this.Context.Extensions; 171//// command.Extensions = this.Context.Extensions;
235 command.FilesWithEmbeddedFiles = filesWithEmbeddedFiles; 172//// command.FilesWithEmbeddedFiles = filesWithEmbeddedFiles;
236 command.IntermediateFolder = this.Context.IntermediateFolder; 173//// command.IntermediateFolder = this.Context.IntermediateFolder;
237 command.Intermediate = this.Context.IntermediateRepresentation; 174//// command.Intermediate = this.Context.IntermediateRepresentation;
238 command.SupportDelayedResolution = true; 175//// command.SupportDelayedResolution = true;
239 command.Execute(); 176//// command.Execute();
240 177
241 delayedFields = command.DelayedFields; 178//// delayedFields = command.DelayedFields;
242 } 179//// }
243 180
244#if REVISIT_FOR_PATCHING 181////#if REVISIT_FOR_PATCHING
245 if (this.Context.IntermediateRepresentation.SubStorages != null) 182//// if (this.Context.IntermediateRepresentation.SubStorages != null)
246 { 183//// {
247 foreach (SubStorage transform in this.Context.IntermediateRepresentation.SubStorages) 184//// foreach (SubStorage transform in this.Context.IntermediateRepresentation.SubStorages)
248 { 185//// {
249 var command = new ResolveFieldsCommand(); 186//// var command = new ResolveFieldsCommand();
250 command.BuildingPatch = buildingPatch; 187//// command.BuildingPatch = buildingPatch;
251 command.BindVariableResolver = this.Context.WixVariableResolver; 188//// command.BindVariableResolver = this.Context.WixVariableResolver;
252 command.BindPaths = this.Context.BindPaths; 189//// command.BindPaths = this.Context.BindPaths;
253 command.Extensions = this.Context.Extensions; 190//// command.Extensions = this.Context.Extensions;
254 command.FilesWithEmbeddedFiles = filesWithEmbeddedFiles; 191//// command.FilesWithEmbeddedFiles = filesWithEmbeddedFiles;
255 command.IntermediateFolder = this.Context.IntermediateFolder; 192//// command.IntermediateFolder = this.Context.IntermediateFolder;
256 command.Intermediate = this.Context.IntermediateRepresentation; 193//// command.Intermediate = this.Context.IntermediateRepresentation;
257 command.SupportDelayedResolution = false; 194//// command.SupportDelayedResolution = false;
258 command.Execute(); 195//// command.Execute();
259 } 196//// }
260 } 197//// }
261#endif 198////#endif
262 199
263 var expectedEmbeddedFiles = filesWithEmbeddedFiles.GetExpectedEmbeddedFiles(); 200//// var expectedEmbeddedFiles = filesWithEmbeddedFiles.GetExpectedEmbeddedFiles();
264 201
265 return new ResolveResult 202//// return new ResolveResult
266 { 203//// {
267 ExpectedEmbeddedFiles = expectedEmbeddedFiles, 204//// ExpectedEmbeddedFiles = expectedEmbeddedFiles,
268 DelayedFields = delayedFields, 205//// DelayedFields = delayedFields,
269 }; 206//// };
270 } 207//// }
271 208
272 private BindResult BackendBind() 209 private BindResult BackendBind()
273 { 210 {
274 var backendFactories = this.Context.ExtensionManager.Create<IBackendFactory>(); 211 var extensionManager = this.Context.ServiceProvider.GetService<IExtensionManager>();
212
213 var backendFactories = extensionManager.Create<IBackendFactory>();
275 214
276 var entrySection = this.Context.IntermediateRepresentation.Sections[0]; 215 var entrySection = this.Context.IntermediateRepresentation.Sections[0];
277 216
@@ -289,31 +228,6 @@ namespace WixToolset.Core
289 return null; 228 return null;
290 } 229 }
291 230
292 private void Layout(BindResult result)
293 {
294 try
295 {
296 this.LayoutMedia(result.FileTransfers);
297 }
298 finally
299 {
300 if (!String.IsNullOrEmpty(this.Context.ContentsFile) && result.ContentFilePaths != null)
301 {
302 this.CreateContentsFile(this.Context.ContentsFile, result.ContentFilePaths);
303 }
304
305 if (!String.IsNullOrEmpty(this.Context.OutputsFile) && result.FileTransfers != null)
306 {
307 this.CreateOutputsFile(this.Context.OutputsFile, result.FileTransfers, this.Context.OutputPdbPath);
308 }
309
310 if (!String.IsNullOrEmpty(this.Context.BuiltOutputsFile) && result.FileTransfers != null)
311 {
312 this.CreateBuiltOutputsFile(this.Context.BuiltOutputsFile, result.FileTransfers, this.Context.OutputPdbPath);
313 }
314 }
315 }
316
317 /// <summary> 231 /// <summary>
318 /// Binds an output. 232 /// Binds an output.
319 /// </summary> 233 /// </summary>
@@ -429,35 +343,6 @@ namespace WixToolset.Core
429#endif 343#endif
430 344
431 /// <summary> 345 /// <summary>
432 /// Does any housekeeping after Bind.
433 /// </summary>
434 /// <param name="tidy">Whether or not any actual tidying should be done.</param>
435 public void Cleanup(bool tidy)
436 {
437 if (tidy)
438 {
439 if (!this.DeleteTempFiles())
440 {
441 this.Context.Messaging.Write(WarningMessages.FailedToDeleteTempDir(this.TempFilesLocation));
442 }
443 }
444 else
445 {
446 this.Context.Messaging.Write(VerboseMessages.BinderTempDirLocatedAt(this.TempFilesLocation));
447 }
448 }
449
450 /// <summary>
451 /// Cleans up the temp files used by the Binder.
452 /// </summary>
453 /// <returns>True if all files were deleted, false otherwise.</returns>
454 private bool DeleteTempFiles()
455 {
456 bool deleted = Common.DeleteTempFiles(this.TempFilesLocation, this.Context.Messaging);
457 return deleted;
458 }
459
460 /// <summary>
461 /// Populates the WixBuildInfo table in an output. 346 /// Populates the WixBuildInfo table in an output.
462 /// </summary> 347 /// </summary>
463 /// <param name="output">The output.</param> 348 /// <param name="output">The output.</param>
@@ -585,231 +470,5 @@ namespace WixToolset.Core
585 command.Execute(); 470 command.Execute();
586 } 471 }
587#endif 472#endif
588
589 /// <summary>
590 /// Final step in binding that transfers (moves/copies) all files generated into the appropriate
591 /// location in the source image
592 /// </summary>
593 /// <param name="fileTransfers">List of files to transfer.</param>
594 private void LayoutMedia(IEnumerable<FileTransfer> transfers)
595 {
596 if (null != transfers && transfers.Any())
597 {
598 this.Context.Messaging.Write(VerboseMessages.LayingOutMedia());
599
600 var command = new TransferFilesCommand(this.Context.Messaging, this.Context.BindPaths, this.Context.Extensions, transfers, this.Context.SuppressAclReset);
601 command.Execute();
602 }
603 }
604
605 /// <summary>
606 /// Get the source path of a directory.
607 /// </summary>
608 /// <param name="directories">All cached directories.</param>
609 /// <param name="componentIdGenSeeds">Hash table of Component GUID generation seeds indexed by directory id.</param>
610 /// <param name="directory">Directory identifier.</param>
611 /// <param name="canonicalize">Canonicalize the path for standard directories.</param>
612 /// <returns>Source path of a directory.</returns>
613 public static string GetDirectoryPath(Dictionary<string, ResolvedDirectory> directories, Dictionary<string, string> componentIdGenSeeds, string directory, bool canonicalize)
614 {
615 if (!directories.ContainsKey(directory))
616 {
617 throw new WixException(ErrorMessages.ExpectedDirectory(directory));
618 }
619
620 ResolvedDirectory resolvedDirectory = (ResolvedDirectory)directories[directory];
621
622 if (null == resolvedDirectory.Path)
623 {
624 if (null != componentIdGenSeeds && componentIdGenSeeds.ContainsKey(directory))
625 {
626 resolvedDirectory.Path = (string)componentIdGenSeeds[directory];
627 }
628 else if (canonicalize && WindowsInstallerStandard.IsStandardDirectory(directory))
629 {
630 // when canonicalization is on, standard directories are treated equally
631 resolvedDirectory.Path = directory;
632 }
633 else
634 {
635 string name = resolvedDirectory.Name;
636
637 if (canonicalize && null != name)
638 {
639 name = name.ToLower(CultureInfo.InvariantCulture);
640 }
641
642 if (String.IsNullOrEmpty(resolvedDirectory.DirectoryParent))
643 {
644 resolvedDirectory.Path = name;
645 }
646 else
647 {
648 string parentPath = GetDirectoryPath(directories, componentIdGenSeeds, resolvedDirectory.DirectoryParent, canonicalize);
649
650 if (null != resolvedDirectory.Name)
651 {
652 resolvedDirectory.Path = Path.Combine(parentPath, name);
653 }
654 else
655 {
656 resolvedDirectory.Path = parentPath;
657 }
658 }
659 }
660 }
661
662 return resolvedDirectory.Path;
663 }
664
665 /// <summary>
666 /// Gets the source path of a file.
667 /// </summary>
668 /// <param name="directories">All cached directories in <see cref="ResolvedDirectory"/>.</param>
669 /// <param name="directoryId">Parent directory identifier.</param>
670 /// <param name="fileName">File name (in long|source format).</param>
671 /// <param name="compressed">Specifies the package is compressed.</param>
672 /// <param name="useLongName">Specifies the package uses long file names.</param>
673 /// <returns>Source path of file relative to package directory.</returns>
674 public static string GetFileSourcePath(Dictionary<string, ResolvedDirectory> directories, string directoryId, string fileName, bool compressed, bool useLongName)
675 {
676 string fileSourcePath = Common.GetName(fileName, true, useLongName);
677
678 if (compressed)
679 {
680 // Use just the file name of the file since all uncompressed files must appear
681 // in the root of the image in a compressed package.
682 }
683 else
684 {
685 // Get the relative path of where we want the file to be layed out as specified
686 // in the Directory table.
687 string directoryPath = Binder.GetDirectoryPath(directories, null, directoryId, false);
688 fileSourcePath = Path.Combine(directoryPath, fileSourcePath);
689 }
690
691 // Strip off "SourceDir" if it's still on there.
692 if (fileSourcePath.StartsWith("SourceDir\\", StringComparison.Ordinal))
693 {
694 fileSourcePath = fileSourcePath.Substring(10);
695 }
696
697 return fileSourcePath;
698 }
699
700 /// <summary>
701 /// Writes the paths to the content files included in the package to a text file.
702 /// </summary>
703 /// <param name="path">Path to write file.</param>
704 /// <param name="contentFilePaths">Collection of paths to content files that will be written to file.</param>
705 private void CreateContentsFile(string path, IEnumerable<string> contentFilePaths)
706 {
707 string directory = Path.GetDirectoryName(path);
708 if (!Directory.Exists(directory))
709 {
710 Directory.CreateDirectory(directory);
711 }
712
713 using (StreamWriter contents = new StreamWriter(path, false))
714 {
715 foreach (string contentPath in contentFilePaths)
716 {
717 contents.WriteLine(contentPath);
718 }
719 }
720 }
721
722 /// <summary>
723 /// Writes the paths to the content files included in the bundle to a text file.
724 /// </summary>
725 /// <param name="path">Path to write file.</param>
726 /// <param name="payloads">Collection of payloads whose source will be written to file.</param>
727 private void CreateContentsFile(string path, IEnumerable<WixBundlePayloadTuple> payloads)
728 {
729 string directory = Path.GetDirectoryName(path);
730 if (!Directory.Exists(directory))
731 {
732 Directory.CreateDirectory(directory);
733 }
734
735 using (StreamWriter contents = new StreamWriter(path, false))
736 {
737 foreach (var payload in payloads)
738 {
739 if (payload.ContentFile)
740 {
741 var fullPath = Path.GetFullPath(payload.SourceFile);
742 contents.WriteLine(fullPath);
743 }
744 }
745 }
746 }
747
748 /// <summary>
749 /// Writes the paths to the output files to a text file.
750 /// </summary>
751 /// <param name="path">Path to write file.</param>
752 /// <param name="fileTransfers">Collection of files that were transferred to the output directory.</param>
753 /// <param name="pdbPath">Optional path to created .wixpdb.</param>
754 private void CreateOutputsFile(string path, IEnumerable<FileTransfer> fileTransfers, string pdbPath)
755 {
756 string directory = Path.GetDirectoryName(path);
757 if (!Directory.Exists(directory))
758 {
759 Directory.CreateDirectory(directory);
760 }
761
762 using (StreamWriter outputs = new StreamWriter(path, false))
763 {
764 foreach (FileTransfer fileTransfer in fileTransfers)
765 {
766 // Don't list files where the source is the same as the destination since
767 // that might be the only place the file exists. The outputs file is often
768 // used to delete stuff and losing the original source would be bad.
769 if (!fileTransfer.Redundant)
770 {
771 outputs.WriteLine(fileTransfer.Destination);
772 }
773 }
774
775 if (!String.IsNullOrEmpty(pdbPath))
776 {
777 outputs.WriteLine(Path.GetFullPath(pdbPath));
778 }
779 }
780 }
781
782 /// <summary>
783 /// Writes the paths to the built output files to a text file.
784 /// </summary>
785 /// <param name="path">Path to write file.</param>
786 /// <param name="fileTransfers">Collection of files that were transferred to the output directory.</param>
787 /// <param name="pdbPath">Optional path to created .wixpdb.</param>
788 private void CreateBuiltOutputsFile(string path, IEnumerable<FileTransfer> fileTransfers, string pdbPath)
789 {
790 string directory = Path.GetDirectoryName(path);
791 if (!Directory.Exists(directory))
792 {
793 Directory.CreateDirectory(directory);
794 }
795
796 using (StreamWriter outputs = new StreamWriter(path, false))
797 {
798 foreach (FileTransfer fileTransfer in fileTransfers)
799 {
800 // Only write the built file transfers. Also, skip redundant
801 // files for the same reason spelled out in this.CreateOutputsFile().
802 if (fileTransfer.Built && !fileTransfer.Redundant)
803 {
804 outputs.WriteLine(fileTransfer.Destination);
805 }
806 }
807
808 if (!String.IsNullOrEmpty(pdbPath))
809 {
810 outputs.WriteLine(Path.GetFullPath(pdbPath));
811 }
812 }
813 }
814 } 473 }
815} 474}
diff --git a/src/WixToolset.Core/BinderFileManager.cs b/src/WixToolset.Core/BinderFileManager.cs
index 1527d93d..5e08bc40 100644
--- a/src/WixToolset.Core/BinderFileManager.cs
+++ b/src/WixToolset.Core/BinderFileManager.cs
@@ -12,7 +12,7 @@ namespace WixToolset
12 using WixToolset.Data.Rows; 12 using WixToolset.Data.Rows;
13 using WixToolset.Extensibility; 13 using WixToolset.Extensibility;
14 14
15#if false 15#if DEAD_CODE
16 /// <summary> 16 /// <summary>
17 /// Base class for creating a binder file manager. 17 /// Base class for creating a binder file manager.
18 /// </summary> 18 /// </summary>
diff --git a/src/WixToolset.Core/BinderFileManagerCore.cs b/src/WixToolset.Core/BinderFileManagerCore.cs
index e699f0ce..aa9a45de 100644
--- a/src/WixToolset.Core/BinderFileManagerCore.cs
+++ b/src/WixToolset.Core/BinderFileManagerCore.cs
@@ -9,6 +9,7 @@ namespace WixToolset
9 using WixToolset.Data.Bind; 9 using WixToolset.Data.Bind;
10 using WixToolset.Extensibility; 10 using WixToolset.Extensibility;
11 11
12#if DEAD_CODE
12 public class BinderFileManagerCore : IBinderFileManagerCore 13 public class BinderFileManagerCore : IBinderFileManagerCore
13 { 14 {
14 private Dictionary<string, List<string>>[] bindPaths; 15 private Dictionary<string, List<string>>[] bindPaths;
@@ -97,4 +98,5 @@ namespace WixToolset
97 return Enumerable.Empty<string>(); 98 return Enumerable.Empty<string>();
98 } 99 }
99 } 100 }
101#endif
100} 102}
diff --git a/src/WixToolset.Core/CommandLine/BuildCommand.cs b/src/WixToolset.Core/CommandLine/BuildCommand.cs
index 43b75f33..92aa3343 100644
--- a/src/WixToolset.Core/CommandLine/BuildCommand.cs
+++ b/src/WixToolset.Core/CommandLine/BuildCommand.cs
@@ -7,7 +7,7 @@ namespace WixToolset.Core
7 using System.IO; 7 using System.IO;
8 using System.Linq; 8 using System.Linq;
9 using WixToolset.Data; 9 using WixToolset.Data;
10 using WixToolset.Data.Tuples; 10 using WixToolset.Data.Bind;
11 using WixToolset.Extensibility; 11 using WixToolset.Extensibility;
12 using WixToolset.Extensibility.Services; 12 using WixToolset.Extensibility.Services;
13 13
@@ -90,6 +90,12 @@ namespace WixToolset.Core
90 90
91 library?.Save(this.OutputPath); 91 library?.Save(this.OutputPath);
92 } 92 }
93 else if (this.OutputType == OutputType.Wixout)
94 {
95 var output = this.LinkPhase(intermediates);
96
97 output?.Save(this.OutputPath);
98 }
93 else 99 else
94 { 100 {
95 var output = this.LinkPhase(intermediates); 101 var output = this.LinkPhase(intermediates);
@@ -147,22 +153,20 @@ namespace WixToolset.Core
147 { 153 {
148 var localizations = this.LoadLocalizationFiles().ToList(); 154 var localizations = this.LoadLocalizationFiles().ToList();
149 155
150 // If there was an error adding localization files, then bail. 156 // If there was an error loading localization files, then bail.
151 if (this.Messaging.EncounteredError) 157 if (this.Messaging.EncounteredError)
152 { 158 {
153 return null; 159 return null;
154 } 160 }
155 161
156 var resolver = CreateWixResolverWithVariables(null, null);
157
158 var context = new LibraryContext(); 162 var context = new LibraryContext();
163 context.Messaging = this.Messaging;
159 context.BindFiles = this.BindFiles; 164 context.BindFiles = this.BindFiles;
160 context.BindPaths = this.BindPaths; 165 context.BindPaths = this.BindPaths;
161 context.Extensions = this.ExtensionManager.Create<ILibrarianExtension>(); 166 context.Extensions = this.ExtensionManager.Create<ILibrarianExtension>();
162 context.Localizations = localizations; 167 context.Localizations = localizations;
163 context.LibraryId = Guid.NewGuid().ToString("N"); 168 context.LibraryId = Guid.NewGuid().ToString("N");
164 context.Intermediates = intermediates; 169 context.Intermediates = intermediates;
165 context.WixVariableResolver = resolver;
166 170
167 var librarian = new Librarian(); 171 var librarian = new Librarian();
168 return librarian.Combine(context); 172 return librarian.Combine(context);
@@ -174,6 +178,11 @@ namespace WixToolset.Core
174 178
175 var libraries = this.LoadLibraries(creator); 179 var libraries = this.LoadLibraries(creator);
176 180
181 if (this.Messaging.EncounteredError)
182 {
183 return null;
184 }
185
177 var context = this.ServiceProvider.GetService<ILinkContext>(); 186 var context = this.ServiceProvider.GetService<ILinkContext>();
178 context.Messaging = this.Messaging; 187 context.Messaging = this.Messaging;
179 context.Extensions = this.ExtensionManager.Create<ILinkerExtension>(); 188 context.Extensions = this.ExtensionManager.Create<ILinkerExtension>();
@@ -183,48 +192,72 @@ namespace WixToolset.Core
183 context.TupleDefinitionCreator = creator; 192 context.TupleDefinitionCreator = creator;
184 193
185 var linker = new Linker(); 194 var linker = new Linker();
186 var output = linker.Link(context); 195 return linker.Link(context);
187 return output;
188 } 196 }
189 197
190 private void BindPhase(Intermediate output) 198 private void BindPhase(Intermediate output)
191 { 199 {
192 var localizations = this.LoadLocalizationFiles().ToList(); 200 var localizations = this.LoadLocalizationFiles().ToList();
193 201
194 var localizer = new Localizer(this.Messaging, localizations); 202 // If there was an error loading localization files, then bail.
203 if (this.Messaging.EncounteredError)
204 {
205 return;
206 }
195 207
196 var resolver = CreateWixResolverWithVariables(localizer, output); 208 ResolveResult resolveResult;
209 {
210 var resolver = new Resolver(this.ServiceProvider, this.BindPaths, output, this.IntermediateFolder, localizations);
211 resolveResult = resolver.Execute();
212 }
197 213
198 var intermediateFolder = this.IntermediateFolder; 214 if (this.Messaging.EncounteredError)
199 if (String.IsNullOrEmpty(intermediateFolder))
200 { 215 {
201 intermediateFolder = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); 216 return;
202 } 217 }
203 218
204 var context = this.ServiceProvider.GetService<IBindContext>(); 219 BindResult bindResult;
205 context.Messaging = this.Messaging; 220 {
206 context.ExtensionManager = this.ExtensionManager; 221 var intermediateFolder = this.IntermediateFolder;
207 context.BindPaths = this.BindPaths ?? Array.Empty<BindPath>(); 222 if (String.IsNullOrEmpty(intermediateFolder))
208 //context.CabbingThreadCount = this.CabbingThreadCount; 223 {
209 context.CabCachePath = this.CabCachePath; 224 intermediateFolder = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
210 context.Codepage = localizer.Codepage; 225 }
211 //context.DefaultCompressionLevel = this.DefaultCompressionLevel; 226
212 //context.Ices = this.Ices; 227 var context = this.ServiceProvider.GetService<IBindContext>();
213 context.IntermediateFolder = intermediateFolder; 228 context.Messaging = this.Messaging;
214 context.IntermediateRepresentation = output; 229 //context.CabbingThreadCount = this.CabbingThreadCount;
215 context.OutputPath = this.OutputPath; 230 context.CabCachePath = this.CabCachePath;
216 context.OutputPdbPath = Path.ChangeExtension(this.OutputPath, ".wixpdb"); 231 context.Codepage = resolveResult.Codepage;
217 //context.SuppressIces = this.SuppressIces; 232 //context.DefaultCompressionLevel = this.DefaultCompressionLevel;
218 context.SuppressValidation = true; 233 context.DelayedFields = resolveResult.DelayedFields;
219 //context.SuppressValidation = this.SuppressValidation; 234 context.ExpectedEmbeddedFiles = resolveResult.ExpectedEmbeddedFiles;
220 context.WixVariableResolver = resolver; 235 //context.Ices = this.Ices;
221 context.ContentsFile = this.ContentsFile; 236 context.IntermediateFolder = intermediateFolder;
222 context.OutputsFile = this.OutputsFile; 237 context.IntermediateRepresentation = resolveResult.IntermediateRepresentation;
223 context.BuiltOutputsFile = this.BuiltOutputsFile; 238 context.OutputPath = this.OutputPath;
224 context.WixprojectFile = this.WixProjectFile; 239 context.OutputPdbPath = Path.ChangeExtension(this.OutputPath, ".wixpdb");
225 240 //context.SuppressIces = this.SuppressIces;
226 var binder = new Binder(); 241 context.SuppressValidation = true; // TODO: set this correctly
227 binder.Bind(context); 242 context.ContentsFile = this.ContentsFile;
243 context.OutputsFile = this.OutputsFile;
244 context.BuiltOutputsFile = this.BuiltOutputsFile;
245 context.WixprojectFile = this.WixProjectFile;
246
247 var binder = new Binder();
248 bindResult = binder.Bind(context);
249 }
250
251 if (this.Messaging.EncounteredError)
252 {
253 return;
254 }
255
256 {
257 // TODO: correctly set SuppressAclReset bool at the end.
258 var layout = new Layout(this.ServiceProvider, bindResult.FileTransfers, bindResult.ContentFilePaths, this.ContentsFile, this.OutputsFile, this.BuiltOutputsFile, false);
259 layout.Execute();
260 }
228 } 261 }
229 262
230 private IEnumerable<Intermediate> LoadLibraries(ITupleDefinitionCreator creator) 263 private IEnumerable<Intermediate> LoadLibraries(ITupleDefinitionCreator creator)
@@ -264,22 +297,5 @@ namespace WixToolset.Core
264 yield return localization; 297 yield return localization;
265 } 298 }
266 } 299 }
267
268 private WixVariableResolver CreateWixResolverWithVariables(Localizer localizer, Intermediate output)
269 {
270 var resolver = new WixVariableResolver(this.Messaging, localizer);
271
272 // Gather all the wix variables.
273 var wixVariables = output?.Sections.SelectMany(s => s.Tuples).OfType<WixVariableTuple>();
274 if (wixVariables != null)
275 {
276 foreach (var wixVariableRow in wixVariables)
277 {
278 resolver.AddVariable(wixVariableRow);
279 }
280 }
281
282 return resolver;
283 }
284 } 300 }
285} 301}
diff --git a/src/WixToolset.Core/CommandLine/CommandLine.cs b/src/WixToolset.Core/CommandLine/CommandLine.cs
index 9db1b2f9..97f79755 100644
--- a/src/WixToolset.Core/CommandLine/CommandLine.cs
+++ b/src/WixToolset.Core/CommandLine/CommandLine.cs
@@ -260,6 +260,10 @@ namespace WixToolset.Core
260 case "transform": 260 case "transform":
261 case ".mst": 261 case ".mst":
262 return OutputType.Transform; 262 return OutputType.Transform;
263
264 case "wixout":
265 case ".wixout":
266 return OutputType.Wixout;
263 } 267 }
264 268
265 return OutputType.Unknown; 269 return OutputType.Unknown;
diff --git a/src/WixToolset.Core/Compiler.cs b/src/WixToolset.Core/Compiler.cs
index 74ca86ac..2dd7da4d 100644
--- a/src/WixToolset.Core/Compiler.cs
+++ b/src/WixToolset.Core/Compiler.cs
@@ -154,24 +154,40 @@ namespace WixToolset.Core
154 } 154 }
155 155
156 // Resolve any Component Id placeholders compiled into the intermediate. 156 // Resolve any Component Id placeholders compiled into the intermediate.
157 if (0 < this.componentIdPlaceholdersResolver.VariableCount) 157 this.ResolveComponentIdPlaceholders(target);
158 }
159 finally
160 {
161 foreach (var extension in context.Extensions)
158 { 162 {
159 foreach (var section in target.Sections) 163 extension.PostCompile(target);
164 }
165
166 this.Core = null;
167 }
168
169 return this.Context.Messaging.EncounteredError ? null : target;
170 }
171
172 private void ResolveComponentIdPlaceholders(Intermediate target)
173 {
174 if (0 < this.componentIdPlaceholdersResolver.VariableCount)
175 {
176 foreach (var section in target.Sections)
177 {
178 foreach (var tuple in section.Tuples)
160 { 179 {
161 foreach (var tuple in section.Tuples) 180 foreach (var field in tuple.Fields)
162 { 181 {
163 foreach (var field in tuple.Fields) 182 if (field?.Type == IntermediateFieldType.String)
164 { 183 {
165 if (field != null && field.Type == IntermediateFieldType.String) 184 var data = field.AsString();
185 if (!String.IsNullOrEmpty(data))
166 { 186 {
167 var data = field.AsString(); 187 var resolved = this.componentIdPlaceholdersResolver.ResolveVariables(tuple.SourceLineNumbers, data, false, false);
168 if (!String.IsNullOrEmpty(data)) 188 if (resolved.UpdatedValue)
169 { 189 {
170 var resolved = this.componentIdPlaceholdersResolver.ResolveVariables(tuple.SourceLineNumbers, data, false, false, out var defaultIgnored, out var delayedIgnored); 190 field.Set(resolved.Value);
171 if (data != resolved)
172 {
173 field.Set(resolved);
174 }
175 } 191 }
176 } 192 }
177 } 193 }
@@ -179,17 +195,6 @@ namespace WixToolset.Core
179 } 195 }
180 } 196 }
181 } 197 }
182 finally
183 {
184 foreach (var extension in context.Extensions)
185 {
186 extension.PostCompile(target);
187 }
188
189 this.Core = null;
190 }
191
192 return this.Context.Messaging.EncounteredError ? null : target;
193 } 198 }
194 199
195 /// <summary> 200 /// <summary>
@@ -2422,7 +2427,7 @@ namespace WixToolset.Core
2422 { 2427 {
2423 if (isGeneratableGuidOk || keyFound && !String.IsNullOrEmpty(keyPath)) 2428 if (isGeneratableGuidOk || keyFound && !String.IsNullOrEmpty(keyPath))
2424 { 2429 {
2425 this.componentIdPlaceholdersResolver.AddVariable(componentIdPlaceholder, keyPath); 2430 this.componentIdPlaceholdersResolver.AddVariable(componentIdPlaceholder, keyPath, false);
2426 2431
2427 id = new Identifier(keyPath, AccessModifier.Private); 2432 id = new Identifier(keyPath, AccessModifier.Private);
2428 } 2433 }
diff --git a/src/WixToolset.Core/Layout.cs b/src/WixToolset.Core/Layout.cs
new file mode 100644
index 00000000..d7322a12
--- /dev/null
+++ b/src/WixToolset.Core/Layout.cs
@@ -0,0 +1,188 @@
1// 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.
2
3namespace WixToolset.Core
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using System.Linq;
9 using WixToolset.Core.Bind;
10 using WixToolset.Data;
11 using WixToolset.Data.Bind;
12 using WixToolset.Data.Tuples;
13 using WixToolset.Extensibility;
14 using WixToolset.Extensibility.Services;
15
16 /// <summary>
17 /// Layout for the WiX toolset.
18 /// </summary>
19 public sealed class Layout
20 {
21 public Layout(IServiceProvider serviceProvider, IEnumerable<FileTransfer> fileTransfers, IEnumerable<string> contentFilePaths, string contentsFile, string outputsFile, string builtOutputsFile, bool suppressAclReset)
22 {
23 this.ServiceProvider = serviceProvider;
24 this.FileTransfers = fileTransfers;
25 this.ContentFilePaths = contentFilePaths;
26 this.ContentsFile = contentsFile;
27 this.OutputsFile = outputsFile;
28 this.BuiltOutputsFile = builtOutputsFile;
29 this.SuppressAclReset = suppressAclReset;
30 this.Messaging = this.ServiceProvider.GetService<IMessaging>();
31 }
32
33 private IServiceProvider ServiceProvider { get; }
34
35 private IEnumerable<FileTransfer> FileTransfers { get; }
36
37 private IEnumerable<string> ContentFilePaths { get; }
38
39 private string ContentsFile { get; }
40
41 private string OutputsFile { get; }
42
43 private string BuiltOutputsFile { get; }
44
45 private bool SuppressAclReset { get; }
46
47 private IMessaging Messaging { get; }
48
49 public void Execute()
50 {
51 var extensionManager = this.ServiceProvider.GetService<IExtensionManager>();
52
53 var context = this.ServiceProvider.GetService<ILayoutContext>();
54 context.Messaging = this.Messaging;
55 context.Extensions = extensionManager.Create<ILayoutExtension>();
56 context.FileSystemExtensions = extensionManager.Create<IFileSystemExtension>();
57 context.FileTransfers = this.FileTransfers;
58 context.ContentFilePaths = this.ContentFilePaths;
59 context.ContentsFile = this.ContentsFile;
60 context.OutputPdbPath = this.OutputsFile;
61 context.BuiltOutputsFile = this.BuiltOutputsFile;
62 context.SuppressAclReset = this.SuppressAclReset;
63
64 // Pre-layout.
65 //
66 foreach (var extension in context.Extensions)
67 {
68 extension.PreLayout(context);
69 }
70
71 try
72 {
73 // Final step in binding that transfers (moves/copies) all files generated into the appropriate
74 // location in the source image.
75 if (context.FileTransfers?.Any() == true)
76 {
77 this.Messaging.Write(VerboseMessages.LayingOutMedia());
78
79 var command = new TransferFilesCommand(context.Messaging, context.FileSystemExtensions, context.FileTransfers, context.SuppressAclReset);
80 command.Execute();
81 }
82 }
83 finally
84 {
85 if (!String.IsNullOrEmpty(context.ContentsFile) && context.ContentFilePaths != null)
86 {
87 this.CreateContentsFile(context.ContentsFile, context.ContentFilePaths);
88 }
89
90 if (!String.IsNullOrEmpty(context.OutputsFile) && context.FileTransfers != null)
91 {
92 this.CreateOutputsFile(context.OutputsFile, context.FileTransfers, context.OutputPdbPath);
93 }
94
95 if (!String.IsNullOrEmpty(context.BuiltOutputsFile) && context.FileTransfers != null)
96 {
97 this.CreateBuiltOutputsFile(context.BuiltOutputsFile, context.FileTransfers, context.OutputPdbPath);
98 }
99 }
100
101 // Post-layout.
102 foreach (var extension in context.Extensions)
103 {
104 extension.PostLayout();
105 }
106 }
107
108 /// <summary>
109 /// Writes the paths to the content files to a text file.
110 /// </summary>
111 /// <param name="path">Path to write file.</param>
112 /// <param name="contentFilePaths">Collection of paths to content files that will be written to file.</param>
113 private void CreateContentsFile(string path, IEnumerable<string> contentFilePaths)
114 {
115 var directory = Path.GetDirectoryName(path);
116 Directory.CreateDirectory(directory);
117
118 using (var contents = new StreamWriter(path, false))
119 {
120 foreach (string contentPath in contentFilePaths)
121 {
122 contents.WriteLine(contentPath);
123 }
124 }
125 }
126
127 /// <summary>
128 /// Writes the paths to the output files to a text file.
129 /// </summary>
130 /// <param name="path">Path to write file.</param>
131 /// <param name="fileTransfers">Collection of files that were transferred to the output directory.</param>
132 /// <param name="pdbPath">Optional path to created .wixpdb.</param>
133 private void CreateOutputsFile(string path, IEnumerable<FileTransfer> fileTransfers, string pdbPath)
134 {
135 var directory = Path.GetDirectoryName(path);
136 Directory.CreateDirectory(directory);
137
138 using (var outputs = new StreamWriter(path, false))
139 {
140 foreach (FileTransfer fileTransfer in fileTransfers)
141 {
142 // Don't list files where the source is the same as the destination since
143 // that might be the only place the file exists. The outputs file is often
144 // used to delete stuff and losing the original source would be bad.
145 if (!fileTransfer.Redundant)
146 {
147 outputs.WriteLine(fileTransfer.Destination);
148 }
149 }
150
151 if (!String.IsNullOrEmpty(pdbPath))
152 {
153 outputs.WriteLine(Path.GetFullPath(pdbPath));
154 }
155 }
156 }
157
158 /// <summary>
159 /// Writes the paths to the built output files to a text file.
160 /// </summary>
161 /// <param name="path">Path to write file.</param>
162 /// <param name="fileTransfers">Collection of files that were transferred to the output directory.</param>
163 /// <param name="pdbPath">Optional path to created .wixpdb.</param>
164 private void CreateBuiltOutputsFile(string path, IEnumerable<FileTransfer> fileTransfers, string pdbPath)
165 {
166 var directory = Path.GetDirectoryName(path);
167 Directory.CreateDirectory(directory);
168
169 using (var outputs = new StreamWriter(path, false))
170 {
171 foreach (FileTransfer fileTransfer in fileTransfers)
172 {
173 // Only write the built file transfers. Also, skip redundant
174 // files for the same reason spelled out in this.CreateOutputsFile().
175 if (fileTransfer.Built && !fileTransfer.Redundant)
176 {
177 outputs.WriteLine(fileTransfer.Destination);
178 }
179 }
180
181 if (!String.IsNullOrEmpty(pdbPath))
182 {
183 outputs.WriteLine(Path.GetFullPath(pdbPath));
184 }
185 }
186 }
187 }
188}
diff --git a/src/WixToolset.Core/LayoutContext.cs b/src/WixToolset.Core/LayoutContext.cs
new file mode 100644
index 00000000..24f171a9
--- /dev/null
+++ b/src/WixToolset.Core/LayoutContext.cs
@@ -0,0 +1,40 @@
1// 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.
2
3namespace WixToolset.Core
4{
5 using System;
6 using System.Collections.Generic;
7 using WixToolset.Data.Bind;
8 using WixToolset.Extensibility;
9 using WixToolset.Extensibility.Services;
10
11 public class LayoutContext : ILayoutContext
12 {
13 internal LayoutContext(IServiceProvider serviceProvider)
14 {
15 this.ServiceProvider = serviceProvider;
16 }
17
18 public IServiceProvider ServiceProvider { get; }
19
20 public IMessaging Messaging { get; set; }
21
22 public IEnumerable<ILayoutExtension> Extensions { get; set; }
23
24 public IEnumerable<IFileSystemExtension> FileSystemExtensions { get; set; }
25
26 public IEnumerable<FileTransfer> FileTransfers { get; set; }
27
28 public IEnumerable<string> ContentFilePaths { get; set; }
29
30 public string OutputPdbPath { get; set; }
31
32 public string ContentsFile { get; set; }
33
34 public string OutputsFile { get; set; }
35
36 public string BuiltOutputsFile { get; set; }
37
38 public bool SuppressAclReset { get; set; }
39 }
40}
diff --git a/src/WixToolset.Core/Librarian.cs b/src/WixToolset.Core/Librarian.cs
index a64d77dd..2efb0f9b 100644
--- a/src/WixToolset.Core/Librarian.cs
+++ b/src/WixToolset.Core/Librarian.cs
@@ -38,11 +38,14 @@ namespace WixToolset.Core
38 38
39 var sections = this.Context.Intermediates.SelectMany(i => i.Sections).ToList(); 39 var sections = this.Context.Intermediates.SelectMany(i => i.Sections).ToList();
40 40
41 var fileResolver = new FileResolver(this.Context.BindPaths, this.Context.Extensions); 41 var embedFilePaths = this.ResolveFilePathsToEmbed(sections);
42 42
43 var embedFilePaths = ResolveFilePathsToEmbed(sections, fileResolver); 43 var localizationsByCulture = this.CollateLocalizations(this.Context.Localizations);
44 44
45 var localizationsByCulture = CollateLocalizations(this.Context.Localizations); 45 if (this.Context.Messaging.EncounteredError)
46 {
47 return null;
48 }
46 49
47 foreach (var section in sections) 50 foreach (var section in sections)
48 { 51 {
@@ -83,7 +86,7 @@ namespace WixToolset.Core
83 return (this.Context.Messaging.EncounteredError ? null : library); 86 return (this.Context.Messaging.EncounteredError ? null : library);
84 } 87 }
85 88
86 private static Dictionary<string, Localization> CollateLocalizations(IEnumerable<Localization> localizations) 89 private Dictionary<string, Localization> CollateLocalizations(IEnumerable<Localization> localizations)
87 { 90 {
88 var localizationsByCulture = new Dictionary<string, Localization>(StringComparer.OrdinalIgnoreCase); 91 var localizationsByCulture = new Dictionary<string, Localization>(StringComparer.OrdinalIgnoreCase);
89 92
@@ -91,7 +94,14 @@ namespace WixToolset.Core
91 { 94 {
92 if (localizationsByCulture.TryGetValue(localization.Culture, out var existingCulture)) 95 if (localizationsByCulture.TryGetValue(localization.Culture, out var existingCulture))
93 { 96 {
94 existingCulture.Merge(localization); 97 try
98 {
99 existingCulture.Merge(localization);
100 }
101 catch (WixException e)
102 {
103 this.Context.Messaging.Write(e.Error);
104 }
95 } 105 }
96 else 106 else
97 { 107 {
@@ -102,13 +112,17 @@ namespace WixToolset.Core
102 return localizationsByCulture; 112 return localizationsByCulture;
103 } 113 }
104 114
105 private List<string> ResolveFilePathsToEmbed(IEnumerable<IntermediateSection> sections, FileResolver fileResolver) 115 private List<string> ResolveFilePathsToEmbed(IEnumerable<IntermediateSection> sections)
106 { 116 {
107 var embedFilePaths = new List<string>(); 117 var embedFilePaths = new List<string>();
108 118
109 // Resolve paths to files that are to be embedded in the library. 119 // Resolve paths to files that are to be embedded in the library.
110 if (this.Context.BindFiles) 120 if (this.Context.BindFiles)
111 { 121 {
122 var variableResolver = new WixVariableResolver(this.Context.Messaging);
123
124 var fileResolver = new FileResolver(this.Context.BindPaths, this.Context.Extensions);
125
112 foreach (var tuple in sections.SelectMany(s => s.Tuples)) 126 foreach (var tuple in sections.SelectMany(s => s.Tuples))
113 { 127 {
114 foreach (var field in tuple.Fields.Where(f => f.Type == IntermediateFieldType.Path)) 128 foreach (var field in tuple.Fields.Where(f => f.Type == IntermediateFieldType.Path))
@@ -117,9 +131,9 @@ namespace WixToolset.Core
117 131
118 if (pathField != null) 132 if (pathField != null)
119 { 133 {
120 var resolvedPath = this.Context.WixVariableResolver.ResolveVariables(tuple.SourceLineNumbers, pathField.Path, false); 134 var resolution = variableResolver.ResolveVariables(tuple.SourceLineNumbers, pathField.Path, false);
121 135
122 var file = fileResolver.Resolve(tuple.SourceLineNumbers, tuple.Definition.Name, resolvedPath); 136 var file = fileResolver.Resolve(tuple.SourceLineNumbers, tuple.Definition, resolution.Value);
123 137
124 if (!String.IsNullOrEmpty(file)) 138 if (!String.IsNullOrEmpty(file))
125 { 139 {
diff --git a/src/WixToolset.Core/LibraryContext.cs b/src/WixToolset.Core/LibraryContext.cs
index c6278482..c1aec5ce 100644
--- a/src/WixToolset.Core/LibraryContext.cs
+++ b/src/WixToolset.Core/LibraryContext.cs
@@ -25,7 +25,5 @@ namespace WixToolset.Core
25 public IEnumerable<Localization> Localizations { get; set; } 25 public IEnumerable<Localization> Localizations { get; set; }
26 26
27 public IEnumerable<Intermediate> Intermediates { get; set; } 27 public IEnumerable<Intermediate> Intermediates { get; set; }
28
29 public IBindVariableResolver WixVariableResolver { get; set; }
30 } 28 }
31} 29}
diff --git a/src/WixToolset.Core/Linker.cs b/src/WixToolset.Core/Linker.cs
index d980d79f..93f50efa 100644
--- a/src/WixToolset.Core/Linker.cs
+++ b/src/WixToolset.Core/Linker.cs
@@ -8,11 +8,11 @@ namespace WixToolset.Core
8 using System.Diagnostics; 8 using System.Diagnostics;
9 using System.Globalization; 9 using System.Globalization;
10 using System.Linq; 10 using System.Linq;
11 using WixToolset.Data;
12 using WixToolset.Link;
13 using WixToolset.Core.Link; 11 using WixToolset.Core.Link;
12 using WixToolset.Data;
14 using WixToolset.Data.Tuples; 13 using WixToolset.Data.Tuples;
15 using WixToolset.Extensibility.Services; 14 using WixToolset.Extensibility;
15 using WixToolset.Link;
16 16
17 /// <summary> 17 /// <summary>
18 /// Linker core of the WiX toolset. 18 /// Linker core of the WiX toolset.
diff --git a/src/WixToolset.Core/PreprocessContext.cs b/src/WixToolset.Core/PreprocessContext.cs
index cb29d1ff..a6085b81 100644
--- a/src/WixToolset.Core/PreprocessContext.cs
+++ b/src/WixToolset.Core/PreprocessContext.cs
@@ -8,9 +8,6 @@ namespace WixToolset.Core
8 using WixToolset.Extensibility; 8 using WixToolset.Extensibility;
9 using WixToolset.Extensibility.Services; 9 using WixToolset.Extensibility.Services;
10 10
11 /// <summary>
12 /// The preprocessor core.
13 /// </summary>
14 internal class PreprocessContext : IPreprocessContext 11 internal class PreprocessContext : IPreprocessContext
15 { 12 {
16 internal PreprocessContext(IServiceProvider serviceProvider) 13 internal PreprocessContext(IServiceProvider serviceProvider)
diff --git a/src/WixToolset.Core/ResolveContext.cs b/src/WixToolset.Core/ResolveContext.cs
new file mode 100644
index 00000000..6d7b9df1
--- /dev/null
+++ b/src/WixToolset.Core/ResolveContext.cs
@@ -0,0 +1,32 @@
1// 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.
2
3namespace WixToolset.Core
4{
5 using System;
6 using System.Collections.Generic;
7 using WixToolset.Data;
8 using WixToolset.Extensibility;
9 using WixToolset.Extensibility.Services;
10
11 public class ResolveContext : IResolveContext
12 {
13 internal ResolveContext(IServiceProvider serviceProvider)
14 {
15 this.ServiceProvider = serviceProvider;
16 }
17
18 public IServiceProvider ServiceProvider { get; }
19
20 public IMessaging Messaging { get; set; }
21
22 public IEnumerable<BindPath> BindPaths { get; set; }
23
24 public IEnumerable<IResolverExtension> Extensions { get; set; }
25
26 public string IntermediateFolder { get; set; }
27
28 public Intermediate IntermediateRepresentation { get; set; }
29
30 public IBindVariableResolver WixVariableResolver { get; set; }
31 }
32}
diff --git a/src/WixToolset.Core/Resolver.cs b/src/WixToolset.Core/Resolver.cs
new file mode 100644
index 00000000..b0d3a189
--- /dev/null
+++ b/src/WixToolset.Core/Resolver.cs
@@ -0,0 +1,234 @@
1// 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.
2
3namespace WixToolset.Core
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Linq;
8 using WixToolset.Core.Bind;
9 using WixToolset.Data;
10 using WixToolset.Data.Tuples;
11 using WixToolset.Extensibility;
12 using WixToolset.Extensibility.Services;
13
14 /// <summary>
15 /// Resolver for the WiX toolset.
16 /// </summary>
17 public sealed class Resolver
18 {
19 public Resolver(IServiceProvider serviceProvider, IEnumerable<BindPath> bindPaths, Intermediate intermediateRepresentation, string intermediateFolder, IEnumerable<Localization> localizations)
20 {
21 this.ServiceProvider = serviceProvider;
22 this.BindPaths = bindPaths;
23 this.IntermediateRepresentation = intermediateRepresentation;
24 this.IntermediateFolder = intermediateFolder;
25 this.Localizations = localizations;
26
27 this.Messaging = this.ServiceProvider.GetService<IMessaging>();
28 }
29
30 private IServiceProvider ServiceProvider { get; }
31
32 private IEnumerable<BindPath> BindPaths { get; }
33
34 private Intermediate IntermediateRepresentation { get; }
35
36 private string IntermediateFolder { get; }
37
38 private IEnumerable<Localization> Localizations { get; }
39
40 private IMessaging Messaging { get; }
41
42 public ResolveResult Execute()
43 {
44 var localizer = new Localizer(this.Messaging, this.Localizations);
45
46 var variableResolver = new WixVariableResolver(this.Messaging, localizer);
47
48 var context = this.ServiceProvider.GetService<IResolveContext>();
49 context.Messaging = this.Messaging;
50 context.BindPaths = this.BindPaths;
51 context.Extensions = this.ServiceProvider.GetService<IExtensionManager>().Create<IResolverExtension>();
52 context.IntermediateFolder = this.IntermediateFolder;
53 context.IntermediateRepresentation = this.IntermediateRepresentation;
54 context.WixVariableResolver = this.PopulateVariableResolver(variableResolver);
55
56 // Preresolve.
57 //
58 foreach (IResolverExtension extension in context.Extensions)
59 {
60 extension.PreResolve(context);
61 }
62
63 // Resolve.
64 //
65 this.LocalizeUI(context);
66
67 var resolveResult = this.Resolve(localizer.Codepage, context);
68
69 if (resolveResult != null)
70 {
71 // Postresolve.
72 //
73 foreach (IResolverExtension extension in context.Extensions)
74 {
75 extension.PostResolve(resolveResult);
76 }
77 }
78
79 return resolveResult;
80 }
81
82 private ResolveResult Resolve(int codepage, IResolveContext context)
83 {
84 var buildingPatch = context.IntermediateRepresentation.Sections.Any(s => s.Type == SectionType.Patch);
85
86 var filesWithEmbeddedFiles = new ExtractEmbeddedFiles();
87
88 IEnumerable<DelayedField> delayedFields;
89 {
90 var command = new ResolveFieldsCommand();
91 command.Messaging = context.Messaging;
92 command.BuildingPatch = buildingPatch;
93 command.BindVariableResolver = context.WixVariableResolver;
94 command.BindPaths = context.BindPaths;
95 command.Extensions = context.Extensions;
96 command.FilesWithEmbeddedFiles = filesWithEmbeddedFiles;
97 command.IntermediateFolder = context.IntermediateFolder;
98 command.Intermediate = context.IntermediateRepresentation;
99 command.SupportDelayedResolution = true;
100 command.Execute();
101
102 delayedFields = command.DelayedFields;
103 }
104
105#if REVISIT_FOR_PATCHING
106 if (context.IntermediateRepresentation.SubStorages != null)
107 {
108 foreach (SubStorage transform in context.IntermediateRepresentation.SubStorages)
109 {
110 var command = new ResolveFieldsCommand();
111 command.BuildingPatch = buildingPatch;
112 command.BindVariableResolver = context.WixVariableResolver;
113 command.BindPaths = context.BindPaths;
114 command.Extensions = context.Extensions;
115 command.FilesWithEmbeddedFiles = filesWithEmbeddedFiles;
116 command.IntermediateFolder = context.IntermediateFolder;
117 command.Intermediate = context.IntermediateRepresentation;
118 command.SupportDelayedResolution = false;
119 command.Execute();
120 }
121 }
122#endif
123
124 var expectedEmbeddedFiles = filesWithEmbeddedFiles.GetExpectedEmbeddedFiles();
125
126 return new ResolveResult
127 {
128 Codepage = codepage,
129 ExpectedEmbeddedFiles = expectedEmbeddedFiles,
130 DelayedFields = delayedFields,
131 IntermediateRepresentation = context.IntermediateRepresentation
132 };
133 }
134
135 /// <summary>
136 /// Localize dialogs and controls.
137 /// </summary>
138 private void LocalizeUI(IResolveContext context)
139 {
140 foreach (var section in context.IntermediateRepresentation.Sections)
141 {
142 foreach (var row in section.Tuples.OfType<DialogTuple>())
143 {
144 string dialog = row.Dialog;
145
146 if (context.WixVariableResolver.TryGetLocalizedControl(dialog, null, out LocalizedControl localizedControl))
147 {
148 if (CompilerConstants.IntegerNotSet != localizedControl.X)
149 {
150 row.HCentering = localizedControl.X;
151 }
152
153 if (CompilerConstants.IntegerNotSet != localizedControl.Y)
154 {
155 row.VCentering = localizedControl.Y;
156 }
157
158 if (CompilerConstants.IntegerNotSet != localizedControl.Width)
159 {
160 row.Width = localizedControl.Width;
161 }
162
163 if (CompilerConstants.IntegerNotSet != localizedControl.Height)
164 {
165 row.Height = localizedControl.Height;
166 }
167
168 row.Attributes = row.Attributes | localizedControl.Attributes;
169
170 if (!String.IsNullOrEmpty(localizedControl.Text))
171 {
172 row.Title = localizedControl.Text;
173 }
174 }
175 }
176
177 foreach (var row in section.Tuples.OfType<ControlTuple>())
178 {
179 string dialog = row.Dialog_;
180 string control = row.Control;
181
182 if (context.WixVariableResolver.TryGetLocalizedControl(dialog, control, out LocalizedControl localizedControl))
183 {
184 if (CompilerConstants.IntegerNotSet != localizedControl.X)
185 {
186 row.X = localizedControl.X;
187 }
188
189 if (CompilerConstants.IntegerNotSet != localizedControl.Y)
190 {
191 row.Y = localizedControl.Y;
192 }
193
194 if (CompilerConstants.IntegerNotSet != localizedControl.Width)
195 {
196 row.Width = localizedControl.Width;
197 }
198
199 if (CompilerConstants.IntegerNotSet != localizedControl.Height)
200 {
201 row.Height = localizedControl.Height;
202 }
203
204 row.Attributes = row.Attributes | localizedControl.Attributes;
205
206 if (!String.IsNullOrEmpty(localizedControl.Text))
207 {
208 row.Text = localizedControl.Text;
209 }
210 }
211 }
212 }
213 }
214
215 private WixVariableResolver PopulateVariableResolver(WixVariableResolver resolver)
216 {
217 // Gather all the wix variables.
218 var wixVariableTuples = this.IntermediateRepresentation.Sections.SelectMany(s => s.Tuples).OfType<WixVariableTuple>();
219 foreach (var tuple in wixVariableTuples)
220 {
221 try
222 {
223 resolver.AddVariable(tuple.WixVariable, tuple.Value, tuple.Overridable);
224 }
225 catch (ArgumentException)
226 {
227 this.Messaging.Write(ErrorMessages.WixVariableCollision(tuple.SourceLineNumbers, tuple.WixVariable));
228 }
229 }
230
231 return resolver;
232 }
233 }
234}
diff --git a/src/WixToolset.Core/WixToolsetServiceProvider.cs b/src/WixToolset.Core/WixToolsetServiceProvider.cs
index 995daa89..74b312c6 100644
--- a/src/WixToolset.Core/WixToolsetServiceProvider.cs
+++ b/src/WixToolset.Core/WixToolsetServiceProvider.cs
@@ -42,6 +42,16 @@ namespace WixToolset.Core
42 return new BindContext(this); 42 return new BindContext(this);
43 } 43 }
44 44
45 if (serviceType == typeof(ILayoutContext))
46 {
47 return new LayoutContext(this);
48 }
49
50 if (serviceType == typeof(IResolveContext))
51 {
52 return new ResolveContext(this);
53 }
54
45 if (serviceType == typeof(IInscribeContext)) 55 if (serviceType == typeof(IInscribeContext))
46 { 56 {
47 return new InscribeContext(this); 57 return new InscribeContext(this);
diff --git a/src/WixToolset.Core/WixVariableResolver.cs b/src/WixToolset.Core/WixVariableResolver.cs
index f8bccef0..7339840e 100644
--- a/src/WixToolset.Core/WixVariableResolver.cs
+++ b/src/WixToolset.Core/WixVariableResolver.cs
@@ -9,8 +9,6 @@ namespace WixToolset.Core
9 using System.Text; 9 using System.Text;
10 using System.Text.RegularExpressions; 10 using System.Text.RegularExpressions;
11 using WixToolset.Data; 11 using WixToolset.Data;
12 using WixToolset.Data.Tuples;
13 using WixToolset.Extensibility;
14 using WixToolset.Extensibility.Services; 12 using WixToolset.Extensibility.Services;
15 13
16 /// <summary> 14 /// <summary>
@@ -37,14 +35,15 @@ namespace WixToolset.Core
37 /// <summary> 35 /// <summary>
38 /// Gets the count of variables added to the resolver. 36 /// Gets the count of variables added to the resolver.
39 /// </summary> 37 /// </summary>
40 public int VariableCount => this.wixVariables.Count; 38 public int VariableCount => this.wixVariables.Count;
41 39
42 /// <summary> 40 /// <summary>
43 /// Add a variable. 41 /// Add a variable.
44 /// </summary> 42 /// </summary>
45 /// <param name="name">The name of the variable.</param> 43 /// <param name="name">The name of the variable.</param>
46 /// <param name="value">The value of the variable.</param> 44 /// <param name="value">The value of the variable.</param>
47 public void AddVariable(string name, string value) 45 /// <param name="overridable">Indicates whether the variable can be overridden by an existing variable.</param>
46 public void AddVariable(string name, string value, bool overridable)
48 { 47 {
49 try 48 try
50 { 49 {
@@ -52,25 +51,9 @@ namespace WixToolset.Core
52 } 51 }
53 catch (ArgumentException) 52 catch (ArgumentException)
54 { 53 {
55 this.Messaging.Write(ErrorMessages.WixVariableCollision(null, name)); 54 if (!overridable)
56 }
57 }
58
59 /// <summary>
60 /// Add a variable.
61 /// </summary>
62 /// <param name="wixVariableRow">The WixVariableRow to add.</param>
63 public void AddVariable(WixVariableTuple wixVariableRow)
64 {
65 try
66 {
67 this.wixVariables.Add(wixVariableRow.WixVariable, wixVariableRow.Value);
68 }
69 catch (ArgumentException)
70 {
71 if (!wixVariableRow.Overridable) // collision
72 { 55 {
73 this.Messaging.Write(ErrorMessages.WixVariableCollision(wixVariableRow.SourceLineNumbers, wixVariableRow.WixVariable)); 56 throw;
74 } 57 }
75 } 58 }
76 } 59 }
@@ -82,37 +65,9 @@ namespace WixToolset.Core
82 /// <param name="value">The value to resolve.</param> 65 /// <param name="value">The value to resolve.</param>
83 /// <param name="localizationOnly">true to only resolve localization variables; false otherwise.</param> 66 /// <param name="localizationOnly">true to only resolve localization variables; false otherwise.</param>
84 /// <returns>The resolved value.</returns> 67 /// <returns>The resolved value.</returns>
85 public string ResolveVariables(SourceLineNumber sourceLineNumbers, string value, bool localizationOnly) 68 public BindVariableResolution ResolveVariables(SourceLineNumber sourceLineNumbers, string value, bool localizationOnly)
86 { 69 {
87 return this.ResolveVariables(sourceLineNumbers, value, localizationOnly, out var defaultIgnored, out var delayedIgnored); 70 return this.ResolveVariables(sourceLineNumbers, value, localizationOnly, true);
88 }
89
90 /// <summary>
91 /// Resolve the wix variables in a value.
92 /// </summary>
93 /// <param name="sourceLineNumbers">The source line information for the value.</param>
94 /// <param name="value">The value to resolve.</param>
95 /// <param name="localizationOnly">true to only resolve localization variables; false otherwise.</param>
96 /// <param name="isDefault">true if the resolved value was the default.</param>
97 /// <returns>The resolved value.</returns>
98 public string ResolveVariables(SourceLineNumber sourceLineNumbers, string value, bool localizationOnly, out bool isDefault)
99 {
100 return this.ResolveVariables(sourceLineNumbers, value, localizationOnly, out isDefault, out var ignored);
101 }
102
103 /// <summary>
104 /// Resolve the wix variables in a value.
105 /// </summary>
106 /// <param name="sourceLineNumbers">The source line information for the value.</param>
107 /// <param name="value">The value to resolve.</param>
108 /// <param name="localizationOnly">true to only resolve localization variables; false otherwise.</param>
109 /// <param name="errorOnUnknown">true if unknown variables should throw errors.</param>
110 /// <param name="isDefault">true if the resolved value was the default.</param>
111 /// <param name="delayedResolve">true if the value has variables that cannot yet be resolved.</param>
112 /// <returns>The resolved value.</returns>
113 public string ResolveVariables(SourceLineNumber sourceLineNumbers, string value, bool localizationOnly, out bool isDefault, out bool delayedResolve)
114 {
115 return this.ResolveVariables(sourceLineNumbers, value, localizationOnly, true, out isDefault, out delayedResolve);
116 } 71 }
117 72
118 /// <summary> 73 /// <summary>
@@ -125,13 +80,12 @@ namespace WixToolset.Core
125 /// <param name="isDefault">true if the resolved value was the default.</param> 80 /// <param name="isDefault">true if the resolved value was the default.</param>
126 /// <param name="delayedResolve">true if the value has variables that cannot yet be resolved.</param> 81 /// <param name="delayedResolve">true if the value has variables that cannot yet be resolved.</param>
127 /// <returns>The resolved value.</returns> 82 /// <returns>The resolved value.</returns>
128 public string ResolveVariables(SourceLineNumber sourceLineNumbers, string value, bool localizationOnly, bool errorOnUnknown, out bool isDefault, out bool delayedResolve) 83 internal BindVariableResolution ResolveVariables(SourceLineNumber sourceLineNumbers, string value, bool localizationOnly, bool errorOnUnknown)
129 { 84 {
130 MatchCollection matches = Common.WixVariableRegex.Matches(value); 85 MatchCollection matches = Common.WixVariableRegex.Matches(value);
131 86
132 // the value is the default unless its substituted further down 87 // the value is the default unless its substituted further down
133 isDefault = true; 88 var result = new BindVariableResolution { IsDefault = true, Value = value };
134 delayedResolve = false;
135 89
136 if (0 < matches.Count) 90 if (0 < matches.Count)
137 { 91 {
@@ -172,6 +126,8 @@ namespace WixToolset.Core
172 if (!localizationOnly) 126 if (!localizationOnly)
173 { 127 {
174 sb.Remove(matches[i].Index - 1, 1); 128 sb.Remove(matches[i].Index - 1, 1);
129
130 result.UpdatedValue = true;
175 } 131 }
176 } 132 }
177 else 133 else
@@ -200,7 +156,7 @@ namespace WixToolset.Core
200 if (this.wixVariables.TryGetValue(variableId, out resolvedValue)) 156 if (this.wixVariables.TryGetValue(variableId, out resolvedValue))
201 { 157 {
202 resolvedValue = resolvedValue ?? String.Empty; 158 resolvedValue = resolvedValue ?? String.Empty;
203 isDefault = false; 159 result.IsDefault = false;
204 } 160 }
205 else if (null != variableDefaultValue) // default the resolved value to the inline value if one was specified 161 else if (null != variableDefaultValue) // default the resolved value to the inline value if one was specified
206 { 162 {
@@ -212,16 +168,17 @@ namespace WixToolset.Core
212 if ("bind" == variableNamespace) 168 if ("bind" == variableNamespace)
213 { 169 {
214 // can't resolve these yet, but keep track of where we find them so they can be resolved later with less effort 170 // can't resolve these yet, but keep track of where we find them so they can be resolved later with less effort
215 delayedResolve = true; 171 result.DelayedResolve = true;
216 } 172 }
217 else 173 else
218 { 174 {
219
220 // insert the resolved value if it was found or display an error 175 // insert the resolved value if it was found or display an error
221 if (null != resolvedValue) 176 if (null != resolvedValue)
222 { 177 {
223 sb.Remove(matches[i].Index, matches[i].Length); 178 sb.Remove(matches[i].Index, matches[i].Length);
224 sb.Insert(matches[i].Index, resolvedValue); 179 sb.Insert(matches[i].Index, resolvedValue);
180
181 result.UpdatedValue = true;
225 } 182 }
226 else if ("loc" == variableNamespace && errorOnUnknown) // unresolved loc variable 183 else if ("loc" == variableNamespace && errorOnUnknown) // unresolved loc variable
227 { 184 {
@@ -235,10 +192,10 @@ namespace WixToolset.Core
235 } 192 }
236 } 193 }
237 194
238 value = sb.ToString(); 195 result.Value = sb.ToString();
239 } 196 }
240 197
241 return value; 198 return result;
242 } 199 }
243 200
244 /// <summary> 201 /// <summary>
diff --git a/src/test/WixToolsetTest.CoreIntegration/ProgramFixture.cs b/src/test/WixToolsetTest.CoreIntegration/ProgramFixture.cs
index 4f34f330..b4a7c8b2 100644
--- a/src/test/WixToolsetTest.CoreIntegration/ProgramFixture.cs
+++ b/src/test/WixToolsetTest.CoreIntegration/ProgramFixture.cs
@@ -20,7 +20,8 @@ namespace WixToolsetTest.CoreIntegration
20 20
21 using (var fs = new DisposableFileSystem()) 21 using (var fs = new DisposableFileSystem())
22 { 22 {
23 var intermediateFolder = fs.GetFolder(); 23 var baseFolder = fs.GetFolder();
24 var intermediateFolder = Path.Combine(baseFolder, "obj");
24 25
25 var program = new Program(); 26 var program = new Program();
26 var result = program.Run(new WixToolsetServiceProvider(), null, new[] 27 var result = program.Run(new WixToolsetServiceProvider(), null, new[]
@@ -31,16 +32,16 @@ namespace WixToolsetTest.CoreIntegration
31 "-loc", Path.Combine(folder, "Package.en-us.wxl"), 32 "-loc", Path.Combine(folder, "Package.en-us.wxl"),
32 "-bindpath", Path.Combine(folder, "data"), 33 "-bindpath", Path.Combine(folder, "data"),
33 "-intermediateFolder", intermediateFolder, 34 "-intermediateFolder", intermediateFolder,
34 "-o", Path.Combine(intermediateFolder, @"bin\test.msi") 35 "-o", Path.Combine(baseFolder, @"bin\test.msi")
35 }); 36 });
36 37
37 Assert.Equal(0, result); 38 Assert.Equal(0, result);
38 39
39 Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi"))); 40 Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.msi")));
40 Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb"))); 41 Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb")));
41 Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\MsiPackage\test.txt"))); 42 Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\MsiPackage\test.txt")));
42 43
43 var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\test.wir")); 44 var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wir"));
44 var section = intermediate.Sections.Single(); 45 var section = intermediate.Sections.Single();
45 46
46 var wixFile = section.Tuples.OfType<WixFileTuple>().Single(); 47 var wixFile = section.Tuples.OfType<WixFileTuple>().Single();
@@ -245,6 +246,38 @@ namespace WixToolsetTest.CoreIntegration
245 } 246 }
246 } 247 }
247 248
249 [Fact]
250 public void CanBuildWixout()
251 {
252 var folder = TestData.Get(@"TestData\SingleFile");
253
254 using (var fs = new DisposableFileSystem())
255 {
256 var baseFolder = fs.GetFolder();
257 var intermediateFolder = Path.Combine(baseFolder, "obj");
258
259 var program = new Program();
260 var result = program.Run(new WixToolsetServiceProvider(), null, new[]
261 {
262 "build",
263 Path.Combine(folder, "Package.wxs"),
264 Path.Combine(folder, "PackageComponents.wxs"),
265 "-loc", Path.Combine(folder, "Package.en-us.wxl"),
266 "-bindpath", Path.Combine(folder, "data"),
267 "-intermediateFolder", intermediateFolder,
268 "-o", Path.Combine(baseFolder, @"bin\test.wixout")
269 });
270
271 Assert.Equal(0, result);
272
273 var builtFiles = Directory.GetFiles(Path.Combine(baseFolder, @"bin"));
274
275 Assert.Equal(new[]{
276 "test.wixout"
277 }, builtFiles.Select(Path.GetFileName).ToArray());
278 }
279 }
280
248 [Fact(Skip = "Not implemented yet.")] 281 [Fact(Skip = "Not implemented yet.")]
249 public void CanBuildInstanceTransform() 282 public void CanBuildInstanceTransform()
250 { 283 {