22// The .NET Foundation licenses this file to you under the MIT license.
33
44using System . Runtime . InteropServices ;
5+ using Microsoft . Win32 ;
56
67namespace Microsoft . DotNet . Cli ;
78
@@ -12,45 +13,73 @@ namespace Microsoft.DotNet.Cli;
1213internal static class MixedInstallationDetector
1314{
1415 /// <summary>
15- /// Gets the known global installation root paths for the current platform.
16+ /// Gets the global installation root path for the current platform.
1617 /// Based on https://github.com/dotnet/designs/blob/main/accepted/2020/install-locations.md
1718 /// and https://github.com/dotnet/designs/blob/main/accepted/2021/install-location-per-architecture.md
1819 /// </summary>
19- private static readonly string [ ] GlobalInstallRoots = GetGlobalInstallRoots ( ) ;
20-
21- private static string [ ] GetGlobalInstallRoots ( )
20+ private static string ? GetGlobalInstallRoot ( )
2221 {
2322 if ( RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) )
2423 {
25- // Windows global install locations
26- return new [ ]
27- {
28- Path . Combine ( Environment . GetFolderPath ( Environment . SpecialFolder . ProgramFiles ) , "dotnet" ) ,
29- Path . Combine ( Environment . GetFolderPath ( Environment . SpecialFolder . ProgramFilesX86 ) , "dotnet" )
30- } ;
31- }
32- else if ( RuntimeInformation . IsOSPlatform ( OSPlatform . OSX ) )
33- {
34- // macOS global install locations
35- return new [ ]
24+ // Windows: Read from registry HKLM\SOFTWARE\dotnet\Setup\InstalledVersions\<arch>\InstallLocation
25+ // Use 32-bit registry view as specified in the spec
26+ try
3627 {
37- "/usr/local/share/dotnet"
38- } ;
39- }
40- else if ( RuntimeInformation . IsOSPlatform ( OSPlatform . Linux ) )
41- {
42- // Linux global install locations (various distros use different paths)
43- return new [ ]
28+ string arch = RuntimeInformation . ProcessArchitecture . ToString ( ) . ToLowerInvariant ( ) ;
29+ using ( var hklm = RegistryKey . OpenBaseKey ( RegistryHive . LocalMachine , RegistryView . Registry32 ) )
30+ using ( var key = hklm . OpenSubKey ( $@ "SOFTWARE\dotnet\Setup\InstalledVersions\{ arch } ") )
31+ {
32+ if ( key != null )
33+ {
34+ var installLocation = key . GetValue ( "InstallLocation" ) as string ;
35+ if ( ! string . IsNullOrEmpty ( installLocation ) )
36+ {
37+ return installLocation ;
38+ }
39+ }
40+ }
41+ }
42+ catch
4443 {
45- "/usr/share/dotnet" ,
46- "/usr/lib64/dotnet" ,
47- "/usr/lib/dotnet"
48- } ;
44+ // If registry reading fails, return null
45+ }
4946 }
5047 else
5148 {
52- return Array . Empty < string > ( ) ;
49+ // Linux/macOS: Read from /etc/dotnet/install_location or /etc/dotnet/install_location_<arch>
50+ try
51+ {
52+ string arch = RuntimeInformation . ProcessArchitecture . ToString ( ) . ToLowerInvariant ( ) ;
53+ string archSpecificPath = $ "/etc/dotnet/install_location_{ arch } ";
54+ string defaultPath = "/etc/dotnet/install_location" ;
55+
56+ // Try arch-specific location first
57+ if ( File . Exists ( archSpecificPath ) )
58+ {
59+ string location = File . ReadAllText ( archSpecificPath ) . Trim ( ) ;
60+ if ( ! string . IsNullOrEmpty ( location ) )
61+ {
62+ return location ;
63+ }
64+ }
65+
66+ // Fall back to default location
67+ if ( File . Exists ( defaultPath ) )
68+ {
69+ string location = File . ReadAllText ( defaultPath ) . Trim ( ) ;
70+ if ( ! string . IsNullOrEmpty ( location ) )
71+ {
72+ return location ;
73+ }
74+ }
75+ }
76+ catch
77+ {
78+ // If file reading fails, return null
79+ }
5380 }
81+
82+ return null ;
5483 }
5584
5685 /// <summary>
@@ -66,32 +95,28 @@ public static bool IsMixedInstallation(string muxerPath, string? dotnetRoot)
6695 return false ;
6796 }
6897
98+ // Get the registered global install location
99+ string ? globalInstallRoot = GetGlobalInstallRoot ( ) ;
100+ if ( string . IsNullOrEmpty ( globalInstallRoot ) )
101+ {
102+ // No global install registered, cannot detect mixed installation
103+ return false ;
104+ }
105+
69106 // Normalize paths for comparison
70107 string normalizedMuxerPath = Path . GetFullPath ( muxerPath ) ;
71108 string normalizedDotnetRoot = Path . GetFullPath ( dotnetRoot ) ;
109+ string normalizedGlobalRoot = Path . GetFullPath ( globalInstallRoot ) ;
72110
73- // Check if the muxer is in a global install root
74- bool isInGlobalRoot = false ;
75- string ? muxerRoot = null ;
76-
77- foreach ( var globalRoot in GlobalInstallRoots )
78- {
79- if ( normalizedMuxerPath . StartsWith ( globalRoot , GetStringComparison ( ) ) )
80- {
81- isInGlobalRoot = true ;
82- muxerRoot = globalRoot ;
83- break ;
84- }
85- }
86-
87- if ( ! isInGlobalRoot )
111+ // Check if the muxer is in the global install root
112+ if ( ! normalizedMuxerPath . StartsWith ( normalizedGlobalRoot , GetStringComparison ( ) ) )
88113 {
89- // Muxer is not in a global install root, no mixed installation
114+ // Muxer is not in the global install root, no mixed installation
90115 return false ;
91116 }
92117
93- // Check if DOTNET_ROOT points to a different location than the muxer's root
94- bool isDifferentRoot = ! normalizedDotnetRoot . StartsWith ( muxerRoot ! , GetStringComparison ( ) ) ;
118+ // Check if DOTNET_ROOT points to a different location than the global install root
119+ bool isDifferentRoot = ! normalizedDotnetRoot . StartsWith ( normalizedGlobalRoot , GetStringComparison ( ) ) ;
95120
96121 return isDifferentRoot ;
97122 }
0 commit comments