Skip to content

Commit

Permalink
Simplify helper functions for native code
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
liblit committed Aug 9, 2024
1 parent 96d2a96 commit e73f808
Show file tree
Hide file tree
Showing 6 changed files with 130 additions and 140 deletions.
118 changes: 57 additions & 61 deletions build-logic/src/main/kotlin/com/ibm/wala/gradle/cast/helpers.kt
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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<AbstractLinkTask> {
/**
* 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 <T : Task> Provider<T>.configure(action: T.() -> Unit) {
get().configure(closureOf(action))
}

fun AbstractLinkTask.addCastLibrary(binary: CppBinary) {
configure(
closureOf<AbstractLinkTask> {
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<String>
) = subdirs.map { project.file("$currentJavaHome/$it/libjvm.$extension") }.find { it.exists() }!!
private fun File.findJvmLibrary(extension: String, subdirs: List<String>) =
subdirs.map { resolve("$it/libjvm.$extension") }.find { it.exists() }!!

fun AbstractLinkTask.addJvmLibrary(binary: CppBinary) {
project.dependencies(
closureOf<DependencyHandler> {
val currentJavaHome = Jvm.current().javaHome
val family = binary.targetMachine.operatingSystemFamily

fun addJvmLibrary(binary: CppBinary, linkTask: AbstractLinkTask, project: Project) =
project.dependencies(
project.closureOf<DependencyHandler> {
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}")
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,10 @@ repositories.mavenCentral()

// workaround for <https://github.com/gradle/gradle/issues/4802>
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
}
}

Expand Down
18 changes: 8 additions & 10 deletions cast/cast/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -9,17 +10,14 @@ plugins {

library {
binaries.whenElementFinalized {
compileTask.get().configure(closureOf<CppCompile> { macros["BUILD_CAST_DLL"] = "1" })
compileTask.configure { macros["BUILD_CAST_DLL"] = "1" }

this as CppSharedLibrary
linkTask
.get()
.configure(
closureOf<LinkSharedLibrary> {
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)
}
}
}
113 changes: 55 additions & 58 deletions cast/smoke_main/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -59,65 +60,61 @@ application {
}

binaries.whenElementFinalized {
(this as CppExecutable)
.linkTask
.get()
.configure(
closureOf<LinkExecutable> {
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<File>()
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! <https://github.com/wala/WALA/issues/608>
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<File>()
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! <https://github.com/wala/WALA/issues/608>
tasks.named("check").configure { dependsOn(checkSmokeMain) }
}
}
}
}
}
3 changes: 2 additions & 1 deletion cast/xlator_test/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
9 changes: 4 additions & 5 deletions core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
}
Expand Down

0 comments on commit e73f808

Please sign in to comment.