diff --git a/.github/actions/pregen/action.yml b/.github/actions/pregen/action.yml new file mode 100644 index 0000000000..2c61be413a --- /dev/null +++ b/.github/actions/pregen/action.yml @@ -0,0 +1,61 @@ +name: 'Setup and run pregeneration' +description: 'Sets up the dependencies needed to generate generated files and runs all generation scripts' + +runs: + using: "composite" + steps: + - name: Set up Python 3.12 + uses: actions/setup-python@v5 + with: + python-version: '3.12' + - name: Install jinja and protobuf + run: python -m pip install jinja2 protobuf grpcio-tools + shell: bash + - name: Install protobuf dependencies + run: | + sudo apt-get update + sudo apt-get install -y protobuf-compiler + wget https://github.com/HebiRobotics/QuickBuffers/releases/download/1.3.3/protoc-gen-quickbuf-1.3.3-linux-x86_64.exe + chmod +x protoc-gen-quickbuf-1.3.3-linux-x86_64.exe + shell: bash + - name: Regenerate hal + run: ./hal/generate_usage_reporting.py + shell: bash + + - name: Regenerate ntcore + run: ./ntcore/generate_topics.py + shell: bash + + - name: Regenerate imgui + run: | + ./thirdparty/imgui_suite/generate_fonts.sh + ./thirdparty/imgui_suite/generate_gl3w.py + shell: bash + + - name: Regenerate HIDs + run: | + ./wpilibc/generate_hids.py + ./wpilibj/generate_hids.py + ./wpilibNewCommands/generate_hids.py + shell: bash + + - name: Regenerate PWM motor controllers + run: | + ./wpilibc/generate_pwm_motor_controllers.py + ./wpilibj/generate_pwm_motor_controllers.py + shell: bash + + - name: Regenerate wpimath + run: | + ./wpimath/generate_nanopb.py + ./wpimath/generate_numbers.py + ./wpimath/generate_quickbuf.py --quickbuf_plugin protoc-gen-quickbuf-1.3.3-linux-x86_64.exe + shell: bash + + - name: Regenerate wpiunits + run: ./wpiunits/generate_units.py + shell: bash + + - name: Regenerate wpiutil nanopb + run: ./wpiutil/generate_nanopb.py + shell: bash diff --git a/.github/workflows/comment-command.yml b/.github/workflows/comment-command.yml deleted file mode 100644 index 7ee4d75869..0000000000 --- a/.github/workflows/comment-command.yml +++ /dev/null @@ -1,103 +0,0 @@ -name: Comment Commands -on: - issue_comment: - types: [ created ] - -jobs: - format: - if: github.event.issue.pull_request && startsWith(github.event.comment.body, '/format') - runs-on: ubuntu-24.04 - steps: - - name: React Rocket - uses: actions/github-script@v7 - with: - script: | - const {owner, repo} = context.issue - github.rest.reactions.createForIssueComment({ - owner, - repo, - comment_id: context.payload.comment.id, - content: "rocket", - }); - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - token: ${{ secrets.COMMENT_COMMAND_PAT_TOKEN }} - - name: Fetch all history and metadata - run: | - git checkout -b pr - git branch -f main origin/main - - name: Checkout PR - run: | - gh pr checkout $NUMBER - env: - GITHUB_TOKEN: "${{ secrets.COMMENT_COMMAND_PAT_TOKEN }}" - NUMBER: ${{ github.event.issue.number }} - - name: Set up Python 3.12 - uses: actions/setup-python@v5 - with: - python-version: '3.12' - - name: Setup Java - uses: actions/setup-java@v4 - with: - distribution: 'temurin' - java-version: 17 - - name: Install wpiformat - run: | - python -m venv ${{ runner.temp }}/wpiformat - ${{ runner.temp }}/wpiformat/bin/pip3 install wpiformat==2024.50 - - name: Run wpiformat - run: ${{ runner.temp }}/wpiformat/bin/wpiformat - - name: Run spotlessApply - run: ./gradlew spotlessApply - - name: Commit - run: | - # Set credentials - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - # Commit - git commit -am "Formatting fixes" - git push - - pregen: - if: github.event.issue.pull_request && startsWith(github.event.comment.body, '/pregen') - runs-on: ubuntu-24.04 - steps: - - name: React Rocket - uses: actions/github-script@v7 - with: - script: | - const {owner, repo} = context.issue - github.rest.reactions.createForIssueComment({ - owner, - repo, - comment_id: context.payload.comment.id, - content: "rocket", - }); - - uses: actions/checkout@v4 - with: - token: ${{ secrets.COMMENT_COMMAND_PAT_TOKEN }} - - name: Checkout PR - run: | - gh pr checkout $NUMBER - env: - GITHUB_TOKEN: "${{ secrets.COMMENT_COMMAND_PAT_TOKEN }}" - NUMBER: ${{ github.event.issue.number }} - - name: Set up Python 3.12 - uses: actions/setup-python@v5 - with: - python-version: '3.12' - - name: Install jinja - run: python -m pip install jinja2 - - name: Install protobuf dependencies - run: sudo apt-get update && sudo apt-get install -y protobuf-compiler && wget https://github.com/HebiRobotics/QuickBuffers/releases/download/1.3.3/protoc-gen-quickbuf-1.3.3-linux-x86_64.exe && chmod +x protoc-gen-quickbuf-1.3.3-linux-x86_64.exe - - name: Regenerate all - run: ./.github/workflows/pregen_all.py --quickbuf_plugin=protoc-gen-quickbuf-1.3.3-linux-x86_64.exe - - name: Commit - run: | - # Set credentials - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - # Commit - git commit -am "Regenerate pregenerated files" - git push diff --git a/.github/workflows/pregen_all.py b/.github/workflows/pregen_all.py deleted file mode 100755 index 581abe8683..0000000000 --- a/.github/workflows/pregen_all.py +++ /dev/null @@ -1,85 +0,0 @@ -#!/usr/bin/env python3 - -import argparse -import subprocess -import sys -from pathlib import Path - - -def main(): - script_path = Path(__file__).resolve() - REPO_ROOT = script_path.parent.parent.parent - parser = argparse.ArgumentParser() - parser.add_argument( - "--quickbuf_plugin", - help="Path to the quickbuf protoc plugin", - required=True, - ) - args = parser.parse_args() - subprocess.run( - [sys.executable, f"{REPO_ROOT}/hal/generate_usage_reporting.py"], check=True - ) - subprocess.run( - [ - sys.executable, - f"{REPO_ROOT}/hal/generate_nanopb.py", - ], - check=True, - ) - subprocess.run( - [sys.executable, f"{REPO_ROOT}/ntcore/generate_topics.py"], check=True - ) - subprocess.run( - [sys.executable, f"{REPO_ROOT}/wpimath/generate_numbers.py"], check=True - ) - subprocess.run( - [ - sys.executable, - f"{REPO_ROOT}/wpimath/generate_quickbuf.py", - f"--quickbuf_plugin={args.quickbuf_plugin}", - ], - check=True, - ) - subprocess.run( - [ - sys.executable, - f"{REPO_ROOT}/wpimath/generate_nanopb.py", - ], - check=True, - ) - subprocess.run( - [sys.executable, f"{REPO_ROOT}/wpiunits/generate_units.py"], check=True - ) - subprocess.run( - [sys.executable, f"{REPO_ROOT}/wpilibc/generate_hids.py"], check=True - ) - subprocess.run( - [sys.executable, f"{REPO_ROOT}/wpilibj/generate_hids.py"], check=True - ) - subprocess.run( - [sys.executable, f"{REPO_ROOT}/wpilibNewCommands/generate_hids.py"], check=True - ) - subprocess.run( - [sys.executable, f"{REPO_ROOT}/wpilibc/generate_pwm_motor_controllers.py"], - check=True, - ) - subprocess.run( - [sys.executable, f"{REPO_ROOT}/wpilibj/generate_pwm_motor_controllers.py"], - check=True, - ) - subprocess.run( - [ - sys.executable, - f"{REPO_ROOT}/wpiutil/generate_nanopb.py", - ], - check=True, - ) - subprocess.run( - [sys.executable, f"{REPO_ROOT}/thirdparty/imgui_suite/generate_gl3w.py"], - check=True, - ) - subprocess.run(f"{REPO_ROOT}/thirdparty/imgui_suite/generate_fonts.sh", check=True) - - -if __name__ == "__main__": - main() diff --git a/.github/workflows/pregenerate.yml b/.github/workflows/pregenerate.yml index cb8ed0c70c..c4c5f8ef99 100644 --- a/.github/workflows/pregenerate.yml +++ b/.github/workflows/pregenerate.yml @@ -18,16 +18,8 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Set up Python 3.12 - uses: actions/setup-python@v5 - with: - python-version: '3.12' - - name: Install jinja and protobuf - run: python -m pip install jinja2 protobuf grpcio-tools - - name: Install protobuf dependencies - run: sudo apt-get update && sudo apt-get install -y protobuf-compiler && wget https://github.com/HebiRobotics/QuickBuffers/releases/download/1.3.3/protoc-gen-quickbuf-1.3.3-linux-x86_64.exe && chmod +x protoc-gen-quickbuf-1.3.3-linux-x86_64.exe - - name: Regenerate all - run: python ./.github/workflows/pregen_all.py --quickbuf_plugin protoc-gen-quickbuf-1.3.3-linux-x86_64.exe + - name: Run pregen + uses: ./.github/actions/pregen - name: Add untracked files to index so they count as changes run: git add -A - name: Check output diff --git a/WORKSPACE b/WORKSPACE index 602b2ed429..0d7786c2a2 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -35,8 +35,8 @@ maven_install( # Download toolchains http_archive( name = "rules_bzlmodrio_toolchains", - sha256 = "2ef1cafce7f4fd4e909bb5de8b0dc771a934646afd55d5f100ff31f6b500df98", - url = "https://github.com/wpilibsuite/rules_bzlmodRio_toolchains/releases/download/2024-1.bcr1/rules_bzlmodRio_toolchains-2024-1.bcr1.tar.gz", + sha256 = "fe267e2af53c1def1e962700a9aeda9e8fdfa9fb46b72167c615ec0e25447dd6", + url = "https://github.com/wpilibsuite/rules_bzlmodRio_toolchains/releases/download/2025-1/rules_bzlmodRio_toolchains-2025-1.tar.gz", ) load("@rules_bzlmodrio_toolchains//:maven_deps.bzl", "setup_legacy_setup_toolchains_dependencies") @@ -71,6 +71,12 @@ register_toolchains( "@local_bullseye_64//:macos", "@local_bullseye_64//:linux", "@local_bullseye_64//:windows", + "@local_bookworm_32//:macos", + "@local_bookworm_32//:linux", + "@local_bookworm_32//:windows", + "@local_bookworm_64//:macos", + "@local_bookworm_64//:linux", + "@local_bookworm_64//:windows", ) setup_legacy_setup_jdk_dependencies() @@ -87,8 +93,8 @@ setup_legacy_bzlmodrio_ni_cpp_dependencies() http_archive( name = "bzlmodrio-opencv", - sha256 = "5314cce05b49451a46bf3e3140fc401342e53d5f3357612ed4473e59bb616cba", - url = "https://github.com/wpilibsuite/bzlmodRio-opencv/releases/download/2024.4.8.0-4.bcr1/bzlmodRio-opencv-2024.4.8.0-4.bcr1.tar.gz", + sha256 = "4f4a607956ca8555618736c3058dd96e09d02df19e95088c1e352d2319fd70c7", + url = "https://github.com/wpilibsuite/bzlmodRio-opencv/releases/download/2025.4.10.0-2/bzlmodRio-opencv-2025.4.10.0-2.tar.gz", ) load("@bzlmodrio-opencv//:maven_cpp_deps.bzl", "setup_legacy_bzlmodrio_opencv_cpp_dependencies") @@ -98,3 +104,16 @@ setup_legacy_bzlmodrio_opencv_cpp_dependencies() load("@bzlmodrio-opencv//:maven_java_deps.bzl", "setup_legacy_bzlmodrio_opencv_java_dependencies") setup_legacy_bzlmodrio_opencv_java_dependencies() + +http_archive( + name = "build_bazel_apple_support", + sha256 = "c4bb2b7367c484382300aee75be598b92f847896fb31bbd22f3a2346adf66a80", + url = "https://github.com/bazelbuild/apple_support/releases/download/1.15.1/apple_support.1.15.1.tar.gz", +) + +load( + "@build_bazel_apple_support//lib:repositories.bzl", + "apple_support_dependencies", +) + +apple_support_dependencies() diff --git a/apriltag/BUILD.bazel b/apriltag/BUILD.bazel new file mode 100644 index 0000000000..ea1ef1abe0 --- /dev/null +++ b/apriltag/BUILD.bazel @@ -0,0 +1,112 @@ +load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library", "cc_test") +load("@rules_java//java:defs.bzl", "java_binary", "java_library") +load("@rules_python//python:defs.bzl", "py_binary") +load("//shared/bazel/rules/gen:gen-resources.bzl", "generate_resources") + +cc_library( + name = "thirdparty-apriltag", + srcs = glob(["src/main/native/thirdparty/apriltag/src/**"]), + hdrs = glob(["src/main/native/thirdparty/apriltag/include/**"]), + copts = select({ + "@bazel_tools//src/conditions:darwin": [ + "-Wno-format-nonliteral", + "-Wno-gnu-zero-variadic-macro-arguments", + "-Wno-uninitialized", + "-Wno-sign-compare", + "-Wno-type-limits", + ], + "@bazel_tools//src/conditions:windows": [ + "/wd4005", + "/wd4018", + "/wd4244", + "/wd4267", + "/wd4996", + ], + "@rules_bzlmodrio_toolchains//constraints/combined:is_linux": [ + "-Wno-format-nonliteral", + "-Wno-maybe-uninitialized", + "-Wno-sign-compare", + "-Wno-type-limits", + ], + }), + includes = ["src/main/native/thirdparty/apriltag/include/common"], + strip_include_prefix = "src/main/native/thirdparty/apriltag/include", + visibility = ["//visibility:public"], +) + +generate_resources( + name = "generate-resources", + namespace = "frc", + prefix = "APRILTAG", + resource_files = glob(["src/main/native/resources/**"]), + visibility = ["//visibility:public"], +) + +cc_library( + name = "apriltag.static", + srcs = [":generate-resources"] + glob( + ["src/main/native/cpp/**"], + exclude = ["src/main/native/cpp/jni/**"], + ), + hdrs = glob(["src/main/native/include/**/*"]), + defines = ["WPILIB_EXPORTS"], + strip_include_prefix = "src/main/native/include", + visibility = ["//visibility:public"], + deps = [ + ":thirdparty-apriltag", + "//wpimath:wpimath.static", + "//wpiutil:wpiutil.static", + ], +) + +java_library( + name = "apriltag-java", + srcs = glob(["src/main/java/**/*.java"]), + resource_strip_prefix = "apriltag/src/main/native/resources", + resources = glob(["src/main/native/resources/**"]), + visibility = ["//visibility:public"], + deps = [ + "//wpimath:wpimath-java", + "//wpiutil:wpiutil-java", + "@bzlmodrio-opencv//libraries/java/opencv", + "@maven//:com_fasterxml_jackson_core_jackson_annotations", + "@maven//:com_fasterxml_jackson_core_jackson_core", + "@maven//:com_fasterxml_jackson_core_jackson_databind", + ], +) + +cc_test( + name = "apriltag-cpp-test", + size = "small", + srcs = glob(["src/test/native/cpp/**"]), + tags = [ + "no-asan", + ], + deps = [ + ":apriltag.static", + "//thirdparty/googletest:googletest.static", + ], +) + +cc_binary( + name = "DevMain-Cpp", + srcs = ["src/dev/native/cpp/main.cpp"], + deps = [ + ":apriltag.static", + ], +) + +java_binary( + name = "DevMain-Java", + srcs = ["src/dev/java/edu/wpi/first/apriltag/DevMain.java"], + main_class = "edu.wpi.first.apriltag.DevMain", + deps = [ + ":apriltag-java", + ], +) + +py_binary( + name = "convert_apriltag_layouts", + srcs = ["convert_apriltag_layouts.py"], + tags = ["manual"], +) diff --git a/cameraserver/BUILD.bazel b/cameraserver/BUILD.bazel index 542f0f3c96..73995c0135 100644 --- a/cameraserver/BUILD.bazel +++ b/cameraserver/BUILD.bazel @@ -1,6 +1,22 @@ -load("@rules_cc//cc:defs.bzl", "cc_binary") +load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library", "cc_test") load("@rules_java//java:defs.bzl", "java_binary", "java_library") +cc_library( + name = "cameraserver.static", + srcs = glob(["src/main/native/cpp/**"]), + hdrs = glob(["src/main/native/include/**/*"]), + includes = [ + "cpp", + "src/main/native/include", + ], + strip_include_prefix = "src/main/native/include", + visibility = ["//visibility:public"], + deps = [ + "//cscore:cscore.static", + "//ntcore:ntcore.static", + ], +) + java_library( name = "cameraserver-java", srcs = glob(["src/main/java/**/*.java"]), @@ -16,10 +32,21 @@ java_library( ], ) +cc_test( + name = "cameraserver-cpp-test", + size = "small", + srcs = glob(["src/test/native/**"]), + deps = [ + ":cameraserver.static", + "//thirdparty/googletest:googletest.static", + ], +) + cc_binary( name = "DevMain-Cpp", srcs = ["src/dev/native/cpp/main.cpp"], deps = [ + ":cameraserver.static", ], ) diff --git a/cscore/BUILD.bazel b/cscore/BUILD.bazel index a2470f8afe..d40fb4d2fd 100644 --- a/cscore/BUILD.bazel +++ b/cscore/BUILD.bazel @@ -1,5 +1,76 @@ +load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library", "cc_test", "objc_library") load("@rules_java//java:defs.bzl", "java_binary", "java_library") +WIN_SRCS = glob([ + "src/main/native/windows/**/*.cpp", + "src/main/native/windows/**/*.h", +]) + +LINUX_SRCS = glob([ + "src/main/native/linux/**/*.cpp", + "src/main/native/linux/**/*.h", +]) + +MAC_SRCS = glob(["src/main/native/osx/**/*.cpp"]) + +filegroup( + name = "native-srcs", + srcs = select({ + "@bazel_tools//src/conditions:darwin": MAC_SRCS, + "@bazel_tools//src/conditions:windows": WIN_SRCS, + "@rules_bzlmodrio_toolchains//constraints/combined:is_linux": LINUX_SRCS, + }), +) + +objc_library( + name = "cscore-mac", + srcs = glob([ + "src/main/native/objcpp/**/*.mm", + "src/main/native/cpp/*.h", + ]), + hdrs = glob([ + "src/main/native/include/**/*", + "src/main/native/objcpp/**/*.h", + ]), + copts = [ + "-std=c++20", + ], + includes = [ + "src/main/native/cpp", + "src/main/native/include", + "src/main/native/objcpp", + ], + tags = ["manual"], + deps = [ + "//wpinet:wpinet.static", + "//wpiutil:wpiutil.static", + "@bzlmodrio-opencv//libraries/cpp/opencv", + ], +) + +cc_library( + name = "cscore.static", + srcs = [":native-srcs"] + glob( + ["src/main/native/cpp/**"], + exclude = ["src/main/native/cpp/jni/**"], + ), + hdrs = glob(["src/main/native/include/**/*"]), + includes = [ + "src/main/native/cpp", + "src/main/native/include", + ], + strip_include_prefix = "src/main/native/include", + visibility = ["//visibility:public"], + deps = [ + "//wpinet:wpinet.static", + "//wpiutil:wpiutil.static", + "@bzlmodrio-opencv//libraries/cpp/opencv", + ] + select({ + "@bazel_tools//src/conditions:darwin": [":cscore-mac"], + "//conditions:default": [], + }), +) + java_library( name = "cscore-java", srcs = glob(["src/main/java/**/*.java"]), @@ -10,6 +81,24 @@ java_library( ], ) +cc_test( + name = "cscore-cpp-test", + size = "small", + srcs = glob(["src/test/native/**"]), + deps = [ + ":cscore.static", + "//thirdparty/googletest:googletest.static", + ], +) + +cc_binary( + name = "DevMain-Cpp", + srcs = ["src/dev/native/cpp/main.cpp"], + deps = [ + ":cscore.static", + ], +) + java_binary( name = "DevMain-Java", srcs = ["src/dev/java/edu/wpi/first/cscore/DevMain.java"], diff --git a/developerRobot/CMakeLists.txt b/developerRobot/CMakeLists.txt index f942e01bb0..3b2b9827a5 100644 --- a/developerRobot/CMakeLists.txt +++ b/developerRobot/CMakeLists.txt @@ -5,4 +5,4 @@ include(CompileWarnings) file(GLOB developerRobotCpp_src src/main/native/cpp/*.cpp) add_executable(developerRobotCpp ${developerRobotCpp_src}) -target_link_libraries(developerRobotCpp wpilibc) +target_link_libraries(developerRobotCpp wpilibc wpilibNewCommands apriltag) diff --git a/developerRobot/build.gradle b/developerRobot/build.gradle index fa4d7b3592..09b1441de1 100644 --- a/developerRobot/build.gradle +++ b/developerRobot/build.gradle @@ -133,6 +133,19 @@ deploy { } } +// Prevent the eclipse compiler (used by the VS Code extension for intellisense and debugging) +// from generating bad class files from annotation processors like Epilogue +eclipse { + classpath { + containers 'org.eclipse.buildship.core.gradleclasspathcontainer' + file.whenMerged { cp -> + def entries = cp.entries; + def src = new org.gradle.plugins.ide.eclipse.model.SourceFolder('build/generated/sources/annotationProcessor/java/main/', null) + entries.add(src) + } + } +} + tasks.register('deployJava') { try { dependsOn tasks.named('deploydeveloperRobotJavasystemcore') diff --git a/epilogue-processor/BUILD.bazel b/epilogue-processor/BUILD.bazel new file mode 100644 index 0000000000..a9a8e083ab --- /dev/null +++ b/epilogue-processor/BUILD.bazel @@ -0,0 +1,22 @@ +load("@rules_java//java:defs.bzl", "java_library", "java_plugin") + +java_library( + name = "processor", + srcs = glob(["src/main/java/**/*.java"]), + visibility = ["//visibility:public"], + runtime_deps = [ + "//wpilibNewCommands:wpilibNewCommands-java", + ], + deps = [ + "//epilogue-runtime:epilogue", + ], +) + +java_plugin( + name = "plugin", + processor_class = "edu.wpi.first.epilogue.processor.AnnotationProcessor", + visibility = ["//visibility:public"], + deps = [ + ":processor", + ], +) diff --git a/epilogue-processor/src/main/java/edu/wpi/first/epilogue/processor/LoggerGenerator.java b/epilogue-processor/src/main/java/edu/wpi/first/epilogue/processor/LoggerGenerator.java index 499d9912a7..e7cb686f71 100644 --- a/epilogue-processor/src/main/java/edu/wpi/first/epilogue/processor/LoggerGenerator.java +++ b/epilogue-processor/src/main/java/edu/wpi/first/epilogue/processor/LoggerGenerator.java @@ -43,6 +43,8 @@ public class LoggerGenerator { LoggerGenerator::isBuiltInJavaMethod; private final ProcessingEnvironment m_processingEnv; private final List m_handlers; + + @SuppressWarnings("BadAnnotationImplementation") private final Logged m_defaultConfig = new Logged() { @Override diff --git a/epilogue-runtime/BUILD.bazel b/epilogue-runtime/BUILD.bazel new file mode 100644 index 0000000000..05db6b7527 --- /dev/null +++ b/epilogue-runtime/BUILD.bazel @@ -0,0 +1,12 @@ +load("@rules_java//java:defs.bzl", "java_library") + +java_library( + name = "epilogue", + srcs = glob(["src/main/java/**/*.java"]), + visibility = ["//visibility:public"], + deps = [ + "//ntcore:networktables-java", + "//wpiunits", + "//wpiutil:wpiutil-java", + ], +) diff --git a/fieldImages/BUILD.bazel b/fieldImages/BUILD.bazel new file mode 100644 index 0000000000..b5cfc82cfa --- /dev/null +++ b/fieldImages/BUILD.bazel @@ -0,0 +1,31 @@ +load("@rules_cc//cc:defs.bzl", "cc_library") +load("@rules_java//java:defs.bzl", "java_library") +load("//shared/bazel/rules/gen:gen-resources.bzl", "generate_resources") + +generate_resources( + name = "generate-resources", + namespace = "fields", + prefix = "FIELDS", + resource_files = glob(["src/main/native/resources/**"]), + visibility = ["//visibility:public"], +) + +cc_library( + name = "fieldImages", + srcs = [":generate-resources"] + glob(["src/main/native/cpp/**"]), + hdrs = glob(["src/main/native/include/**/*"]), + strip_include_prefix = "src/main/native/include", + visibility = ["//visibility:public"], +) + +java_library( + name = "fieldImages-java", + srcs = glob(["src/main/java/**/*.java"]), + resource_strip_prefix = "fieldImages/src/main/native/resources", + resources = glob(["src/main/native/resources/**"]), + visibility = ["//visibility:public"], + deps = [ + "@maven//:com_fasterxml_jackson_core_jackson_annotations", + "@maven//:com_fasterxml_jackson_core_jackson_databind", + ], +) diff --git a/hal/src/generate/ResourceType.txt b/hal/src/generate/ResourceType.txt index 5723e81564..aafdeb0bf0 100644 --- a/hal/src/generate/ResourceType.txt +++ b/hal/src/generate/ResourceType.txt @@ -124,3 +124,4 @@ kResourceType_Koors40 = 122 kResourceType_ThriftyNova = 123 kResourceType_PWFSEN36005 = 124 kResourceType_LaserShark = 125 +kResourceType_RevServoHub = 126 diff --git a/hal/src/generated/main/java/edu/wpi/first/hal/FRCNetComm.java b/hal/src/generated/main/java/edu/wpi/first/hal/FRCNetComm.java index 0f1b55c096..9c73848199 100644 --- a/hal/src/generated/main/java/edu/wpi/first/hal/FRCNetComm.java +++ b/hal/src/generated/main/java/edu/wpi/first/hal/FRCNetComm.java @@ -271,6 +271,8 @@ public final class FRCNetComm { public static final int kResourceType_PWFSEN36005 = 124; /** kResourceType_LaserShark = 125. */ public static final int kResourceType_LaserShark = 125; + /** kResourceType_RevServoHub = 126. */ + public static final int kResourceType_RevServoHub = 126; } /** diff --git a/hal/src/generated/main/native/include/hal/FRCUsageReporting.h b/hal/src/generated/main/native/include/hal/FRCUsageReporting.h index fa702d0b58..60267b6cac 100644 --- a/hal/src/generated/main/native/include/hal/FRCUsageReporting.h +++ b/hal/src/generated/main/native/include/hal/FRCUsageReporting.h @@ -177,6 +177,7 @@ namespace HALUsageReporting { kResourceType_ThriftyNova = 123, kResourceType_PWFSEN36005 = 124, kResourceType_LaserShark = 125, + kResourceType_RevServoHub = 126, }; enum tInstances : int32_t { kLanguage_LabVIEW = 1, diff --git a/hal/src/generated/main/native/include/hal/UsageReporting.h b/hal/src/generated/main/native/include/hal/UsageReporting.h index 2c37cfbe61..0f1271b7bf 100644 --- a/hal/src/generated/main/native/include/hal/UsageReporting.h +++ b/hal/src/generated/main/native/include/hal/UsageReporting.h @@ -146,6 +146,7 @@ typedef enum kResourceType_ThriftyNova = 123, kResourceType_PWFSEN36005 = 124, kResourceType_LaserShark = 125, + kResourceType_RevServoHub = 126, // kResourceType_MaximumID = 255, } tResourceType; diff --git a/hal/src/main/java/edu/wpi/first/hal/CANAPITypes.java b/hal/src/main/java/edu/wpi/first/hal/CANAPITypes.java index 2d8e0dd6f5..e485ee6399 100644 --- a/hal/src/main/java/edu/wpi/first/hal/CANAPITypes.java +++ b/hal/src/main/java/edu/wpi/first/hal/CANAPITypes.java @@ -47,6 +47,8 @@ public final class CANAPITypes { kMiscellaneous(10), /** IO breakout. */ kIOBreakout(11), + /** Servo Controller. */ + kServoController(12), /** Firmware update. */ kFirmwareUpdate(31); diff --git a/hal/src/main/native/include/hal/CANAPITypes.h b/hal/src/main/native/include/hal/CANAPITypes.h index 247732c895..7cfe93b4c4 100644 --- a/hal/src/main/native/include/hal/CANAPITypes.h +++ b/hal/src/main/native/include/hal/CANAPITypes.h @@ -44,6 +44,8 @@ HAL_ENUM(HAL_CANDeviceType) { HAL_CAN_Dev_kMiscellaneous = 10, /// IO breakout. HAL_CAN_Dev_kIOBreakout = 11, + // Servo controller. + HAL_CAN_Dev_kServoController = 12, /// Firmware update. HAL_CAN_Dev_kFirmwareUpdate = 31 }; diff --git a/romiVendordep/BUILD.bazel b/romiVendordep/BUILD.bazel new file mode 100644 index 0000000000..7cc0913b6e --- /dev/null +++ b/romiVendordep/BUILD.bazel @@ -0,0 +1,53 @@ +load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library", "cc_test") +load("@rules_java//java:defs.bzl", "java_binary", "java_library") + +cc_library( + name = "romi-cpp.static", + srcs = glob([ + "src/main/native/cpp/**", + ]), + hdrs = glob(["src/main/native/include/**"]), + strip_include_prefix = "src/main/native/include", + visibility = ["//visibility:public"], + deps = [ + "//wpilibc:wpilibc.static", + ], +) + +java_library( + name = "romi-java", + srcs = glob(["src/main/java/**/*.java"]), + visibility = ["//visibility:public"], + deps = [ + "//hal:hal-java", + "//wpilibj", + ], +) + +cc_test( + name = "romi-test", + size = "small", + srcs = glob(["src/test/native/cpp/**"]), + deps = [ + "//thirdparty/googletest:googletest.static", + ], +) + +cc_binary( + name = "DevMain-Cpp", + srcs = ["src/dev/native/cpp/main.cpp"], + deps = [ + ":romi-cpp.static", + ], +) + +java_binary( + name = "DevMain-Java", + srcs = ["src/dev/java/edu/wpi/first/wpilibj/romi/DevMain.java"], + main_class = "edu.wpi.first.wpilibj.romi.DevMain", + deps = [ + "//hal:hal-java", + "//ntcore:networktables-java", + "//wpiutil:wpiutil-java", + ], +) diff --git a/shared/bazel/compiler_flags/osx_flags.rc b/shared/bazel/compiler_flags/osx_flags.rc index da12307993..2aa48df280 100644 --- a/shared/bazel/compiler_flags/osx_flags.rc +++ b/shared/bazel/compiler_flags/osx_flags.rc @@ -42,3 +42,6 @@ build:macos --linkopt=-Wl,-rpath,'@loader_path'" # Things not in nativetools build:macos --copt=-Wno-shorten-64-to-32 + +build:macos --host_per_file_copt=external/zlib/.*\.c@-Wno-deprecated-non-prototype +build:macos --host_per_file_copt=external/com_google_protobuf/.*\.cc@-Wno-unused-function diff --git a/shared/bazel/compiler_flags/roborio_flags.rc b/shared/bazel/compiler_flags/roborio_flags.rc index 691da8d5e6..a0a5055726 100644 --- a/shared/bazel/compiler_flags/roborio_flags.rc +++ b/shared/bazel/compiler_flags/roborio_flags.rc @@ -14,3 +14,5 @@ build:roborio --cxxopt=-Wno-error=deprecated-declarations # Extra 11 build:roborio --cxxopt=-Wno-error=deprecated-enum-enum-conversion + +build:roborio --host_per_file_copt=external/zlib/.*\.c@-Wno-deprecated-non-prototype diff --git a/shared/examplecheck.gradle b/shared/examplecheck.gradle index ed913dcc1c..644c095cf2 100644 --- a/shared/examplecheck.gradle +++ b/shared/examplecheck.gradle @@ -67,8 +67,9 @@ def tagList = [ "SmartDashboard", "Shuffleboard", "Sendable", "DataLog", /* --- Controls --- */ - "Exponential Profile", "PID", "State-Space", "Path Following", "Trajectory", "SysId", - "Simulation", "Trapezoid Profile", "Profiled PID", "Odometry", "LQR", "Pose Estimator", + "Exponential Profile", "PID", "State-Space", "LTVUnicycleController", "Path Following", + "Trajectory", "SysId", "Simulation", "Trapezoid Profile", "Profiled PID", "Odometry", + "LQR", "Pose Estimator", /* --- Hardware --- */ "Analog", "Ultrasonic", "Gyro", "Pneumatics", "I2C", "Duty Cycle", "PDP", "DMA", "Relay", diff --git a/wpilibNewCommands/BUILD.bazel b/wpilibNewCommands/BUILD.bazel new file mode 100644 index 0000000000..6add11137a --- /dev/null +++ b/wpilibNewCommands/BUILD.bazel @@ -0,0 +1,87 @@ +load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library", "cc_test") +load("@rules_java//java:defs.bzl", "java_binary", "java_library") + +cc_library( + name = "generated_cc_headers", + hdrs = glob(["src/generated/main/native/include/**"]), + includes = ["src/generated/main/native/include"], + strip_include_prefix = "src/generated/main/native/include", + visibility = ["//wpilibNewCommands:__subpackages__"], +) + +filegroup( + name = "generated_cc_source", + srcs = glob(["src/generated/main/native/cpp/**"]), + visibility = ["//wpilibNewCommands:__subpackages__"], +) + +filegroup( + name = "generated_java", + srcs = glob(["src/generated/main/java/**/*.java"]), + visibility = ["//wpilibNewCommands:__subpackages__"], +) + +cc_library( + name = "wpilibNewCommands.static", + srcs = glob(["src/main/native/cpp/**"]) + [":generated_cc_source"], + hdrs = glob(["src/main/native/include/**"]), + includes = ["src/main/native/include"], + strip_include_prefix = "src/main/native/include", + visibility = ["//visibility:public"], + deps = [ + ":generated_cc_headers", + "//wpilibc:wpilibc.static", + ], +) + +java_library( + name = "wpilibNewCommands-java", + srcs = glob(["src/main/java/**/*.java"]) + [":generated_java"], + visibility = ["//visibility:public"], + deps = [ + "//cscore:cscore-java", + "//hal:hal-java", + "//ntcore:networktables-java", + "//wpilibj", + "//wpimath:wpimath-java", + "//wpinet:wpinet-java", + "//wpiunits", + "//wpiutil:wpiutil-java", + ], +) + +cc_test( + name = "wpilibNewCommands-cpp-test", + size = "small", + srcs = glob([ + "src/test/native/**/*.cpp", + "src/test/native/**/*.h", + ]), + tags = [ + "no-tsan", + "no-ubsan", + ], + deps = [ + ":wpilibNewCommands.static", + "//thirdparty/googletest:googletest.static", + ], +) + +cc_binary( + name = "DevMain-Cpp", + srcs = ["src/dev/native/cpp/main.cpp"], + deps = [ + ], +) + +java_binary( + name = "DevMain-Java", + srcs = ["src/dev/java/edu/wpi/first/wpilibj2/commands/DevMain.java"], + main_class = "edu.wpi.first.wpilibj2.commands.DevMain", + deps = [ + "//hal:hal-java", + "//ntcore:networktables-java", + "//wpimath:wpimath-java", + "//wpiutil:wpiutil-java", + ], +) diff --git a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/ProxyCommand.java b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/ProxyCommand.java index 7225dee1a9..d3235c9e19 100644 --- a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/ProxyCommand.java +++ b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/ProxyCommand.java @@ -34,8 +34,7 @@ public class ProxyCommand extends Command { * @deprecated This constructor's similarity to {@link DeferredCommand} is confusing and opens * potential footguns for users who do not fully understand the semantics and implications of * proxying, but who simply want runtime construction. Users who do know what they are doing - * and need a supplier-constructed proxied command should instead proxy a DeferredCommand - * using the asProxy decorator. + * and need a supplier-constructed proxied command should instead defer a proxy command. * @see DeferredCommand */ @Deprecated(since = "2025", forRemoval = true) diff --git a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/button/Trigger.java b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/button/Trigger.java index d7b1dd0893..8dc3ae52b3 100644 --- a/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/button/Trigger.java +++ b/wpilibNewCommands/src/main/java/edu/wpi/first/wpilibj2/command/button/Trigger.java @@ -24,6 +24,18 @@ import java.util.function.BooleanSupplier; *

This class is provided by the NewCommands VendorDep */ public class Trigger implements BooleanSupplier { + /** Functional interface for the body of a trigger binding. */ + @FunctionalInterface + private interface BindingBody { + /** + * Executes the body of the binding. + * + * @param previous The previous state of the condition. + * @param current The current state of the condition. + */ + void run(boolean previous, boolean current); + } + private final BooleanSupplier m_condition; private final EventLoop m_loop; @@ -49,6 +61,27 @@ public class Trigger implements BooleanSupplier { this(CommandScheduler.getInstance().getDefaultButtonLoop(), condition); } + /** + * Adds a binding to the EventLoop. + * + * @param body The body of the binding to add. + */ + private void addBinding(BindingBody body) { + m_loop.bind( + new Runnable() { + private boolean m_previous = m_condition.getAsBoolean(); + + @Override + public void run() { + boolean current = m_condition.getAsBoolean(); + + body.run(m_previous, current); + + m_previous = current; + } + }); + } + /** * Starts the command when the condition changes. * @@ -57,19 +90,10 @@ public class Trigger implements BooleanSupplier { */ public Trigger onChange(Command command) { requireNonNullParam(command, "command", "onChange"); - m_loop.bind( - new Runnable() { - private boolean m_pressedLast = m_condition.getAsBoolean(); - - @Override - public void run() { - boolean pressed = m_condition.getAsBoolean(); - - if (m_pressedLast != pressed) { - command.schedule(); - } - - m_pressedLast = pressed; + addBinding( + (previous, current) -> { + if (previous != current) { + command.schedule(); } }); return this; @@ -83,19 +107,10 @@ public class Trigger implements BooleanSupplier { */ public Trigger onTrue(Command command) { requireNonNullParam(command, "command", "onTrue"); - m_loop.bind( - new Runnable() { - private boolean m_pressedLast = m_condition.getAsBoolean(); - - @Override - public void run() { - boolean pressed = m_condition.getAsBoolean(); - - if (!m_pressedLast && pressed) { - command.schedule(); - } - - m_pressedLast = pressed; + addBinding( + (previous, current) -> { + if (!previous && current) { + command.schedule(); } }); return this; @@ -109,19 +124,10 @@ public class Trigger implements BooleanSupplier { */ public Trigger onFalse(Command command) { requireNonNullParam(command, "command", "onFalse"); - m_loop.bind( - new Runnable() { - private boolean m_pressedLast = m_condition.getAsBoolean(); - - @Override - public void run() { - boolean pressed = m_condition.getAsBoolean(); - - if (m_pressedLast && !pressed) { - command.schedule(); - } - - m_pressedLast = pressed; + addBinding( + (previous, current) -> { + if (previous && !current) { + command.schedule(); } }); return this; @@ -139,21 +145,12 @@ public class Trigger implements BooleanSupplier { */ public Trigger whileTrue(Command command) { requireNonNullParam(command, "command", "whileTrue"); - m_loop.bind( - new Runnable() { - private boolean m_pressedLast = m_condition.getAsBoolean(); - - @Override - public void run() { - boolean pressed = m_condition.getAsBoolean(); - - if (!m_pressedLast && pressed) { - command.schedule(); - } else if (m_pressedLast && !pressed) { - command.cancel(); - } - - m_pressedLast = pressed; + addBinding( + (previous, current) -> { + if (!previous && current) { + command.schedule(); + } else if (previous && !current) { + command.cancel(); } }); return this; @@ -171,21 +168,12 @@ public class Trigger implements BooleanSupplier { */ public Trigger whileFalse(Command command) { requireNonNullParam(command, "command", "whileFalse"); - m_loop.bind( - new Runnable() { - private boolean m_pressedLast = m_condition.getAsBoolean(); - - @Override - public void run() { - boolean pressed = m_condition.getAsBoolean(); - - if (m_pressedLast && !pressed) { - command.schedule(); - } else if (!m_pressedLast && pressed) { - command.cancel(); - } - - m_pressedLast = pressed; + addBinding( + (previous, current) -> { + if (previous && !current) { + command.schedule(); + } else if (!previous && current) { + command.cancel(); } }); return this; @@ -199,23 +187,14 @@ public class Trigger implements BooleanSupplier { */ public Trigger toggleOnTrue(Command command) { requireNonNullParam(command, "command", "toggleOnTrue"); - m_loop.bind( - new Runnable() { - private boolean m_pressedLast = m_condition.getAsBoolean(); - - @Override - public void run() { - boolean pressed = m_condition.getAsBoolean(); - - if (!m_pressedLast && pressed) { - if (command.isScheduled()) { - command.cancel(); - } else { - command.schedule(); - } + addBinding( + (previous, current) -> { + if (!previous && current) { + if (command.isScheduled()) { + command.cancel(); + } else { + command.schedule(); } - - m_pressedLast = pressed; } }); return this; @@ -229,23 +208,14 @@ public class Trigger implements BooleanSupplier { */ public Trigger toggleOnFalse(Command command) { requireNonNullParam(command, "command", "toggleOnFalse"); - m_loop.bind( - new Runnable() { - private boolean m_pressedLast = m_condition.getAsBoolean(); - - @Override - public void run() { - boolean pressed = m_condition.getAsBoolean(); - - if (m_pressedLast && !pressed) { - if (command.isScheduled()) { - command.cancel(); - } else { - command.schedule(); - } + addBinding( + (previous, current) -> { + if (previous && !current) { + if (command.isScheduled()) { + command.cancel(); + } else { + command.schedule(); } - - m_pressedLast = pressed; } }); return this; diff --git a/wpilibNewCommands/src/main/native/cpp/frc2/command/button/Trigger.cpp b/wpilibNewCommands/src/main/native/cpp/frc2/command/button/Trigger.cpp index a3b02d18f3..00f179da08 100644 --- a/wpilibNewCommands/src/main/native/cpp/frc2/command/button/Trigger.cpp +++ b/wpilibNewCommands/src/main/native/cpp/frc2/command/button/Trigger.cpp @@ -15,159 +15,117 @@ using namespace frc2; Trigger::Trigger(const Trigger& other) = default; +void Trigger::AddBinding(wpi::unique_function&& body) { + m_loop->Bind([condition = m_condition, previous = m_condition(), + body = std::move(body)]() mutable { + bool current = condition(); + + body(previous, current); + + previous = current; + }); +} + Trigger Trigger::OnChange(Command* command) { - m_loop->Bind( - [condition = m_condition, previous = m_condition(), command]() mutable { - bool current = condition(); - - if (previous != current) { - command->Schedule(); - } - - previous = current; - }); + AddBinding([command](bool previous, bool current) { + if (previous != current) { + command->Schedule(); + } + }); return *this; } Trigger Trigger::OnChange(CommandPtr&& command) { - m_loop->Bind([condition = m_condition, previous = m_condition(), - command = std::move(command)]() mutable { - bool current = condition(); - + AddBinding([command = std::move(command)](bool previous, bool current) { if (previous != current) { command.Schedule(); } - - previous = current; }); return *this; } Trigger Trigger::OnTrue(Command* command) { - m_loop->Bind( - [condition = m_condition, previous = m_condition(), command]() mutable { - bool current = condition(); - - if (!previous && current) { - command->Schedule(); - } - - previous = current; - }); + AddBinding([command](bool previous, bool current) { + if (!previous && current) { + command->Schedule(); + } + }); return *this; } Trigger Trigger::OnTrue(CommandPtr&& command) { - m_loop->Bind([condition = m_condition, previous = m_condition(), - command = std::move(command)]() mutable { - bool current = condition(); - + AddBinding([command = std::move(command)](bool previous, bool current) { if (!previous && current) { command.Schedule(); } - - previous = current; }); return *this; } Trigger Trigger::OnFalse(Command* command) { - m_loop->Bind( - [condition = m_condition, previous = m_condition(), command]() mutable { - bool current = condition(); - - if (previous && !current) { - command->Schedule(); - } - - previous = current; - }); + AddBinding([command](bool previous, bool current) { + if (previous && !current) { + command->Schedule(); + } + }); return *this; } Trigger Trigger::OnFalse(CommandPtr&& command) { - m_loop->Bind([condition = m_condition, previous = m_condition(), - command = std::move(command)]() mutable { - bool current = condition(); - + AddBinding([command = std::move(command)](bool previous, bool current) { if (previous && !current) { command.Schedule(); } - - previous = current; }); return *this; } Trigger Trigger::WhileTrue(Command* command) { - m_loop->Bind( - [condition = m_condition, previous = m_condition(), command]() mutable { - bool current = condition(); - - if (!previous && current) { - command->Schedule(); - } else if (previous && !current) { - command->Cancel(); - } - - previous = current; - }); + AddBinding([command](bool previous, bool current) { + if (!previous && current) { + command->Schedule(); + } else if (previous && !current) { + command->Cancel(); + } + }); return *this; } Trigger Trigger::WhileTrue(CommandPtr&& command) { - m_loop->Bind([condition = m_condition, previous = m_condition(), - command = std::move(command)]() mutable { - bool current = condition(); - + AddBinding([command = std::move(command)](bool previous, bool current) { if (!previous && current) { command.Schedule(); } else if (previous && !current) { command.Cancel(); } - - previous = current; }); return *this; } Trigger Trigger::WhileFalse(Command* command) { - m_loop->Bind( - [condition = m_condition, previous = m_condition(), command]() mutable { - bool current = condition(); - - if (previous && !current) { - command->Schedule(); - } else if (!previous && current) { - command->Cancel(); - } - - previous = current; - }); + AddBinding([command](bool previous, bool current) { + if (previous && !current) { + command->Schedule(); + } else if (!previous && current) { + command->Cancel(); + } + }); return *this; } Trigger Trigger::WhileFalse(CommandPtr&& command) { - m_loop->Bind([condition = m_condition, previous = m_condition(), - command = std::move(command)]() mutable { - bool current = condition(); - + AddBinding([command = std::move(command)](bool previous, bool current) { if (!previous && current) { command.Schedule(); } else if (previous && !current) { command.Cancel(); } - - previous = current; }); return *this; } Trigger Trigger::ToggleOnTrue(Command* command) { - m_loop->Bind([condition = m_condition, previous = m_condition(), - command = command]() mutable { - bool current = condition(); - + AddBinding([command](bool previous, bool current) { if (!previous && current) { if (command->IsScheduled()) { command->Cancel(); @@ -175,17 +133,12 @@ Trigger Trigger::ToggleOnTrue(Command* command) { command->Schedule(); } } - - previous = current; }); return *this; } Trigger Trigger::ToggleOnTrue(CommandPtr&& command) { - m_loop->Bind([condition = m_condition, previous = m_condition(), - command = std::move(command)]() mutable { - bool current = condition(); - + AddBinding([command = std::move(command)](bool previous, bool current) { if (!previous && current) { if (command.IsScheduled()) { command.Cancel(); @@ -193,17 +146,12 @@ Trigger Trigger::ToggleOnTrue(CommandPtr&& command) { command.Schedule(); } } - - previous = current; }); return *this; } Trigger Trigger::ToggleOnFalse(Command* command) { - m_loop->Bind([condition = m_condition, previous = m_condition(), - command = command]() mutable { - bool current = condition(); - + AddBinding([command](bool previous, bool current) { if (previous && !current) { if (command->IsScheduled()) { command->Cancel(); @@ -211,17 +159,12 @@ Trigger Trigger::ToggleOnFalse(Command* command) { command->Schedule(); } } - - previous = current; }); return *this; } Trigger Trigger::ToggleOnFalse(CommandPtr&& command) { - m_loop->Bind([condition = m_condition, previous = m_condition(), - command = std::move(command)]() mutable { - bool current = condition(); - + AddBinding([command = std::move(command)](bool previous, bool current) { if (previous && !current) { if (command.IsScheduled()) { command.Cancel(); @@ -229,8 +172,6 @@ Trigger Trigger::ToggleOnFalse(CommandPtr&& command) { command.Schedule(); } } - - previous = current; }); return *this; } diff --git a/wpilibNewCommands/src/main/native/include/frc2/command/ProxyCommand.h b/wpilibNewCommands/src/main/native/include/frc2/command/ProxyCommand.h index 714427d104..353effe9dc 100644 --- a/wpilibNewCommands/src/main/native/include/frc2/command/ProxyCommand.h +++ b/wpilibNewCommands/src/main/native/include/frc2/command/ProxyCommand.h @@ -41,12 +41,11 @@ class ProxyCommand : public CommandHelper { * confusing and opens potential footguns for users who do not fully * understand the semantics and implications of proxying, but who simply want * runtime construction. Users who do know what they are doing and need a - * supplier-constructed proxied command should instead proxy a DeferredCommand - * using the AsProxy decorator. + * supplier-constructed proxied command should instead defer a proxy command. * @see DeferredCommand */ WPI_IGNORE_DEPRECATED - [[deprecated("Proxy a DeferredCommand instead")]] + [[deprecated("Defer a proxy command instead.")]] explicit ProxyCommand(wpi::unique_function supplier); /** @@ -62,11 +61,10 @@ class ProxyCommand : public CommandHelper { * confusing and opens potential footguns for users who do not fully * understand the semantics and implications of proxying, but who simply want * runtime construction. Users who do know what they are doing and need a - * supplier-constructed proxied command should instead proxy a DeferredCommand - * using the AsProxy decorator. + * supplier-constructed proxied command should instead defer a proxy command. * @see DeferredCommand */ - [[deprecated("Proxy a DeferredCommand instead")]] + [[deprecated("Defer a proxy command instead.")]] explicit ProxyCommand(wpi::unique_function supplier); WPI_UNIGNORE_DEPRECATED diff --git a/wpilibNewCommands/src/main/native/include/frc2/command/button/Trigger.h b/wpilibNewCommands/src/main/native/include/frc2/command/button/Trigger.h index a238263133..9e86b1b700 100644 --- a/wpilibNewCommands/src/main/native/include/frc2/command/button/Trigger.h +++ b/wpilibNewCommands/src/main/native/include/frc2/command/button/Trigger.h @@ -11,6 +11,7 @@ #include #include #include +#include #include "frc2/command/Command.h" #include "frc2/command/CommandScheduler.h" @@ -291,6 +292,13 @@ class Trigger { bool Get() const; private: + /** + * Adds a binding to the EventLoop. + * + * @param body The body of the binding to add. + */ + void AddBinding(wpi::unique_function&& body); + frc::EventLoop* m_loop; std::function m_condition; }; diff --git a/wpilibc/BUILD.bazel b/wpilibc/BUILD.bazel new file mode 100644 index 0000000000..c11298d3d5 --- /dev/null +++ b/wpilibc/BUILD.bazel @@ -0,0 +1,80 @@ +load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library", "cc_test") +load("//shared/bazel/rules/gen:gen-version-file.bzl", "generate_version_file") + +generate_version_file( + name = "generate-version", + output_file = "WPILibVersion.cpp", + template = "src/generate/WPILibVersion.cpp.in", + visibility = ["//wpilibc:__subpackages__"], +) + +cc_library( + name = "generated_cc_headers", + hdrs = glob(["src/generated/main/native/include/**"]), + includes = ["src/generated/main/native/include"], + strip_include_prefix = "src/generated/main/native/include", + visibility = ["//wpilibc:__subpackages__"], +) + +filegroup( + name = "generated_cc_source", + srcs = glob( + ["src/generated/main/native/cpp/**"], + exclude = ["src/generated/main/native/cpp/jni/**"], + ), + visibility = ["//wpilibc:__subpackages__"], +) + +cc_library( + name = "wpilibc.static", + srcs = [ + ":generate-version", + ] + glob([ + "src/main/native/cppcs/**", + "src/main/native/cpp/**", + ]) + [":generated_cc_source"], + hdrs = glob(["src/main/native/include/**"]), + strip_include_prefix = "src/main/native/include", + visibility = ["//visibility:public"], + deps = [ + ":generated_cc_headers", + "//cameraserver:cameraserver.static", + "//cscore:cscore.static", + "//hal:wpiHal.static", + "//ntcore:ntcore.static", + "//wpimath:wpimath.static", + "//wpinet:wpinet.static", + "//wpiutil:wpiutil.static", + ], +) + +cc_library( + name = "test-headers", + testonly = True, + hdrs = glob(["src/test/native/include/**"]), + includes = ["src/test/native/include"], +) + +cc_test( + name = "wpilibc-test", + size = "small", + srcs = glob(["src/test/native/cpp/**"]), + tags = [ + "no-asan", + "no-tsan", + "no-ubsan", + ], + deps = [ + ":test-headers", + ":wpilibc.static", + "//thirdparty/googletest:googletest.static", + ], +) + +cc_binary( + name = "DevMain-Cpp", + srcs = ["src/dev/native/cpp/main.cpp"], + deps = [ + ":wpilibc.static", + ], +) diff --git a/wpilibcExamples/src/main/cpp/examples/examples.json b/wpilibcExamples/src/main/cpp/examples/examples.json index c0354003f2..8723e307ca 100644 --- a/wpilibcExamples/src/main/cpp/examples/examples.json +++ b/wpilibcExamples/src/main/cpp/examples/examples.json @@ -759,10 +759,11 @@ }, { "name": "SimpleDifferentialDriveSimulation", - "description": "Simulate a differential drivetrain and follow trajectories with LTV unicycle controller (non-command-based).", + "description": "Simulate a differential drivetrain and follow trajectories with LTVUnicycleController (non-command-based).", "tags": [ "Differential Drive", "State-Space", + "LTVUnicycleController", "Path Following", "Trajectory", "Encoder", diff --git a/wpilibcIntegrationTests/BUILD.bazel b/wpilibcIntegrationTests/BUILD.bazel new file mode 100644 index 0000000000..c67b2dd969 --- /dev/null +++ b/wpilibcIntegrationTests/BUILD.bazel @@ -0,0 +1,24 @@ +load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library") + +ATHENA_SOURCES = glob(["src/main/native/cpp/**"]) + +NON_ATHENA_SOURCES = glob(["src/main/native/dt/**"]) + +cc_library( + name = "test_headers", + hdrs = glob(["src/main/native/include/**"]), + strip_include_prefix = "src/main/native/include", +) + +cc_binary( + name = "wpilibcIntegrationTests", + srcs = select({ + "@rules_bzlmodrio_toolchains//constraints/is_roborio:roborio": ATHENA_SOURCES, + "//conditions:default": NON_ATHENA_SOURCES, + }), + deps = [ + ":test_headers", + "//thirdparty/googletest:googletest.static", + "//wpilibc:wpilibc.static", + ], +) diff --git a/wpilibjExamples/src/main/java/edu/wpi/first/wpilibj/examples/examples.json b/wpilibjExamples/src/main/java/edu/wpi/first/wpilibj/examples/examples.json index d4b37d9d57..1abd22855b 100644 --- a/wpilibjExamples/src/main/java/edu/wpi/first/wpilibj/examples/examples.json +++ b/wpilibjExamples/src/main/java/edu/wpi/first/wpilibj/examples/examples.json @@ -650,10 +650,11 @@ }, { "name": "SimpleDifferentialDriveSimulation", - "description": "Simulate a differential drivetrain and follow trajectories with LTV unicycle controller (non-command-based).", + "description": "Simulate a differential drivetrain and follow trajectories with LTVUnicycleController (non-command-based).", "tags": [ "Differential Drive", "State-Space", + "LTVUnicycleController", "Path Following", "Trajectory", "Encoder", diff --git a/wpimath/BUILD.bazel b/wpimath/BUILD.bazel index 2f650293fd..7979a8ba1c 100644 --- a/wpimath/BUILD.bazel +++ b/wpimath/BUILD.bazel @@ -1,3 +1,4 @@ +load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library", "cc_test") load("@rules_java//java:defs.bzl", "java_binary", "java_library") load("@rules_python//python:defs.bzl", "py_binary") @@ -7,6 +8,76 @@ filegroup( visibility = ["//wpimath:__subpackages__"], ) +cc_library( + name = "eigen-headers", + hdrs = glob([ + "src/main/native/thirdparty/eigen/include/**", + ]), + includes = ["src/main/native/thirdparty/eigen/include"], + strip_include_prefix = "src/main/native/thirdparty/eigen/include", + visibility = ["//wpimath:__subpackages__"], +) + +cc_library( + name = "gcem", + hdrs = glob([ + "src/main/native/thirdparty/gcem/include/**", + ]), + includes = ["src/main/native/thirdparty/gcem/include"], + strip_include_prefix = "src/main/native/thirdparty/gcem/include", + visibility = ["//wpimath:__subpackages__"], +) + +cc_library( + name = "sleipnir-headers", + hdrs = glob([ + "src/main/native/thirdparty/sleipnir/include/**/*.hpp", + ]), + includes = ["src/main/native/thirdparty/sleipnir/include"], + strip_include_prefix = "src/main/native/thirdparty/sleipnir/include", + visibility = ["//wpimath:__subpackages__"], +) + +filegroup( + name = "sleipnir-srcs", + srcs = glob(["src/main/native/thirdparty/sleipnir/src/**"]), + visibility = ["//wpimath:__subpackages__"], +) + +cc_library( + name = "nanopb-generated-headers", + hdrs = glob(["src/generated/main/native/cpp/**/*.h"]), + includes = ["src/generated/main/native/cpp"], + strip_include_prefix = "src/generated/main/native/cpp", + visibility = ["//wpiutil:__subpackages__"], +) + +cc_library( + name = "wpimath.static", + srcs = glob( + [ + "src/main/native/cpp/**", + "src/generated/main/native/cpp/**/*.cpp", + ], + exclude = ["src/main/native/cpp/jni/**"], + ) + [":sleipnir-srcs"], + hdrs = glob(["src/main/native/include/**"]), + defines = ["WPILIB_EXPORTS"], + includes = [ + "src/main/native/include", + "src/main/native/thirdparty/sleipnir/src", + ], + strip_include_prefix = "src/main/native/include", + visibility = ["//visibility:public"], + deps = [ + ":eigen-headers", + ":gcem", + ":nanopb-generated-headers", + ":sleipnir-headers", + "//wpiutil:wpiutil.static", + ], +) + java_library( name = "wpimath-java", srcs = [":generated_java"] + glob(["src/main/java/**/*.java"]), @@ -24,6 +95,40 @@ java_library( ], ) +cc_library( + name = "test_headers", + hdrs = glob([ + "src/test/native/include/**", + ]), + strip_include_prefix = "src/test/native/include", +) + +cc_test( + name = "wpimath-cpp-test", + size = "small", + srcs = glob([ + "src/test/native/cpp/**/*.cpp", + "src/test/native/cpp/**/*.h", + ]), + tags = [ + "no-bullseye", + "no-raspi", + ], + deps = [ + ":test_headers", + ":wpimath.static", + "//thirdparty/googletest:googletest.static", + ], +) + +cc_binary( + name = "DevMain-Cpp", + srcs = ["src/dev/native/cpp/main.cpp"], + deps = [ + ":wpimath.static", + ], +) + java_binary( name = "DevMain-Java", srcs = ["src/dev/java/edu/wpi/first/math/DevMain.java"], diff --git a/wpinet/src/main/java/edu/wpi/first/net/WPINetJNI.java b/wpinet/src/main/java/edu/wpi/first/net/WPINetJNI.java index 084f0220f5..a4bfadf2b3 100644 --- a/wpinet/src/main/java/edu/wpi/first/net/WPINetJNI.java +++ b/wpinet/src/main/java/edu/wpi/first/net/WPINetJNI.java @@ -80,6 +80,22 @@ public class WPINetJNI { */ public static native void removePortForwarder(int port); + /** + * Create a web server at the given port. Note that local ports less than 1024 won't work as a + * normal user. + * + * @param port local port number + * @param path local path to document root + */ + public static native void startWebServer(int port, String path); + + /** + * Stop web server running at the given port. + * + * @param port local port number + */ + public static native void stopWebServer(int port); + /** * Creates a MulticastServiceAnnouncer. * diff --git a/wpinet/src/main/java/edu/wpi/first/net/WebServer.java b/wpinet/src/main/java/edu/wpi/first/net/WebServer.java new file mode 100644 index 0000000000..e7a71a5e79 --- /dev/null +++ b/wpinet/src/main/java/edu/wpi/first/net/WebServer.java @@ -0,0 +1,33 @@ +// 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.net; + +/** A web server using the HTTP protocol. */ +public final class WebServer { + private WebServer() { + throw new UnsupportedOperationException("This is a utility class!"); + } + + /** + * Create a web server at the given port. Note that local ports less than 1024 won't work as a + * normal user. Also, many ports are blocked by the FRC robot radio; check the game manual for + * what is allowed through the radio firewall. + * + * @param port local port number + * @param path local path to document root + */ + public static void start(int port, String path) { + WPINetJNI.startWebServer(port, path); + } + + /** + * Stop web server running at the given port. + * + * @param port local port number + */ + public static void stop(int port) { + WPINetJNI.stopWebServer(port); + } +} diff --git a/wpinet/src/main/native/cpp/HttpUtil.cpp b/wpinet/src/main/native/cpp/HttpUtil.cpp index 92022ee797..83d9ded426 100644 --- a/wpinet/src/main/native/cpp/HttpUtil.cpp +++ b/wpinet/src/main/native/cpp/HttpUtil.cpp @@ -81,6 +81,22 @@ std::string_view EscapeURI(std::string_view str, SmallVectorImpl& buf, return {buf.data(), buf.size()}; } +std::string_view EscapeHTML(std::string_view str, SmallVectorImpl& buf) { + buf.clear(); + for (auto i = str.begin(), end = str.end(); i != end; ++i) { + if (*i == '&') { + buf.append({'&', 'a', 'm', 'p', ';'}); + } else if (*i == '<') { + buf.append({'&', 'l', 't', ';'}); + } else if (*i == '>') { + buf.append({'&', 'g', 't', ';'}); + } else { + buf.push_back(*i); + } + } + return {buf.data(), buf.size()}; +} + HttpQueryMap::HttpQueryMap(std::string_view query) { SmallVector queryElems; split(query, queryElems, '&', 100, false); diff --git a/wpinet/src/main/native/cpp/WebServer.cpp b/wpinet/src/main/native/cpp/WebServer.cpp new file mode 100644 index 0000000000..9efd76a190 --- /dev/null +++ b/wpinet/src/main/native/cpp/WebServer.cpp @@ -0,0 +1,396 @@ +// 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 "wpinet/WebServer.h" + +#ifndef _WIN32 +#include +#endif + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wpinet/EventLoopRunner.h" +#include "wpinet/HttpServerConnection.h" +#include "wpinet/HttpUtil.h" +#include "wpinet/UrlParser.h" +#include "wpinet/raw_uv_ostream.h" +#include "wpinet/uv/GetAddrInfo.h" +#include "wpinet/uv/Stream.h" +#include "wpinet/uv/Tcp.h" +#include "wpinet/uv/Timer.h" + +using namespace wpi; + +namespace { +class MyHttpConnection : public wpi::HttpServerConnection, + public std::enable_shared_from_this { + public: + explicit MyHttpConnection(std::shared_ptr stream, + std::string_view path) + : HttpServerConnection{std::move(stream)}, m_path{path} {} + + protected: + void ProcessRequest() override; + void SendFileResponse(int code, std::string_view codeText, + std::string_view contentType, fs::path filename, + std::string_view extraHeader = {}); + + std::string m_path; +}; +} // namespace + +#ifndef _WIN32 +namespace { +class SendfileReq : public uv::RequestImpl { + public: + SendfileReq(uv_file out, uv_file in, int64_t inOffset, size_t len) + : m_out(out), m_in(in), m_inOffset(inOffset), m_len(len) { + error = [this](uv::Error err) { GetLoop().error(err); }; + } + + uv::Loop& GetLoop() const { + return *static_cast(GetRaw()->loop->data); + } + + int Send(uv::Loop& loop) { + int err = uv_fs_sendfile(loop.GetRaw(), GetRaw(), m_out, m_in, m_inOffset, + m_len, [](uv_fs_t* req) { + auto& h = *static_cast(req->data); + if (req->result < 0) { + h.ReportError(req->result); + h.complete(); + h.Release(); + return; + } + + h.m_inOffset += req->result; + h.m_len -= req->result; + if (h.m_len == 0) { + // done + h.complete(); + h.Release(); // this is always a one-shot + return; + } + + // need to send more + h.Send(h.GetLoop()); + }); + if (err < 0) { + ReportError(err); + complete(); + } + return err; + } + + wpi::sig::Signal<> complete; + + private: + uv_file m_out; + uv_file m_in; + int64_t m_inOffset; + size_t m_len; +}; +} // namespace + +static void Sendfile(uv::Loop& loop, uv_file out, uv_file in, int64_t inOffset, + size_t len, std::function complete) { + auto req = std::make_shared(out, in, inOffset, len); + if (complete) { + req->complete.connect(complete); + } + int err = req->Send(loop); + if (err >= 0) { + req->Keep(); + } +} +#endif + +static std::string_view GetMimeType(std::string_view ext) { + static const wpi::StringMap map{ + {"css", "text/css"}, + {"csv", "text/csv"}, + {"gif", "image/gif"}, + {"htm", "text/html"}, + {"html", "text/html"}, + {"ico", "image/vnd.microsoft.icon"}, + {"jar", "application/java-archive"}, + {"jpeg", "image/jpeg"}, + {"jpg", "image/jpeg"}, + {"js", "text/javascript"}, + {"json", "text/json"}, + {"mjs", "text/javascript"}, + {"pdf", "application/pdf"}, + {"png", "image/png"}, + {"sh", "application/x-sh"}, + {"svg", "image/svg+xml"}, + {"txt", "text/plain"}, + {"webp", "image/webp"}, + {"xhtml", "application/xhtml+xml"}, + {"xml", "application/xml"}, + {"zip", "application/zip"}, + }; + auto it = map.find(ext); + if (it == map.end()) { + return "application/octet-stream"; + } + return it->second; +} + +void MyHttpConnection::SendFileResponse(int code, std::string_view codeText, + std::string_view contentType, + fs::path filename, + std::string_view extraHeader) { +#ifdef _WIN32 + auto membuf = wpi::MemoryBuffer::GetFile(filename.string()); + if (!membuf) { + SendError(404); + return; + } + + wpi::SmallVector toSend; + wpi::raw_uv_ostream os{toSend, 4096}; + BuildHeader(os, code, codeText, contentType, (*membuf)->size(), extraHeader); + SendData(os.bufs(), false); + auto buf = (*membuf)->GetBuffer(); + m_stream.Write( + {{buf}}, [closeAfter = !m_keepAlive, stream = &m_stream, + membuf = std::shared_ptr{std::move(*membuf)}](auto, uv::Error) { + if (closeAfter) { + stream->Close(); + } + }); +#else + // open file + std::error_code ec; + auto infile = fs::OpenFileForRead(filename, ec); + if (ec) { + SendError(404); + return; + } + int infd = fs::FileToFd(infile, ec, fs::OF_None); + if (ec) { + fs::CloseFile(infile); + SendError(404); + return; + } + + // get file size + auto size = fs::file_size(filename, ec); + if (ec) { + SendError(404); + ::close(infd); + return; + } + + uv_os_fd_t outfd; + int err = uv_fileno(m_stream.GetRawHandle(), &outfd); + if (err < 0) { + m_stream.GetLoopRef().ReportError(err); + SendError(404); + ::close(infd); + return; + } + + wpi::SmallVector toSend; + wpi::raw_uv_ostream os{toSend, 4096}; + BuildHeader(os, code, codeText, contentType, size, extraHeader); + SendData(os.bufs(), false); + + // close after write completes if we aren't keeping alive + // since we're using sendfile, set socket to blocking + m_stream.SetBlocking(true); + Sendfile(m_stream.GetLoopRef(), outfd, infd, 0, size, + [infd, closeAfter = !m_keepAlive, stream = &m_stream] { + ::close(infd); + if (closeAfter) { + stream->Close(); + } else { + stream->SetBlocking(false); + } + }); +#endif +} + +void MyHttpConnection::ProcessRequest() { + // fmt::print(stderr, "HTTP request: '{}'\n", m_request.GetUrl()); + wpi::UrlParser url{m_request.GetUrl(), + m_request.GetMethod() == wpi::HTTP_CONNECT}; + if (!url.IsValid()) { + // failed to parse URL + SendError(400); + return; + } + + std::string_view path; + if (url.HasPath()) { + path = url.GetPath(); + } + // fmt::print(stderr, "path: \"{}\"\n", path); + + wpi::SmallString<128> pathBuf; + bool error; + path = UnescapeURI(path, pathBuf, &error); + if (error) { + SendError(400); + return; + } + + std::string_view query; + if (url.HasQuery()) { + query = url.GetQuery(); + } + // fmt::print(stderr, "query: \"{}\"\n", query); + HttpQueryMap qmap{query}; + + const bool isGET = m_request.GetMethod() == wpi::HTTP_GET; + if (isGET && wpi::starts_with(path, '/') && !wpi::contains(path, "..")) { + fs::path fullpath = fmt::format("{}{}", m_path, path); + std::error_code ec; + bool isdir = fs::is_directory(fullpath, ec); + if (isdir) { + if (!wpi::ends_with(path, '/')) { + // redirect to trailing / location + SendResponse(301, "Moved Permanently", "text/plain", "", + fmt::format("Location: {}/\r\n\r\n", path)); + return; + } + // generate directory listing + wpi::SmallString<64> formatBuf; + if (qmap.Get("format", formatBuf).value_or("") == "json") { + wpi::json dirs = wpi::json::array(); + wpi::json files = wpi::json::array(); + for (auto&& entry : fs::directory_iterator{fullpath}) { + bool subdir = entry.is_directory(ec); + std::string name = entry.path().filename().string(); + if (subdir) { + dirs.emplace_back(wpi::json{{"name", std::move(name)}}); + } else { + files.emplace_back( + wpi::json{{"name", std::move(name)}, + {"size", subdir ? 0 : entry.file_size(ec)}}); + } + } + SendResponse( + 200, "OK", "text/json", + wpi::json{{"dirs", std::move(dirs)}, {"files", std::move(files)}} + .dump()); + } else { + wpi::StringMap dirs; + wpi::StringMap files; + for (auto&& entry : fs::directory_iterator{fullpath}) { + bool subdir = entry.is_directory(ec); + std::string name = entry.path().filename().string(); + wpi::SmallString<128> nameUriBuf, nameHtmlBuf; + if (subdir) { + dirs.emplace( + name, fmt::format( + "{}/", + EscapeURI(name, nameUriBuf), + EscapeHTML(name, nameHtmlBuf))); + } else { + files.emplace( + name, fmt::format( + "{}{}", + EscapeURI(name, nameUriBuf), + EscapeHTML(name, nameHtmlBuf), entry.file_size(ec))); + } + } + + std::string html = fmt::format( + "{}" + "\n", + path); + for (auto&& str : dirs) { + html += str.second; + } + for (auto&& str : files) { + html += str.second; + } + html += "
NameSize
"; + SendResponse(200, "OK", "text/html", html); + } + } else { + wpi::SmallString<128> extraHeadersBuf; + wpi::raw_svector_ostream os{extraHeadersBuf}; + os << "Content-Disposition: filename=\""; + os.write_escaped(fullpath.filename().string()); + os << "\"\r\n"; + SendFileResponse(200, "OK", GetMimeType(wpi::rsplit(path, '.').second), + fullpath, os.str()); + } + } else { + SendError(404, "Resource not found"); + } +} + +struct WebServer::Impl { + public: + EventLoopRunner runner; + DenseMap> servers; +}; + +WebServer::WebServer() : m_impl{new Impl} {} + +WebServer& WebServer::GetInstance() { + static WebServer instance; + return instance; +} + +void WebServer::Start(unsigned int port, std::string_view path) { + m_impl->runner.ExecSync([&](uv::Loop& loop) { + auto server = uv::Tcp::Create(loop); + if (!server) { + wpi::print(stderr, "WebServer: Creating server failed\n"); + return; + } + + // bind to local port + server->Bind("", port); + + // when we get a connection, accept it + server->connection.connect( + [serverPtr = server.get(), path = std::string{path}] { + auto client = serverPtr->Accept(); + if (!client) { + wpi::print(stderr, "WebServer: Connecting to client failed\n"); + return; + } + + // close on error + client->error.connect([clientPtr = client.get()](uv::Error err) { + clientPtr->Close(); + }); + + auto conn = std::make_shared(client, path); + client->SetData(conn); + }); + + // start listening for incoming connections + server->Listen(); + + m_impl->servers[port] = server; + }); +} + +void WebServer::Stop(unsigned int port) { + m_impl->runner.ExecSync([&](uv::Loop& loop) { + if (auto server = m_impl->servers.lookup(port).lock()) { + server->Close(); + m_impl->servers.erase(port); + } + }); +} diff --git a/wpinet/src/main/native/cpp/jni/WPINetJNI.cpp b/wpinet/src/main/native/cpp/jni/WPINetJNI.cpp index fb59c4d959..04efdab204 100644 --- a/wpinet/src/main/native/cpp/jni/WPINetJNI.cpp +++ b/wpinet/src/main/native/cpp/jni/WPINetJNI.cpp @@ -16,6 +16,7 @@ #include "wpinet/MulticastServiceAnnouncer.h" #include "wpinet/MulticastServiceResolver.h" #include "wpinet/PortForwarder.h" +#include "wpinet/WebServer.h" using namespace wpi::java; @@ -80,6 +81,31 @@ Java_edu_wpi_first_net_WPINetJNI_removePortForwarder wpi::PortForwarder::GetInstance().Remove(port); } +/* + * Class: edu_wpi_first_net_WPINetJNI + * Method: startWebServer + * Signature: (ILjava/lang/String;)V + */ +JNIEXPORT void JNICALL +Java_edu_wpi_first_net_WPINetJNI_startWebServer + (JNIEnv* env, jclass, jint port, jstring path) +{ + wpi::WebServer::GetInstance().Start(static_cast(port), + JStringRef{env, path}.str()); +} + +/* + * Class: edu_wpi_first_net_WPINetJNI + * Method: stopWebServer + * Signature: (I)V + */ +JNIEXPORT void JNICALL +Java_edu_wpi_first_net_WPINetJNI_stopWebServer + (JNIEnv* env, jclass, jint port) +{ + wpi::WebServer::GetInstance().Stop(port); +} + /* * Class: edu_wpi_first_net_WPINetJNI * Method: createMulticastServiceAnnouncer diff --git a/wpinet/src/main/native/include/wpinet/HttpUtil.h b/wpinet/src/main/native/include/wpinet/HttpUtil.h index 1509a6f1a0..dca5225809 100644 --- a/wpinet/src/main/native/include/wpinet/HttpUtil.h +++ b/wpinet/src/main/native/include/wpinet/HttpUtil.h @@ -39,6 +39,11 @@ std::string_view UnescapeURI(std::string_view str, SmallVectorImpl& buf, std::string_view EscapeURI(std::string_view str, SmallVectorImpl& buf, bool spacePlus = true); +// Escape a string for HTML output. +// @param buf Buffer for output +// @return Escaped string +std::string_view EscapeHTML(std::string_view str, SmallVectorImpl& buf); + // Parse a set of HTTP headers. Saves just the Content-Type and Content-Length // fields. // @param is Input stream diff --git a/wpinet/src/main/native/include/wpinet/WebServer.h b/wpinet/src/main/native/include/wpinet/WebServer.h new file mode 100644 index 0000000000..ebfb9a36b2 --- /dev/null +++ b/wpinet/src/main/native/include/wpinet/WebServer.h @@ -0,0 +1,58 @@ +// 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. + +#ifndef WPINET_WEBSERVER_H_ +#define WPINET_WEBSERVER_H_ + +#pragma once + +#include +#include + +namespace wpi { + +/** + * A web server using the HTTP protocol. + */ +class WebServer { + public: + WebServer(const WebServer&) = delete; + WebServer& operator=(const WebServer&) = delete; + + /** + * Get an instance of the WebServer class. + * + * This is a singleton to guarantee that there is only a single instance + * regardless of how many times GetInstance is called. + */ + static WebServer& GetInstance(); + + /** + * Create a web server at the given port. + * Note that local ports less than 1024 won't work as a normal user. Also, + * many ports are blocked by the FRC robot radio; check the game manual for + * what is allowed through the radio firewall. + * + * @param port local port number + * @param path local path to document root + */ + void Start(unsigned int port, std::string_view path); + + /** + * Stop web server running at the given port. + * + * @param port local port number + */ + void Stop(unsigned int port); + + private: + WebServer(); + + struct Impl; + std::unique_ptr m_impl; +}; + +} // namespace wpi + +#endif // WPINET_WEBSERVER_H_ diff --git a/wpiunits/src/main/java/edu/wpi/first/units/MomentOfInertiaUnit.java b/wpiunits/src/main/java/edu/wpi/first/units/MomentOfInertiaUnit.java index 74362d9a9f..ab92427001 100644 --- a/wpiunits/src/main/java/edu/wpi/first/units/MomentOfInertiaUnit.java +++ b/wpiunits/src/main/java/edu/wpi/first/units/MomentOfInertiaUnit.java @@ -28,7 +28,7 @@ public final class MomentOfInertiaUnit extends PerUnit baseUnit, + MomentOfInertiaUnit baseUnit, UnaryFunction toBaseConverter, UnaryFunction fromBaseConverter, String name, diff --git a/wpiutil/BUILD.bazel b/wpiutil/BUILD.bazel index 6e9318bedf..928fa4e8f3 100644 --- a/wpiutil/BUILD.bazel +++ b/wpiutil/BUILD.bazel @@ -197,9 +197,9 @@ cc_library( ":llvm-srcs", ":memory-srcs", ":mpack-srcs", + ":nanopb-srcs", ":native-srcs", ":protobuf-srcs", - ":nanopb-srcs", ], hdrs = glob(["src/main/native/include/**/*"]), includes = ["src/main/native/include"], @@ -215,8 +215,8 @@ cc_library( ":llvm-headers", ":memory-headers", ":mpack-headers", - ":protobuf-headers", ":nanopb-headers", + ":protobuf-headers", ":sigslot-headers", ] + select({ "@rules_bzlmodrio_toolchains//constraints/is_roborio:roborio": ["@bzlmodrio-ni//libraries/cpp/ni:shared"], diff --git a/wpiutil/src/main/java/edu/wpi/first/util/RuntimeLoader.java b/wpiutil/src/main/java/edu/wpi/first/util/RuntimeLoader.java index 452cacfe72..5ceaa076ad 100644 --- a/wpiutil/src/main/java/edu/wpi/first/util/RuntimeLoader.java +++ b/wpiutil/src/main/java/edu/wpi/first/util/RuntimeLoader.java @@ -16,17 +16,20 @@ public final class RuntimeLoader { * @return A load error message. */ private static String getLoadErrorMessage(String libraryName, UnsatisfiedLinkError ule) { + String jvmLocation = ProcessHandle.current().info().command().orElse("Unknown"); StringBuilder msg = new StringBuilder(512); msg.append(libraryName) .append(" could not be loaded from path.\n" + "\tattempted to load for platform ") .append(CombinedRuntimeLoader.getPlatformPath()) .append("\nLast Load Error: \n") .append(ule.getMessage()) - .append('\n'); + .append('\n') + .append(String.format("JVM Location: %s\n", jvmLocation)); if (System.getProperty("os.name").startsWith("Windows")) { msg.append( - "A common cause of this error is missing the C++ runtime.\n" - + "Download the latest at https://support.microsoft.com/en-us/help/2977003/the-latest-supported-visual-c-downloads\n"); + "A common cause of this error is using a JVM with an incorrect MSVC runtime.\n" + + "Ensure you are using the WPILib JVM (The current running JVM is listed above)\n" + + "See https://wpilib.org/jvmruntime for more information\n"); } return msg.toString(); } diff --git a/xrpVendordep/BUILD.bazel b/xrpVendordep/BUILD.bazel new file mode 100644 index 0000000000..964ea19d70 --- /dev/null +++ b/xrpVendordep/BUILD.bazel @@ -0,0 +1,54 @@ +load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library", "cc_test") +load("@rules_java//java:defs.bzl", "java_binary", "java_library") + +cc_library( + name = "xrp-cpp", + srcs = glob([ + "src/main/native/cpp/**", + ]), + hdrs = glob(["src/main/native/include/**"]), + strip_include_prefix = "src/main/native/include", + visibility = ["//visibility:public"], + deps = [ + "//wpilibc:wpilibc.static", + ], +) + +java_library( + name = "xrp-java", + srcs = glob(["src/main/java/**/*.java"]), + visibility = ["//visibility:public"], + deps = [ + "//hal:hal-java", + "//wpilibj", + "//wpimath:wpimath-java", + ], +) + +cc_test( + name = "xrp-cpp-test", + size = "small", + srcs = glob(["src/test/native/cpp/**"]), + deps = [ + "//thirdparty/googletest:googletest.static", + ], +) + +cc_binary( + name = "DevMain-Cpp", + srcs = ["src/dev/native/cpp/main.cpp"], + deps = [ + "//wpiutil:wpiutil.static", + ], +) + +java_binary( + name = "DevMain-Java", + srcs = ["src/dev/java/edu/wpi/first/wpilibj/xrp/DevMain.java"], + main_class = "edu.wpi.first.wpilibj.xrp.DevMain", + deps = [ + "//hal:hal-java", + "//ntcore:networktables-java", + "//wpiutil:wpiutil-java", + ], +)