Skip to content

Commit d03ff8c

Browse files
committed
[Java.Interop.BootstrapTasks] Support single-digit versions
The `<JdkInfo/>` task sorts preferred JDK versions based on the version number embedded into the directory name. This unfortunately requires that the version number embedded into the directory name contain two or more version parts. This is true on macOS and Windows: # macOS $ ls -1tr /Library/Java/JavaVirtualMachines/ 1.6.0_65-b14-462.jdk jdk1.8.0_77.jdk jdk-9.0.4.jdk But this is not necessarily the case on Linux: $ ls -1 /usr/lib/jvm java-8-openjdk-amd64 java-8-oracle Single-part version numbers aren't supported by `Version.TryParse()`, so when `$(JI_MAX_JDK)` is set -- causing `maxVersion` to be non-null -- while the version *isn't* extracted from the directory name, the [comparison can fail][0]: [0]: https://jenkins.mono-project.com/job/xamarin-anroid-linux-pr-builder/2917/console The "JdkInfo" task failed unexpectedly. System.ArgumentNullException: Value cannot be null. Parameter name: v1 at System.Version.op_LessThanOrEqual (System.Version v1, System.Version v2) [0x00003] in <2f83d2ff70e3444cb3582fe4e97bad63>:0 at Java.Interop.BootstrapTasks.JdkInfo+<>c__DisplayClass31_0.<GetJavaHomePathFromMachine>b__2 (<>f__AnonymousType0`2[<Path>j__TPar,<Version>j__TPar] v) [0x00000] in …/xamarin-android/external/Java.Interop/src/Java.Interop.BootstrapTasks/Java.Interop.BootstrapTasks/JdkInfo.cs:172 ... at Java.Interop.BootstrapTasks.JdkInfo.GetJavaHomePathFromMachine (System.Version maxVersion) [0x00015] in …/xamarin-android/external/Java.Interop/src/Java.Interop.BootstrapTasks/Java.Interop.BootstrapTasks/JdkInfo.cs:163 at Java.Interop.BootstrapTasks.JdkInfo.Execute () [0x00176] in …/xamarin-android/external/Java.Interop/src/Java.Interop.BootstrapTasks/Java.Interop.BootstrapTasks/JdkInfo.cs:56 Fix this by allowing extraction of Version numbers from single-part version codes as seen on Linux, allowing us to perform comparisons against non-null versions. If multiple directories *still* have matching version numbers, then we sort the output so that the location with the most recent write time is used. Additionally, check some OS-specific preferred locations: * On macOS, `/usr/libexec/java_home`, which is a program which prints out the preferred JDK location to stdout. * On Linux, `/usr/alternatives/java` is a symlink to the preferred JDK location, which we can read using `readlink`. Treat the OS-specific locations similar to `$JAVA_HOME`, and check them *before* attempting the `JdkInfo.JdksRoot` children.
1 parent ca27edd commit d03ff8c

File tree

2 files changed

+187
-56
lines changed
  • build-tools/scripts
  • src/Java.Interop.BootstrapTasks/Java.Interop.BootstrapTasks

2 files changed

+187
-56
lines changed

build-tools/scripts/jdk.mk

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@
2727

2828
OS ?= $(shell uname)
2929

30-
_INCLUDE_MK = bin/Build$(CONFIGURATION)/JdkInfo.mk
30+
_INCLUDE_MK = bin/Build$(CONFIGURATION)/JdkInfo.mk
31+
_INCLUDE_PROPS = bin/Build$(CONFIGURATION)/JdkInfo.props
3132

3233
prepare:: $(_INCLUDE_MK)
3334

@@ -41,7 +42,7 @@ ifeq ($(OS),Linux)
4142
_JDKS_ROOT := /usr/lib/jvm
4243
endif # $(OS)=Linux
4344

44-
$(_INCLUDE_MK): bin/Build$(CONFIGURATION)/Java.Interop.BootstrapTasks.dll
45+
$(_INCLUDE_MK) $(_INCLUDE_PROPS): bin/Build$(CONFIGURATION)/Java.Interop.BootstrapTasks.dll
4546
$(MSBUILD) $(MSBUILD_FLAGS) build-tools/scripts/jdk.targets /t:GetPreferredJdkRoot \
4647
/p:JdksRoot="$(_JDKS_ROOT)" \
4748
$(if $(JI_MAX_JDK),"/p:MaximumJdkVersion=$(JI_MAX_JDK)")

src/Java.Interop.BootstrapTasks/Java.Interop.BootstrapTasks/JdkInfo.cs

Lines changed: 184 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -40,41 +40,46 @@ public override bool Execute ()
4040
Log.LogMessage (MessageImportance.Low, $" {nameof (MaximumJdkVersion)}: {MaximumJdkVersion}");
4141
Log.LogMessage (MessageImportance.Low, $" {nameof (PropertyFile)}: {PropertyFile}");
4242

43-
var maxVersion = GetMaxJdkVersion ();
43+
var maxVersion = GetVersion (MaximumJdkVersion);
44+
45+
string jarPath = null;
46+
string javacPath = null;
47+
string jdkJvmPath = null;
48+
string includePath = null;
49+
4450
var java_home = GetJavaHomePathFromEnvironment ();
45-
if (java_home != null) {
46-
var java_home_v = GetVersionFromPath (java_home);
47-
if (maxVersion != null && java_home_v != null && java_home_v > maxVersion) {
48-
Log.LogMessage (MessageImportance.Low, $" Skipping JAVA_HOME default value of `{java_home}` as it exceeds MaximumJdkVersion={MaximumJdkVersion}.");
49-
java_home = null;
50-
}
51-
if (java_home != null && !Directory.Exists (Path.Combine (java_home, "include"))) {
52-
Log.LogMessage (MessageImportance.Low, $" Skipping JAVA_HOME default value of `{java_home}` as it does not contain an `include` subdirectory.");
53-
java_home = null;
54-
}
51+
if (!ValidateJdkPath (maxVersion, java_home, out jarPath, out javacPath, out jdkJvmPath, out includePath)) {
52+
java_home = null;
53+
}
54+
55+
if (java_home == null &&
56+
(java_home = GetJavaHomePathFromLibexec ()) != null &&
57+
!ValidateJdkPath (maxVersion, java_home, out jarPath, out javacPath, out jdkJvmPath, out includePath)) {
58+
java_home = null;
59+
}
60+
61+
if (java_home == null &&
62+
(java_home = GetJavaHomePathFromAlternatives ()) != null &&
63+
!ValidateJdkPath (maxVersion, java_home, out jarPath, out javacPath, out jdkJvmPath, out includePath)) {
64+
java_home = null;
65+
}
66+
67+
if (java_home == null &&
68+
(java_home = GetJavaHomePathFromMachine (maxVersion)) != null &&
69+
!ValidateJdkPath (maxVersion, java_home, out jarPath, out javacPath, out jdkJvmPath, out includePath)) {
70+
java_home = null;
5571
}
56-
java_home = java_home ?? GetJavaHomePathFromMachine (maxVersion);
5772

58-
if (string.IsNullOrEmpty (java_home)) {
73+
if (java_home == null) {
5974
Log.LogError ("Could not determine JAVA_HOME location. Please set JdksRoot or export the JAVA_HOME environment variable.");
6075
return false;
6176
}
6277

6378
var includes = new List<string> () {
64-
Path.Combine (java_home, "include"),
79+
includePath,
6580
};
66-
includes.AddRange (Directory.GetDirectories (includes [0]));
81+
includes.AddRange (Directory.GetDirectories (includePath));
6782

68-
var jarPath = FindExecutablesInDirectory (Path.Combine (java_home, "bin"), "jar").First ();
69-
var javacPath = FindExecutablesInDirectory (Path.Combine (java_home, "bin"), "javac").First ();
70-
var jdkJvmPaths = OS.IsMacOS
71-
? FindLibrariesInDirectory (java_home, "jli")
72-
: FindLibrariesInDirectory (Path.Combine (java_home, "jre"), "jvm");
73-
var jdkJvmPath = jdkJvmPaths.First ();
74-
75-
FileExists (jarPath);
76-
FileExists (javacPath);
77-
FileExists (jdkJvmPath);
7883
if (Log.HasLoggedErrors) {
7984
return false;
8085
}
@@ -92,26 +97,25 @@ public override bool Execute ()
9297
return !Log.HasLoggedErrors;
9398
}
9499

95-
Version GetMaxJdkVersion ()
100+
Version GetVersion (string value)
96101
{
97-
if (string.IsNullOrEmpty (MaximumJdkVersion))
102+
if (string.IsNullOrEmpty (value))
98103
return null;
99-
if (!MaximumJdkVersion.Contains (".")) {
100-
MaximumJdkVersion += ".0";
104+
if (!value.Contains (".")) {
105+
value += ".0";
101106
}
102-
return new Version (MaximumJdkVersion);
107+
Version v;
108+
if (Version.TryParse (value, out v))
109+
return v;
110+
return null;
103111
}
104112

105113
Version GetVersionFromPath (string path)
106114
{
107115
var m = VersionExtractor.Match (path);
108116
if (!m.Success)
109117
return null;
110-
Version v;
111-
if (!Version.TryParse (m.Groups ["version"].Value, out v)) {
112-
return null;
113-
}
114-
return v;
118+
return GetVersion (m.Groups ["version"].Value);
115119
}
116120

117121
void FileExists (string path)
@@ -150,6 +154,56 @@ void WriteMakeFragmentFile (string jarPath, string javacPath, string jdkJvmPath,
150154
}
151155
}
152156

157+
bool ValidateJdkPath (Version maxVersion, string java_home)
158+
{
159+
return ValidateJdkPath (maxVersion, java_home,
160+
out _, out _, out _, out _);
161+
}
162+
163+
bool ValidateJdkPath (Version maxVersion, string java_home,
164+
out string jarPath, out string javacPath, out string jdkJvmPath, out string includePath)
165+
{
166+
jarPath = javacPath = jdkJvmPath = includePath = null;
167+
168+
if (string.IsNullOrEmpty (java_home) || !Directory.Exists (java_home))
169+
return false;
170+
171+
var pathVersion = GetVersionFromPath (java_home);
172+
if (maxVersion != null && pathVersion != null && pathVersion > maxVersion) {
173+
Log.LogMessage (MessageImportance.Low,
174+
$" Skipping JAVA_HOME value of `{java_home}` as it exceeds MaximumJdkVersion={MaximumJdkVersion}.");
175+
return false;
176+
}
177+
178+
jarPath = FindExecutablesInDirectory (Path.Combine (java_home, "bin"), "jar").FirstOrDefault ();
179+
javacPath = FindExecutablesInDirectory (Path.Combine (java_home, "bin"), "javac").FirstOrDefault ();
180+
var jdkJvmPaths = OS.IsMacOS
181+
? FindLibrariesInDirectory (java_home, "jli")
182+
: FindLibrariesInDirectory (Path.Combine (java_home, "jre"), "jvm");
183+
jdkJvmPath = jdkJvmPaths.FirstOrDefault ();
184+
includePath = Path.Combine (java_home, "include");
185+
186+
if (jarPath == null) {
187+
Log.LogMessage (MessageImportance.Low, $" Skipping JAVA_HOME value of `{java_home}` as `jar` could not be found.");
188+
return false;
189+
}
190+
if (javacPath == null) {
191+
Log.LogMessage (MessageImportance.Low, $" Skipping JAVA_HOME value of `{java_home}` as `javac` could not be found.");
192+
return false;
193+
}
194+
if (jdkJvmPath == null) {
195+
var jvm = OS.IsMacOS ? "libjli.dylib" : string.Format (OS.NativeLibraryFormat, "jvm");
196+
Log.LogMessage (MessageImportance.Low, $" Skipping JAVA_HOME value of `{java_home}` as `{jvm} could not be found.");
197+
return false;
198+
}
199+
if (!Directory.Exists (includePath)) {
200+
Log.LogMessage (MessageImportance.Low, $" Skipping JAVA_HOME value of `{java_home}` as the `include` directory could not be found.");
201+
return false;
202+
}
203+
204+
return true;
205+
}
206+
153207
string GetJavaHomePathFromEnvironment ()
154208
{
155209
var java_home = Environment.GetEnvironmentVariable ("JAVA_HOME");
@@ -158,24 +212,74 @@ string GetJavaHomePathFromEnvironment ()
158212
return null;
159213
}
160214

215+
// macOS
216+
string GetJavaHomePathFromLibexec ()
217+
{
218+
var java_home = Path.GetFullPath ("/usr/libexec/java_home");
219+
if (!File.Exists (java_home)) {
220+
return null;
221+
}
222+
string path = null;
223+
Exec (java_home, "", (o, e) => {
224+
if (string.IsNullOrEmpty (e.Data))
225+
return;
226+
Log.LogMessage (MessageImportance.Low, $" {e.Data}");
227+
path = e.Data;
228+
});
229+
return path;
230+
}
231+
232+
// Linux
233+
string GetJavaHomePathFromAlternatives ()
234+
{
235+
var alternatives = Path.GetFullPath ("/etc/alternatives/java");
236+
if (!File.Exists (alternatives))
237+
return null;
238+
string targetJava = null;
239+
Exec ("readlink", $"\"{alternatives}\"", (o, e) => {
240+
if (string.IsNullOrEmpty (e.Data))
241+
return;
242+
Log.LogMessage (MessageImportance.Low, $" {e.Data}");
243+
targetJava = e.Data;
244+
});
245+
if (string.IsNullOrEmpty (targetJava))
246+
return null;
247+
return GetJavaHomePathFromJava (targetJava);
248+
}
249+
161250
string GetJavaHomePathFromMachine (Version maxVersion)
162251
{
163252
var java_homes = GetJavaHomePathsFromDirectory (JdksRoot)
164253
.Concat (GetJavaHomePathsFromJava ())
165254
.Concat (GetJavaHomePathsFromWindowsRegistry ())
166-
.Distinct ()
167255
.Where (d => Directory.Exists (d))
168-
.Select (jh => new {
256+
.Distinct ()
257+
.ToList ();
258+
259+
foreach (var p in java_homes) {
260+
Log.LogMessage (MessageImportance.Low, $" Possible JAVA_HOME location: {p}");
261+
}
262+
263+
var versionComparer = new ComparisonComparer<JdkComparisonInfo>((x, y) => {
264+
int r = 0;
265+
if (x.Version != null && y.Version != null)
266+
r = x.Version.CompareTo (y.Version);
267+
return r;
268+
});
269+
270+
java_homes = java_homes.Where (d => ValidateJdkPath (maxVersion, d))
271+
.Select (jh => new JdkComparisonInfo {
169272
Path = jh,
170273
Version = GetVersionFromPath (jh),
171274
})
172-
.Where (v => maxVersion == null ? true : v.Version <= maxVersion)
173-
.OrderByDescending (v => v.Version)
275+
.Where (v => (maxVersion == null || v.Version == null) ? true : v.Version <= maxVersion)
276+
.OrderByDescending (v => v, versionComparer)
277+
.ThenByDescending (v => Directory.GetLastWriteTimeUtc (v.Path))
174278
.Select (v => v.Path)
175279
.ToList ();
176280

177281
foreach (var p in java_homes) {
178-
Log.LogMessage (MessageImportance.Low, $" Possible JAVA_HOME location: {p}");
282+
Log.LogMessage (MessageImportance.Low, $" Filtered JAVA_HOME location: {p}");
179283
}
180284

181285
return java_homes.FirstOrDefault ();
@@ -201,26 +305,32 @@ IEnumerable<string> GetJavaHomePathsFromJava ()
201305
.SelectMany (p => FindExecutablesInDirectory (p, "java"));
202306

203307
foreach (var exe in javas) {
204-
const string JavaHome = "java.home = ";
205-
string java_home = null;
206-
Exec (exe, "-XshowSettings:properties -version", (o, e) => {
207-
int i = e.Data?.IndexOf (JavaHome) ?? -1;
208-
if (i < 0)
209-
return;
210-
Log.LogMessage (MessageImportance.Low, $" {e.Data}");
211-
java_home = e.Data.Substring (JavaHome.Length + i);
212-
// `java -XshowSettings:properties -version | grep java.home` ends with `/jre` on macOS.
213-
// We need the parent dir so we can properly lookup the `include` directories
214-
if (java_home.EndsWith ("jre", StringComparison.OrdinalIgnoreCase)) {
215-
java_home = Path.GetDirectoryName (java_home);
216-
}
217-
});
308+
var java_home = GetJavaHomePathFromJava (exe);
218309
if (string.IsNullOrEmpty (java_home))
219310
continue;
220311
yield return java_home;
221312
}
222313
}
223314

315+
string GetJavaHomePathFromJava (string java)
316+
{
317+
const string JavaHome = "java.home = ";
318+
string java_home = null;
319+
Exec (java, "-XshowSettings:properties -version", (o, e) => {
320+
int i = e.Data?.IndexOf (JavaHome) ?? -1;
321+
if (i < 0)
322+
return;
323+
Log.LogMessage (MessageImportance.Low, $" {e.Data}");
324+
java_home = e.Data.Substring (JavaHome.Length + i);
325+
// `java -XshowSettings:properties -version 2>&1 | grep java.home` ends with `/jre` on macOS.
326+
// We need the parent dir so we can properly lookup the `include` directories
327+
if (java_home.EndsWith ("jre", StringComparison.OrdinalIgnoreCase)) {
328+
java_home = Path.GetDirectoryName (java_home);
329+
}
330+
});
331+
return java_home;
332+
}
333+
224334
void Exec (string java, string arguments, DataReceivedEventHandler output)
225335
{
226336
Log.LogMessage (MessageImportance.Low, $" Tool {java} execution started with arguments: {arguments}");
@@ -346,5 +456,25 @@ IEnumerable<string> FindLibrariesInDirectory (string dir, string libraryName)
346456
var library = string.Format (OS.NativeLibraryFormat, libraryName);
347457
return Directory.EnumerateFiles (dir, library, SearchOption.AllDirectories);
348458
}
459+
460+
class JdkComparisonInfo {
461+
public string Path;
462+
public Version Version;
463+
}
464+
}
465+
466+
class ComparisonComparer<T> : IComparer<T> {
467+
468+
Comparison<T> comparison;
469+
470+
public ComparisonComparer (Comparison<T> comparison)
471+
{
472+
this.comparison = comparison;
473+
}
474+
475+
public int Compare (T x, T y)
476+
{
477+
return comparison (x, y);
478+
}
349479
}
350480
}

0 commit comments

Comments
 (0)