diff --git a/README.md b/README.md index 556043437a..d847683de2 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ Note that these are case sensitive! * x86 - `-PtgtIp`: Specifies where `./gradlew deploy` should try to copy the fat JAR to - `-Pprofile`: enables JVM profiling +- `doJniCheck`: Enables checking JNI symbol matching between Java/C++ ## Building @@ -59,6 +60,10 @@ To run them, use the commands listed below. Photonlib must first be published to ~/photonvision/photonlib-cpp-examples$ ./gradlew :simulateNative ``` +## Installing cross-toolchains + +Install cross toolchains using `./gradlew installArm64Toolchain`/`./gradlew installRoboRioToolchain`. Needed for building for arm64 or rio targets. + ## Acknowledgments PhotonVision was forked from [Chameleon Vision](https://github.com/Chameleon-Vision/chameleon-vision/). Thank you to everyone who worked on the original project. diff --git a/build.gradle b/build.gradle index 3c00130fd5..47453850bc 100644 --- a/build.gradle +++ b/build.gradle @@ -1,9 +1,12 @@ plugins { + id "java" + id "cpp" id "com.diffplug.spotless" version "6.22.0" id "edu.wpi.first.NativeUtils" version "2024.6.1" apply false id "edu.wpi.first.wpilib.repositories.WPILibRepositoriesPlugin" version "2020.2" id "edu.wpi.first.GradleRIO" version "2024.1.1-beta-4" id 'edu.wpi.first.WpilibTools' version '1.3.0' + id 'edu.wpi.first.GradleJni' version '1.1.0' } allprojects { @@ -90,3 +93,8 @@ spotless { wrapper { gradleVersion '8.4' } + +import edu.wpi.first.toolchain.NativePlatforms +ext.getCurrentArch = { + return NativePlatforms.desktop +} diff --git a/photon-core/build.gradle b/photon-core/build.gradle index d5de014915..059ada791d 100644 --- a/photon-core/build.gradle +++ b/photon-core/build.gradle @@ -1,7 +1,27 @@ +plugins { + id 'edu.wpi.first.WpilibTools' version '1.3.0' +} + import java.nio.file.Path apply from: "${rootDir}/shared/common.gradle" +ext { + nativeName = "photoncore" + + main_native_libs = ["opencv_shared"] + + test_native_libs = ["opencv_shared"] + + dev_native_libs = [ + "opencv_shared", + ] +} + +def sharedCvConfigs = [photoncore: []] +def staticCvConfigs = [:] + + dependencies { // JOGL stuff (currently we only distribute for aarch64, which is Pi 4) implementation "org.jogamp.gluegen:gluegen-rt:$joglVersion" @@ -22,3 +42,119 @@ task writeCurrentVersion { } build.dependsOn writeCurrentVersion + +apply from: "${rootDir}/shared/javacpp/setupBuild.gradle" + +apply plugin: 'edu.wpi.first.GradleJni' + +def nativeOutputDir = file("$buildDir/${nativeName}_outs") +println("Saving to $nativeOutputDir") + +model { + components { + "${nativeName}JNI"(JniNativeLibrarySpec) { + baseName = nativeName + 'jni' + + enableCheckTask project.hasProperty('doJniCheck') + javaCompileTasks << compileJava + jniCrossCompileOptions << JniCrossCompileOptions(nativeUtils.wpi.platforms.roborio) + jniCrossCompileOptions << JniCrossCompileOptions(nativeUtils.wpi.platforms.linuxarm32) + jniCrossCompileOptions << JniCrossCompileOptions(nativeUtils.wpi.platforms.linuxarm64) + + sources { + cpp { + source { + srcDirs 'src/main/native/jni' + if (project.hasProperty('generatedSources')) { + srcDir generatedSources + } + include '**/*.cpp' + } + exportedHeaders { + srcDir 'src/main/native/include' + if (project.hasProperty('generatedHeaders')) { + srcDir generatedHeaders + } + include '**/*.h' + } + } + } + + binaries.all { + if (it instanceof StaticLibraryBinarySpec) { + it.buildable = false + return + } + lib library: "${nativeName}", linkage: 'static' + if (project.hasProperty('jniSplitSetup')) { + jniSplitSetup(it) + } + } + + if(project.hasProperty("jni_native_libs")) jni_native_libs.each { name -> + nativeUtils.useRequiredLibrary(it, name) + } + + appendDebugPathToBinaries(binaries) + } + } + + tasks { + def ts = $.components + project.tasks.register('copyPhotonCoreNative') { testTask-> + // println("Hello!") + def systemArch = wpilibTools.getCurrentPlatform().platformName + def sharedLibs = [] + ts.each { + // println("=========") + // println it.baseName + it.binaries.each { + def arch = it.targetPlatform.name + // println("checking $arch against $systemArch") + if (arch == systemArch && it.buildType.name == 'release' && it instanceof SharedLibraryBinarySpec) { + testTask.dependsOn it.tasks.build + + println "Selecting ${it}" + // println it.tasks + // println it.getSharedLibraryFile() + // println it.tasks.build.class + // def f = it.sharedLibraryFile.parentFile; + + + sharedLibs << it + testTask.dependsOn it + } + } + } + + doLast { + sharedLibs.each { so -> + def arch = so.targetPlatform.architecture.name + def os = so.targetPlatform.operatingSystem.name + + copy { + from so.sharedLibraryFile + // We know all should be shared libs only at this point + into file(nativeOutputDir.path + "/${nativeUtils.getPlatformPath(so)}/shared") + } + } + } + } + } +} + +// yay misdirection +// tasks.register('testDesktop') { +// dependsOn copyPhotonCoreNative +// } +// jar.dependsOn tasks.named('testDesktop') + +// jar { +// from nativeOutputDir +// println("Adding to JAR $nativeOutputDir") +// } + + +// Make sure our files get added to resources, and that build happens before JAR does +// sourceSets.main.resources.srcDirs += nativeOutputDir +// println sourceSets.main.resources.srcDirs diff --git a/photon-core/src/dev/native/cpp/devmain.cpp b/photon-core/src/dev/native/cpp/devmain.cpp new file mode 100644 index 0000000000..c98cb86d51 --- /dev/null +++ b/photon-core/src/dev/native/cpp/devmain.cpp @@ -0,0 +1,31 @@ +/* + * Copyright (C) Photon Vision. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +// Includes to test opencv linkage +#include +#include +#include + +#include "test.h" + +int main() { + // Just empty to check library linking happens + + return 1; +} diff --git a/photon-core/src/main/java/org/photonvision/jni/CalibrationHelper.java b/photon-core/src/main/java/org/photonvision/jni/CalibrationHelper.java new file mode 100644 index 0000000000..230ca72b8e --- /dev/null +++ b/photon-core/src/main/java/org/photonvision/jni/CalibrationHelper.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) Photon Vision. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.photonvision.jni; + +public class CalibrationHelper { + public static class CalResult {} + + public static native long Create(int width, int height, long overlayMatPtr, double tolerance); + + public static native long Destroy(); + + public static native CalResult Detect(long inputImg, long outputImg); + + public static void main(String[] args) { + System.load( + "/home/matt/Documents/GitHub/photonvision/photon-core/build/libs/photoncoreJNI/shared/linuxx86-64/release/libphotoncorejni.so"); + System.out.println(Create(1, 2, 3, 4)); + } +} diff --git a/photon-core/src/main/java/org/photonvision/raspi/LibCameraJNI.java b/photon-core/src/main/java/org/photonvision/raspi/LibCameraJNI.java index 8a12aeabfa..a243a47967 100644 --- a/photon-core/src/main/java/org/photonvision/raspi/LibCameraJNI.java +++ b/photon-core/src/main/java/org/photonvision/raspi/LibCameraJNI.java @@ -116,7 +116,7 @@ public static boolean isSupported() { * * @param width Camera video mode width in pixels * @param height Camera video mode height in pixels - * @param fps Camera video mode FPS + * @param rotation Asdf * @return success of creating a camera object */ public static native boolean createCamera(int width, int height, int rotation); diff --git a/photon-core/src/main/native/cpp/test.cpp b/photon-core/src/main/native/cpp/test.cpp new file mode 100644 index 0000000000..e42ed323cc --- /dev/null +++ b/photon-core/src/main/native/cpp/test.cpp @@ -0,0 +1,34 @@ +/* + * Copyright (C) Photon Vision. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "test.h" + +#include + +#include +#include +#include + +int some_test() { + cv::Mat mat = cv::imread( + "/home/matt/Documents/GitHub/photonvision/test-resources/testimages/2022/" + "WPI/FarLaunchpad13ft10in.png"); + + std::printf("mat size %i %i\n", mat.rows, mat.cols); + + return 1; +} diff --git a/photon-core/src/main/native/include/test.h b/photon-core/src/main/native/include/test.h new file mode 100644 index 0000000000..ae673c990a --- /dev/null +++ b/photon-core/src/main/native/include/test.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) Photon Vision. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +int some_test(); diff --git a/photon-core/src/main/native/jni/CalibrationHelperJni.cpp b/photon-core/src/main/native/jni/CalibrationHelperJni.cpp new file mode 100644 index 0000000000..d5766cf4bd --- /dev/null +++ b/photon-core/src/main/native/jni/CalibrationHelperJni.cpp @@ -0,0 +1,34 @@ +/* + * Copyright (C) Photon Vision. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +extern "C" { + +/* + * Class: org_photonvision_jni_CalibrationHelper + * Method: Create + * Signature: (IIJD)J + */ +JNIEXPORT jlong JNICALL +Java_org_photonvision_jni_CalibrationHelper_Create + (JNIEnv*, jclass, jint, jint, jlong, jdouble) +{ + return 0; +} + +} // extern "C" diff --git a/photon-core/src/main/native/jni/TestJni.cpp b/photon-core/src/main/native/jni/TestJni.cpp new file mode 100644 index 0000000000..69de712e6c --- /dev/null +++ b/photon-core/src/main/native/jni/TestJni.cpp @@ -0,0 +1,23 @@ +/* + * Copyright (C) Photon Vision. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include "test.h" + +extern "C" { +JNIEXPORT jint JNICALL some_native_function(void) { return some_test(); } +} // extern "C" diff --git a/photon-core/src/test/native/cpp/devmain.cpp b/photon-core/src/test/native/cpp/devmain.cpp new file mode 100644 index 0000000000..8384921a68 --- /dev/null +++ b/photon-core/src/test/native/cpp/devmain.cpp @@ -0,0 +1,25 @@ +/* + * Copyright (C) Photon Vision. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +#include "test.h" + +int main(int argc, char** argv) { + std::printf("hello!\n"); + return some_test(); +} diff --git a/photon-lib/build.gradle b/photon-lib/build.gradle index 099308f470..02b468d11b 100644 --- a/photon-lib/build.gradle +++ b/photon-lib/build.gradle @@ -11,6 +11,23 @@ ext { includePhotonTargeting = true // Include the generated Version file generatedHeaders = "src/generate/native/include" + + test_native_libs = [ + "cscore_shared", + "cameraserver_shared", + "wpilib_executable_shared", + "googletest_static", + "apriltag_shared", + "opencv_shared", + ] + + main_native_libs = [ + "wpilib_shared", + "apriltag_shared", + "opencv_shared" + ] + + dev_native_libs = [] } apply from: "${rootDir}/shared/javacpp/setupBuild.gradle" diff --git a/photon-server/build.gradle b/photon-server/build.gradle index c60c441760..124544988f 100644 --- a/photon-server/build.gradle +++ b/photon-server/build.gradle @@ -13,6 +13,9 @@ dependencies { // Needed for Javalin Runtime Logging implementation "org.slf4j:slf4j-simple:2.0.7" + + // implementation "org.photonvision:photoncore-cpp:${pubVersion}:" + jniPlatform + "@zip" + implementation "org.photonvision:photoncore-jni:${pubVersion}:" + jniPlatform; } group 'org.photonvision' diff --git a/photon-targeting/build.gradle b/photon-targeting/build.gradle index 4238e951e6..0d14b6737f 100644 --- a/photon-targeting/build.gradle +++ b/photon-targeting/build.gradle @@ -4,6 +4,23 @@ plugins { ext { nativeName = "photontargeting" + + main_native_libs = [ + "wpilib_shared", + "apriltag_shared", + "opencv_shared" + ] + + test_native_libs = [ + "cscore_shared", + "cameraserver_shared", + "wpilib_executable_shared", + "googletest_static", + "apriltag_shared", + "opencv_shared", + ] + + dev_native_libs = [] } apply from: "${rootDir}/shared/javacpp/setupBuild.gradle" diff --git a/shared/javacpp/publish.gradle b/shared/javacpp/publish.gradle index 22389e2342..6f5fa80813 100644 --- a/shared/javacpp/publish.gradle +++ b/shared/javacpp/publish.gradle @@ -1,3 +1,5 @@ +import java.security.MessageDigest + apply plugin: 'maven-publish' def outputsFolder = file("$buildDir/outputs") @@ -5,6 +7,7 @@ def outputsFolder = file("$buildDir/outputs") def baseArtifactId = nativeName def artifactGroupId = 'org.photonvision' def zipBaseName = "_GROUP_org_photonvision_${baseArtifactId}_ID_${baseArtifactId}-cpp_CLS" +def jniBaseName = "_GROUP_org_photonvision_${nativeName}_ID_${nativeName}-jni_CLS" def licenseFile = file("$rootDir/LICENCE") @@ -52,7 +55,32 @@ addTaskToCopyAllOutputs(cppHeadersZip) model { publishing { - def taskList = createComponentZipTasks($.components, [nativeName], zipBaseName, Zip, project, includeStandardZipFormat) + def taskList = createComponentZipTasks($.components, [ + nativeName, + "${nativeName}JNI" + ], zipBaseName, Zip, project, includeStandardZipFormat) + + def jniTaskList = createComponentZipTasks($.components, ["${nativeName}JNI"], jniBaseName, Jar, project, { task, value -> + value.each { binary -> + if (binary.buildable) { + if (binary instanceof SharedLibraryBinarySpec) { + task.dependsOn binary.tasks.link + def hashFile = new File(binary.sharedLibraryFile.parentFile.absolutePath, "${binary.component.baseName}.hash") + task.outputs.file(hashFile) + task.inputs.file(binary.sharedLibraryFile) + task.from(hashFile) { + into nativeUtils.getPlatformPath(binary) + } + task.doFirst { + hashFile.text = MessageDigest.getInstance("MD5").digest(binary.sharedLibraryFile.bytes).encodeHex().toString() + } + task.from(binary.sharedLibraryFile) { + into nativeUtils.getPlatformPath(binary) + } + } + } + } + }) publications { cpp(MavenPublication) { @@ -66,6 +94,15 @@ model { groupId artifactGroupId version pubVersion } + jni(MavenPublication) { + jniTaskList.each { + artifact it + } + + artifactId = "${baseArtifactId}-jni" + groupId artifactGroupId + version pubVersion + } } repositories { diff --git a/shared/javacpp/setupBuild.gradle b/shared/javacpp/setupBuild.gradle index dd8a5bec32..5f0a921b55 100644 --- a/shared/javacpp/setupBuild.gradle +++ b/shared/javacpp/setupBuild.gradle @@ -57,9 +57,45 @@ model { } } - nativeUtils.useRequiredLibrary(it, "wpilib_shared") - nativeUtils.useRequiredLibrary(it, "apriltag_shared") - nativeUtils.useRequiredLibrary(it, "opencv_shared") + main_native_libs.each { name -> + nativeUtils.useRequiredLibrary(it, name) + } + + appendDebugPathToBinaries(binaries) + } + // By default, a development executable will be generated. This is to help the case of + // testing specific functionality of the library. + "${nativeName}Dev"(NativeExecutableSpec) { + targetBuildTypes 'debug' + sources { + cpp { + source { + srcDirs 'src/dev/native/cpp' + include '**/*.cpp' + } + exportedHeaders { + srcDir 'src/main/native/include' + if (project.hasProperty('generatedHeaders')) { + srcDir generatedHeaders + } + } + } + } + binaries.all { + lib library: nativeName, linkage: 'shared' + it.tasks.withType(CppCompile) { + // it.dependsOn generateProto + } + if (project.hasProperty('exeSplitSetup')) { + exeSplitSetup(it) + } + } + + dev_native_libs.each { name -> + nativeUtils.useRequiredLibrary(it, name) + } + + appendDebugPathToBinaries(binaries) } } testSuites { @@ -88,12 +124,9 @@ model { } } - nativeUtils.useRequiredLibrary(it, "cscore_shared") - nativeUtils.useRequiredLibrary(it, "cameraserver_shared") - nativeUtils.useRequiredLibrary(it, "wpilib_executable_shared") - nativeUtils.useRequiredLibrary(it, "googletest_static") - nativeUtils.useRequiredLibrary(it, "apriltag_shared") - nativeUtils.useRequiredLibrary(it, "opencv_shared") + test_native_libs.each { name -> + nativeUtils.useRequiredLibrary(it, name) + } } } }