main
cs 238 lines 9.38 KB
Raw
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
3 namespace WixToolset.Core.ExtensibilityServices
4 {
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using System.Linq;
9 using System.Reflection;
10 using WixToolset.Data;
11 using WixToolset.Extensibility;
12 using WixToolset.Extensibility.Data;
13 using WixToolset.Extensibility.Services;
14 using WixToolset.Versioning;
15
16 internal class ExtensionManager : IExtensionManager
17 {
18 // This value needs to stay in sync with the Property in "wix.props" with the same name.
19 private const string WixToolsetExtensionPackageFolder = "wixext6";
20
21 private const string UserWixFolderName = ".wix";
22 private const string MachineWixFolderName = "WixToolset";
23 private const string ExtensionsFolderName = "extensions";
24 private const string UserEnvironmentName = "WIX_EXTENSIONS";
25
26 private readonly List<IExtensionFactory> extensionFactories = new List<IExtensionFactory>();
27 private readonly Dictionary<Type, List<object>> loadedExtensionsByType = new Dictionary<Type, List<object>>();
28
29 public ExtensionManager(IWixToolsetCoreServiceProvider serviceProvider)
30 {
31 this.ServiceProvider = serviceProvider;
32 }
33
34 private IWixToolsetCoreServiceProvider ServiceProvider { get; }
35
36 public void Add(Assembly extensionAssembly)
37 {
38 var types = extensionAssembly.GetTypes().Where(t => !t.IsAbstract && !t.IsInterface && typeof(IExtensionFactory).IsAssignableFrom(t));
39 var factories = types.Select(this.CreateExtensionFactory).ToList();
40
41 if (!factories.Any())
42 {
43 var path = Path.GetFullPath(new Uri(extensionAssembly.CodeBase).LocalPath);
44 throw new WixException(ErrorMessages.InvalidExtension(path, "The extension does not implement IExtensionFactory. All extensions must have at least one implementation of IExtensionFactory."));
45 }
46
47 this.extensionFactories.AddRange(factories);
48 }
49
50 public void Load(string extensionPath)
51 {
52 var checkPath = extensionPath;
53 var checkedPaths = new List<string> { checkPath };
54 try
55 {
56 if (!TryLoadFromPath(checkPath, out var assembly) && !Path.IsPathRooted(extensionPath))
57 {
58 if (TryParseExtensionReference(extensionPath, out var extensionId, out var extensionVersion))
59 {
60 foreach (var cacheLocation in this.GetCacheLocations())
61 {
62 var extensionFolder = Path.Combine(cacheLocation.Path, extensionId);
63
64 if (!Directory.Exists(extensionFolder))
65 {
66 continue;
67 }
68
69 var versionFolder = extensionVersion;
70 if (String.IsNullOrEmpty(versionFolder) && !TryFindLatestVersionInFolder(extensionFolder, out versionFolder))
71 {
72 checkedPaths.Add(extensionFolder);
73 continue;
74 }
75
76 checkPath = Path.Combine(extensionFolder, versionFolder, WixToolsetExtensionPackageFolder, extensionId + ".dll");
77 checkedPaths.Add(checkPath);
78
79 if (TryLoadFromPath(checkPath, out assembly))
80 {
81 break;
82 }
83 }
84 }
85 }
86
87 if (assembly == null)
88 {
89 throw new WixException(ErrorMessages.CouldNotFindExtensionInPaths(extensionPath, checkedPaths));
90 }
91
92 this.Add(assembly);
93 }
94 catch (ReflectionTypeLoadException rtle)
95 {
96 throw new WixException(ErrorMessages.InvalidExtension(checkPath, String.Join(Environment.NewLine, rtle.LoaderExceptions.Select(le => le.ToString()))));
97 }
98 catch (WixException)
99 {
100 throw;
101 }
102 catch (Exception e)
103 {
104 throw new WixException(ErrorMessages.InvalidExtension(checkPath, e.Message), e);
105 }
106 }
107
108 public IReadOnlyCollection<IExtensionCacheLocation> GetCacheLocations()
109 {
110 var locations = new List<IExtensionCacheLocation>();
111
112 var path = Path.Combine(Environment.CurrentDirectory, UserWixFolderName, ExtensionsFolderName);
113 locations.Add(new ExtensionCacheLocation(path, ExtensionCacheLocationScope.Project));
114
115 path = Environment.GetEnvironmentVariable(UserEnvironmentName) ?? Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
116 path = Path.Combine(path, UserWixFolderName, ExtensionsFolderName);
117 locations.Add(new ExtensionCacheLocation(path, ExtensionCacheLocationScope.User));
118
119 if (Environment.Is64BitOperatingSystem)
120 {
121 path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonProgramFiles), MachineWixFolderName, ExtensionsFolderName);
122 locations.Add(new ExtensionCacheLocation(path, ExtensionCacheLocationScope.Machine));
123 }
124
125 path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonProgramFilesX86), MachineWixFolderName, ExtensionsFolderName);
126 locations.Add(new ExtensionCacheLocation(path, ExtensionCacheLocationScope.Machine));
127
128 path = Path.Combine(Path.GetDirectoryName(new Uri(Assembly.GetCallingAssembly().CodeBase).LocalPath), ExtensionsFolderName);
129 locations.Add(new ExtensionCacheLocation(path, ExtensionCacheLocationScope.Machine));
130
131 return locations;
132 }
133
134 public string GetExtensionPackageRootFolderName()
135 {
136 return WixToolsetExtensionPackageFolder;
137 }
138
139 public IReadOnlyCollection<T> GetServices<T>() where T : class
140 {
141 if (!this.loadedExtensionsByType.TryGetValue(typeof(T), out var extensions))
142 {
143 extensions = new List<object>();
144
145 foreach (var factory in this.extensionFactories)
146 {
147 if (factory.TryCreateExtension(typeof(T), out var obj) && obj is T extension)
148 {
149 extensions.Add(extension);
150 }
151 }
152
153 this.loadedExtensionsByType.Add(typeof(T), extensions);
154 }
155
156 return extensions.Cast<T>().ToList();
157 }
158
159 private IExtensionFactory CreateExtensionFactory(Type type)
160 {
161 var constructor = type.GetConstructor(new[] { typeof(IWixToolsetCoreServiceProvider) });
162 if (constructor != null)
163 {
164 return (IExtensionFactory)constructor.Invoke(new[] { this.ServiceProvider });
165 }
166
167 return (IExtensionFactory)Activator.CreateInstance(type);
168 }
169
170 private static bool TryParseExtensionReference(string extensionReference, out string extensionId, out string extensionVersion)
171 {
172 extensionId = extensionReference ?? String.Empty;
173 extensionVersion = String.Empty;
174
175 var index = extensionId.LastIndexOf('/');
176 if (index > 0)
177 {
178 extensionVersion = extensionReference.Substring(index + 1);
179 extensionId = extensionReference.Substring(0, index);
180
181 if (!WixVersion.TryParse(extensionVersion, out _))
182 {
183 return false;
184 }
185
186 if (String.IsNullOrEmpty(extensionId))
187 {
188 return false;
189 }
190 }
191
192 return true;
193 }
194
195 private static bool TryFindLatestVersionInFolder(string basePath, out string foundVersionFolder)
196 {
197 foundVersionFolder = null;
198
199 try
200 {
201 WixVersion highestVersion = null;
202 foreach (var versionPath in Directory.GetDirectories(basePath))
203 {
204 var versionFolder = Path.GetFileName(versionPath);
205 if (WixVersion.TryParse(versionFolder, out var checkVersion) &&
206 (highestVersion == null || highestVersion < checkVersion))
207 {
208 foundVersionFolder = versionFolder;
209 highestVersion = checkVersion;
210 }
211 }
212 }
213 catch (IOException)
214 {
215 }
216
217 return !String.IsNullOrEmpty(foundVersionFolder);
218 }
219
220 private static bool TryLoadFromPath(string extensionPath, out Assembly assembly)
221 {
222 try
223 {
224 if (File.Exists(extensionPath))
225 {
226 assembly = Assembly.LoadFrom(extensionPath);
227 return true;
228 }
229 }
230 catch (IOException e) when (e is FileLoadException || e is FileNotFoundException)
231 {
232 }
233
234 assembly = null;
235 return false;
236 }
237 }
238 }