From e20e46eac12d1abd5c2d839386a7a33e0b665f73 Mon Sep 17 00:00:00 2001 From: sciencewhiz Date: Mon, 14 Apr 2025 19:56:17 -0700 Subject: [PATCH 1/4] [examples] Add compilable code snippets This enables frc-docs to use RLIs for things that are currently in-line code blocks, and ensures they compile, which is important with the 2027 breaking changes coming. They are kept separate from the examples to ensure they don't polute the VSCode examples finder. Adds the Encoder snippets used in the frc-docs Encoder article as the first instance of this. --- shared/examplecheck.gradle | 19 +++ wpilibcExamples/build.gradle | 123 ++++++++++++++++-- .../main/cpp/snippets/Encoder/cpp/Robot.cpp | 68 ++++++++++ .../src/main/cpp/snippets/snippets.json | 12 ++ wpilibjExamples/build.gradle | 43 ++++++ .../first/wpilibj/snippets/encoder/Main.java | 25 ++++ .../first/wpilibj/snippets/encoder/Robot.java | 59 +++++++++ .../wpi/first/wpilibj/snippets/snippets.json | 13 ++ 8 files changed, 348 insertions(+), 14 deletions(-) create mode 100644 wpilibcExamples/src/main/cpp/snippets/Encoder/cpp/Robot.cpp create mode 100644 wpilibcExamples/src/main/cpp/snippets/snippets.json create mode 100644 wpilibjExamples/src/main/java/edu/wpi/first/wpilibj/snippets/encoder/Main.java create mode 100644 wpilibjExamples/src/main/java/edu/wpi/first/wpilibj/snippets/encoder/Robot.java create mode 100644 wpilibjExamples/src/main/java/edu/wpi/first/wpilibj/snippets/snippets.json diff --git a/shared/examplecheck.gradle b/shared/examplecheck.gradle index 644c095cf21..27d90762de8 100644 --- a/shared/examplecheck.gradle +++ b/shared/examplecheck.gradle @@ -103,6 +103,24 @@ task checkExamples(type: Task) { } } +task checkSnippets(type: Task) { + doLast { + def parsedJson = new groovy.json.JsonSlurper().parseText(snippetsFile.text) + fileCheck(parsedJson, snippetsDirectory) + parsedJson.each { + assert it.name != null + assert it.description != null + assert it.tags != null + assert it.tags.findAll { !tagList.contains(it) }.empty + assert it.foldername != null + assert it.gradlebase != null + if (it.gradlebase == 'java') { + assert it.mainclass != null + } + } + } +} + task checkCommands(type: Task) { doLast { def parsedJson = new groovy.json.JsonSlurper().parseText(commandFile.text) @@ -127,3 +145,4 @@ task checkCommands(type: Task) { check.dependsOn checkTemplates check.dependsOn checkExamples check.dependsOn checkCommands +check.dependsOn checkSnippets diff --git a/wpilibcExamples/build.gradle b/wpilibcExamples/build.gradle index eb1aca52513..aa0aaedef42 100644 --- a/wpilibcExamples/build.gradle +++ b/wpilibcExamples/build.gradle @@ -14,9 +14,20 @@ apply from: "${rootDir}/shared/googletest.gradle" ext.examplesMap = [:] ext.templatesMap = [:] +ext.snippetsMap = [:] -File examplesTree = file("$projectDir/src/main/cpp/examples") -examplesTree.list(new FilenameFilter() { +ext { + templateDirectory = new File("$projectDir/src/main/cpp/templates/") + templateFile = new File("$projectDir/src/main/cpp/templates/templates.json") + exampleDirectory = new File("$projectDir/src/main/cpp/examples/") + exampleFile = new File("$projectDir/src/main/cpp/examples/examples.json") + commandDirectory = new File("$projectDir/src/main/cpp/commands/") + commandFile = new File("$projectDir/src/main/cpp/commands/commands.json") + snippetsDirectory = new File("$projectDir/src/main/cpp/snippets/") + snippetsFile = new File("$projectDir/src/main/cpp/snippets/snippets.json") +} + +exampleDirectory.list(new FilenameFilter() { @Override public boolean accept(File current, String name) { return new File(current, name).isDirectory(); @@ -24,8 +35,7 @@ examplesTree.list(new FilenameFilter() { }).each { examplesMap.put(it, []) } -File templatesTree = file("$projectDir/src/main/cpp/templates") -templatesTree.list(new FilenameFilter() { +templateDirectory.list(new FilenameFilter() { @Override public boolean accept(File current, String name) { return new File(current, name).isDirectory(); @@ -33,6 +43,14 @@ templatesTree.list(new FilenameFilter() { }).each { templatesMap.put(it, []) } +snippetsDirectory.list(new FilenameFilter() { + @Override + public boolean accept(File current, String name) { + return new File(current, name).isDirectory(); + } + }).each { + snippetsMap.put(it, []) + } nativeUtils.platformConfigs.named(nativeUtils.wpi.platforms.windowsx64).configure { linker.args.remove('/DEBUG:FULL') @@ -41,7 +59,7 @@ nativeUtils.platformConfigs.named(nativeUtils.wpi.platforms.windowsx64).configur } ext { - sharedCvConfigs = examplesMap + templatesMap + [commands: []] + sharedCvConfigs = examplesMap + templatesMap + snippetsMap.collectEntries { key, value -> ['snippets' + key, value] } + [commands: []] staticCvConfigs = [:] useJava = false useCpp = true @@ -171,6 +189,57 @@ model { } } } + snippetsMap.each { key, value -> + "snippets${key}"(NativeExecutableSpec) { + targetBuildTypes 'debug' + binaries.all { binary -> + lib project: ':wpilibNewCommands', library: 'wpilibNewCommands', linkage: 'shared' + lib project: ':romiVendordep', library: 'romiVendordep', linkage: 'shared' + lib project: ':xrpVendordep', library: 'xrpVendordep', linkage: 'shared' + lib project: ':wpilibc', library: 'wpilibc', linkage: 'shared' + lib project: ':apriltag', library: 'apriltag', linkage: 'shared' + lib project: ':wpimath', library: 'wpimath', linkage: 'shared' + project(':ntcore').addNtcoreDependency(binary, 'shared') + lib project: ':cscore', library: 'cscore', linkage: 'shared' + project(':hal').addHalDependency(binary, 'shared') + lib project: ':cameraserver', library: 'cameraserver', linkage: 'shared' + lib project: ':wpinet', library: 'wpinet', linkage: 'shared' + lib project: ':wpiutil', library: 'wpiutil', linkage: 'shared' + if (binary.targetPlatform.name == nativeUtils.wpi.platforms.roborio) { + nativeUtils.useRequiredLibrary(binary, 'ni_link_libraries', 'ni_runtime_libraries') + } + if (binary.targetPlatform.name == getCurrentArch()) { + simModules.each { + lib project: ":simulation:$it", library: it, linkage: 'shared' + } + } + } + sources { + cpp { + source { + srcDirs 'src/main/cpp/snippets/' + "${key}" + "/cpp" + include '**/*.cpp' + } + exportedHeaders { + srcDirs 'src/main/cpp/snippets/' + "${key}" + "/include" + include '**/*.h' + } + } + } + sources { + c { + source { + srcDirs 'src/main/cpp/snippets/' + "${key}" + "/c" + include '**/*.c' + } + exportedHeaders { + srcDirs 'src/main/cpp/snippets/' + "${key}" + "/include" + include '**/*.h' + } + } + } + } + } } testSuites { examplesMap.each { key, value -> @@ -207,6 +276,41 @@ model { } } } + testSuites { + snippetsMap.each { key, value -> + def testFolder = new File("${rootDir}/wpilibcExamples/src/test/cpp/snippets/${key}") + if (testFolder.exists()) { + "snippets${key}Test"(GoogleTestTestSuiteSpec) { + for (NativeComponentSpec c : $.components) { + if (c.name == key) { + testing c + break + } + } + sources { + cpp { + source { + srcDirs "src/test/cpp/examples/${key}/cpp" + include '**/*.cpp' + } + exportedHeaders { + srcDirs "src/test/cpp/examples/${key}/include" + } + } + c { + source { + srcDirs "src/test/cpp/examples/${key}/c" + include '**/*.c' + } + exportedHeaders { + srcDirs "src/test/cpp/examples/${key}/include" + } + } + } + } + } + } + } binaries { withType(GoogleTestTestSuiteBinarySpec) { lib project: ':wpilibNewCommands', library: 'wpilibNewCommands', linkage: 'shared' @@ -253,15 +357,6 @@ model { } } -ext { - templateDirectory = new File("$projectDir/src/main/cpp/templates/") - templateFile = new File("$projectDir/src/main/cpp/templates/templates.json") - exampleDirectory = new File("$projectDir/src/main/cpp/examples/") - exampleFile = new File("$projectDir/src/main/cpp/examples/examples.json") - commandDirectory = new File("$projectDir/src/main/cpp/commands/") - commandFile = new File("$projectDir/src/main/cpp/commands/commands.json") -} - model { // Create run tasks for all examples. tasks { diff --git a/wpilibcExamples/src/main/cpp/snippets/Encoder/cpp/Robot.cpp b/wpilibcExamples/src/main/cpp/snippets/Encoder/cpp/Robot.cpp new file mode 100644 index 00000000000..66d22449973 --- /dev/null +++ b/wpilibcExamples/src/main/cpp/snippets/Encoder/cpp/Robot.cpp @@ -0,0 +1,68 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#include + +#include +#include +#include +#include + +/** + * Encoder snippets for frc-docs. + */ +WPI_IGNORE_DEPRECATED +class Robot : public frc::TimedRobot { + public: + Robot() { + // Configures the encoder to return a distance of 4 for every 256 pulses + // Also changes the units of getRate + m_encoder.SetDistancePerPulse(4.0 / 256.0); + // Configures the encoder to consider itself stopped after .1 seconds + m_encoder.SetMaxPeriod(0.1_s); + // Configures the encoder to consider itself stopped when its rate is below + // 10 + m_encoder.SetMinRate(10); + // Reverses the direction of the encoder + m_encoder.SetReverseDirection(true); + // Configures an encoder to average its period measurement over 5 samples + // Can be between 1 and 127 samples + m_encoder.SetSamplesToAverage(5); + } + + void TeleopPeriodic() override { + // Gets the distance traveled + m_encoder.GetDistance(); + + // Gets the current rate of the encoder + m_encoder.GetRate(); + + // Gets whether the encoder is stopped + m_encoder.GetStopped(); + + // Gets the last direction in which the encoder moved + m_encoder.GetDirection(); + + // Gets the current period of the encoder + m_encoder.GetPeriod(); + + // Resets the encoder to read a distance of zero + m_encoder.Reset(); + } + + private: + // Initializes an encoder on DIO pins 0 and 1 + // Defaults to 4X decoding and non-inverted + frc::Encoder m_encoder{0, 1}; + + // Initializes an encoder on DIO pins 0 and 1 + // 2X encoding and non-inverted + frc::Encoder m_encoder2x{0, 1, false, frc::Encoder::EncodingType::k2X}; +}; + +#ifndef RUNNING_FRC_TESTS +int main() { + return frc::StartRobot(); +} +#endif diff --git a/wpilibcExamples/src/main/cpp/snippets/snippets.json b/wpilibcExamples/src/main/cpp/snippets/snippets.json new file mode 100644 index 00000000000..884e075dfdd --- /dev/null +++ b/wpilibcExamples/src/main/cpp/snippets/snippets.json @@ -0,0 +1,12 @@ +[ + { + "name": "Encoder", + "description": "Snippets of Encoder class usage for frc-docs.", + "tags": [ + "Hardware", + "Encoder" + ], + "foldername": "Encoder", + "gradlebase": "cpp" + } +] diff --git a/wpilibjExamples/build.gradle b/wpilibjExamples/build.gradle index 115b2539650..eec38c0a353 100644 --- a/wpilibjExamples/build.gradle +++ b/wpilibjExamples/build.gradle @@ -72,6 +72,8 @@ ext { exampleFile = new File("$projectDir/src/main/java/edu/wpi/first/wpilibj/examples/examples.json") commandDirectory = new File("$projectDir/src/main/java/edu/wpi/first/wpilibj/commands/") commandFile = new File("$projectDir/src/main/java/edu/wpi/first/wpilibj/commands/commands.json") + snippetsDirectory = new File("$projectDir/src/main/java/edu/wpi/first/wpilibj/snippets/") + snippetsFile = new File("$projectDir/src/main/java/edu/wpi/first/wpilibj/snippets/snippets.json") } apply plugin: 'cpp' @@ -203,6 +205,47 @@ model { test.dependsOn(testTask) } } + new groovy.json.JsonSlurper().parseText(snippetsFile.text).each { entry -> + project.tasks.create("runSnippet${entry.foldername}", JavaExec) { run -> + run.group "run snippets" + run.mainClass = "edu.wpi.first.wpilibj.snippets." + entry.foldername + "." + entry.mainclass + run.classpath = sourceSets.main.runtimeClasspath + run.dependsOn it.tasks.install + run.systemProperty 'java.library.path', filePath + doFirst { doFirstTask(run) } + + if (org.gradle.internal.os.OperatingSystem.current().isMacOsX()) { + run.jvmArgs = ['-XstartOnFirstThread'] + } + } + project.tasks.create("testSnippet${entry.foldername}", Test) { testTask -> + testTask.group "verification" + testTask.useJUnitPlatform() + testTask.filter { + includeTestsMatching("edu.wpi.first.wpilibj.snippets.${entry.foldername}.*") + setFailOnNoMatchingTests(false) + } + test.filter { + excludeTestsMatching("edu.wpi.first.wpilibj.snippets.${entry.foldername}.*") + setFailOnNoMatchingTests(false) + } + testTask.testClassesDirs = sourceSets.test.output.classesDirs + testTask.classpath = sourceSets.test.runtimeClasspath + testTask.dependsOn it.tasks.install + + testTask.systemProperty 'junit.jupiter.extensions.autodetection.enabled', 'true' + testTask.testLogging { + events "failed" + exceptionFormat "full" + } + testTask.systemProperty 'java.library.path', filePath + + if (project.hasProperty('onlylinuxathena') || project.hasProperty('onlylinuxarm32') || project.hasProperty('onlylinuxarm64') || project.hasProperty('onlywindowsarm64')) { + testTask.enabled = false + } + test.dependsOn(testTask) + } + } found = true } diff --git a/wpilibjExamples/src/main/java/edu/wpi/first/wpilibj/snippets/encoder/Main.java b/wpilibjExamples/src/main/java/edu/wpi/first/wpilibj/snippets/encoder/Main.java new file mode 100644 index 00000000000..4fef5feb296 --- /dev/null +++ b/wpilibjExamples/src/main/java/edu/wpi/first/wpilibj/snippets/encoder/Main.java @@ -0,0 +1,25 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +package edu.wpi.first.wpilibj.snippets.encoder; + +import edu.wpi.first.wpilibj.RobotBase; + +/** + * Do NOT add any static variables to this class, or any initialization at all. Unless you know what + * you are doing, do not modify this file except to change the parameter class to the startRobot + * call. + */ +public final class Main { + private Main() {} + + /** + * Main initialization function. Do not perform any initialization here. + * + *

If you change your main robot class, change the parameter type. + */ + public static void main(String... args) { + RobotBase.startRobot(Robot::new); + } +} diff --git a/wpilibjExamples/src/main/java/edu/wpi/first/wpilibj/snippets/encoder/Robot.java b/wpilibjExamples/src/main/java/edu/wpi/first/wpilibj/snippets/encoder/Robot.java new file mode 100644 index 00000000000..53f71fc509f --- /dev/null +++ b/wpilibjExamples/src/main/java/edu/wpi/first/wpilibj/snippets/encoder/Robot.java @@ -0,0 +1,59 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +package edu.wpi.first.wpilibj.snippets.encoder; + +import edu.wpi.first.wpilibj.Encoder; +import edu.wpi.first.wpilibj.TimedRobot; + +/** Encoder snippets for frc-docs. */ +@SuppressWarnings("deprecation") +public class Robot extends TimedRobot { + // Initializes an encoder on DIO pins 0 and 1 + // Defaults to 4X decoding and non-inverted + Encoder m_encoder = new Encoder(0, 1); + + // Initializes an encoder on DIO pins 0 and 1 + // 2X encoding and non-inverted + Encoder m_encoder2x = new Encoder(0, 1, false, Encoder.EncodingType.k2X); + + /** Called once at the beginning of the robot program. */ + public Robot() { + // Configures the encoder to return a distance of 4 for every 256 pulses + // Also changes the units of getRate + m_encoder.setDistancePerPulse(4.0 / 256.0); + // Configures the encoder to consider itself stopped after .1 seconds + m_encoder.setMaxPeriod(0.1); + // Configures the encoder to consider itself stopped when its rate is below 10 + m_encoder.setMinRate(10); + // Reverses the direction of the encoder + m_encoder.setReverseDirection(true); + // Configures an encoder to average its period measurement over 5 samples + // Can be between 1 and 127 samples + m_encoder.setSamplesToAverage(5); + + m_encoder2x.getRate(); + } + + @Override + public void teleopPeriodic() { + // Gets the distance traveled + m_encoder.getDistance(); + + // Gets the current rate of the encoder + m_encoder.getRate(); + + // Gets whether the encoder is stopped + m_encoder.getStopped(); + + // Gets the last direction in which the encoder moved + m_encoder.getDirection(); + + // Gets the current period of the encoder + m_encoder.getPeriod(); + + // Resets the encoder to read a distance of zero + m_encoder.reset(); + } +} diff --git a/wpilibjExamples/src/main/java/edu/wpi/first/wpilibj/snippets/snippets.json b/wpilibjExamples/src/main/java/edu/wpi/first/wpilibj/snippets/snippets.json new file mode 100644 index 00000000000..8e8786f7133 --- /dev/null +++ b/wpilibjExamples/src/main/java/edu/wpi/first/wpilibj/snippets/snippets.json @@ -0,0 +1,13 @@ +[ + { + "name": "Encoder", + "description": "Snippets of Encoder class usage for frc-docs.", + "tags": [ + "Hardware", + "Encoder" + ], + "foldername": "encoder", + "gradlebase": "java", + "mainclass": "Main" + } +] From 0c9e8332d1bb4d829442733238d6aa3b88da1bf0 Mon Sep 17 00:00:00 2001 From: sciencewhiz Date: Tue, 15 Apr 2025 21:01:04 -0700 Subject: [PATCH 2/4] Add cmake --- wpilibcExamples/CMakeLists.txt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/wpilibcExamples/CMakeLists.txt b/wpilibcExamples/CMakeLists.txt index aaec73c88fe..33caad5533e 100644 --- a/wpilibcExamples/CMakeLists.txt +++ b/wpilibcExamples/CMakeLists.txt @@ -5,6 +5,7 @@ include(SubDirList) subdir_list(TEMPLATES ${CMAKE_SOURCE_DIR}/wpilibcExamples/src/main/cpp/templates) subdir_list(EXAMPLES ${CMAKE_SOURCE_DIR}/wpilibcExamples/src/main/cpp/examples) +subdir_list(SNIPPETS ${CMAKE_SOURCE_DIR}/wpilibcExamples/src/main/cpp/snippets) add_custom_target(wpilibcExamples) add_custom_target(wpilibcExamples_test) @@ -63,3 +64,16 @@ foreach(template ${TEMPLATES}) target_link_libraries(${template} wpilibc wpilibNewCommands romiVendordep xrpVendordep) add_dependencies(wpilibcExamples ${template}) endforeach() + +foreach(snippet ${SNIPPETS}) + file( + GLOB_RECURSE sources + src/main/cpp/snippets/${snippet}/cpp/*.cpp + src/main/cpp/snippets/${snippet}/c/*.c + ) + add_executable(${snippet} ${sources}) + wpilib_target_warnings(${snippet}) + target_include_directories(${snippet} PUBLIC src/main/cpp/snippets/${snippet}/include) + target_link_libraries(${snippet} wpilibc wpilibNewCommands romiVendordep xrpVendordep) + add_dependencies(wpilibcExamples ${snippet}) +endforeach() From be795b9a0167abb93d94d0fb772360d36c3532ef Mon Sep 17 00:00:00 2001 From: sciencewhiz Date: Tue, 15 Apr 2025 21:01:04 -0700 Subject: [PATCH 3/4] Fix cmake for unique names --- wpilibcExamples/CMakeLists.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/wpilibcExamples/CMakeLists.txt b/wpilibcExamples/CMakeLists.txt index 33caad5533e..f79cd15d68c 100644 --- a/wpilibcExamples/CMakeLists.txt +++ b/wpilibcExamples/CMakeLists.txt @@ -71,9 +71,9 @@ foreach(snippet ${SNIPPETS}) src/main/cpp/snippets/${snippet}/cpp/*.cpp src/main/cpp/snippets/${snippet}/c/*.c ) - add_executable(${snippet} ${sources}) + add_executable(snippet${snippet} ${sources}) wpilib_target_warnings(${snippet}) - target_include_directories(${snippet} PUBLIC src/main/cpp/snippets/${snippet}/include) - target_link_libraries(${snippet} wpilibc wpilibNewCommands romiVendordep xrpVendordep) - add_dependencies(wpilibcExamples ${snippet}) + target_include_directories(snippet${snippet} PUBLIC src/main/cpp/snippets/${snippet}/include) + target_link_libraries(snippet${snippet} wpilibc wpilibNewCommands romiVendordep xrpVendordep) + add_dependencies(wpilibcExamples snippet${snippet}) endforeach() From 01265d99fc7f36293d81638a13861a44d0e0df14 Mon Sep 17 00:00:00 2001 From: sciencewhiz Date: Sat, 19 Apr 2025 09:06:02 -0700 Subject: [PATCH 4/4] Add link to frc-doc page in source --- wpilibcExamples/src/main/cpp/snippets/Encoder/cpp/Robot.cpp | 1 + .../java/edu/wpi/first/wpilibj/snippets/encoder/Robot.java | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/wpilibcExamples/src/main/cpp/snippets/Encoder/cpp/Robot.cpp b/wpilibcExamples/src/main/cpp/snippets/Encoder/cpp/Robot.cpp index 66d22449973..7f736d5c1bd 100644 --- a/wpilibcExamples/src/main/cpp/snippets/Encoder/cpp/Robot.cpp +++ b/wpilibcExamples/src/main/cpp/snippets/Encoder/cpp/Robot.cpp @@ -11,6 +11,7 @@ /** * Encoder snippets for frc-docs. + * https://docs.wpilib.org/en/stable/docs/software/hardware-apis/sensors/encoders-software.html */ WPI_IGNORE_DEPRECATED class Robot : public frc::TimedRobot { diff --git a/wpilibjExamples/src/main/java/edu/wpi/first/wpilibj/snippets/encoder/Robot.java b/wpilibjExamples/src/main/java/edu/wpi/first/wpilibj/snippets/encoder/Robot.java index 53f71fc509f..de1511bd552 100644 --- a/wpilibjExamples/src/main/java/edu/wpi/first/wpilibj/snippets/encoder/Robot.java +++ b/wpilibjExamples/src/main/java/edu/wpi/first/wpilibj/snippets/encoder/Robot.java @@ -7,7 +7,10 @@ import edu.wpi.first.wpilibj.Encoder; import edu.wpi.first.wpilibj.TimedRobot; -/** Encoder snippets for frc-docs. */ +/** + * Encoder snippets for frc-docs. + * https://docs.wpilib.org/en/stable/docs/software/hardware-apis/sensors/encoders-software.html + */ @SuppressWarnings("deprecation") public class Robot extends TimedRobot { // Initializes an encoder on DIO pins 0 and 1