This repository has been archived by the owner on Oct 18, 2024. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 328
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: JDK is not recognized if only JDK 21 is installed (fixes #1616)
This also adds a preference in 'Build & run' which allows user to select the currently installed JDK version
- Loading branch information
Showing
15 changed files
with
609 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
79 changes: 79 additions & 0 deletions
79
app/src/main/java/com/itsaky/androidide/app/configuration/JdkDistributionProviderImpl.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
/* | ||
* This file is part of AndroidIDE. | ||
* | ||
* AndroidIDE is free software: you can redistribute it and/or modify | ||
* it under the terms of the GNU General Public License as published by | ||
* the Free Software Foundation, either version 3 of the License, or | ||
* (at your option) any later version. | ||
* | ||
* AndroidIDE is distributed in the hope that it will be useful, | ||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
* GNU General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU General Public License | ||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. | ||
*/ | ||
|
||
package com.itsaky.androidide.app.configuration | ||
|
||
import com.google.auto.service.AutoService | ||
import com.itsaky.androidide.models.JdkDistribution | ||
import com.itsaky.androidide.preferences.internal.javaHome | ||
import com.itsaky.androidide.utils.Environment | ||
import com.itsaky.androidide.utils.ILogger | ||
import com.itsaky.androidide.utils.JdkUtils | ||
import java.io.File | ||
|
||
/** | ||
* @author Akash Yadav | ||
*/ | ||
@AutoService(IJdkDistributionProvider::class) | ||
class JdkDistributionProviderImpl : IJdkDistributionProvider { | ||
|
||
companion object { | ||
|
||
private val log = ILogger.newInstance("JdkDistributionProviderImpl") | ||
} | ||
|
||
override val installedDistributions: List<JdkDistribution> by lazy { | ||
JdkUtils.findJavaInstallations().also { distributions -> | ||
|
||
// set the default value for the 'javaHome' preference | ||
if (javaHome.isBlank() && distributions.isNotEmpty()) { | ||
var defaultDist = distributions.find { | ||
it.javaVersion.startsWith(IJdkDistributionProvider.DEFAULT_JAVA_VERSION) | ||
} | ||
|
||
if (defaultDist == null) { | ||
// if JDK 17 is not installed, use the first available installation | ||
defaultDist = distributions[0] | ||
} | ||
|
||
javaHome = defaultDist.javaHome | ||
} | ||
|
||
val home = File(javaHome) | ||
val java = File(home, "bin/java") | ||
|
||
// the previously selected JDK distribution does not exist | ||
// check if we have other distributions installed | ||
if (!home.exists() || !java.exists() || !java.isFile) { | ||
if (distributions.isNotEmpty()) { | ||
log.warn( | ||
"Previously selected java.home does not exists! Falling back to ${distributions[0]}...") | ||
javaHome = distributions[0].javaHome | ||
} | ||
} | ||
|
||
if (!java.canExecute()) { | ||
java.setExecutable(true) | ||
} | ||
|
||
log.debug("Setting Environment.JAVA_HOME to $javaHome") | ||
|
||
Environment.JAVA_HOME = File(javaHome) | ||
Environment.JAVA = Environment.JAVA_HOME.resolve("bin/java") | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
165 changes: 165 additions & 0 deletions
165
app/src/main/java/com/itsaky/androidide/utils/JdkUtils.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,165 @@ | ||
/* | ||
* This file is part of AndroidIDE. | ||
* | ||
* AndroidIDE is free software: you can redistribute it and/or modify | ||
* it under the terms of the GNU General Public License as published by | ||
* the Free Software Foundation, either version 3 of the License, or | ||
* (at your option) any later version. | ||
* | ||
* AndroidIDE is distributed in the hope that it will be useful, | ||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
* GNU General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU General Public License | ||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>. | ||
*/ | ||
|
||
package com.itsaky.androidide.utils | ||
|
||
import androidx.annotation.VisibleForTesting | ||
import androidx.annotation.WorkerThread | ||
import com.itsaky.androidide.app.IDEApplication | ||
import com.itsaky.androidide.models.JdkDistribution | ||
import com.itsaky.androidide.shell.executeProcessAsync | ||
import com.termux.shared.termux.shell.command.environment.TermuxShellEnvironment | ||
import java.io.File | ||
import java.nio.file.Files | ||
|
||
/** | ||
* Utilities related to JDK installations. | ||
* | ||
* @author Akash Yadav | ||
*/ | ||
object JdkUtils { | ||
|
||
private val log = ILogger.newInstance("JdkUtils") | ||
|
||
/** | ||
* Finds the available JDK installations and returns the JAVA_HOME for each installation. | ||
*/ | ||
@JvmStatic | ||
@WorkerThread | ||
fun findJavaInstallations(): List<JdkDistribution> { | ||
|
||
// a valid JDK can be installed anywhere in the file system | ||
// however, we currently only check for installations that are located in $PREFIX/opt dir | ||
// TODO: Find a way to efficiently list all JDK installations, including those which are located | ||
// outside of $PREFIX/opt | ||
return try { | ||
val optDir = File(Environment.PREFIX, "opt") | ||
if (!optDir.exists() || !optDir.isDirectory) { | ||
emptyList() | ||
} else { | ||
optDir.listFiles()?.mapNotNull { dir -> | ||
if (Files.isSymbolicLink(dir.toPath())) { | ||
// ignore symbolic links | ||
return@mapNotNull null | ||
} | ||
|
||
val java = File(dir, "bin/java") | ||
if (!canExecute(java)) { | ||
// java binary does not exist or is not executable | ||
return@mapNotNull null | ||
} | ||
|
||
return@mapNotNull getDistFromJavaBin(java) | ||
} ?: run { | ||
log.error("Failed to list files in $optDir") | ||
emptyList() | ||
} | ||
} | ||
} catch (e: Exception) { | ||
log.error("Failed to list java alternatives", e) | ||
emptyList() | ||
} | ||
} | ||
|
||
private fun canExecute(file: File): Boolean { | ||
return file.exists() && file.isFile && file.canExecute() | ||
} | ||
|
||
/** | ||
* Returns a [JdkDistribution] instances representing the JDK installation of the given | ||
* `java` binary executable. This binary file is executed to extract the actual `java.home` | ||
* value. | ||
* | ||
* @param java The path to the `java` binary executable. | ||
* @return The [JdkDistribution] instance, or `null` if there was an error while getting required | ||
* information from the installation. | ||
*/ | ||
@JvmStatic | ||
fun getDistFromJavaBin(java: File): JdkDistribution? { | ||
if (!java.exists() || !java.isFile || !java.canExecute()) { | ||
log.error( | ||
"Failed to lookup JDK installation. File '$java' does not exist or cannot be executed.") | ||
return null | ||
} | ||
|
||
val properties = readProperties(java) ?: run { | ||
log.error("Failed to retrieve Java properties from java binary: '$java'") | ||
return null | ||
} | ||
|
||
return readDistFromProps(properties) | ||
} | ||
|
||
@VisibleForTesting | ||
internal fun readDistFromProps(properties: String): JdkDistribution? { | ||
val javaHome = Regex("java\\.home\\s*=\\s*(.*)").find(properties)?.groupValues?.get(1) ?: run { | ||
log.error("Failed to determine property 'java.home'. Properties:", properties) | ||
return null | ||
} | ||
|
||
log.debug("Found java.home=${javaHome}") | ||
|
||
val javaVersion = Regex("java\\.version\\s*=\\s*(.*)").find(properties)?.groupValues?.get(1) | ||
?: run { | ||
log.error("Failed to determine property 'java.version'. Properties:", properties) | ||
return null | ||
} | ||
|
||
log.debug("Found java.version=${javaVersion}") | ||
|
||
return JdkDistribution(javaVersion, javaHome) | ||
} | ||
|
||
/** | ||
* Returns a [JdkDistribution] instance representing the JDK installation at the given | ||
* location. | ||
* | ||
* @param javaHome The path to the installed JDK. | ||
* @return The [JdkDistribution] instance, or `null` if there was an error while getting required | ||
* information from the installation. | ||
*/ | ||
@JvmStatic | ||
fun getDistFromJavaHome(javaHome: File): JdkDistribution? { | ||
return getDistFromJavaBin(javaHome.resolve("bin/java")) | ||
} | ||
|
||
private fun readProperties(file: File): String? { | ||
val propsCmd = "${file.absolutePath} -XshowSettings:properties -version" | ||
val process = executeWithBash(propsCmd) ?: return null | ||
return process.inputStream.bufferedReader().readText() | ||
} | ||
|
||
@WorkerThread | ||
private fun executeWithBash(cmd: String): Process? { | ||
val shell = Environment.BASH_SHELL | ||
|
||
if (!canExecute(shell)) { | ||
log.warn( | ||
"Unable to determine JDK installations. Command ${shell.absolutePath} not found or is not executable.") | ||
return null | ||
} | ||
|
||
val env = HashMap(TermuxShellEnvironment().getEnvironment(IDEApplication.instance, false)) | ||
|
||
return executeProcessAsync { | ||
command = listOf(shell.absolutePath, "-c", cmd) | ||
environment = env | ||
redirectErrorStream = true | ||
workingDirectory = Environment.HOME | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.