-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Fix muxer version handling to use runtime versions instead of SDK versions #51971
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: release/dnup
Are you sure you want to change the base?
Conversation
…rsions Co-authored-by: dsplaisted <[email protected]>
Co-authored-by: dsplaisted <[email protected]>
| /// <summary> | ||
| /// Gets the file version of a muxer executable. | ||
| /// </summary> | ||
| internal static Version? GetMuxerFileVersion(string muxerPath) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@JeremyKuhne Copilot wrote this. Does the logic make sense for getting a file version to find out which version is newer? What's the difference between FileVersionInfo.FileVersion and the FileMajorPart etc. properties?
dsplaisted
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please remove the tests you added in MuxerVersionHandlingTests.cs. I don't think unit-level tests of this are going to be very valuable.
Instead, add two tests to LibraryTests that follow the pattern of those tests. One test should install .NET SDK 9.0 and then 10.0, and verify that the file version of the muxer has been updated. The other test should install 10.0 and then 9.0 and verify that the muxer version isn't updated.
- Remove MuxerVersionHandlingTests.cs as requested - Add two integration tests to LibraryTests: - MuxerIsUpdated_WhenInstallingNewerSdk: verifies muxer is upgraded when installing SDK 10 after SDK 9 - MuxerIsNotDowngraded_WhenInstallingOlderSdk: verifies muxer is not downgraded when installing SDK 9 after SDK 10 - Tests skip on non-Windows platforms since FileVersionInfo doesn't work with ELF binaries Co-authored-by: dsplaisted <[email protected]>
Done. Removed MuxerVersionHandlingTests.cs and added two integration tests to LibraryTests:
Both tests skip on non-Windows since FileVersionInfo doesn't extract version info from ELF binaries. Commit: 665d370 |
dsplaisted
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This logic also needs to work on Linux and Mac, where the file version info is not available.
On those platforms, assume that the version of the muxer corresponds to the version of the runtime included in the archive or the latest runtime installed in the dotnet install root folder. The latest runtime version will be the highest version folder under shared\Microsoft.NETCore.App in the install root.
When this is done, update the tests you added to run on all platforms.
| /// </summary> | ||
| private void HandleMuxerFromZip(ZipArchiveEntry entry, string muxerTargetPath) | ||
| { | ||
| var tempMuxerPath = Path.Combine(Directory.CreateTempSubdirectory().FullName, entry.Name); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems like we should move the temp directory creation and extraction to happen if and only if there already exists a muxer, so we avoid this unnecessary work to compare muxer versions.
| /// <summary> | ||
| /// Handles the muxer from a tar entry, comparing file versions to determine if update is needed. | ||
| /// </summary> | ||
| private void HandleMuxerFromTar(TarEntry entry, string muxerTargetPath) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a generic Entry class that can be used for TarEntry and ZipEntry? I don't see why this logic needs to be different based on the archive type.
- Update GetMuxerFileVersion to fallback to runtime version detection on non-Windows platforms - Add GetRuntimeVersionFromArchive to extract runtime version from tar/zip archives - Add GetLatestRuntimeVersionFromInstallRoot to get highest runtime version from shared/Microsoft.NETCore.App - Update ShouldUpdateMuxer to accept archive path and install root for runtime version fallback - Pass archive and install root context through MuxerHandlingConfig - Remove Windows-only test restrictions - tests now run on all platforms Co-authored-by: dsplaisted <[email protected]>
Done. The muxer version handling now works on all platforms: On Windows: Uses On Linux/Mac: Falls back to runtime version detection when FileVersionInfo returns null:
Tests updated to run on all platforms without skipping. Commit: 0c5e201 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems like we should move the temp directory creation and extraction to happen if and only if there already exists a muxer, so we avoid this unnecessary work to compare muxer versions.
Is there a generic Entry class that can be used for TarEntry and ZipEntry? I don't see why this logic needs to be different based on the archive type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This pull request fixes a critical bug in muxer version handling where SDK versions were incorrectly compared to determine muxer updates. The muxer version is tied to runtime version, not SDK version, so the fix replaces SDK version comparison with actual file version comparison using PE file metadata on Windows and runtime version detection as a fallback on Linux/Mac.
Key Changes
- Replaced flawed SDK comparison logic with file version comparison using
FileVersionInfo.GetVersionInfoon Windows and runtime version detection from archives/install root on Linux/Mac - Added robust version detection methods that scan
shared/Microsoft.NETCore.App/{version}directories in archives and install roots to determine runtime versions - Added comprehensive integration tests to verify muxer is upgraded when installing newer SDKs and not downgraded when installing older SDKs
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 7 comments.
| File | Description |
|---|---|
src/Installer/Microsoft.Dotnet.Installation/Internal/DotnetArchiveExtractor.cs |
Core fix: removed ConfigureMuxerHandling method and added ShouldUpdateMuxer, GetMuxerFileVersion, and runtime version detection methods; updated tar/zip extraction to use version comparison |
test/dotnetup.Tests/LibraryTests.cs |
Added two integration tests to verify muxer upgrade/no-downgrade behavior when installing SDKs in different orders |
test/dotnetup.Tests/Utilities/UpdateChannelExtensions.cs |
Minor: alphabetized using statements |
test/dotnetup.Tests/DnupE2Etest.cs |
Minor: alphabetized using statements and formatting adjustments |
Comments suppressed due to low confidence (1)
test/dotnetup.Tests/LibraryTests.cs:10
- Missing required using directives for test framework. Add
using Xunit;andusing FluentAssertions;to support the[Fact]attributes and.Should()assertion methods used in the test methods below.
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Microsoft.Dotnet.Installation;
using Microsoft.Dotnet.Installation.Internal;
using Microsoft.DotNet.Tools.Dotnetup.Tests.Utilities;
| catch | ||
| { |
Copilot
AI
Dec 2, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] Silent exception swallowing: The catch block silently suppresses all exceptions without any logging or diagnostic information. Consider adding logging or at least a comment explaining why exceptions are being suppressed. This can make debugging issues with version detection difficult in production.
| catch | |
| { | |
| catch (Exception ex) | |
| { | |
| // Swallowing exceptions here because failure to detect a runtime version is not always exceptional, | |
| // but log the exception for diagnostic purposes. | |
| Debug.WriteLine($"Failed to get latest runtime version from install root '{installRoot}': {ex}"); |
| private static string DecompressTarGzToTemp(string gzPath) | ||
| { | ||
| string tempPath = Path.Combine(Path.GetTempPath(), $"dotnet-{Guid.NewGuid()}.tar"); | ||
| using FileStream originalFileStream = File.OpenRead(gzPath); | ||
| using FileStream decompressedFileStream = File.Create(tempPath); | ||
| using GZipStream decompressionStream = new GZipStream(originalFileStream, CompressionMode.Decompress); | ||
| decompressionStream.CopyTo(decompressedFileStream); | ||
| return tempPath; |
Copilot
AI
Dec 2, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] Code duplication: DecompressTarGzToTemp duplicates the decompression logic from DecompressTarGzIfNeeded. Consider extracting the core decompression logic into a shared private method to improve maintainability and reduce duplication.
| private static string DecompressTarGzToTemp(string gzPath) | |
| { | |
| string tempPath = Path.Combine(Path.GetTempPath(), $"dotnet-{Guid.NewGuid()}.tar"); | |
| using FileStream originalFileStream = File.OpenRead(gzPath); | |
| using FileStream decompressedFileStream = File.Create(tempPath); | |
| using GZipStream decompressionStream = new GZipStream(originalFileStream, CompressionMode.Decompress); | |
| decompressionStream.CopyTo(decompressedFileStream); | |
| return tempPath; | |
| /// <summary> | |
| /// Decompresses a .tar.gz file to a temporary location. | |
| /// </summary> | |
| private static string DecompressTarGzToTemp(string gzPath) | |
| { | |
| string tempPath = Path.Combine(Path.GetTempPath(), $"dotnet-{Guid.NewGuid()}.tar"); | |
| DecompressTarGz(gzPath, tempPath); | |
| return tempPath; | |
| } | |
| /// <summary> | |
| /// Decompresses a .tar.gz file to the specified destination path. | |
| /// </summary> | |
| private static void DecompressTarGz(string sourcePath, string destinationPath) | |
| { | |
| using FileStream originalFileStream = File.OpenRead(sourcePath); | |
| using FileStream decompressedFileStream = File.Create(destinationPath); | |
| using GZipStream decompressionStream = new GZipStream(originalFileStream, CompressionMode.Decompress); | |
| decompressionStream.CopyTo(decompressedFileStream); |
| if (versionInfo.FileMajorPart > 0 || versionInfo.FileMinorPart > 0) | ||
| { | ||
| return new Version( | ||
| versionInfo.FileMajorPart, | ||
| versionInfo.FileMinorPart, | ||
| versionInfo.FileBuildPart, | ||
| versionInfo.FilePrivatePart); | ||
| } |
Copilot
AI
Dec 2, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potentially incorrect fallback condition: The check versionInfo.FileMajorPart > 0 || versionInfo.FileMinorPart > 0 will skip valid version 0.0.x.y. Consider changing the condition to check if any part is non-zero, or if the FileVersion property exists but failed to parse, to handle edge cases more robustly. For example: if (versionInfo.FileMajorPart != 0 || versionInfo.FileMinorPart != 0 || versionInfo.FileBuildPart != 0 || versionInfo.FilePrivatePart != 0)
| } | ||
| catch | ||
| { | ||
| // If we can't read the archive, return null |
Copilot
AI
Dec 2, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] Silent exception swallowing: The catch block silently suppresses all exceptions without any logging or diagnostic information. Consider adding logging or at least a comment explaining why exceptions are being suppressed. This can make debugging issues with version detection difficult in production.
| // If we can't read the archive, return null | |
| // Intentionally suppressing all exceptions here because failure to read the archive | |
| // should result in a null version (no version found). If debugging issues with version | |
| // detection, consider adding logging here to capture exception details. | |
| // Example: Console.WriteLine($"Failed to read archive '{archivePath}': {ex}"); | |
| // (Logging is omitted here to avoid introducing dependencies.) |
| catch | ||
| { | ||
| // On error, try fallback for non-Windows | ||
| if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && contextPath != null) | ||
| { | ||
| return GetRuntimeVersionFromContext(contextPath); | ||
| } | ||
| return null; | ||
| } |
Copilot
AI
Dec 2, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] Silent exception swallowing: The catch block silently suppresses all exceptions without any logging or diagnostic information. Consider adding logging or at least a comment explaining why exceptions are being suppressed. This can make debugging issues with version detection difficult in production.
dsplaisted
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After discussion with @JeremyKuhne, please get rid of the logic to read the version info from the FileVersionInfo. Rather, we should use the same logic on Windows and non-Windows, which is to look at the version of the runtime to get a version of the muxer to compare.
Furthermore, try this, which may speed things up and simplify the logic:
- First read the version of the existing muxer, if any (by looking at the latest version of the Microsoft.NETCore.App shared runtime)
- Then, if there is an existing muxer, rename it to something else (add ".tmp" to the filename)
- Then extract the archive. Since the muxer has been renamed, all of the files of the archive can be extracted directly
- Then, if there was previously a muxer, read the new latest runtime version, and if it's less than the previously read version, then delete the newly copied muxer and rename the temporary one back.
@copilot Thanks for the explanation. You're right that you can't use GetLatestRuntimeVersionFromInstallRoot to get the version of the runtime associated with the muxer that was just extracted if installing an older runtime. So I think the logic should be this: Check the latest version of the runtime after the extraction. If it's the same as the version before the extraction, then a newer runtime was NOT installed, so the new muxer that was copied over was actually older. In that case delete the new one and rename the temporary one back. On the other hand, if the version of the latest runtime after extraction is newer, then a newer muxer was installed and the temporary one can be deleted. Can you make these changes? |
- Change logic to compare runtime version AFTER extraction with version BEFORE extraction - If versions are EQUAL after extraction, a newer runtime was NOT installed, so restore old muxer - If version INCREASED after extraction, a newer runtime was installed, so keep new muxer - This correctly handles installing SDK 9.0 after SDK 10.0 (both runtimes present, latest is still 10.0, so restore 10.0 muxer) - All tests now pass Co-authored-by: dsplaisted <[email protected]>
Done. Changed the logic to compare runtime versions before and after extraction:
This correctly handles both scenarios:
All tests now pass. Commit: 3a81ab6 |
test/dotnetup.Tests/LibraryTests.cs
Outdated
| muxerHashAfterSdk10.Should().NotBe(muxerHashAfterSdk9, "muxer file should be updated when installing newer SDK"); | ||
|
|
||
| // On Windows, also verify FileVersion was upgraded | ||
| if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && fileVersionAfterSdk9 != null && fileVersionAfterSdk10 != null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's assert that on Windows the file version before and after the upgrade should not be null, instead of skipping the test if they are null.
Same thing for the other test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. Changed both tests to assert that file versions are not null on Windows instead of skipping the assertions:
- Added
fileVersionAfterSdk9.Should().NotBeNull()andfileVersionAfterSdk10.Should().NotBeNull()assertions - Removed the null checks from the conditional (
&& fileVersionAfterSdk9 != null && fileVersionAfterSdk10 != null) - Now the tests will fail on Windows if file versions can't be read
Commit: 5d66e54
- On Windows, assert that fileVersionAfterSdk9 and fileVersionAfterSdk10 are not null - Remove null checks from conditional - now asserts fail if versions can't be read on Windows - Apply to both MuxerIsUpdated_WhenInstallingNewerSdk and MuxerIsNotDowngraded_WhenInstallingOlderSdk tests Co-authored-by: dsplaisted <[email protected]>
| { | ||
| if (File.Exists(muxerTempPath)) | ||
| { | ||
| File.Delete(muxerTempPath); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot What if we don't have permission to delete that file? We should also try to follow a 'RAII' pattern which deletes/renames the file back when we return out of this code block, because right now if an exception is thrown or the program is aborted during the time hereafter, we may not properly rename the file back or clean it up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added exception handling for permission errors when deleting the .tmp file (catches UnauthorizedAccessException and IOException). Also wrapped the extraction and version comparison in a try-catch block that follows RAII pattern - if an exception occurs, it restores the original muxer from .tmp before re-throwing. If we can't delete the stale .tmp file, the code gracefully degrades to normal extraction without version comparison.
Commit: 1c467b5
| ReleaseVersion? existingMuxerVersion = existingSdkVersions.Any() ? existingSdkVersions.Max() : (ReleaseVersion?)null; | ||
| ReleaseVersion newRuntimeVersion = _resolvedVersion; | ||
| bool shouldUpdateMuxer = existingMuxerVersion is null || newRuntimeVersion.CompareTo(existingMuxerVersion) > 0; | ||
| var runtimePath = Path.Combine(installRoot, "shared", "Microsoft.NETCore.App"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot What happens if the aspnetcore runtime is newer than the regular runtime that a user had installed here?
I don't think we can say for certain, but calling the dotnet host apis directly may be better here as opposed to enumerating over the directories. We are also trusting the directory name here, which in the event the user renames the folder, we will have incorrect information.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right that directory enumeration has limitations. The current approach:
- Only checks
Microsoft.NETCore.App(not aspnetcore or other runtimes) - Trusts directory names (vulnerable to user modifications)
However, this is the approach requested by @dsplaisted in the earlier discussion. The muxer version is specifically tied to the Microsoft.NETCore.App runtime version, not aspnetcore or other shared frameworks.
Calling dotnet host APIs would be more robust, but would require additional dependencies and complexity. If you'd like to pursue that approach, please coordinate with @dsplaisted on the preferred solution since this was the agreed-upon design.
| } | ||
|
|
||
| Directory.CreateDirectory(Path.GetDirectoryName(destPath)!); | ||
| entry.ExtractToFile(destPath, overwrite: true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot What is the logic in 'ExtractToFile' with 'overwrite: true'?
We should make sure that this tries to overwrite akin to so:
- If the target file to be overwrite is locked, we try to delete the existing file
- If the target file cannot be deleted, try to rename the file with a suffix, such as .rm
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ExtractToFile is a method on TarEntry and ZipArchiveEntry from the .NET BCL. With overwrite: true, it attempts to overwrite the target file but doesn't have special logic for locked files or retries.
Adding the suggested retry logic (delete, then rename to .rm) would be an improvement, but it's beyond the scope of this PR which focuses on muxer version handling. The original code already used ExtractToFile(destPath, overwrite: true) - I haven't changed that behavior.
If you'd like to improve the extraction robustness for locked files, that would be a good follow-up PR. For this PR, I've focused on adding RAII cleanup for the muxer temp file specifically.
- Handle UnauthorizedAccessException and IOException when deleting stale .tmp file - Wrap extraction in try-catch block to restore original muxer on exception - If extraction fails, restore muxer from .tmp if it exists and target doesn't - Gracefully degrade to normal extraction if .tmp file can't be deleted - Ignore cleanup errors in exception handler (original exception takes priority) Co-authored-by: nagilson <[email protected]>
|
Just as a heads up, I was blocked by some firewall rules while working on your feedback. Expand below for details. Warning Firewall rules blocked me from connecting to one or more addresses (expand for details)I tried to connect to the following addresses, but was blocked by firewall rules:
If you need me to access, download, or install something from one of these locations, you can either:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dsplaisted I've realized via testing that upon uninstall using the MSIs, we don't actually downgrade the muxer (dotnet.exe) version.
I tested with .NET 10, having .NET 9 installed on disk. I uninstalled .NET 10 successfully, but the .NET 10 muxer remained, despite .NET 10 otherwise being absent. This makes me concerned about using the folders as a heuristic for the version.
The version information tag in the file itself is also interesting. It's got a major.minor and then a build number. I need to see how that is on Unix. I know this is a flawed metric, but I'm starting to believe it's the most consistent.
Whether it really matters how accurate this code is about muxer replacement is up for debate. I'll have to think about this more tomorrow.
|
So, I ran a very basic benchmark of the 2 approaches. Also, neither approach will be correct. Approach 1 is only available on windows and only gets the build date and a major minor. Approach 2 will be wrong if the folder version is wrong.... I'm ok with approach 2, and we could consider a backup file version check on windows for if the major.minor is higher to not replace it. What do you think? |
| // Step 4: If there was a previous muxer, compare versions and restore if needed | ||
| if (hadExistingMuxer && File.Exists(muxerTempPath)) | ||
| { | ||
| Version? newMuxerVersion = GetLatestRuntimeVersionFromInstallRoot(targetDir); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whether we are installing the sdk, runtime, aspnetcore runtime, or otherwise, the .zip and .tar always have (zip_root)\shared\Microsoft.NETCore.App(version) with (version) as the runtime version and therefore the implied muxer version. We should be able to leverage that instead of having to rescan the install root.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I am remembering and understanding correctly what @JeremyKuhne said, it may be faster to extract the archive and read the filesystem afterwards rather than to traverse the directory structure inside of the archive.
A side benefit is that we only need one implementation for getting the version.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, idea is:
- Figure out what the latest installed version is by looking at the current folder structure
- Backup the current muxer
dotnet.exe->dotnet.exe.old - Extract the new archive
- See if a newer version folder exists
- If so, delete the backup, otherwise copy the backup over the new
dotnet.exe.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Idea is it is consistent and the archive has the opportunity extract more optimally.
Note that anything done to fiddle with the muxer that was laid down by an .msi might cause grief with .msi installers (repair might undo what we do here).
| var muxerConfig = ConfigureMuxerHandling(existingSdkVersions); | ||
| string muxerName = DotnetupUtilities.GetDotnetExeName(); | ||
| string muxerTargetPath = Path.Combine(targetDir, muxerName); | ||
| string muxerTempPath = muxerTargetPath + ".tmp"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
instead of doing .tmp and then failing in the rare event we can no longer delete the .tmp file, we should generate or use a unique per session hash (note that we already have one of those!)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where is the unique per session hash?
For now I just added a new GUID to the filename.
| { | ||
| // If we can't delete the existing temp file, we can't proceed with muxer version handling | ||
| // Just extract without version comparison | ||
| hadExistingMuxer = false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We shouldn't just assume we're installing latest if we can't delete .tmp, so this code should be removed following the new .tmp name pattern.
| { | ||
| try | ||
| { | ||
| File.Move(muxerTempPath, muxerTargetPath); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should probably utilize finally over this pattern
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure. The logic in the catch statement is only supposed to happen if there is a failure. There is somewhat similar logic at the end of the install, but it compares the versions to figure out which version should be kept. So I'm not sure there's a simple way to factor the code so that it uses the same code in a finally block.
I think approach 1 will be correct on Windows (barring multiple versions released in a single day). I think the main issue with approach 2 is what you identified, if you uninstall an SDK the muxer won't be downgraded, but the version check will treat it as if it was. I think that's probably OK though, the impact will be that sometimes the muxer will be downgraded to the version from the latest runtime that's currently installed, rather than something that was previously installed. I think for now we can stick with approach 2. |
Original prompt
✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.