Feature variants (#376)

Migrate from buildSrc to build-logic. Setup feature variants.
This commit is contained in:
Edwin Jakobs
2025-09-17 01:03:02 -07:00
committed by GitHub
parent 2979963d3f
commit b7ba6f6daa
100 changed files with 424 additions and 326 deletions

View File

@@ -0,0 +1,7 @@
plugins {
`kotlin-dsl`
}
repositories {
gradlePluginPortal()
}

View File

@@ -0,0 +1,24 @@
plugins {
`kotlin-dsl`
}
val preload: SourceSet by project.sourceSets.creating
repositories {
mavenCentral()
mavenLocal()
}
val libs = extensions.getByType<VersionCatalogsExtension>().named("libs")
dependencies {
implementation(project(":orx-variant-plugin"))
implementation(libs.findLibrary("kotlin-gradle-plugin").get())
implementation(libs.findLibrary("dokka-gradle-plugin").get())
"preloadImplementation"(openrndr.application)
"preloadImplementation"(openrndr.orextensions)
}
kotlin {
compilerOptions {
freeCompilerArgs.add("-Xskip-metadata-version-check")
}
}
tasks.getByName("compileKotlin").dependsOn("compilePreloadKotlin")

View File

@@ -0,0 +1,182 @@
import org.gradle.api.DefaultTask
import org.gradle.api.Project
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.FileCollection
import org.gradle.api.file.FileType
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.*
import org.gradle.kotlin.dsl.register
import org.gradle.process.ExecOperations
import org.gradle.work.InputChanges
import java.io.File
import java.net.URLClassLoader
import javax.inject.Inject
private class CustomClassLoader(parent: ClassLoader) : ClassLoader(parent) {
fun findClass(file: File): Class<*> = defineClass(null, file.readBytes(), 0, file.readBytes().size)
}
abstract class CollectScreenshotsTask @Inject constructor() : DefaultTask() {
@get:InputDirectory
@get:PathSensitive(PathSensitivity.NAME_ONLY)
@get:SkipWhenEmpty
abstract val inputDir: DirectoryProperty
@get:InputFiles
abstract val runtimeDependencies: Property<FileCollection>
@get:OutputDirectory
abstract val outputDir: DirectoryProperty
@get:Input
@get:Optional
abstract val ignore: ListProperty<String>
@get:Inject
abstract val execOperations: ExecOperations
@TaskAction
fun execute(inputChanges: InputChanges) {
val preloadClass = File(project.rootProject.projectDir, "build-logic/orx-convention/build/classes/kotlin/preload")
require(preloadClass.exists()) {
"preload class not found: '${preloadClass.absolutePath}'"
}
// Execute demos and produce PNG files
inputChanges.getFileChanges(inputDir).forEach { change ->
if (change.fileType == FileType.DIRECTORY) return@forEach
if (change.file.extension == "class") {
var klassName = change.file.nameWithoutExtension
if (klassName.dropLast(2) in ignore.get()) {
return@forEach
}
try {
val cp = (runtimeDependencies.get().map { it.toURI().toURL() } + inputDir.get().asFile.toURI()
.toURL()).toTypedArray()
val ucl = URLClassLoader(cp)
val ccl = CustomClassLoader(ucl)
val tempClass = ccl.findClass(change.file)
klassName = tempClass.name
val klass = ucl.loadClass(klassName)
klass.getMethod("main")
} catch (e: NoSuchMethodException) {
return@forEach
}
println("Collecting screenshot for $klassName")
val imageName = klassName.replace(".", "-")
val pngFile = "${outputDir.get().asFile}/$imageName.png"
fun launchDemoProgram() {
execOperations.javaexec {
this.classpath += project.files(inputDir.get().asFile, preloadClass)
this.classpath += runtimeDependencies.get()
this.mainClass.set(klassName)
this.workingDir(project.rootProject.projectDir)
this.jvmArgs(
"-DtakeScreenshot=true",
"-DscreenshotPath=$pngFile",
"-Dorg.openrndr.exceptions=JVM",
"-Dorg.openrndr.gl3.debug=true",
"-Dorg.openrndr.gl3.delete_angle_on_exit=false"
)
}
}
// A. Create an empty image for quick tests
//File(pngFile).createNewFile()
// B. Create an actual image by running a demo program
runCatching {
launchDemoProgram()
}.onFailure {
println("Retrying $klassName after error: ${it.message}")
Thread.sleep(5000)
launchDemoProgram()
}
}
}
// List produced PNG images.
// Only executed if there are changes in the inputDir.
val demoImageBaseNames = outputDir.get().asFile.listFiles { file: File ->
file.extension == "png"
}!!.sortedBy { it.absolutePath.lowercase() }.map { it.nameWithoutExtension }
// Update readme.md using the found PNG images
val readme = File(project.projectDir, "README.md")
if (readme.exists()) {
var readmeLines = readme.readLines().toMutableList()
val screenshotsLine = readmeLines.indexOfFirst { it == "<!-- __demos__ -->" }
if (screenshotsLine != -1) {
readmeLines = readmeLines.subList(0, screenshotsLine)
}
readmeLines.add("<!-- __demos__ -->")
readmeLines.add("## Demos")
val isKotlinMultiplatform = project.plugins.hasPlugin("org.jetbrains.kotlin.multiplatform")
val demoModuleName = if (isKotlinMultiplatform) "jvmDemo" else "demo"
for (demoImageBaseName in demoImageBaseNames) {
val projectPath = project.projectDir.relativeTo(project.rootDir)
// val url = "" // for local testing
val url = "https://raw.githubusercontent.com/openrndr/orx/media/$projectPath/"
val imagePath = demoImageBaseName.dropLast(2).replace("-", "/")
val ktFilePath = "src/$demoModuleName/kotlin/$imagePath.kt"
val ktFile = File("$projectPath/$ktFilePath")
val description = if (ktFile.isFile) {
val codeLines = ktFile.readLines()
val main = codeLines.indexOfFirst { it.startsWith("fun main") }
val head = codeLines.take(main)
val start = head.indexOfLast { it.startsWith("/**") }
val end = head.indexOfLast { it.endsWith("*/") }
if ((start < end) && (end < main)) {
codeLines.subList(start + 1, end).joinToString("\n") { line ->
val trimmed = line.trimStart(' ', '*')
if(trimmed.startsWith("@see")) "" else trimmed
}
} else {
println("/** comment */ missing in $projectPath/$ktFilePath")
""
}
} else ""
readmeLines.add(
"""
|### $imagePath
|
|$description
|
|![$demoImageBaseName](${url}images/$demoImageBaseName.png)
|
|[source code]($ktFilePath)
|
""".trimMargin()
)
}
readme.delete()
readme.writeText(readmeLines.joinToString("\n"))
}
}
}
object ScreenshotsHelper {
fun collectScreenshots(
project: Project,
sourceSet: SourceSet,
config: CollectScreenshotsTask.() -> Unit
): CollectScreenshotsTask {
val task = project.tasks.register<CollectScreenshotsTask>("collectScreenshots").get()
task.outputDir.set(project.file(project.projectDir.toString() + "/images"))
task.inputDir.set(File(project.layout.buildDirectory.get().asFile, "classes/kotlin/${sourceSet.name}"))
task.runtimeDependencies.set(sourceSet.runtimeClasspath)
task.config()
task.dependsOn(sourceSet.output)
return task
}
}

View File

@@ -0,0 +1,67 @@
import org.gradle.api.DefaultTask
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.FileType
import org.gradle.api.provider.Property
import org.gradle.api.tasks.*
import org.gradle.work.ChangeType
import org.gradle.work.Incremental
import org.gradle.work.InputChanges
import org.gradle.workers.WorkerExecutor
import javax.inject.Inject
abstract class EmbedShadersTask : DefaultTask() {
@get:Incremental
@get:PathSensitive(PathSensitivity.NAME_ONLY)
@get:InputDirectory
abstract val inputDir: DirectoryProperty
@get:OutputDirectory
abstract val outputDir: DirectoryProperty
@get:Input
abstract val defaultPackage: Property<String>
@get:Input
abstract val defaultVisibility: Property<String>
@get:Input
abstract val namePrefix: Property<String>
@Inject
abstract fun getWorkerExecutor(): WorkerExecutor
init {
defaultVisibility.set("")
namePrefix.set("")
}
@TaskAction
fun execute(inputChanges: InputChanges) {
inputChanges.getFileChanges(inputDir).forEach { change ->
if (change.fileType == FileType.DIRECTORY) return@forEach
val name = "${namePrefix.get()}${change.file.nameWithoutExtension.replace("-", "_")}"
val targetFile = outputDir.file(change.normalizedPath.replace(".", "_") + ".kt").get().asFile
if (change.changeType == ChangeType.REMOVED) {
targetFile.delete()
} else {
val contents = change.file.readText()
val lines = contents.split("\n")
var packageStatement = "package ${defaultPackage.get()}\n"
val visibilityStatement =
if (defaultVisibility.get().isNotBlank()) "${defaultVisibility.get()} " else ""
val r = Regex("#pragma package ([a-z.]+)")
for (line in lines) {
val m = r.find(line.trim())
if (m != null) {
packageStatement = "package ${m.groupValues[1]}\n"
}
}
val text =
"${packageStatement}${visibilityStatement}const val $name = ${"\"\"\""}${contents}${"\"\"\""}"
targetFile.writeText(text)
}
}
}
}

View File

@@ -0,0 +1,21 @@
package org.openrndr.extra.convention
import org.gradle.api.Project
import org.gradle.kotlin.dsl.named
import org.gradle.nativeplatform.MachineArchitecture
import org.gradle.nativeplatform.OperatingSystemFamily
import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform
val currentOperatingSystemName: String = DefaultNativePlatform.getCurrentOperatingSystem().toFamilyName()
val currentArchitectureName: String = DefaultNativePlatform.getCurrentArchitecture().name
fun Project.addHostMachineAttributesToRuntimeConfigurations() {
configurations.matching {
it.name.endsWith("runtimeClasspath", ignoreCase = true)
}.configureEach {
attributes {
attribute(OperatingSystemFamily.OPERATING_SYSTEM_ATTRIBUTE, objects.named(currentOperatingSystemName))
attribute(MachineArchitecture.ARCHITECTURE_ATTRIBUTE, objects.named(currentArchitectureName))
}
}
}

View File

@@ -0,0 +1,4 @@
package org.openrndr.extra.convention
addHostMachineAttributesToRuntimeConfigurations()

View File

@@ -0,0 +1,38 @@
package org.openrndr.extra.convention
plugins {
id("org.jetbrains.dokka")
}
repositories {
mavenCentral()
}
dokka {
pluginsConfiguration.html {
customStyleSheets.from(rootProject.file("dokka/styles/extra.css"))
customAssets.from(rootProject.file("dokka/images/logo-icon.svg"))
}
dokkaSourceSets.configureEach {
skipDeprecated.set(false)
val sourcesDirectory = try {
file("src/$name/kotlin", PathValidation.EXISTS)
} catch (_: InvalidUserDataException) {
return@configureEach
}
// Specifies the location of the project source code on the Web.
// If provided, Dokka generates "source" links for each declaration.
sourceLink {
// Unix based directory relative path to the root of the project (where you execute gradle respectively).
localDirectory = sourcesDirectory
// URL showing where the source code can be accessed through the web browser
remoteUrl("https://github.com/openrndr/orx/blob/master/${moduleName.get()}/src/$name/kotlin")
// Suffix which is used to append the line number to the URL. Use #L for GitHub
remoteLineSuffix.set("#L")
}
}
}

View File

@@ -0,0 +1,152 @@
package org.openrndr.extra.convention
import ScreenshotsHelper.collectScreenshots
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
val sharedLibs = extensions.getByType(VersionCatalogsExtension::class.java).named("sharedLibs")
val openrndr = extensions.getByType(VersionCatalogsExtension::class.java).named("openrndr")
val libs = extensions.getByType(VersionCatalogsExtension::class.java).named("libs")
val shouldPublish = project.name !in setOf("openrndr-demos", "orx-git-archiver-gradle")
plugins {
java
kotlin("jvm")
`maven-publish` apply false
id("org.openrndr.extra.convention.component-metadata-rule")
id("org.openrndr.extra.convention.dokka")
signing
}
if (shouldPublish) {
apply(plugin = "maven-publish")
}
repositories {
mavenCentral()
mavenLocal()
}
group = "org.openrndr.extra"
val main: SourceSet by project.sourceSets.getting
@Suppress("UNUSED_VARIABLE")
val demo: SourceSet by project.sourceSets.creating {
val skipDemos = setOf(
"openrndr-demos",
"orx-axidraw",
"orx-midi",
"orx-minim",
"orx-realsense2",
"orx-runway",
"orx-syphon",
"orx-video-profiles",
)
if (project.name !in skipDemos) {
collectScreenshots(project, this@creating) { }
}
}
dependencies {
implementation(sharedLibs.findLibrary("kotlin-stdlib").get())
implementation(sharedLibs.findLibrary("kotlin-logging").get())
testImplementation(sharedLibs.findLibrary("kotlin-test").get())
testRuntimeOnly(sharedLibs.findLibrary("slf4j-simple").get())
"demoImplementation"(main.output.classesDirs + main.runtimeClasspath)
"demoImplementation"(openrndr.findLibrary("application").get())
"demoImplementation"(openrndr.findLibrary("orextensions").get())
"demoRuntimeOnly"(openrndr.findLibrary("gl3").get())
"demoRuntimeOnly"(sharedLibs.findLibrary("slf4j-simple").get())
}
tasks {
@Suppress("UNUSED_VARIABLE")
val test by getting(Test::class) {
if (DefaultNativePlatform.getCurrentOperatingSystem().isMacOsX) {
allJvmArgs = allJvmArgs + "-XstartOnFirstThread"
}
useJUnitPlatform()
testLogging.exceptionFormat = TestExceptionFormat.FULL
}
@Suppress("UNUSED_VARIABLE")
val javadoc by getting(Javadoc::class) {
options {
this as StandardJavadocDocletOptions
addBooleanOption("Xdoclint:none", true)
}
}
withType<KotlinCompile> {
compilerOptions {
jvmTarget.set(JvmTarget.valueOf("JVM_${libs.findVersion("jvmTarget").get().displayName.replace(".", "_")}"))
freeCompilerArgs.add("-Xexpect-actual-classes")
freeCompilerArgs.add("-Xjdk-release=${libs.findVersion("jvmTarget").get().displayName}")
apiVersion.set(KotlinVersion.valueOf("KOTLIN_${libs.findVersion("kotlinApi").get().displayName.replace(".", "_")}"))
languageVersion.set(KotlinVersion.valueOf("KOTLIN_${libs.findVersion("kotlinLanguage").get().displayName.replace(".", "_")}"))
}
}
}
java {
withJavadocJar()
withSourcesJar()
targetCompatibility = JavaVersion.valueOf("VERSION_${libs.findVersion("jvmTarget").get().displayName}")
sourceCompatibility = JavaVersion.valueOf("VERSION_${libs.findVersion("jvmTarget").get().displayName}")
}
val isReleaseVersion = !(version.toString()).endsWith("SNAPSHOT")
if (shouldPublish) {
publishing {
publications {
create<MavenPublication>("maven") {
from(components["java"])
groupId = "org.openrndr.extra"
artifactId = project.name
description = project.name
versionMapping {
allVariants {
fromResolutionResult()
}
}
pom {
name.set(project.name)
description.set(project.name)
url.set("https://openrndr.org")
developers {
developer {
id.set("edwinjakobs")
name.set("Edwin Jakobs")
email.set("edwin@openrndr.org")
}
}
licenses {
license {
name.set("BSD-2-Clause")
url.set("https://github.com/openrndr/orx/blob/master/LICENSE")
distribution.set("repo")
}
}
scm {
connection.set("scm:git:git@github.com:openrndr/orx.git")
developerConnection.set("scm:git:ssh://github.com/openrndr/orx.git")
url.set("https://github.com/openrndr/orx")
}
}
}
}
}
signing {
setRequired({ isReleaseVersion && gradle.taskGraph.hasTask("publish") })
sign(publishing.publications)
}
}

View File

@@ -0,0 +1,198 @@
package org.openrndr.extra.convention
import CollectScreenshotsTask
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask
import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile
val libs = extensions.getByType(VersionCatalogsExtension::class.java).named("libs")
val sharedLibs = extensions.getByType(VersionCatalogsExtension::class.java).named("sharedLibs")
val openrndr = extensions.getByType(VersionCatalogsExtension::class.java).named("openrndr")
val shouldPublish = project.name !in setOf("openrndr-demos")
plugins {
kotlin("multiplatform")
`maven-publish` apply false
id("org.openrndr.extra.convention.component-metadata-rule")
id("org.openrndr.extra.convention.dokka")
signing
}
if (shouldPublish) {
apply(plugin = "maven-publish")
}
repositories {
mavenCentral()
mavenLocal()
}
group = "org.openrndr.extra"
tasks.withType<KotlinCompilationTask<*>> {
compilerOptions {
apiVersion.set(KotlinVersion.valueOf("KOTLIN_${libs.findVersion("kotlinApi").get().displayName.replace(".", "_")}"))
languageVersion.set(KotlinVersion.valueOf("KOTLIN_${libs.findVersion("kotlinLanguage").get().displayName.replace(".", "_")}"))
freeCompilerArgs.add("-Xexpect-actual-classes")
}
}
tasks.withType<KotlinJvmCompile>().configureEach {
compilerOptions {
jvmTarget.set(JvmTarget.fromTarget(libs.findVersion("jvmTarget").get().displayName))
freeCompilerArgs.add("-Xjdk-release=${libs.findVersion("jvmTarget").get().displayName}")
}
}
kotlin {
jvm {
compilations {
val main by getting
val demo by creating {
associateWith(main)
tasks.register<CollectScreenshotsTask>("collectScreenshots") {
// since Kotlin 2.1.20 output.classesDirs no longer contains a single file
inputDir.set(output.classesDirs.filter { it.path.contains("classes/kotlin") }.singleFile)
runtimeDependencies.set(runtimeDependencyFiles)
outputDir.set(project.file(project.projectDir.toString() + "/images"))
dependsOn(compileTaskProvider)
}
dependencies {
runtimeOnly(openrndr.findLibrary("gl3").get())
}
}
}
testRuns["test"].executionTask {
useJUnitPlatform()
testLogging.exceptionFormat = TestExceptionFormat.FULL
}
@OptIn(ExperimentalKotlinGradlePluginApi::class)
mainRun {
classpath(kotlin.jvm().compilations.getByName("demo").output.allOutputs)
classpath(kotlin.jvm().compilations.getByName("demo").configurations.runtimeDependencyConfiguration!!)
}
}
js(IR) {
browser()
nodejs()
}
sourceSets {
val commonMain by getting {
dependencies {
implementation(libs.findLibrary("kotlin-stdlib").get())
implementation(sharedLibs.findLibrary("kotlin-logging").get())
}
}
val commonTest by getting {
dependencies {
implementation(libs.findLibrary("kotlin-test").get())
}
}
val jvmTest by getting {
dependencies {
runtimeOnly(sharedLibs.findBundle("jupiter").get())
runtimeOnly(sharedLibs.findLibrary("slf4j.simple").get())
}
}
val jvmDemo by getting {
dependencies {
implementation(openrndr.findLibrary("application").get())
implementation(openrndr.findLibrary("orextensions").get())
runtimeOnly(openrndr.findLibrary("gl3").get())
runtimeOnly(sharedLibs.findLibrary("slf4j-simple").get())
}
}
}
}
val isReleaseVersion = !(version.toString()).endsWith("SNAPSHOT")
if (shouldPublish) {
publishing {
publications {
val fjdj = tasks.register("fakeJavaDocJar", Jar::class) {
archiveClassifier.set("javadoc")
}
named("js") {
this as MavenPublication
versionMapping {
allVariants {
fromResolutionOf("jsMainResolvableDependenciesMetadata")
}
}
}
named("jvm") {
this as MavenPublication
this.artifact(fjdj)
versionMapping {
allVariants {
fromResolutionOf("jvmMainResolvableDependenciesMetadata")
}
}
}
named("kotlinMultiplatform") {
this as MavenPublication
versionMapping {
allVariants {
fromResolutionOf("commonMainResolvableDependenciesMetadata")
}
}
}
all {
this as MavenPublication
pom {
name.set(project.name)
description.set(project.name)
url.set("https://openrndr.org")
developers {
developer {
id.set("edwinjakobs")
name.set("Edwin Jakobs")
email.set("edwin@openrndr.org")
}
}
licenses {
license {
name.set("BSD-2-Clause")
url.set("https://github.com/openrndr/orx/blob/master/LICENSE")
distribution.set("repo")
}
}
scm {
connection.set("scm:git:git@github.com:openrndr/orx.git")
developerConnection.set("scm:git:ssh://github.com/openrndr/orx.git")
url.set("https://github.com/openrndr/orx")
}
}
}
}
}
signing {
setRequired({ isReleaseVersion && gradle.taskGraph.hasTask("publish") })
sign(publishing.publications)
}
}
tasks.withType<JavaExec>().matching { it.name == "jvmRun" }.configureEach {
workingDir = rootDir
val os: OperatingSystem? = DefaultNativePlatform.getCurrentOperatingSystem()
if (os?.name == "Mac OS X") {
setJvmArgs(listOf("-XstartOnFirstThread"))
}
}

View File

@@ -0,0 +1,5 @@
package org.openrndr.extra.convention
plugins {
id("orx-variant")
}

View File

@@ -0,0 +1,14 @@
package org.openrndr
import org.openrndr.extensions.SingleScreenshot
/**
* This [Preload] class is used by the [CollectScreenshots] task to inject the [SingleScreenshot] extension
*/
class Preload : ApplicationPreload() {
override fun onProgramSetup(program: Program) {
program.extend(SingleScreenshot()) {
this.outputFile = System.getProperty("screenshotPath")
}
}
}

View File

@@ -0,0 +1,17 @@
plugins {
`kotlin-dsl`
}
repositories {
mavenCentral()
}
gradlePlugin {
plugins {
create("orxVariants") {
id = "orx-variant"
implementationClass = "org.openrndr.extra.variant.plugin.VariantPlugin"
}
}
}

View File

@@ -0,0 +1,169 @@
package org.openrndr.extra.variant.plugin
import org.gradle.api.Action
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.Dependency
import org.gradle.api.attributes.Attribute
import org.gradle.api.attributes.Bundling
import org.gradle.api.attributes.Category
import org.gradle.api.attributes.LibraryElements
import org.gradle.api.attributes.Usage
import org.gradle.api.attributes.java.TargetJvmVersion
import org.gradle.api.component.AdhocComponentWithVariants
import org.gradle.api.model.ObjectFactory
import org.gradle.api.plugins.jvm.JvmComponentDependencies
import org.gradle.api.tasks.Nested
import org.gradle.api.tasks.SourceSet
import org.gradle.api.tasks.SourceSetContainer
import org.gradle.api.tasks.TaskContainer
import org.gradle.jvm.tasks.Jar
import org.gradle.kotlin.dsl.dependencies
import org.gradle.kotlin.dsl.named
import org.gradle.language.jvm.tasks.ProcessResources
import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform
import javax.inject.Inject
fun arch(arch: String = System.getProperty("os.arch")): String {
return when (arch) {
"x86-64", "x86_64", "amd64" -> "x86-64"
"arm64", "aarch64" -> "aarch64"
else -> error("unsupported arch $arch")
}
}
abstract class VariantContainer @Inject constructor(
@Inject val tasks: TaskContainer,
val apiElements: Configuration,
val runtimeElements: Configuration,
val sourceSet: SourceSet
) {
@Nested
abstract fun getDependencies(): JvmComponentDependencies
fun Dependency.withClassifier(classifier: String): String {
return "$group:$name:$version:$classifier"
}
/**
* Setup dependencies for this variant.
*/
fun dependencies(action: Action<in JvmComponentDependencies>) {
action.execute(getDependencies())
}
/**
* Specify that this variant comes with a resource bundle.
*/
fun jar(action: Action<Unit>) {
sourceSet.resources.srcDirs.add(sourceSet.java.srcDirs.first().parentFile.resolve("resources"))
sourceSet.resources.includes.add("**/*.*")
tasks.named<Jar>(sourceSet.jarTaskName).configure {
include("**/*.*")
dependsOn(tasks.named<ProcessResources>(sourceSet.processResourcesTaskName))
manifest {
//this.attributes()
}
this.from(sourceSet.resources.srcDirs)
}
runtimeElements.outgoing.artifact(tasks.named(sourceSet.jarTaskName))
action.execute(Unit)
}
}
abstract class VariantExtension(
@Inject val objectFactory: ObjectFactory,
@Inject val project: Project
) {
fun platform(os: String, arch: String, f: VariantContainer.() -> Unit) {
val sourceSets = project.extensions.getByType(SourceSetContainer::class.java)
val sourceSetArch = arch.replace("-", "_")
val nameMain = "${os}${sourceSetArch.capitalize()}Main"
val platformMain = sourceSets.create(nameMain)
val tasks = project.tasks
tasks.register(platformMain.jarTaskName, Jar::class.java) {
archiveClassifier.set("$os-$arch")
}
val configurations = project.configurations
val objects = project.objects
val main = sourceSets.getByName("main")
val mainApi = configurations.getByName(main.apiElementsConfigurationName)
val mainRuntimeOnly = configurations.getByName(main.runtimeElementsConfigurationName)
mainApi.attributes {
val osAttribute = Attribute.of("org.gradle.native.operatingSystem", String::class.java)
attribute(osAttribute, "do_not_use_me")
}
val platformMainRuntimeElements = configurations.create(platformMain.runtimeElementsConfigurationName) {
extendsFrom(mainRuntimeOnly, mainApi)
isCanBeResolved = false
isCanBeConsumed = true
val osAttribute = Attribute.of("org.gradle.native.operatingSystem", String::class.java)
val archAttribute = Attribute.of("org.gradle.native.architecture", String::class.java)
val typeAttribute = Attribute.of("org.jetbrains.kotlin.platform.type", String::class.java)
val environmentAttribute = Attribute.of("org.gradle.jvm.environment", String::class.java)
attributes {
attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage.JAVA_RUNTIME))
attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.LIBRARY))
attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements.JAR))
attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 17)
attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling.EXTERNAL))
attribute(osAttribute, os)
attribute(archAttribute, arch)
attribute(typeAttribute, "jvm")
attribute(environmentAttribute, "standard-jvm")
}
outgoing.artifact(tasks.named(main.jarTaskName))
outgoing.artifact(tasks.named(platformMain.jarTaskName))
}
val javaComponent = project.components.getByName("java") as AdhocComponentWithVariants
javaComponent.addVariantsFromConfiguration(platformMainRuntimeElements) {
platformMain.runtimeClasspath.files.add(platformMain.resources.srcDirs.first())
}
val variantContainer = objectFactory.newInstance(
VariantContainer::class.java,
platformMainRuntimeElements,
platformMainRuntimeElements,
platformMain
)
variantContainer.f()
platformMainRuntimeElements.dependencies.addAll(variantContainer.getDependencies().runtimeOnly.dependencies.get())
/*
Setup dependencies for current platform. This will make in-module tests and demos work.
*/
val currentOperatingSystemName: String = DefaultNativePlatform.getCurrentOperatingSystem().toFamilyName()
val currentArchitectureName: String = arch()
if (currentOperatingSystemName == os && currentArchitectureName == arch) {
project.dependencies {
add("testRuntimeOnly", platformMain.output)
add("demoRuntimeOnly", platformMain.output)
for (i in platformMainRuntimeElements.dependencies) {
add("testRuntimeOnly", i)
add("demoRuntimeOnly", i)
}
}
}
}
}
class VariantPlugin : Plugin<Project> {
override fun apply(target: Project) {
val project = target
project.extensions.create("variants", VariantExtension::class.java)
}
}

View File

@@ -0,0 +1,27 @@
include("orx-convention", "orx-variant-plugin")
dependencyResolutionManagement {
repositories {
mavenCentral()
mavenLocal {
include("org.openrndr")
}
}
versionCatalogs {
create("libs") {
from(files("../gradle/libs.versions.toml"))
}
// We use a regex to get the openrndr version from the primary catalog as there is no public Gradle API to parse catalogs.
val regEx = Regex("^openrndr[ ]*=[ ]*(?:\\{[ ]*require[ ]*=[ ]*)?\"(.*)\"[ ]*(?:\\})?", RegexOption.MULTILINE)
val openrndrVersion = regEx.find(File(rootDir,"../gradle/libs.versions.toml").readText())?.groupValues?.get(1) ?: error("can't find openrndr version")
create("sharedLibs") {
from("org.openrndr:openrndr-dependency-catalog:$openrndrVersion")
}
create("openrndr") {
from("org.openrndr:openrndr-module-catalog:$openrndrVersion")
}
}
}