You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
The logic used to find a JDK in various tool operations is unclear and confusing #124252
Investigation into #122609 revealed that the logic used to to find a java binary for Java-dependent activites was inconsistent across those activities. For more about that logic and how it was inconsistent, see this comment from #122600. While this issue was fixed by making the implementations of Java-finding logic consistent across the tool, this consistency is fragile; because these implementations exist independently of each other. This also isn't the first time this has been noticed (see #9429).
The tool code should be refactored to 1) make JDK-finding logic as explicit as possible1 and 2) make JDK-finding logic as consistent as possible. This should reduce the chance of future confusion and/or introduction of bugs.
For example, gradlew and Android SDK tools (such as avdmanager and sdkmanager) look for Java by first consulting JAVA_HOME on the environment and then java on the path. When invoking those tools, the flutter tool will set JAVA_HOME to the path of the Android Studio-bundled JDK if it exists. This means that the order in which we look for the JDK for these operations is 1) the Android-Studio bundled JDK, 2) JAVA_HOME, and then 3) java on PATH (which java). However, the existence of 2 and 3 is not obvious to those reading only the flutter tool code. ↩
I think one of the things that's made this a bit tricky, and led to the existing situation where we don't simply use the same logic everywhere, is that there are a couple of different kinds of ways that we invoke a JDK:
In some places (1a and 2 in your list), we run a java executable directly. These are a good fit for the existing findJavaBinary method, which provides the path of a java executable to run.
In other places (1b, 3, 4), we run a wrapper script that finds and runs java. (Either the Gradle wrapper gradlew, or the Android SDK command-line tools like avdmanager and sdkmanager.) We can't directly tell these what JDK to use in the form of a path to the java executable, so findJavaBinary doesn't directly help.
We can and should tell those wrapper scripts how to find the exact same JDK we use elsewhere. But the way we tell them will just have to be a bit more subtle.
The key is that all of those wrapper scripts have the same logic for how they find what JDK to use. They have verbatim the same code for it, in fact (#122609 (comment)) — but even if they didn't, the core logic has I think been standard in the Java world for a long time. That logic is:
Look for the environment variable JAVA_HOME. If there's an executable at "$JAVA_HOME"/bin/java, then use that as the java command to run.
Else fall back to looking for a java command on the PATH.
So to get those wrapper scripts to use the JDK we want them to, we just have to set JAVA_HOME and/or PATH appropriately.
There are several possible ways to do that, but I think the cleanest approach is:
Observe that our findJavaBinary method already has the same structure:
First it looks for a directory that could be used as a JAVA_HOME: a directory that should have a subfolder bin with a file java that is a suitable java command. (Its first choice for this is androidStudio?.javaPath, i.e. a JDK folder found within Android Studio.)
If that fails, it looks for a java command on the PATH.
So, refactor findJavaBinary to make that structure explicit: there's a findJavaHome that returns a directory that could be used as a JAVA_HOME. Then the implementation of findJavaBinary reduces to
except it's a few lines longer than that in order to retain the printTrace calls.
Then, when running any Java-world wrapper script, including Gradle or the Android SDK command-line tools, do so with a call like:
finalString? javaHome =findJavaHome(/* … */);
await _processUtils.run(
args,
environment: { 'JAVA_HOME': javaHome ??'' }, // let PATH pass through from the tool's own environment
);
(Probably encapsulate that into a helper that lives next to findJavaHome and findJavaBinary, so that the call actually looks more like await _processUtils.run(args, environment: findJavaEnvironment(/* … */));.)
This will cause the wrapper script to use exactly the same java executable as findJavaBinary would return: if findJavaHome finds something, it will use that, and if not then it will find java on the PATH.
This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of flutter doctor -v and a minimal reproduction of the issue.
Activity
andrewkolos commentedon Apr 5, 2023
Replaced placeholder text.
gnprice commentedon Apr 7, 2023
Thanks for filing this!
I think one of the things that's made this a bit tricky, and led to the existing situation where we don't simply use the same logic everywhere, is that there are a couple of different kinds of ways that we invoke a JDK:
javaexecutable directly. These are a good fit for the existingfindJavaBinarymethod, which provides the path of ajavaexecutable to run.java. (Either the Gradle wrappergradlew, or the Android SDK command-line tools likeavdmanagerandsdkmanager.) We can't directly tell these what JDK to use in the form of a path to thejavaexecutable, sofindJavaBinarydoesn't directly help.We can and should tell those wrapper scripts how to find the exact same JDK we use elsewhere. But the way we tell them will just have to be a bit more subtle.
The key is that all of those wrapper scripts have the same logic for how they find what JDK to use. They have verbatim the same code for it, in fact (#122609 (comment)) — but even if they didn't, the core logic has I think been standard in the Java world for a long time. That logic is:
"$JAVA_HOME"/bin/java, then use that as thejavacommand to run.javacommand on the PATH.So to get those wrapper scripts to use the JDK we want them to, we just have to set JAVA_HOME and/or PATH appropriately.
There are several possible ways to do that, but I think the cleanest approach is:
findJavaBinarymethod already has the same structure:binwith a filejavathat is a suitablejavacommand. (Its first choice for this isandroidStudio?.javaPath, i.e. a JDK folder found within Android Studio.)javacommand on the PATH.findJavaBinaryto make that structure explicit: there's afindJavaHomethat returns a directory that could be used as a JAVA_HOME. Then the implementation offindJavaBinaryreduces toprintTracecalls.findJavaHomeandfindJavaBinary, so that the call actually looks more likeawait _processUtils.run(args, environment: findJavaEnvironment(/* … */));.)javaexecutable asfindJavaBinarywould return: iffindJavaHomefinds something, it will use that, and if not then it will findjavaon the PATH.abarke commentedon Apr 25, 2023
+1 I ran into this today... real pain #106416 (comment)
As a flutter noob with first impressions, this doesn't feel good and doesn't provide me with much confidence.
[tools] use Java class for all java-searching behavior (#127354)
github-actions commentedon Jun 15, 2023
This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of
flutter doctor -vand a minimal reproduction of the issue.