aboutsummaryrefslogtreecommitdiff
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
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
-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)