aboutsummaryrefslogtreecommitdiff
path: root/src/WixToolset.Core/Bind/FileResolver.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/WixToolset.Core/Bind/FileResolver.cs')
-rw-r--r--src/WixToolset.Core/Bind/FileResolver.cs231
1 files changed, 231 insertions, 0 deletions
diff --git a/src/WixToolset.Core/Bind/FileResolver.cs b/src/WixToolset.Core/Bind/FileResolver.cs
new file mode 100644
index 00000000..8d624e6f
--- /dev/null
+++ b/src/WixToolset.Core/Bind/FileResolver.cs
@@ -0,0 +1,231 @@
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.Linq;
9 using System.Runtime.InteropServices;
10 using WixToolset.Data;
11 using WixToolset.Data.Bind;
12 using WixToolset.Extensibility;
13
14 internal class FileResolver
15 {
16 private const string BindPathOpenString = "!(bindpath.";
17
18 private FileResolver(IEnumerable<BindPath> bindPaths)
19 {
20 this.BindPaths = (bindPaths ?? Array.Empty<BindPath>()).ToLookup(b => b.Stage);
21 this.RebaseTarget = this.BindPaths[BindStage.Target].Any();
22 this.RebaseUpdated = this.BindPaths[BindStage.Updated].Any();
23 }
24
25 public FileResolver(IEnumerable<BindPath> bindPaths, IEnumerable<IBinderExtension> extensions) : this(bindPaths)
26 {
27 this.BinderExtensions = extensions ?? Array.Empty<IBinderExtension>();
28 }
29
30 public FileResolver(IEnumerable<BindPath> bindPaths, IEnumerable<ILibrarianExtension> extensions) : this(bindPaths)
31 {
32 this.LibrarianExtensions = extensions ?? Array.Empty<ILibrarianExtension>();
33 }
34
35 private ILookup<BindStage, BindPath> BindPaths { get; }
36
37 public bool RebaseTarget { get; }
38
39 public bool RebaseUpdated { get; }
40
41 private IEnumerable<IBinderExtension> BinderExtensions { get; }
42
43 private IEnumerable<ILibrarianExtension> LibrarianExtensions { get; }
44
45 /// <summary>
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 {
111 foreach (var extension in this.LibrarianExtensions)
112 {
113 var resolved = extension.Resolve(sourceLineNumbers, table, path);
114
115 if (null != resolved)
116 {
117 return resolved;
118 }
119 }
120
121 return this.ResolveUsingBindPaths(path, table, sourceLineNumbers, BindStage.Normal);
122 }
123
124 /// <summary>
125 /// Resolves the source path of a file using binder extensions.
126 /// </summary>
127 /// <param name="source">Original source value.</param>
128 /// <param name="type">Optional type of source file being resolved.</param>
129 /// <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>
131 /// <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)
133 {
134 foreach (var extension in this.BinderExtensions)
135 {
136 var resolved = extension.ResolveFile(source, type, sourceLineNumbers, bindStage);
137
138 if (null != resolved)
139 {
140 return resolved;
141 }
142 }
143
144 return this.ResolveUsingBindPaths(source, type, sourceLineNumbers, bindStage);
145 }
146
147 private string ResolveUsingBindPaths(string source, string type, SourceLineNumber sourceLineNumbers, BindStage bindStage)
148 {
149 string resolved = null;
150
151 // If the file exists, we're good to go.
152 if (CheckFileExists(source))
153 {
154 resolved = source;
155 }
156 else if (Path.IsPathRooted(source)) // path is rooted so bindpaths won't help, bail since the file apparently doesn't exist.
157 {
158 resolved = null;
159 }
160 else // not a rooted path so let's try applying all the different source resolution options.
161 {
162 string bindName = String.Empty;
163 var path = source;
164 string pathWithoutSourceDir = null;
165
166 if (source.StartsWith(BindPathOpenString, StringComparison.Ordinal))
167 {
168 int closeParen = source.IndexOf(')', BindPathOpenString.Length);
169 if (-1 != closeParen)
170 {
171 bindName = source.Substring(BindPathOpenString.Length, closeParen - BindPathOpenString.Length);
172 path = source.Substring(BindPathOpenString.Length + bindName.Length + 1); // +1 for the closing brace.
173 path = path.TrimStart('\\'); // remove starting '\\' char so the path doesn't look rooted.
174 }
175 }
176 else if (source.StartsWith("SourceDir\\", StringComparison.Ordinal) || source.StartsWith("SourceDir/", StringComparison.Ordinal))
177 {
178 pathWithoutSourceDir = path.Substring(10);
179 }
180
181 var bindPaths = this.BindPaths[bindStage];
182
183 foreach (var bindPath in bindPaths)
184 {
185 if (!String.IsNullOrEmpty(pathWithoutSourceDir))
186 {
187 var filePath = Path.Combine(bindPath.Path, pathWithoutSourceDir);
188
189 if (CheckFileExists(filePath))
190 {
191 resolved = filePath;
192 }
193 }
194
195 if (String.IsNullOrEmpty(resolved))
196 {
197 var filePath = Path.Combine(bindPath.Path, path);
198
199 if (CheckFileExists(filePath))
200 {
201 resolved = filePath;
202 }
203 }
204 }
205 }
206
207 if (null == resolved)
208 {
209 throw new WixFileNotFoundException(sourceLineNumbers, source, type);
210 }
211
212 // Didn't find the file.
213 return resolved;
214 }
215
216 private static bool CheckFileExists(string path)
217 {
218 try
219 {
220 return File.Exists(path);
221 }
222 catch (ArgumentException)
223 {
224 throw new WixException(WixErrors.IllegalCharactersInPath(path));
225 }
226 }
227
228 [DllImport("Kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
229 private static extern bool CreateHardLink(string lpFileName, string lpExistingFileName, IntPtr lpSecurityAttributes);
230 }
231}