Skip to content
This repository was archived by the owner on Jul 13, 2026. It is now read-only.

Commit 237642c

Browse files
authored
[Xamarin.Android.Tools.AndroidSdk] Probe for Microsoft OpenJDK dirs (#113)
Context: https://aka.ms/getopenjdk Context: https://devblogs.microsoft.com/java Remove support for the obsolete (and never updated) `microsoft_dist_openjdk_*` builds of OpenJDK. Despite having "microsoft" in the name, it was maintained by a different team which has moved on to other things.
1 parent e618e00 commit 237642c

7 files changed

Lines changed: 265 additions & 30 deletions

File tree

‎src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ public static void DetectAndSetPreferredJavaSdkPathToLatest (Action<TraceLevel,
185185

186186
logger=logger??DefaultConsoleLogger;
187187

188-
varlatestJdk=JdkInfo.GetMacOSMicrosoftJdks(logger).FirstOrDefault();
188+
varlatestJdk=JdkInfo.GetMicrosoftOpenJdks(logger).FirstOrDefault();
189189
if(latestJdk==null)
190190
thrownewNotSupportedException("No Microsoft OpenJDK could be found. Please re-run the Visual Studio installer or manually specify the JDK path in settings.");
191191

‎src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs‎

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ public static IEnumerable<JdkInfo> GetKnownSystemJdkInfos (Action<TraceLevel, st
287287
returnGetEnvironmentVariableJdks("JI_JAVA_HOME",logger)
288288
.Concat(GetWindowsJdks(logger))
289289
.Concat(GetConfiguredJdks(logger))
290-
.Concat(GetMacOSMicrosoftJdks(logger))
290+
.Concat(GetMacOSMicrosoftOpenJdks(logger))
291291
.Concat(GetEnvironmentVariableJdks("JAVA_HOME",logger))
292292
.Concat(GetPathEnvironmentJdks(logger))
293293
.Concat(GetLibexecJdks(logger))
@@ -313,27 +313,47 @@ static IEnumerable<string> GetConfiguredJdkPaths (Action<TraceLevel, string> log
313313
}
314314
}
315315

316-
internalstaticIEnumerable<JdkInfo>GetMacOSMicrosoftJdks(Action<TraceLevel,string>logger)
316+
internalstaticIEnumerable<JdkInfo>GetMicrosoftOpenJdks(Action<TraceLevel,string>logger)
317317
{
318-
returnGetMacOSMicrosoftJdkPaths()
319-
.Select(p =>TryGetJdkInfo(p,logger,"$HOME/Library/Developer/Xamarin/jdk"))
318+
foreach(vardirinGetMacOSMicrosoftOpenJdks(logger))
319+
yieldreturndir;
320+
if(Path.DirectorySeparatorChar!='\\')
321+
yieldbreak;
322+
foreach(vardirinAndroidSdkWindows.GetJdkInfos(logger)){
323+
yieldreturndir;
324+
}
325+
}
326+
327+
staticIEnumerable<JdkInfo>GetMacOSMicrosoftOpenJdks(Action<TraceLevel,string>logger)
328+
{
329+
returnGetMacOSMicrosoftOpenJdkPaths()
330+
.Select(p =>TryGetJdkInfo(p,logger,"/Library/Java/JavaVirtualMachines/microsoft-*.jdk"))
320331
.Where(jdk =>jdk!=null)
321332
.Select(jdk =>jdk!)
322333
.OrderByDescending(jdk =>jdk,JdkInfoVersionComparer.Default);
323334
}
324335

325-
staticIEnumerable<string>GetMacOSMicrosoftJdkPaths()
336+
staticIEnumerable<string>GetMacOSMicrosoftOpenJdkPaths()
326337
{
338+
varroot="/Library/Java/JavaVirtualMachines";
339+
varpattern="microsoft-*.jdk";
340+
vartoHome=Path.Combine("Contents","Home");
327341
varjdks=AppDomain.CurrentDomain.GetData($"GetMacOSMicrosoftJdkPaths jdks override! {typeof(JdkInfo).AssemblyQualifiedName}")
328342
?.ToString();
329-
if(jdks==null){
330-
varhome=Environment.GetFolderPath(Environment.SpecialFolder.Personal);
331-
jdks=Path.Combine(home,"Library","Developer","Xamarin","jdk");
343+
if(jdks!=null){
344+
root=jdks;
345+
toHome="";
346+
pattern="*";
347+
}
348+
if(!Directory.Exists(root)){
349+
yieldbreak;
350+
}
351+
foreach(vardirinDirectory.EnumerateDirectories(root,pattern)){
352+
varhome=Path.Combine(dir,toHome);
353+
if(!Directory.Exists(home))
354+
continue;
355+
yieldreturnhome;
332356
}
333-
if(!Directory.Exists(jdks))
334-
returnEnumerable.Empty<string>();
335-
336-
returnDirectory.EnumerateDirectories(jdks);
337357
}
338358

339359
staticJdkInfo?TryGetJdkInfo(stringpath,Action<TraceLevel,string>logger,stringlocator)

‎src/Xamarin.Android.Tools.AndroidSdk/OS.cs‎

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
usingSystem;
2+
usingSystem.Collections.Generic;
23
usingSystem.Runtime.InteropServices;
34
usingSystem.IO;
45
usingSystem.Text;
@@ -139,9 +140,89 @@ static extern int RegSetValueExW (UIntPtr hKey, string lpValueName, int lpReserv
139140
staticexternintRegCreateKeyEx(UIntPtrhKey,stringsubKey,uintreserved,string?@class,uintoptions,
140141
uintsamDesired,IntPtrlpSecurityAttributes,outUIntPtrphkResult,outDispositionlpdwDisposition);
141142

143+
// https://docs.microsoft.com/en-us/windows/win32/api/winreg/nf-winreg-regenumkeyexw
144+
[DllImport(ADVAPI,CharSet=CharSet.Unicode,SetLastError=true)]
145+
staticexternintRegEnumKeyExW(
146+
UIntPtrhKey,
147+
uintdwIndex,
148+
[Out]char[]lpName,
149+
refuintlpcchName,
150+
IntPtrlpReserved,
151+
IntPtrlpClass,
152+
IntPtrlpcchClass,
153+
IntPtrlpftLastWriteTime
154+
);
155+
156+
// https://docs.microsoft.com/en-us/windows/win32/api/winreg/nf-winreg-regqueryinfokeyw
157+
[DllImport(ADVAPI,CharSet=CharSet.Unicode,SetLastError=true)]
158+
staticexternintRegQueryInfoKey(
159+
UIntPtrhKey,
160+
IntPtrlpClass,
161+
IntPtrlpcchClass,
162+
IntPtrlpReserved,
163+
outuintlpcSubkey,
164+
outuintlpcchMaxSubkeyLen,
165+
IntPtrlpcchMaxClassLen,
166+
IntPtrlpcValues,
167+
IntPtrlpcchMaxValueNameLen,
168+
IntPtrlpcbMaxValueLen,
169+
IntPtrlpSecurityDescriptor,
170+
IntPtrlpftLastWriteTime
171+
);
172+
142173
[DllImport("advapi32.dll",SetLastError=true)]
143174
staticexternintRegCloseKey(UIntPtrhKey);
144175

176+
publicstaticIEnumerable<string>EnumerateSubkeys(UIntPtrkey,stringsubkey,Wow64wow64)
177+
{
178+
UIntPtrregKeyHandle;
179+
uintsam=(uint)Rights.Read+(uint)wow64;
180+
intr=RegOpenKeyEx(key,subkey,0,sam,outregKeyHandle);
181+
if(r!=0){
182+
yieldbreak;
183+
}
184+
try{
185+
r=RegQueryInfoKey(
186+
hKey:regKeyHandle,
187+
lpClass:IntPtr.Zero,
188+
lpcchClass:IntPtr.Zero,
189+
lpReserved:IntPtr.Zero,
190+
lpcSubkey:outuintcSubkeys,
191+
lpcchMaxSubkeyLen:outuintcchMaxSubkeyLen,
192+
lpcchMaxClassLen:IntPtr.Zero,
193+
lpcValues:IntPtr.Zero,
194+
lpcchMaxValueNameLen:IntPtr.Zero,
195+
lpcbMaxValueLen:IntPtr.Zero,
196+
lpSecurityDescriptor:IntPtr.Zero,
197+
lpftLastWriteTime:IntPtr.Zero
198+
);
199+
if(r!=0){
200+
yieldbreak;
201+
}
202+
varname=newchar[cchMaxSubkeyLen+1];
203+
for(uinti=0;i<cSubkeys;++i){
204+
varnameLen=(uint)name.Length;
205+
r=RegEnumKeyExW(
206+
hKey:regKeyHandle,
207+
dwIndex:i,
208+
lpName:name,
209+
lpcchName:refnameLen,
210+
lpReserved:IntPtr.Zero,
211+
lpClass:IntPtr.Zero,
212+
lpcchClass:IntPtr.Zero,
213+
lpftLastWriteTime:IntPtr.Zero
214+
);
215+
if(r!=0){
216+
continue;
217+
}
218+
yieldreturnnewstring(name,0,(int)nameLen);
219+
}
220+
}
221+
finally{
222+
RegCloseKey(regKeyHandle);
223+
}
224+
}
225+
145226
publicstaticstring?GetValueString(UIntPtrkey,stringsubkey,stringvalueName,Wow64wow64)
146227
{
147228
UIntPtrregKeyHandle;
@@ -192,6 +273,9 @@ enum Rights : uint
192273
SetValue=0x0002,
193274
CreateSubKey=0x0004,
194275
EnumerateSubKey=0x0008,
276+
Notify=0x0010,
277+
Read=_StandardRead|QueryValue|EnumerateSubKey|Notify,
278+
_StandardRead=0x20000,
195279
}
196280

197281
enumOptions
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
usingSystem.Runtime.CompilerServices;
2+
3+
[assembly:InternalsVisibleTo(
4+
"Xamarin.Android.Tools.AndroidSdk-Tests, PublicKey="+
5+
"0024000004800000940000000602000000240000525341310004000011000000438ac2a5acfbf1"+
6+
"6cbd2b2b47a62762f273df9cb2795ceccdf77d10bf508e69e7a362ea7a45455bbf3ac955e1f2e2"+
7+
"814f144e5d817efc4c6502cc012df310783348304e3ae38573c6d658c234025821fda87a0be8a0"+
8+
"d504df564e2c93b2b878925f42503e9d54dfef9f9586d9e6f38a305769587b1de01f6c0410328b"+
9+
"2c9733db"
10+
)]

‎src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs‎

Lines changed: 68 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ class AndroidSdkWindows : AndroidSdkBase
1818
conststringANDROID_INSTALLER_KEY="Path";
1919
conststringXAMARIN_ANDROID_INSTALLER_PATH=@"SOFTWARE\Xamarin\MonoAndroid";
2020
conststringXAMARIN_ANDROID_INSTALLER_KEY="PrivateAndroidSdkPath";
21+
conststringMICROSOFT_OPENJDK_PATH=@"SOFTWARE\Microsoft\JDK";
2122

2223
publicAndroidSdkWindows(Action<TraceLevel,string>logger)
2324
:base(logger)
@@ -131,8 +132,9 @@ IEnumerable<JdkInfo> ToJdkInfos (IEnumerable<string> paths, string locator)
131132
}
132133

133134
returnToJdkInfos(GetPreferredJdkPaths(),"Preferred Registry")
134-
.Concat(ToJdkInfos(GetOpenJdkPaths(),"OpenJDK"))
135-
.Concat(ToJdkInfos(GetKnownOpenJdkPaths(),"Well-known OpenJDK paths"))
135+
.Concat(ToJdkInfos(GetMicrosoftOpenJdkFilesystemPaths(),"Microsoft OpenJDK Filesystem"))
136+
.Concat(ToJdkInfos(GetMicrosoftOpenJdkRegistryPaths(),"Microsoft OpenJDK Registry"))
137+
.Concat(ToJdkInfos(GetVSAndroidJdkPaths(),@"HKLM\SOFTWARE\Microsoft\VisualStudio\Android@JavaHome"))
136138
.Concat(ToJdkInfos(GetOracleJdkPaths(),"Oracle JDK"))
137139
;
138140
}
@@ -150,7 +152,7 @@ private static IEnumerable<string> GetPreferredJdkPaths ()
150152
}
151153
}
152154

153-
privatestaticIEnumerable<string>GetOpenJdkPaths()
155+
privatestaticIEnumerable<string>GetVSAndroidJdkPaths()
154156
{
155157
varroot=RegistryEx.LocalMachine;
156158
varwows=new[]{RegistryEx.Wow64.Key32,RegistryEx.Wow64.Key64};
@@ -163,28 +165,24 @@ private static IEnumerable<string> GetOpenJdkPaths ()
163165
}
164166
}
165167

166-
/// <summary>
167-
/// Locate OpenJDK installations by well known path.
168-
/// </summary>
169-
/// <returns>List of valid OpenJDK paths in version descending order.</returns>
170-
privatestaticIEnumerable<string>GetKnownOpenJdkPaths()
168+
staticIEnumerable<string>GetMicrosoftOpenJdkFilesystemPaths()
171169
{
172-
stringJdkFolderNamePattern="microsoft_dist_openjdk_";
170+
conststringJdkFolderNamePrefix="jdk-";
173171

174172
varpaths=newList<Tuple<string,Version>>();
175173
varrootPaths=newList<string>{
176-
Path.Combine(Environment.ExpandEnvironmentVariables("%ProgramW6432%"),"Android","jdk"),
174+
Path.Combine(Environment.ExpandEnvironmentVariables("%ProgramW6432%"),"Microsoft"),
177175
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86),"Android","jdk"),
178176
};
179177

180178
foreach(varrootPathinrootPaths){
181-
if(Directory.Exists(rootPath)){
182-
foreach(vardirectoryNameinDirectory.EnumerateDirectories(rootPath,$"{JdkFolderNamePattern}*").ToList()){
183-
varversionString=directoryName.Replace($"{rootPath}\\{JdkFolderNamePattern}",string.Empty);
184-
if(Version.TryParse(versionString,outVersion?ver)){
185-
paths.Add(newTuple<string,Version>(directoryName,ver));
186-
}
187-
}
179+
if(!Directory.Exists(rootPath))
180+
continue;
181+
foreach(vardirectoryNameinDirectory.EnumerateDirectories(rootPath,$"{JdkFolderNamePrefix}*")){
182+
varversion=ExtractVersion(directoryName,JdkFolderNamePrefix);
183+
if(version==null)
184+
continue;
185+
paths.Add(Tuple.Create(directoryName,version));
188186
}
189187
}
190188

@@ -193,6 +191,59 @@ private static IEnumerable<string> GetKnownOpenJdkPaths ()
193191
.Select(openJdk =>openJdk.Item1);
194192
}
195193

194+
staticIEnumerable<string>GetMicrosoftOpenJdkRegistryPaths()
195+
{
196+
varpaths=newList<(Versionversion,stringpath)>();
197+
varroots=new[]{RegistryEx.CurrentUser,RegistryEx.LocalMachine};
198+
varwows=new[]{RegistryEx.Wow64.Key32,RegistryEx.Wow64.Key64};
199+
foreach(varrootinroots)
200+
foreach(varwowinwows){
201+
foreach(varsubkeyNameinRegistryEx.EnumerateSubkeys(root,MICROSOFT_OPENJDK_PATH,wow)){
202+
if(!Version.TryParse(subkeyName,outvarversion))
203+
continue;
204+
varmsiKey=$@"{MICROSOFT_OPENJDK_PATH}\{subkeyName}\hotspot\MSI";
205+
varpath=RegistryEx.GetValueString(root,msiKey,"Path",wow);
206+
if(path==null)
207+
continue;
208+
paths.Add((version,path));
209+
}
210+
}
211+
212+
returnpaths.OrderByDescending(e =>e.version)
213+
.Select(e =>e.path);
214+
}
215+
216+
internalstaticVersion?ExtractVersion(stringpath,stringprefix)
217+
{
218+
varname=Path.GetFileName(path);
219+
if(name.Length<=prefix.Length)
220+
returnnull;
221+
if(!name.StartsWith(prefix,StringComparison.OrdinalIgnoreCase))
222+
returnnull;
223+
224+
varstart=prefix.Length;
225+
while(start<name.Length&&!char.IsDigit(name,start)){
226+
++start;
227+
}
228+
if(start==name.Length)
229+
returnnull;
230+
231+
name=name.Substring(start);
232+
intend=0;
233+
while(end<name.Length&&
234+
(char.IsDigit(name[end])||name[end]=='.')){
235+
end++;
236+
}
237+
238+
do{
239+
if(Version.TryParse(name.Substring(0,end),outvarv))
240+
returnv;
241+
end=name.LastIndexOf('.',end-1);
242+
}while(end>0);
243+
244+
returnnull;
245+
}
246+
196247
privatestaticIEnumerable<string>GetOracleJdkPaths()
197248
{
198249
stringsubkey=@"SOFTWARE\JavaSoft\Java Development Kit";
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
usingSystem;
2+
usingSystem.Collections.Generic;
3+
usingSystem.Diagnostics;
4+
usingSystem.IO;
5+
usingSystem.Linq;
6+
usingSystem.Text;
7+
usingSystem.Xml.Linq;
8+
9+
usingNUnit.Framework;
10+
11+
namespaceXamarin.Android.Tools.Tests
12+
{
13+
[TestFixture]
14+
publicclassAndroidSdkWindowsTests
15+
{
16+
[Test]
17+
publicvoidExtractVersion()
18+
{
19+
varsep=Path.DirectorySeparatorChar;
20+
21+
vartests=new[]{
22+
new{
23+
Path=$"foo{sep}",
24+
Prefix="",
25+
Expected=(Version)null,
26+
},
27+
new{
28+
Path=$"foo{sep}bar-1-extra",
29+
Prefix="bar-",
30+
Expected=(Version)null,
31+
},
32+
new{
33+
Path=$"foo{sep}abcdef",
34+
Prefix="a",
35+
Expected=(Version)null,
36+
},
37+
new{
38+
Path=$"foo{sep}a{sep}b.c.d",
39+
Prefix="none-of-the-above",
40+
Expected=(Version)null,
41+
},
42+
new{
43+
Path=$"jdks{sep}jdk-1.2.3-hotspot-extra",
44+
Prefix="jdk-",
45+
Expected=newVersion(1,2,3),
46+
},
47+
new{
48+
Path=$"jdks{sep}jdk-1.2.3-hotspot-extra",
49+
Prefix="jdk",
50+
Expected=newVersion(1,2,3),
51+
},
52+
new{
53+
Path=$"jdks{sep}jdk-1.2.3.4.5.6-extra",
54+
Prefix="jdk-",
55+
Expected=newVersion(1,2,3,4),
56+
},
57+
};
58+
59+
foreach(vartestintests){
60+
Assert.AreEqual(
61+
test.Expected,
62+
AndroidSdkWindows.ExtractVersion(test.Path,test.Prefix),
63+
$"Version couldn't be extracted from Path=`{test.Path}` Prefix=`{test.Prefix}`!"
64+
);
65+
}
66+
}
67+
}
68+
}

‎tests/Xamarin.Android.Tools.AndroidSdk-Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
If $(TargetFramework) is declared here instead, it will not be evaluated before Directory.Build.props
66
is loaded and the wrong $(TestOutputFullPath) will be used. -->
77
<TargetFrameworks>netcoreapp3.1</TargetFrameworks>
8+
<SignAssembly>true</SignAssembly>
9+
<AssemblyOriginatorKeyFile>..\..\product.snk</AssemblyOriginatorKeyFile>
810
<IsPackable>false</IsPackable>
911
<OutputPath>$(TestOutputFullPath)</OutputPath>
1012
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>

0 commit comments

Comments
 (0)