Skip to content

Commit

Permalink
feat(gradle-inspector): Add an option to bootstrap a JDK version
Browse files Browse the repository at this point in the history
Signed-off-by: Sebastian Schuberth <sebastian@doubleopen.org>
  • Loading branch information
sschuberth committed Sep 12, 2024
1 parent f2f2f7c commit fcfab20
Show file tree
Hide file tree
Showing 4 changed files with 163 additions and 6 deletions.
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ commonsCompress = "1.27.1"
cvssCalculator = "1.4.3"
cyclonedx = "9.0.5"
diffUtils = "4.12"
discoClient = "2.0.39"
diskLruCache = "2.0.2"
exposed = "0.54.0"
flexmark = "0.64.8"
Expand Down Expand Up @@ -93,6 +94,7 @@ cyclonedx = { module = "org.cyclonedx:cyclonedx-core-java", version.ref = "cyclo
detekt-api = { module = "io.gitlab.arturbosch.detekt:detekt-api", version.ref = "detektPlugin" }
detekt-test = { module = "io.gitlab.arturbosch.detekt:detekt-test", version.ref = "detektPlugin" }
diffUtils = { module = "io.github.java-diff-utils:java-diff-utils", version.ref = "diffUtils" }
discoClient = { module = "io.foojay.api:discoclient", version.ref = "discoClient" }
diskLruCache = { module = "com.jakewharton:disklrucache", version.ref = "diskLruCache" }
exposed-core = { module = "org.jetbrains.exposed:exposed-core", version.ref = "exposed" }
exposed-dao = { module = "org.jetbrains.exposed:exposed-dao", version.ref = "exposed" }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,12 @@ import org.ossreviewtoolkit.model.config.RepositoryConfiguration
import org.ossreviewtoolkit.model.createAndLogIssue
import org.ossreviewtoolkit.model.utils.DependencyGraphBuilder
import org.ossreviewtoolkit.utils.common.Os
import org.ossreviewtoolkit.utils.common.collectMessages
import org.ossreviewtoolkit.utils.common.safeMkdirs
import org.ossreviewtoolkit.utils.common.splitOnWhitespace
import org.ossreviewtoolkit.utils.common.unquote
import org.ossreviewtoolkit.utils.ort.Environment
import org.ossreviewtoolkit.utils.ort.JavaBootstrapper
import org.ossreviewtoolkit.utils.ort.ortToolsDirectory

import org.semver4j.Semver
Expand All @@ -77,6 +80,11 @@ private val GRADLE_USER_HOME = Os.env["GRADLE_USER_HOME"]?.let { File(it) } ?: O
*/
const val OPTION_GRADLE_VERSION = "gradleVersion"

/**
* The name of the option to specify the Java version to use.
*/
const val OPTION_JAVA_VERSION = "javaVersion"

/**
* The name of the option to specify the Java home to use.
*/
Expand All @@ -88,8 +96,10 @@ const val OPTION_JAVA_HOME = "javaHome"
* This package manager supports the following [options][PackageManagerConfiguration.options]:
* - *gradleVersion*: The version of Gradle to use when analyzing projects. Defaults to the version defined in the
* Gradle wrapper properties.
* - *javaVersion*: The version of Java to use when analyzing projects. By default, the same Java version as for ORT
* itself it used. Overrides `javaHome` if both are specified.
* - *javaHome*: The directory of the Java home to use when analyzing projects. By default, the same Java home as for
* ORT itself it used.
* ORT itself is used.
*/
class GradleInspector(
name: String,
Expand Down Expand Up @@ -142,7 +152,10 @@ class GradleInspector(
return initScript.apply { writeText(initScriptText) }
}

private fun GradleConnector.getOrtDependencyTreeModel(projectDir: File): OrtDependencyTreeModel =
private fun GradleConnector.getOrtDependencyTreeModel(
projectDir: File,
issues: MutableList<Issue>
): OrtDependencyTreeModel =
forProjectDirectory(projectDir).connect().use { connection ->
val stdout = ByteArrayOutputStream()
val stderr = ByteArrayOutputStream()
Expand All @@ -168,9 +181,22 @@ class GradleInspector(
addProgressListener(ProgressListener { logger.debug(it.displayName) })
}

options[OPTION_JAVA_HOME]?.also {
val javaHome = options[OPTION_JAVA_VERSION]?.let {
val requestedVersion = Semver.coerce(it)
val runningVersion = Semver.coerce(Environment().javaVersion)
if (requestedVersion != runningVersion) {
JavaBootstrapper.installJdk("TEMURIN", it)
.onFailure { e ->
issues += createAndLogIssue(managerName, e.collectMessages())
}.getOrNull()
} else {
null
}
} ?: options[OPTION_JAVA_HOME]?.let { File(it) }

javaHome?.also {
logger.info { "Setting Java home for project analysis to '$it'." }
setJavaHome(File(it))
setJavaHome(it)
}
}
.setJvmArguments(jvmArgs)
Expand Down Expand Up @@ -212,9 +238,8 @@ class GradleInspector(
gradleConnector.daemonMaxIdleTime(1, TimeUnit.SECONDS)
}

val dependencyTreeModel = gradleConnector.getOrtDependencyTreeModel(projectDir)

val issues = mutableListOf<Issue>()
val dependencyTreeModel = gradleConnector.getOrtDependencyTreeModel(projectDir, issues)

dependencyTreeModel.errors.distinct().mapTo(issues) {
createAndLogIssue(source = managerName, message = it, severity = Severity.ERROR)
Expand Down
1 change: 1 addition & 0 deletions utils/ort/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ dependencies {

implementation(libs.awsS3)
implementation(libs.commonsCompress)
implementation(libs.discoClient)

testImplementation(libs.mockk)
}
129 changes: 129 additions & 0 deletions utils/ort/src/main/kotlin/JavaBootstrapper.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/*
* Copyright (C) 2024 The ORT Project Authors (see <https://github.com/oss-review-toolkit/ort/blob/main/NOTICE>)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
* License-Filename: LICENSE
*/

package org.ossreviewtoolkit.utils.ort

import eu.hansolo.jdktools.ArchiveType
import eu.hansolo.jdktools.Latest
import eu.hansolo.jdktools.LibCType
import eu.hansolo.jdktools.Match
import eu.hansolo.jdktools.OperatingSystem
import eu.hansolo.jdktools.PackageType
import eu.hansolo.jdktools.TermOfSupport
import eu.hansolo.jdktools.util.Helper

import io.foojay.api.discoclient.DiscoClient
import io.foojay.api.discoclient.pkg.Scope

import java.io.File

import kotlin.time.measureTime
import kotlin.time.measureTimedValue

import org.apache.logging.log4j.kotlin.logger

import org.ossreviewtoolkit.utils.common.safeMkdirs
import org.ossreviewtoolkit.utils.common.unpack

object JavaBootstrapper {
private val discoClient by lazy { DiscoClient(Environment.ORT_USER_AGENT) }

/**
* Return the single top-level directory contained in this directory, if any, or return this directory otherwise.
*/
private fun File.singleContainedDirectoryOrThis() =
walk().maxDepth(1).filter { it != this && it.isDirectory }.singleOrNull() ?: this

/**
* Install a JDK matching [distributionName] and [version] below [ortToolsDirectory] and return its directory on
* success, or an exception on failure.
*/
fun installJdk(distributionName: String, version: String): Result<File> {
val versionResult = eu.hansolo.jdktools.versioning.Semver.fromText(version)
if (versionResult.error1 != null) return Result.failure(versionResult.error1)

val semVer = versionResult.semver1

logger.info { "Setting up JDK '$distributionName' in version '$semVer'..." }

val operatingSystem = Helper.getOperatingSystem()
val architecture = Helper.getArchitecture()

val libcType = when (operatingSystem) {
OperatingSystem.LINUX -> LibCType.GLIBC
OperatingSystem.LINUX_MUSL, OperatingSystem.ALPINE_LINUX -> LibCType.MUSL
else -> LibCType.NONE
}

val pkgs = discoClient.getPkgs(
/* distributions = */ null,
semVer.versionNumber,
Latest.PER_VERSION,
operatingSystem,
libcType,
architecture,
architecture.bitness,
if (operatingSystem == OperatingSystem.WINDOWS) ArchiveType.ZIP else ArchiveType.TAR_GZ,
PackageType.JDK,
/* javafxBundled = */ false,
/* directlyDownloadable = */ true,
listOf(semVer.releaseStatus),
TermOfSupport.NONE,
listOf(Scope.PUBLIC),
Match.ANY
)

val pkg = pkgs.sortedBy { it.id }.find { it.distributionName == distributionName }
?: return Result.failure(
IllegalArgumentException(
"No package found for JDK '$distributionName' in version '$version'."
)
)

val installDir = ortToolsDirectory.resolve("jdks").resolve(pkg.id).apply {
if (isDirectory) {
logger.info { "Not downloading the JDK again as the directory '$this' already exists." }
return Result.success(singleContainedDirectoryOrThis())
}

safeMkdirs()
}

val url = discoClient.getPkgDirectDownloadUri(pkg.id)
logger.info { "Downloading the JDK package from $url..." }

val (archive, downloadDuration) = measureTimedValue {
okHttpClient.downloadFile(url, installDir).getOrElse {
return Result.failure(it)
}
}

logger.info { "Downloading the JDK took $downloadDuration." }

val unpackDuration = measureTime { archive.unpack(installDir) }

logger.info { "Unpacking the JDK took $unpackDuration." }

if (!archive.delete()) {
logger.warn { "Unable to delete the JDK archive from '$archive'." }
}

return Result.success(installDir.singleContainedDirectoryOrThis())
}
}

0 comments on commit fcfab20

Please sign in to comment.