From 6bd73ef90ec07fe5b29d3f00b3d6ef4c8d6ad665 Mon Sep 17 00:00:00 2001 From: Thad House Date: Wed, 17 Jun 2026 21:27:29 -0700 Subject: [PATCH 01/10] Enable configuring java run task to run simulation --- .../wpilib/gradlerio/wpi/java/WPIJavaExtension.java | 11 ++++++++++- testing/java/build.gradle | 4 ++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/wpilib/gradlerio/wpi/java/WPIJavaExtension.java b/src/main/java/org/wpilib/gradlerio/wpi/java/WPIJavaExtension.java index bd44d91d..53d89574 100644 --- a/src/main/java/org/wpilib/gradlerio/wpi/java/WPIJavaExtension.java +++ b/src/main/java/org/wpilib/gradlerio/wpi/java/WPIJavaExtension.java @@ -19,6 +19,7 @@ import org.gradle.api.file.FileCollection; import org.gradle.api.provider.Property; import org.gradle.api.provider.Provider; +import org.gradle.api.tasks.JavaExec; import org.gradle.api.tasks.TaskProvider; import org.gradle.api.tasks.testing.Test; import org.gradle.api.tasks.testing.logging.TestLogEvent; @@ -114,7 +115,7 @@ public TaskProvider getSimulationTaskRelease() { return simulationTaskRelease; } - private void configureSimulationTask(JavaSimulationTask t, boolean debug, + private void configureSimulationTask(JavaExec t, boolean debug, Provider extract) { configureExecutableNatives(t, extract); List jvmArgs = new ArrayList<>(); @@ -199,6 +200,14 @@ public void configureExecutableTasks(Jar jar) { simulationTaskRelease.configure(x -> x.classpath(jar)); } + public void configureJavaExecApplication(JavaExec application) { + configureJavaExecApplication(application, false); + } + + public void configureJavaExecApplication(JavaExec application, boolean debug) { + configureSimulationTask(application, debug, debug ? extractNativeDebugArtifacts : extractNativeReleaseArtifacts); + } + @Inject public WPIJavaExtension(Project project, SimulationExtension sim, WPIVersionsExtension versions) { this.project = project; diff --git a/testing/java/build.gradle b/testing/java/build.gradle index e859cd05..1abf68cd 100644 --- a/testing/java/build.gradle +++ b/testing/java/build.gradle @@ -1,5 +1,6 @@ plugins { id "java" + id "application" id "org.wpilib.GradleRIO" version "2027.0.0-alpha-6" } @@ -99,6 +100,9 @@ jar { duplicatesStrategy = DuplicatesStrategy.INCLUDE } +application.mainClass = ROBOT_MAIN_CLASS + deployArtifact.jarTask = jar wpi.java.configureExecutableTasks(jar) +wpi.java.configureJavaExecApplication(run) wpi.java.configureTestTasks(test) From 36d98214b9aac083ce161657361eec3d40465264 Mon Sep 17 00:00:00 2001 From: Thad House Date: Wed, 17 Jun 2026 21:37:27 -0700 Subject: [PATCH 02/10] Try something to verify CI do not merge --- gradle.properties | 2 +- testing/asm/build.gradle | 2 +- testing/cpp/build.gradle | 2 +- testing/java/build.gradle | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/gradle.properties b/gradle.properties index 3706a9e6..49050fc1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1 @@ -version = 2027.0.0-alpha-6 +version = 2027.0.0-alpha-67 diff --git a/testing/asm/build.gradle b/testing/asm/build.gradle index 1c01a4c6..139ff04f 100644 --- a/testing/asm/build.gradle +++ b/testing/asm/build.gradle @@ -1,7 +1,7 @@ plugins { id "assembler" - id "org.wpilib.GradleRIO" version "2027.0.0-alpha-6" + id "org.wpilib.GradleRIO" version "2027.0.0-alpha-67" } def projectFolder = project.buildFile.parentFile diff --git a/testing/cpp/build.gradle b/testing/cpp/build.gradle index bf7d5ffe..8f47ea6e 100644 --- a/testing/cpp/build.gradle +++ b/testing/cpp/build.gradle @@ -2,7 +2,7 @@ plugins { id "cpp" id "google-test-test-suite" - id "org.wpilib.GradleRIO" version "2027.0.0-alpha-6" + id "org.wpilib.GradleRIO" version "2027.0.0-alpha-67" } def projectFolder = project.buildFile.parentFile diff --git a/testing/java/build.gradle b/testing/java/build.gradle index 1abf68cd..ade6fd43 100644 --- a/testing/java/build.gradle +++ b/testing/java/build.gradle @@ -1,7 +1,7 @@ plugins { id "java" id "application" - id "org.wpilib.GradleRIO" version "2027.0.0-alpha-6" + id "org.wpilib.GradleRIO" version "2027.0.0-alpha-67" } def projectFolder = project.buildFile.parentFile From 3e87895e557a47d0a48c33f3a60f40afa309dca4 Mon Sep 17 00:00:00 2001 From: Thad House Date: Wed, 17 Jun 2026 21:46:12 -0700 Subject: [PATCH 03/10] Tracing --- .github/workflows/ci.yml | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d66292e7..6997ecc0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,6 +14,8 @@ jobs: build: needs: validation runs-on: ubuntu-24.04 + env: + MAVEN_LOCAL_REPO: ${{ github.workspace }}/ci-maven-repository steps: - uses: actions/checkout@v4 with: @@ -23,18 +25,27 @@ jobs: java-version: 25 distribution: temurin - name: Publish to Maven Local - run: ./gradlew build publishToMavenLocal --stacktrace -PlocalPublish + run: ./gradlew -Dmaven.repo.local="$MAVEN_LOCAL_REPO" build publishToMavenLocal --stacktrace -PlocalPublish + - name: Trace Maven local publish output + run: | + set -eu + echo "MAVEN_LOCAL_REPO=$MAVEN_LOCAL_REPO" + test -d "$MAVEN_LOCAL_REPO" + du -sh "$MAVEN_LOCAL_REPO" + find "$MAVEN_LOCAL_REPO/org/wpilib" -maxdepth 8 -type f | sort | sed -n '1,200p' - name: Check output run: git --no-pager diff --exit-code HEAD - uses: actions/upload-artifact@v4 with: name: Maven - path: ~/.m2/repository/ + path: ${{ env.MAVEN_LOCAL_REPO }}/ test_examples: needs: build runs-on: ubuntu-24.04 container: wpilib/debian-base:trixie + env: + MAVEN_LOCAL_REPO: ${{ github.workspace }}/ci-maven-repository strategy: matrix: language: [cpp, java] #, asm, jni] @@ -44,17 +55,33 @@ jobs: with: java-version: 25 distribution: temurin + - name: Trace Maven local configuration + run: | + set -eu + echo "HOME=$HOME" + echo "MAVEN_LOCAL_REPO=$MAVEN_LOCAL_REPO" + id + getent passwd "$(id -u)" || true + java -XshowSettings:properties -version 2>&1 | grep 'user.home\|java.home' || true - uses: actions/download-artifact@v4 with: name: Maven - path: ~/.m2/repository/ + path: ${{ env.MAVEN_LOCAL_REPO }}/ + - name: Trace downloaded Maven artifact + run: | + set -eu + echo "Downloaded Maven repository:" + test -d "$MAVEN_LOCAL_REPO" + du -sh "$MAVEN_LOCAL_REPO" + find "$MAVEN_LOCAL_REPO" -maxdepth 4 -type d | sort | sed -n '1,120p' + find "$MAVEN_LOCAL_REPO/org/wpilib" -maxdepth 8 -type f | sort | sed -n '1,200p' - name: Setup SystemCore Toolchain if: ${{ (matrix.language == 'cpp') || (matrix.language == 'asm') }} - run: ../../gradlew installSystemCoreToolchain + run: ../../gradlew -Dmaven.repo.local="$MAVEN_LOCAL_REPO" installSystemCoreToolchain working-directory: testing/${{ matrix.language }} - name: Test ${{ matrix.language }} Build - run: ../../gradlew build + run: ../../gradlew -Dmaven.repo.local="$MAVEN_LOCAL_REPO" build working-directory: testing/${{ matrix.language }} publish: From dd1b342e31c5943ae7fd5fc8c8719328027736a6 Mon Sep 17 00:00:00 2001 From: Thad House Date: Wed, 17 Jun 2026 22:00:30 -0700 Subject: [PATCH 04/10] Fix tracing --- .github/workflows/ci.yml | 23 ----------------------- gradle.properties | 2 +- testing/asm/build.gradle | 2 +- testing/cpp/build.gradle | 2 +- testing/java/build.gradle | 2 +- 5 files changed, 4 insertions(+), 27 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6997ecc0..65511f60 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,13 +26,6 @@ jobs: distribution: temurin - name: Publish to Maven Local run: ./gradlew -Dmaven.repo.local="$MAVEN_LOCAL_REPO" build publishToMavenLocal --stacktrace -PlocalPublish - - name: Trace Maven local publish output - run: | - set -eu - echo "MAVEN_LOCAL_REPO=$MAVEN_LOCAL_REPO" - test -d "$MAVEN_LOCAL_REPO" - du -sh "$MAVEN_LOCAL_REPO" - find "$MAVEN_LOCAL_REPO/org/wpilib" -maxdepth 8 -type f | sort | sed -n '1,200p' - name: Check output run: git --no-pager diff --exit-code HEAD - uses: actions/upload-artifact@v4 @@ -55,26 +48,10 @@ jobs: with: java-version: 25 distribution: temurin - - name: Trace Maven local configuration - run: | - set -eu - echo "HOME=$HOME" - echo "MAVEN_LOCAL_REPO=$MAVEN_LOCAL_REPO" - id - getent passwd "$(id -u)" || true - java -XshowSettings:properties -version 2>&1 | grep 'user.home\|java.home' || true - uses: actions/download-artifact@v4 with: name: Maven path: ${{ env.MAVEN_LOCAL_REPO }}/ - - name: Trace downloaded Maven artifact - run: | - set -eu - echo "Downloaded Maven repository:" - test -d "$MAVEN_LOCAL_REPO" - du -sh "$MAVEN_LOCAL_REPO" - find "$MAVEN_LOCAL_REPO" -maxdepth 4 -type d | sort | sed -n '1,120p' - find "$MAVEN_LOCAL_REPO/org/wpilib" -maxdepth 8 -type f | sort | sed -n '1,200p' - name: Setup SystemCore Toolchain if: ${{ (matrix.language == 'cpp') || (matrix.language == 'asm') }} diff --git a/gradle.properties b/gradle.properties index 49050fc1..3706a9e6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1 @@ -version = 2027.0.0-alpha-67 +version = 2027.0.0-alpha-6 diff --git a/testing/asm/build.gradle b/testing/asm/build.gradle index 139ff04f..1c01a4c6 100644 --- a/testing/asm/build.gradle +++ b/testing/asm/build.gradle @@ -1,7 +1,7 @@ plugins { id "assembler" - id "org.wpilib.GradleRIO" version "2027.0.0-alpha-67" + id "org.wpilib.GradleRIO" version "2027.0.0-alpha-6" } def projectFolder = project.buildFile.parentFile diff --git a/testing/cpp/build.gradle b/testing/cpp/build.gradle index 8f47ea6e..bf7d5ffe 100644 --- a/testing/cpp/build.gradle +++ b/testing/cpp/build.gradle @@ -2,7 +2,7 @@ plugins { id "cpp" id "google-test-test-suite" - id "org.wpilib.GradleRIO" version "2027.0.0-alpha-67" + id "org.wpilib.GradleRIO" version "2027.0.0-alpha-6" } def projectFolder = project.buildFile.parentFile diff --git a/testing/java/build.gradle b/testing/java/build.gradle index ade6fd43..1abf68cd 100644 --- a/testing/java/build.gradle +++ b/testing/java/build.gradle @@ -1,7 +1,7 @@ plugins { id "java" id "application" - id "org.wpilib.GradleRIO" version "2027.0.0-alpha-67" + id "org.wpilib.GradleRIO" version "2027.0.0-alpha-6" } def projectFolder = project.buildFile.parentFile From f53ef55c06cbd2b8cd3dcef1d9e95562a762b642 Mon Sep 17 00:00:00 2001 From: Thad House Date: Sat, 20 Jun 2026 19:20:03 -0700 Subject: [PATCH 05/10] Deploy using classpath --- build.gradle | 2 +- .../deploy/DebuggableJavaArtifact.java | 4 +- .../systemcore/RobotCommandArtifact.java | 54 +++++++++--- .../deploy/systemcore/WPILibJavaArtifact.java | 84 ++++++++++++------- .../systemcore/WPILibNativeArtifact.java | 2 +- testing/java/build.gradle | 13 +-- 6 files changed, 100 insertions(+), 59 deletions(-) diff --git a/build.gradle b/build.gradle index c6362710..9b495ee0 100644 --- a/build.gradle +++ b/build.gradle @@ -23,7 +23,7 @@ def processstarterToolVersion = "2027.0.0-alpha-6-68-g5a7d7d50e" dependencies { api 'com.google.code.gson:gson:2.13.1' - api 'org.wpilib:native-utils:2027.9.0' + api 'org.wpilib:native-utils:2027.10.0' api 'de.undercouch:gradle-download-task:5.6.0' diff --git a/src/main/java/org/wpilib/gradlerio/deploy/DebuggableJavaArtifact.java b/src/main/java/org/wpilib/gradlerio/deploy/DebuggableJavaArtifact.java index 55edb73e..b09f9ca4 100644 --- a/src/main/java/org/wpilib/gradlerio/deploy/DebuggableJavaArtifact.java +++ b/src/main/java/org/wpilib/gradlerio/deploy/DebuggableJavaArtifact.java @@ -2,12 +2,12 @@ import javax.inject.Inject; -import org.wpilib.deployutils.deploy.artifact.JavaArtifact; +import org.wpilib.deployutils.deploy.artifact.JavaClasspathArtifact; import org.wpilib.deployutils.deploy.context.DeployContext; import org.wpilib.deployutils.deploy.sessions.IPSessionController; import org.wpilib.deployutils.deploy.target.RemoteTarget; -public class DebuggableJavaArtifact extends JavaArtifact implements DebuggableArtifact { +public class DebuggableJavaArtifact extends JavaClasspathArtifact implements DebuggableArtifact { private int debugPort = 8349; diff --git a/src/main/java/org/wpilib/gradlerio/deploy/systemcore/RobotCommandArtifact.java b/src/main/java/org/wpilib/gradlerio/deploy/systemcore/RobotCommandArtifact.java index 5b53a0a1..b446b29c 100644 --- a/src/main/java/org/wpilib/gradlerio/deploy/systemcore/RobotCommandArtifact.java +++ b/src/main/java/org/wpilib/gradlerio/deploy/systemcore/RobotCommandArtifact.java @@ -1,17 +1,27 @@ package org.wpilib.gradlerio.deploy.systemcore; +import java.util.HashMap; +import java.util.Map; import java.util.function.Function; import javax.inject.Inject; -import org.wpilib.deployutils.deploy.artifact.FileArtifact; +import org.gradle.api.file.ConfigurableFileCollection; +import org.gradle.api.file.FileCollection; +import org.wpilib.deployutils.PathUtils; +import org.wpilib.deployutils.deploy.artifact.CommandArtifact; +import org.wpilib.deployutils.deploy.artifact.FileCollectionArtifact; import org.wpilib.deployutils.deploy.context.DeployContext; import org.wpilib.gradlerio.deploy.DeployStage; import org.wpilib.gradlerio.deploy.StagedDeployTarget; -public class RobotCommandArtifact extends FileArtifact { +public class RobotCommandArtifact extends CommandArtifact { - private Function startCommandFunc; + public static final String ROBOT_COMMAND_FILE = "robotCommand"; + public static final String ARG_FILE = "robotCommand.args"; + + private Function robotCommandFunc; + private Function argFileFunc; @Inject public RobotCommandArtifact(String name, StagedDeployTarget target) { @@ -20,20 +30,42 @@ public RobotCommandArtifact(String name, StagedDeployTarget target) { target.setDeployStage(this, DeployStage.FileDeploy); } - public Function getStartCommandFunc() { - return startCommandFunc; + public Function getRobotCommandFunc() { + return robotCommandFunc; + } + + public void setRobotCommandFunc(Function robotCommandFunc) { + this.robotCommandFunc = robotCommandFunc; + } + + public Function getArgFileFunc() { + return argFileFunc; } - public void setStartCommandFunc(Function startCommandFunc) { - this.startCommandFunc = startCommandFunc; + public void setArgFileFunc(Function argFileFunc) { + this.argFileFunc = argFileFunc; } @Override public void deploy(DeployContext ctx) { - String content = startCommandFunc.apply(ctx); + String robotCommandContent = robotCommandFunc.apply(ctx); - ctx.execute("echo '" + content + "' > /home/systemcore/robotCommand"); - ctx.execute("chmod +x /home/systemcore/robotCommand; chown systemcore /home/systemcore/robotCommand"); - } + var robotCommandPath = PathUtils.combine(ctx.getWorkingDir(), ROBOT_COMMAND_FILE); + var argsPath = PathUtils.combine(ctx.getWorkingDir(), ARG_FILE); + + Map files = new HashMap<>(); + files.put(robotCommandPath, robotCommandContent); + + if (argFileFunc != null) { + String argFileContent = argFileFunc.apply(ctx); + files.put(argsPath, argFileContent); + } + ctx.putStringFiles(files); + + ctx.execute("chmod +x " + robotCommandPath + "; chown systemcore " + robotCommandPath); + if (argFileFunc != null) { + ctx.execute("chmod +x " + argsPath + "; chown systemcore " + argsPath); + } + } } diff --git a/src/main/java/org/wpilib/gradlerio/deploy/systemcore/WPILibJavaArtifact.java b/src/main/java/org/wpilib/gradlerio/deploy/systemcore/WPILibJavaArtifact.java index f0633b06..a8196c77 100644 --- a/src/main/java/org/wpilib/gradlerio/deploy/systemcore/WPILibJavaArtifact.java +++ b/src/main/java/org/wpilib/gradlerio/deploy/systemcore/WPILibJavaArtifact.java @@ -1,13 +1,13 @@ package org.wpilib.gradlerio.deploy.systemcore; +import java.io.File; import java.util.ArrayList; import java.util.List; import javax.inject.Inject; -import org.gradle.api.tasks.TaskProvider; +import org.gradle.api.artifacts.Configuration; import org.gradle.api.tasks.bundling.Jar; - import org.wpilib.deployutils.PathUtils; import org.wpilib.deployutils.deploy.context.DeployContext; import org.wpilib.gradlerio.deploy.DebuggableJavaArtifact; @@ -16,6 +16,7 @@ import org.wpilib.gradlerio.wpi.WPIExtension; public class WPILibJavaArtifact extends DebuggableJavaArtifact { + public static final String CLASSPATH_PATH = "/home/systemcore/wpilib/classpath"; private final RobotCommandArtifact robotCommandArtifact; @@ -45,9 +46,15 @@ public WPILibJavaArtifact(String name, SystemCore target) { var debugConfiguration = target.getProject().getConfigurations().create("systemcoreDebug"); var releaseConfiguration = target.getProject().getConfigurations().create("systemcoreRelease"); + this.getDirectory().set(CLASSPATH_PATH); + robotCommandArtifact = target.getArtifacts().create("robotCommand" + name, RobotCommandArtifact.class, art -> { - art.setStartCommandFunc(this::generateStartCommand); + art.setRobotCommandFunc(this::generateStartCommand); + art.setArgFileFunc(this::generateArgFile); art.dependsOn(getJarProvider()); + art.dependsOn(getConfigurationProvider()); + art.dependsOn(this.getDeployTask()); + //art.dependsOn(getJarProvider()); }); nativeZipArtifact = target.getArtifacts().create("nativeZips" + name, WPILibJNILibraryArtifact.class, artifact -> { @@ -89,20 +96,12 @@ public void setGcType(GarbageCollectorType gcType) { this.gcType = gcType; } - private String getBinFile(DeployContext ctx) { - return PathUtils.combine(ctx.getWorkingDir(), getFilename().getOrElse(getFile().get().getName())); - } - - @Override - public void setJarTask(Jar jarTask) { - robotCommandArtifact.getDeployTask().configure(x -> x.dependsOn(jarTask)); - super.setJarTask(jarTask); - } + private String mainClass; - @Override - public void setJarTask(TaskProvider jarTask) { - robotCommandArtifact.getDeployTask().configure(x -> x.dependsOn(jarTask)); - super.setJarTask(jarTask); + public void configureApplication(Configuration runtimeClasspath, Jar jarTask, String mainClass) { + setConfiguration(runtimeClasspath); + setJar(jarTask); + this.mainClass = mainClass; } public RobotCommandArtifact getRobotCommandArtifact() { @@ -121,29 +120,50 @@ public List getArguments() { return arguments; } - private String generateStartCommand(DeployContext ctx) { - StringBuilder builder = new StringBuilder(); - builder.append(javaCommand); - builder.append(" "); - builder.append(String.join(" ", gcType.getGcArguments())); - builder.append(" "); - builder.append(String.join(" ", jvmArgs)); - builder.append(" "); + private String generateArgFile(DeployContext ctx) { + List args = new ArrayList<>(); + args.addAll(gcType.getGcArguments()); + args.addAll(jvmArgs); + + args.add("-cp \"\\"); + + // Put the entire deploy classpath + String deployDirectory = getDirectory().get(); + + List files = new ArrayList<>(getFiles().get().getFiles()); + + for (int i = 0; i < files.size(); i++) { + File file = files.get(i); + String path = PathUtils.combine(deployDirectory, file.getName()); + if (i != files.size() - 1) { + args.add(path + ":\\"); + } else { + args.add(path + "\""); + } + } // Debug stuff boolean debug = systemCore.getDebug().get(); if (debug) { - builder.append("-XX:+UsePerfData -agentlib:jdwp=transport=dt_socket,address=0.0.0.0:"); - builder.append(getDebugPort()); - builder.append(",server=y,suspend=y "); + args.add("-XX:+UsePerfData -agentlib:jdwp=transport=dt_socket,address=0.0.0.0:" + getDebugPort() + ",server=y,suspend=y"); } - String binFile = getBinFile(ctx); + args.add(mainClass); + + args.addAll(arguments); + + args.add(""); + + return String.join("\n", args); + } + + private String generateStartCommand(DeployContext ctx) { + StringBuilder builder = new StringBuilder(); + + builder.append(javaCommand); - builder.append("-jar \""); - builder.append(binFile); - builder.append("\" "); - builder.append(String.join(" ", arguments)); + builder.append(" @"); + builder.append(PathUtils.combine(ctx.getWorkingDir(), RobotCommandArtifact.ARG_FILE)); return builder.toString(); } diff --git a/src/main/java/org/wpilib/gradlerio/deploy/systemcore/WPILibNativeArtifact.java b/src/main/java/org/wpilib/gradlerio/deploy/systemcore/WPILibNativeArtifact.java index 2fdba7d2..494b3f9f 100644 --- a/src/main/java/org/wpilib/gradlerio/deploy/systemcore/WPILibNativeArtifact.java +++ b/src/main/java/org/wpilib/gradlerio/deploy/systemcore/WPILibNativeArtifact.java @@ -56,7 +56,7 @@ public WPILibNativeArtifact(String name, SystemCore target) { }); robotCommandArtifact = target.getArtifacts().create("robotCommand" + name, RobotCommandArtifact.class, art -> { - art.setStartCommandFunc(this::generateStartCommand); + art.setRobotCommandFunc(this::generateStartCommand); art.dependsOn(getInstallTaskProvider()); }); diff --git a/testing/java/build.gradle b/testing/java/build.gradle index 1abf68cd..26e49476 100644 --- a/testing/java/build.gradle +++ b/testing/java/build.gradle @@ -89,20 +89,9 @@ wpi.sim.envVar("HALSIMWS_HOST", "10.0.0.2") wpi.sim.addWebsocketsServer().defaultEnabled = true wpi.sim.addWebsocketsClient().defaultEnabled = true -// Setting up my Jar File. In this case, adding all libraries into the main jar ('fat jar') -// in order to make them all available at runtime. Also adding the manifest so WPILib -// knows where to look for our Robot Class. -jar { - from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } } - - manifest org.wpilib.gradlerio.GradleRIOPlugin.javaManifest(ROBOT_MAIN_CLASS) - - duplicatesStrategy = DuplicatesStrategy.INCLUDE -} - application.mainClass = ROBOT_MAIN_CLASS -deployArtifact.jarTask = jar +deployArtifact.configureApplication(configurations.runtimeClasspath, jar, ROBOT_MAIN_CLASS) wpi.java.configureExecutableTasks(jar) wpi.java.configureJavaExecApplication(run) wpi.java.configureTestTasks(test) From 7e095ca56438c2184ae4584d15becbba46231e53 Mon Sep 17 00:00:00 2001 From: Thad House Date: Sat, 20 Jun 2026 20:42:10 -0700 Subject: [PATCH 06/10] Use application for deploy task, add kotlin example --- .github/workflows/ci.yml | 2 +- .../deploy/systemcore/WPILibJavaArtifact.java | 22 +++-- testing/cpp/README.md | 4 - testing/java/README.md | 4 - testing/java/build.gradle | 2 +- .../kotlin/.wpilib/wpilib_preferences.json | 6 ++ testing/kotlin/build.gradle | 97 +++++++++++++++++++ testing/kotlin/settings.gradle | 27 ++++++ .../main/kotlin/first/team0000/robot/Main.kt | 21 ++++ .../main/kotlin/first/team0000/robot/Robot.kt | 85 ++++++++++++++++ testing/kotlin/src/test/kotlin/TestCode.kt | 9 ++ 11 files changed, 262 insertions(+), 17 deletions(-) delete mode 100644 testing/cpp/README.md delete mode 100644 testing/java/README.md create mode 100644 testing/kotlin/.wpilib/wpilib_preferences.json create mode 100644 testing/kotlin/build.gradle create mode 100644 testing/kotlin/settings.gradle create mode 100644 testing/kotlin/src/main/kotlin/first/team0000/robot/Main.kt create mode 100644 testing/kotlin/src/main/kotlin/first/team0000/robot/Robot.kt create mode 100644 testing/kotlin/src/test/kotlin/TestCode.kt diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 65511f60..fe790628 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,7 +41,7 @@ jobs: MAVEN_LOCAL_REPO: ${{ github.workspace }}/ci-maven-repository strategy: matrix: - language: [cpp, java] #, asm, jni] + language: [cpp, java, kotlin] #, asm, jni] steps: - uses: actions/checkout@v4 - uses: actions/setup-java@v4 diff --git a/src/main/java/org/wpilib/gradlerio/deploy/systemcore/WPILibJavaArtifact.java b/src/main/java/org/wpilib/gradlerio/deploy/systemcore/WPILibJavaArtifact.java index a8196c77..28313b83 100644 --- a/src/main/java/org/wpilib/gradlerio/deploy/systemcore/WPILibJavaArtifact.java +++ b/src/main/java/org/wpilib/gradlerio/deploy/systemcore/WPILibJavaArtifact.java @@ -7,6 +7,10 @@ import javax.inject.Inject; import org.gradle.api.artifacts.Configuration; +import org.gradle.api.plugins.JavaApplication; +import org.gradle.api.plugins.internal.JavaPluginHelper; +import org.gradle.api.plugins.jvm.internal.JvmFeatureInternal; +import org.gradle.api.provider.Property; import org.gradle.api.tasks.bundling.Jar; import org.wpilib.deployutils.PathUtils; import org.wpilib.deployutils.deploy.context.DeployContext; @@ -27,6 +31,8 @@ public class WPILibJavaArtifact extends DebuggableJavaArtifact { private final SystemCore systemCore; + private final Property mainClass; + private GarbageCollectorType gcType = GarbageCollectorType.ZGC; private String javaCommand = "/usr/bin/java"; @@ -46,7 +52,10 @@ public WPILibJavaArtifact(String name, SystemCore target) { var debugConfiguration = target.getProject().getConfigurations().create("systemcoreDebug"); var releaseConfiguration = target.getProject().getConfigurations().create("systemcoreRelease"); + this.mainClass = target.getProject().getObjects().property(String.class); + this.getDirectory().set(CLASSPATH_PATH); + this.getDeleteOldFiles().set(true); robotCommandArtifact = target.getArtifacts().create("robotCommand" + name, RobotCommandArtifact.class, art -> { art.setRobotCommandFunc(this::generateStartCommand); @@ -96,12 +105,11 @@ public void setGcType(GarbageCollectorType gcType) { this.gcType = gcType; } - private String mainClass; - - public void configureApplication(Configuration runtimeClasspath, Jar jarTask, String mainClass) { - setConfiguration(runtimeClasspath); - setJar(jarTask); - this.mainClass = mainClass; + public void configureApplication(JavaApplication javaApplication) { + JvmFeatureInternal mainFeature = JavaPluginHelper.getJavaComponent(getTarget().getProject()).getMainFeature(); + setConfiguration(mainFeature.getRuntimeClasspathConfiguration()); + setJar(mainFeature.getJarTask().get()); + this.mainClass.set(javaApplication.getMainClass()); } public RobotCommandArtifact getRobotCommandArtifact() { @@ -148,7 +156,7 @@ private String generateArgFile(DeployContext ctx) { args.add("-XX:+UsePerfData -agentlib:jdwp=transport=dt_socket,address=0.0.0.0:" + getDebugPort() + ",server=y,suspend=y"); } - args.add(mainClass); + args.add(mainClass.get()); args.addAll(arguments); diff --git a/testing/cpp/README.md b/testing/cpp/README.md deleted file mode 100644 index 18d11fac..00000000 --- a/testing/cpp/README.md +++ /dev/null @@ -1,4 +0,0 @@ -C++ ---- - -This example involves building and deploying C++ sources. This should be all you need to get started. \ No newline at end of file diff --git a/testing/java/README.md b/testing/java/README.md deleted file mode 100644 index 962f13ea..00000000 --- a/testing/java/README.md +++ /dev/null @@ -1,4 +0,0 @@ -Java ---- - -This example involves building and deploying Java sources. This should be all you need to get started, however there are plenty of resources online for building more advanced java projects should you require. \ No newline at end of file diff --git a/testing/java/build.gradle b/testing/java/build.gradle index 26e49476..85c2be88 100644 --- a/testing/java/build.gradle +++ b/testing/java/build.gradle @@ -91,7 +91,7 @@ wpi.sim.addWebsocketsClient().defaultEnabled = true application.mainClass = ROBOT_MAIN_CLASS -deployArtifact.configureApplication(configurations.runtimeClasspath, jar, ROBOT_MAIN_CLASS) +deployArtifact.configureApplication(application) wpi.java.configureExecutableTasks(jar) wpi.java.configureJavaExecApplication(run) wpi.java.configureTestTasks(test) diff --git a/testing/kotlin/.wpilib/wpilib_preferences.json b/testing/kotlin/.wpilib/wpilib_preferences.json new file mode 100644 index 00000000..e9209995 --- /dev/null +++ b/testing/kotlin/.wpilib/wpilib_preferences.json @@ -0,0 +1,6 @@ +{ + "enableCppIntellisense": false, + "currentLanguage": "java", + "projectYear": "DoNotUse", + "teamNumber": 9999 +} diff --git a/testing/kotlin/build.gradle b/testing/kotlin/build.gradle new file mode 100644 index 00000000..e693b181 --- /dev/null +++ b/testing/kotlin/build.gradle @@ -0,0 +1,97 @@ +plugins { + id "org.jetbrains.kotlin.jvm" version "2.4.0" + id "application" + id "org.wpilib.GradleRIO" version "2027.0.0-alpha-6" +} + +def projectFolder = project.buildFile.parentFile +def testingFolder = projectFolder.parentFile + +if (testingFolder.name != 'testing' || projectFolder.name != 'kotlin') { + throw new GradleException("These projects are not to be used for robot projects. See README.md in the GradleRIO testing folder for the correct templates to use.") +} + +java { + sourceCompatibility = JavaVersion.VERSION_25 + targetCompatibility = JavaVersion.VERSION_25 +} + +def ROBOT_MAIN_CLASS = "first.team0000.robot.Main" + +// Define my targets (SystemCore) and artifacts (deployable files) +// This is added by GradleRIO's backing project DeployUtils. +deploy { + targets { + systemcore(getTargetTypeClass('SystemCore')) { + // Team number is loaded either from the .wpilib/wpilib_preferences.json + // or from command line. If not found an exception will be thrown. + // You can use getTeamOrDefault(team) instead of getTeamNumber if you + // want to store a team number in this file. + team = project.wpilib.getTeamNumber() + // Use the default systemcore host name. This must be called after setting team + // as happens on the line above + useDefaultSystemcoreHostName() + debug = project.wpilib.getDebugOrDefault(false) + + artifacts { + // First part is artifact name, 2nd is artifact type + // getTargetTypeClass is a shortcut to get the class type using a string + + wpilibJava(getArtifactTypeClass('WPILibJavaArtifact')) { + } + + // Static files artifact + wpilibStaticFileDeploy(getArtifactTypeClass('FileTreeArtifact')) { + files = project.fileTree('src/main/deploy') + directory = '/home/systemcore/deploy' + } + } + } + } +} + +def deployArtifact = deploy.targets.systemcore.artifacts.wpilibJava + +wpi.java.debugJni = false + +// Defining my dependencies. In this case, WPILib (+ friends), and vendor libraries. +// Also defines JUnit 4. +dependencies { + annotationProcessor wpi.java.deps.wpilibAnnotations() + implementation wpi.java.deps.wpilib() + implementation wpi.java.vendor.java() + + systemcoreDebug wpi.java.deps.wpilibJniDebug(wpi.platforms.systemcore) + systemcoreDebug wpi.java.vendor.jniDebug(wpi.platforms.systemcore) + + systemcoreRelease wpi.java.deps.wpilibJniRelease(wpi.platforms.systemcore) + systemcoreRelease wpi.java.vendor.jniRelease(wpi.platforms.systemcore) + + nativeDebug wpi.java.deps.wpilibJniDebug(wpi.platforms.desktop) + nativeDebug wpi.java.vendor.jniDebug(wpi.platforms.desktop) + simulationDebug wpi.sim.enableDebug() + + nativeRelease wpi.java.deps.wpilibJniRelease(wpi.platforms.desktop) + nativeRelease wpi.java.vendor.jniRelease(wpi.platforms.desktop) + simulationRelease wpi.sim.enableRelease() + + testImplementation 'junit:junit:4.12' +} + + +// Simulation configuration (e.g. environment variables). + +wpi.sim.addGui().defaultEnabled = true +wpi.sim.addDriverstation() + +//Sets the websocket client remote host. +wpi.sim.envVar("HALSIMWS_HOST", "10.0.0.2") +wpi.sim.addWebsocketsServer().defaultEnabled = true +wpi.sim.addWebsocketsClient().defaultEnabled = true + +application.mainClass = ROBOT_MAIN_CLASS + +deployArtifact.configureApplication(application) +wpi.java.configureExecutableTasks(jar) +wpi.java.configureJavaExecApplication(run) +wpi.java.configureTestTasks(test) diff --git a/testing/kotlin/settings.gradle b/testing/kotlin/settings.gradle new file mode 100644 index 00000000..ce7076f6 --- /dev/null +++ b/testing/kotlin/settings.gradle @@ -0,0 +1,27 @@ +import org.gradle.internal.os.OperatingSystem + +pluginManagement { + repositories { + mavenLocal() + gradlePluginPortal() + String wpilibYear = '2027_alpha5' + File wpilibHome + if (OperatingSystem.current().isWindows()) { + String publicFolder = System.getenv('PUBLIC') + if (publicFolder == null) { + publicFolder = "C:\\Users\\Public" + } + def homeRoot = new File(publicFolder, "wpilib") + wpilibHome = new File(homeRoot, wpilibYear) + } else { + def userFolder = System.getProperty("user.home") + def homeRoot = new File(userFolder, "wpilib") + wpilibHome = new File(homeRoot, wpilibYear) + } + def wpilibHomeMaven = new File(wpilibHome, 'maven') + maven { + name = 'wpilibHome' + url = wpilibHomeMaven + } + } +} diff --git a/testing/kotlin/src/main/kotlin/first/team0000/robot/Main.kt b/testing/kotlin/src/main/kotlin/first/team0000/robot/Main.kt new file mode 100644 index 00000000..b81e9e88 --- /dev/null +++ b/testing/kotlin/src/main/kotlin/first/team0000/robot/Main.kt @@ -0,0 +1,21 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FIRST teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +@file:JvmName("Main") + +package first.team0000.robot + +import org.wpilib.framework.RobotBase + +/** + * Main initialization function. Do not perform any initialization here. + * + * If you change your main robot class, change the parameter type. + */ +fun main() { + RobotBase.startRobot(Robot::class.java) +} diff --git a/testing/kotlin/src/main/kotlin/first/team0000/robot/Robot.kt b/testing/kotlin/src/main/kotlin/first/team0000/robot/Robot.kt new file mode 100644 index 00000000..bb63adf2 --- /dev/null +++ b/testing/kotlin/src/main/kotlin/first/team0000/robot/Robot.kt @@ -0,0 +1,85 @@ +package first.team0000.robot + +import org.wpilib.framework.TimedRobot +import org.wpilib.smartdashboard.SendableChooser +import org.wpilib.smartdashboard.SmartDashboard + +/** + * The VM is configured to automatically run this class, and to call the + * functions corresponding to each mode, as described in the TimedRobot + * documentation. If you change the name of this class or the package after + * creating this project, you must also update the build.gradle file in the + * project. + */ +class Robot : TimedRobot() { + private var autoSelected = kDefaultAuto + private val chooser = SendableChooser() + + init { + chooser.setDefaultOption("Default Auto", kDefaultAuto) + chooser.addOption("My Auto", kCustomAuto) + SmartDashboard.putData("Auto choices", chooser) + } + + /** + * This function is called every robot packet, no matter the mode. Use + * this for items like diagnostics that you want ran during disabled, + * autonomous, teleoperated and test. + * + * This runs after the mode specific periodic functions, but before + * LiveWindow and SmartDashboard integrated updating. + */ + override fun robotPeriodic() { + } + + /** + * This autonomous (along with the chooser code above) shows how to select + * between different autonomous modes using the dashboard. The sendable + * chooser code works with the Java SmartDashboard. If you prefer the + * LabVIEW Dashboard, remove all of the chooser code and uncomment the + * getString line to get the auto name from the text box below the Gyro + * + * You can add additional auto modes by adding additional comparisons to + * the switch structure below with additional strings. If using the + * SendableChooser make sure to add them to the chooser code above as well. + */ + override fun autonomousInit() { + autoSelected = chooser.selected ?: kDefaultAuto + // autoSelected = SmartDashboard.getString("Auto Selector", kDefaultAuto) + println("Auto selected: $autoSelected") + } + + /** + * This function is called periodically during autonomous. + */ + override fun autonomousPeriodic() { + when (autoSelected) { + kCustomAuto -> { + // Put custom auto code here + } + kDefaultAuto -> { + // Put default auto code here + } + else -> { + // Put default auto code here + } + } + } + + /** + * This function is called periodically during operator control. + */ + override fun teleopPeriodic() { + } + + /** + * This function is called periodically during utility mode. + */ + override fun utilityPeriodic() { + } + + private companion object { + const val kDefaultAuto = "Default" + const val kCustomAuto = "My Auto" + } +} diff --git a/testing/kotlin/src/test/kotlin/TestCode.kt b/testing/kotlin/src/test/kotlin/TestCode.kt new file mode 100644 index 00000000..f9753689 --- /dev/null +++ b/testing/kotlin/src/test/kotlin/TestCode.kt @@ -0,0 +1,9 @@ +import org.junit.Test +import org.wpilib.vision.camera.CameraServerJNI + +class TestCode { + @Test + fun jniLinkTest() { + CameraServerJNI.getHostname() + } +} From 7fffc64366f80ce9919fa9e106990cdc69df7c71 Mon Sep 17 00:00:00 2001 From: Thad House Date: Sat, 20 Jun 2026 21:53:06 -0700 Subject: [PATCH 07/10] Fully remove simulateJava --- .../deploy/systemcore/WPILibJavaArtifact.java | 11 ++- .../JavaExternalSimulationTask.java | 62 ++++++++---- .../gradlerio/wpi/java/WPIJavaExtension.java | 98 ++++++------------- testing/java/build.gradle | 6 +- testing/kotlin/build.gradle | 6 +- 5 files changed, 87 insertions(+), 96 deletions(-) diff --git a/src/main/java/org/wpilib/gradlerio/deploy/systemcore/WPILibJavaArtifact.java b/src/main/java/org/wpilib/gradlerio/deploy/systemcore/WPILibJavaArtifact.java index 28313b83..92f7b19b 100644 --- a/src/main/java/org/wpilib/gradlerio/deploy/systemcore/WPILibJavaArtifact.java +++ b/src/main/java/org/wpilib/gradlerio/deploy/systemcore/WPILibJavaArtifact.java @@ -37,10 +37,18 @@ public class WPILibJavaArtifact extends DebuggableJavaArtifact { private String javaCommand = "/usr/bin/java"; + private final Property debugJni; + + public Property getDebugJni() { + return debugJni; + } + @Inject public WPILibJavaArtifact(String name, SystemCore target) { super(name, target); systemCore = target; + debugJni = target.getProject().getObjects().property(Boolean.class); + debugJni.set(false); jvmArgs.add("-Djava.library.path=" + WPILibDeployPlugin.LIB_DEPLOY_DIR); jvmArgs.add("--add-opens"); @@ -63,14 +71,13 @@ public WPILibJavaArtifact(String name, SystemCore target) { art.dependsOn(getJarProvider()); art.dependsOn(getConfigurationProvider()); art.dependsOn(this.getDeployTask()); - //art.dependsOn(getJarProvider()); }); nativeZipArtifact = target.getArtifacts().create("nativeZips" + name, WPILibJNILibraryArtifact.class, artifact -> { target.setDeployStage(artifact, DeployStage.FileDeploy); var cbl = target.getProject().getProviders().provider(() -> { - boolean debug = target.getProject().getExtensions().getByType(WPIExtension.class).getJava().getDebugJni().get(); + boolean debug = getDebugJni().get(); if (debug) { return debugConfiguration; } else { diff --git a/src/main/java/org/wpilib/gradlerio/simulation/JavaExternalSimulationTask.java b/src/main/java/org/wpilib/gradlerio/simulation/JavaExternalSimulationTask.java index 064349c1..cd44dd7f 100644 --- a/src/main/java/org/wpilib/gradlerio/simulation/JavaExternalSimulationTask.java +++ b/src/main/java/org/wpilib/gradlerio/simulation/JavaExternalSimulationTask.java @@ -12,41 +12,53 @@ import org.codehaus.groovy.runtime.ResourceGroovyMethods; import org.gradle.api.DefaultTask; -import org.gradle.api.Project; +import org.gradle.api.GradleException; import org.gradle.api.file.RegularFileProperty; import org.gradle.api.model.ObjectFactory; +import org.gradle.api.plugins.JavaApplication; +import org.gradle.api.plugins.internal.JavaPluginHelper; +import org.gradle.api.plugins.jvm.internal.JvmFeatureInternal; import org.gradle.api.provider.Provider; -import org.gradle.api.tasks.Internal; import org.gradle.api.tasks.OutputFile; import org.gradle.api.tasks.TaskAction; -import org.gradle.jvm.tasks.Jar; import org.wpilib.gradlerio.wpi.WPIExtension; import org.wpilib.gradlerio.wpi.java.ExtractNativeJavaArtifacts; import org.wpilib.gradlerio.wpi.simulation.SimulationExtension; public class JavaExternalSimulationTask extends DefaultTask { - private final List jars = new ArrayList<>(); private Provider extractJni; private boolean isDebug; + private final String projectName; + private final WPIExtension ext; + private JavaApplication application; + + // @Internal + // public List getJars() { + // return jars; + // } + + // public void setDependencies(SimulationExtension sim, + // Provider extract, boolean debug, Project project) + // { + // this.extractJni = extract; + // isDebug = debug; + // this.dependsOn(extractJni); + // } - @Internal - public List getJars() { - return jars; + @Inject + public JavaExternalSimulationTask(ObjectFactory objects) { + getOutputs().upToDateWhen(spec -> false); + simulationFile = objects.fileProperty(); + this.projectName = getProject().getName(); + this.ext = getProject().getExtensions().getByType(WPIExtension.class); } - public void setDependencies(SimulationExtension sim, Provider extract, boolean debug, Project project) { + public void setDependencies(Provider extract, boolean debug) { this.extractJni = extract; isDebug = debug; this.dependsOn(extractJni); } - @Inject - public JavaExternalSimulationTask(ObjectFactory objects) { - getOutputs().upToDateWhen(spec -> false); - dependsOn(jars); - simulationFile = objects.fileProperty(); - } - private final RegularFileProperty simulationFile; @OutputFile @@ -72,9 +84,19 @@ public SimInfo(String name, List extensions, Map env } } + public void setApplication(JavaApplication application) { + this.application = application; + JvmFeatureInternal mainFeature = JavaPluginHelper.getJavaComponent(getProject()).getMainFeature(); + dependsOn(mainFeature.getRuntimeClasspathConfiguration()); + dependsOn(mainFeature.getJarTask()); + } + @TaskAction public void execute() throws IOException { - var ext = getProject().getExtensions().getByType(WPIExtension.class); + if (application == null) { + throw new GradleException("Java application is not set for simulation task."); + } + SimulationExtension sim = ext.getSim(); File ldpath = extractJni.get().getDestinationDirectory().get().getAsFile(); @@ -85,13 +107,11 @@ public void execute() throws IOException { Map env = sim.getEnvironment(); - for (Jar jar : jars) { - String name = jar.getName() + " (in project " + getProject().getName() + ")"; + String name = application.getApplicationName() + " (in project " + projectName + ")"; - String mainClass = (String)jar.getManifest().getAttributes().get("Main-Class"); + String mainClass = application.getMainClass().get(); - simInfo.add(new SimInfo(name, extensions, env, ldpath.getAbsolutePath(), mainClass)); - } + simInfo.add(new SimInfo(name, extensions, env, ldpath.getAbsolutePath(), mainClass)); GsonBuilder builder = new GsonBuilder(); builder.setPrettyPrinting(); diff --git a/src/main/java/org/wpilib/gradlerio/wpi/java/WPIJavaExtension.java b/src/main/java/org/wpilib/gradlerio/wpi/java/WPIJavaExtension.java index 53d89574..d8b24b4a 100644 --- a/src/main/java/org/wpilib/gradlerio/wpi/java/WPIJavaExtension.java +++ b/src/main/java/org/wpilib/gradlerio/wpi/java/WPIJavaExtension.java @@ -17,6 +17,9 @@ import org.gradle.api.artifacts.Configuration; import org.gradle.api.file.DirectoryProperty; import org.gradle.api.file.FileCollection; +import org.gradle.api.plugins.JavaApplication; +import org.gradle.api.plugins.internal.JavaPluginHelper; +import org.gradle.api.plugins.jvm.internal.JvmFeatureInternal; import org.gradle.api.provider.Property; import org.gradle.api.provider.Provider; import org.gradle.api.tasks.JavaExec; @@ -86,10 +89,10 @@ public FileCollection getReleaseFileCollection() { return releaseFileCollection; } - private final Property debugJni; + private final Property runSimWithDebugJni; - public Property getDebugJni() { - return debugJni; + public Property getRunSimWithDebugJni() { + return runSimWithDebugJni; } private final TaskProvider externalSimulationTaskDebug; @@ -104,20 +107,10 @@ public TaskProvider getExternalSimulationTaskRelease return externalSimulationTaskRelease; } - private final TaskProvider simulationTaskDebug; - private final TaskProvider simulationTaskRelease; + private final Provider typedExtractNativeArtifacts; - public TaskProvider getSimulationTaskDebug() { - return simulationTaskDebug; - } - - public TaskProvider getSimulationTaskRelease() { - return simulationTaskRelease; - } - - private void configureSimulationTask(JavaExec t, boolean debug, - Provider extract) { - configureExecutableNatives(t, extract); + private void configureSimulationTask(JavaExec t) { + configureExecutableNatives(t); List jvmArgs = new ArrayList<>(); jvmArgs.add("--add-opens"); jvmArgs.add("java.base/jdk.internal.vm=ALL-UNNAMED"); @@ -133,8 +126,8 @@ private void configureSimulationTask(JavaExec t, boolean debug, @Override public void execute(Task task) { - File ldpath = extract.get().getDestinationDirectory().get().getAsFile(); - List extensions = sim.getHalSimLocations(List.of(ldpath), debug); + File ldpath = typedExtractNativeArtifacts.get().getDestinationDirectory().get().getAsFile(); + List extensions = sim.getHalSimLocations(List.of(ldpath), getRunSimWithDebugJni().get()); Map env = sim.getEnvironment(); t.environment(env); @@ -155,13 +148,13 @@ public void execute(Task task) { }); } - private void configureExecutableNatives(JavaForkOptions t, Provider extract) { + private void configureExecutableNatives(JavaForkOptions t) { Task tt = (Task) t; - tt.dependsOn(extract); + tt.dependsOn(typedExtractNativeArtifacts); Provider destDir = project.getProviders().provider(() -> { - return extract.get().getDestinationDirectory(); + return typedExtractNativeArtifacts.get().getDestinationDirectory(); }); tt.getInputs().dir(destDir); @@ -170,18 +163,7 @@ private void configureExecutableNatives(JavaForkOptions t, Provider debug = this.getDebugJni(); - TaskProvider debugTask = this.getExtractNativeDebugArtifacts(); - TaskProvider releaseTask = this.getExtractNativeReleaseArtifacts(); - Provider extract = project.getProviders().provider(() -> { - if (debug.get()) { - return debugTask.get(); - } else { - return releaseTask.get(); - } - }); - - configureExecutableNatives(t, extract); + configureExecutableNatives(t); t.testLogging(new Action() { @Override @@ -193,19 +175,12 @@ public void execute(TestLogging log) { }); } - public void configureExecutableTasks(Jar jar) { - externalSimulationTaskDebug.configure(x -> x.getJars().add(jar)); - externalSimulationTaskRelease.configure(x -> x.getJars().add(jar)); - simulationTaskDebug.configure(x -> x.classpath(jar)); - simulationTaskRelease.configure(x -> x.classpath(jar)); - } - - public void configureJavaExecApplication(JavaExec application) { - configureJavaExecApplication(application, false); - } - - public void configureJavaExecApplication(JavaExec application, boolean debug) { - configureSimulationTask(application, debug, debug ? extractNativeDebugArtifacts : extractNativeReleaseArtifacts); + public void configureApplication(JavaApplication application) { + externalSimulationTaskDebug.configure(x -> x.setApplication(application)); + externalSimulationTaskRelease.configure(x -> x.setApplication(application)); + project.getTasks().named("run").configure(t -> { + configureSimulationTask((JavaExec) t); + }); } @Inject @@ -217,8 +192,8 @@ public WPIJavaExtension(Project project, SimulationExtension sim, WPIVersionsExt extractNativeReleaseArtifacts = project.getTasks().register("extractReleaseNative", ExtractNativeJavaArtifacts.class); - debugJni = project.getObjects().property(Boolean.class); - debugJni.set(false); + runSimWithDebugJni = project.getObjects().property(Boolean.class); + runSimWithDebugJni.set(false); deps = project.getObjects().newInstance(WPIJavaDepsExtension.class, versions); vendor = project.getExtensions().getByType(WPIVendorDepsExtension.class).getJavaVendor(); @@ -264,32 +239,21 @@ public WPIJavaExtension(Project project, SimulationExtension sim, WPIVersionsExt externalSimulationTaskDebug = project.getTasks().register("simulateExternalJavaDebug", JavaExternalSimulationTask.class, t -> { t.getSimulationFile().set(project.getLayout().getBuildDirectory().file("sim/debug_java.json")); - t.setDependencies(sim, extractNativeDebugArtifacts, true, project); + t.setDependencies(extractNativeDebugArtifacts, true); }); externalSimulationTaskRelease = project.getTasks().register("simulateExternalJavaRelease", JavaExternalSimulationTask.class, t -> { t.getSimulationFile().set(project.getLayout().getBuildDirectory().file("sim/release_java.json")); - t.setDependencies(sim, extractNativeReleaseArtifacts, false, project); + t.setDependencies(extractNativeReleaseArtifacts, false); }); - simulationTaskDebug = project.getTasks().register("simulateJavaDebug", JavaSimulationTask.class, t -> { - configureSimulationTask(t, true, extractNativeDebugArtifacts); - }); - - simulationTaskRelease = project.getTasks().register("simulateJavaRelease", JavaSimulationTask.class, t -> { - configureSimulationTask(t, false, extractNativeReleaseArtifacts); - }); - - project.getTasks().register("simulateJava", t -> { - var simTask = project.getProviders().provider(() -> { - if (getDebugJni().get()) { - return simulationTaskDebug; - } else { - return simulationTaskRelease; - } - }); - t.dependsOn(simTask); + typedExtractNativeArtifacts = project.getProviders().provider(() -> { + if (runSimWithDebugJni.get()) { + return extractNativeDebugArtifacts.get(); + } else { + return extractNativeReleaseArtifacts.get(); + } }); } } diff --git a/testing/java/build.gradle b/testing/java/build.gradle index 85c2be88..d063048d 100644 --- a/testing/java/build.gradle +++ b/testing/java/build.gradle @@ -38,6 +38,7 @@ deploy { // getTargetTypeClass is a shortcut to get the class type using a string wpilibJava(getArtifactTypeClass('WPILibJavaArtifact')) { + debugJni = false } // Static files artifact @@ -52,7 +53,7 @@ deploy { def deployArtifact = deploy.targets.systemcore.artifacts.wpilibJava -wpi.java.debugJni = false +wpi.java.runSimWithDebugJni = false // Defining my dependencies. In this case, WPILib (+ friends), and vendor libraries. // Also defines JUnit 4. @@ -92,6 +93,5 @@ wpi.sim.addWebsocketsClient().defaultEnabled = true application.mainClass = ROBOT_MAIN_CLASS deployArtifact.configureApplication(application) -wpi.java.configureExecutableTasks(jar) -wpi.java.configureJavaExecApplication(run) +wpi.java.configureApplication(application) wpi.java.configureTestTasks(test) diff --git a/testing/kotlin/build.gradle b/testing/kotlin/build.gradle index e693b181..744f681d 100644 --- a/testing/kotlin/build.gradle +++ b/testing/kotlin/build.gradle @@ -38,6 +38,7 @@ deploy { // getTargetTypeClass is a shortcut to get the class type using a string wpilibJava(getArtifactTypeClass('WPILibJavaArtifact')) { + debugJni = false } // Static files artifact @@ -52,7 +53,7 @@ deploy { def deployArtifact = deploy.targets.systemcore.artifacts.wpilibJava -wpi.java.debugJni = false +wpi.java.runSimWithDebugJni = false // Defining my dependencies. In this case, WPILib (+ friends), and vendor libraries. // Also defines JUnit 4. @@ -92,6 +93,5 @@ wpi.sim.addWebsocketsClient().defaultEnabled = true application.mainClass = ROBOT_MAIN_CLASS deployArtifact.configureApplication(application) -wpi.java.configureExecutableTasks(jar) -wpi.java.configureJavaExecApplication(run) +wpi.java.configureApplication(application) wpi.java.configureTestTasks(test) From cd49cfc908444555e14889da3dc4991be1e84e35 Mon Sep 17 00:00:00 2001 From: Thad House Date: Sat, 20 Jun 2026 22:23:48 -0700 Subject: [PATCH 08/10] Support external running from VS Code --- .../gradlerio/simulation/HalSimPair.java | 4 ++ .../JavaExternalSimulationTask.java | 21 +++------ .../gradlerio/wpi/java/WPIJavaExtension.java | 43 ++++++++++++++++--- 3 files changed, 47 insertions(+), 21 deletions(-) diff --git a/src/main/java/org/wpilib/gradlerio/simulation/HalSimPair.java b/src/main/java/org/wpilib/gradlerio/simulation/HalSimPair.java index 767ab7d5..81cab40e 100644 --- a/src/main/java/org/wpilib/gradlerio/simulation/HalSimPair.java +++ b/src/main/java/org/wpilib/gradlerio/simulation/HalSimPair.java @@ -10,4 +10,8 @@ public HalSimPair(String name, String libName, boolean defaultEnabled) { this.libName = libName; this.defaultEnabled = defaultEnabled; } + + public HalSimPair withDefaultEnabled(boolean defaultEnabled) { + return new HalSimPair(this.name, this.libName, defaultEnabled); + } } diff --git a/src/main/java/org/wpilib/gradlerio/simulation/JavaExternalSimulationTask.java b/src/main/java/org/wpilib/gradlerio/simulation/JavaExternalSimulationTask.java index cd44dd7f..7ef03126 100644 --- a/src/main/java/org/wpilib/gradlerio/simulation/JavaExternalSimulationTask.java +++ b/src/main/java/org/wpilib/gradlerio/simulation/JavaExternalSimulationTask.java @@ -31,19 +31,7 @@ public class JavaExternalSimulationTask extends DefaultTask { private final String projectName; private final WPIExtension ext; private JavaApplication application; - - // @Internal - // public List getJars() { - // return jars; - // } - - // public void setDependencies(SimulationExtension sim, - // Provider extract, boolean debug, Project project) - // { - // this.extractJni = extract; - // isDebug = debug; - // this.dependsOn(extractJni); - // } + private String taskPath; @Inject public JavaExternalSimulationTask(ObjectFactory objects) { @@ -73,14 +61,16 @@ public static class SimInfo { public final Map environment; public final String libraryDir; public final String mainClassName; + public final String taskPath; public SimInfo(String name, List extensions, Map environment, String libraryDir, - String mainClassName) { + String mainClassName, String taskPath) { this.name = name; this.extensions = extensions; this.environment = environment; this.libraryDir = libraryDir; this.mainClassName = mainClassName; + this.taskPath = taskPath; } } @@ -89,6 +79,7 @@ public void setApplication(JavaApplication application) { JvmFeatureInternal mainFeature = JavaPluginHelper.getJavaComponent(getProject()).getMainFeature(); dependsOn(mainFeature.getRuntimeClasspathConfiguration()); dependsOn(mainFeature.getJarTask()); + this.taskPath = getProject().getTasks().named("run").get().getPath(); } @TaskAction @@ -111,7 +102,7 @@ public void execute() throws IOException { String mainClass = application.getMainClass().get(); - simInfo.add(new SimInfo(name, extensions, env, ldpath.getAbsolutePath(), mainClass)); + simInfo.add(new SimInfo(name, extensions, env, ldpath.getAbsolutePath(), mainClass, taskPath)); GsonBuilder builder = new GsonBuilder(); builder.setPrettyPrinting(); diff --git a/src/main/java/org/wpilib/gradlerio/wpi/java/WPIJavaExtension.java b/src/main/java/org/wpilib/gradlerio/wpi/java/WPIJavaExtension.java index d8b24b4a..2ccc82c2 100644 --- a/src/main/java/org/wpilib/gradlerio/wpi/java/WPIJavaExtension.java +++ b/src/main/java/org/wpilib/gradlerio/wpi/java/WPIJavaExtension.java @@ -1,6 +1,7 @@ package org.wpilib.gradlerio.wpi.java; import java.io.File; +import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -10,7 +11,9 @@ import javax.inject.Inject; +import org.codehaus.groovy.runtime.ResourceGroovyMethods; import org.gradle.api.Action; +import org.gradle.api.GradleException; import org.gradle.api.Project; import org.gradle.api.Task; import org.gradle.api.artifacts.ArtifactView; @@ -18,8 +21,6 @@ import org.gradle.api.file.DirectoryProperty; import org.gradle.api.file.FileCollection; import org.gradle.api.plugins.JavaApplication; -import org.gradle.api.plugins.internal.JavaPluginHelper; -import org.gradle.api.plugins.jvm.internal.JvmFeatureInternal; import org.gradle.api.provider.Property; import org.gradle.api.provider.Provider; import org.gradle.api.tasks.JavaExec; @@ -29,18 +30,18 @@ import org.gradle.api.tasks.testing.logging.TestLogging; import org.gradle.api.tasks.util.PatternFilterable; import org.gradle.api.tasks.util.PatternSet; -import org.gradle.jvm.tasks.Jar; import org.gradle.process.JavaForkOptions; import org.gradle.internal.os.OperatingSystem; import org.wpilib.gradlerio.simulation.HalSimPair; import org.wpilib.gradlerio.simulation.JavaExternalSimulationTask; -import org.wpilib.gradlerio.simulation.JavaSimulationTask; import org.wpilib.gradlerio.wpi.WPIPlugin; import org.wpilib.gradlerio.wpi.WPIVersionsExtension; import org.wpilib.gradlerio.wpi.simulation.SimulationExtension; import org.wpilib.nativeutils.vendordeps.WPIJavaVendorDepsExtension; import org.wpilib.nativeutils.vendordeps.WPIVendorDepsExtension; +import com.google.gson.Gson; + public class WPIJavaExtension { private final Project project; private final SimulationExtension sim; @@ -109,7 +110,11 @@ public TaskProvider getExternalSimulationTaskRelease private final Provider typedExtractNativeArtifacts; - private void configureSimulationTask(JavaExec t) { + private static class ExternalRunConfig { + public List runningExtensions; + } + + private void configureRunTask(JavaExec t) { configureExecutableNatives(t); List jvmArgs = new ArrayList<>(); jvmArgs.add("--add-opens"); @@ -122,12 +127,38 @@ private void configureSimulationTask(JavaExec t) { } t.jvmArgs(jvmArgs); + List externalExtensions = null; + + Object externalConfigProperty = t.getProject().findProperty("externalRunConfiguration"); + if (externalConfigProperty instanceof String runConfigString) { + File runConfigFile = new File(runConfigString); + Gson gson = new Gson(); + try { + String runConfig = ResourceGroovyMethods.getText(runConfigFile, "UTF-8"); + ExternalRunConfig runConfigObj = gson.fromJson(runConfig, ExternalRunConfig.class); + externalExtensions = runConfigObj.runningExtensions; + } catch (IOException e) { + throw new GradleException("Failed to read run configuration file: " + runConfigFile, e); + } + } + + List finalExternalExtensions = externalExtensions; + t.doFirst(new Action() { @Override public void execute(Task task) { File ldpath = typedExtractNativeArtifacts.get().getDestinationDirectory().get().getAsFile(); List extensions = sim.getHalSimLocations(List.of(ldpath), getRunSimWithDebugJni().get()); + + // Enumerate external extensions, launching the ones requested + if (finalExternalExtensions != null) { + for (int i = 0; i < extensions.size(); i++) { + HalSimPair ext = extensions.get(i); + extensions.set(i, ext.withDefaultEnabled(finalExternalExtensions.contains(ext.libName))); + } + } + Map env = sim.getEnvironment(); t.environment(env); @@ -179,7 +210,7 @@ public void configureApplication(JavaApplication application) { externalSimulationTaskDebug.configure(x -> x.setApplication(application)); externalSimulationTaskRelease.configure(x -> x.setApplication(application)); project.getTasks().named("run").configure(t -> { - configureSimulationTask((JavaExec) t); + configureRunTask((JavaExec) t); }); } From 1740c5aa8f99fbb71eff71740195d57212ed728b Mon Sep 17 00:00:00 2001 From: Thad House Date: Sat, 20 Jun 2026 22:34:13 -0700 Subject: [PATCH 09/10] Strink to single external task too --- .../JavaExternalSimulationTask.java | 23 +++++++++---- .../gradlerio/wpi/java/WPIJavaExtension.java | 34 ++++++------------- 2 files changed, 27 insertions(+), 30 deletions(-) diff --git a/src/main/java/org/wpilib/gradlerio/simulation/JavaExternalSimulationTask.java b/src/main/java/org/wpilib/gradlerio/simulation/JavaExternalSimulationTask.java index 7ef03126..e93a414c 100644 --- a/src/main/java/org/wpilib/gradlerio/simulation/JavaExternalSimulationTask.java +++ b/src/main/java/org/wpilib/gradlerio/simulation/JavaExternalSimulationTask.java @@ -18,7 +18,9 @@ import org.gradle.api.plugins.JavaApplication; import org.gradle.api.plugins.internal.JavaPluginHelper; import org.gradle.api.plugins.jvm.internal.JvmFeatureInternal; +import org.gradle.api.provider.Property; import org.gradle.api.provider.Provider; +import org.gradle.api.tasks.JavaExec; import org.gradle.api.tasks.OutputFile; import org.gradle.api.tasks.TaskAction; import org.wpilib.gradlerio.wpi.WPIExtension; @@ -27,11 +29,12 @@ public class JavaExternalSimulationTask extends DefaultTask { private Provider extractJni; - private boolean isDebug; private final String projectName; private final WPIExtension ext; private JavaApplication application; private String taskPath; + private Property debugPort; + private Property runSimWithDebugJni; @Inject public JavaExternalSimulationTask(ObjectFactory objects) { @@ -39,12 +42,14 @@ public JavaExternalSimulationTask(ObjectFactory objects) { simulationFile = objects.fileProperty(); this.projectName = getProject().getName(); this.ext = getProject().getExtensions().getByType(WPIExtension.class); + this.debugPort = objects.property(Integer.class); + this.runSimWithDebugJni = objects.property(Boolean.class); } - public void setDependencies(Provider extract, boolean debug) { + public void setDependencies(Provider extract, Provider runSimWithDebugJni) { this.extractJni = extract; - isDebug = debug; this.dependsOn(extractJni); + this.runSimWithDebugJni.set(runSimWithDebugJni); } private final RegularFileProperty simulationFile; @@ -62,15 +67,17 @@ public static class SimInfo { public final String libraryDir; public final String mainClassName; public final String taskPath; + public final int debugPort; public SimInfo(String name, List extensions, Map environment, String libraryDir, - String mainClassName, String taskPath) { + String mainClassName, String taskPath, int debugPort) { this.name = name; this.extensions = extensions; this.environment = environment; this.libraryDir = libraryDir; this.mainClassName = mainClassName; this.taskPath = taskPath; + this.debugPort = debugPort; } } @@ -79,7 +86,9 @@ public void setApplication(JavaApplication application) { JvmFeatureInternal mainFeature = JavaPluginHelper.getJavaComponent(getProject()).getMainFeature(); dependsOn(mainFeature.getRuntimeClasspathConfiguration()); dependsOn(mainFeature.getJarTask()); - this.taskPath = getProject().getTasks().named("run").get().getPath(); + JavaExec runTask = (JavaExec) getProject().getTasks().named("run").get(); + this.taskPath = runTask.getPath(); + this.debugPort.set(runTask.getDebugOptions().getPort()); } @TaskAction @@ -94,7 +103,7 @@ public void execute() throws IOException { List simInfo = new ArrayList<>(); - List extensions = sim.getHalSimLocations(List.of(ldpath), isDebug); + List extensions = sim.getHalSimLocations(List.of(ldpath), runSimWithDebugJni.get()); Map env = sim.getEnvironment(); @@ -102,7 +111,7 @@ public void execute() throws IOException { String mainClass = application.getMainClass().get(); - simInfo.add(new SimInfo(name, extensions, env, ldpath.getAbsolutePath(), mainClass, taskPath)); + simInfo.add(new SimInfo(name, extensions, env, ldpath.getAbsolutePath(), mainClass, taskPath, debugPort.get())); GsonBuilder builder = new GsonBuilder(); builder.setPrettyPrinting(); diff --git a/src/main/java/org/wpilib/gradlerio/wpi/java/WPIJavaExtension.java b/src/main/java/org/wpilib/gradlerio/wpi/java/WPIJavaExtension.java index 2ccc82c2..18bb3bfd 100644 --- a/src/main/java/org/wpilib/gradlerio/wpi/java/WPIJavaExtension.java +++ b/src/main/java/org/wpilib/gradlerio/wpi/java/WPIJavaExtension.java @@ -96,16 +96,10 @@ public Property getRunSimWithDebugJni() { return runSimWithDebugJni; } - private final TaskProvider externalSimulationTaskDebug; + private final TaskProvider externalSimulationTask; - public TaskProvider getExternalSimulationTaskDebug() { - return externalSimulationTaskDebug; - } - - private final TaskProvider externalSimulationTaskRelease; - - public TaskProvider getExternalSimulationTaskRelease() { - return externalSimulationTaskRelease; + public TaskProvider getExternalSimulationTask() { + return externalSimulationTask; } private final Provider typedExtractNativeArtifacts; @@ -137,6 +131,7 @@ private void configureRunTask(JavaExec t) { String runConfig = ResourceGroovyMethods.getText(runConfigFile, "UTF-8"); ExternalRunConfig runConfigObj = gson.fromJson(runConfig, ExternalRunConfig.class); externalExtensions = runConfigObj.runningExtensions; + t.setDebug(true); } catch (IOException e) { throw new GradleException("Failed to read run configuration file: " + runConfigFile, e); } @@ -207,8 +202,7 @@ public void execute(TestLogging log) { } public void configureApplication(JavaApplication application) { - externalSimulationTaskDebug.configure(x -> x.setApplication(application)); - externalSimulationTaskRelease.configure(x -> x.setApplication(application)); + externalSimulationTask.configure(x -> x.setApplication(application)); project.getTasks().named("run").configure(t -> { configureRunTask((JavaExec) t); }); @@ -267,18 +261,6 @@ public WPIJavaExtension(Project project, SimulationExtension sim, WPIVersionsExt extract.getFiles().from(releaseFileCollection); }); - externalSimulationTaskDebug = project.getTasks().register("simulateExternalJavaDebug", - JavaExternalSimulationTask.class, t -> { - t.getSimulationFile().set(project.getLayout().getBuildDirectory().file("sim/debug_java.json")); - t.setDependencies(extractNativeDebugArtifacts, true); - }); - - externalSimulationTaskRelease = project.getTasks().register("simulateExternalJavaRelease", - JavaExternalSimulationTask.class, t -> { - t.getSimulationFile().set(project.getLayout().getBuildDirectory().file("sim/release_java.json")); - t.setDependencies(extractNativeReleaseArtifacts, false); - }); - typedExtractNativeArtifacts = project.getProviders().provider(() -> { if (runSimWithDebugJni.get()) { return extractNativeDebugArtifacts.get(); @@ -286,5 +268,11 @@ public WPIJavaExtension(Project project, SimulationExtension sim, WPIVersionsExt return extractNativeReleaseArtifacts.get(); } }); + + externalSimulationTask = project.getTasks().register("simulateExternalJava", + JavaExternalSimulationTask.class, t -> { + t.getSimulationFile().set(project.getLayout().getBuildDirectory().file("sim/java.json")); + t.setDependencies(typedExtractNativeArtifacts, runSimWithDebugJni); + }); } } From a327b20f2d135aff69ef026adf1a5b65ed0e4fd4 Mon Sep 17 00:00:00 2001 From: Thad House Date: Sat, 20 Jun 2026 22:49:03 -0700 Subject: [PATCH 10/10] Fix jvm 25 --- testing/kotlin/build.gradle | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/testing/kotlin/build.gradle b/testing/kotlin/build.gradle index 744f681d..8de914e2 100644 --- a/testing/kotlin/build.gradle +++ b/testing/kotlin/build.gradle @@ -11,9 +11,8 @@ if (testingFolder.name != 'testing' || projectFolder.name != 'kotlin') { throw new GradleException("These projects are not to be used for robot projects. See README.md in the GradleRIO testing folder for the correct templates to use.") } -java { - sourceCompatibility = JavaVersion.VERSION_25 - targetCompatibility = JavaVersion.VERSION_25 +kotlin { + jvmToolchain(25) } def ROBOT_MAIN_CLASS = "first.team0000.robot.Main"