diff --git a/.gitignore b/.gitignore index 67057f9425..6a81e3cf74 100644 --- a/.gitignore +++ b/.gitignore @@ -124,3 +124,5 @@ generated/ /app/windows/obj /java/gradle/build /java/gradle/example/.processing + +libProcessing/ffi/include/* \ No newline at end of file diff --git a/BUILD.md b/BUILD.md index a7176776a2..e44112537a 100644 --- a/BUILD.md +++ b/BUILD.md @@ -12,9 +12,9 @@ First, [download the IntelliJ IDEA Community Edition](https://www.jetbrains.com/ 1. Clone the Processing4 repository to your machine locally 1. Open the cloned repository in IntelliJ IDEA CE 1. When prompted, select **Trust Project**. You can preview the project in Safe Mode but you won't be able to build Processing. -1. IntelliJ may ask if you want to load Gradle project. If you allow this, make sure you are using JDK version 17. +1. IntelliJ may ask if you want to load Gradle project. If you allow this, make sure you are using JDK version 25. 1. In the main menu, go to File > Project Structure > Project Settings > Project. -1. In the SDK Dropdown option, select a JDK version 17 or Download the jdk +1. In the SDK Dropdown option, select a JDK version 25 or Download the jdk 1. Click the green Run Icon in the top right of the window. This is also where you can find the option to debug Processing. 1. Logs can be found in the `Build` or `Debug` pane on the bottom left of the window @@ -46,18 +46,43 @@ If you don't have them installed, you will need to install [Git](https://git-scm cd processing4 ``` -2. **Install Temurin JDK 17:** - - Download and install the appropriate version for your platform: +2. **Install Temurin JDK 25:** + +Processing requires the Temurin distribution of OpenJDK. - - [Linux (x86)](https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.15%2B6/OpenJDK17U-jdk_x64_linux_hotspot_17.0.15_6.tar.gz) - - [macOS (Apple Silicon)](https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.15%2B6/OpenJDK17U-jdk_aarch64_mac_hotspot_17.0.15_6.pkg) - - [Other platforms](https://adoptium.net/temurin/releases/?package=jdk&version=17&os=any&arch=any) +You can download it from [Adoptium](https://adoptium.net/), from [GitHub releases](https://github.com/adoptium/temurin25-binaries/releases), +or find it in the package manager for your platform. + +### macOS: +```bash +brew install --cask temurin@25 +```` + +### Windows (using winget): +```bash +winget install --id=EclipseAdoptium.Temurin.25.JDK -e +``` + +### SDKMAN! + +[SDKMAN!](https://sdkman.io/) is a useful tool for developers working on multiple versions of the JVM. + +## Install `jextract` + +`jextract` is a tool included in the JDK that generates Java bindings from C header files. +It is required to build Processing when using WebGPU. You can download it [here](https://jdk.java.net/jextract/) +or install it using SDKMAN!: + +```bash +sdk install jextract +```` 3. **Set the `JAVA_HOME` environment variable:** +It may be necessary to set the `JAVA_HOME` environment variable to point to your Temurin JDK 25 installation. + ```bash - export JAVA_HOME=/path/to/temurin/jdk-17.0.15+6/ + export JAVA_HOME=/path/to/temurin/jdk-25/ ``` ### Build, Run, and Package Processing @@ -136,18 +161,18 @@ If you are specifically trying to run the `Processing CLI`, you can test command If you’re building Processing using IntelliJ IDEA and something’s not working, here are a few things that might help: -### Use the Correct JDK (temurin-17) +### Use the Correct JDK (temurin-25) -Make sure IntelliJ is using **temurin-17**, not another version. Some users have reported issues with ms-17. +Make sure IntelliJ is using **temurin-25**, not another version. 1. Go to **File > Project Structure > Project** -2. Set the **Project SDK** to: `temurin-17 java version "17.0.15"` +2. Set the **Project SDK** to: `temurin-25"` ![JDK Selection](.github/media/troubleshooting-Intellij-setting-djk-version-manually.png) If it is not already installed, you can download it by: 1. Clicking the SDK input field and then selecting the `Download JDK...` option from the menu -2. Select Version: `17`, Vendor: `Eclipse Temurin (AdoptOpenJDK HotSpot)` +2. Select Version: `25`, Vendor: `Eclipse Temurin (AdoptOpenJDK HotSpot)` ![JDK Download](.github/media/troubleshooting-Intellij-download-jdk.png) @@ -155,7 +180,6 @@ If it is not already installed, you can download it by: Now go back to your main window and 1. Click the green Run Icon in the top right of the window. - ### “Duplicate content roots detected” You may see this warning in IntelliJ: diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 0d3fcbd12d..0dd13783ca 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -4,9 +4,7 @@ import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform import org.jetbrains.compose.desktop.application.dsl.TargetFormat import org.jetbrains.compose.desktop.application.tasks.AbstractJPackageTask import org.jetbrains.compose.internal.de.undercouch.gradle.tasks.download.Download -import java.io.FileOutputStream -import java.util.zip.ZipEntry -import java.util.zip.ZipOutputStream +import processing.gradle.SignResourcesTask // TODO: Update to 2.10.20 and add hot-reloading: https://github.com/JetBrains/compose-hot-reload @@ -49,14 +47,17 @@ compose.desktop { application { mainClass = "processing.app.ProcessingKt" - jvmArgs(*listOf( - Pair("processing.version", rootProject.version), - Pair("processing.revision", findProperty("revision") ?: Int.MAX_VALUE), - Pair("processing.contributions.source", "https://contributions.processing.org/contribs"), - Pair("processing.download.page", "https://processing.org/download/"), - Pair("processing.download.latest", "https://processing.org/download/latest.txt"), - Pair("processing.tutorials", "https://processing.org/tutorials/"), - ).map { "-D${it.first}=${it.second}" }.toTypedArray()) + jvmArgs( + "--enable-native-access=ALL-UNNAMED", // Required for Java 25 native library access + *listOf( + Pair("processing.version", rootProject.version), + Pair("processing.revision", findProperty("revision") ?: Int.MAX_VALUE), + Pair("processing.contributions.source", "https://contributions.processing.org/contribs"), + Pair("processing.download.page", "https://processing.org/download/"), + Pair("processing.download.latest", "https://processing.org/download/latest.txt"), + Pair("processing.tutorials", "https://processing.org/tutorials/"), + ).map { "-D${it.first}=${it.second}" }.toTypedArray() + ) nativeDistributions{ modules("jdk.jdi", "java.compiler", "jdk.accessibility", "java.management.rmi", "java.scripting", "jdk.httpserver") @@ -421,82 +422,14 @@ tasks.register("includeProcessingResources"){ finalizedBy("signResources") } -tasks.register("signResources"){ +tasks.register("signResources") { onlyIf { OperatingSystem.current().isMacOsX && compose.desktop.application.nativeDistributions.macOS.signing.sign.get() } group = "compose desktop" - val resourcesPath = composeResources("") - - // find jars in the resources directory - val jars = mutableListOf() - doFirst{ - fileTree(resourcesPath) - .matching { include("**/Info.plist") } - .singleOrNull() - ?.let { file -> - copy { - from(file) - into(resourcesPath) - } - } - fileTree(resourcesPath) { - include("**/*.jar") - exclude("**/*.jar.tmp/**") - }.forEach { file -> - val tempDir = file.parentFile.resolve("${file.name}.tmp") - copy { - from(zipTree(file)) - into(tempDir) - } - file.delete() - jars.add(tempDir) - } - fileTree(resourcesPath){ - include("**/bin/**") - include("**/*.jnilib") - include("**/*.dylib") - include("**/*aarch64*") - include("**/*x86_64*") - include("**/*ffmpeg*") - include("**/ffmpeg*/**") - exclude("jdk/**") - exclude("*.jar") - exclude("*.so") - exclude("*.dll") - }.forEach{ file -> - exec { - commandLine("codesign", "--timestamp", "--force", "--deep","--options=runtime", "--sign", "Developer ID Application", file) - } - } - jars.forEach { file -> - FileOutputStream(File(file.parentFile, file.nameWithoutExtension)).use { fos -> - ZipOutputStream(fos).use { zos -> - file.walkTopDown().forEach { fileEntry -> - if (fileEntry.isFile) { - // Calculate the relative path for the zip entry - val zipEntryPath = fileEntry.relativeTo(file).path - val entry = ZipEntry(zipEntryPath) - zos.putNextEntry(entry) - - // Copy file contents to the zip - fileEntry.inputStream().use { input -> - input.copyTo(zos) - } - zos.closeEntry() - } - } - } - } - - file.deleteRecursively() - } - file(composeResources("Info.plist")).delete() - } - - + resourcesPath.set(composeResources("")) } tasks.register("setExecutablePermissions") { description = "Sets executable permissions on binaries in Processing.app resources" diff --git a/build.gradle.kts b/build.gradle.kts index 8e7ad44a7a..55e128bfc4 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -12,6 +12,27 @@ plugins { // Can be deleted after the migration to Gradle is complete layout.buildDirectory = file(".build") +val enableWebGPU = findProperty("enableWebGPU")?.toString()?.toBoolean() ?: true + +allprojects { + tasks.withType().configureEach { + val javaVersion = if (enableWebGPU) "24" else "17" + sourceCompatibility = javaVersion + targetCompatibility = javaVersion + } + + tasks.withType().configureEach { + compilerOptions { + val kotlinTarget = if (enableWebGPU) { + org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_24 + } else { + org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17 + } + jvmTarget.set(kotlinTarget) + } + } +} + // Configure the dependencyUpdates task tasks { dependencyUpdates { diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts new file mode 100644 index 0000000000..876c922b22 --- /dev/null +++ b/buildSrc/build.gradle.kts @@ -0,0 +1,7 @@ +plugins { + `kotlin-dsl` +} + +repositories { + mavenCentral() +} diff --git a/buildSrc/src/main/kotlin/processing/gradle/CargoBuildTask.kt b/buildSrc/src/main/kotlin/processing/gradle/CargoBuildTask.kt new file mode 100644 index 0000000000..0b73d75ac6 --- /dev/null +++ b/buildSrc/src/main/kotlin/processing/gradle/CargoBuildTask.kt @@ -0,0 +1,56 @@ +package processing.gradle + +import org.gradle.api.DefaultTask +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.provider.Property +import org.gradle.api.tasks.* +import org.gradle.process.ExecOperations +import javax.inject.Inject + +abstract class CargoBuildTask : DefaultTask() { + + @get:Inject + abstract val execOperations: ExecOperations + + @get:InputDirectory + abstract val cargoWorkspaceDir: DirectoryProperty + + @get:Input + abstract val manifestPath: Property + + @get:Input + abstract val release: Property + + @get:Input + abstract val cargoPath: Property + + @get:OutputFile + abstract val outputLibrary: RegularFileProperty + + init { + group = "rust" + description = "Builds Rust library using cargo" + + // release by default + release.convention(true) + } + + @TaskAction + fun build() { + val buildType = if (release.get()) "release" else "debug" + logger.lifecycle("Building Rust library ($buildType mode)...") + + val args = mutableListOf("build") + if (release.get()) { + args.add("--release") + } + args.add("--manifest-path") + args.add(manifestPath.get()) + + execOperations.exec { + workingDir = cargoWorkspaceDir.get().asFile + commandLine = listOf(cargoPath.get()) + args + } + } +} diff --git a/buildSrc/src/main/kotlin/processing/gradle/CargoCleanTask.kt b/buildSrc/src/main/kotlin/processing/gradle/CargoCleanTask.kt new file mode 100644 index 0000000000..1caf9133bf --- /dev/null +++ b/buildSrc/src/main/kotlin/processing/gradle/CargoCleanTask.kt @@ -0,0 +1,38 @@ +package processing.gradle + +import org.gradle.api.DefaultTask +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.provider.Property +import org.gradle.api.tasks.* +import org.gradle.process.ExecOperations +import javax.inject.Inject + +abstract class CargoCleanTask : DefaultTask() { + + @get:Inject + abstract val execOperations: ExecOperations + + @get:InputDirectory + abstract val cargoWorkspaceDir: DirectoryProperty + + @get:Input + abstract val manifestPath: Property + + @get:Input + abstract val cargoPath: Property + + init { + group = "rust" + description = "Cleans Rust build artifacts" + } + + @TaskAction + fun clean() { + logger.lifecycle("Cleaning Rust build artifacts...") + + execOperations.exec { + workingDir = cargoWorkspaceDir.get().asFile + commandLine(cargoPath.get(), "clean", "--manifest-path", manifestPath.get()) + } + } +} diff --git a/buildSrc/src/main/kotlin/processing/gradle/DownloadJextractTask.kt b/buildSrc/src/main/kotlin/processing/gradle/DownloadJextractTask.kt new file mode 100644 index 0000000000..254195a38d --- /dev/null +++ b/buildSrc/src/main/kotlin/processing/gradle/DownloadJextractTask.kt @@ -0,0 +1,60 @@ +package processing.gradle + +import org.gradle.api.DefaultTask +import org.gradle.api.GradleException +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.provider.Property +import org.gradle.api.tasks.* +import java.net.URI + +abstract class DownloadJextractTask : DefaultTask() { + + @get:Input + abstract val jextractVersion: Property + + @get:Input + abstract val platform: Property + + @get:OutputDirectory + abstract val jextractDir: DirectoryProperty + + @get:Internal + abstract val downloadTarball: RegularFileProperty + + init { + group = "rust" + description = "Downloads and extracts jextract for the current platform" + } + + @TaskAction + fun download() { + val version = jextractVersion.get() + val plat = platform.get() + val fileName = "openjdk-$version" + "_${plat}_bin.tar.gz" + val downloadUrl = "https://download.java.net/java/early_access/jextract/22/6/$fileName" + val tarFile = downloadTarball.get().asFile + + if (!tarFile.exists()) { + logger.lifecycle("Downloading jextract from $downloadUrl") + try { + tarFile.outputStream().use { output -> + URI.create(downloadUrl).toURL().openStream().use { input -> + input.copyTo(output) + } + } + } catch (e: Exception) { + throw GradleException("Failed to download jextract: ${e.message}", e) + } + } + + val extractDir = jextractDir.get().asFile + logger.lifecycle("Extracting jextract to ${extractDir.parent}") + project.copy { + from(project.tarTree(tarFile)) + into(extractDir.parent) + } + + logger.lifecycle("jextract extracted to: $extractDir") + } +} diff --git a/buildSrc/src/main/kotlin/processing/gradle/GenerateJextractBindingsTask.kt b/buildSrc/src/main/kotlin/processing/gradle/GenerateJextractBindingsTask.kt new file mode 100644 index 0000000000..22fc9c240c --- /dev/null +++ b/buildSrc/src/main/kotlin/processing/gradle/GenerateJextractBindingsTask.kt @@ -0,0 +1,49 @@ +package processing.gradle + +import org.gradle.api.DefaultTask +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.provider.Property +import org.gradle.api.tasks.* +import org.gradle.process.ExecOperations +import javax.inject.Inject + +abstract class GenerateJextractBindingsTask : DefaultTask() { + + @get:Inject + abstract val execOperations: ExecOperations + + @get:InputFile + abstract val headerFile: RegularFileProperty + + @get:OutputDirectory + abstract val outputDirectory: DirectoryProperty + + @get:Input + abstract val targetPackage: Property + + @get:Input + abstract val jextractPath: Property + + init { + group = "rust" + description = "Generates Java Panama FFM bindings from C headers" + } + + @TaskAction + fun generate() { + val outDir = outputDirectory.get().asFile + outDir.mkdirs() + + logger.lifecycle("Generating Java bindings from ${headerFile.get().asFile}...") + + execOperations.exec { + commandLine( + jextractPath.get(), + "--output", outDir.absolutePath, + "--target-package", targetPackage.get(), + headerFile.get().asFile.absolutePath + ) + } + } +} diff --git a/buildSrc/src/main/kotlin/processing/gradle/JextractUtils.kt b/buildSrc/src/main/kotlin/processing/gradle/JextractUtils.kt new file mode 100644 index 0000000000..505a222a59 --- /dev/null +++ b/buildSrc/src/main/kotlin/processing/gradle/JextractUtils.kt @@ -0,0 +1,29 @@ +package processing.gradle + +object JextractUtils { + fun findUserJextract(): String? { + val jextractHome = System.getenv("JEXTRACT_HOME") ?: return null + + val isWindows = System.getProperty("os.name").lowercase().contains("windows") + val path = if (isWindows) { + "$jextractHome/bin/jextract.bat" + } else { + "$jextractHome/bin/jextract" + } + + val file = java.io.File(path) + if (file.exists()) { + return path + } + + return null + } + + fun getExecutableName(): String { + return if (System.getProperty("os.name").lowercase().contains("windows")) { + "jextract.bat" + } else { + "jextract" + } + } +} diff --git a/buildSrc/src/main/kotlin/processing/gradle/PlatformUtils.kt b/buildSrc/src/main/kotlin/processing/gradle/PlatformUtils.kt new file mode 100644 index 0000000000..f442dba985 --- /dev/null +++ b/buildSrc/src/main/kotlin/processing/gradle/PlatformUtils.kt @@ -0,0 +1,53 @@ +package processing.gradle + +import org.gradle.api.GradleException + +object PlatformUtils { + data class Platform( + val os: String, + val arch: String, + val libExtension: String, + val target: String + ) { + val libName: String + get() = if (os == "windows") "processing.$libExtension" else "libprocessing.$libExtension" + + val jextractPlatform: String + get() { + val jextractArch = if (arch == "x86_64") "x64" else arch + return "$os-$jextractArch" + } + } + + fun detect(): Platform { + val osName = System.getProperty("os.name").lowercase() + val osArch = System.getProperty("os.arch").lowercase() + + val os = when { + osName.contains("mac") || osName.contains("darwin") -> "macos" + osName.contains("win") -> "windows" + osName.contains("linux") -> "linux" + else -> throw GradleException("Unsupported OS: $osName") + } + + val arch = when { + osArch.contains("aarch64") || osArch.contains("arm") -> "aarch64" + osArch.contains("x86_64") || osArch.contains("amd64") -> "x86_64" + else -> throw GradleException("Unsupported architecture: $osArch") + } + + val libExtension = when (os) { + "macos" -> "dylib" + "windows" -> "dll" + "linux" -> "so" + else -> throw GradleException("Unknown platform: $os") + } + + return Platform(os, arch, libExtension, "$os-$arch") + } + + fun getCargoPath(): String { + return System.getenv("CARGO_HOME")?.let { "$it/bin/cargo" } + ?: "${System.getProperty("user.home")}/.cargo/bin/cargo" + } +} diff --git a/buildSrc/src/main/kotlin/processing/gradle/SignResourcesTask.kt b/buildSrc/src/main/kotlin/processing/gradle/SignResourcesTask.kt new file mode 100644 index 0000000000..91be783ea3 --- /dev/null +++ b/buildSrc/src/main/kotlin/processing/gradle/SignResourcesTask.kt @@ -0,0 +1,108 @@ +package processing.gradle + +import org.gradle.api.DefaultTask +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.tasks.InputDirectory +import org.gradle.api.tasks.TaskAction +import org.gradle.process.ExecOperations +import java.io.File +import java.io.FileOutputStream +import java.util.zip.ZipEntry +import java.util.zip.ZipOutputStream +import javax.inject.Inject + +abstract class SignResourcesTask : DefaultTask() { + + @get:Inject + abstract val execOperations: ExecOperations + + @get:InputDirectory + abstract val resourcesPath: DirectoryProperty + + init { + group = "compose desktop" + description = "Signs macOS resources (binaries and libraries) for distribution" + } + + @TaskAction + fun signResources() { + val resourcesDir = resourcesPath.get().asFile + val jars = mutableListOf() + + // Copy Info.plist if present + project.fileTree(resourcesDir) + .matching { include("**/Info.plist") } + .singleOrNull() + ?.let { file -> + project.copy { + from(file) + into(resourcesDir) + } + } + + // Extract JARs to temporary directories for signing + project.fileTree(resourcesDir) { + include("**/*.jar") + exclude("**/*.jar.tmp/**") + }.forEach { file -> + val tempDir = file.parentFile.resolve("${file.name}.tmp") + project.copy { + from(project.zipTree(file)) + into(tempDir) + } + file.delete() + jars.add(tempDir) + } + + // Sign all binaries and native libraries + project.fileTree(resourcesDir) { + include("**/bin/**") + include("**/*.jnilib") + include("**/*.dylib") + include("**/*aarch64*") + include("**/*x86_64*") + include("**/*ffmpeg*") + include("**/ffmpeg*/**") + exclude("jdk/**") + exclude("*.jar") + exclude("*.so") + exclude("*.dll") + }.forEach { file -> + execOperations.exec { + commandLine( + "codesign", + "--timestamp", + "--force", + "--deep", + "--options=runtime", + "--sign", + "Developer ID Application", + file + ) + } + } + + // Repackage JARs after signing + jars.forEach { file -> + FileOutputStream(File(file.parentFile, file.nameWithoutExtension)).use { fos -> + ZipOutputStream(fos).use { zos -> + file.walkTopDown().forEach { fileEntry -> + if (fileEntry.isFile) { + val zipEntryPath = fileEntry.relativeTo(file).path + val entry = ZipEntry(zipEntryPath) + zos.putNextEntry(entry) + fileEntry.inputStream().use { input -> + input.copyTo(zos) + } + zos.closeEntry() + } + } + } + } + file.deleteRecursively() + } + + // Clean up Info.plist + File(resourcesDir, "Info.plist").delete() + } +} diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 8f7211b131..30a79fcc44 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -1,4 +1,5 @@ import com.vanniktech.maven.publish.SonatypeHost +import processing.gradle.* plugins { id("java") @@ -11,10 +12,16 @@ repositories { maven { url = uri("https://jogamp.org/deployment/maven") } } +val enableWebGPU = findProperty("enableWebGPU")?.toString()?.toBoolean() ?: true + sourceSets{ main{ java{ srcDirs("src") + if (!enableWebGPU) { + exclude("processing/webgpu/**") + exclude("processing/ffi/**") + } } resources{ srcDirs("src") @@ -35,6 +42,122 @@ dependencies { testImplementation(libs.junit) } +if (enableWebGPU) { + val currentPlatform = PlatformUtils.detect() + val libProcessingDir = file("${project.rootDir}/libProcessing") + val rustTargetDir = file("$libProcessingDir/target") + val nativeOutputDir = file("${layout.buildDirectory.get()}/native/${currentPlatform.target}") + + val buildRustRelease by tasks.registering(CargoBuildTask::class) { + cargoWorkspaceDir.set(libProcessingDir) + manifestPath.set("ffi/Cargo.toml") + release.set(true) + cargoPath.set(PlatformUtils.getCargoPath()) + outputLibrary.set(file("$rustTargetDir/release/${currentPlatform.libName}")) + + inputs.files(fileTree("$libProcessingDir/ffi/src")) + inputs.file("$libProcessingDir/ffi/Cargo.toml") + inputs.file("$libProcessingDir/ffi/build.rs") + inputs.file("$libProcessingDir/ffi/cbindgen.toml") + inputs.files(fileTree("$libProcessingDir/renderer/src")) + inputs.file("$libProcessingDir/renderer/Cargo.toml") + inputs.file("$libProcessingDir/Cargo.toml") + outputs.file("$libProcessingDir/ffi/include/processing.h") + } + + val copyNativeLibs by tasks.registering(Copy::class) { + group = "rust" + description = "Copy processing library to build directory" + + dependsOn(buildRustRelease) + + from("$rustTargetDir/release") { + include(currentPlatform.libName) + } + + into(nativeOutputDir) + } + + val bundleNativeLibs by tasks.registering(Copy::class) { + group = "rust" + description = "Bundle native library into resources" + + dependsOn(copyNativeLibs) + + from(nativeOutputDir) + into("${sourceSets.main.get().output.resourcesDir}/native/${currentPlatform.target}") + } + + val cleanRust by tasks.registering(CargoCleanTask::class) { + cargoWorkspaceDir.set(libProcessingDir) + manifestPath.set("ffi/Cargo.toml") + cargoPath.set(PlatformUtils.getCargoPath()) + + mustRunAfter(buildRustRelease) + } + + tasks.named("clean") { + dependsOn(cleanRust) + } + + val generatedJavaDir = file("${layout.buildDirectory.get()}/generated/sources/jextract/java") + + sourceSets.main { + java.srcDirs(generatedJavaDir) + } + + val jextractVersionString = "22-jextract+6-47" + val jextractDirectory = file("${gradle.gradleUserHomeDir}/jextract-22") + val jextractTarballFile = file("${gradle.gradleUserHomeDir}/jextract-$jextractVersionString.tar.gz") + + val downloadJextract by tasks.registering(DownloadJextractTask::class) { + jextractVersion.set(jextractVersionString) + platform.set(currentPlatform.jextractPlatform) + jextractDir.set(jextractDirectory) + downloadTarball.set(jextractTarballFile) + + onlyIf { !jextractDirectory.exists() } + } + + val makeJextractExecutable by tasks.registering(Exec::class) { + group = "rust" + description = "Make jextract binary executable on Unix systems" + + dependsOn(downloadJextract) + onlyIf { !System.getProperty("os.name").lowercase().contains("windows") } + + val jextractBin = file("$jextractDirectory/bin/jextract") + commandLine("chmod", "+x", jextractBin.absolutePath) + } + + val generateJavaBindings by tasks.registering(GenerateJextractBindingsTask::class) { + dependsOn(buildRustRelease) + + val userJextract = JextractUtils.findUserJextract() + if (userJextract == null) { + dependsOn(downloadJextract, makeJextractExecutable) + } + + headerFile.set(file("$libProcessingDir/ffi/include/processing.h")) + outputDirectory.set(generatedJavaDir) + targetPackage.set("processing.ffi") + + jextractPath.set(userJextract ?: "$jextractDirectory/bin/${JextractUtils.getExecutableName()}") + } + + tasks.named("compileJava") { + dependsOn(generateJavaBindings) + } + + tasks.named("compileKotlin") { + dependsOn(generateJavaBindings) + } + + tasks.named("processResources") { + dependsOn(bundleNativeLibs) + } +} + mavenPublishing{ publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL) signAllPublications() diff --git a/core/src/processing/core/NativeLibrary.java b/core/src/processing/core/NativeLibrary.java new file mode 100644 index 0000000000..15b7331c2b --- /dev/null +++ b/core/src/processing/core/NativeLibrary.java @@ -0,0 +1,110 @@ +package processing.core; + +import java.io.*; +import java.nio.file.*; + +/** + * Handles loading of Processing's native Rust library (libprocessing). + */ +public class NativeLibrary { + private static boolean loaded = false; + private static Throwable loadError = null; + + private static final String LIBRARY_NAME = "processing"; + + // Platform + private static final String OS_NAME = System.getProperty("os.name").toLowerCase(); + private static final String OS_ARCH = System.getProperty("os.arch").toLowerCase(); + + private static final String platform; + private static final String architecture; + private static final String libraryExtension; + + static { + // platform + if (OS_NAME.contains("mac") || OS_NAME.contains("darwin")) { + platform = "macos"; + libraryExtension = "dylib"; + } else if (OS_NAME.contains("win")) { + platform = "windows"; + libraryExtension = "dll"; + } else if (OS_NAME.contains("linux")) { + platform = "linux"; + libraryExtension = "so"; + } else { + throw new UnsupportedOperationException("Unsupported OS: " + OS_NAME); + } + + // architecture + if (OS_ARCH.contains("aarch64") || OS_ARCH.contains("arm")) { + architecture = "aarch64"; + } else if (OS_ARCH.contains("x86_64") || OS_ARCH.contains("amd64")) { + architecture = "x86_64"; + } else { + throw new UnsupportedOperationException("Unsupported architecture: " + OS_ARCH); + } + + // Load the dll + try { + loadNativeLibrary(); + loaded = true; + } catch (Throwable e) { + loadError = e; + System.err.println("Warning: Failed to load Processing native library: " + e.getMessage()); + } + } + + /** + * Ensures the native library is loaded. Throws if loading failed. + */ + public static void ensureLoaded() { + if (!loaded) { + throw new RuntimeException("Native library failed to load", loadError); + } + } + + /** + * Returns whether the native library was successfully loaded. + */ + public static boolean isLoaded() { + return loaded; + } + + /** + * Returns the platform string (e.g., "macos-aarch64"). + */ + public static String getPlatform() { + return platform + "-" + architecture; + } + + /** + * Extracts and loads the native library from JAR resources. + */ + private static void loadNativeLibrary() throws IOException { + String platformTarget = platform + "-" + architecture; + String libraryFileName = "lib" + LIBRARY_NAME + "." + libraryExtension; + String resourcePath = "/native/" + platformTarget + "/" + libraryFileName; + + // check classloader for resource in jar + InputStream libraryStream = NativeLibrary.class.getResourceAsStream(resourcePath); + if (libraryStream == null) { + throw new FileNotFoundException( + "Native library not found in JAR: " + resourcePath + + " (platform: " + platformTarget + ")" + ); + } + + // extract + Path tempDir = Files.createTempDirectory("processing-native-"); + tempDir.toFile().deleteOnExit(); + + Path libraryPath = tempDir.resolve(libraryFileName); + Files.copy(libraryStream, libraryPath, StandardCopyOption.REPLACE_EXISTING); + libraryStream.close(); + + libraryPath.toFile().deleteOnExit(); + + // load! + System.load(libraryPath.toAbsolutePath().toString()); + } +} diff --git a/core/src/processing/webgpu/PWebGPU.java b/core/src/processing/webgpu/PWebGPU.java new file mode 100644 index 0000000000..304b5b26d1 --- /dev/null +++ b/core/src/processing/webgpu/PWebGPU.java @@ -0,0 +1,28 @@ +package processing.webgpu; + +import processing.core.NativeLibrary; +import processing.ffi.processing_h; + +/** + * PWebGPU provides the native interface layer for libProcessing's WebGPU support. + */ +public class PWebGPU { + + static { + NativeLibrary.ensureLoaded(); + } + + /** + * Ensure the native library is loaded. + */ + public static void ensureLoaded() { + NativeLibrary.ensureLoaded(); + } + + /** + * It's just math, silly! + */ + public static long add(long left, long right) { + return processing_h.processing_add(left, right); + } +} diff --git a/core/test/processing/webgpu/PWebGPUTest.java b/core/test/processing/webgpu/PWebGPUTest.java new file mode 100644 index 0000000000..4b77d7aa74 --- /dev/null +++ b/core/test/processing/webgpu/PWebGPUTest.java @@ -0,0 +1,15 @@ +package processing.webgpu; + +import org.junit.Test; +import static org.junit.Assert.*; + +/** + * Tests for the PWebGPU native interface. + */ +public class PWebGPUTest { + @Test + public void testAddFunction() { + long result = PWebGPU.add(2, 2); + assertEquals("2 + 2 should equal 4", 4L, result); + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 050502f4ca..3b7e92605f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] -kotlin = "2.0.20" -compose-plugin = "1.7.1" +kotlin = "2.3.0-Beta1" +compose-plugin = "1.9.0" jogl = "2.5.0" antlr = "4.13.2" jupiter = "5.12.0" diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 94113f200e..2e1113280e 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/libProcessing/Cargo.lock b/libProcessing/Cargo.lock index 331701f444..8611d649c2 100644 --- a/libProcessing/Cargo.lock +++ b/libProcessing/Cargo.lock @@ -2,14 +2,527 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" +dependencies = [ + "windows-sys 0.60.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.60.2", +] + +[[package]] +name = "bitflags" +version = "2.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" + +[[package]] +name = "cbindgen" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "975982cdb7ad6a142be15bdf84aea7ec6a9e5d4d797c004d43185b24cfe4e684" +dependencies = [ + "clap", + "heck", + "indexmap", + "log", + "proc-macro2", + "quote", + "serde", + "serde_json", + "syn", + "tempfile", + "toml", +] + +[[package]] +name = "cfg-if" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" + +[[package]] +name = "clap" +version = "4.5.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4512b90fa68d3a9932cea5184017c5d200f5921df706d45e853537dea51508f" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.5.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0025e98baa12e766c67ba13ff4695a887a1eba19569aad00a472546795bd6730" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_lex" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + [[package]] name = "ffi" version = "0.1.0" +dependencies = [ + "cbindgen", + "renderer", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "hashbrown" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "indexmap" +version = "2.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "libProcessing" version = "0.1.0" +[[package]] +name = "libc" +version = "0.2.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "log" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" + +[[package]] +name = "proc-macro2" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "renderer" version = "0.1.0" + +[[package]] +name = "rustix" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", + "serde_core", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +dependencies = [ + "fastrand", + "getrandom", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "toml_write", + "winnow", +] + +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + +[[package]] +name = "unicode-ident" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winnow" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" diff --git a/libProcessing/ffi/Cargo.toml b/libProcessing/ffi/Cargo.toml index 68c7917cd9..243f499d19 100644 --- a/libProcessing/ffi/Cargo.toml +++ b/libProcessing/ffi/Cargo.toml @@ -3,4 +3,12 @@ name = "ffi" version = "0.1.0" edition = "2024" +[lib] +name = "processing" +crate-type = ["cdylib"] + [dependencies] +renderer = { path = "../renderer" } + +[build-dependencies] +cbindgen = "0.29" diff --git a/libProcessing/ffi/build.rs b/libProcessing/ffi/build.rs new file mode 100644 index 0000000000..b9dcb3d616 --- /dev/null +++ b/libProcessing/ffi/build.rs @@ -0,0 +1,23 @@ +use std::env; +use std::path::PathBuf; + +fn main() { + let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + let output_dir = PathBuf::from(&crate_dir).join("include"); + + std::fs::create_dir_all(&output_dir).expect("Failed to create include directory"); + + let output_file = output_dir.join("processing.h"); + let config_path = PathBuf::from(&crate_dir).join("cbindgen.toml"); + + cbindgen::Builder::new() + .with_config(cbindgen::Config::from_file(&config_path).expect("Failed to load cbindgen.toml")) + .with_crate(&crate_dir) + .generate() + .expect("Unable to generate bindings") + .write_to_file(&output_file); + + println!("cargo:rerun-if-changed=src/lib.rs"); + println!("cargo:rerun-if-changed=cbindgen.toml"); + println!("cargo:warning=Generated header at: {}", output_file.display()); +} diff --git a/libProcessing/ffi/cbindgen.toml b/libProcessing/ffi/cbindgen.toml new file mode 100644 index 0000000000..923b8ac566 --- /dev/null +++ b/libProcessing/ffi/cbindgen.toml @@ -0,0 +1,12 @@ +language = "C" +pragma_once = true +include_guard = "PROCESSING_H" +autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */" +include_version = true +documentation = true +documentation_style = "c99" + +[export] +# Export all + +[export.rename] \ No newline at end of file diff --git a/libProcessing/ffi/src/lib.rs b/libProcessing/ffi/src/lib.rs index b93cf3ffd9..9381e76b59 100644 --- a/libProcessing/ffi/src/lib.rs +++ b/libProcessing/ffi/src/lib.rs @@ -1,14 +1,4 @@ -pub fn add(left: u64, right: u64) -> u64 { - left + right -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); - } -} +#[unsafe(no_mangle)] +pub extern "C" fn processing_add(left: u64, right: u64) -> u64 { + renderer::add(left, right) +} \ No newline at end of file diff --git a/libProcessing/renderer/src/lib.rs b/libProcessing/renderer/src/lib.rs index b93cf3ffd9..048c97f076 100644 --- a/libProcessing/renderer/src/lib.rs +++ b/libProcessing/renderer/src/lib.rs @@ -1,14 +1,4 @@ pub fn add(left: u64, right: u64) -> u64 { + println!("Adding {} and {} in Rust", left, right); left + right -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); - } -} +} \ No newline at end of file