From d98c9736596e7ea029ad4c2642b62b3d88f3540c Mon Sep 17 00:00:00 2001 From: Ben Liblit Date: Wed, 31 Jul 2024 21:31:49 -0400 Subject: [PATCH] Simplify helper functions for native code The `cast/helpers.kt` script defines various functions that help us configure tasks that work with native code, such as natively compiled libraries. I ported these from Groovy to Kotlin when I was still fairly new to the latter. I've gotten better with Kotlin since then, and I now see how some of these functions could be simplified. For example, judicious use of extension methods makes some functions easier to call, and also makes it easier for those functions' implementations to access properties of the receiver object. In other cases, some function parameters can be computed from other parameters, thereby making the function easier to call correctly and harder to call incorrectly. None of this constitutes a radical change to the suite of helper functions or how they are used to configure tasks. It's just a bunch of small cleanups and simplifications from 2024-me, who knows Kotlin better than 2022-me. --- .../com/ibm/wala/gradle/cast/helpers.kt | 118 +++++++++--------- .../com/ibm/wala/gradle/project.gradle.kts | 9 +- cast/cast/build.gradle.kts | 18 ++- cast/smoke_main/build.gradle.kts | 113 ++++++++--------- cast/xlator_test/build.gradle.kts | 3 +- core/build.gradle.kts | 9 +- 6 files changed, 130 insertions(+), 140 deletions(-) diff --git a/build-logic/src/main/kotlin/com/ibm/wala/gradle/cast/helpers.kt b/build-logic/src/main/kotlin/com/ibm/wala/gradle/cast/helpers.kt index 6a3c425c58..d7519707d1 100644 --- a/build-logic/src/main/kotlin/com/ibm/wala/gradle/cast/helpers.kt +++ b/build-logic/src/main/kotlin/com/ibm/wala/gradle/cast/helpers.kt @@ -1,10 +1,12 @@ package com.ibm.wala.gradle.cast import java.io.File -import org.gradle.api.Project +import org.gradle.api.Task import org.gradle.api.artifacts.Configuration import org.gradle.api.artifacts.dsl.DependencyHandler +import org.gradle.api.provider.Provider import org.gradle.api.tasks.TaskInstantiationException +import org.gradle.api.tasks.TaskProvider import org.gradle.internal.jvm.Jvm import org.gradle.kotlin.dsl.closureOf import org.gradle.kotlin.dsl.extra @@ -18,74 +20,68 @@ import org.gradle.nativeplatform.tasks.LinkSharedLibrary // helpers for building native CAst components // -fun addCastLibrary(binary: CppBinary, linkTask: AbstractLinkTask, project: Project) { - linkTask.configure( - project.closureOf { +/** + * Configures the provided [Task] using the given action. + * + * [TaskProvider] already offers a [TaskProvider.configure] method that is compatible with Gradle's + * [task configuration avoidance APIs](https://docs.gradle.org/current/userguide/task_configuration_avoidance.html). + * Unfortunately, many of the APIs for native compilation provide access only to [Provider]<[Task]> + * instances, which have no configuration-avoiding `configure` method. Instead, the best we can do + * is to [get][Provider.get] the provided [Task], then configure it using [Task.configure]. + * + * @param action The configuration action to be applied to the task. + */ +fun Provider.configure(action: T.() -> Unit) { + get().configure(closureOf(action)) +} + +fun AbstractLinkTask.addCastLibrary(binary: CppBinary) { + configure( + closureOf { project.project(":cast:cast").tasks.named(name, LinkSharedLibrary::class.java) { - addRpath(linkTask, nativeLibraryOutput) + this@addCastLibrary.addRpath(nativeLibraryOutput) } - addJvmLibrary(binary, linkTask, project) + addJvmLibrary(binary) }) } -fun findJvmLibrary( - project: Project, - extension: String, - currentJavaHome: File, - subdirs: List -) = subdirs.map { project.file("$currentJavaHome/$it/libjvm.$extension") }.find { it.exists() }!! +private fun File.findJvmLibrary(extension: String, subdirs: List) = + subdirs.map { resolve("$it/libjvm.$extension") }.find { it.exists() }!! + +fun AbstractLinkTask.addJvmLibrary(binary: CppBinary) { + project.dependencies( + closureOf { + val currentJavaHome = Jvm.current().javaHome + val family = binary.targetMachine.operatingSystemFamily -fun addJvmLibrary(binary: CppBinary, linkTask: AbstractLinkTask, project: Project) = - project.dependencies( - project.closureOf { - val currentJavaHome = Jvm.current().javaHome - val family = binary.targetMachine.operatingSystemFamily + val (osIncludeSubdir, libJVM) = + when (family.name) { + OperatingSystemFamily.LINUX -> + "linux" to + currentJavaHome.findJvmLibrary( + "so", listOf("jre/lib/amd64/server", "lib/amd64/server", "lib/server")) + OperatingSystemFamily.MACOS -> + "darwin" to + currentJavaHome.findJvmLibrary( + "dylib", listOf("jre/lib/server", "lib/server")) + OperatingSystemFamily.WINDOWS -> "win32" to currentJavaHome.resolve("lib/jvm.lib") + else -> + throw TaskInstantiationException( + "unrecognized operating system family \"$family\"") + } - data class Details( - val osIncludeSubdir: String, - val libJVM: File, - ) - when (family.name) { - OperatingSystemFamily.LINUX -> - Details( - "linux", - findJvmLibrary( - project, - "so", - currentJavaHome, - listOf( - "jre/lib/amd64/server", - "lib/amd64/server", - "lib/server", - ))) - OperatingSystemFamily.MACOS -> - Details( - "darwin", - findJvmLibrary( - project, - "dylib", - currentJavaHome, - listOf( - "jre/lib/server", - "lib/server", - ))) - OperatingSystemFamily.WINDOWS -> - Details("win32", project.file("$currentJavaHome/lib/jvm.lib")) - else -> - throw TaskInstantiationException("unrecognized operating system family \"$family\"") - }.run { - val jniIncludeDir = "$currentJavaHome/include" - binary.compileTask - .get() - .includes(project.files(jniIncludeDir, "$jniIncludeDir/$osIncludeSubdir")) - add((binary.linkLibraries as Configuration).name, project.files(libJVM)) - addRpath(linkTask, libJVM) - } - }) + val jniIncludeDir = "$currentJavaHome/include" + binary.compileTask + .get() + .includes(project.files(jniIncludeDir, "$jniIncludeDir/$osIncludeSubdir")) + add((binary.linkLibraries as Configuration).name, project.files(libJVM)) + addRpath(libJVM) + }) +} -fun addRpath(linkTask: AbstractLinkTask, library: File) { - if (!(linkTask.project.rootProject.extra["isWindows"] as Boolean)) { - linkTask.linkerArgs.add("-Wl,-rpath,${library.parent}") +fun AbstractLinkTask.addRpath(library: File) { + if (!(project.rootProject.extra["isWindows"] as Boolean)) { + linkerArgs.add("-Wl,-rpath,${library.parent}") } } diff --git a/build-logic/src/main/kotlin/com/ibm/wala/gradle/project.gradle.kts b/build-logic/src/main/kotlin/com/ibm/wala/gradle/project.gradle.kts index a41069ed46..2589a808ba 100644 --- a/build-logic/src/main/kotlin/com/ibm/wala/gradle/project.gradle.kts +++ b/build-logic/src/main/kotlin/com/ibm/wala/gradle/project.gradle.kts @@ -19,11 +19,10 @@ repositories.mavenCentral() // workaround for eclipse.classpath.file.whenMerged { - (this as Classpath).run { - entries.forEach { - if (it is AbstractClasspathEntry && it.entryAttributes["gradle_used_by_scope"] == "test") - it.entryAttributes["test"] = true - } + this as Classpath + entries.forEach { + if (it is AbstractClasspathEntry && it.entryAttributes["gradle_used_by_scope"] == "test") + it.entryAttributes["test"] = true } } diff --git a/cast/cast/build.gradle.kts b/cast/cast/build.gradle.kts index eacfa387ff..454bc055c2 100644 --- a/cast/cast/build.gradle.kts +++ b/cast/cast/build.gradle.kts @@ -1,4 +1,5 @@ import com.ibm.wala.gradle.cast.addJvmLibrary +import com.ibm.wala.gradle.cast.configure import com.ibm.wala.gradle.cast.nativeLibraryOutput plugins { @@ -9,17 +10,14 @@ plugins { library { binaries.whenElementFinalized { - compileTask.get().configure(closureOf { macros["BUILD_CAST_DLL"] = "1" }) + compileTask.configure { macros["BUILD_CAST_DLL"] = "1" } this as CppSharedLibrary - linkTask - .get() - .configure( - closureOf { - if (targetMachine.operatingSystemFamily.isMacOs) { - linkerArgs.add("-Wl,-install_name,@rpath/${nativeLibraryOutput.name}") - } - addJvmLibrary(this@whenElementFinalized, this, project) - }) + linkTask.configure { + if (targetMachine.operatingSystemFamily.isMacOs) { + linkerArgs.add("-Wl,-install_name,@rpath/${nativeLibraryOutput.name}") + } + addJvmLibrary(this@whenElementFinalized) + } } } diff --git a/cast/smoke_main/build.gradle.kts b/cast/smoke_main/build.gradle.kts index 880a2dcc56..7b370eae18 100644 --- a/cast/smoke_main/build.gradle.kts +++ b/cast/smoke_main/build.gradle.kts @@ -1,5 +1,6 @@ import com.ibm.wala.gradle.cast.addCastLibrary import com.ibm.wala.gradle.cast.addRpath +import com.ibm.wala.gradle.cast.configure import org.gradle.api.attributes.LibraryElements.CLASSES import org.gradle.api.attributes.LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE import org.gradle.api.attributes.LibraryElements.RESOURCES @@ -59,65 +60,61 @@ application { } binaries.whenElementFinalized { - (this as CppExecutable) - .linkTask - .get() - .configure( - closureOf { - val libxlatorTest = - (if (isOptimized) xlatorTestReleaseSharedLibraryConfig - else xlatorTestDebugSharedLibraryConfig) - .singleFile - addRpath(this, libxlatorTest) - addCastLibrary(this@whenElementFinalized, this, project) - - if (isDebuggable && !isOptimized) { - val checkSmokeMain by - tasks.registering(Exec::class) { - notCompatibleWithConfigurationCache( - "https://github.com/gradle/gradle/issues/13485") - - // main executable to run for test - inputs.file(linkedFile) - executable( - object { - val toString by lazy { linkedFile.get().asFile.toString() } - - override fun toString() = toString - }) - - // xlator Java bytecode + implementation of native methods - val pathElements = project.objects.listProperty() - pathElements.addAll(files("../build/classes/java/test", libxlatorTest.parent)) - - // "primordial.txt" resource loaded during test - pathElements.add(coreResources.singleFile) - inputs.files(coreResources) - - // additional supporting Java class files - inputs.files(smokeMainExtraPathElements) - pathElements.addAll(smokeMainExtraPathElements) - - // all combined as a colon-delimited path list - argumentProviders.add { listOf(pathElements.get().joinToString(":")) } - - // log output to file, although we don"t validate it - val outFile = project.layout.buildDirectory.file("${name}.log") - outputs.file(outFile) - doFirst { - outFile.get().asFile.outputStream().let { - standardOutput = it - errorOutput = it - } - } - } - - if (!(rootProject.extra["isWindows"] as Boolean)) { - // Known to be broken on Windows, but not intentionally so. Please fix if you - // know how! - tasks.named("check").configure { dependsOn(checkSmokeMain) } + this as CppExecutable + linkTask.configure { + val libxlatorTest = + (if (isOptimized) xlatorTestReleaseSharedLibraryConfig + else xlatorTestDebugSharedLibraryConfig) + .singleFile + addRpath(libxlatorTest) + addCastLibrary(this@whenElementFinalized) + + if (isDebuggable && !isOptimized) { + val checkSmokeMain by + tasks.registering(Exec::class) { + notCompatibleWithConfigurationCache("https://github.com/gradle/gradle/issues/13485") + + // main executable to run for test + inputs.file(linkedFile) + executable( + object { + val toString by lazy { linkedFile.get().asFile.toString() } + + override fun toString() = toString + }) + + // xlator Java bytecode + implementation of native methods + val pathElements = project.objects.listProperty() + pathElements.addAll(files("../build/classes/java/test", libxlatorTest.parent)) + + // "primordial.txt" resource loaded during test + pathElements.add(coreResources.singleFile) + inputs.files(coreResources) + + // additional supporting Java class files + inputs.files(smokeMainExtraPathElements) + pathElements.addAll(smokeMainExtraPathElements) + + // all combined as a colon-delimited path list + argumentProviders.add { listOf(pathElements.get().joinToString(":")) } + + // log output to file, although we don"t validate it + val outFile = project.layout.buildDirectory.file("${name}.log") + outputs.file(outFile) + doFirst { + outFile.get().asFile.outputStream().let { + standardOutput = it + errorOutput = it } } - }) + } + + if (!(rootProject.extra["isWindows"] as Boolean)) { + // Known to be broken on Windows, but not intentionally so. Please fix if you + // know how! + tasks.named("check").configure { dependsOn(checkSmokeMain) } + } + } + } } } diff --git a/cast/xlator_test/build.gradle.kts b/cast/xlator_test/build.gradle.kts index 8b46f8fbbe..d5aeee3e9f 100644 --- a/cast/xlator_test/build.gradle.kts +++ b/cast/xlator_test/build.gradle.kts @@ -18,6 +18,7 @@ library { dependencies { implementation(projects.cast.cast) } binaries.whenElementFinalized { - addCastLibrary(this, (this as CppSharedLibrary).linkTask.get(), project) + this as CppSharedLibrary + linkTask.get().addCastLibrary(this) } } diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 46655799f6..13a8f0fc0a 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -15,11 +15,10 @@ plugins { eclipse { project.natures("org.eclipse.pde.PluginNature") classpath.file.whenMerged { - (this as Classpath).run { - entries.forEach { - if (it is AbstractClasspathEntry && it.path == "src/testSubjects/java") { - it.entryAttributes["ignore_optional_problems"] = true - } + this as Classpath + entries.forEach { + if (it is AbstractClasspathEntry && it.path == "src/testSubjects/java") { + it.entryAttributes["ignore_optional_problems"] = true } } }