diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d66292e7..fe790628 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,21 +25,23 @@ 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: 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] + language: [cpp, java, kotlin] #, asm, jni] steps: - uses: actions/checkout@v4 - uses: actions/setup-java@v4 @@ -47,14 +51,14 @@ jobs: - uses: actions/download-artifact@v4 with: name: Maven - path: ~/.m2/repository/ + path: ${{ env.MAVEN_LOCAL_REPO }}/ - 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: 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..92f7b19b 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,17 @@ 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.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; import org.wpilib.gradlerio.deploy.DebuggableJavaArtifact; @@ -16,6 +20,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; @@ -26,14 +31,24 @@ public class WPILibJavaArtifact extends DebuggableJavaArtifact { private final SystemCore systemCore; + private final Property mainClass; + private GarbageCollectorType gcType = GarbageCollectorType.ZGC; 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"); @@ -45,16 +60,24 @@ 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.setStartCommandFunc(this::generateStartCommand); + art.setRobotCommandFunc(this::generateStartCommand); + art.setArgFileFunc(this::generateArgFile); art.dependsOn(getJarProvider()); + art.dependsOn(getConfigurationProvider()); + art.dependsOn(this.getDeployTask()); }); 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 { @@ -89,20 +112,11 @@ 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); - } - - @Override - public void setJarTask(TaskProvider jarTask) { - robotCommandArtifact.getDeployTask().configure(x -> x.dependsOn(jarTask)); - super.setJarTask(jarTask); + 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() { @@ -121,29 +135,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.get()); + + 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/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 064349c1..e93a414c 100644 --- a/src/main/java/org/wpilib/gradlerio/simulation/JavaExternalSimulationTask.java +++ b/src/main/java/org/wpilib/gradlerio/simulation/JavaExternalSimulationTask.java @@ -12,39 +12,44 @@ 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.Property; import org.gradle.api.provider.Provider; -import org.gradle.api.tasks.Internal; +import org.gradle.api.tasks.JavaExec; 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; - - @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 final String projectName; + private final WPIExtension ext; + private JavaApplication application; + private String taskPath; + private Property debugPort; + private Property runSimWithDebugJni; @Inject public JavaExternalSimulationTask(ObjectFactory objects) { getOutputs().upToDateWhen(spec -> false); - dependsOn(jars); 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, Provider runSimWithDebugJni) { + this.extractJni = extract; + this.dependsOn(extractJni); + this.runSimWithDebugJni.set(runSimWithDebugJni); } private final RegularFileProperty simulationFile; @@ -61,37 +66,52 @@ public static class SimInfo { public final Map environment; 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 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; } } + public void setApplication(JavaApplication application) { + this.application = application; + JvmFeatureInternal mainFeature = JavaPluginHelper.getJavaComponent(getProject()).getMainFeature(); + dependsOn(mainFeature.getRuntimeClasspathConfiguration()); + dependsOn(mainFeature.getJarTask()); + JavaExec runTask = (JavaExec) getProject().getTasks().named("run").get(); + this.taskPath = runTask.getPath(); + this.debugPort.set(runTask.getDebugOptions().getPort()); + } + @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(); 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(); - 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, 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 bd44d91d..18bb3bfd 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,33 +11,37 @@ 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; 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.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; 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; @@ -85,38 +90,26 @@ public FileCollection getReleaseFileCollection() { return releaseFileCollection; } - private final Property debugJni; - - public Property getDebugJni() { - return debugJni; - } - - private final TaskProvider externalSimulationTaskDebug; + private final Property runSimWithDebugJni; - public TaskProvider getExternalSimulationTaskDebug() { - return externalSimulationTaskDebug; + public Property getRunSimWithDebugJni() { + return runSimWithDebugJni; } - private final TaskProvider externalSimulationTaskRelease; + private final TaskProvider externalSimulationTask; - public TaskProvider getExternalSimulationTaskRelease() { - return externalSimulationTaskRelease; + public TaskProvider getExternalSimulationTask() { + return externalSimulationTask; } - private final TaskProvider simulationTaskDebug; - private final TaskProvider simulationTaskRelease; + private final Provider typedExtractNativeArtifacts; - public TaskProvider getSimulationTaskDebug() { - return simulationTaskDebug; + private static class ExternalRunConfig { + public List runningExtensions; } - public TaskProvider getSimulationTaskRelease() { - return simulationTaskRelease; - } - - private void configureSimulationTask(JavaSimulationTask t, boolean debug, - Provider extract) { - configureExecutableNatives(t, extract); + private void configureRunTask(JavaExec t) { + configureExecutableNatives(t); List jvmArgs = new ArrayList<>(); jvmArgs.add("--add-opens"); jvmArgs.add("java.base/jdk.internal.vm=ALL-UNNAMED"); @@ -128,12 +121,39 @@ private void configureSimulationTask(JavaSimulationTask t, boolean debug, } 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; + t.setDebug(true); + } 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 = 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()); + + // 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); @@ -154,13 +174,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); @@ -169,18 +189,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 @@ -192,11 +201,11 @@ 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 configureApplication(JavaApplication application) { + externalSimulationTask.configure(x -> x.setApplication(application)); + project.getTasks().named("run").configure(t -> { + configureRunTask((JavaExec) t); + }); } @Inject @@ -208,8 +217,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(); @@ -252,35 +261,18 @@ 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(sim, extractNativeDebugArtifacts, true, project); - }); + typedExtractNativeArtifacts = project.getProviders().provider(() -> { + if (runSimWithDebugJni.get()) { + return extractNativeDebugArtifacts.get(); + } else { + return extractNativeReleaseArtifacts.get(); + } + }); - externalSimulationTaskRelease = project.getTasks().register("simulateExternalJavaRelease", + externalSimulationTask = project.getTasks().register("simulateExternalJava", JavaExternalSimulationTask.class, t -> { - t.getSimulationFile().set(project.getLayout().getBuildDirectory().file("sim/release_java.json")); - t.setDependencies(sim, extractNativeReleaseArtifacts, false, project); + t.getSimulationFile().set(project.getLayout().getBuildDirectory().file("sim/java.json")); + t.setDependencies(typedExtractNativeArtifacts, runSimWithDebugJni); }); - - 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); - }); } } 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 e859cd05..d063048d 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" } @@ -37,6 +38,7 @@ deploy { // getTargetTypeClass is a shortcut to get the class type using a string wpilibJava(getArtifactTypeClass('WPILibJavaArtifact')) { + debugJni = false } // Static files artifact @@ -51,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. @@ -88,17 +90,8 @@ 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) } } +application.mainClass = ROBOT_MAIN_CLASS - manifest org.wpilib.gradlerio.GradleRIOPlugin.javaManifest(ROBOT_MAIN_CLASS) - - duplicatesStrategy = DuplicatesStrategy.INCLUDE -} - -deployArtifact.jarTask = jar -wpi.java.configureExecutableTasks(jar) +deployArtifact.configureApplication(application) +wpi.java.configureApplication(application) 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..8de914e2 --- /dev/null +++ b/testing/kotlin/build.gradle @@ -0,0 +1,96 @@ +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.") +} + +kotlin { + jvmToolchain(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')) { + debugJni = false + } + + // 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.runSimWithDebugJni = 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.configureApplication(application) +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() + } +}