Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create photon-targeting-JNI framework #1428

Merged
merged 2 commits into from
Sep 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -160,14 +160,14 @@ jobs:
- run: git fetch --tags --force
- run: |
chmod +x gradlew
./gradlew photon-targeting:build photon-lib:build -Pbuildalldesktop -i
- run: ./gradlew photon-lib:publish photon-targeting:publish -Pbuildalldesktop
./gradlew photon-targeting:build photon-lib:build -i
- run: ./gradlew photon-lib:publish photon-targeting:publish
name: Publish
env:
ARTIFACTORY_API_KEY: ${{ secrets.ARTIFACTORY_API_KEY }}
if: github.event_name == 'push' && github.repository_owner == 'photonvision'
# Copy artifacts to build/outputs/maven
- run: ./gradlew photon-lib:publish photon-targeting:publish -PcopyOfflineArtifacts -Pbuildalldesktop
- run: ./gradlew photon-lib:publish photon-targeting:publish -PcopyOfflineArtifacts
- uses: actions/upload-artifact@v4
with:
name: maven-${{ matrix.artifact-name }}
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ Note that these are case sensitive!
- `-PtgtIP`: Specifies where `./gradlew deploy` should try to copy the fat JAR to
- `-Pprofile`: enables JVM profiling

If you're cross-compiling, you'll need the wpilib toolchain installed. This can be done via Gradle: for example `./gradlew installArm64Toolchain` or `./gradlew installRoboRioToolchain`

## Out-of-Source Dependencies

PhotonVision uses the following additonal out-of-source repositories for building code.
Expand Down
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import edu.wpi.first.toolchain.*

plugins {
id "java"
id "cpp"
id "com.diffplug.spotless" version "6.24.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.3.2"
id 'edu.wpi.first.WpilibTools' version '1.3.0'
id 'com.google.protobuf' version '0.9.4' apply false
id 'edu.wpi.first.GradleJni' version '1.1.0'
}

allprojects {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/

package org.photonvision.common.hardware;

import java.io.IOException;
import org.photonvision.common.util.ShellExec;

@SuppressWarnings("unused")
public class PlatformUtils {
private static final ShellExec shell = new ShellExec(true, false);
private static final boolean isRoot = checkForRoot();

@SuppressWarnings("StatementWithEmptyBody")
private static boolean checkForRoot() {
if (Platform.isLinux()) {
try {
shell.executeBashCommand("id -u");
} catch (IOException e) {
e.printStackTrace();
}

while (!shell.isOutputCompleted()) {
// TODO: add timeout
}

if (shell.getExitCode() == 0) {
return shell.getOutput().split("\n")[0].equals("0");
}

} else {
return true;
}
return false;
}

public static boolean isRoot() {
return isRoot;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.photonvision.common.dataflow.DataChangeSource;
import org.photonvision.common.dataflow.events.DataChangeEvent;
import org.photonvision.common.hardware.Platform;
import org.photonvision.common.hardware.PlatformUtils;
import org.photonvision.common.logging.LogGroup;
import org.photonvision.common.logging.Logger;
import org.photonvision.common.util.ShellExec;
Expand Down Expand Up @@ -54,7 +55,7 @@ public void initialize(boolean shouldManage) {
var config = ConfigManager.getInstance().getConfig().getNetworkConfig();
logger.info("Setting " + config.connectionType + " with team " + config.ntServerAddress);
if (Platform.isLinux()) {
if (!Platform.isRoot()) {
if (!PlatformUtils.isRoot()) {
logger.error("Cannot manage hostname without root!");
}

Expand Down
25 changes: 25 additions & 0 deletions photon-lib/build.gradle
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
plugins {
id 'edu.wpi.first.WpilibTools' version '1.3.0'
}

import java.nio.file.Path

ext {
Expand Down Expand Up @@ -314,3 +318,24 @@ publishing {
}
}
}

// setup wpilib bundled native libs
wpilibTools.deps.wpilibVersion = wpi.versions.wpilibVersion.get()

def nativeConfigName = 'wpilibNatives'
def nativeConfig = configurations.create(nativeConfigName)

def nativeTasks = wpilibTools.createExtractionTasks {
configurationName = nativeConfigName
}

nativeTasks.addToSourceSetResources(sourceSets.test)

nativeConfig.dependencies.add wpilibTools.deps.wpilib("wpimath")
nativeConfig.dependencies.add wpilibTools.deps.wpilib("wpinet")
nativeConfig.dependencies.add wpilibTools.deps.wpilib("wpiutil")
nativeConfig.dependencies.add wpilibTools.deps.wpilib("ntcore")
nativeConfig.dependencies.add wpilibTools.deps.wpilib("cscore")
nativeConfig.dependencies.add wpilibTools.deps.wpilib("apriltag")
nativeConfig.dependencies.add wpilibTools.deps.wpilib("hal")
nativeConfig.dependencies.add wpilibTools.deps.wpilibOpenCv("frc" + wpi.frcYear.get(), wpi.versions.opencvVersion.get())
4 changes: 1 addition & 3 deletions photon-server/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,7 @@ tasks.register("buildAndCopyUI") {

run {
environment "PATH_PREFIX", "../"
}

run {
if (project.hasProperty("profile")) {
jvmArgs=[
"-Dcom.sun.management.jmxremote=true",
Expand Down Expand Up @@ -105,7 +103,7 @@ task findDeployTarget {

task deploy {
dependsOn findDeployTarget
dependsOn assemble
dependsOn 'shadowJar'

doLast {
println 'Starting deployment to ' + findDeployTarget.rmt.host
Expand Down
104 changes: 93 additions & 11 deletions photon-targeting/build.gradle
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
plugins {
id 'edu.wpi.first.WpilibTools' version '1.3.0'
}

ext {
nativeName = "photontargeting"
}

apply plugin: 'cpp'
apply plugin: 'google-test-test-suite'
apply plugin: 'edu.wpi.first.NativeUtils'
apply plugin: 'edu.wpi.first.GradleJni'

apply from: "${rootDir}/shared/config.gradle"
apply from: "${rootDir}/shared/javacommon.gradle"
Expand All @@ -19,6 +24,20 @@ nativeUtils {

sourceSets.main.java.srcDir "${projectDir}/src/generated/main/java"

// Folder whose contents will be included in the final jar
def outputsFolder = file("$buildDir/extra_resources")

// Sync task: like the copy task, but all files that exist in the destination directory will be deleted before copying files
task syncOutputsFolder(type: Sync) {
into outputsFolder
}

// And package our outputs folder into the final jar
jar {
from outputsFolder
dependsOn syncOutputsFolder
}

model {
components {
"${nativeName}"(NativeLibrarySpec) {
Expand All @@ -42,14 +61,59 @@ model {
it.tasks.withType(CppCompile) {
it.dependsOn generateProto
}
if(project.hasProperty('includePhotonTargeting')) {
lib project: ':photon-targeting', library: 'photontargeting', linkage: 'shared'
}

nativeUtils.useRequiredLibrary(it, "wpiutil_shared")
nativeUtils.useRequiredLibrary(it, "wpimath_shared")
nativeUtils.useRequiredLibrary(it, "wpinet_shared")
nativeUtils.useRequiredLibrary(it, "wpilibc_shared")
nativeUtils.useRequiredLibrary(it, "ntcore_shared")
}
"${nativeName}JNI"(JniNativeLibrarySpec) {

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'
include '**/*.cpp', '**/*.cc'
}
}
}

nativeUtils.useRequiredLibrary(it, "wpilib_shared")
nativeUtils.useRequiredLibrary(it, "apriltag_shared")
nativeUtils.useRequiredLibrary(it, "opencv_shared")
binaries.all {
lib library: nativeName, linkage: 'shared'
}

nativeUtils.useRequiredLibrary(it, "wpiutil_shared")
nativeUtils.useRequiredLibrary(it, "wpinet_shared")
nativeUtils.useRequiredLibrary(it, "ntcore_shared")
}

all {
binaries.withType(SharedLibraryBinarySpec) { binary ->
// check that we're building for the platform (per PArchOverride/wpilib plat detection)
if (binary.targetPlatform.name == jniPlatform) {

// only include release binaries (hard coded for now)
def isDebug = binary.buildType.name.contains('debug')
if (!isDebug) {
syncOutputsFolder {
// Just shove the shared library into the root of the jar output by photon-targeting:jar
from(binary.sharedLibraryFile) {
into "nativelibraries/${wpilibNativeName}/"
}
// And (not sure if this is a hack) make the jar task depend on the build task
dependsOn binary.identifier.projectScopedName
}
}
}
}
}
}
testSuites {
Expand All @@ -76,17 +140,11 @@ model {
it.tasks.withType(CppCompile) {
it.dependsOn generateProto
}
if(project.hasProperty('includePhotonTargeting')) {
lib project: ':photon-targeting', library: 'photontargeting', linkage: 'shared'
}
}

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")
}
}

Expand Down Expand Up @@ -129,3 +187,27 @@ cppHeadersZip {
into '/'
}
}

// make sure native libraries can be loaded in tests
test {
classpath += files(outputsFolder)
dependsOn syncOutputsFolder
}

// setup wpilib bundled native libs
wpilibTools.deps.wpilibVersion = wpi.versions.wpilibVersion.get()

def nativeConfigName = 'wpilibNatives'
def nativeConfig = configurations.create(nativeConfigName)

def nativeTasks = wpilibTools.createExtractionTasks {
configurationName = nativeConfigName
}

nativeTasks.addToSourceSetResources(sourceSets.test)

nativeConfig.dependencies.add wpilibTools.deps.wpilib("wpiutil")
nativeConfig.dependencies.add wpilibTools.deps.wpilib("wpimath")
nativeConfig.dependencies.add wpilibTools.deps.wpilib("wpinet")
nativeConfig.dependencies.add wpilibTools.deps.wpilib("ntcore")
nativeConfig.dependencies.add wpilibTools.deps.wpilib("hal")
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,11 @@

package org.photonvision.common.hardware;

import com.jogamp.common.os.Platform.OSType;
import edu.wpi.first.util.RuntimeDetector;
import java.io.BufferedReader;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import org.photonvision.common.util.ShellExec;

@SuppressWarnings("unused")
public enum Platform {
Expand Down Expand Up @@ -63,7 +61,6 @@ private enum OSType {
UNKNOWN
}

private static final ShellExec shell = new ShellExec(true, false);
public final String description;
public final String nativeLibraryFolderName;
public final boolean isPi;
Expand All @@ -72,7 +69,6 @@ private enum OSType {

// Set once at init, shouldn't be needed after.
private static final Platform currentPlatform = getCurrentPlatform();
private static final boolean isRoot = checkForRoot();

Platform(
String description,
Expand Down Expand Up @@ -115,10 +111,6 @@ public static String getNativeLibraryFolderName() {
return currentPlatform.nativeLibraryFolderName;
}

public static boolean isRoot() {
return isRoot;
}

public static boolean isSupported() {
return currentPlatform.isSupported;
}
Expand All @@ -131,29 +123,6 @@ public static boolean isSupported() {
private static final String UnknownPlatformString =
String.format("Unknown Platform. OS: %s, Architecture: %s", OS_NAME, OS_ARCH);

@SuppressWarnings("StatementWithEmptyBody")
private static boolean checkForRoot() {
if (isLinux()) {
try {
shell.executeBashCommand("id -u");
} catch (IOException e) {
e.printStackTrace();
}

while (!shell.isOutputCompleted()) {
// TODO: add timeout
}

if (shell.getExitCode() == 0) {
return shell.getOutput().split("\n")[0].equals("0");
}

} else {
return true;
}
return false;
}

private static Platform getCurrentPlatform() {
if (RuntimeDetector.isWindows()) {
if (RuntimeDetector.is32BitIntel()) {
Expand Down
Loading