aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorRob Mensching <rob@firegiant.com>2022-03-19 22:53:06 -0700
committerRob Mensching <rob@firegiant.com>2022-03-20 09:00:57 -0700
commitba4bb7b2080d74918d6d856fba6f86caa410149b (patch)
treedf15e170b01ad2cff63405e347bd8276229b5321 /src
parentf481f3bfd0b6196dee0034801e4d8768f1fbea63 (diff)
downloadwix-ba4bb7b2080d74918d6d856fba6f86caa410149b.tar.gz
wix-ba4bb7b2080d74918d6d856fba6f86caa410149b.tar.bz2
wix-ba4bb7b2080d74918d6d856fba6f86caa410149b.zip
Centralize cache locations in IExtensionManager
This removes the duplication of cache location definitions between IExtensionManager and extension cache command. Also, adds an extension cache test. Fixes 6536
Diffstat (limited to 'src')
-rw-r--r--src/api/wix/WixToolset.Extensibility/Data/IExtensionCacheLocation.cs41
-rw-r--r--src/api/wix/WixToolset.Extensibility/Services/IExtensionManager.cs7
-rw-r--r--src/wix/WixToolset.Core.ExtensionCache/ExtensionCacheManager.cs66
-rw-r--r--src/wix/WixToolset.Core.ExtensionCache/ExtensionCacheManagerCommand.cs9
-rw-r--r--src/wix/WixToolset.Core.ExtensionCache/ExtensionCacheWarnings.cs24
-rw-r--r--src/wix/WixToolset.Core.ExtensionCache/WixToolsetCoreServiceProviderExtensions.cs5
-rw-r--r--src/wix/WixToolset.Core.TestPackage/WixRunner.cs4
-rw-r--r--src/wix/WixToolset.Core.TestPackage/WixToolset.Core.TestPackage.csproj1
-rw-r--r--src/wix/WixToolset.Core/ExtensibilityServices/ExtensionCacheLocation.cs19
-rw-r--r--src/wix/WixToolset.Core/ExtensibilityServices/ExtensionManager.cs74
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/ExtensionFixture.cs46
11 files changed, 235 insertions, 61 deletions
diff --git a/src/api/wix/WixToolset.Extensibility/Data/IExtensionCacheLocation.cs b/src/api/wix/WixToolset.Extensibility/Data/IExtensionCacheLocation.cs
new file mode 100644
index 00000000..53158875
--- /dev/null
+++ b/src/api/wix/WixToolset.Extensibility/Data/IExtensionCacheLocation.cs
@@ -0,0 +1,41 @@
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.Extensibility.Data
4{
5 /// <summary>
6 /// Extension cache location scope.
7 /// </summary>
8 public enum ExtensionCacheLocationScope
9 {
10 /// <summary>
11 /// Project extension cache location.
12 /// </summary>
13 Project,
14
15 /// <summary>
16 /// User extension cache location.
17 /// </summary>
18 User,
19
20 /// <summary>
21 /// Machine extension cache location.
22 /// </summary>
23 Machine,
24 }
25
26 /// <summary>
27 /// Location where extensions may be cached.
28 /// </summary>
29 public interface IExtensionCacheLocation
30 {
31 /// <summary>
32 /// Path for the extension cache location.
33 /// </summary>
34 string Path { get; }
35
36 /// <summary>
37 /// Scope for the extension cache location.
38 /// </summary>
39 ExtensionCacheLocationScope Scope { get; }
40 }
41}
diff --git a/src/api/wix/WixToolset.Extensibility/Services/IExtensionManager.cs b/src/api/wix/WixToolset.Extensibility/Services/IExtensionManager.cs
index 8e49c38d..fe939a59 100644
--- a/src/api/wix/WixToolset.Extensibility/Services/IExtensionManager.cs
+++ b/src/api/wix/WixToolset.Extensibility/Services/IExtensionManager.cs
@@ -4,6 +4,7 @@ namespace WixToolset.Extensibility.Services
4{ 4{
5 using System.Collections.Generic; 5 using System.Collections.Generic;
6 using System.Reflection; 6 using System.Reflection;
7 using WixToolset.Extensibility.Data;
7 8
8 /// <summary> 9 /// <summary>
9 /// Loads extensions and uses the extensions' factories to provide services. 10 /// Loads extensions and uses the extensions' factories to provide services.
@@ -33,6 +34,12 @@ namespace WixToolset.Extensibility.Services
33 void Load(string extensionReference); 34 void Load(string extensionReference);
34 35
35 /// <summary> 36 /// <summary>
37 /// Gets extensions cache locations.
38 /// </summary>
39 /// <returns>List of cache locations where extensions may be found.</returns>
40 IReadOnlyCollection<IExtensionCacheLocation> GetCacheLocations();
41
42 /// <summary>
36 /// Gets extensions of specified type from factories loaded into the extension manager. 43 /// Gets extensions of specified type from factories loaded into the extension manager.
37 /// </summary> 44 /// </summary>
38 /// <typeparam name="T">Type of extension to get.</typeparam> 45 /// <typeparam name="T">Type of extension to get.</typeparam>
diff --git a/src/wix/WixToolset.Core.ExtensionCache/ExtensionCacheManager.cs b/src/wix/WixToolset.Core.ExtensionCache/ExtensionCacheManager.cs
index 256eeb0b..b50ad6e8 100644
--- a/src/wix/WixToolset.Core.ExtensionCache/ExtensionCacheManager.cs
+++ b/src/wix/WixToolset.Core.ExtensionCache/ExtensionCacheManager.cs
@@ -15,22 +15,26 @@ namespace WixToolset.Core.ExtensionCache
15 using NuGet.Protocol; 15 using NuGet.Protocol;
16 using NuGet.Protocol.Core.Types; 16 using NuGet.Protocol.Core.Types;
17 using NuGet.Versioning; 17 using NuGet.Versioning;
18 using WixToolset.Extensibility.Data;
19 using WixToolset.Extensibility.Services;
18 20
19 /// <summary> 21 /// <summary>
20 /// Extension cache manager. 22 /// Extension cache manager.
21 /// </summary> 23 /// </summary>
22 internal class ExtensionCacheManager 24 internal class ExtensionCacheManager
23 { 25 {
24 public string CacheFolder(bool global) => global ? this.GlobalCacheFolder() : this.LocalCacheFolder(); 26 private IReadOnlyCollection<IExtensionCacheLocation> cacheLocations;
25 27
26 public string LocalCacheFolder() => Path.Combine(Environment.CurrentDirectory, ".wix", "extensions"); 28 public ExtensionCacheManager(IMessaging messaging, IExtensionManager extensionManager)
27
28 public string GlobalCacheFolder()
29 { 29 {
30 var baseFolder = Environment.GetEnvironmentVariable("WIX_EXTENSIONS") ?? Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); 30 this.Messaging = messaging;
31 return Path.Combine(baseFolder, ".wix", "extensions"); 31 this.ExtensionManager = extensionManager;
32 } 32 }
33 33
34 private IMessaging Messaging { get; }
35
36 private IExtensionManager ExtensionManager { get; }
37
34 public async Task<bool> AddAsync(bool global, string extension, CancellationToken cancellationToken) 38 public async Task<bool> AddAsync(bool global, string extension, CancellationToken cancellationToken)
35 { 39 {
36 if (String.IsNullOrEmpty(extension)) 40 if (String.IsNullOrEmpty(extension))
@@ -54,11 +58,11 @@ namespace WixToolset.Core.ExtensionCache
54 58
55 (var extensionId, var extensionVersion) = ParseExtensionReference(extension); 59 (var extensionId, var extensionVersion) = ParseExtensionReference(extension);
56 60
57 var cacheFolder = this.CacheFolder(global); 61 var cacheFolder = this.GetCacheFolder(global);
58 62
59 cacheFolder = Path.Combine(cacheFolder, extensionId, extensionVersion); 63 var extensionFolder = Path.Combine(cacheFolder, extensionId, extensionVersion);
60 64
61 if (Directory.Exists(cacheFolder)) 65 if (Directory.Exists(extensionFolder))
62 { 66 {
63 cancellationToken.ThrowIfCancellationRequested(); 67 cancellationToken.ThrowIfCancellationRequested();
64 68
@@ -75,7 +79,7 @@ namespace WixToolset.Core.ExtensionCache
75 79
76 (var extensionId, var extensionVersion) = ParseExtensionReference(extension); 80 (var extensionId, var extensionVersion) = ParseExtensionReference(extension);
77 81
78 var cacheFolder = this.CacheFolder(global); 82 var cacheFolder = this.GetCacheFolder(global);
79 83
80 var searchFolder = Path.Combine(cacheFolder, extensionId, extensionVersion); 84 var searchFolder = Path.Combine(cacheFolder, extensionId, extensionVersion);
81 85
@@ -127,6 +131,20 @@ namespace WixToolset.Core.ExtensionCache
127 return Task.FromResult((IEnumerable<CachedExtension>)found); 131 return Task.FromResult((IEnumerable<CachedExtension>)found);
128 } 132 }
129 133
134 private string GetCacheFolder(bool global)
135 {
136 if (this.cacheLocations == null)
137 {
138 this.cacheLocations = this.ExtensionManager.GetCacheLocations();
139 }
140
141 var requestedScope = global ? ExtensionCacheLocationScope.User : ExtensionCacheLocationScope.Project;
142
143 var cacheLocation = this.cacheLocations.First(l => l.Scope == requestedScope);
144
145 return cacheLocation.Path;
146 }
147
130 private async Task<bool> DownloadAndExtractAsync(bool global, string id, string version, CancellationToken cancellationToken) 148 private async Task<bool> DownloadAndExtractAsync(bool global, string id, string version, CancellationToken cancellationToken)
131 { 149 {
132 var logger = NullLogger.Instance; 150 var logger = NullLogger.Instance;
@@ -149,15 +167,22 @@ namespace WixToolset.Core.ExtensionCache
149 var repository = Repository.Factory.GetCoreV3(source.Source); 167 var repository = Repository.Factory.GetCoreV3(source.Source);
150 var resource = await repository.GetResourceAsync<FindPackageByIdResource>(); 168 var resource = await repository.GetResourceAsync<FindPackageByIdResource>();
151 169
152 var availableVersions = await resource.GetAllVersionsAsync(id, cache, logger, cancellationToken); 170 try
153 foreach (var availableVersion in availableVersions)
154 { 171 {
155 if (nugetVersion is null || nugetVersion < availableVersion) 172 var availableVersions = await resource.GetAllVersionsAsync(id, cache, logger, cancellationToken);
173 foreach (var availableVersion in availableVersions)
156 { 174 {
157 nugetVersion = availableVersion; 175 if (nugetVersion is null || nugetVersion < availableVersion)
158 versionSource = source; 176 {
177 nugetVersion = availableVersion;
178 versionSource = source;
179 }
159 } 180 }
160 } 181 }
182 catch (FatalProtocolException e)
183 {
184 this.Messaging.Write(ExtensionCacheWarnings.NugetException(id, e.Message));
185 }
161 } 186 }
162 187
163 if (nugetVersion is null) 188 if (nugetVersion is null)
@@ -168,7 +193,9 @@ namespace WixToolset.Core.ExtensionCache
168 193
169 var searchSources = versionSource is null ? sources : new[] { versionSource }; 194 var searchSources = versionSource is null ? sources : new[] { versionSource };
170 195
171 var extensionFolder = Path.Combine(this.CacheFolder(global), id, nugetVersion.ToString()); 196 var cacheFolder = this.GetCacheFolder(global);
197
198 var extensionFolder = Path.Combine(cacheFolder, id, nugetVersion.ToString());
172 199
173 foreach (var source in searchSources) 200 foreach (var source in searchSources)
174 { 201 {
@@ -183,6 +210,8 @@ namespace WixToolset.Core.ExtensionCache
183 { 210 {
184 stream.Position = 0; 211 stream.Position = 0;
185 212
213 Directory.CreateDirectory(extensionFolder);
214
186 using (var archive = new PackageArchiveReader(stream)) 215 using (var archive = new PackageArchiveReader(stream))
187 { 216 {
188 var files = PackagingConstants.Folders.Known.SelectMany(folder => archive.GetFiles(folder)).Distinct(StringComparer.OrdinalIgnoreCase); 217 var files = PackagingConstants.Folders.Known.SelectMany(folder => archive.GetFiles(folder)).Distinct(StringComparer.OrdinalIgnoreCase);
@@ -198,7 +227,10 @@ namespace WixToolset.Core.ExtensionCache
198 return false; 227 return false;
199 } 228 }
200 229
201 private string ExtractProgress(string sourceFile, string targetPath, Stream fileStream) => fileStream.CopyToFile(targetPath); 230 private string ExtractProgress(string sourceFile, string targetPath, Stream fileStream)
231 {
232 return fileStream.CopyToFile(targetPath);
233 }
202 234
203 private static (string extensionId, string extensionVersion) ParseExtensionReference(string extensionReference) 235 private static (string extensionId, string extensionVersion) ParseExtensionReference(string extensionReference)
204 { 236 {
diff --git a/src/wix/WixToolset.Core.ExtensionCache/ExtensionCacheManagerCommand.cs b/src/wix/WixToolset.Core.ExtensionCache/ExtensionCacheManagerCommand.cs
index d37ee341..008fcebd 100644
--- a/src/wix/WixToolset.Core.ExtensionCache/ExtensionCacheManagerCommand.cs
+++ b/src/wix/WixToolset.Core.ExtensionCache/ExtensionCacheManagerCommand.cs
@@ -25,17 +25,20 @@ namespace WixToolset.Core.ExtensionCache
25 public ExtensionCacheManagerCommand(IServiceProvider serviceProvider) 25 public ExtensionCacheManagerCommand(IServiceProvider serviceProvider)
26 { 26 {
27 this.Messaging = serviceProvider.GetService<IMessaging>(); 27 this.Messaging = serviceProvider.GetService<IMessaging>();
28 this.ExtensionManager = serviceProvider.GetService<IExtensionManager>();
28 this.ExtensionReferences = new List<string>(); 29 this.ExtensionReferences = new List<string>();
29 } 30 }
30 31
31 private IMessaging Messaging { get; }
32
33 public bool ShowHelp { get; set; } 32 public bool ShowHelp { get; set; }
34 33
35 public bool ShowLogo { get; set; } 34 public bool ShowLogo { get; set; }
36 35
37 public bool StopParsing { get; set; } 36 public bool StopParsing { get; set; }
38 37
38 private IMessaging Messaging { get; }
39
40 private IExtensionManager ExtensionManager { get; }
41
39 private bool Global { get; set; } 42 private bool Global { get; set; }
40 43
41 private CacheSubcommand? Subcommand { get; set; } 44 private CacheSubcommand? Subcommand { get; set; }
@@ -51,7 +54,7 @@ namespace WixToolset.Core.ExtensionCache
51 } 54 }
52 55
53 var success = false; 56 var success = false;
54 var cacheManager = new ExtensionCacheManager(); 57 var cacheManager = new ExtensionCacheManager(this.Messaging, this.ExtensionManager);
55 58
56 switch (this.Subcommand) 59 switch (this.Subcommand)
57 { 60 {
diff --git a/src/wix/WixToolset.Core.ExtensionCache/ExtensionCacheWarnings.cs b/src/wix/WixToolset.Core.ExtensionCache/ExtensionCacheWarnings.cs
new file mode 100644
index 00000000..ddcbfdea
--- /dev/null
+++ b/src/wix/WixToolset.Core.ExtensionCache/ExtensionCacheWarnings.cs
@@ -0,0 +1,24 @@
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.ExtensionCache
4{
5 using WixToolset.Data;
6
7 internal static class ExtensionCacheWarnings
8 {
9 public static Message NugetException(string extensionId, string exceptionMessage)
10 {
11 return Message(new SourceLineNumber(extensionId), Ids.NugetException, "{0}", exceptionMessage);
12 }
13
14 private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args)
15 {
16 return new Message(sourceLineNumber, MessageLevel.Warning, (int)id, format, args);
17 }
18
19 public enum Ids
20 {
21 NugetException = 6100,
22 } // last available is 6499. 6500 is ExtensionCacheErrors.
23 }
24}
diff --git a/src/wix/WixToolset.Core.ExtensionCache/WixToolsetCoreServiceProviderExtensions.cs b/src/wix/WixToolset.Core.ExtensionCache/WixToolsetCoreServiceProviderExtensions.cs
index 424fc469..b591661e 100644
--- a/src/wix/WixToolset.Core.ExtensionCache/WixToolsetCoreServiceProviderExtensions.cs
+++ b/src/wix/WixToolset.Core.ExtensionCache/WixToolsetCoreServiceProviderExtensions.cs
@@ -27,7 +27,10 @@ namespace WixToolset.Core.ExtensionCache
27 27
28 private static ExtensionCacheManager CreateExtensionCacheManager(IWixToolsetCoreServiceProvider coreProvider, Dictionary<Type, object> singletons) 28 private static ExtensionCacheManager CreateExtensionCacheManager(IWixToolsetCoreServiceProvider coreProvider, Dictionary<Type, object> singletons)
29 { 29 {
30 var extensionCacheManager = new ExtensionCacheManager(); 30 var messaging = coreProvider.GetService<IMessaging>();
31 var extensionManager = coreProvider.GetService<IExtensionManager>();
32
33 var extensionCacheManager = new ExtensionCacheManager(messaging, extensionManager);
31 singletons.Add(typeof(ExtensionCacheManager), extensionCacheManager); 34 singletons.Add(typeof(ExtensionCacheManager), extensionCacheManager);
32 35
33 return extensionCacheManager; 36 return extensionCacheManager;
diff --git a/src/wix/WixToolset.Core.TestPackage/WixRunner.cs b/src/wix/WixToolset.Core.TestPackage/WixRunner.cs
index ed7c49b8..8ad3af9a 100644
--- a/src/wix/WixToolset.Core.TestPackage/WixRunner.cs
+++ b/src/wix/WixToolset.Core.TestPackage/WixRunner.cs
@@ -7,6 +7,7 @@ namespace WixToolset.Core.TestPackage
7 using System.Threading; 7 using System.Threading;
8 using System.Threading.Tasks; 8 using System.Threading.Tasks;
9 using WixToolset.Core.Burn; 9 using WixToolset.Core.Burn;
10 using WixToolset.Core.ExtensionCache;
10 using WixToolset.Core.WindowsInstaller; 11 using WixToolset.Core.WindowsInstaller;
11 using WixToolset.Data; 12 using WixToolset.Data;
12 using WixToolset.Extensibility.Services; 13 using WixToolset.Extensibility.Services;
@@ -65,7 +66,8 @@ namespace WixToolset.Core.TestPackage
65 public static Task<int> Execute(string[] args, IWixToolsetCoreServiceProvider coreProvider, out List<Message> messages, bool warningsAsErrors = true) 66 public static Task<int> Execute(string[] args, IWixToolsetCoreServiceProvider coreProvider, out List<Message> messages, bool warningsAsErrors = true)
66 { 67 {
67 coreProvider.AddWindowsInstallerBackend() 68 coreProvider.AddWindowsInstallerBackend()
68 .AddBundleBackend(); 69 .AddBundleBackend()
70 .AddExtensionCacheManager();
69 71
70 var listener = new TestMessageListener(); 72 var listener = new TestMessageListener();
71 73
diff --git a/src/wix/WixToolset.Core.TestPackage/WixToolset.Core.TestPackage.csproj b/src/wix/WixToolset.Core.TestPackage/WixToolset.Core.TestPackage.csproj
index bda54cd1..0c78cc32 100644
--- a/src/wix/WixToolset.Core.TestPackage/WixToolset.Core.TestPackage.csproj
+++ b/src/wix/WixToolset.Core.TestPackage/WixToolset.Core.TestPackage.csproj
@@ -16,6 +16,7 @@
16 <ProjectReference Include="..\WixToolset.Core.Native\WixToolset.Core.Native.csproj" PrivateAssets="true" /> 16 <ProjectReference Include="..\WixToolset.Core.Native\WixToolset.Core.Native.csproj" PrivateAssets="true" />
17 <ProjectReference Include="..\WixToolset.Core\WixToolset.Core.csproj" PrivateAssets="true" /> 17 <ProjectReference Include="..\WixToolset.Core\WixToolset.Core.csproj" PrivateAssets="true" />
18 <ProjectReference Include="..\WixToolset.Core.Burn\WixToolset.Core.Burn.csproj" PrivateAssets="true" /> 18 <ProjectReference Include="..\WixToolset.Core.Burn\WixToolset.Core.Burn.csproj" PrivateAssets="true" />
19 <ProjectReference Include="..\WixToolset.Core.ExtensionCache\WixToolset.Core.ExtensionCache.csproj" PrivateAssets="true" />
19 <ProjectReference Include="..\WixToolset.Core.WindowsInstaller\WixToolset.Core.WindowsInstaller.csproj" PrivateAssets="true" /> 20 <ProjectReference Include="..\WixToolset.Core.WindowsInstaller\WixToolset.Core.WindowsInstaller.csproj" PrivateAssets="true" />
20 </ItemGroup> 21 </ItemGroup>
21 22
diff --git a/src/wix/WixToolset.Core/ExtensibilityServices/ExtensionCacheLocation.cs b/src/wix/WixToolset.Core/ExtensibilityServices/ExtensionCacheLocation.cs
new file mode 100644
index 00000000..b45cd315
--- /dev/null
+++ b/src/wix/WixToolset.Core/ExtensibilityServices/ExtensionCacheLocation.cs
@@ -0,0 +1,19 @@
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.ExtensibilityServices
4{
5 using WixToolset.Extensibility.Data;
6
7 internal class ExtensionCacheLocation : IExtensionCacheLocation
8 {
9 public ExtensionCacheLocation(string path, ExtensionCacheLocationScope scope)
10 {
11 this.Path = path;
12 this.Scope = scope;
13 }
14
15 public string Path { get; }
16
17 public ExtensionCacheLocationScope Scope { get; }
18 }
19}
diff --git a/src/wix/WixToolset.Core/ExtensibilityServices/ExtensionManager.cs b/src/wix/WixToolset.Core/ExtensibilityServices/ExtensionManager.cs
index 2340ed9e..71334841 100644
--- a/src/wix/WixToolset.Core/ExtensibilityServices/ExtensionManager.cs
+++ b/src/wix/WixToolset.Core/ExtensibilityServices/ExtensionManager.cs
@@ -9,6 +9,7 @@ namespace WixToolset.Core.ExtensibilityServices
9 using System.Reflection; 9 using System.Reflection;
10 using WixToolset.Data; 10 using WixToolset.Data;
11 using WixToolset.Extensibility; 11 using WixToolset.Extensibility;
12 using WixToolset.Extensibility.Data;
12 using WixToolset.Extensibility.Services; 13 using WixToolset.Extensibility.Services;
13 14
14 internal class ExtensionManager : IExtensionManager 15 internal class ExtensionManager : IExtensionManager
@@ -16,6 +17,7 @@ namespace WixToolset.Core.ExtensibilityServices
16 private const string UserWixFolderName = ".wix4"; 17 private const string UserWixFolderName = ".wix4";
17 private const string MachineWixFolderName = "WixToolset4"; 18 private const string MachineWixFolderName = "WixToolset4";
18 private const string ExtensionsFolderName = "extensions"; 19 private const string ExtensionsFolderName = "extensions";
20 private const string UserEnvironmentName = "WIX_EXTENSIONS";
19 21
20 private readonly List<IExtensionFactory> extensionFactories = new List<IExtensionFactory>(); 22 private readonly List<IExtensionFactory> extensionFactories = new List<IExtensionFactory>();
21 private readonly Dictionary<Type, List<object>> loadedExtensionsByType = new Dictionary<Type, List<object>>(); 23 private readonly Dictionary<Type, List<object>> loadedExtensionsByType = new Dictionary<Type, List<object>>();
@@ -51,9 +53,14 @@ namespace WixToolset.Core.ExtensibilityServices
51 { 53 {
52 if (TryParseExtensionReference(extensionPath, out var extensionId, out var extensionVersion)) 54 if (TryParseExtensionReference(extensionPath, out var extensionId, out var extensionVersion))
53 { 55 {
54 foreach (var cachePath in this.CacheLocations()) 56 foreach (var cacheLocation in this.GetCacheLocations())
55 { 57 {
56 var extensionFolder = Path.Combine(cachePath, extensionId); 58 var extensionFolder = Path.Combine(cacheLocation.Path, extensionId);
59
60 if (!Directory.Exists(extensionFolder))
61 {
62 continue;
63 }
57 64
58 var versionFolder = extensionVersion; 65 var versionFolder = extensionVersion;
59 if (String.IsNullOrEmpty(versionFolder) && !TryFindLatestVersionInFolder(extensionFolder, out versionFolder)) 66 if (String.IsNullOrEmpty(versionFolder) && !TryFindLatestVersionInFolder(extensionFolder, out versionFolder))
@@ -94,6 +101,32 @@ namespace WixToolset.Core.ExtensibilityServices
94 } 101 }
95 } 102 }
96 103
104 public IReadOnlyCollection<IExtensionCacheLocation> GetCacheLocations()
105 {
106 var locations = new List<IExtensionCacheLocation>();
107
108 var path = Path.Combine(Environment.CurrentDirectory, UserWixFolderName, ExtensionsFolderName);
109 locations.Add(new ExtensionCacheLocation(path, ExtensionCacheLocationScope.Project));
110
111 path = Environment.GetEnvironmentVariable(UserEnvironmentName) ?? Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
112 path = Path.Combine(path, UserWixFolderName, ExtensionsFolderName);
113 locations.Add(new ExtensionCacheLocation(path, ExtensionCacheLocationScope.User));
114
115 if (Environment.Is64BitOperatingSystem)
116 {
117 path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonProgramFiles), MachineWixFolderName, ExtensionsFolderName);
118 locations.Add(new ExtensionCacheLocation(path, ExtensionCacheLocationScope.Machine));
119 }
120
121 path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonProgramFilesX86), MachineWixFolderName, ExtensionsFolderName);
122 locations.Add(new ExtensionCacheLocation(path, ExtensionCacheLocationScope.Machine));
123
124 path = Path.Combine(Path.GetDirectoryName(new Uri(Assembly.GetCallingAssembly().CodeBase).LocalPath), ExtensionsFolderName);
125 locations.Add(new ExtensionCacheLocation(path, ExtensionCacheLocationScope.Machine));
126
127 return locations;
128 }
129
97 public IReadOnlyCollection<T> GetServices<T>() where T : class 130 public IReadOnlyCollection<T> GetServices<T>() where T : class
98 { 131 {
99 if (!this.loadedExtensionsByType.TryGetValue(typeof(T), out var extensions)) 132 if (!this.loadedExtensionsByType.TryGetValue(typeof(T), out var extensions))
@@ -125,43 +158,6 @@ namespace WixToolset.Core.ExtensibilityServices
125 return (IExtensionFactory)Activator.CreateInstance(type); 158 return (IExtensionFactory)Activator.CreateInstance(type);
126 } 159 }
127 160
128 private IEnumerable<string> CacheLocations()
129 {
130 var path = Path.Combine(Environment.CurrentDirectory, UserWixFolderName, ExtensionsFolderName);
131 if (Directory.Exists(path))
132 {
133 yield return path;
134 }
135
136 path = Environment.GetEnvironmentVariable("WIX_EXTENSIONS") ?? Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
137 path = Path.Combine(path, UserWixFolderName, ExtensionsFolderName);
138 if (Directory.Exists(path))
139 {
140 yield return path;
141 }
142
143 if (Environment.Is64BitOperatingSystem)
144 {
145 path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonProgramFiles), MachineWixFolderName, ExtensionsFolderName);
146 if (Directory.Exists(path))
147 {
148 yield return path;
149 }
150 }
151
152 path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonProgramFilesX86), MachineWixFolderName, ExtensionsFolderName);
153 if (Directory.Exists(path))
154 {
155 yield return path;
156 }
157
158 path = Path.Combine(Path.GetDirectoryName(new Uri(Assembly.GetCallingAssembly().CodeBase).LocalPath), ExtensionsFolderName);
159 if (Directory.Exists(path))
160 {
161 yield return path;
162 }
163 }
164
165 private static bool TryParseExtensionReference(string extensionReference, out string extensionId, out string extensionVersion) 161 private static bool TryParseExtensionReference(string extensionReference, out string extensionId, out string extensionVersion)
166 { 162 {
167 extensionId = extensionReference ?? String.Empty; 163 extensionId = extensionReference ?? String.Empty;
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/ExtensionFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/ExtensionFixture.cs
index 225355bf..d814000c 100644
--- a/src/wix/test/WixToolsetTest.CoreIntegration/ExtensionFixture.cs
+++ b/src/wix/test/WixToolsetTest.CoreIntegration/ExtensionFixture.cs
@@ -144,6 +144,52 @@ namespace WixToolsetTest.CoreIntegration
144 } 144 }
145 } 145 }
146 146
147 [Fact]
148 public void CanManipulateExtensionCache()
149 {
150 var currentFolder = Environment.CurrentDirectory;
151
152 try
153 {
154 using (var fs = new DisposableFileSystem())
155 {
156 var folder = fs.GetFolder(true);
157 Environment.CurrentDirectory = folder;
158
159 var result = WixRunner.Execute(new[]
160 {
161 "extension", "add", "WixToolset.UI.wixext"
162 });
163
164 result.AssertSuccess();
165
166 var cacheFolder = Path.Combine(folder, ".wix4", "extensions", "WixToolset.UI.wixext");
167 Assert.True(Directory.Exists(cacheFolder), $"Expected folder '{cacheFolder}' to exist");
168
169 result = WixRunner.Execute(new[]
170 {
171 "extension", "list"
172 });
173
174 result.AssertSuccess();
175 var output = result.Messages.Select(m => m.ToString()).Single();
176 Assert.StartsWith("WixToolset.UI.wixext 4.", output);
177
178 result = WixRunner.Execute(new[]
179 {
180 "extension", "remove", "WixToolset.UI.wixext"
181 });
182
183 result.AssertSuccess();
184 Assert.False(Directory.Exists(cacheFolder), $"Expected folder '{cacheFolder}' to NOT exist");
185 }
186 }
187 finally
188 {
189 Environment.CurrentDirectory = currentFolder;
190 }
191 }
192
147 private static void Build(string[] args) 193 private static void Build(string[] args)
148 { 194 {
149 var result = WixRunner.Execute(args) 195 var result = WixRunner.Execute(args)