diff --git a/apriltag/src/main/java/edu/wpi/first/apriltag/jni/AprilTagJNI.java b/apriltag/src/main/java/edu/wpi/first/apriltag/jni/AprilTagJNI.java index 87c61e8279c..2c6837d49c9 100644 --- a/apriltag/src/main/java/edu/wpi/first/apriltag/jni/AprilTagJNI.java +++ b/apriltag/src/main/java/edu/wpi/first/apriltag/jni/AprilTagJNI.java @@ -18,8 +18,8 @@ public class AprilTagJNI { static boolean libraryLoaded = false; /** Sets whether JNI should be loaded in the static block. */ - public static class Helper { - private static AtomicBoolean extractOnStaticLoad = new AtomicBoolean(true); + public static final class Helper { + private static AtomicBoolean extractOnStaticLoad = new AtomicBoolean(false); /** * Returns true if the JNI should be loaded in the static block. @@ -55,6 +55,19 @@ private Helper() {} } } + /** + * Force load the library. + * + * @throws IOException if library load failed + */ + public static synchronized void forceLoad() throws IOException { + if (libraryLoaded) { + return; + } + RuntimeLoader.loadLibrary("apriltagjni"); + libraryLoaded = true; + } + /** * Constructs an AprilTag detector engine. * diff --git a/apriltag/src/test/java/edu/wpi/first/apriltag/AprilTagJniTestExtension.java b/apriltag/src/test/java/edu/wpi/first/apriltag/AprilTagJniTestExtension.java new file mode 100644 index 00000000000..e5345103b2c --- /dev/null +++ b/apriltag/src/test/java/edu/wpi/first/apriltag/AprilTagJniTestExtension.java @@ -0,0 +1,40 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +package edu.wpi.first.apriltag; + +import edu.wpi.first.apriltag.jni.AprilTagJNI; +import edu.wpi.first.util.WPIUtilJNI; +import java.io.IOException; +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ExtensionContext.Namespace; + +public final class AprilTagJniTestExtension implements BeforeAllCallback { + private static ExtensionContext getRoot(ExtensionContext context) { + return context.getParent().map(AprilTagJniTestExtension::getRoot).orElse(context); + } + + @Override + public void beforeAll(ExtensionContext context) throws Exception { + getRoot(context) + .getStore(Namespace.GLOBAL) + .getOrComputeIfAbsent( + "April Tag Initialized", + key -> { + initializeNatives(); + return true; + }, + Boolean.class); + } + + private void initializeNatives() { + try { + WPIUtilJNI.forceLoad(); + AprilTagJNI.forceLoad(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/apriltag/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension b/apriltag/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension new file mode 100644 index 00000000000..149d61ac0e7 --- /dev/null +++ b/apriltag/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension @@ -0,0 +1 @@ +edu.wpi.first.apriltag.AprilTagJniTestExtension diff --git a/cscore/src/main/java/edu/wpi/first/cscore/CameraServerJNI.java b/cscore/src/main/java/edu/wpi/first/cscore/CameraServerJNI.java index b0ac27aef53..ec2b79ee1eb 100644 --- a/cscore/src/main/java/edu/wpi/first/cscore/CameraServerJNI.java +++ b/cscore/src/main/java/edu/wpi/first/cscore/CameraServerJNI.java @@ -16,8 +16,8 @@ public class CameraServerJNI { static boolean libraryLoaded = false; /** Sets whether JNI should be loaded in the static block. */ - public static class Helper { - private static AtomicBoolean extractOnStaticLoad = new AtomicBoolean(true); + public static final class Helper { + private static AtomicBoolean extractOnStaticLoad = new AtomicBoolean(false); /** * Returns true if the JNI should be loaded in the static block. diff --git a/cscore/src/main/java/edu/wpi/first/cscore/OpenCvLoader.java b/cscore/src/main/java/edu/wpi/first/cscore/OpenCvLoader.java index d6df2eec926..f16866ded12 100644 --- a/cscore/src/main/java/edu/wpi/first/cscore/OpenCvLoader.java +++ b/cscore/src/main/java/edu/wpi/first/cscore/OpenCvLoader.java @@ -16,7 +16,7 @@ public final class OpenCvLoader { /** Sets whether JNI should be loaded in the static block. */ public static final class Helper { - private static AtomicBoolean extractOnStaticLoad = new AtomicBoolean(true); + private static AtomicBoolean extractOnStaticLoad = new AtomicBoolean(false); /** * Returns true if the JNI should be loaded in the static block. diff --git a/cscore/src/test/java/edu/wpi/first/cscore/CameraServerJniTestExtension.java b/cscore/src/test/java/edu/wpi/first/cscore/CameraServerJniTestExtension.java new file mode 100644 index 00000000000..fba8ba7a5ab --- /dev/null +++ b/cscore/src/test/java/edu/wpi/first/cscore/CameraServerJniTestExtension.java @@ -0,0 +1,41 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +package edu.wpi.first.cscore; + +import edu.wpi.first.util.WPIUtilJNI; + +import java.io.IOException; +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ExtensionContext.Namespace; + +public final class CameraServerJniTestExtension implements BeforeAllCallback { + private static ExtensionContext getRoot(ExtensionContext context) { + return context.getParent().map(CameraServerJniTestExtension::getRoot).orElse(context); + } + + @Override + public void beforeAll(ExtensionContext context) throws Exception { + getRoot(context) + .getStore(Namespace.GLOBAL) + .getOrComputeIfAbsent( + "CsCore Initialized", + key -> { + initializeNatives(); + return true; + }, + Boolean.class); + } + + private void initializeNatives() { + try { + WPIUtilJNI.forceLoad(); + CameraServerJNI.forceLoad(); + OpenCvLoader.forceLoad(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/cscore/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension b/cscore/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension new file mode 100644 index 00000000000..3e39d456fc1 --- /dev/null +++ b/cscore/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension @@ -0,0 +1 @@ +edu.wpi.first.cscore.CameraServerJniTestExtension diff --git a/hal/src/main/java/edu/wpi/first/hal/JNIWrapper.java b/hal/src/main/java/edu/wpi/first/hal/JNIWrapper.java index 5cc2e126558..44f14bf36ba 100644 --- a/hal/src/main/java/edu/wpi/first/hal/JNIWrapper.java +++ b/hal/src/main/java/edu/wpi/first/hal/JNIWrapper.java @@ -13,8 +13,8 @@ public class JNIWrapper { static boolean libraryLoaded = false; /** Sets whether JNI should be loaded in the static block. */ - public static class Helper { - private static AtomicBoolean extractOnStaticLoad = new AtomicBoolean(true); + public static final class Helper { + private static AtomicBoolean extractOnStaticLoad = new AtomicBoolean(false); /** * Returns true if the JNI should be loaded in the static block. diff --git a/hal/src/test/java/edu/wpi/first/hal/HalJniTestExtension.java b/hal/src/test/java/edu/wpi/first/hal/HalJniTestExtension.java new file mode 100644 index 00000000000..05aa5a51096 --- /dev/null +++ b/hal/src/test/java/edu/wpi/first/hal/HalJniTestExtension.java @@ -0,0 +1,39 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +package edu.wpi.first.hal; + +import edu.wpi.first.util.WPIUtilJNI; +import java.io.IOException; +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ExtensionContext.Namespace; + +public final class HalJniTestExtension implements BeforeAllCallback { + private static ExtensionContext getRoot(ExtensionContext context) { + return context.getParent().map(HalJniTestExtension::getRoot).orElse(context); + } + + @Override + public void beforeAll(ExtensionContext context) throws Exception { + getRoot(context) + .getStore(Namespace.GLOBAL) + .getOrComputeIfAbsent( + "HAL Initialized", + key -> { + initializeNatives(); + return true; + }, + Boolean.class); + } + + private void initializeNatives() { + try { + WPIUtilJNI.forceLoad(); + JNIWrapper.forceLoad(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/hal/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension b/hal/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension new file mode 100644 index 00000000000..8c64204de27 --- /dev/null +++ b/hal/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension @@ -0,0 +1 @@ +edu.wpi.first.hal.HalJniTestExtension diff --git a/ntcore/src/generate/main/java/NetworkTablesJNI.java.jinja b/ntcore/src/generate/main/java/NetworkTablesJNI.java.jinja index 17d15c17540..2bc02ddee89 100644 --- a/ntcore/src/generate/main/java/NetworkTablesJNI.java.jinja +++ b/ntcore/src/generate/main/java/NetworkTablesJNI.java.jinja @@ -19,8 +19,8 @@ public final class NetworkTablesJNI { static boolean libraryLoaded = false; /** Sets whether JNI should be loaded in the static block. */ - public static class Helper { - private static AtomicBoolean extractOnStaticLoad = new AtomicBoolean(true); + public static final class Helper { + private static AtomicBoolean extractOnStaticLoad = new AtomicBoolean(false); /** * Returns true if the JNI should be loaded in the static block. diff --git a/ntcore/src/generated/main/java/edu/wpi/first/networktables/NetworkTablesJNI.java b/ntcore/src/generated/main/java/edu/wpi/first/networktables/NetworkTablesJNI.java index 4741f7642a7..20bc953fab1 100644 --- a/ntcore/src/generated/main/java/edu/wpi/first/networktables/NetworkTablesJNI.java +++ b/ntcore/src/generated/main/java/edu/wpi/first/networktables/NetworkTablesJNI.java @@ -19,8 +19,8 @@ public final class NetworkTablesJNI { static boolean libraryLoaded = false; /** Sets whether JNI should be loaded in the static block. */ - public static class Helper { - private static AtomicBoolean extractOnStaticLoad = new AtomicBoolean(true); + public static final class Helper { + private static AtomicBoolean extractOnStaticLoad = new AtomicBoolean(false); /** * Returns true if the JNI should be loaded in the static block. diff --git a/ntcore/src/test/java/edu/wpi/first/networktables/NetworkTablesJniTestExtension.java b/ntcore/src/test/java/edu/wpi/first/networktables/NetworkTablesJniTestExtension.java new file mode 100644 index 00000000000..7941b28ba54 --- /dev/null +++ b/ntcore/src/test/java/edu/wpi/first/networktables/NetworkTablesJniTestExtension.java @@ -0,0 +1,40 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +package edu.wpi.first.networktables; + +import edu.wpi.first.util.WPIUtilJNI; + +import java.io.IOException; +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ExtensionContext.Namespace; + +public final class NetworkTablesJniTestExtension implements BeforeAllCallback { + private static ExtensionContext getRoot(ExtensionContext context) { + return context.getParent().map(NetworkTablesJniTestExtension::getRoot).orElse(context); + } + + @Override + public void beforeAll(ExtensionContext context) throws Exception { + getRoot(context) + .getStore(Namespace.GLOBAL) + .getOrComputeIfAbsent( + "Ntcore Initialized", + key -> { + initializeNatives(); + return true; + }, + Boolean.class); + } + + private void initializeNatives() { + try { + WPIUtilJNI.forceLoad(); + NetworkTablesJNI.forceLoad(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/ntcore/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension b/ntcore/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension new file mode 100644 index 00000000000..178c9c4f7eb --- /dev/null +++ b/ntcore/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension @@ -0,0 +1 @@ +edu.wpi.first.networktables.NetworkTablesJniTestExtension diff --git a/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/MockHardwareExtension.java b/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/MockHardwareExtension.java index 2de1a075833..dfb3b353259 100644 --- a/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/MockHardwareExtension.java +++ b/wpilibNewCommands/src/test/java/edu/wpi/first/wpilibj2/MockHardwareExtension.java @@ -4,8 +4,9 @@ package edu.wpi.first.wpilibj2; -import edu.wpi.first.hal.HAL; +import edu.wpi.first.wpilibj.RobotBase; import edu.wpi.first.wpilibj.simulation.DriverStationSim; + import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ExtensionContext.Namespace; @@ -29,7 +30,8 @@ public void beforeAll(ExtensionContext context) { } private void initializeHardware() { - HAL.initialize(500, 0); + RobotBase.loadLibrariesAndInitializeHal(); + DriverStationSim.setDsAttached(true); DriverStationSim.setAutonomous(false); DriverStationSim.setEnabled(true); diff --git a/wpilibj/CMakeLists.txt b/wpilibj/CMakeLists.txt index 440f7585592..518fd3fd137 100644 --- a/wpilibj/CMakeLists.txt +++ b/wpilibj/CMakeLists.txt @@ -38,8 +38,10 @@ if(WITH_JAVA) ${OPENCV_JAR_FILE} cscore_jar cameraserver_jar + apriltag_jar wpimath_jar wpiunits_jar + wpinet_jar wpiutil_jar OUTPUT_NAME wpilibj OUTPUT_DIR ${WPILIB_BINARY_DIR}/${java_lib_dest} diff --git a/wpilibj/build.gradle b/wpilibj/build.gradle index d397f934801..a767773a55a 100644 --- a/wpilibj/build.gradle +++ b/wpilibj/build.gradle @@ -70,6 +70,7 @@ dependencies { implementation project(':ntcore') implementation project(':cscore') implementation project(':cameraserver') + implementation project(':apriltag') testImplementation 'org.mockito:mockito-core:4.1.0' devImplementation sourceSets.main.output } diff --git a/wpilibj/src/main/java/edu/wpi/first/wpilibj/RobotBase.java b/wpilibj/src/main/java/edu/wpi/first/wpilibj/RobotBase.java index 2de7cf1ce86..c20d82a4acd 100644 --- a/wpilibj/src/main/java/edu/wpi/first/wpilibj/RobotBase.java +++ b/wpilibj/src/main/java/edu/wpi/first/wpilibj/RobotBase.java @@ -4,17 +4,24 @@ package edu.wpi.first.wpilibj; +import edu.wpi.first.apriltag.jni.AprilTagJNI; import edu.wpi.first.cameraserver.CameraServerShared; import edu.wpi.first.cameraserver.CameraServerSharedStore; +import edu.wpi.first.cscore.CameraServerJNI; +import edu.wpi.first.cscore.OpenCvLoader; import edu.wpi.first.hal.FRCNetComm.tInstances; import edu.wpi.first.hal.FRCNetComm.tResourceType; import edu.wpi.first.hal.HAL; import edu.wpi.first.hal.HALUtil; +import edu.wpi.first.hal.JNIWrapper; import edu.wpi.first.math.MathShared; import edu.wpi.first.math.MathSharedStore; import edu.wpi.first.math.MathUsageId; +import edu.wpi.first.math.jni.WPIMathJNI; +import edu.wpi.first.net.WPINetJNI; import edu.wpi.first.networktables.MultiSubscriber; import edu.wpi.first.networktables.NetworkTableInstance; +import edu.wpi.first.networktables.NetworkTablesJNI; import edu.wpi.first.util.WPIUtilJNI; import edu.wpi.first.wpilibj.livewindow.LiveWindow; import edu.wpi.first.wpilibj.shuffleboard.Shuffleboard; @@ -390,6 +397,50 @@ public static void suppressExitWarning(boolean value) { m_runMutex.unlock(); } + /** + * Loads base native libraries needed by WPILib in all robot projects. + * + * @throws IOException If any library was not found + */ + public static void loadBaseNativeLibraries() throws IOException { + WPIUtilJNI.forceLoad(); + WPINetJNI.forceLoad(); + WPIMathJNI.forceLoad(); + JNIWrapper.forceLoad(); + NetworkTablesJNI.forceLoad(); + } + + /** + * Sets the static load behavior for all of the vision libraries + * + * @param loadOnStaticInitialization true to load on static initialization, false to disable (default) + */ + public static void setVisionNativeLibrariesStaticLoadBehavior(boolean loadOnStaticInitialization) { + CameraServerJNI.Helper.setExtractOnStaticLoad(true); + OpenCvLoader.Helper.setExtractOnStaticLoad(true); + AprilTagJNI.Helper.setExtractOnStaticLoad(true); + } + + /** + * Configure all native libraries to match how a robot program works. + * + *
This will load all the base JNI libraries, set the vision libaries
+ * to load on static initialization, and initialize the HAL.
+ *
+ * @return HAL initialization result, true for success.
+ */
+ public static boolean loadLibrariesAndInitializeHal() {
+ try {
+ loadBaseNativeLibraries();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ setVisionNativeLibrariesStaticLoadBehavior(true);
+
+ return HAL.initialize(500, 0);
+ }
+
/**
* Starting point for the applications.
*
@@ -397,7 +448,7 @@ public static void suppressExitWarning(boolean value) {
* @param robotSupplier Function that returns an instance of the robot subclass.
*/
public static