mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-07-03 03:01:44 +00:00
Compare commits
202 Commits
v2025.1.1-
...
v2025.1.1-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
80c391e182 | ||
|
|
70f36cce7e | ||
|
|
564c1f2de2 | ||
|
|
a1b642a402 | ||
|
|
f9b3efb712 | ||
|
|
782459dff4 | ||
|
|
4bca79b9af | ||
|
|
68285dae77 | ||
|
|
5e3dba672a | ||
|
|
e943424609 | ||
|
|
4225b732fd | ||
|
|
39d05ebe7c | ||
|
|
cc41a0ed24 | ||
|
|
d5edb4060d | ||
|
|
e08fdeba21 | ||
|
|
544553a58f | ||
|
|
838c5fbcd7 | ||
|
|
38a239b531 | ||
|
|
9a1b4245fa | ||
|
|
e222efaa01 | ||
|
|
f772bb141d | ||
|
|
278efa6384 | ||
|
|
e876452967 | ||
|
|
5c95966d44 | ||
|
|
882233bede | ||
|
|
1921d019b7 | ||
|
|
c3fc7c829d | ||
|
|
4ce8930342 | ||
|
|
60198c0bec | ||
|
|
54f0e11fc0 | ||
|
|
de82ed434d | ||
|
|
4dd3a36d2e | ||
|
|
92f8c89267 | ||
|
|
1eecaaf337 | ||
|
|
892e062316 | ||
|
|
9807d60566 | ||
|
|
c387a7ecae | ||
|
|
715cbb6b76 | ||
|
|
9d40b993f8 | ||
|
|
92ee5bc523 | ||
|
|
5012ad7499 | ||
|
|
145450b73d | ||
|
|
b91864a5ec | ||
|
|
0941251375 | ||
|
|
7d178615fa | ||
|
|
806d56e564 | ||
|
|
65f3345407 | ||
|
|
5e1c6a84ce | ||
|
|
a0af0fd572 | ||
|
|
f377a9c573 | ||
|
|
62338c7287 | ||
|
|
49e3e4a0be | ||
|
|
59dbfc9c2d | ||
|
|
9607c6c10d | ||
|
|
6ef5b85758 | ||
|
|
b6de7acbdb | ||
|
|
fe28fa1ded | ||
|
|
b7eb9fb8f9 | ||
|
|
1d58c5025e | ||
|
|
b4a8d33486 | ||
|
|
0a3ccf93c6 | ||
|
|
d92f17b014 | ||
|
|
4c225ef2c1 | ||
|
|
561078ce29 | ||
|
|
d312bccfeb | ||
|
|
9826539198 | ||
|
|
33f7067216 | ||
|
|
ac1836ec44 | ||
|
|
57e10755fd | ||
|
|
8ec22b7d5c | ||
|
|
a04c40f589 | ||
|
|
b4bec566f0 | ||
|
|
d76827db48 | ||
|
|
602c4caa02 | ||
|
|
661c321fe2 | ||
|
|
d1de7663d3 | ||
|
|
b040059108 | ||
|
|
0798ac53d0 | ||
|
|
ded7c87d63 | ||
|
|
2acf111f56 | ||
|
|
aa7dd258c4 | ||
|
|
ca51197486 | ||
|
|
91142ba5fe | ||
|
|
969664ceaa | ||
|
|
1e545c38a8 | ||
|
|
6eb652e10e | ||
|
|
fff73ee6e1 | ||
|
|
bade0a8716 | ||
|
|
453335e354 | ||
|
|
c289562a06 | ||
|
|
07345712dc | ||
|
|
425bf83036 | ||
|
|
6adad7bad7 | ||
|
|
280d2c7e32 | ||
|
|
edc3963955 | ||
|
|
c58be2580d | ||
|
|
f40bd3593d | ||
|
|
3cc541f261 | ||
|
|
811b130968 | ||
|
|
d39dfd64ea | ||
|
|
661bae568f | ||
|
|
01f85abcfe | ||
|
|
396f8203ac | ||
|
|
4a43ddbacf | ||
|
|
0921054a28 | ||
|
|
f738fc92f0 | ||
|
|
a0f38f83f9 | ||
|
|
876be30724 | ||
|
|
de318fab91 | ||
|
|
8b8b634f65 | ||
|
|
fd2e0c0427 | ||
|
|
a66fa339dc | ||
|
|
44a45d44e2 | ||
|
|
af652817d9 | ||
|
|
5a16b0e108 | ||
|
|
71c050389a | ||
|
|
83fa422338 | ||
|
|
3113627be6 | ||
|
|
f2d2500d1d | ||
|
|
63e623d70b | ||
|
|
9e8d37c03b | ||
|
|
ad09d73dd6 | ||
|
|
3fd33b1f72 | ||
|
|
043c155087 | ||
|
|
7a6c7af412 | ||
|
|
8588b5e520 | ||
|
|
debb52156c | ||
|
|
23e71e10e4 | ||
|
|
44c0bbc4a9 | ||
|
|
a48f3c35f4 | ||
|
|
7c91b81906 | ||
|
|
eb8583596c | ||
|
|
dfd1084526 | ||
|
|
2cfe114c78 | ||
|
|
caae5357b7 | ||
|
|
22e91bfacd | ||
|
|
67e1b5fe95 | ||
|
|
27e07d6787 | ||
|
|
e49d452e46 | ||
|
|
5c0edc2410 | ||
|
|
309b370223 | ||
|
|
f620141e0d | ||
|
|
5f3cf517d3 | ||
|
|
328a781040 | ||
|
|
ebf83e4340 | ||
|
|
9f6f267f5c | ||
|
|
21980c7447 | ||
|
|
75fc4d18ef | ||
|
|
8f81b7723d | ||
|
|
fe45265a3a | ||
|
|
0f313c672f | ||
|
|
aaf139320e | ||
|
|
89c5d98fe9 | ||
|
|
defcc02806 | ||
|
|
e6e928d670 | ||
|
|
f03e0cdf6a | ||
|
|
412c042c6c | ||
|
|
f44c3eda43 | ||
|
|
018dcaea4f | ||
|
|
85ffb7814b | ||
|
|
6207992709 | ||
|
|
42a433b6fa | ||
|
|
2c857cd82a | ||
|
|
1c220ebc60 | ||
|
|
1cfed736ce | ||
|
|
46b5631ba7 | ||
|
|
d2b19d8928 | ||
|
|
9a5f73d787 | ||
|
|
db552317e7 | ||
|
|
03cb3c70b4 | ||
|
|
ac907f755a | ||
|
|
a3b12b3bd9 | ||
|
|
cbc9264468 | ||
|
|
28ac2e3554 | ||
|
|
58c0cd46b1 | ||
|
|
115a02211c | ||
|
|
f553dee6cb | ||
|
|
e8d2d1c39a | ||
|
|
7cc7fa1845 | ||
|
|
cbdb4e81f6 | ||
|
|
05c7fd929b | ||
|
|
0c824bd447 | ||
|
|
ed18b41198 | ||
|
|
6745fc7c2f | ||
|
|
40af8db28a | ||
|
|
5ac132f6a2 | ||
|
|
dd72a78aa4 | ||
|
|
36e0c9d6db | ||
|
|
95b9bd880b | ||
|
|
2054d0f57e | ||
|
|
ee22482f4a | ||
|
|
796dbd3b86 | ||
|
|
0424e5ba36 | ||
|
|
f7dddb8014 | ||
|
|
68715aa484 | ||
|
|
fad06ae1e7 | ||
|
|
40caabea23 | ||
|
|
59dc9ad8f4 | ||
|
|
2b1c5aa4fc | ||
|
|
0bada2e102 | ||
|
|
ee281ea448 | ||
|
|
bedfc09268 |
20
.bazelignore
Normal file
20
.bazelignore
Normal file
@@ -0,0 +1,20 @@
|
||||
build_cmake
|
||||
build-cmake
|
||||
|
||||
# Auto generated by vscode
|
||||
apriltag/bin
|
||||
cameraserver/bin
|
||||
cameraserver/multiCameraServer/bin
|
||||
cscore/bin
|
||||
fieldImages/bin
|
||||
hal/bin
|
||||
developerRobot/bin
|
||||
ntcore/bin
|
||||
romiVendordep/bin
|
||||
wpilibNewCommands/bin
|
||||
wpilibj/bin
|
||||
wpimath/bin
|
||||
wpinet/bin
|
||||
wpiutil/bin
|
||||
wpiunits/bin
|
||||
xrpVendordep/bin
|
||||
51
.bazelrc
Normal file
51
.bazelrc
Normal file
@@ -0,0 +1,51 @@
|
||||
try-import %workspace%/bazel_auth.rc
|
||||
try-import %workspace%/user.bazelrc
|
||||
|
||||
common --noenable_bzlmod
|
||||
|
||||
build --java_language_version=17
|
||||
build --java_runtime_version=roboriojdk_17
|
||||
build --tool_java_language_version=17
|
||||
build --tool_java_runtime_version=remotejdk_17
|
||||
|
||||
test --test_output=errors
|
||||
test --test_verbose_timeout_warnings
|
||||
|
||||
import shared/bazel/compiler_flags/sanitizers.rc
|
||||
import shared/bazel/compiler_flags/base_linux_flags.rc
|
||||
import shared/bazel/compiler_flags/linux_flags.rc
|
||||
import shared/bazel/compiler_flags/osx_flags.rc
|
||||
import shared/bazel/compiler_flags/roborio_flags.rc
|
||||
import shared/bazel/compiler_flags/windows_flags.rc
|
||||
import shared/bazel/compiler_flags/coverage_flags.rc
|
||||
|
||||
build:build_java --test_tag_filters=allwpilib-build-java --build_tag_filters=allwpilib-build-java
|
||||
build:build_cpp --test_tag_filters=+allwpilib-build-cpp --build_tag_filters=+allwpilib-build-cpp
|
||||
build:no_example --test_tag_filters=-wpi-example --build_tag_filters=-wpi-example
|
||||
test:no_example --test_tag_filters=-wpi-example --build_tag_filters=-wpi-example
|
||||
|
||||
# Build Buddy Cache Setup
|
||||
build:build_buddy --bes_results_url=https://app.buildbuddy.io/invocation/
|
||||
build:build_buddy --bes_backend=grpcs://remote.buildbuddy.io
|
||||
build:build_buddy --remote_cache=grpcs://remote.buildbuddy.io
|
||||
build:build_buddy --remote_timeout=3600
|
||||
|
||||
# Additional suggestions from buildbuddy for speed
|
||||
build:build_buddy --experimental_remote_cache_compression
|
||||
build:build_buddy --experimental_remote_cache_compression_threshold=100
|
||||
build:build_buddy --noslim_profile
|
||||
build:build_buddy --experimental_profile_include_target_label
|
||||
build:build_buddy --experimental_profile_include_primary_output
|
||||
build:build_buddy --nolegacy_important_outputs
|
||||
|
||||
common:build_buddy_readonly --noremote_upload_local_results
|
||||
|
||||
# This config should be used locally. It downloads more than the CI version
|
||||
build:remote_user --config=build_buddy
|
||||
build:remote_user --config=build_buddy_readonly
|
||||
build:remote_user --remote_download_toplevel
|
||||
|
||||
build:ci --config=build_buddy
|
||||
build:ci --remote_download_minimal
|
||||
|
||||
build --build_metadata=REPO_URL=https://github.com/wpilibsuite/allwpilib.git
|
||||
1
.bazelversion
Normal file
1
.bazelversion
Normal file
@@ -0,0 +1 @@
|
||||
7.3.1
|
||||
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -2,7 +2,7 @@
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: bug
|
||||
labels: 'type: bug'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -2,7 +2,7 @@
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: enhancement
|
||||
labels: 'type: feature'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/question.md
vendored
2
.github/ISSUE_TEMPLATE/question.md
vendored
@@ -2,7 +2,7 @@
|
||||
name: Question
|
||||
about: Ask about features or parts of this project
|
||||
title: ''
|
||||
labels: ''
|
||||
labels: 'type: support'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
27
.github/actions/setup-build-buddy/action.yml
vendored
Normal file
27
.github/actions/setup-build-buddy/action.yml
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
name: 'Setup BuildBuddy acache'
|
||||
description: 'Sets up the build buddy cache to be readonly / writing based on the presence of environment variables'
|
||||
|
||||
inputs:
|
||||
token:
|
||||
description: 'Build Buddy API token'
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Setup without key
|
||||
env:
|
||||
API_KEY: ${{ inputs.token }}
|
||||
if: ${{ env.API_KEY == '' }}
|
||||
shell: bash
|
||||
run: |
|
||||
echo "No API key secret detected, will setup readonly cache"
|
||||
echo "build:ci --config=build_buddy_readonly" > bazel_auth.rc
|
||||
|
||||
- name: Set with key
|
||||
env:
|
||||
API_KEY: ${{ inputs.token }}
|
||||
if: ${{ env.API_KEY != '' }}
|
||||
shell: bash
|
||||
run: |
|
||||
echo "API Key detected!"
|
||||
echo "build:build_buddy --remote_header=x-buildbuddy-api-key=${{ env.API_KEY }}" > bazel_auth.rc
|
||||
56
.github/labeler.yml
vendored
Normal file
56
.github/labeler.yml
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
'2027':
|
||||
- base-branch: '2027'
|
||||
'component: apriltag':
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: apriltag/**
|
||||
'component: command-based':
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: wpilibNewCommands/**
|
||||
'component: cscore':
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: cscore/**
|
||||
'component: datalogtool':
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: datalogtool/**
|
||||
'component: epilogue':
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: epilogue-*/**
|
||||
'component: examples':
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: wpilib*Examples/**
|
||||
'component: glass':
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: glass/**
|
||||
'component: hal':
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: hal/**
|
||||
'component: ntcore':
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: ntcore/**
|
||||
'component: outlineviewer':
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: outlineviewer/**
|
||||
'component: sysid':
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: sysid/**
|
||||
'component: teamnumbersetter':
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: roborioteamnumbersetter/**
|
||||
'component: wpilibc':
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: wpilibc/**
|
||||
'component: wpilibj':
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: wpilibj/**
|
||||
'component: wpimath':
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: wpimath/**
|
||||
'component: wpinet':
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: wpinet/**
|
||||
'component: wpiunits':
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: wpiunits/**
|
||||
'component: wpiutil':
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: wpiutil/**
|
||||
109
.github/workflows/bazel.yml
vendored
Normal file
109
.github/workflows/bazel.yml
vendored
Normal file
@@ -0,0 +1,109 @@
|
||||
name: Bazel
|
||||
|
||||
on: [pull_request, push]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build-windows:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- { name: "Windows (native)", os: windows-2022, action: "test", config: "--config=windows", }
|
||||
- { name: "Windows (arm)", os: windows-2022, action: "build", config: "--config=windows_arm", }
|
||||
|
||||
name: "Build ${{ matrix.name }}"
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with: { fetch-depth: 0 }
|
||||
- uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: 17
|
||||
architecture: x64
|
||||
|
||||
- id: Setup_build_buddy
|
||||
uses: ./.github/actions/setup-build-buddy
|
||||
with:
|
||||
token: ${{ secrets.BUILDBUDDY_API_KEY }}
|
||||
|
||||
- name: Build Release
|
||||
run: bazel --output_user_root=C:\\bazelroot ${{ matrix.action }} -k ... --config=ci -c opt ${{ matrix.config }} --verbose_failures
|
||||
shell: bash
|
||||
|
||||
build-mac:
|
||||
name: "Mac"
|
||||
runs-on: macos-14
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with: { fetch-depth: 0 }
|
||||
|
||||
- id: Setup_build_buddy
|
||||
uses: ./.github/actions/setup-build-buddy
|
||||
with:
|
||||
token: ${{ secrets.BUILDBUDDY_API_KEY }}
|
||||
|
||||
- name: Build Release
|
||||
run: bazel test -k ... --config=ci -c opt --config=macos --nojava_header_compilation --verbose_failures
|
||||
shell: bash
|
||||
|
||||
build-linux:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- { name: "Linux (native)", os: ubuntu-22.04, action: "test", config: "--config=linux", }
|
||||
- { name: "Linux (roborio)", os: ubuntu-22.04, action: "build", config: "--config=roborio", }
|
||||
name: "${{ matrix.name }}"
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with: { fetch-depth: 0 }
|
||||
- uses: bazelbuild/setup-bazelisk@v3
|
||||
|
||||
- id: Setup_build_buddy
|
||||
uses: ./.github/actions/setup-build-buddy
|
||||
with:
|
||||
token: ${{ secrets.BUILDBUDDY_API_KEY }}
|
||||
|
||||
- name: Build and Test Release
|
||||
run: bazel ${{ matrix.action }} ... --config=ci -c opt ${{ matrix.config }} -k --verbose_failures
|
||||
|
||||
buildifier:
|
||||
name: "buildifier"
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Set up Go 1.15.x
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
cache: false
|
||||
go-version: 1.15.x
|
||||
id: go
|
||||
|
||||
- name: Install Buildifier
|
||||
run: |
|
||||
cd $(mktemp -d)
|
||||
GO111MODULE=on go get github.com/bazelbuild/buildtools/buildifier@6.0.0
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with: { fetch-depth: 0 }
|
||||
|
||||
- name: Run buildifier
|
||||
run: buildifier -warnings all --lint=fix -r .
|
||||
|
||||
- name: Check Output
|
||||
run: git --no-pager diff --exit-code HEAD
|
||||
|
||||
- name: Generate diff
|
||||
run: git diff HEAD > bazel-lint-fixes.patch
|
||||
if: ${{ failure() }}
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.platform }}-bazel-lint-fixes
|
||||
path: bazel-lint-fixes.patch
|
||||
if: ${{ failure() }}
|
||||
51
.github/workflows/cmake-android.yml
vendored
Normal file
51
.github/workflows/cmake-android.yml
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
name: CMake Android
|
||||
|
||||
on: [pull_request, push]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
SCCACHE_WEBDAV_ENDPOINT: "https://frcmaven.wpi.edu/artifactory/wpilib-generic-cache-cmake-local"
|
||||
SCCACHE_WEBDAV_KEY_PREFIX: "sccache"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-22.04
|
||||
name: Android Arm64
|
||||
abi: arm64-v8a
|
||||
- os: ubuntu-22.04
|
||||
name: Android X64
|
||||
abi: "x86_64"
|
||||
|
||||
name: "Build - ${{ matrix.name }}"
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: nttld/setup-ndk@v1
|
||||
id: setup-ndk
|
||||
with:
|
||||
ndk-version: r27c
|
||||
add-to-path: false
|
||||
|
||||
- uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: 17
|
||||
|
||||
- name: Install sccache
|
||||
uses: mozilla-actions/sccache-action@v0.0.5
|
||||
|
||||
- name: Install dependencies
|
||||
run: sudo apt-get update && sudo apt-get install -y ninja-build
|
||||
|
||||
- name: configure
|
||||
run: cmake --preset with-sccache -DCMAKE_BUILD_TYPE=RelWithDebInfo -DWITH_WPILIB=OFF -DWITH_GUI=OFF -DWITH_CSCORE=OFF -DWITH_TESTS=OFF -DWITH_SIMULATION_MODULES=OFF -DWITH_PROTOBUF=OFF -DWITH_JAVA=ON -DBUILD_SHARED_LIBS=ON -DCMAKE_TOOLCHAIN_FILE=${{ steps.setup-ndk.outputs.ndk-path }}/build/cmake/android.toolchain.cmake -DANDROID_ABI="${{ matrix.abi }}" -DANDROID_PLATFORM=android-24
|
||||
|
||||
- name: build
|
||||
run: cmake --build build-cmake --parallel $(nproc)
|
||||
2
.github/workflows/cmake.yml
vendored
2
.github/workflows/cmake.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
include:
|
||||
- os: ubuntu-22.04
|
||||
name: Linux
|
||||
container: wpilib/roborio-cross-ubuntu:2024-22.04
|
||||
container: wpilib/roborio-cross-ubuntu:2025-22.04
|
||||
flags: "--preset with-java-and-sccache -DCMAKE_BUILD_TYPE=Release -DWITH_EXAMPLES=ON"
|
||||
- os: macOS-14
|
||||
name: macOS
|
||||
|
||||
10
.github/workflows/comment-command.yml
vendored
10
.github/workflows/comment-command.yml
vendored
@@ -33,17 +33,17 @@ jobs:
|
||||
env:
|
||||
GITHUB_TOKEN: "${{ secrets.COMMENT_COMMAND_PAT_TOKEN }}"
|
||||
NUMBER: ${{ github.event.issue.number }}
|
||||
- name: Set up Python 3.10
|
||||
- name: Set up Python 3.12
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.10'
|
||||
python-version: '3.12'
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: 17
|
||||
- name: Install wpiformat
|
||||
run: pip3 install wpiformat==2024.42
|
||||
run: pip3 install wpiformat==2024.50
|
||||
- name: Run wpiformat
|
||||
run: wpiformat
|
||||
- name: Run spotlessApply
|
||||
@@ -81,10 +81,10 @@ jobs:
|
||||
env:
|
||||
GITHUB_TOKEN: "${{ secrets.COMMENT_COMMAND_PAT_TOKEN }}"
|
||||
NUMBER: ${{ github.event.issue.number }}
|
||||
- name: Set up Python 3.9
|
||||
- name: Set up Python 3.12
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.9
|
||||
python-version: '3.12'
|
||||
- name: Install jinja
|
||||
run: python -m pip install jinja2
|
||||
- name: Install protobuf dependencies
|
||||
|
||||
6
.github/workflows/documentation.yml
vendored
6
.github/workflows/documentation.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
publish:
|
||||
name: "Documentation - Publish"
|
||||
runs-on: ubuntu-22.04
|
||||
if: github.repository == 'wpilibsuite/allwpilib' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v'))
|
||||
if: github.repository == 'wpilibsuite/allwpilib' && (github.ref == 'refs/heads/main' || (startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027')))
|
||||
concurrency: ci-docs-publish
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -32,12 +32,12 @@ jobs:
|
||||
run: |
|
||||
echo "EXTRA_GRADLE_ARGS=-PreleaseMode" >> $GITHUB_ENV
|
||||
echo "BRANCH=beta" >> $GITHUB_ENV
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027')
|
||||
- name: Set environment variables (Release)
|
||||
run: |
|
||||
echo "EXTRA_GRADLE_ARGS=-PreleaseMode" >> $GITHUB_ENV
|
||||
echo "BRANCH=release" >> $GITHUB_ENV
|
||||
if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, 'alpha') && !contains(github.ref, 'beta')
|
||||
if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, 'alpha') && !contains(github.ref, 'beta') && !contains(github.ref, '2027')
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew docs:generateJavaDocs docs:doxygen -PbuildServer ${{ env.EXTRA_GRADLE_ARGS }}
|
||||
- name: Install SSH Client 🔑
|
||||
|
||||
47
.github/workflows/gradle.yml
vendored
47
.github/workflows/gradle.yml
vendored
@@ -12,13 +12,13 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- container: wpilib/roborio-cross-ubuntu:2024-22.04
|
||||
- container: wpilib/roborio-cross-ubuntu:2025-22.04
|
||||
artifact-name: Athena
|
||||
build-options: "-Ponlylinuxathena"
|
||||
- container: wpilib/raspbian-cross-ubuntu:bullseye-22.04
|
||||
- container: wpilib/raspbian-cross-ubuntu:bookworm-22.04
|
||||
artifact-name: Arm32
|
||||
build-options: "-Ponlylinuxarm32"
|
||||
- container: wpilib/aarch64-cross-ubuntu:bullseye-22.04
|
||||
- container: wpilib/aarch64-cross-ubuntu:bookworm-22.04
|
||||
artifact-name: Arm64
|
||||
build-options: "-Ponlylinuxarm64"
|
||||
- container: wpilib/ubuntu-base:22.04
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
fetch-depth: 0
|
||||
- name: Set release environment variable
|
||||
run: echo "EXTRA_GRADLE_ARGS=-PreleaseMode" >> $GITHUB_ENV
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027')
|
||||
- name: Build with Gradle
|
||||
uses: addnab/docker-run-action@v3
|
||||
with:
|
||||
@@ -96,9 +96,16 @@ jobs:
|
||||
task: "build"
|
||||
outputs: "build/allOutputs"
|
||||
- os: windows-2022
|
||||
artifact-name: Win32
|
||||
artifact-name: Win32FFI
|
||||
architecture: x86
|
||||
task: ":ntcoreffi:build"
|
||||
build-options: "-Pntcoreffibuild \"-Dorg.gradle.jvmargs=-Xmx1096m\""
|
||||
outputs: "ntcoreffi/build/outputs"
|
||||
- os: windows-2022
|
||||
artifact-name: Win64FFI
|
||||
architecture: x64
|
||||
task: ":ntcoreffi:build"
|
||||
build-options: "-Pntcoreffibuild -Pbuildwinarm64"
|
||||
outputs: "ntcoreffi/build/outputs"
|
||||
name: "Build - ${{ matrix.artifact-name }}"
|
||||
runs-on: ${{ matrix.os }}
|
||||
@@ -119,16 +126,16 @@ jobs:
|
||||
keychain-password: ${{ secrets.APPLE_KEYCHAIN_PASSWORD }}
|
||||
if: |
|
||||
matrix.artifact-name == 'macOS' && (github.repository == 'wpilibsuite/allwpilib' &&
|
||||
(github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')))
|
||||
(github.ref == 'refs/heads/main' || (startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027'))))
|
||||
- name: Set Keychain Lock Timeout
|
||||
run: security set-keychain-settings -lut 3600
|
||||
run: security set-keychain-settings -lut 21600
|
||||
if: |
|
||||
matrix.artifact-name == 'macOS' && (github.repository == 'wpilibsuite/allwpilib' &&
|
||||
(github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')))
|
||||
(github.ref == 'refs/heads/main' || (startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027'))))
|
||||
- name: Set release environment variable
|
||||
run: echo "EXTRA_GRADLE_ARGS=-PreleaseMode" >> $GITHUB_ENV
|
||||
shell: bash
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027')
|
||||
- name: Set Java Heap Size
|
||||
run: sed -i 's/-Xmx2g/-Xmx1g/g' gradle.properties
|
||||
if: matrix.artifact-name == 'Win32'
|
||||
@@ -159,7 +166,7 @@ jobs:
|
||||
run: ./gradlew copyAllOutputs --build-cache -PbuildServer -PskipJavaFormat -PdeveloperID=${{ secrets.APPLE_DEVELOPER_ID }} ${{ matrix.build-options }} ${{ env.EXTRA_GRADLE_ARGS }}
|
||||
if: |
|
||||
matrix.artifact-name == 'macOS' && (github.repository == 'wpilibsuite/allwpilib' &&
|
||||
(github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')))
|
||||
(github.ref == 'refs/heads/main' || (startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027'))))
|
||||
- name: Check disk free space (Windows)
|
||||
run: wmic logicaldisk get caption, freespace
|
||||
if: matrix.os == 'windows-2022'
|
||||
@@ -184,7 +191,7 @@ jobs:
|
||||
java-version: 17
|
||||
- name: Set release environment variable
|
||||
run: echo "EXTRA_GRADLE_ARGS=-PreleaseMode" >> $GITHUB_ENV
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027')
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew docs:zipDocs --build-cache -PbuildServer ${{ env.EXTRA_GRADLE_ARGS }}
|
||||
env:
|
||||
@@ -203,7 +210,7 @@ jobs:
|
||||
- name: Free Disk Space
|
||||
if: |
|
||||
github.repository == 'wpilibsuite/allwpilib' &&
|
||||
(github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v'))
|
||||
(github.ref == 'refs/heads/main' || (startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027')))
|
||||
uses: jlumbroso/free-disk-space@main
|
||||
with:
|
||||
tool-cache: false
|
||||
@@ -216,31 +223,31 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
if: |
|
||||
github.repository == 'wpilibsuite/allwpilib' &&
|
||||
(github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v'))
|
||||
(github.ref == 'refs/heads/main' || (startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027')))
|
||||
with:
|
||||
repository: wpilibsuite/build-tools
|
||||
- uses: actions/download-artifact@v4
|
||||
if: |
|
||||
github.repository == 'wpilibsuite/allwpilib' &&
|
||||
(github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v'))
|
||||
(github.ref == 'refs/heads/main' || (startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027')))
|
||||
with:
|
||||
path: combiner/products/build/allOutputs
|
||||
- name: Flatten Artifacts
|
||||
if: |
|
||||
github.repository == 'wpilibsuite/allwpilib' &&
|
||||
(github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v'))
|
||||
(github.ref == 'refs/heads/main' || (startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027')))
|
||||
run: rsync -a --delete combiner/products/build/allOutputs/*/* combiner/products/build/allOutputs/
|
||||
- name: Check version number exists
|
||||
if: |
|
||||
github.repository == 'wpilibsuite/allwpilib' &&
|
||||
(github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v'))
|
||||
(github.ref == 'refs/heads/main' || (startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027')))
|
||||
run: |
|
||||
cat combiner/products/build/allOutputs/version.txt
|
||||
test -s combiner/products/build/allOutputs/version.txt
|
||||
- uses: actions/setup-java@v4
|
||||
if: |
|
||||
github.repository == 'wpilibsuite/allwpilib' &&
|
||||
(github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v'))
|
||||
(github.ref == 'refs/heads/main' || (startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027')))
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: 17
|
||||
@@ -256,7 +263,7 @@ jobs:
|
||||
- name: Combine (Release)
|
||||
if: |
|
||||
github.repository == 'wpilibsuite/allwpilib' &&
|
||||
startsWith(github.ref, 'refs/tags/v')
|
||||
startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027')
|
||||
run: cd combiner && ./gradlew publish -Pallwpilib -PreleaseRepoPublish
|
||||
env:
|
||||
RUN_AZURE_ARTIFACTORY_RELEASE: "TRUE"
|
||||
@@ -265,7 +272,7 @@ jobs:
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: |
|
||||
github.repository == 'wpilibsuite/allwpilib' &&
|
||||
(github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v'))
|
||||
(github.ref == 'refs/heads/main' || (startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027')))
|
||||
with:
|
||||
name: Maven
|
||||
path: ~/releases
|
||||
@@ -281,7 +288,7 @@ jobs:
|
||||
- uses: peter-evans/repository-dispatch@v3
|
||||
if: |
|
||||
github.repository == 'wpilibsuite/allwpilib' &&
|
||||
startsWith(github.ref, 'refs/tags/v')
|
||||
startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '2027')
|
||||
with:
|
||||
token: ${{ secrets.TOOL_REPO_ACCESS_TOKEN }}
|
||||
repository: wpilibsuite/${{ matrix.repo }}
|
||||
|
||||
12
.github/workflows/labeler.yml
vendored
Normal file
12
.github/workflows/labeler.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
name: "Pull Request Labeler"
|
||||
on:
|
||||
- pull_request_target
|
||||
|
||||
jobs:
|
||||
labeler:
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/labeler@v5
|
||||
14
.github/workflows/lint-format.yml
vendored
14
.github/workflows/lint-format.yml
vendored
@@ -22,12 +22,12 @@ jobs:
|
||||
run: |
|
||||
git checkout -b pr
|
||||
git branch -f main origin/main
|
||||
- name: Set up Python 3.10
|
||||
- name: Set up Python 3.12
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.10'
|
||||
python-version: '3.12'
|
||||
- name: Install wpiformat
|
||||
run: pip3 install wpiformat==2024.42
|
||||
run: pip3 install wpiformat==2024.50
|
||||
- name: Run
|
||||
run: wpiformat
|
||||
- name: Check output
|
||||
@@ -51,7 +51,7 @@ jobs:
|
||||
tidy:
|
||||
name: "clang-tidy"
|
||||
runs-on: ubuntu-22.04
|
||||
container: wpilib/roborio-cross-ubuntu:2024-22.04
|
||||
container: wpilib/ubuntu-base:22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -61,12 +61,12 @@ jobs:
|
||||
git config --global --add safe.directory /__w/allwpilib/allwpilib
|
||||
git checkout -b pr
|
||||
git branch -f main origin/main
|
||||
- name: Set up Python 3.10
|
||||
- name: Set up Python 3.12
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.10'
|
||||
python-version: '3.12'
|
||||
- name: Install wpiformat
|
||||
run: pip3 install wpiformat==2024.42
|
||||
run: pip3 install wpiformat==2024.50
|
||||
- name: Create compile_commands.json
|
||||
run: |
|
||||
./gradlew generateCompileCommands -Ptoolchain-optional-roboRio
|
||||
|
||||
70
.github/workflows/pregen_all.py
vendored
70
.github/workflows/pregen_all.py
vendored
@@ -6,7 +6,7 @@ import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def main(argv):
|
||||
def main():
|
||||
script_path = Path(__file__).resolve()
|
||||
REPO_ROOT = script_path.parent.parent.parent
|
||||
parser = argparse.ArgumentParser()
|
||||
@@ -15,26 +15,64 @@ def main(argv):
|
||||
help="Path to the quickbuf protoc plugin",
|
||||
required=True,
|
||||
)
|
||||
args = parser.parse_args(argv)
|
||||
subprocess.run(["python", f"{REPO_ROOT}/hal/generate_usage_reporting.py"])
|
||||
subprocess.run(["python", f"{REPO_ROOT}/ntcore/generate_topics.py"])
|
||||
subprocess.run(["python", f"{REPO_ROOT}/wpimath/generate_numbers.py"])
|
||||
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}/ntcore/generate_topics.py"], check=True
|
||||
)
|
||||
subprocess.run(
|
||||
[sys.executable, f"{REPO_ROOT}/wpimath/generate_numbers.py"], check=True
|
||||
)
|
||||
subprocess.run(
|
||||
[
|
||||
"python",
|
||||
sys.executable,
|
||||
f"{REPO_ROOT}/wpimath/generate_quickbuf.py",
|
||||
f"--quickbuf_plugin={args.quickbuf_plugin}",
|
||||
]
|
||||
],
|
||||
check=True,
|
||||
)
|
||||
subprocess.run(["python", f"{REPO_ROOT}/wpiunits/generate_units.py"])
|
||||
subprocess.run(["python", f"{REPO_ROOT}/wpilibc/generate_hids.py"])
|
||||
subprocess.run(["python", f"{REPO_ROOT}/wpilibj/generate_hids.py"])
|
||||
subprocess.run(["python", f"{REPO_ROOT}/wpilibNewCommands/generate_hids.py"])
|
||||
subprocess.run(["python", f"{REPO_ROOT}/wpilibc/generate_pwm_motor_controllers.py"])
|
||||
subprocess.run(["python", f"{REPO_ROOT}/wpilibj/generate_pwm_motor_controllers.py"])
|
||||
subprocess.run(["python", f"{REPO_ROOT}/thirdparty/imgui_suite/generate_gl3w.py"])
|
||||
subprocess.run(f"{REPO_ROOT}/thirdparty/imgui_suite/generate_fonts.sh")
|
||||
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(sys.argv[1:])
|
||||
main()
|
||||
|
||||
8
.github/workflows/pregenerate.yml
vendored
8
.github/workflows/pregenerate.yml
vendored
@@ -18,12 +18,12 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up Python 3.9
|
||||
- name: Set up Python 3.12
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.9
|
||||
- name: Install jinja
|
||||
run: python -m pip install jinja2
|
||||
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
|
||||
|
||||
2
.github/workflows/sanitizers.yml
vendored
2
.github/workflows/sanitizers.yml
vendored
@@ -30,7 +30,7 @@ jobs:
|
||||
ctest-flags: ""
|
||||
name: "${{ matrix.name }}"
|
||||
runs-on: ubuntu-22.04
|
||||
container: wpilib/roborio-cross-ubuntu:2024-22.04
|
||||
container: wpilib/roborio-cross-ubuntu:2025-22.04
|
||||
steps:
|
||||
- name: Install Dependencies
|
||||
run: sudo apt-get update && sudo apt-get install -y libopencv-dev libopencv4.5-java clang-14 libprotobuf-dev protobuf-compiler ninja-build
|
||||
|
||||
8
.github/workflows/sentinel-build.yml
vendored
8
.github/workflows/sentinel-build.yml
vendored
@@ -16,13 +16,13 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- container: wpilib/roborio-cross-ubuntu:2024-22.04
|
||||
- container: wpilib/roborio-cross-ubuntu:2025-22.04
|
||||
artifact-name: Athena
|
||||
build-options: "-Ponlylinuxathena"
|
||||
- container: wpilib/raspbian-cross-ubuntu:bullseye-22.04
|
||||
- container: wpilib/raspbian-cross-ubuntu:bookworm-22.04
|
||||
artifact-name: Arm32
|
||||
build-options: "-Ponlylinuxarm32"
|
||||
- container: wpilib/aarch64-cross-ubuntu:bullseye-22.04
|
||||
- container: wpilib/aarch64-cross-ubuntu:bookworm-22.04
|
||||
artifact-name: Arm64
|
||||
build-options: "-Ponlylinuxarm64"
|
||||
- container: wpilib/ubuntu-base:22.04
|
||||
@@ -119,7 +119,7 @@ jobs:
|
||||
if: |
|
||||
matrix.artifact-name == 'macOS' && (github.repository_owner == 'wpilibsuite' && github.ref == 'refs/heads/main')
|
||||
- name: Set Keychain Lock Timeout
|
||||
run: security set-keychain-settings -lut 3600
|
||||
run: security set-keychain-settings -lut 21600
|
||||
if: |
|
||||
matrix.artifact-name == 'macOS' && (github.repository_owner == 'wpilibsuite' && github.ref == 'refs/heads/main')
|
||||
- name: Set Java Heap Size
|
||||
|
||||
2
.github/workflows/tools.yml
vendored
2
.github/workflows/tools.yml
vendored
@@ -32,7 +32,7 @@ jobs:
|
||||
- name: Build WPILib with Gradle
|
||||
uses: addnab/docker-run-action@v3
|
||||
with:
|
||||
image: wpilib/roborio-cross-ubuntu:2024-22.04
|
||||
image: wpilib/roborio-cross-ubuntu:2025-22.04
|
||||
options: -v ${{ github.workspace }}:/work -w /work -e GITHUB_REF -e CI -e DISPLAY
|
||||
run: df . && rm -f semicolon_delimited_script && ./gradlew :wpilibc:publish :wpilibj:publish :wpilibNewCommands:publish :hal:publish :cameraserver:publish :ntcore:publish :cscore:publish :wpimath:publish :wpinet:publish :wpiutil:publish :apriltag:publish :wpiunits:publish :simulation:halsim_gui:publish :simulation:halsim_ds_socket:publish :fieldImages:publish :epilogue-processor:publish :epilogue-runtime:publish :thirdparty:googletest:publish -x test -x Javadoc -x doxygen --build-cache && cp -r /root/releases/maven/development /work
|
||||
- uses: actions/upload-artifact@v4
|
||||
|
||||
26
.github/workflows/upstream-utils.yml
vendored
26
.github/workflows/upstream-utils.yml
vendored
@@ -22,10 +22,10 @@ jobs:
|
||||
run: |
|
||||
git checkout -b pr
|
||||
git branch -f main origin/main
|
||||
- name: Set up Python 3.9
|
||||
- name: Set up Python 3.12
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.9
|
||||
python-version: '3.12'
|
||||
- name: Configure committer identity
|
||||
run: |
|
||||
git config --global user.email "you@example.com"
|
||||
@@ -35,102 +35,122 @@ jobs:
|
||||
cd upstream_utils
|
||||
./apriltag.py clone
|
||||
./apriltag.py copy-src
|
||||
./apriltag.py format-patch
|
||||
- name: Run argparse_lib.py
|
||||
run: |
|
||||
cd upstream_utils
|
||||
./argparse_lib.py clone
|
||||
./argparse_lib.py copy-src
|
||||
./argparse_lib.py format-patch
|
||||
- name: Run eigen.py
|
||||
run: |
|
||||
cd upstream_utils
|
||||
./eigen.py clone
|
||||
./eigen.py copy-src
|
||||
./eigen.py format-patch
|
||||
- name: Run expected.py
|
||||
run: |
|
||||
cd upstream_utils
|
||||
./expected.py clone
|
||||
./expected.py copy-src
|
||||
./expected.py format-patch
|
||||
- name: Run fmt.py
|
||||
run: |
|
||||
cd upstream_utils
|
||||
./fmt.py clone
|
||||
./fmt.py copy-src
|
||||
./fmt.py format-patch
|
||||
- name: Run gcem.py
|
||||
run: |
|
||||
cd upstream_utils
|
||||
./gcem.py clone
|
||||
./gcem.py copy-src
|
||||
./gcem.py format-patch
|
||||
- name: Run gl3w.py
|
||||
run: |
|
||||
cd upstream_utils
|
||||
./gl3w.py clone
|
||||
./gl3w.py copy-src
|
||||
./gl3w.py format-patch
|
||||
- name: Run glfw.py
|
||||
run: |
|
||||
cd upstream_utils
|
||||
./glfw.py clone
|
||||
./glfw.py copy-src
|
||||
./glfw.py format-patch
|
||||
- name: Run googletest.py
|
||||
run: |
|
||||
cd upstream_utils
|
||||
./googletest.py clone
|
||||
./googletest.py copy-src
|
||||
./googletest.py format-patch
|
||||
- name: Run imgui.py
|
||||
run: |
|
||||
cd upstream_utils
|
||||
./imgui.py clone
|
||||
./imgui.py copy-src
|
||||
./imgui.py format-patch
|
||||
- name: Run implot.py
|
||||
run: |
|
||||
cd upstream_utils
|
||||
./implot.py clone
|
||||
./implot.py copy-src
|
||||
./implot.py format-patch
|
||||
- name: Run json.py
|
||||
run: |
|
||||
cd upstream_utils
|
||||
./json.py clone
|
||||
./json.py copy-src
|
||||
./json.py format-patch
|
||||
- name: Run libuv.py
|
||||
run: |
|
||||
cd upstream_utils
|
||||
./libuv.py clone
|
||||
./libuv.py copy-src
|
||||
./libuv.py format-patch
|
||||
- name: Run llvm.py
|
||||
run: |
|
||||
cd upstream_utils
|
||||
./llvm.py clone
|
||||
./llvm.py copy-src
|
||||
./llvm.py format-patch
|
||||
- name: Run mpack.py
|
||||
run: |
|
||||
cd upstream_utils
|
||||
./mpack.py clone
|
||||
./mpack.py copy-src
|
||||
./mpack.py format-patch
|
||||
- name: Run stack_walker.py
|
||||
run: |
|
||||
cd upstream_utils
|
||||
./stack_walker.py clone
|
||||
./stack_walker.py copy-src
|
||||
./stack_walker.py format-patch
|
||||
- name: Run memory.py
|
||||
run: |
|
||||
cd upstream_utils
|
||||
./memory.py clone
|
||||
./memory.py copy-src
|
||||
./memory.py format-patch
|
||||
- name: Run protobuf.py
|
||||
run: |
|
||||
cd upstream_utils
|
||||
./protobuf.py clone
|
||||
./protobuf.py copy-src
|
||||
./protobuf.py format-patch
|
||||
- name: Run sleipnir.py
|
||||
run: |
|
||||
cd upstream_utils
|
||||
./sleipnir.py clone
|
||||
./sleipnir.py copy-src
|
||||
./sleipnir.py format-patch
|
||||
- name: Run stb.py
|
||||
run: |
|
||||
cd upstream_utils
|
||||
./stb.py clone
|
||||
./stb.py copy-src
|
||||
./stb.py format-patch
|
||||
- name: Add untracked files to index so they count as changes
|
||||
run: git add -A
|
||||
- name: Check output
|
||||
run: git --no-pager diff --exit-code HEAD
|
||||
run: git --no-pager diff --exit-code HEAD ':!*.bazel'
|
||||
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -15,6 +15,8 @@ networktables.json
|
||||
ntcore/connectionlistenertest.json
|
||||
ntcore/timesynctest.json
|
||||
|
||||
nanopb_pb2.py
|
||||
|
||||
# Created by the jenkins test script
|
||||
test-reports
|
||||
|
||||
@@ -249,9 +251,7 @@ imgui.ini
|
||||
/bazel-*
|
||||
user.bazelrc
|
||||
coverage_report/
|
||||
bazel_auth.rc
|
||||
|
||||
# ctest
|
||||
/Testing/
|
||||
|
||||
# protobuf
|
||||
!wpiprotoplugin.jar
|
||||
|
||||
@@ -66,6 +66,14 @@ set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
|
||||
option(BUILD_SHARED_LIBS "Build with shared libs (needed for JNI)" ON)
|
||||
option(WITH_JAVA "Include Java and JNI in the build" OFF)
|
||||
option(WITH_JAVA_SOURCE "Build Java source jars" ${WITH_JAVA})
|
||||
option(WITH_DOCS "Build Doxygen docs (needs Git for versioning)" OFF)
|
||||
cmake_dependent_option(
|
||||
DOCS_WARNINGS_AS_ERRORS
|
||||
"Make docs warnings into errors"
|
||||
OFF
|
||||
WITH_DOCS
|
||||
OFF
|
||||
)
|
||||
option(WITH_CSCORE "Build cscore (needs OpenCV)" ON)
|
||||
option(WITH_NTCORE "Build ntcore" ON)
|
||||
option(WITH_WPIMATH "Build wpimath" ON)
|
||||
@@ -123,12 +131,17 @@ set(java_lib_dest java)
|
||||
if(WITH_JAVA OR WITH_JAVA_SOURCE)
|
||||
set(CMAKE_JAVA_COMPILE_FLAGS "-encoding" "UTF8" "-Xlint:unchecked")
|
||||
find_package(Java REQUIRED COMPONENTS Development)
|
||||
find_package(JNI REQUIRED COMPONENTS JVM)
|
||||
else()
|
||||
# Protoc requires the java runtime
|
||||
find_package(Java REQUIRED COMPONENTS Runtime)
|
||||
if(NOT ANDROID)
|
||||
find_package(JNI REQUIRED COMPONENTS JVM)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(WITH_DOCS)
|
||||
find_package(Doxygen REQUIRED)
|
||||
find_package(Git REQUIRED)
|
||||
include(AddDoxygenDocs)
|
||||
add_doxygen_docs()
|
||||
endif()
|
||||
find_package(LIBSSH CONFIG 0.7.1)
|
||||
|
||||
set(CMAKE_FIND_PACKAGE_PREFER_CONFIG ON)
|
||||
@@ -279,8 +292,6 @@ if(WITH_NTCORE)
|
||||
add_subdirectory(ntcore)
|
||||
endif()
|
||||
|
||||
add_subdirectory(protoplugin)
|
||||
|
||||
if(WITH_WPIMATH)
|
||||
if(WITH_JAVA)
|
||||
set(WPIUNITS_DEP_REPLACE ${WPIUNITS_DEP_REPLACE_IMPL})
|
||||
|
||||
@@ -13,7 +13,7 @@ This article contains instructions on building projects using a development buil
|
||||
|
||||
Development builds are the per-commit build hosted every time a commit is pushed to the [allwpilib](https://github.com/wpilibsuite/allwpilib/) repository. These builds are then hosted on [artifactory](https://frcmaven.wpi.edu/artifactory/webapp/#/home).
|
||||
|
||||
To build a project using a development build, find the build.gradle file and open it. Then, add the following code below the plugin section and replace YEAR with the year of the development version. It is also necessary to use a 2024 GradleRIO version, ie `2024.0.0-alpha-1`
|
||||
To build a project using a development build, find the build.gradle file and open it. Then, add the following code below the plugin section and replace YEAR with the year of the development version. It is also necessary to use a 2025 GradleRIO version, ie `2025.1.1-beta-1`
|
||||
|
||||
```groovy
|
||||
wpi.maven.useLocal = false
|
||||
@@ -28,13 +28,13 @@ Java
|
||||
```groovy
|
||||
plugins {
|
||||
id "java"
|
||||
id "edu.wpi.first.GradleRIO" version "2024.0.0-alpha-1"
|
||||
id "edu.wpi.first.GradleRIO" version "2025.1.1-beta-1"
|
||||
}
|
||||
|
||||
wpi.maven.useLocal = false
|
||||
wpi.maven.useDevelopment = true
|
||||
wpi.versions.wpilibVersion = '2024.+'
|
||||
wpi.versions.wpimathVersion = '2024.+'
|
||||
wpi.versions.wpilibVersion = '2025.+'
|
||||
wpi.versions.wpimathVersion = '2025.+'
|
||||
```
|
||||
|
||||
C++
|
||||
@@ -42,13 +42,13 @@ C++
|
||||
plugins {
|
||||
id "cpp"
|
||||
id "google-test-test-suite"
|
||||
id "edu.wpi.first.GradleRIO" version "2024.0.0-alpha-1"
|
||||
id "edu.wpi.first.GradleRIO" version "2025.1.1-beta-1"
|
||||
}
|
||||
|
||||
wpi.maven.useLocal = false
|
||||
wpi.maven.useDevelopment = true
|
||||
wpi.versions.wpilibVersion = '2024.+'
|
||||
wpi.versions.wpimathVersion = '2024.+'
|
||||
wpi.versions.wpilibVersion = '2025.+'
|
||||
wpi.versions.wpimathVersion = '2025.+'
|
||||
```
|
||||
|
||||
### Development Build Documentation
|
||||
@@ -64,7 +64,7 @@ Java
|
||||
```groovy
|
||||
plugins {
|
||||
id "java"
|
||||
id "edu.wpi.first.GradleRIO" version "2024.0.0-alpha-1"
|
||||
id "edu.wpi.first.GradleRIO" version "2025.1.1-beta-1"
|
||||
}
|
||||
|
||||
wpi.maven.useLocal = false
|
||||
@@ -78,7 +78,7 @@ C++
|
||||
plugins {
|
||||
id "cpp"
|
||||
id "google-test-test-suite"
|
||||
id "edu.wpi.first.GradleRIO" version "2024.0.0-alpha-1"
|
||||
id "edu.wpi.first.GradleRIO" version "2025.1.1-beta-1"
|
||||
}
|
||||
|
||||
wpi.maven.useLocal = false
|
||||
|
||||
25
README-Bazel.md
Normal file
25
README-Bazel.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# WPILib Bazel Support
|
||||
|
||||
WPILib is normally built with Gradle, but [Bazel](https://www.bazel.build/) can also be used to increase development speed due to the superior caching ability and the ability to use remote caching and remote execution (on select platforms)
|
||||
|
||||
|
||||
## Prerequisites
|
||||
- Install [Bazelisk](https://github.com/bazelbuild/bazelisk/releases) and add it to your path. Bazelisk is a wrapper that will download the correct version of bazel specified in the repository. Note: You can alias/rename the binary to `bazel` if you want to keep the familiar `bazel build` vs `bazelisk build` syntax.
|
||||
|
||||
## Building
|
||||
To build the entire repository, simply run `bazel build //...`. To run all of the unit tests, run `bazel test //...`
|
||||
Other examples:
|
||||
- `bazel build //wpimath/...` - Builds every target in the wpimath folder
|
||||
- `bazel test //wpiutil:wpiutil-cpp-test` - Runs only the cpp test target in the wpiutil folder
|
||||
- `bazel coverage //wpiutil/...` - (*Nix only) - Runs a code coverage report for both C++ and Java on all the targets under wpiutil
|
||||
|
||||
## User settings
|
||||
When invoking bazel, it will check if `user.bazelrc` exists for additional, user specified flags. You can use these settings to do things like always ignore buildin a specific folder, or limiting the CPU/RAM usage during a build.
|
||||
Examples:
|
||||
- `build --build_tag_filters=-wpi-example` - Do not build any targets tagged with `wpi-example` (Currently all of the targets in wpilibcExamples and wpilibjExamples contain this tag)
|
||||
- `build -c opt` - Always build optimized targets. The default compiler flags were chosen to build as fast as possible, and thus don't contain many optimizations
|
||||
- `build -k` - `-k` is analogous to the MAKE flag `--keep-going`, so the build will not stop on the first error.
|
||||
- ```
|
||||
build --local_ram_resources=HOST_RAM*.5 # Don't use more than half my RAM when building
|
||||
build --local_cpu_resources=HOST_CPUS-1 # Leave one core alone
|
||||
```
|
||||
@@ -66,6 +66,8 @@ The following build options are available:
|
||||
* This option will build the HAL and wpilibc/j during the build. The HAL is the simulation HAL, unless the external HAL options are used. The CMake build has no capability to build for the roboRIO.
|
||||
* `WITH_WPIMATH` (ON Default)
|
||||
* This option will build the wpimath library. This option must be on to build wpilib.
|
||||
* `WITH_PROTOBUF` (ON Default)
|
||||
* This option will build with the protobuf library.
|
||||
* `WITH_WPIUNITS` (`WITH_JAVA` Default)
|
||||
* This option will build the wpiunits library. This option must be on to build the Java wpimath library and requires `WITH_JAVA` to also be on.
|
||||
* `OPENCV_JAVA_INSTALL_DIR`
|
||||
@@ -17,6 +17,7 @@ Welcome to the WPILib project. This repository contains the HAL, WPILibJ, and WP
|
||||
- [Custom toolchain location](#custom-toolchain-location)
|
||||
- [Formatting/Linting](#formattinglinting)
|
||||
- [CMake](#cmake)
|
||||
- [Bazel](#bazel)
|
||||
- [Running examples in simulation](#running-examples-in-simulation)
|
||||
- [Publishing](#publishing)
|
||||
- [Structure and Organization](#structure-and-organization)
|
||||
@@ -149,7 +150,11 @@ Several files within WPILib are generated using Jinja. If a PR is opened that mo
|
||||
|
||||
### CMake
|
||||
|
||||
CMake is also supported for building. See [README-CMAKE.md](README-CMAKE.md).
|
||||
CMake is also supported for building. See [README-CMake.md](README-CMake.md).
|
||||
|
||||
### Bazel
|
||||
|
||||
Bazel is also supported for building. See [README-Bazel.md](README-Bazel.md).
|
||||
|
||||
## Running examples in simulation
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ Portable File Dialogs wpigui/src/main/native/include/portable-file-dialogs.h
|
||||
V8 export-template wpiutil/src/main/native/include/wpi/SymbolExports.h
|
||||
GCEM wpimath/src/main/native/thirdparty/gcem/include/
|
||||
Sleipnir wpimath/src/main/native/thirdparty/sleipnir
|
||||
Debugging wpiutil/src/main/native/thirdparty/debugging
|
||||
|
||||
==============================================================================
|
||||
Google Test License
|
||||
@@ -1224,3 +1225,29 @@ Redistribution and use in source and binary forms, with or without modification,
|
||||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
=================
|
||||
Debugging License
|
||||
=================
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021-2022 René Ferdinand Rivera Morell
|
||||
Copyright (c) 2018 Isabella Muerte
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
100
WORKSPACE
Normal file
100
WORKSPACE
Normal file
@@ -0,0 +1,100 @@
|
||||
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
|
||||
|
||||
# Download Extra java rules
|
||||
http_archive(
|
||||
name = "rules_jvm_external",
|
||||
sha256 = "08ea921df02ffe9924123b0686dc04fd0ff875710bfadb7ad42badb931b0fd50",
|
||||
strip_prefix = "rules_jvm_external-6.1",
|
||||
url = "https://github.com/bazelbuild/rules_jvm_external/releases/download/6.1/rules_jvm_external-6.1.tar.gz",
|
||||
)
|
||||
|
||||
load("@rules_jvm_external//:repositories.bzl", "rules_jvm_external_deps")
|
||||
|
||||
rules_jvm_external_deps()
|
||||
|
||||
load("@rules_jvm_external//:defs.bzl", "maven_install")
|
||||
|
||||
maven_artifacts = [
|
||||
"org.ejml:ejml-simple:0.43.1",
|
||||
"com.fasterxml.jackson.core:jackson-annotations:2.15.2",
|
||||
"com.fasterxml.jackson.core:jackson-core:2.15.2",
|
||||
"com.fasterxml.jackson.core:jackson-databind:2.15.2",
|
||||
"us.hebi.quickbuf:quickbuf-runtime:1.3.3",
|
||||
"com.google.code.gson:gson:2.10.1",
|
||||
]
|
||||
|
||||
maven_install(
|
||||
name = "maven",
|
||||
artifacts = maven_artifacts,
|
||||
repositories = [
|
||||
"https://repo1.maven.org/maven2",
|
||||
"https://frcmaven.wpi.edu/artifactory/release/",
|
||||
],
|
||||
)
|
||||
|
||||
# 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",
|
||||
)
|
||||
|
||||
load("@rules_bzlmodrio_toolchains//:maven_deps.bzl", "setup_legacy_setup_toolchains_dependencies")
|
||||
|
||||
setup_legacy_setup_toolchains_dependencies()
|
||||
|
||||
load("@rules_bzlmodrio_toolchains//toolchains:load_toolchains.bzl", "load_toolchains")
|
||||
|
||||
load_toolchains()
|
||||
|
||||
#
|
||||
http_archive(
|
||||
name = "rules_bzlmodrio_jdk",
|
||||
sha256 = "a00d5fa971fbcad8a17b1968cdc5350688397035e90b0cb94e040d375ecd97b4",
|
||||
url = "https://github.com/wpilibsuite/rules_bzlmodRio_jdk/releases/download/17.0.8.1-1/rules_bzlmodRio_jdk-17.0.8.1-1.tar.gz",
|
||||
)
|
||||
|
||||
load("@rules_bzlmodrio_jdk//:maven_deps.bzl", "setup_legacy_setup_jdk_dependencies")
|
||||
|
||||
setup_legacy_setup_jdk_dependencies()
|
||||
|
||||
register_toolchains(
|
||||
"@local_roborio//:macos",
|
||||
"@local_roborio//:linux",
|
||||
"@local_roborio//:windows",
|
||||
"@local_raspi_32//:macos",
|
||||
"@local_raspi_32//:linux",
|
||||
"@local_raspi_32//:windows",
|
||||
"@local_bullseye_32//:macos",
|
||||
"@local_bullseye_32//:linux",
|
||||
"@local_bullseye_32//:windows",
|
||||
"@local_bullseye_64//:macos",
|
||||
"@local_bullseye_64//:linux",
|
||||
"@local_bullseye_64//:windows",
|
||||
)
|
||||
|
||||
setup_legacy_setup_jdk_dependencies()
|
||||
|
||||
http_archive(
|
||||
name = "bzlmodrio-ni",
|
||||
sha256 = "197fceac88bf44fb8427d5e000b0083118d3346172dd2ad31eccf83a5e61b3ce",
|
||||
url = "https://github.com/wpilibsuite/bzlmodRio-ni/releases/download/2025.0.0/bzlmodRio-ni-2025.0.0.tar.gz",
|
||||
)
|
||||
|
||||
load("@bzlmodrio-ni//:maven_cpp_deps.bzl", "setup_legacy_bzlmodrio_ni_cpp_dependencies")
|
||||
|
||||
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",
|
||||
)
|
||||
|
||||
load("@bzlmodrio-opencv//:maven_cpp_deps.bzl", "setup_legacy_bzlmodrio_opencv_cpp_dependencies")
|
||||
|
||||
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()
|
||||
@@ -297,7 +297,7 @@ public class AprilTagDetector implements AutoCloseable {
|
||||
* @return Results (array of AprilTagDetection)
|
||||
*/
|
||||
public AprilTagDetection[] detect(Mat img) {
|
||||
return AprilTagJNI.detect(m_native, img.cols(), img.rows(), img.cols(), img.dataAddr());
|
||||
return AprilTagJNI.detect(m_native, img.cols(), img.rows(), (int) img.step1(), img.dataAddr());
|
||||
}
|
||||
|
||||
private long m_native;
|
||||
|
||||
@@ -130,7 +130,7 @@ void AprilTagDetector::RemoveFamily(std::string_view fam) {
|
||||
apriltag_detector_remove_family(
|
||||
static_cast<apriltag_detector_t*>(m_impl),
|
||||
static_cast<apriltag_family_t*>(it->second));
|
||||
DestroyFamily(it->getKey(), it->second);
|
||||
DestroyFamily(it->first, it->second);
|
||||
m_families.erase(it);
|
||||
}
|
||||
}
|
||||
@@ -158,7 +158,7 @@ void AprilTagDetector::Destroy() {
|
||||
|
||||
void AprilTagDetector::DestroyFamilies() {
|
||||
for (auto&& entry : m_families) {
|
||||
DestroyFamily(entry.getKey(), entry.second);
|
||||
DestroyFamily(entry.first, entry.second);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -131,6 +131,34 @@ class AprilTagDetectorTest {
|
||||
return image;
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDecodeCropped() {
|
||||
detector.addFamily("tag16h5");
|
||||
detector.addFamily("tag36h11");
|
||||
|
||||
Mat image;
|
||||
try {
|
||||
image = loadImage("tag1_640_480.jpg");
|
||||
} catch (IOException ex) {
|
||||
fail(ex);
|
||||
return;
|
||||
}
|
||||
|
||||
// Pre-knowledge -- the tag is within this ROI of this particular test image
|
||||
var cropped = image.submat(100, 400, 220, 570);
|
||||
|
||||
try {
|
||||
AprilTagDetection[] results = detector.detect(cropped);
|
||||
assertEquals(1, results.length);
|
||||
assertEquals("tag36h11", results[0].getFamily());
|
||||
assertEquals(1, results[0].getId());
|
||||
assertEquals(0, results[0].getHamming());
|
||||
} finally {
|
||||
cropped.release();
|
||||
image.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDecodeAndPose() {
|
||||
detector.addFamily("tag16h5");
|
||||
|
||||
@@ -18,10 +18,9 @@ plugins {
|
||||
id 'idea'
|
||||
id 'visual-studio'
|
||||
id 'net.ltgt.errorprone' version '3.1.0' apply false
|
||||
id 'com.github.johnrengelman.shadow' version '8.1.1' apply false
|
||||
id 'com.gradleup.shadow' version '8.3.4' apply false
|
||||
id 'com.diffplug.spotless' version '6.20.0' apply false
|
||||
id 'com.github.spotbugs' version '6.0.2' apply false
|
||||
id 'com.google.protobuf' version '0.9.3' apply false
|
||||
}
|
||||
|
||||
wpilibVersioning.buildServerMode = project.hasProperty('buildServer')
|
||||
@@ -171,5 +170,5 @@ ext.getCurrentArch = {
|
||||
}
|
||||
|
||||
wrapper {
|
||||
gradleVersion = '8.5'
|
||||
gradleVersion = '8.11'
|
||||
}
|
||||
|
||||
@@ -9,5 +9,5 @@ repositories {
|
||||
}
|
||||
}
|
||||
dependencies {
|
||||
implementation "edu.wpi.first:native-utils:2025.3.0"
|
||||
implementation "edu.wpi.first:native-utils:2025.9.0"
|
||||
}
|
||||
|
||||
32
cameraserver/BUILD.bazel
Normal file
32
cameraserver/BUILD.bazel
Normal file
@@ -0,0 +1,32 @@
|
||||
load("@rules_cc//cc:defs.bzl", "cc_binary")
|
||||
load("@rules_java//java:defs.bzl", "java_binary", "java_library")
|
||||
|
||||
java_library(
|
||||
name = "cameraserver-java",
|
||||
srcs = glob(["src/main/java/**/*.java"]),
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//cscore:cscore-java",
|
||||
"//hal:hal-java",
|
||||
"//ntcore:networktables-java",
|
||||
"//wpimath:wpimath-java",
|
||||
"//wpinet:wpinet-java",
|
||||
"//wpiutil:wpiutil-java",
|
||||
"@bzlmodrio-opencv//libraries/java/opencv",
|
||||
],
|
||||
)
|
||||
|
||||
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/cameraserver/DevMain.java"],
|
||||
main_class = "edu.wpi.first.cameraserver.DevMain",
|
||||
deps = [
|
||||
],
|
||||
)
|
||||
@@ -30,19 +30,6 @@ apply from: "${rootDir}/shared/opencv.gradle"
|
||||
|
||||
nativeUtils.exportsConfigs {
|
||||
cameraserver {
|
||||
x64ExcludeSymbols = [
|
||||
'_CT??_R0?AV_System_error',
|
||||
'_CT??_R0?AVexception',
|
||||
'_CT??_R0?AVfailure',
|
||||
'_CT??_R0?AVruntime_error',
|
||||
'_CT??_R0?AVsystem_error',
|
||||
'_CTA5?AVfailure',
|
||||
'_TI5?AVfailure',
|
||||
'_CT??_R0?AVout_of_range',
|
||||
'_CTA3?AVout_of_range',
|
||||
'_TI3?AVout_of_range',
|
||||
'_CT??_R0?AVbad_cast'
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
16
cameraserver/multiCameraServer/BUILD.bazel
Normal file
16
cameraserver/multiCameraServer/BUILD.bazel
Normal file
@@ -0,0 +1,16 @@
|
||||
load("@rules_java//java:defs.bzl", "java_binary")
|
||||
|
||||
java_binary(
|
||||
name = "multiCameraServer-java",
|
||||
srcs = ["src/main/java/edu/wpi/Main.java"],
|
||||
main_class = "edu.wpi.Main",
|
||||
deps = [
|
||||
"//cameraserver:cameraserver-java",
|
||||
"//cscore:cscore-java",
|
||||
"//hal:hal-java",
|
||||
"//ntcore:networktables-java",
|
||||
"//wpimath:wpimath-java",
|
||||
"//wpiutil:wpiutil-java",
|
||||
"@maven//:com_google_code_gson_gson",
|
||||
],
|
||||
)
|
||||
@@ -22,7 +22,7 @@ application {
|
||||
mainClass = 'edu.wpi.Main'
|
||||
}
|
||||
|
||||
apply plugin: 'com.github.johnrengelman.shadow'
|
||||
apply plugin: 'com.gradleup.shadow'
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
|
||||
@@ -22,7 +22,7 @@ class DefaultCameraServerShared : public frc::CameraServerShared {
|
||||
void ReportDriverStationErrorV(fmt::string_view format,
|
||||
fmt::format_args args) override {}
|
||||
std::pair<std::thread::id, bool> GetRobotMainThreadId() const override {
|
||||
return std::make_pair(std::thread::id(), false);
|
||||
return std::pair{std::thread::id(), false};
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
@@ -9,10 +9,10 @@
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include <wpi/deprecated.h>
|
||||
|
||||
#include "cscore.h"
|
||||
#include "cscore_cv.h"
|
||||
|
||||
namespace frc {
|
||||
@@ -130,7 +130,9 @@ class CameraServer {
|
||||
*/
|
||||
template <typename T>
|
||||
[[deprecated("Call StartAutomaticCapture with a HttpCamera instead.")]]
|
||||
static cs::AxisCamera AddAxisCamera(std::initializer_list<T> hosts);
|
||||
static cs::AxisCamera AddAxisCamera(std::initializer_list<T> hosts) {
|
||||
return AddAxisCamera("Axis Camera", hosts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an Axis IP camera.
|
||||
@@ -185,7 +187,14 @@ class CameraServer {
|
||||
template <typename T>
|
||||
[[deprecated("Call StartAutomaticCapture with a HttpCamera instead.")]]
|
||||
static cs::AxisCamera AddAxisCamera(std::string_view name,
|
||||
std::initializer_list<T> hosts);
|
||||
std::initializer_list<T> hosts) {
|
||||
std::vector<std::string> vec;
|
||||
vec.reserve(hosts.size());
|
||||
for (const auto& host : hosts) {
|
||||
vec.emplace_back(host);
|
||||
}
|
||||
return AddAxisCamera(name, vec);
|
||||
}
|
||||
WPI_UNIGNORE_DEPRECATED
|
||||
|
||||
/**
|
||||
@@ -316,5 +325,3 @@ class CameraServer {
|
||||
};
|
||||
|
||||
} // namespace frc
|
||||
|
||||
#include "cameraserver/CameraServer.inc"
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "cameraserver/CameraServer.h"
|
||||
|
||||
namespace frc {
|
||||
|
||||
WPI_IGNORE_DEPRECATED
|
||||
template <typename T>
|
||||
inline cs::AxisCamera CameraServer::AddAxisCamera(
|
||||
std::initializer_list<T> hosts) {
|
||||
return AddAxisCamera("Axis Camera", hosts);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline cs::AxisCamera CameraServer::AddAxisCamera(
|
||||
std::string_view name, std::initializer_list<T> hosts) {
|
||||
std::vector<std::string> vec;
|
||||
vec.reserve(hosts.size());
|
||||
for (const auto& host : hosts) {
|
||||
vec.emplace_back(host);
|
||||
}
|
||||
return AddAxisCamera(name, vec);
|
||||
}
|
||||
WPI_UNIGNORE_DEPRECATED
|
||||
|
||||
} // namespace frc
|
||||
@@ -8,7 +8,6 @@
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
#include "cscore.h"
|
||||
#include "cscore_cv.h"
|
||||
#include "vision/VisionPipeline.h"
|
||||
|
||||
@@ -81,17 +80,35 @@ class VisionRunnerBase {
|
||||
template <typename T>
|
||||
class VisionRunner : public VisionRunnerBase {
|
||||
public:
|
||||
/**
|
||||
* Creates a new vision runner. It will take images from the {@code
|
||||
* videoSource}, send them to the {@code pipeline}, and call the {@code
|
||||
* listener} when the pipeline has finished to alert user code when it is safe
|
||||
* to access the pipeline's outputs.
|
||||
*
|
||||
* @param videoSource The video source to use to supply images for the
|
||||
* pipeline
|
||||
* @param pipeline The vision pipeline to run
|
||||
* @param listener A function to call after the pipeline has finished
|
||||
* running
|
||||
*/
|
||||
VisionRunner(cs::VideoSource videoSource, T* pipeline,
|
||||
std::function<void(T&)> listener);
|
||||
std::function<void(T&)> listener)
|
||||
: VisionRunnerBase(videoSource),
|
||||
m_pipeline(pipeline),
|
||||
m_listener(listener) {}
|
||||
|
||||
virtual ~VisionRunner() = default;
|
||||
|
||||
protected:
|
||||
void DoProcess(cv::Mat& image) override;
|
||||
void DoProcess(cv::Mat& image) override {
|
||||
m_pipeline->Process(image);
|
||||
m_listener(*m_pipeline);
|
||||
}
|
||||
|
||||
private:
|
||||
T* m_pipeline;
|
||||
std::function<void(T&)> m_listener;
|
||||
};
|
||||
} // namespace frc
|
||||
|
||||
#include "VisionRunner.inc"
|
||||
} // namespace frc
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "vision/VisionRunner.h"
|
||||
|
||||
namespace frc {
|
||||
|
||||
/**
|
||||
* Creates a new vision runner. It will take images from the {@code
|
||||
* videoSource}, send them to the {@code pipeline}, and call the {@code
|
||||
* listener} when the pipeline has finished to alert user code when it is safe
|
||||
* to access the pipeline's outputs.
|
||||
*
|
||||
* @param videoSource The video source to use to supply images for the pipeline
|
||||
* @param pipeline The vision pipeline to run
|
||||
* @param listener A function to call after the pipeline has finished running
|
||||
*/
|
||||
template <typename T>
|
||||
VisionRunner<T>::VisionRunner(cs::VideoSource videoSource, T* pipeline,
|
||||
std::function<void(T&)> listener)
|
||||
: VisionRunnerBase(videoSource),
|
||||
m_pipeline(pipeline),
|
||||
m_listener(listener) {}
|
||||
|
||||
template <typename T>
|
||||
void VisionRunner<T>::DoProcess(cv::Mat& image) {
|
||||
m_pipeline->Process(image);
|
||||
m_listener(*m_pipeline);
|
||||
}
|
||||
|
||||
} // namespace frc
|
||||
144
cmake/modules/AddDoxygenDocs.cmake
Normal file
144
cmake/modules/AddDoxygenDocs.cmake
Normal file
@@ -0,0 +1,144 @@
|
||||
macro(add_doxygen_docs)
|
||||
set(dirs
|
||||
apriltag
|
||||
cameraserver
|
||||
cscore
|
||||
fieldImages
|
||||
hal
|
||||
ntcore
|
||||
romiVendordep
|
||||
wpilibc
|
||||
wpilibNewCommands
|
||||
wpimath
|
||||
wpinet
|
||||
wpiutil
|
||||
xrpVendordep
|
||||
)
|
||||
foreach(dir ${dirs})
|
||||
list(APPEND docs_dirs ${dir}/src/main/native/include)
|
||||
file(GLOB dirs ${dir}/src/main/native/thirdparty/*/include)
|
||||
list(FILTER dirs EXCLUDE REGEX eigen|protobuf)
|
||||
set(DOXYGEN_EXCLUDE_PATTERNS "*.pb.h" "**/.clang-tidy" "**/.clang-format")
|
||||
|
||||
if(DOCS_WARNINGS_AS_ERRORS)
|
||||
set(DOXYGEN_WARN_AS_ERROR "FAIL_ON_WARNINGS_PRINT")
|
||||
list(FILTER dirs EXCLUDE REGEX fmt|memory|units)
|
||||
list(
|
||||
APPEND
|
||||
DOXYGEN_EXCLUDE_PATTERNS
|
||||
# apriltag
|
||||
"apriltag_pose.h"
|
||||
# llvm
|
||||
"wpi/AlignOf.h"
|
||||
"wpi/Casting.h"
|
||||
"wpi/Chrono.h"
|
||||
"wpi/Compiler.h"
|
||||
"wpi/ConvertUTF.h"
|
||||
"wpi/DenseMap.h"
|
||||
"wpi/DenseMapInfo.h"
|
||||
"wpi/Endian.h"
|
||||
"wpi/EpochTracker.h"
|
||||
"wpi/Errc.h"
|
||||
"wpi/Errno.h"
|
||||
"wpi/ErrorHandling.h"
|
||||
"wpi/bit.h"
|
||||
"wpi/fs.h"
|
||||
"wpi/FunctionExtras.h"
|
||||
"wpi/function_ref.h"
|
||||
"wpi/Hashing.h"
|
||||
"wpi/iterator.h"
|
||||
"wpi/iterator_range.h"
|
||||
"wpi/ManagedStatic.h"
|
||||
"wpi/MapVector.h"
|
||||
"wpi/MathExtras.h"
|
||||
"wpi/MemAlloc.h"
|
||||
"wpi/PointerIntPair.h"
|
||||
"wpi/PointerLikeTypeTraits.h"
|
||||
"wpi/PointerUnion.h"
|
||||
"wpi/raw_os_ostream.h"
|
||||
"wpi/raw_ostream.h"
|
||||
"wpi/SmallPtrSet.h"
|
||||
"wpi/SmallSet.h"
|
||||
"wpi/SmallString.h"
|
||||
"wpi/SmallVector.h"
|
||||
"wpi/StringExtras.h"
|
||||
"wpi/StringMap.h"
|
||||
"wpi/SwapByteOrder.h"
|
||||
"wpi/type_traits.h"
|
||||
"wpi/VersionTuple.h"
|
||||
"wpi/WindowsError.h"
|
||||
# libuv
|
||||
"uv.h"
|
||||
"uv/**"
|
||||
# json
|
||||
"wpi/adl_serializer.h"
|
||||
"wpi/byte_container_with_subtype.h"
|
||||
"wpi/json.h"
|
||||
"wpi/json_fwd.h"
|
||||
"wpi/ordered_map.h"
|
||||
# mpack
|
||||
"wpi/mpack.h"
|
||||
)
|
||||
endif()
|
||||
list(APPEND docs_dirs ${dirs})
|
||||
list(APPEND docs_dirs ${dir}/src/generated/main/native/include)
|
||||
endforeach()
|
||||
|
||||
set(DOXYGEN_CASE_SENSE_NAMES false)
|
||||
set(DOXYGEN_EXTENSION_MAPPING inc=C++ no_extension=C++)
|
||||
set(DOXYGEN_EXTRACT_ALL true)
|
||||
set(DOXYGEN_EXTRACT_STATIC true)
|
||||
set(DOXYGEN_FILE_PATTERNS "*")
|
||||
set(DOXYGEN_FULL_PATH_NAMES true)
|
||||
set(DOXYGEN_FULL_SIDEBAR false)
|
||||
set(DOXYGEN_GENERATE_HTML true)
|
||||
set(DOXYGEN_GENERATE_LATEX false)
|
||||
set(DOXYGEN_GENERATE_TREEVIEW true)
|
||||
set(DOXYGEN_HTML_COLORSTYLE "LIGHT")
|
||||
set(DOXYGEN_HTML_EXTRA_STYLESHEET docs/theme.css)
|
||||
set(DOXYGEN_JAVADOC_AUTOBRIEF true)
|
||||
set(DOXYGEN_ALIASES
|
||||
"effects=\\par <i>Effects:</i>^^"
|
||||
"notes=\\par <i>Notes:</i>^^"
|
||||
"requires=\\par <i>Requires:</i>^^"
|
||||
"requiredbe=\\par <i>Required Behavior:</i>^^"
|
||||
"concept{2}=<a href=\"md_doc_concepts.html#1\">2</a>"
|
||||
"defaultbe=\\par <i>Default Behavior:</i>^^"
|
||||
)
|
||||
set(DOXYGEN_PROJECT_NAME WPILibC++)
|
||||
set(DOXYGEN_PROJECT_NUMBER version)
|
||||
set(DOXYGEN_PROJECT_LOGO wpiutil/src/main/native/resources/wpilib-128.png)
|
||||
set(DOXYGEN_QUIET true)
|
||||
set(DOXYGEN_RECURSIVE true)
|
||||
set(DOXYGEN_STRIP_CODE_COMMENTS false)
|
||||
set(DOXYGEN_STRIP_FROM_PATH ${docs_dirs})
|
||||
set(DOXYGEN_STRIP_FROM_INC_PATH ${docs_dirs})
|
||||
set(DOXYGEN_TIMESTAMP "DATETIME")
|
||||
set(DOXYGEN_USE_MATHJAX true)
|
||||
set(DOXYGEN_WARNINGS false)
|
||||
set(DOXYGEN_WARN_IF_INCOMPLETE_DOC true)
|
||||
set(DOXYGEN_WARN_IF_UNDOCUMENTED false)
|
||||
set(DOXYGEN_WARN_NO_PARAMDOC true)
|
||||
|
||||
set(DOXYGEN_ENABLE_PREPROCESSING true)
|
||||
set(DOXYGEN_MACRO_EXPANSION true)
|
||||
set(DOXYGEN_EXPAND_ONLY_PREDEF true)
|
||||
set(DOXYGEN_PREDEFINED
|
||||
"__cplusplus"
|
||||
"HAL_ENUM(name)=enum name : int32_t"
|
||||
"DOXYGEN"
|
||||
"WPI_NOEXCEPT:=noexcept"
|
||||
"WPI_SFINAE(x):="
|
||||
"WPI_REQUIRES(x):="
|
||||
"WPI_REQUIRES_RET(...):="
|
||||
"WPI_ENABLE_IF(...):="
|
||||
"WPI_CONSTEXPR:=constexpr"
|
||||
"WPI_CONSTEXPR_FNC:=constexpr"
|
||||
"WPI_IMPL_DEFINED(...):=implementation_defined"
|
||||
"WPI_EBO(...):="
|
||||
)
|
||||
execute_process(COMMAND git describe OUTPUT_VARIABLE version)
|
||||
string(SUBSTRING ${version} 1 -1 version)
|
||||
set(DOXYGEN_PROJECT_NUMBER ${version})
|
||||
doxygen_add_docs(docs ${docs_dirs})
|
||||
endmacro()
|
||||
@@ -55,9 +55,4 @@ macro(wpilib_target_warnings target)
|
||||
)
|
||||
target_compile_options(${target} PRIVATE -gz=zlib)
|
||||
endif()
|
||||
|
||||
# Disable std::mutex constexpr constructor on MSVC
|
||||
if(MSVC)
|
||||
target_compile_options(${target} PRIVATE /D_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR)
|
||||
endif()
|
||||
endmacro()
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
function(wpi_protobuf_generate)
|
||||
set(_singleargs PROTOC_OUT_DIR PLUGIN DEPENDENCIES)
|
||||
if(COMMAND target_sources)
|
||||
list(APPEND _singleargs TARGET)
|
||||
endif()
|
||||
set(_multiargs PROTOS)
|
||||
|
||||
cmake_parse_arguments(
|
||||
wpi_protobuf_generate
|
||||
"${_options}"
|
||||
"${_singleargs}"
|
||||
"${_multiargs}"
|
||||
"${ARGN}"
|
||||
)
|
||||
|
||||
if(NOT wpi_protobuf_generate_PROTOS)
|
||||
message(SEND_ERROR "Error: protobuf_generate called without any targets or source files")
|
||||
return()
|
||||
endif()
|
||||
|
||||
if(NOT wpi_protobuf_generate_TARGET)
|
||||
message(SEND_ERROR "Error: wpi_protobuf_generate called without a target")
|
||||
return()
|
||||
endif()
|
||||
|
||||
if(NOT wpi_protobuf_generate_PROTOC_OUT_DIR)
|
||||
message(SEND_ERROR "Error: protobuf_generate called without a protoc out directory")
|
||||
return()
|
||||
endif()
|
||||
|
||||
if(NOT wpi_protobuf_generate_PLUGIN)
|
||||
message(SEND_ERROR "Error: wpi_protobuf_generate called without a plugin")
|
||||
return()
|
||||
endif()
|
||||
|
||||
set(_generate_extensions .pb.h .pb.cc)
|
||||
|
||||
# Create an include path for each file specified
|
||||
foreach(_file ${wpi_protobuf_generate_PROTOS})
|
||||
get_filename_component(_abs_file ${_file} ABSOLUTE)
|
||||
get_filename_component(_abs_dir ${_abs_file} DIRECTORY)
|
||||
list(FIND _protobuf_include_path ${_abs_dir} _contains_already)
|
||||
if(${_contains_already} EQUAL -1)
|
||||
list(APPEND _protobuf_include_path -I ${_abs_dir})
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
set(_generated_srcs_all)
|
||||
foreach(_proto ${wpi_protobuf_generate_PROTOS})
|
||||
get_filename_component(_abs_file ${_proto} ABSOLUTE)
|
||||
get_filename_component(_abs_dir ${_abs_file} DIRECTORY)
|
||||
get_filename_component(_basename ${_proto} NAME_WLE)
|
||||
file(RELATIVE_PATH _rel_dir ${CMAKE_CURRENT_SOURCE_DIR} ${_abs_dir})
|
||||
|
||||
set(_possible_rel_dir)
|
||||
|
||||
set(_generated_srcs)
|
||||
foreach(_ext ${_generate_extensions})
|
||||
list(
|
||||
APPEND
|
||||
_generated_srcs
|
||||
"${wpi_protobuf_generate_PROTOC_OUT_DIR}/${_possible_rel_dir}${_basename}${_ext}"
|
||||
)
|
||||
endforeach()
|
||||
|
||||
list(APPEND _generated_srcs_all ${_generated_srcs})
|
||||
|
||||
set(_comment "Running WPILib protocol buffer compiler on ${_proto}")
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${_generated_srcs}
|
||||
COMMAND protobuf::protoc
|
||||
ARGS
|
||||
--cpp_out ${wpi_protobuf_generate_PROTOC_OUT_DIR} --wpilib_out
|
||||
${wpi_protobuf_generate_PROTOC_OUT_DIR}
|
||||
--plugin=protoc-gen-wpilib=${wpi_protobuf_generate_PLUGIN} ${_protobuf_include_path}
|
||||
${_abs_file}
|
||||
DEPENDS
|
||||
${_abs_file}
|
||||
protobuf::protoc
|
||||
${wpi_protobuf_generate_DEPENDENCIES}
|
||||
${wpi_protobuf_generate_PLUGIN}
|
||||
COMMENT ${_comment}
|
||||
VERBATIM
|
||||
)
|
||||
endforeach()
|
||||
|
||||
set_source_files_properties(${_generated_srcs_all} PROPERTIES GENERATED TRUE)
|
||||
if(wpi_protobuf_generate_TARGET)
|
||||
target_sources(${wpi_protobuf_generate_TARGET} PRIVATE ${_generated_srcs_all})
|
||||
endif()
|
||||
endfunction()
|
||||
@@ -1,4 +0,0 @@
|
||||
set(GCC_COMPILER_VERSION "" CACHE STRING "GCC Compiler version")
|
||||
set(GNU_MACHINE "arm-frc2022-linux-gnueabi" CACHE STRING "GNU compiler triple")
|
||||
set(SOFTFP yes)
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/arm.toolchain.cmake")
|
||||
@@ -1,98 +0,0 @@
|
||||
set(GCC_COMPILER_VERSION "" CACHE STRING "GCC Compiler version")
|
||||
set(GNU_MACHINE "arm-raspbian10-linux-gnueabi" CACHE STRING "GNU compiler triple")
|
||||
|
||||
if(COMMAND toolchain_save_config)
|
||||
return() # prevent recursive call
|
||||
endif()
|
||||
|
||||
set(CMAKE_SYSTEM_NAME Linux)
|
||||
set(CMAKE_SYSTEM_VERSION 1)
|
||||
if(NOT DEFINED CMAKE_SYSTEM_PROCESSOR)
|
||||
set(CMAKE_SYSTEM_PROCESSOR arm)
|
||||
else()
|
||||
#message("CMAKE_SYSTEM_PROCESSOR=${CMAKE_SYSTEM_PROCESSOR}")
|
||||
endif()
|
||||
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/opencv/platforms/linux/gnu.toolchain.cmake")
|
||||
|
||||
if(CMAKE_SYSTEM_PROCESSOR STREQUAL arm AND NOT ARM_IGNORE_FP)
|
||||
set(FLOAT_ABI_SUFFIX "")
|
||||
if(NOT SOFTFP)
|
||||
set(FLOAT_ABI_SUFFIX "hf")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(NOT "x${GCC_COMPILER_VERSION}" STREQUAL "x")
|
||||
set(__GCC_VER_SUFFIX "-${GCC_COMPILER_VERSION}")
|
||||
endif()
|
||||
|
||||
if(NOT DEFINED CMAKE_C_COMPILER)
|
||||
find_program(CMAKE_C_COMPILER NAMES ${GNU_MACHINE}${FLOAT_ABI_SUFFIX}-gcc${__GCC_VER_SUFFIX})
|
||||
else()
|
||||
#message(WARNING "CMAKE_C_COMPILER=${CMAKE_C_COMPILER} is defined")
|
||||
endif()
|
||||
if(NOT DEFINED CMAKE_CXX_COMPILER)
|
||||
find_program(CMAKE_CXX_COMPILER NAMES ${GNU_MACHINE}${FLOAT_ABI_SUFFIX}-g++${__GCC_VER_SUFFIX})
|
||||
else()
|
||||
#message(WARNING "CMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER} is defined")
|
||||
endif()
|
||||
if(NOT DEFINED CMAKE_LINKER)
|
||||
find_program(CMAKE_LINKER NAMES ${GNU_MACHINE}${FLOAT_ABI_SUFFIX}-ld${__GCC_VER_SUFFIX} ${GNU_MACHINE}${FLOAT_ABI_SUFFIX}-ld)
|
||||
else()
|
||||
#message(WARNING "CMAKE_LINKER=${CMAKE_LINKER} is defined")
|
||||
endif()
|
||||
if(NOT DEFINED CMAKE_AR)
|
||||
find_program(CMAKE_AR NAMES ${GNU_MACHINE}${FLOAT_ABI_SUFFIX}-ar${__GCC_VER_SUFFIX} ${GNU_MACHINE}${FLOAT_ABI_SUFFIX}-ar)
|
||||
else()
|
||||
#message(WARNING "CMAKE_AR=${CMAKE_AR} is defined")
|
||||
endif()
|
||||
|
||||
if(NOT DEFINED ARM_LINUX_SYSROOT AND DEFINED GNU_MACHINE)
|
||||
set(ARM_LINUX_SYSROOT /usr/${GNU_MACHINE}${FLOAT_ABI_SUFFIX})
|
||||
endif()
|
||||
|
||||
if(NOT DEFINED CMAKE_CXX_FLAGS)
|
||||
set(CMAKE_CXX_FLAGS "" CACHE INTERNAL "")
|
||||
set(CMAKE_C_FLAGS "" CACHE INTERNAL "")
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "" CACHE INTERNAL "")
|
||||
set(CMAKE_MODULE_LINKER_FLAGS "" CACHE INTERNAL "")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "" CACHE INTERNAL "")
|
||||
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdata-sections -Wa,--noexecstack -fsigned-char -Wno-psabi")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fdata-sections -Wa,--noexecstack -fsigned-char -Wno-psabi")
|
||||
if(CMAKE_SYSTEM_PROCESSOR STREQUAL arm)
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-z,nocopyreloc")
|
||||
endif()
|
||||
if(CMAKE_SYSTEM_PROCESSOR STREQUAL arm)
|
||||
set(ARM_LINKER_FLAGS "-Wl,--fix-cortex-a8 -Wl,--no-undefined -Wl,--gc-sections -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now")
|
||||
elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL aarch64)
|
||||
set(ARM_LINKER_FLAGS "-Wl,--no-undefined -Wl,--gc-sections -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now")
|
||||
endif()
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "${ARM_LINKER_FLAGS} ${CMAKE_SHARED_LINKER_FLAGS}")
|
||||
set(CMAKE_MODULE_LINKER_FLAGS "${ARM_LINKER_FLAGS} ${CMAKE_MODULE_LINKER_FLAGS}")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${ARM_LINKER_FLAGS} ${CMAKE_EXE_LINKER_FLAGS}")
|
||||
else()
|
||||
#message(WARNING "CMAKE_CXX_FLAGS='${CMAKE_CXX_FLAGS}' is defined")
|
||||
endif()
|
||||
|
||||
if(USE_NEON)
|
||||
message(WARNING "You use obsolete variable USE_NEON to enable NEON instruction set. Use -DENABLE_NEON=ON instead." )
|
||||
set(ENABLE_NEON TRUE)
|
||||
elseif(USE_VFPV3)
|
||||
message(WARNING "You use obsolete variable USE_VFPV3 to enable VFPV3 instruction set. Use -DENABLE_VFPV3=ON instead." )
|
||||
set(ENABLE_VFPV3 TRUE)
|
||||
endif()
|
||||
|
||||
set(CMAKE_FIND_ROOT_PATH ${CMAKE_FIND_ROOT_PATH} ${ARM_LINUX_SYSROOT})
|
||||
|
||||
if(EXISTS ${CUDA_TOOLKIT_ROOT_DIR})
|
||||
set(CMAKE_FIND_ROOT_PATH ${CMAKE_FIND_ROOT_PATH} ${CUDA_TOOLKIT_ROOT_DIR})
|
||||
endif()
|
||||
|
||||
set(TOOLCHAIN_CONFIG_VARS ${TOOLCHAIN_CONFIG_VARS}
|
||||
ARM_LINUX_SYSROOT
|
||||
ENABLE_NEON
|
||||
ENABLE_VFPV3
|
||||
CUDA_TOOLKIT_ROOT_DIR
|
||||
)
|
||||
toolchain_save_config()
|
||||
@@ -1,97 +0,0 @@
|
||||
if(COMMAND toolchain_save_config)
|
||||
return() # prevent recursive call
|
||||
endif()
|
||||
|
||||
set(CMAKE_SYSTEM_NAME Linux)
|
||||
set(CMAKE_SYSTEM_VERSION 1)
|
||||
if(NOT DEFINED CMAKE_SYSTEM_PROCESSOR)
|
||||
set(CMAKE_SYSTEM_PROCESSOR arm)
|
||||
else()
|
||||
#message("CMAKE_SYSTEM_PROCESSOR=${CMAKE_SYSTEM_PROCESSOR}")
|
||||
endif()
|
||||
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/gnu.toolchain.cmake")
|
||||
|
||||
if(CMAKE_SYSTEM_PROCESSOR STREQUAL arm AND NOT ARM_IGNORE_FP)
|
||||
set(FLOAT_ABI_SUFFIX "")
|
||||
if(NOT SOFTFP)
|
||||
set(FLOAT_ABI_SUFFIX "hf")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(NOT "x${GCC_COMPILER_VERSION}" STREQUAL "x")
|
||||
set(__GCC_VER_SUFFIX "-${GCC_COMPILER_VERSION}")
|
||||
endif()
|
||||
|
||||
if(NOT DEFINED CMAKE_C_COMPILER)
|
||||
find_program(CMAKE_C_COMPILER NAMES ${GNU_MACHINE}${FLOAT_ABI_SUFFIX}-gcc${__GCC_VER_SUFFIX})
|
||||
else()
|
||||
#message(WARNING "CMAKE_C_COMPILER=${CMAKE_C_COMPILER} is defined")
|
||||
endif()
|
||||
if(NOT DEFINED CMAKE_CXX_COMPILER)
|
||||
find_program(CMAKE_CXX_COMPILER NAMES ${GNU_MACHINE}${FLOAT_ABI_SUFFIX}-g++${__GCC_VER_SUFFIX})
|
||||
else()
|
||||
#message(WARNING "CMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER} is defined")
|
||||
endif()
|
||||
if(NOT DEFINED CMAKE_LINKER)
|
||||
find_program(CMAKE_LINKER NAMES ${GNU_MACHINE}${FLOAT_ABI_SUFFIX}-ld${__GCC_VER_SUFFIX} ${GNU_MACHINE}${FLOAT_ABI_SUFFIX}-ld)
|
||||
else()
|
||||
#message(WARNING "CMAKE_LINKER=${CMAKE_LINKER} is defined")
|
||||
endif()
|
||||
if(NOT DEFINED CMAKE_AR)
|
||||
find_program(CMAKE_AR NAMES ${GNU_MACHINE}${FLOAT_ABI_SUFFIX}-ar${__GCC_VER_SUFFIX} ${GNU_MACHINE}${FLOAT_ABI_SUFFIX}-ar)
|
||||
else()
|
||||
#message(WARNING "CMAKE_AR=${CMAKE_AR} is defined")
|
||||
endif()
|
||||
|
||||
if(NOT DEFINED ARM_LINUX_SYSROOT AND DEFINED GNU_MACHINE)
|
||||
set(ARM_LINUX_SYSROOT /usr/${GNU_MACHINE}${FLOAT_ABI_SUFFIX})
|
||||
endif()
|
||||
|
||||
if(NOT DEFINED CMAKE_CXX_FLAGS)
|
||||
set(CMAKE_CXX_FLAGS "" CACHE INTERNAL "")
|
||||
set(CMAKE_C_FLAGS "" CACHE INTERNAL "")
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "" CACHE INTERNAL "")
|
||||
set(CMAKE_MODULE_LINKER_FLAGS "" CACHE INTERNAL "")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "" CACHE INTERNAL "")
|
||||
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdata-sections -Wa,--noexecstack -fsigned-char -Wno-psabi")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fdata-sections -Wa,--noexecstack -fsigned-char -Wno-psabi")
|
||||
if(CMAKE_SYSTEM_PROCESSOR STREQUAL arm)
|
||||
set(CMAKE_CXX_FLAGS "-mthumb ${CMAKE_CXX_FLAGS}")
|
||||
set(CMAKE_C_FLAGS "-mthumb ${CMAKE_C_FLAGS}")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-z,nocopyreloc")
|
||||
endif()
|
||||
if(CMAKE_SYSTEM_PROCESSOR STREQUAL arm)
|
||||
set(ARM_LINKER_FLAGS "-Wl,--fix-cortex-a8 -Wl,--no-undefined -Wl,--gc-sections -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now")
|
||||
elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL aarch64)
|
||||
set(ARM_LINKER_FLAGS "-Wl,--no-undefined -Wl,--gc-sections -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now")
|
||||
endif()
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "${ARM_LINKER_FLAGS} ${CMAKE_SHARED_LINKER_FLAGS}")
|
||||
set(CMAKE_MODULE_LINKER_FLAGS "${ARM_LINKER_FLAGS} ${CMAKE_MODULE_LINKER_FLAGS}")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${ARM_LINKER_FLAGS} ${CMAKE_EXE_LINKER_FLAGS}")
|
||||
else()
|
||||
#message(WARNING "CMAKE_CXX_FLAGS='${CMAKE_CXX_FLAGS}' is defined")
|
||||
endif()
|
||||
|
||||
if(USE_NEON)
|
||||
message(WARNING "You use obsolete variable USE_NEON to enable NEON instruction set. Use -DENABLE_NEON=ON instead." )
|
||||
set(ENABLE_NEON TRUE)
|
||||
elseif(USE_VFPV3)
|
||||
message(WARNING "You use obsolete variable USE_VFPV3 to enable VFPV3 instruction set. Use -DENABLE_VFPV3=ON instead." )
|
||||
set(ENABLE_VFPV3 TRUE)
|
||||
endif()
|
||||
|
||||
set(CMAKE_FIND_ROOT_PATH ${CMAKE_FIND_ROOT_PATH} ${ARM_LINUX_SYSROOT})
|
||||
|
||||
if(EXISTS ${CUDA_TOOLKIT_ROOT_DIR})
|
||||
set(CMAKE_FIND_ROOT_PATH ${CMAKE_FIND_ROOT_PATH} ${CUDA_TOOLKIT_ROOT_DIR})
|
||||
endif()
|
||||
|
||||
set(TOOLCHAIN_CONFIG_VARS ${TOOLCHAIN_CONFIG_VARS}
|
||||
ARM_LINUX_SYSROOT
|
||||
ENABLE_NEON
|
||||
ENABLE_VFPV3
|
||||
CUDA_TOOLKIT_ROOT_DIR
|
||||
)
|
||||
toolchain_save_config()
|
||||
@@ -1,134 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.11)
|
||||
|
||||
# load settings in case of "try compile"
|
||||
set(TOOLCHAIN_CONFIG_FILE "${WPILIB_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/toolchain.config.cmake")
|
||||
get_property(__IN_TRY_COMPILE GLOBAL PROPERTY IN_TRY_COMPILE)
|
||||
if(__IN_TRY_COMPILE)
|
||||
include("${CMAKE_CURRENT_SOURCE_DIR}/../toolchain.config.cmake" OPTIONAL) # WPILIB_BINARY_DIR is different
|
||||
macro(toolchain_save_config)
|
||||
# nothing
|
||||
endmacro()
|
||||
else()
|
||||
macro(toolchain_save_config)
|
||||
set(__config "#message(\"Load TOOLCHAIN config...\")\n")
|
||||
get_cmake_property(__variableNames VARIABLES)
|
||||
set(__vars_list ${ARGN})
|
||||
list(APPEND __vars_list
|
||||
${TOOLCHAIN_CONFIG_VARS}
|
||||
CMAKE_SYSTEM_NAME
|
||||
CMAKE_SYSTEM_VERSION
|
||||
CMAKE_SYSTEM_PROCESSOR
|
||||
CMAKE_C_COMPILER
|
||||
CMAKE_CXX_COMPILER
|
||||
CMAKE_C_FLAGS
|
||||
CMAKE_CXX_FLAGS
|
||||
CMAKE_SHARED_LINKER_FLAGS
|
||||
CMAKE_MODULE_LINKER_FLAGS
|
||||
CMAKE_EXE_LINKER_FLAGS
|
||||
CMAKE_SKIP_RPATH
|
||||
CMAKE_FIND_ROOT_PATH
|
||||
GCC_COMPILER_VERSION
|
||||
)
|
||||
foreach(__var ${__variableNames})
|
||||
foreach(_v ${__vars_list})
|
||||
if("x${__var}" STREQUAL "x${_v}")
|
||||
if(${__var} MATCHES " ")
|
||||
set(__config "${__config}set(${__var} \"${${__var}}\")\n")
|
||||
else()
|
||||
set(__config "${__config}set(${__var} ${${__var}})\n")
|
||||
endif()
|
||||
endif()
|
||||
endforeach()
|
||||
endforeach()
|
||||
if(EXISTS "${TOOLCHAIN_CONFIG_FILE}")
|
||||
file(READ "${TOOLCHAIN_CONFIG_FILE}" __config_old)
|
||||
endif()
|
||||
if("${__config_old}" STREQUAL "${__config}")
|
||||
# nothing
|
||||
else()
|
||||
#message("Update TOOLCHAIN config: ${__config}")
|
||||
file(WRITE "${TOOLCHAIN_CONFIG_FILE}" "${__config}")
|
||||
endif()
|
||||
unset(__config)
|
||||
unset(__config_old)
|
||||
unset(__vars_list)
|
||||
unset(__variableNames)
|
||||
endmacro()
|
||||
endif() # IN_TRY_COMPILE
|
||||
|
||||
if(NOT CMAKE_FIND_ROOT_PATH_MODE_LIBRARY)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
||||
endif()
|
||||
|
||||
if(NOT CMAKE_FIND_ROOT_PATH_MODE_INCLUDE)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
||||
endif()
|
||||
|
||||
if(NOT CMAKE_FIND_ROOT_PATH_MODE_PACKAGE)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
|
||||
endif()
|
||||
|
||||
if(NOT CMAKE_FIND_ROOT_PATH_MODE_PROGRAM)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
||||
endif()
|
||||
|
||||
macro(__cmake_find_root_save_and_reset)
|
||||
foreach(v
|
||||
CMAKE_FIND_ROOT_PATH_MODE_LIBRARY
|
||||
CMAKE_FIND_ROOT_PATH_MODE_INCLUDE
|
||||
CMAKE_FIND_ROOT_PATH_MODE_PACKAGE
|
||||
CMAKE_FIND_ROOT_PATH_MODE_PROGRAM
|
||||
)
|
||||
set(__save_${v} ${${v}})
|
||||
set(${v} NEVER)
|
||||
endforeach()
|
||||
endmacro()
|
||||
|
||||
macro(__cmake_find_root_restore)
|
||||
foreach(v
|
||||
CMAKE_FIND_ROOT_PATH_MODE_LIBRARY
|
||||
CMAKE_FIND_ROOT_PATH_MODE_INCLUDE
|
||||
CMAKE_FIND_ROOT_PATH_MODE_PACKAGE
|
||||
CMAKE_FIND_ROOT_PATH_MODE_PROGRAM
|
||||
)
|
||||
set(${v} ${__save_${v}})
|
||||
unset(__save_${v})
|
||||
endforeach()
|
||||
endmacro()
|
||||
|
||||
|
||||
# macro to find programs on the host OS
|
||||
macro(find_host_program)
|
||||
__cmake_find_root_save_and_reset()
|
||||
if(CMAKE_HOST_WIN32)
|
||||
SET(WIN32 1)
|
||||
SET(UNIX)
|
||||
elseif(CMAKE_HOST_APPLE)
|
||||
SET(APPLE 1)
|
||||
SET(UNIX)
|
||||
endif()
|
||||
find_program(${ARGN})
|
||||
SET(WIN32)
|
||||
SET(APPLE)
|
||||
SET(UNIX 1)
|
||||
__cmake_find_root_restore()
|
||||
endmacro()
|
||||
|
||||
# macro to find packages on the host OS
|
||||
macro(find_host_package)
|
||||
__cmake_find_root_save_and_reset()
|
||||
if(CMAKE_HOST_WIN32)
|
||||
SET(WIN32 1)
|
||||
SET(UNIX)
|
||||
elseif(CMAKE_HOST_APPLE)
|
||||
SET(APPLE 1)
|
||||
SET(UNIX)
|
||||
endif()
|
||||
find_package(${ARGN})
|
||||
SET(WIN32)
|
||||
SET(APPLE)
|
||||
SET(UNIX 1)
|
||||
__cmake_find_root_restore()
|
||||
endmacro()
|
||||
|
||||
set(CMAKE_SKIP_RPATH TRUE CACHE BOOL "If set, runtime paths are not added when using shared libraries.")
|
||||
21
cscore/BUILD.bazel
Normal file
21
cscore/BUILD.bazel
Normal file
@@ -0,0 +1,21 @@
|
||||
load("@rules_java//java:defs.bzl", "java_binary", "java_library")
|
||||
|
||||
java_library(
|
||||
name = "cscore-java",
|
||||
srcs = glob(["src/main/java/**/*.java"]),
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//wpiutil:wpiutil-java",
|
||||
"@bzlmodrio-opencv//libraries/java/opencv",
|
||||
],
|
||||
)
|
||||
|
||||
java_binary(
|
||||
name = "DevMain-Java",
|
||||
srcs = ["src/dev/java/edu/wpi/first/cscore/DevMain.java"],
|
||||
main_class = "edu.wpi.first.cscore.DevMain",
|
||||
deps = [
|
||||
":cscore-java",
|
||||
"//wpiutil:wpiutil-java",
|
||||
],
|
||||
)
|
||||
@@ -164,19 +164,6 @@ run {
|
||||
|
||||
nativeUtils.exportsConfigs {
|
||||
cscore {
|
||||
x64ExcludeSymbols = [
|
||||
'_CT??_R0?AV_System_error',
|
||||
'_CT??_R0?AVexception',
|
||||
'_CT??_R0?AVfailure',
|
||||
'_CT??_R0?AVruntime_error',
|
||||
'_CT??_R0?AVsystem_error',
|
||||
'_CTA5?AVfailure',
|
||||
'_TI5?AVfailure',
|
||||
'_CT??_R0?AVout_of_range',
|
||||
'_CTA3?AVout_of_range',
|
||||
'_TI3?AVout_of_range',
|
||||
'_CT??_R0?AVbad_cast'
|
||||
]
|
||||
}
|
||||
cscoreJNI {
|
||||
x64SymbolFilter = symbolFilter
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <wpi/MemAlloc.h>
|
||||
#include <wpi/StringExtras.h>
|
||||
#include <wpi/timestamp.h>
|
||||
@@ -294,12 +295,27 @@ bool HttpCameraImpl::DeviceStreamFrame(wpi::raw_istream& is,
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned int contentLength = 0;
|
||||
int width, height;
|
||||
if (auto v = wpi::parse_integer<unsigned int>(contentLengthBuf, 10)) {
|
||||
contentLength = v.value();
|
||||
// We know how big it is! Just get a frame of the right size and read
|
||||
// the data directly into it.
|
||||
unsigned int contentLength = v.value();
|
||||
auto image =
|
||||
AllocImage(VideoMode::PixelFormat::kMJPEG, 0, 0, contentLength);
|
||||
is.read(image->data(), contentLength);
|
||||
if (!m_active || is.has_error()) {
|
||||
return false;
|
||||
}
|
||||
if (!GetJpegSize(image->str(), &width, &height)) {
|
||||
SWARNING("did not receive a JPEG image");
|
||||
PutError("did not receive a JPEG image", wpi::Now());
|
||||
return false;
|
||||
}
|
||||
image->width = width;
|
||||
image->height = height;
|
||||
PutFrame(std::move(image), wpi::Now());
|
||||
} else {
|
||||
// Ugh, no Content-Length? Read the blocks of the JPEG file.
|
||||
int width, height;
|
||||
if (!ReadJpeg(is, imageBuf, &width, &height)) {
|
||||
SWARNING("did not receive a JPEG image");
|
||||
PutError("did not receive a JPEG image", wpi::Now());
|
||||
@@ -307,27 +323,18 @@ bool HttpCameraImpl::DeviceStreamFrame(wpi::raw_istream& is,
|
||||
}
|
||||
PutFrame(VideoMode::PixelFormat::kMJPEG, width, height, imageBuf,
|
||||
wpi::Now());
|
||||
++m_frameCount;
|
||||
return true;
|
||||
}
|
||||
|
||||
// We know how big it is! Just get a frame of the right size and read
|
||||
// the data directly into it.
|
||||
auto image = AllocImage(VideoMode::PixelFormat::kMJPEG, 0, 0, contentLength);
|
||||
is.read(image->data(), contentLength);
|
||||
if (!m_active || is.has_error()) {
|
||||
return false;
|
||||
}
|
||||
int width, height;
|
||||
if (!GetJpegSize(image->str(), &width, &height)) {
|
||||
SWARNING("did not receive a JPEG image");
|
||||
PutError("did not receive a JPEG image", wpi::Now());
|
||||
return false;
|
||||
}
|
||||
image->width = width;
|
||||
image->height = height;
|
||||
PutFrame(std::move(image), wpi::Now());
|
||||
++m_frameCount;
|
||||
|
||||
// update video mode if not set
|
||||
std::scoped_lock lock(m_mutex);
|
||||
if (m_mode.pixelFormat != VideoMode::PixelFormat::kMJPEG ||
|
||||
m_mode.width == 0 || m_mode.height == 0) {
|
||||
m_mode.pixelFormat = VideoMode::PixelFormat::kMJPEG;
|
||||
m_mode.width = width;
|
||||
m_mode.height = height;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -518,6 +525,14 @@ bool HttpCameraImpl::SetVideoMode(const VideoMode& mode, CS_Status* status) {
|
||||
}
|
||||
std::scoped_lock lock(m_mutex);
|
||||
m_mode = mode;
|
||||
m_streamSettings.clear();
|
||||
if (mode.width != 0 && mode.height != 0) {
|
||||
m_streamSettings["resolution"] =
|
||||
fmt::format("{}x{}", mode.width, mode.height);
|
||||
}
|
||||
if (mode.fps != 0) {
|
||||
m_streamSettings["fps"] = fmt::format("{}", mode.fps);
|
||||
}
|
||||
m_streamSettingsUpdated = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include <wpi/SmallString.h>
|
||||
#include <wpi/StringMap.h>
|
||||
#include <wpi/condition_variable.h>
|
||||
#include <wpi/raw_istream.h>
|
||||
@@ -136,10 +135,10 @@ class HttpCameraImpl : public SourceImpl {
|
||||
|
||||
wpi::condition_variable m_sinkEnabledCond;
|
||||
|
||||
wpi::StringMap<wpi::SmallString<16>> m_settings;
|
||||
wpi::StringMap<std::string> m_settings;
|
||||
wpi::condition_variable m_settingsCond;
|
||||
|
||||
wpi::StringMap<wpi::SmallString<16>> m_streamSettings;
|
||||
wpi::StringMap<std::string> m_streamSettings;
|
||||
std::atomic_bool m_streamSettingsUpdated{false};
|
||||
|
||||
wpi::condition_variable m_monitorCond;
|
||||
|
||||
@@ -98,6 +98,9 @@ void Instance::DestroySource(CS_Source handle) {
|
||||
|
||||
void Instance::DestroySink(CS_Sink handle) {
|
||||
if (auto data = m_sinks.Free(handle)) {
|
||||
if (auto source = data->sink->GetSource()) {
|
||||
source->Wakeup();
|
||||
}
|
||||
notifier.NotifySink(data->sink->GetName(), handle, CS_SINK_DESTROYED);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,7 +79,8 @@ Frame SourceImpl::GetCurFrame() {
|
||||
Frame SourceImpl::GetNextFrame() {
|
||||
std::unique_lock lock{m_frameMutex};
|
||||
auto oldTime = m_frame.GetTime();
|
||||
m_frameCv.wait(lock, [=, this] { return m_frame.GetTime() != oldTime; });
|
||||
m_frameCv.wait(
|
||||
lock, [=, this] { return oldTime == 0 || m_frame.GetTime() != oldTime; });
|
||||
return m_frame;
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ class Telemetry::Thread : public wpi::SafeThread {
|
||||
|
||||
int64_t Telemetry::Thread::GetValue(CS_Handle handle, CS_TelemetryKind kind,
|
||||
CS_Status* status) {
|
||||
auto it = m_user.find(std::make_pair(handle, static_cast<int>(kind)));
|
||||
auto it = m_user.find(std::pair{handle, static_cast<int>(kind)});
|
||||
if (it == m_user.end()) {
|
||||
*status = CS_EMPTY_VALUE;
|
||||
return 0;
|
||||
@@ -136,8 +136,8 @@ void Telemetry::RecordSourceBytes(const SourceImpl& source, int quantity) {
|
||||
return;
|
||||
}
|
||||
auto handleData = Instance::GetInstance().FindSource(source);
|
||||
thr->m_current[std::make_pair(Handle{handleData.first, Handle::kSource},
|
||||
static_cast<int>(CS_SOURCE_BYTES_RECEIVED))] +=
|
||||
thr->m_current[std::pair{Handle{handleData.first, Handle::kSource},
|
||||
static_cast<int>(CS_SOURCE_BYTES_RECEIVED)}] +=
|
||||
quantity;
|
||||
}
|
||||
|
||||
@@ -147,7 +147,7 @@ void Telemetry::RecordSourceFrames(const SourceImpl& source, int quantity) {
|
||||
return;
|
||||
}
|
||||
auto handleData = Instance::GetInstance().FindSource(source);
|
||||
thr->m_current[std::make_pair(Handle{handleData.first, Handle::kSource},
|
||||
static_cast<int>(CS_SOURCE_FRAMES_RECEIVED))] +=
|
||||
thr->m_current[std::pair{Handle{handleData.first, Handle::kSource},
|
||||
static_cast<int>(CS_SOURCE_FRAMES_RECEIVED)}] +=
|
||||
quantity;
|
||||
}
|
||||
|
||||
@@ -183,10 +183,10 @@ UnlimitedHandleResource<THandle, TStruct, typeValue, TMutex>::FindIf(F func) {
|
||||
for (size_t i = 0; i < m_structures.size(); i++) {
|
||||
auto& structure = m_structures[i];
|
||||
if (structure != nullptr && func(*structure)) {
|
||||
return std::make_pair(MakeHandle(i), structure);
|
||||
return std::pair{MakeHandle(i), structure};
|
||||
}
|
||||
}
|
||||
return std::make_pair(0, nullptr);
|
||||
return std::pair{0, nullptr};
|
||||
}
|
||||
|
||||
} // namespace cs
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,649 +0,0 @@
|
||||
// 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 CSCORE_CSCORE_OO_INC_
|
||||
#define CSCORE_CSCORE_OO_INC_
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <wpi/deprecated.h>
|
||||
|
||||
#include "cscore_oo.h"
|
||||
|
||||
namespace cs {
|
||||
|
||||
inline std::string VideoProperty::GetName() const {
|
||||
m_status = 0;
|
||||
return GetPropertyName(m_handle, &m_status);
|
||||
}
|
||||
|
||||
inline int VideoProperty::Get() const {
|
||||
m_status = 0;
|
||||
return GetProperty(m_handle, &m_status);
|
||||
}
|
||||
|
||||
inline void VideoProperty::Set(int value) {
|
||||
m_status = 0;
|
||||
SetProperty(m_handle, value, &m_status);
|
||||
}
|
||||
|
||||
inline int VideoProperty::GetMin() const {
|
||||
m_status = 0;
|
||||
return GetPropertyMin(m_handle, &m_status);
|
||||
}
|
||||
|
||||
inline int VideoProperty::GetMax() const {
|
||||
m_status = 0;
|
||||
return GetPropertyMax(m_handle, &m_status);
|
||||
}
|
||||
|
||||
inline int VideoProperty::GetStep() const {
|
||||
m_status = 0;
|
||||
return GetPropertyStep(m_handle, &m_status);
|
||||
}
|
||||
|
||||
inline int VideoProperty::GetDefault() const {
|
||||
m_status = 0;
|
||||
return GetPropertyDefault(m_handle, &m_status);
|
||||
}
|
||||
|
||||
inline std::string VideoProperty::GetString() const {
|
||||
m_status = 0;
|
||||
return GetStringProperty(m_handle, &m_status);
|
||||
}
|
||||
|
||||
inline std::string_view VideoProperty::GetString(
|
||||
wpi::SmallVectorImpl<char>& buf) const {
|
||||
m_status = 0;
|
||||
return GetStringProperty(m_handle, buf, &m_status);
|
||||
}
|
||||
|
||||
inline void VideoProperty::SetString(std::string_view value) {
|
||||
m_status = 0;
|
||||
SetStringProperty(m_handle, value, &m_status);
|
||||
}
|
||||
|
||||
inline std::vector<std::string> VideoProperty::GetChoices() const {
|
||||
m_status = 0;
|
||||
return GetEnumPropertyChoices(m_handle, &m_status);
|
||||
}
|
||||
|
||||
inline VideoProperty::VideoProperty(CS_Property handle) : m_handle(handle) {
|
||||
m_status = 0;
|
||||
if (handle == 0) {
|
||||
m_kind = kNone;
|
||||
} else {
|
||||
m_kind =
|
||||
static_cast<Kind>(static_cast<int>(GetPropertyKind(handle, &m_status)));
|
||||
}
|
||||
}
|
||||
|
||||
inline VideoProperty::VideoProperty(CS_Property handle, Kind kind)
|
||||
: m_handle(handle), m_kind(kind) {}
|
||||
|
||||
inline VideoSource::VideoSource(const VideoSource& source)
|
||||
: m_handle(source.m_handle == 0 ? 0
|
||||
: CopySource(source.m_handle, &m_status)) {}
|
||||
|
||||
inline VideoSource::VideoSource(VideoSource&& other) noexcept : VideoSource() {
|
||||
swap(*this, other);
|
||||
}
|
||||
|
||||
inline VideoSource& VideoSource::operator=(VideoSource other) noexcept {
|
||||
swap(*this, other);
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline VideoSource::~VideoSource() {
|
||||
m_status = 0;
|
||||
if (m_handle != 0) {
|
||||
ReleaseSource(m_handle, &m_status);
|
||||
}
|
||||
}
|
||||
|
||||
inline VideoSource::Kind VideoSource::GetKind() const {
|
||||
m_status = 0;
|
||||
return static_cast<VideoSource::Kind>(GetSourceKind(m_handle, &m_status));
|
||||
}
|
||||
|
||||
inline std::string VideoSource::GetName() const {
|
||||
m_status = 0;
|
||||
return GetSourceName(m_handle, &m_status);
|
||||
}
|
||||
|
||||
inline std::string VideoSource::GetDescription() const {
|
||||
m_status = 0;
|
||||
return GetSourceDescription(m_handle, &m_status);
|
||||
}
|
||||
|
||||
inline uint64_t VideoSource::GetLastFrameTime() const {
|
||||
m_status = 0;
|
||||
return GetSourceLastFrameTime(m_handle, &m_status);
|
||||
}
|
||||
|
||||
inline void VideoSource::SetConnectionStrategy(ConnectionStrategy strategy) {
|
||||
m_status = 0;
|
||||
SetSourceConnectionStrategy(
|
||||
m_handle, static_cast<CS_ConnectionStrategy>(static_cast<int>(strategy)),
|
||||
&m_status);
|
||||
}
|
||||
|
||||
inline bool VideoSource::IsConnected() const {
|
||||
m_status = 0;
|
||||
return IsSourceConnected(m_handle, &m_status);
|
||||
}
|
||||
|
||||
inline bool VideoSource::IsEnabled() const {
|
||||
m_status = 0;
|
||||
return IsSourceEnabled(m_handle, &m_status);
|
||||
}
|
||||
|
||||
inline VideoProperty VideoSource::GetProperty(std::string_view name) {
|
||||
m_status = 0;
|
||||
return VideoProperty{GetSourceProperty(m_handle, name, &m_status)};
|
||||
}
|
||||
|
||||
inline VideoMode VideoSource::GetVideoMode() const {
|
||||
m_status = 0;
|
||||
return GetSourceVideoMode(m_handle, &m_status);
|
||||
}
|
||||
|
||||
inline bool VideoSource::SetVideoMode(const VideoMode& mode) {
|
||||
m_status = 0;
|
||||
return SetSourceVideoMode(m_handle, mode, &m_status);
|
||||
}
|
||||
|
||||
inline bool VideoSource::SetVideoMode(VideoMode::PixelFormat pixelFormat,
|
||||
int width, int height, int fps) {
|
||||
m_status = 0;
|
||||
return SetSourceVideoMode(
|
||||
m_handle, VideoMode{pixelFormat, width, height, fps}, &m_status);
|
||||
}
|
||||
|
||||
inline bool VideoSource::SetPixelFormat(VideoMode::PixelFormat pixelFormat) {
|
||||
m_status = 0;
|
||||
return SetSourcePixelFormat(m_handle, pixelFormat, &m_status);
|
||||
}
|
||||
|
||||
inline bool VideoSource::SetResolution(int width, int height) {
|
||||
m_status = 0;
|
||||
return SetSourceResolution(m_handle, width, height, &m_status);
|
||||
}
|
||||
|
||||
inline bool VideoSource::SetFPS(int fps) {
|
||||
m_status = 0;
|
||||
return SetSourceFPS(m_handle, fps, &m_status);
|
||||
}
|
||||
|
||||
inline bool VideoSource::SetConfigJson(std::string_view config) {
|
||||
m_status = 0;
|
||||
return SetSourceConfigJson(m_handle, config, &m_status);
|
||||
}
|
||||
|
||||
inline bool VideoSource::SetConfigJson(const wpi::json& config) {
|
||||
m_status = 0;
|
||||
return SetSourceConfigJson(m_handle, config, &m_status);
|
||||
}
|
||||
|
||||
inline std::string VideoSource::GetConfigJson() const {
|
||||
m_status = 0;
|
||||
return GetSourceConfigJson(m_handle, &m_status);
|
||||
}
|
||||
|
||||
inline double VideoSource::GetActualFPS() const {
|
||||
m_status = 0;
|
||||
return cs::GetTelemetryAverageValue(m_handle, CS_SOURCE_FRAMES_RECEIVED,
|
||||
&m_status);
|
||||
}
|
||||
|
||||
inline double VideoSource::GetActualDataRate() const {
|
||||
m_status = 0;
|
||||
return cs::GetTelemetryAverageValue(m_handle, CS_SOURCE_BYTES_RECEIVED,
|
||||
&m_status);
|
||||
}
|
||||
|
||||
inline std::vector<VideoMode> VideoSource::EnumerateVideoModes() const {
|
||||
CS_Status status = 0;
|
||||
return EnumerateSourceVideoModes(m_handle, &status);
|
||||
}
|
||||
|
||||
inline void VideoCamera::SetBrightness(int brightness) {
|
||||
m_status = 0;
|
||||
SetCameraBrightness(m_handle, brightness, &m_status);
|
||||
}
|
||||
|
||||
inline int VideoCamera::GetBrightness() {
|
||||
m_status = 0;
|
||||
return GetCameraBrightness(m_handle, &m_status);
|
||||
}
|
||||
|
||||
inline void VideoCamera::SetWhiteBalanceAuto() {
|
||||
m_status = 0;
|
||||
SetCameraWhiteBalanceAuto(m_handle, &m_status);
|
||||
}
|
||||
|
||||
inline void VideoCamera::SetWhiteBalanceHoldCurrent() {
|
||||
m_status = 0;
|
||||
SetCameraWhiteBalanceHoldCurrent(m_handle, &m_status);
|
||||
}
|
||||
|
||||
inline void VideoCamera::SetWhiteBalanceManual(int value) {
|
||||
m_status = 0;
|
||||
SetCameraWhiteBalanceManual(m_handle, value, &m_status);
|
||||
}
|
||||
|
||||
inline void VideoCamera::SetExposureAuto() {
|
||||
m_status = 0;
|
||||
SetCameraExposureAuto(m_handle, &m_status);
|
||||
}
|
||||
|
||||
inline void VideoCamera::SetExposureHoldCurrent() {
|
||||
m_status = 0;
|
||||
SetCameraExposureHoldCurrent(m_handle, &m_status);
|
||||
}
|
||||
|
||||
inline void VideoCamera::SetExposureManual(int value) {
|
||||
m_status = 0;
|
||||
SetCameraExposureManual(m_handle, value, &m_status);
|
||||
}
|
||||
|
||||
inline UsbCamera::UsbCamera(std::string_view name, int dev) {
|
||||
m_handle = CreateUsbCameraDev(name, dev, &m_status);
|
||||
}
|
||||
|
||||
inline UsbCamera::UsbCamera(std::string_view name, std::string_view path) {
|
||||
m_handle = CreateUsbCameraPath(name, path, &m_status);
|
||||
}
|
||||
|
||||
inline std::vector<UsbCameraInfo> UsbCamera::EnumerateUsbCameras() {
|
||||
CS_Status status = 0;
|
||||
return ::cs::EnumerateUsbCameras(&status);
|
||||
}
|
||||
|
||||
inline void UsbCamera::SetPath(std::string_view path) {
|
||||
m_status = 0;
|
||||
return ::cs::SetUsbCameraPath(m_handle, path, &m_status);
|
||||
}
|
||||
|
||||
inline std::string UsbCamera::GetPath() const {
|
||||
m_status = 0;
|
||||
return ::cs::GetUsbCameraPath(m_handle, &m_status);
|
||||
}
|
||||
|
||||
inline UsbCameraInfo UsbCamera::GetInfo() const {
|
||||
m_status = 0;
|
||||
return ::cs::GetUsbCameraInfo(m_handle, &m_status);
|
||||
}
|
||||
|
||||
inline void UsbCamera::SetConnectVerbose(int level) {
|
||||
m_status = 0;
|
||||
SetProperty(GetSourceProperty(m_handle, "connect_verbose", &m_status), level,
|
||||
&m_status);
|
||||
}
|
||||
|
||||
inline HttpCamera::HttpCamera(std::string_view name, std::string_view url,
|
||||
HttpCameraKind kind) {
|
||||
m_handle = CreateHttpCamera(
|
||||
name, url, static_cast<CS_HttpCameraKind>(static_cast<int>(kind)),
|
||||
&m_status);
|
||||
}
|
||||
|
||||
inline HttpCamera::HttpCamera(std::string_view name, const char* url,
|
||||
HttpCameraKind kind) {
|
||||
m_handle = CreateHttpCamera(
|
||||
name, url, static_cast<CS_HttpCameraKind>(static_cast<int>(kind)),
|
||||
&m_status);
|
||||
}
|
||||
|
||||
inline HttpCamera::HttpCamera(std::string_view name, const std::string& url,
|
||||
HttpCameraKind kind)
|
||||
: HttpCamera(name, std::string_view{url}, kind) {}
|
||||
|
||||
inline HttpCamera::HttpCamera(std::string_view name,
|
||||
std::span<const std::string> urls,
|
||||
HttpCameraKind kind) {
|
||||
m_handle = CreateHttpCamera(
|
||||
name, urls, static_cast<CS_HttpCameraKind>(static_cast<int>(kind)),
|
||||
&m_status);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline HttpCamera::HttpCamera(std::string_view name,
|
||||
std::initializer_list<T> urls,
|
||||
HttpCameraKind kind) {
|
||||
std::vector<std::string> vec;
|
||||
vec.reserve(urls.size());
|
||||
for (const auto& url : urls) {
|
||||
vec.emplace_back(url);
|
||||
}
|
||||
m_handle = CreateHttpCamera(
|
||||
name, vec, static_cast<CS_HttpCameraKind>(static_cast<int>(kind)),
|
||||
&m_status);
|
||||
}
|
||||
|
||||
inline HttpCamera::HttpCameraKind HttpCamera::GetHttpCameraKind() const {
|
||||
m_status = 0;
|
||||
return static_cast<HttpCameraKind>(
|
||||
static_cast<int>(::cs::GetHttpCameraKind(m_handle, &m_status)));
|
||||
}
|
||||
|
||||
inline void HttpCamera::SetUrls(std::span<const std::string> urls) {
|
||||
m_status = 0;
|
||||
::cs::SetHttpCameraUrls(m_handle, urls, &m_status);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline void HttpCamera::SetUrls(std::initializer_list<T> urls) {
|
||||
std::vector<std::string> vec;
|
||||
vec.reserve(urls.size());
|
||||
for (const auto& url : urls) {
|
||||
vec.emplace_back(url);
|
||||
}
|
||||
m_status = 0;
|
||||
::cs::SetHttpCameraUrls(m_handle, vec, &m_status);
|
||||
}
|
||||
|
||||
inline std::vector<std::string> HttpCamera::GetUrls() const {
|
||||
m_status = 0;
|
||||
return ::cs::GetHttpCameraUrls(m_handle, &m_status);
|
||||
}
|
||||
WPI_IGNORE_DEPRECATED
|
||||
inline std::vector<std::string> AxisCamera::HostToUrl(
|
||||
std::span<const std::string> hosts) {
|
||||
std::vector<std::string> rv;
|
||||
rv.reserve(hosts.size());
|
||||
for (const auto& host : hosts) {
|
||||
rv.emplace_back(HostToUrl(std::string_view{host}));
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline std::vector<std::string> AxisCamera::HostToUrl(
|
||||
std::initializer_list<T> hosts) {
|
||||
std::vector<std::string> rv;
|
||||
rv.reserve(hosts.size());
|
||||
for (const auto& host : hosts) {
|
||||
rv.emplace_back(HostToUrl(std::string_view{host}));
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
inline AxisCamera::AxisCamera(std::string_view name, std::string_view host)
|
||||
: HttpCamera(name, HostToUrl(host), kAxis) {}
|
||||
|
||||
inline AxisCamera::AxisCamera(std::string_view name, const char* host)
|
||||
: HttpCamera(name, HostToUrl(host), kAxis) {}
|
||||
|
||||
inline AxisCamera::AxisCamera(std::string_view name, const std::string& host)
|
||||
: HttpCamera(name, HostToUrl(std::string_view{host}), kAxis) {}
|
||||
|
||||
inline AxisCamera::AxisCamera(std::string_view name,
|
||||
std::span<const std::string> hosts)
|
||||
: HttpCamera(name, HostToUrl(hosts), kAxis) {}
|
||||
|
||||
template <typename T>
|
||||
inline AxisCamera::AxisCamera(std::string_view name,
|
||||
std::initializer_list<T> hosts)
|
||||
: HttpCamera(name, HostToUrl(hosts), kAxis) {}
|
||||
WPI_UNIGNORE_DEPRECATED
|
||||
|
||||
inline void ImageSource::NotifyError(std::string_view msg) {
|
||||
m_status = 0;
|
||||
NotifySourceError(m_handle, msg, &m_status);
|
||||
}
|
||||
|
||||
inline void ImageSource::SetConnected(bool connected) {
|
||||
m_status = 0;
|
||||
SetSourceConnected(m_handle, connected, &m_status);
|
||||
}
|
||||
|
||||
inline void ImageSource::SetDescription(std::string_view description) {
|
||||
m_status = 0;
|
||||
SetSourceDescription(m_handle, description, &m_status);
|
||||
}
|
||||
|
||||
inline VideoProperty ImageSource::CreateProperty(std::string_view name,
|
||||
VideoProperty::Kind kind,
|
||||
int minimum, int maximum,
|
||||
int step, int defaultValue,
|
||||
int value) {
|
||||
m_status = 0;
|
||||
return VideoProperty{CreateSourceProperty(
|
||||
m_handle, name, static_cast<CS_PropertyKind>(static_cast<int>(kind)),
|
||||
minimum, maximum, step, defaultValue, value, &m_status)};
|
||||
}
|
||||
|
||||
inline VideoProperty ImageSource::CreateIntegerProperty(std::string_view name,
|
||||
int minimum,
|
||||
int maximum, int step,
|
||||
int defaultValue,
|
||||
int value) {
|
||||
m_status = 0;
|
||||
return VideoProperty{CreateSourceProperty(
|
||||
m_handle, name,
|
||||
static_cast<CS_PropertyKind>(
|
||||
static_cast<int>(VideoProperty::Kind::kInteger)),
|
||||
minimum, maximum, step, defaultValue, value, &m_status)};
|
||||
}
|
||||
|
||||
inline VideoProperty ImageSource::CreateBooleanProperty(std::string_view name,
|
||||
bool defaultValue,
|
||||
bool value) {
|
||||
m_status = 0;
|
||||
return VideoProperty{CreateSourceProperty(
|
||||
m_handle, name,
|
||||
static_cast<CS_PropertyKind>(
|
||||
static_cast<int>(VideoProperty::Kind::kBoolean)),
|
||||
0, 1, 1, defaultValue ? 1 : 0, value ? 1 : 0, &m_status)};
|
||||
}
|
||||
|
||||
inline VideoProperty ImageSource::CreateStringProperty(std::string_view name,
|
||||
std::string_view value) {
|
||||
m_status = 0;
|
||||
auto prop = VideoProperty{
|
||||
CreateSourceProperty(m_handle, name,
|
||||
static_cast<CS_PropertyKind>(
|
||||
static_cast<int>(VideoProperty::Kind::kString)),
|
||||
0, 0, 0, 0, 0, &m_status)};
|
||||
prop.SetString(value);
|
||||
return prop;
|
||||
}
|
||||
|
||||
inline void ImageSource::SetEnumPropertyChoices(
|
||||
const VideoProperty& property, std::span<const std::string> choices) {
|
||||
m_status = 0;
|
||||
SetSourceEnumPropertyChoices(m_handle, property.m_handle, choices, &m_status);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline void ImageSource::SetEnumPropertyChoices(
|
||||
const VideoProperty& property, std::initializer_list<T> choices) {
|
||||
std::vector<std::string> vec;
|
||||
vec.reserve(choices.size());
|
||||
for (const auto& choice : choices) {
|
||||
vec.emplace_back(choice);
|
||||
}
|
||||
m_status = 0;
|
||||
SetSourceEnumPropertyChoices(m_handle, property.m_handle, vec, &m_status);
|
||||
}
|
||||
|
||||
inline VideoSink::VideoSink(const VideoSink& sink)
|
||||
: m_handle(sink.m_handle == 0 ? 0 : CopySink(sink.m_handle, &m_status)) {}
|
||||
|
||||
inline VideoSink::VideoSink(VideoSink&& other) noexcept : VideoSink() {
|
||||
swap(*this, other);
|
||||
}
|
||||
|
||||
inline VideoSink& VideoSink::operator=(VideoSink other) noexcept {
|
||||
swap(*this, other);
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline VideoSink::~VideoSink() {
|
||||
m_status = 0;
|
||||
if (m_handle != 0) {
|
||||
ReleaseSink(m_handle, &m_status);
|
||||
}
|
||||
}
|
||||
|
||||
inline VideoSink::Kind VideoSink::GetKind() const {
|
||||
m_status = 0;
|
||||
return static_cast<VideoSink::Kind>(GetSinkKind(m_handle, &m_status));
|
||||
}
|
||||
|
||||
inline std::string VideoSink::GetName() const {
|
||||
m_status = 0;
|
||||
return GetSinkName(m_handle, &m_status);
|
||||
}
|
||||
|
||||
inline std::string VideoSink::GetDescription() const {
|
||||
m_status = 0;
|
||||
return GetSinkDescription(m_handle, &m_status);
|
||||
}
|
||||
|
||||
inline VideoProperty VideoSink::GetProperty(std::string_view name) {
|
||||
m_status = 0;
|
||||
return VideoProperty{GetSinkProperty(m_handle, name, &m_status)};
|
||||
}
|
||||
|
||||
inline void VideoSink::SetSource(VideoSource source) {
|
||||
m_status = 0;
|
||||
if (!source) {
|
||||
SetSinkSource(m_handle, 0, &m_status);
|
||||
} else {
|
||||
SetSinkSource(m_handle, source.m_handle, &m_status);
|
||||
}
|
||||
}
|
||||
|
||||
inline VideoSource VideoSink::GetSource() const {
|
||||
m_status = 0;
|
||||
auto handle = GetSinkSource(m_handle, &m_status);
|
||||
return VideoSource{handle == 0 ? 0 : CopySource(handle, &m_status)};
|
||||
}
|
||||
|
||||
inline VideoProperty VideoSink::GetSourceProperty(std::string_view name) {
|
||||
m_status = 0;
|
||||
return VideoProperty{GetSinkSourceProperty(m_handle, name, &m_status)};
|
||||
}
|
||||
|
||||
inline bool VideoSink::SetConfigJson(std::string_view config) {
|
||||
m_status = 0;
|
||||
return SetSinkConfigJson(m_handle, config, &m_status);
|
||||
}
|
||||
|
||||
inline bool VideoSink::SetConfigJson(const wpi::json& config) {
|
||||
m_status = 0;
|
||||
return SetSinkConfigJson(m_handle, config, &m_status);
|
||||
}
|
||||
|
||||
inline std::string VideoSink::GetConfigJson() const {
|
||||
m_status = 0;
|
||||
return GetSinkConfigJson(m_handle, &m_status);
|
||||
}
|
||||
|
||||
inline MjpegServer::MjpegServer(std::string_view name,
|
||||
std::string_view listenAddress, int port) {
|
||||
m_handle = CreateMjpegServer(name, listenAddress, port, &m_status);
|
||||
}
|
||||
|
||||
inline std::string MjpegServer::GetListenAddress() const {
|
||||
m_status = 0;
|
||||
return cs::GetMjpegServerListenAddress(m_handle, &m_status);
|
||||
}
|
||||
|
||||
inline int MjpegServer::GetPort() const {
|
||||
m_status = 0;
|
||||
return cs::GetMjpegServerPort(m_handle, &m_status);
|
||||
}
|
||||
|
||||
inline void MjpegServer::SetResolution(int width, int height) {
|
||||
m_status = 0;
|
||||
SetProperty(GetSinkProperty(m_handle, "width", &m_status), width, &m_status);
|
||||
SetProperty(GetSinkProperty(m_handle, "height", &m_status), height,
|
||||
&m_status);
|
||||
}
|
||||
|
||||
inline void MjpegServer::SetFPS(int fps) {
|
||||
m_status = 0;
|
||||
SetProperty(GetSinkProperty(m_handle, "fps", &m_status), fps, &m_status);
|
||||
}
|
||||
|
||||
inline void MjpegServer::SetCompression(int quality) {
|
||||
m_status = 0;
|
||||
SetProperty(GetSinkProperty(m_handle, "compression", &m_status), quality,
|
||||
&m_status);
|
||||
}
|
||||
|
||||
inline void MjpegServer::SetDefaultCompression(int quality) {
|
||||
m_status = 0;
|
||||
SetProperty(GetSinkProperty(m_handle, "default_compression", &m_status),
|
||||
quality, &m_status);
|
||||
}
|
||||
|
||||
inline void ImageSink::SetDescription(std::string_view description) {
|
||||
m_status = 0;
|
||||
SetSinkDescription(m_handle, description, &m_status);
|
||||
}
|
||||
|
||||
inline std::string ImageSink::GetError() const {
|
||||
m_status = 0;
|
||||
return GetSinkError(m_handle, &m_status);
|
||||
}
|
||||
|
||||
inline void ImageSink::SetEnabled(bool enabled) {
|
||||
m_status = 0;
|
||||
SetSinkEnabled(m_handle, enabled, &m_status);
|
||||
}
|
||||
|
||||
inline VideoSource VideoEvent::GetSource() const {
|
||||
CS_Status status = 0;
|
||||
return VideoSource{sourceHandle == 0 ? 0 : CopySource(sourceHandle, &status)};
|
||||
}
|
||||
|
||||
inline VideoSink VideoEvent::GetSink() const {
|
||||
CS_Status status = 0;
|
||||
return VideoSink{sinkHandle == 0 ? 0 : CopySink(sinkHandle, &status)};
|
||||
}
|
||||
|
||||
inline VideoProperty VideoEvent::GetProperty() const {
|
||||
return VideoProperty{propertyHandle,
|
||||
static_cast<VideoProperty::Kind>(propertyKind)};
|
||||
}
|
||||
|
||||
inline VideoListener::VideoListener(
|
||||
std::function<void(const VideoEvent& event)> callback, int eventMask,
|
||||
bool immediateNotify) {
|
||||
CS_Status status = 0;
|
||||
m_handle = AddListener(
|
||||
[=](const RawEvent& event) {
|
||||
callback(static_cast<const VideoEvent&>(event));
|
||||
},
|
||||
eventMask, immediateNotify, &status);
|
||||
}
|
||||
|
||||
inline VideoListener::VideoListener(VideoListener&& other) noexcept
|
||||
: VideoListener() {
|
||||
swap(*this, other);
|
||||
}
|
||||
|
||||
inline VideoListener& VideoListener::operator=(VideoListener&& other) noexcept {
|
||||
swap(*this, other);
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline VideoListener::~VideoListener() {
|
||||
CS_Status status = 0;
|
||||
if (m_handle != 0) {
|
||||
RemoveListener(m_handle, &status);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace cs
|
||||
|
||||
#endif // CSCORE_CSCORE_OO_INC_
|
||||
@@ -75,25 +75,6 @@ size_t File::Read(void* buf, uint32_t count) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
File::AsyncId File::AsyncReadBegin(uint32_t len) const {
|
||||
int rv = sftp_async_read_begin(m_handle, len);
|
||||
if (rv < 0) {
|
||||
throw Exception{m_handle->sftp};
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
size_t File::AsyncRead(void* data, uint32_t len, AsyncId id) {
|
||||
auto rv = sftp_async_read(m_handle, data, len, id);
|
||||
if (rv == SSH_ERROR) {
|
||||
throw Exception{ssh_get_error(m_handle->sftp->session)};
|
||||
}
|
||||
if (rv == SSH_AGAIN) {
|
||||
return 0;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
size_t File::Write(std::span<const uint8_t> data) {
|
||||
auto rv = sftp_write(m_handle, data.data(), data.size());
|
||||
if (rv < 0) {
|
||||
|
||||
@@ -47,11 +47,7 @@ class File {
|
||||
void SetNonblocking() { sftp_file_set_nonblocking(m_handle); }
|
||||
void SetBlocking() { sftp_file_set_blocking(m_handle); }
|
||||
|
||||
using AsyncId = uint32_t;
|
||||
|
||||
size_t Read(void* buf, uint32_t count);
|
||||
AsyncId AsyncReadBegin(uint32_t len) const;
|
||||
size_t AsyncRead(void* data, uint32_t len, AsyncId id);
|
||||
size_t Write(std::span<const uint8_t> data);
|
||||
|
||||
void Seek(uint64_t offset);
|
||||
|
||||
@@ -9,7 +9,12 @@ This command builds everything.
|
||||
|
||||
## Simulation
|
||||
|
||||
This command runs the C++ subproject on desktop.
|
||||
This command runs the Java project on desktop.
|
||||
```bash
|
||||
./gradlew developerRobot:run
|
||||
```
|
||||
|
||||
This command runs the C++ project on desktop.
|
||||
```bash
|
||||
./gradlew developerRobot:runCpp
|
||||
```
|
||||
|
||||
@@ -35,7 +35,7 @@ application {
|
||||
mainClass = 'frc.robot.Main'
|
||||
}
|
||||
|
||||
apply plugin: 'com.github.johnrengelman.shadow'
|
||||
apply plugin: 'com.gradleup.shadow'
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
@@ -54,6 +54,9 @@ dependencies {
|
||||
implementation project(':cameraserver')
|
||||
implementation project(':wpilibNewCommands')
|
||||
implementation project(':apriltag')
|
||||
implementation project(':wpiunits')
|
||||
implementation project(':epilogue-runtime')
|
||||
annotationProcessor project(':epilogue-processor')
|
||||
}
|
||||
|
||||
tasks.withType(com.github.spotbugs.snom.SpotBugsTask).configureEach {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
id "org.ysb33r.doxygen" version "1.0.3"
|
||||
id "org.ysb33r.doxygen" version "1.0.4"
|
||||
}
|
||||
|
||||
evaluationDependsOn(':apriltag')
|
||||
|
||||
@@ -11,9 +11,12 @@ import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import javax.annotation.processing.AbstractProcessor;
|
||||
import javax.annotation.processing.RoundEnvironment;
|
||||
import javax.annotation.processing.SupportedAnnotationTypes;
|
||||
@@ -86,16 +89,19 @@ public class AnnotationProcessor extends AbstractProcessor {
|
||||
.getMessager()
|
||||
.printMessage(
|
||||
Diagnostic.Kind.ERROR,
|
||||
"Custom logger classes should have a @CustomLoggerFor annotation",
|
||||
"[EPILOGUE] Custom logger classes should have a @CustomLoggerFor annotation",
|
||||
e);
|
||||
});
|
||||
|
||||
var loggedTypes = getLoggedTypes(roundEnv);
|
||||
|
||||
// Handlers are declared in order of priority. If an element could be logged in more than one
|
||||
// way (eg a class implements both Sendable and StructSerializable), the order of the handlers
|
||||
// in this list will determine how it gets logged.
|
||||
m_handlers =
|
||||
List.of(
|
||||
new LoggableHandler(processingEnv), // prioritize epilogue logging over Sendable
|
||||
new LoggableHandler(
|
||||
processingEnv, loggedTypes), // prioritize epilogue logging over Sendable
|
||||
new ConfiguredLoggerHandler(
|
||||
processingEnv, customLoggers), // then customized logging configs
|
||||
new ArrayHandler(processingEnv),
|
||||
@@ -115,12 +121,39 @@ public class AnnotationProcessor extends AbstractProcessor {
|
||||
.findAny()
|
||||
.ifPresent(
|
||||
epilogue -> {
|
||||
processEpilogue(roundEnv, epilogue);
|
||||
processEpilogue(roundEnv, epilogue, loggedTypes);
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the set of all loggable types in the compilation unit. A type is considered loggable if it
|
||||
* is directly annotated with {@code @Logged} or contains a field or method with a {@code @Logged}
|
||||
* annotation.
|
||||
*
|
||||
* @param roundEnv the compilation round environment
|
||||
* @return the set of all loggable types
|
||||
*/
|
||||
private Set<TypeElement> getLoggedTypes(RoundEnvironment roundEnv) {
|
||||
// Fetches everything annotated with @Logged; classes, methods, values, etc.
|
||||
var annotatedElements = roundEnv.getElementsAnnotatedWith(Logged.class);
|
||||
return Stream.concat(
|
||||
// 1. All type elements (classes, interfaces, or enums) with the @Logged annotation
|
||||
annotatedElements.stream()
|
||||
.filter(e -> e instanceof TypeElement)
|
||||
.map(e -> (TypeElement) e),
|
||||
// 2. All type elements containing a field or method with the @Logged annotation
|
||||
annotatedElements.stream()
|
||||
.filter(e -> e instanceof VariableElement || e instanceof ExecutableElement)
|
||||
.map(Element::getEnclosingElement)
|
||||
.filter(e -> e instanceof TypeElement)
|
||||
.map(e -> (TypeElement) e))
|
||||
.sorted(Comparator.comparing(e -> e.getSimpleName().toString()))
|
||||
.collect(
|
||||
Collectors.toCollection(LinkedHashSet::new)); // Collect to a set to avoid duplicates
|
||||
}
|
||||
|
||||
private boolean validateFields(Set<? extends Element> annotatedElements) {
|
||||
var fields =
|
||||
annotatedElements.stream()
|
||||
@@ -281,7 +314,7 @@ public class AnnotationProcessor extends AbstractProcessor {
|
||||
.getMessager()
|
||||
.printMessage(
|
||||
Diagnostic.Kind.ERROR,
|
||||
"Logger classes must have a public no-argument constructor",
|
||||
"[EPILOGUE] Logger classes must have a public no-argument constructor",
|
||||
annotatedElement);
|
||||
continue;
|
||||
}
|
||||
@@ -303,7 +336,17 @@ public class AnnotationProcessor extends AbstractProcessor {
|
||||
.getMessager()
|
||||
.printMessage(
|
||||
Diagnostic.Kind.ERROR,
|
||||
"Multiple custom loggers detected for type " + targetType,
|
||||
"[EPILOGUE] Multiple custom loggers detected for type " + targetType,
|
||||
annotatedElement);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (annotatedElement instanceof TypeElement t && !t.getTypeParameters().isEmpty()) {
|
||||
processingEnv
|
||||
.getMessager()
|
||||
.printMessage(
|
||||
Diagnostic.Kind.ERROR,
|
||||
"[EPILOGUE] Custom logger classes cannot take generic type arguments",
|
||||
annotatedElement);
|
||||
continue;
|
||||
}
|
||||
@@ -315,7 +358,7 @@ public class AnnotationProcessor extends AbstractProcessor {
|
||||
.getMessager()
|
||||
.printMessage(
|
||||
Diagnostic.Kind.ERROR,
|
||||
"Not a subclass of ClassSpecificLogger<" + targetType + ">",
|
||||
"[EPILOGUE] Not a subclass of ClassSpecificLogger<" + targetType + ">",
|
||||
annotatedElement);
|
||||
continue;
|
||||
}
|
||||
@@ -327,7 +370,8 @@ public class AnnotationProcessor extends AbstractProcessor {
|
||||
return customLoggers;
|
||||
}
|
||||
|
||||
private void processEpilogue(RoundEnvironment roundEnv, TypeElement epilogueAnnotation) {
|
||||
private void processEpilogue(
|
||||
RoundEnvironment roundEnv, TypeElement epilogueAnnotation, Set<TypeElement> loggedTypes) {
|
||||
var annotatedElements = roundEnv.getElementsAnnotatedWith(epilogueAnnotation);
|
||||
|
||||
List<String> loggerClassNames = new ArrayList<>();
|
||||
@@ -345,12 +389,7 @@ public class AnnotationProcessor extends AbstractProcessor {
|
||||
return;
|
||||
}
|
||||
|
||||
var classes =
|
||||
annotatedElements.stream()
|
||||
.filter(e -> e instanceof TypeElement)
|
||||
.map(e -> (TypeElement) e)
|
||||
.toList();
|
||||
for (TypeElement clazz : classes) {
|
||||
for (TypeElement clazz : loggedTypes) {
|
||||
try {
|
||||
warnOfNonLoggableElements(clazz);
|
||||
m_loggerGenerator.writeLoggerFile(clazz);
|
||||
@@ -365,7 +404,7 @@ public class AnnotationProcessor extends AbstractProcessor {
|
||||
.getMessager()
|
||||
.printMessage(
|
||||
Diagnostic.Kind.ERROR,
|
||||
"Could not write logger file for " + clazz.getQualifiedName(),
|
||||
"[EPILOGUE] Could not write logger file for " + clazz.getQualifiedName(),
|
||||
clazz);
|
||||
e.printStackTrace(System.err);
|
||||
}
|
||||
@@ -378,7 +417,7 @@ public class AnnotationProcessor extends AbstractProcessor {
|
||||
|
||||
private void warnOfNonLoggableElements(TypeElement clazz) {
|
||||
var config = clazz.getAnnotation(Logged.class);
|
||||
if (config.strategy() == Logged.Strategy.OPT_IN) {
|
||||
if (config == null || config.strategy() == Logged.Strategy.OPT_IN) {
|
||||
// field and method validations will have already checked everything
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ public class ArrayHandler extends ElementHandler {
|
||||
|
||||
if (m_structHandler.isLoggableType(componentType)) {
|
||||
// Struct arrays need to pass in the struct serializer
|
||||
return "dataLogger.log(\""
|
||||
return "backend.log(\""
|
||||
+ loggedName(element)
|
||||
+ "\", "
|
||||
+ elementAccess(element)
|
||||
@@ -69,7 +69,7 @@ public class ArrayHandler extends ElementHandler {
|
||||
+ ")";
|
||||
} else {
|
||||
// Primitive or string array
|
||||
return "dataLogger.log(\"" + loggedName(element) + "\", " + elementAccess(element) + ")";
|
||||
return "backend.log(\"" + loggedName(element) + "\", " + elementAccess(element) + ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ public class CollectionHandler extends ElementHandler {
|
||||
var componentType = ((DeclaredType) dataType).getTypeArguments().get(0);
|
||||
|
||||
if (m_structHandler.isLoggableType(componentType)) {
|
||||
return "dataLogger.log(\""
|
||||
return "backend.log(\""
|
||||
+ loggedName(element)
|
||||
+ "\", "
|
||||
+ elementAccess(element)
|
||||
@@ -51,7 +51,7 @@ public class CollectionHandler extends ElementHandler {
|
||||
+ m_structHandler.structAccess(componentType)
|
||||
+ ")";
|
||||
} else {
|
||||
return "dataLogger.log(\"" + loggedName(element) + "\", " + elementAccess(element) + ")";
|
||||
return "backend.log(\"" + loggedName(element) + "\", " + elementAccess(element) + ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,17 +22,26 @@ public class ConfiguredLoggerHandler extends ElementHandler {
|
||||
|
||||
@Override
|
||||
public boolean isLoggable(Element element) {
|
||||
return m_customLoggers.containsKey(dataType(element));
|
||||
return m_customLoggers.keySet().stream()
|
||||
.anyMatch(m -> m_processingEnv.getTypeUtils().isAssignable(dataType(element), m));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String logInvocation(Element element) {
|
||||
var dataType = dataType(element);
|
||||
var loggerType = m_customLoggers.get(dataType);
|
||||
var loggerType =
|
||||
m_customLoggers.entrySet().stream()
|
||||
.filter(
|
||||
e -> {
|
||||
return m_processingEnv.getTypeUtils().isAssignable(dataType, e.getKey());
|
||||
})
|
||||
.findFirst()
|
||||
.orElseThrow()
|
||||
.getValue();
|
||||
|
||||
return "Epilogue."
|
||||
+ StringUtils.lowerCamelCase(loggerType.asElement().getSimpleName())
|
||||
+ ".tryUpdate(dataLogger.getSubLogger(\""
|
||||
+ ".tryUpdate(backend.getNested(\""
|
||||
+ loggedName(element)
|
||||
+ "\"), "
|
||||
+ elementAccess(element)
|
||||
|
||||
@@ -6,7 +6,7 @@ package edu.wpi.first.epilogue.processor;
|
||||
|
||||
import edu.wpi.first.epilogue.Logged;
|
||||
import edu.wpi.first.epilogue.logging.ClassSpecificLogger;
|
||||
import edu.wpi.first.epilogue.logging.DataLogger;
|
||||
import edu.wpi.first.epilogue.logging.EpilogueBackend;
|
||||
import javax.annotation.processing.ProcessingEnvironment;
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.ExecutableElement;
|
||||
@@ -61,15 +61,46 @@ public abstract class ElementHandler {
|
||||
* @return the name specified in the {@link Logged @Logged} annotation on the element, if present;
|
||||
* otherwise, the field or method's name with no modifications
|
||||
*/
|
||||
public String loggedName(Element element) {
|
||||
var elementName = element.getSimpleName().toString();
|
||||
var config = element.getAnnotation(Logged.class);
|
||||
public static String loggedName(Element element) {
|
||||
var elementConfig = element.getAnnotation(Logged.class);
|
||||
|
||||
if (config != null && !config.name().isBlank()) {
|
||||
return config.name();
|
||||
} else {
|
||||
return elementName;
|
||||
// Use the name provided on the logged element, if one is present
|
||||
if (elementConfig != null && !elementConfig.name().isBlank()) {
|
||||
return elementConfig.name();
|
||||
}
|
||||
|
||||
var config = elementConfig;
|
||||
|
||||
if (config == null) {
|
||||
// Look up the parent class configuration
|
||||
// We assume one is present, since logged elements should only be found if the enclosing class
|
||||
// is @Logged itself
|
||||
Logged parentConfig = null;
|
||||
for (var parent = element.getEnclosingElement();
|
||||
parent != null;
|
||||
parent = parent.getEnclosingElement()) {
|
||||
parentConfig = parent.getAnnotation(Logged.class);
|
||||
if (parentConfig != null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
config = parentConfig;
|
||||
}
|
||||
|
||||
if (config == null) {
|
||||
// Uh oh
|
||||
throw new IllegalStateException(
|
||||
"Could not generate a name for element "
|
||||
+ element
|
||||
+ " without a @Logged annotation AND without being contained within a class with a @Logged annotation!\n\nOpen an issue at https://github.com/wpilibsuite/allwpilib/issues and include a copy of the file that caused this error.");
|
||||
}
|
||||
|
||||
var elementName = element.getSimpleName().toString();
|
||||
return switch (config.defaultNaming()) {
|
||||
case USE_CODE_NAME -> elementName;
|
||||
case USE_HUMAN_NAME -> StringUtils.toHumanName(elementName);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -96,8 +127,10 @@ public abstract class ElementHandler {
|
||||
|
||||
private static String fieldAccess(VariableElement field) {
|
||||
if (field.getModifiers().contains(Modifier.PRIVATE)) {
|
||||
// (com.example.Foo) $fooField.get(object)
|
||||
return "(" + field.asType() + ") $" + field.getSimpleName() + ".get(object)";
|
||||
// ((com.example.Foo) $fooField.get(object))
|
||||
// Extra parentheses so cast evaluates before appended methods
|
||||
// (e.g. when appending .getAsDouble())
|
||||
return "((" + field.asType() + ") $" + field.getSimpleName() + ".get(object))";
|
||||
} else {
|
||||
// object.fooField
|
||||
return "object." + field.getSimpleName();
|
||||
@@ -126,7 +159,7 @@ public abstract class ElementHandler {
|
||||
/**
|
||||
* Generates a code snippet to place in a generated logger file to log the value of a field or
|
||||
* method. Log invocations are placed in a generated implementation of {@link
|
||||
* ClassSpecificLogger#update(DataLogger, Object)}, with access to the data logger and logged
|
||||
* ClassSpecificLogger#update(EpilogueBackend, Object)}, with access to the backend and logged
|
||||
* object passed to the method call.
|
||||
*
|
||||
* @param element the field or method element to generate the logger call for
|
||||
|
||||
@@ -28,6 +28,6 @@ public class EnumHandler extends ElementHandler {
|
||||
|
||||
@Override
|
||||
public String logInvocation(Element element) {
|
||||
return "dataLogger.log(\"" + loggedName(element) + "\", " + elementAccess(element) + ")";
|
||||
return "backend.log(\"" + loggedName(element) + "\", " + elementAccess(element) + ")";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,6 +55,10 @@ public class EpilogueGenerator {
|
||||
out.println("import static edu.wpi.first.units.Units.Seconds;");
|
||||
out.println();
|
||||
|
||||
out.println("import edu.wpi.first.hal.FRCNetComm;");
|
||||
out.println("import edu.wpi.first.hal.HAL;");
|
||||
out.println();
|
||||
|
||||
loggerClassNames.stream()
|
||||
.sorted()
|
||||
.forEach(
|
||||
@@ -80,6 +84,18 @@ public class EpilogueGenerator {
|
||||
out.println();
|
||||
|
||||
out.println("public final class Epilogue {");
|
||||
|
||||
// Usage reporting
|
||||
out.println(
|
||||
"""
|
||||
static {
|
||||
HAL.report(
|
||||
FRCNetComm.tResourceType.kResourceType_LoggingFramework,
|
||||
FRCNetComm.tInstances.kLoggingFramework_Epilogue
|
||||
);
|
||||
}
|
||||
""");
|
||||
|
||||
out.println(
|
||||
" private static final EpilogueConfiguration config = new EpilogueConfiguration();");
|
||||
out.println();
|
||||
@@ -154,9 +170,9 @@ public class EpilogueGenerator {
|
||||
out.println(
|
||||
" "
|
||||
+ StringUtils.loggerFieldName(mainRobotClass)
|
||||
+ ".tryUpdate(config.dataLogger.getSubLogger(config.root), robot, config.errorHandler);");
|
||||
+ ".tryUpdate(config.backend.getNested(config.root), robot, config.errorHandler);");
|
||||
out.println(
|
||||
" config.dataLogger.log(\"Epilogue/Stats/Last Run\", (System.nanoTime() - start) / 1e6);");
|
||||
" config.backend.log(\"Epilogue/Stats/Last Run\", (System.nanoTime() - start) / 1e6);");
|
||||
out.println(" }");
|
||||
|
||||
out.println();
|
||||
@@ -176,7 +192,7 @@ public class EpilogueGenerator {
|
||||
out.println(" config.loggingPeriod = Seconds.of(robot.getPeriod());");
|
||||
out.println(" }");
|
||||
out.println(" if (config.loggingPeriodOffset == null) {");
|
||||
out.println(" config.loggingPeriodOffset = config.loggingPeriod.divide(2);");
|
||||
out.println(" config.loggingPeriodOffset = config.loggingPeriod.div(2);");
|
||||
out.println(" }");
|
||||
out.println();
|
||||
out.println(" robot.addPeriodic(() -> {");
|
||||
|
||||
@@ -5,39 +5,201 @@
|
||||
package edu.wpi.first.epilogue.processor;
|
||||
|
||||
import edu.wpi.first.epilogue.Logged;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.annotation.processing.ProcessingEnvironment;
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.type.DeclaredType;
|
||||
import javax.lang.model.element.ElementKind;
|
||||
import javax.lang.model.element.ExecutableElement;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.element.VariableElement;
|
||||
import javax.lang.model.type.TypeKind;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
|
||||
/** Handles logging for types annotated with the {@link Logged @Logged} annotation. */
|
||||
public class LoggableHandler extends ElementHandler {
|
||||
protected LoggableHandler(ProcessingEnvironment processingEnv) {
|
||||
private final Set<TypeElement> m_loggedTypes;
|
||||
|
||||
protected LoggableHandler(
|
||||
ProcessingEnvironment processingEnv, Collection<? extends Element> loggedTypes) {
|
||||
super(processingEnv);
|
||||
m_loggedTypes =
|
||||
loggedTypes.stream()
|
||||
.filter(e -> e instanceof TypeElement)
|
||||
.map(e -> (TypeElement) e)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLoggable(Element element) {
|
||||
var dataType = dataType(element);
|
||||
return dataType.getAnnotation(Logged.class) != null
|
||||
|| dataType instanceof DeclaredType decl
|
||||
&& decl.asElement().getAnnotation(Logged.class) != null;
|
||||
return m_processingEnv.getTypeUtils().asElement(dataType(element)) instanceof TypeElement t
|
||||
&& m_loggedTypes.contains(t);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String logInvocation(Element element) {
|
||||
TypeMirror dataType = dataType(element);
|
||||
var reflectedType =
|
||||
var declaredType =
|
||||
m_processingEnv
|
||||
.getElementUtils()
|
||||
.getTypeElement(m_processingEnv.getTypeUtils().erasure(dataType).toString());
|
||||
|
||||
return "Epilogue."
|
||||
+ StringUtils.loggerFieldName(reflectedType)
|
||||
+ ".tryUpdate(dataLogger.getSubLogger(\""
|
||||
+ loggedName(element)
|
||||
+ "\"), "
|
||||
+ elementAccess(element)
|
||||
+ ", Epilogue.getConfig().errorHandler)";
|
||||
// Get the list of known loggable subtypes of the input type. This will include the input type.
|
||||
// These are sorted by their distance from the declared type such that "more concrete" types are
|
||||
// checked first so the instanceof chain doesn't check a really generic type first, even if a
|
||||
// more specific loggable type could be used instead.
|
||||
var loggableSubtypes =
|
||||
m_loggedTypes.stream()
|
||||
.filter(
|
||||
l -> m_processingEnv.getTypeUtils().isAssignable(l.asType(), declaredType.asType()))
|
||||
.sorted(inheritanceComparatorFor(declaredType))
|
||||
.toList();
|
||||
|
||||
int size = loggableSubtypes.size();
|
||||
|
||||
// If there are no known loggable subtypes, return just the single logger call
|
||||
if (size == 1) {
|
||||
return generateLoggerCall(element, declaredType, elementAccess(element));
|
||||
}
|
||||
|
||||
// Otherwise, generate an if-else chain to compare the element with its known loggable subtypes
|
||||
// and implementations. A subclass without a @Logged annotation will be logged at runtime using
|
||||
// the generic logger for whatever the field or method's declared type is.
|
||||
|
||||
String varName = cacheVariableName(element);
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
// Cache the value in a variable so it's only read once
|
||||
builder.append("var %s = %s;\n".formatted(varName, elementAccess(element)));
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
TypeElement type = loggableSubtypes.get(i);
|
||||
String part;
|
||||
|
||||
if (i == 0) {
|
||||
// First invocation, generate an "if" statement
|
||||
part = generateIf(type, element, "if", varName);
|
||||
} else if (i == size - 1) {
|
||||
// Final invocation, generate an "else" statement
|
||||
String loggerCall = generateLoggerCall(element, type, varName);
|
||||
part =
|
||||
" else {\n // Base type %s\n %s;\n}"
|
||||
.formatted(declaredType.getQualifiedName(), loggerCall);
|
||||
} else {
|
||||
// Somewhere in the middle, generate an "else if" statement
|
||||
part = generateIf(type, element, " else if", varName);
|
||||
}
|
||||
|
||||
builder.append(part);
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the name of the cache variable to use for a logged element.
|
||||
*
|
||||
* @param element the logged element
|
||||
* @return the cache variable name
|
||||
*/
|
||||
private static String cacheVariableName(Element element) {
|
||||
// Generate unique names in case a field and a method share the same name
|
||||
if (element instanceof VariableElement) {
|
||||
return "$$%s".formatted(element.getSimpleName().toString());
|
||||
} else if (element instanceof ExecutableElement) {
|
||||
return "__%s".formatted(element.getSimpleName().toString());
|
||||
} else {
|
||||
// Generic fallback (shouldn't get here, since only fields and methods are logged)
|
||||
return element.getSimpleName().toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a comparator for sorting inheritors of a given type by their distance (farthest first)
|
||||
* for use in generating if-else instanceof chains. Inheritors at the same distance from the base
|
||||
* type will be further compared so classes come before interfaces, any any further ties are
|
||||
* broken alphabetically by fully-qualified type names.
|
||||
*
|
||||
* @param declaredType the declared type
|
||||
* @return the comparator
|
||||
*/
|
||||
private Comparator<TypeElement> inheritanceComparatorFor(TypeElement declaredType) {
|
||||
Comparator<TypeElement> byDistance =
|
||||
Comparator.comparingInt(
|
||||
inheritor -> {
|
||||
return inheritanceDistance(inheritor.asType(), declaredType.asType());
|
||||
});
|
||||
|
||||
return byDistance
|
||||
.reversed()
|
||||
.thenComparing(type -> type.getKind() == ElementKind.INTERFACE ? 1 : 0)
|
||||
.thenComparing(type -> type.getQualifiedName().toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an instanceof if or if-else statement that checks the type and logs the element using
|
||||
* the logger for the given type, if they're compatible.
|
||||
*
|
||||
* @param type the type to generate the check for
|
||||
* @param element the element to be logged
|
||||
* @param keyword either "if" or " else if"
|
||||
* @param varName the name of the variable in the "instanceof" check
|
||||
* @return the if or else-if statement
|
||||
*/
|
||||
private String generateIf(TypeElement type, Element element, String keyword, String varName) {
|
||||
String ref = type.getQualifiedName().toString().replace('.', '_');
|
||||
String loggerCall = generateLoggerCall(element, type, ref);
|
||||
|
||||
return "%s (%s instanceof %s %s) {\n %s;\n}"
|
||||
.formatted(keyword, varName, type.getQualifiedName(), ref, loggerCall);
|
||||
}
|
||||
|
||||
private String generateLoggerCall(Element element, TypeElement type, String elementReference) {
|
||||
return ("Epilogue.%s.tryUpdate(backend.getNested(\"%s\"), %s, "
|
||||
+ "Epilogue.getConfig().errorHandler)")
|
||||
.formatted(StringUtils.loggerFieldName(type), loggedName(element), elementReference);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the minimum inheritance distance between two types; that is, how many "extends" or
|
||||
* "implements" clauses are required to get from one to the other.
|
||||
*
|
||||
* @param toCheck the type to check
|
||||
* @param base the base type to check against
|
||||
* @return the inheritance distance
|
||||
*/
|
||||
private int inheritanceDistance(TypeMirror toCheck, TypeMirror base) {
|
||||
var types = m_processingEnv.getTypeUtils();
|
||||
|
||||
if (types.isSameType(toCheck, base)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int distance = 1;
|
||||
var parent = toCheck;
|
||||
TypeElement element = m_processingEnv.getElementUtils().getTypeElement(parent.toString());
|
||||
while (!types.isSameType(parent, base)
|
||||
&& element.getInterfaces().stream().noneMatch(i -> types.isSameType(i, base))) {
|
||||
element = m_processingEnv.getElementUtils().getTypeElement(parent.toString());
|
||||
if (parent.getKind() == TypeKind.NONE) {
|
||||
// Interface inheritance, there is no superclass
|
||||
break;
|
||||
}
|
||||
|
||||
parent = element.getSuperclass();
|
||||
|
||||
// Handle cases of interface inheritance
|
||||
distance =
|
||||
1
|
||||
+ element.getInterfaces().stream()
|
||||
.mapToInt(iface -> inheritanceDistance(iface, base))
|
||||
.min()
|
||||
.orElse(distance);
|
||||
}
|
||||
|
||||
return distance;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,25 +7,69 @@ package edu.wpi.first.epilogue.processor;
|
||||
import static java.util.stream.Collectors.groupingBy;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
|
||||
import com.sun.source.tree.IdentifierTree;
|
||||
import com.sun.source.tree.ReturnTree;
|
||||
import com.sun.source.util.SimpleTreeVisitor;
|
||||
import com.sun.source.util.Trees;
|
||||
import edu.wpi.first.epilogue.Logged;
|
||||
import edu.wpi.first.epilogue.NotLogged;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Deque;
|
||||
import java.util.EnumMap;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import javax.annotation.processing.ProcessingEnvironment;
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.ExecutableElement;
|
||||
import javax.lang.model.element.Modifier;
|
||||
import javax.lang.model.element.Name;
|
||||
import javax.lang.model.element.PackageElement;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.element.VariableElement;
|
||||
import javax.tools.Diagnostic;
|
||||
|
||||
/** Generates logger class files for {@link Logged @Logged}-annotated classes. */
|
||||
public class LoggerGenerator {
|
||||
public static final Predicate<ExecutableElement> kIsBuiltInJavaMethod =
|
||||
LoggerGenerator::isBuiltInJavaMethod;
|
||||
private final ProcessingEnvironment m_processingEnv;
|
||||
private final List<ElementHandler> m_handlers;
|
||||
private final Logged m_defaultConfig =
|
||||
new Logged() {
|
||||
@Override
|
||||
public Class<? extends Annotation> annotationType() {
|
||||
return Logged.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Strategy strategy() {
|
||||
return Strategy.OPT_IN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Importance importance() {
|
||||
return Importance.DEBUG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Naming defaultNaming() {
|
||||
return Naming.USE_CODE_NAME;
|
||||
}
|
||||
};
|
||||
|
||||
public LoggerGenerator(ProcessingEnvironment processingEnv, List<ElementHandler> handlers) {
|
||||
this.m_processingEnv = processingEnv;
|
||||
@@ -36,6 +80,19 @@ public class LoggerGenerator {
|
||||
return e.getAnnotation(NotLogged.class) == null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a method is a method declared in java.lang.Object that should not be logged.
|
||||
*
|
||||
* @param e the method to check
|
||||
* @return true if the method is toString, hashCode, or clone; false otherwise
|
||||
*/
|
||||
private static boolean isBuiltInJavaMethod(ExecutableElement e) {
|
||||
Name name = e.getSimpleName();
|
||||
return name.contentEquals("toString")
|
||||
|| name.contentEquals("hashCode")
|
||||
|| name.contentEquals("clone");
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the logger class used to handle data objects of the given type. The generated logger
|
||||
* class will subclass from {@link edu.wpi.first.epilogue.logging.ClassSpecificLogger} and
|
||||
@@ -47,23 +104,32 @@ public class LoggerGenerator {
|
||||
*/
|
||||
public void writeLoggerFile(TypeElement clazz) throws IOException {
|
||||
var config = clazz.getAnnotation(Logged.class);
|
||||
if (config == null) {
|
||||
config = m_defaultConfig;
|
||||
}
|
||||
boolean requireExplicitOptIn = config.strategy() == Logged.Strategy.OPT_IN;
|
||||
|
||||
Predicate<Element> notSkipped = LoggerGenerator::isNotSkipped;
|
||||
Predicate<Element> optedIn =
|
||||
e -> !requireExplicitOptIn || e.getAnnotation(Logged.class) != null;
|
||||
|
||||
var fieldsToLog =
|
||||
clazz.getEnclosedElements().stream()
|
||||
.filter(e -> e instanceof VariableElement)
|
||||
.map(e -> (VariableElement) e)
|
||||
.filter(notSkipped)
|
||||
.filter(optedIn)
|
||||
.filter(e -> !e.getModifiers().contains(Modifier.STATIC))
|
||||
.filter(this::isLoggable)
|
||||
.toList();
|
||||
List<VariableElement> fieldsToLog;
|
||||
if (Objects.equals(clazz.getSuperclass().toString(), "java.lang.Record")) {
|
||||
// Do not log record members - just use the accessor methods
|
||||
fieldsToLog = List.of();
|
||||
} else {
|
||||
fieldsToLog =
|
||||
clazz.getEnclosedElements().stream()
|
||||
.filter(e -> e instanceof VariableElement)
|
||||
.map(e -> (VariableElement) e)
|
||||
.filter(notSkipped)
|
||||
.filter(optedIn)
|
||||
.filter(e -> !e.getModifiers().contains(Modifier.STATIC))
|
||||
.filter(this::isLoggable)
|
||||
.toList();
|
||||
}
|
||||
|
||||
var methodsToLog =
|
||||
List<ExecutableElement> methodsToLog =
|
||||
clazz.getEnclosedElements().stream()
|
||||
.filter(e -> e instanceof ExecutableElement)
|
||||
.map(e -> (ExecutableElement) e)
|
||||
@@ -73,39 +139,73 @@ public class LoggerGenerator {
|
||||
.filter(e -> e.getModifiers().contains(Modifier.PUBLIC))
|
||||
.filter(e -> e.getParameters().isEmpty())
|
||||
.filter(e -> e.getReceiverType() != null)
|
||||
.filter(kIsBuiltInJavaMethod.negate())
|
||||
.filter(this::isLoggable)
|
||||
.filter(e -> !isSimpleGetterMethodForLoggedField(e, fieldsToLog))
|
||||
.toList();
|
||||
|
||||
writeLoggerFile(clazz.getQualifiedName().toString(), config, fieldsToLog, methodsToLog);
|
||||
// Validate no name collisions
|
||||
Map<String, List<Element>> usedNames =
|
||||
Stream.concat(fieldsToLog.stream(), methodsToLog.stream())
|
||||
.reduce(
|
||||
new HashMap<>(),
|
||||
(map, element) -> {
|
||||
String name = ElementHandler.loggedName(element);
|
||||
map.computeIfAbsent(name, _k -> new ArrayList<>()).add(element);
|
||||
|
||||
return map;
|
||||
},
|
||||
(left, right) -> {
|
||||
left.putAll(right);
|
||||
return left;
|
||||
});
|
||||
|
||||
usedNames.forEach(
|
||||
(name, elements) -> {
|
||||
if (elements.size() > 1) {
|
||||
// Collisions!
|
||||
for (Element conflictingElement : elements) {
|
||||
String conflicts =
|
||||
elements.stream()
|
||||
.filter(e -> !e.equals(conflictingElement))
|
||||
.map(e -> e.getEnclosingElement().getSimpleName() + "." + e)
|
||||
.collect(Collectors.joining(", "));
|
||||
|
||||
m_processingEnv
|
||||
.getMessager()
|
||||
.printMessage(
|
||||
Diagnostic.Kind.ERROR,
|
||||
"[EPILOGUE] Conflicting name detected: \""
|
||||
+ name
|
||||
+ "\" is also used by "
|
||||
+ conflicts,
|
||||
conflictingElement);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
writeLoggerFile(clazz, config, fieldsToLog, methodsToLog);
|
||||
}
|
||||
|
||||
private void writeLoggerFile(
|
||||
String className,
|
||||
TypeElement clazz,
|
||||
Logged classConfig,
|
||||
List<VariableElement> loggableFields,
|
||||
List<ExecutableElement> loggableMethods)
|
||||
throws IOException {
|
||||
String packageName = null;
|
||||
int lastDot = className.lastIndexOf('.');
|
||||
if (lastDot > 0) {
|
||||
packageName = className.substring(0, lastDot);
|
||||
// Walk nesting levels, to support inner classes
|
||||
Deque<String> nesting = new ArrayDeque<>();
|
||||
Element enclosing = clazz.getEnclosingElement();
|
||||
while (!(enclosing instanceof PackageElement p)) {
|
||||
nesting.addFirst(enclosing.getSimpleName().toString());
|
||||
enclosing = enclosing.getEnclosingElement();
|
||||
}
|
||||
String packageName = p.getQualifiedName().toString();
|
||||
nesting.addLast(clazz.getSimpleName().toString());
|
||||
String simpleClassName = String.join(".", nesting);
|
||||
|
||||
String simpleClassName = StringUtils.simpleName(className);
|
||||
String loggerClassName = className + "Logger";
|
||||
String loggerSimpleClassName = loggerClassName.substring(lastDot + 1);
|
||||
|
||||
// Use the name on the class config to set the generated logger names
|
||||
// This helps to avoid naming conflicts
|
||||
if (!classConfig.name().isBlank()) {
|
||||
loggerSimpleClassName =
|
||||
StringUtils.capitalize(classConfig.name().replaceAll(" ", "")) + "Logger";
|
||||
if (lastDot > 0) {
|
||||
loggerClassName = packageName + "." + loggerSimpleClassName;
|
||||
} else {
|
||||
loggerClassName = loggerSimpleClassName;
|
||||
}
|
||||
}
|
||||
String loggerClassName = StringUtils.loggerClassName(clazz);
|
||||
String loggerSimpleClassName = StringUtils.simpleName(loggerClassName);
|
||||
|
||||
var loggerFile = m_processingEnv.getFiler().createSourceFile(loggerClassName);
|
||||
|
||||
@@ -123,7 +223,7 @@ public class LoggerGenerator {
|
||||
out.println("import edu.wpi.first.epilogue.Logged;");
|
||||
out.println("import edu.wpi.first.epilogue.Epilogue;");
|
||||
out.println("import edu.wpi.first.epilogue.logging.ClassSpecificLogger;");
|
||||
out.println("import edu.wpi.first.epilogue.logging.DataLogger;");
|
||||
out.println("import edu.wpi.first.epilogue.logging.EpilogueBackend;");
|
||||
if (requiresVarHandles) {
|
||||
out.println("import java.lang.invoke.MethodHandles;");
|
||||
out.println("import java.lang.invoke.VarHandle;");
|
||||
@@ -146,13 +246,13 @@ public class LoggerGenerator {
|
||||
}
|
||||
out.println();
|
||||
|
||||
var clazz = simpleClassName + ".class";
|
||||
var classReference = simpleClassName + ".class";
|
||||
|
||||
out.println(" static {");
|
||||
out.println(" try {");
|
||||
out.println(
|
||||
" var lookup = MethodHandles.privateLookupIn("
|
||||
+ clazz
|
||||
+ classReference
|
||||
+ ", MethodHandles.lookup());");
|
||||
|
||||
for (var privateField : privateFields) {
|
||||
@@ -161,7 +261,7 @@ public class LoggerGenerator {
|
||||
" $"
|
||||
+ fieldName
|
||||
+ " = lookup.findVarHandle("
|
||||
+ clazz
|
||||
+ classReference
|
||||
+ ", \""
|
||||
+ fieldName
|
||||
+ "\", "
|
||||
@@ -184,9 +284,10 @@ public class LoggerGenerator {
|
||||
out.println();
|
||||
|
||||
// @Override
|
||||
// public void update(DataLogger dataLogger, Foo object) {
|
||||
// public void update(EpilogueBackend backend, Foo object) {
|
||||
out.println(" @Override");
|
||||
out.println(" public void update(DataLogger dataLogger, " + simpleClassName + " object) {");
|
||||
out.println(
|
||||
" public void update(EpilogueBackend backend, " + simpleClassName + " object) {");
|
||||
|
||||
// Build a map of importance levels to the fields logged at those levels
|
||||
// e.g. { DEBUG: [fieldA, fieldB], INFO: [fieldC], CRITICAL: [fieldD, fieldE, fieldF] }
|
||||
@@ -242,4 +343,55 @@ public class LoggerGenerator {
|
||||
private boolean isLoggable(Element element) {
|
||||
return m_handlers.stream().anyMatch(h -> h.isLoggable(element));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a method is a simple "getter" method for a field in the given list. Here, we define
|
||||
* "getter" as a method with a single return statement that references the name of a field, with
|
||||
* no other expressions. `getX() { return x; }` would be considered a "getter" method, while
|
||||
* `getX() { return x.clone(); }` would not be. Note that the method name is irrelevant; only the
|
||||
* method body is checked.
|
||||
*
|
||||
* @param ex the method to check
|
||||
* @param fieldsToLog the fields that will already be logged
|
||||
* @return true if the method is a simple "getter" method, false otherwise
|
||||
*/
|
||||
private boolean isSimpleGetterMethodForLoggedField(
|
||||
ExecutableElement ex, List<VariableElement> fieldsToLog) {
|
||||
var trees = Trees.instance(m_processingEnv);
|
||||
var methodTree = trees.getTree(ex);
|
||||
|
||||
if (methodTree == null) {
|
||||
// probably a record's synthetic reader method
|
||||
return false;
|
||||
}
|
||||
|
||||
if (methodTree.getBody() == null) {
|
||||
// Abstract or native method, can't be determined to be a getter
|
||||
return false;
|
||||
}
|
||||
|
||||
var statements = methodTree.getBody().getStatements();
|
||||
if (statements.size() != 1) {
|
||||
// More complex than a simple `return m_field` statement
|
||||
return false;
|
||||
}
|
||||
|
||||
var statement = statements.get(0);
|
||||
if (!(statement instanceof ReturnTree ret)) {
|
||||
// Shouldn't get here, since we've already filtered for methods that return a value
|
||||
// and with a single statement - that one statement should be the return
|
||||
return false;
|
||||
}
|
||||
|
||||
var returnExpression = ret.getExpression();
|
||||
return returnExpression.accept(
|
||||
new SimpleTreeVisitor<Boolean, Void>(false) {
|
||||
@Override
|
||||
public Boolean visitIdentifier(IdentifierTree identifier, Void unused) {
|
||||
return fieldsToLog.stream()
|
||||
.anyMatch(v -> v.getSimpleName().contentEquals(identifier.getName()));
|
||||
}
|
||||
},
|
||||
null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ public class MeasureHandler extends ElementHandler {
|
||||
|
||||
@Override
|
||||
public String logInvocation(Element element) {
|
||||
// DataLogger has builtin support for logging measures
|
||||
return "dataLogger.log(\"" + loggedName(element) + "\", " + elementAccess(element) + ")";
|
||||
// EpilogueBackend has builtin support for logging measures
|
||||
return "backend.log(\"" + loggedName(element) + "\", " + elementAccess(element) + ")";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,6 @@ public class PrimitiveHandler extends ElementHandler {
|
||||
|
||||
@Override
|
||||
public String logInvocation(Element element) {
|
||||
return "dataLogger.log(\"" + loggedName(element) + "\", " + elementAccess(element) + ")";
|
||||
return "backend.log(\"" + loggedName(element) + "\", " + elementAccess(element) + ")";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,24 +4,32 @@
|
||||
|
||||
package edu.wpi.first.epilogue.processor;
|
||||
|
||||
import java.util.Optional;
|
||||
import javax.annotation.processing.ProcessingEnvironment;
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
|
||||
public class SendableHandler extends ElementHandler {
|
||||
private final TypeMirror m_sendableType;
|
||||
private final TypeMirror m_commandType;
|
||||
private final TypeMirror m_subsystemType;
|
||||
private final Optional<TypeMirror> m_sendableType;
|
||||
private final Optional<TypeMirror> m_commandType;
|
||||
private final Optional<TypeMirror> m_subsystemType;
|
||||
|
||||
protected SendableHandler(ProcessingEnvironment processingEnv) {
|
||||
super(processingEnv);
|
||||
|
||||
m_sendableType =
|
||||
lookupTypeElement(processingEnv, "edu.wpi.first.util.sendable.Sendable").asType();
|
||||
Optional.ofNullable(
|
||||
lookupTypeElement(processingEnv, "edu.wpi.first.util.sendable.Sendable"))
|
||||
.map(TypeElement::asType);
|
||||
m_commandType =
|
||||
lookupTypeElement(processingEnv, "edu.wpi.first.wpilibj2.command.Command").asType();
|
||||
Optional.ofNullable(
|
||||
lookupTypeElement(processingEnv, "edu.wpi.first.wpilibj2.command.Command"))
|
||||
.map(TypeElement::asType);
|
||||
m_subsystemType =
|
||||
lookupTypeElement(processingEnv, "edu.wpi.first.wpilibj2.command.SubsystemBase").asType();
|
||||
Optional.ofNullable(
|
||||
lookupTypeElement(processingEnv, "edu.wpi.first.wpilibj2.command.SubsystemBase"))
|
||||
.map(TypeElement::asType);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -30,24 +38,32 @@ public class SendableHandler extends ElementHandler {
|
||||
|
||||
// Accept any sendable type. However, the log invocation will return null
|
||||
// for sendable types that should not be logged (commands, subsystems)
|
||||
return m_processingEnv.getTypeUtils().isAssignable(dataType, m_sendableType);
|
||||
return m_sendableType
|
||||
.map(t -> m_processingEnv.getTypeUtils().isAssignable(dataType, t))
|
||||
.orElse(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String logInvocation(Element element) {
|
||||
var dataType = dataType(element);
|
||||
|
||||
if (m_processingEnv.getTypeUtils().isAssignable(dataType, m_commandType)
|
||||
|| m_processingEnv.getTypeUtils().isAssignable(dataType, m_subsystemType)) {
|
||||
// Do not log commands or subsystems via their sendable implementations
|
||||
// We accept all sendable objects to prevent them from being reported as not loggable,
|
||||
// but their sendable implementations do not include helpful information.
|
||||
// Users are free to provide custom logging implementations for commands, and tag their
|
||||
// subsystems with @Logged to log their contents automatically
|
||||
// Do not log commands or subsystems via their sendable implementations
|
||||
// We accept all sendable objects to prevent them from being reported as not loggable,
|
||||
// but their sendable implementations do not include helpful information.
|
||||
// Users are free to provide custom logging implementations for commands, and tag their
|
||||
// subsystems with @Logged to log their contents automatically
|
||||
if (m_commandType
|
||||
.map(t -> m_processingEnv.getTypeUtils().isAssignable(dataType, t))
|
||||
.orElse(false)) {
|
||||
return null;
|
||||
}
|
||||
if (m_subsystemType
|
||||
.map(t -> m_processingEnv.getTypeUtils().isAssignable(dataType, t))
|
||||
.orElse(false)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return "logSendable(dataLogger.getSubLogger(\""
|
||||
return "logSendable(backend.getNested(\""
|
||||
+ loggedName(element)
|
||||
+ "\"), "
|
||||
+ elementAccess(element)
|
||||
|
||||
@@ -5,6 +5,13 @@
|
||||
package edu.wpi.first.epilogue.processor;
|
||||
|
||||
import edu.wpi.first.epilogue.Logged;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Arrays;
|
||||
import java.util.Deque;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.PackageElement;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
|
||||
public final class StringUtils {
|
||||
@@ -59,6 +66,33 @@ public final class StringUtils {
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits a camel-cased string like "fooBar" into individual words like ["foo", "Bar"].
|
||||
*
|
||||
* @param camelCasedString the camel-cased string to split
|
||||
* @return the individual words in the input
|
||||
*/
|
||||
public static List<String> splitToWords(CharSequence camelCasedString) {
|
||||
// Implementation from https://stackoverflow.com/a/2560017, refactored for readability
|
||||
|
||||
// Uppercase letter not followed by the first letter of the next word
|
||||
// This allows for splitting "IOLayer" into "IO" and "Layer"
|
||||
String penultimateUppercaseLetter = "(?<=[A-Z])(?=[A-Z][a-z])";
|
||||
|
||||
// Any character that's NOT an uppercase letter, immediately followed by an uppercase letter
|
||||
// This allows for splitting "fooBar" into "foo" and "Bar", or "123Bang" into "123" and "Bang"
|
||||
String lastNonUppercaseLetter = "(?<=[^A-Z])(?=[A-Z])";
|
||||
|
||||
// The final letter in a sequence, followed by a non-alpha character like a number or underscore
|
||||
// This allows for splitting "foo123" into "foo" and "123"
|
||||
String finalLetter = "(?<=[A-Za-z])(?=[^A-Za-z])";
|
||||
|
||||
String regex =
|
||||
String.format("%s|%s|%s", penultimateUppercaseLetter, lastNonUppercaseLetter, finalLetter);
|
||||
|
||||
return Arrays.asList(camelCasedString.toString().split(regex));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the field used to hold a logger for data of the given type.
|
||||
*
|
||||
@@ -78,33 +112,50 @@ public final class StringUtils {
|
||||
*/
|
||||
public static String loggerClassName(TypeElement clazz) {
|
||||
var config = clazz.getAnnotation(Logged.class);
|
||||
var className = clazz.getQualifiedName().toString();
|
||||
|
||||
String packageName;
|
||||
int lastDot = className.lastIndexOf('.');
|
||||
if (lastDot <= 0) {
|
||||
packageName = null;
|
||||
Deque<String> nesting = new ArrayDeque<>();
|
||||
Element enclosing = clazz.getEnclosingElement();
|
||||
while (!(enclosing instanceof PackageElement p)) {
|
||||
nesting.addFirst(enclosing.getSimpleName().toString());
|
||||
enclosing = enclosing.getEnclosingElement();
|
||||
}
|
||||
nesting.addLast(clazz.getSimpleName().toString());
|
||||
String packageName = p.getQualifiedName().toString();
|
||||
|
||||
String className;
|
||||
if (config == null || config.name().isEmpty()) {
|
||||
className = String.join("$", nesting);
|
||||
} else {
|
||||
packageName = className.substring(0, lastDot);
|
||||
className = capitalize(config.name()).replaceAll(" ", "");
|
||||
}
|
||||
|
||||
String loggerClassName;
|
||||
return packageName + "." + className + "Logger";
|
||||
}
|
||||
|
||||
// Use the name on the class config to set the generated logger names
|
||||
// This helps to avoid naming conflicts
|
||||
if (config.name().isBlank()) {
|
||||
loggerClassName = className + "Logger";
|
||||
} else {
|
||||
String cleaned = config.name().replaceAll(" ", "");
|
||||
/**
|
||||
* Converts a camelCase element name to separate words, removing common field and method name
|
||||
* prefixes like "m_" and "get".
|
||||
*
|
||||
* @param elementName the camelcased element name
|
||||
* @return the name split into separate words and sanitized
|
||||
*/
|
||||
public static String toHumanName(String elementName) {
|
||||
// Delete common field prefixes (k_name, m_name, s_name)
|
||||
var sanitizedName = elementName.replaceFirst("^[msk]_", "");
|
||||
|
||||
var loggerSimpleClassName = StringUtils.capitalize(cleaned) + "Logger";
|
||||
if (packageName != null) {
|
||||
loggerClassName = packageName + "." + loggerSimpleClassName;
|
||||
} else {
|
||||
loggerClassName = loggerSimpleClassName;
|
||||
}
|
||||
// Drop leading "k" prefix from fields
|
||||
// (though normally these should be static, and thus not logged)
|
||||
if (sanitizedName.matches("^k[A-Z].*$")) {
|
||||
sanitizedName = sanitizedName.substring(1);
|
||||
}
|
||||
|
||||
return loggerClassName;
|
||||
// Drop leading "get" from accessor methods
|
||||
if (sanitizedName.matches("^get[A-Z].*$")) {
|
||||
sanitizedName = sanitizedName.substring(3);
|
||||
}
|
||||
|
||||
return splitToWords(sanitizedName).stream()
|
||||
.map(StringUtils::capitalize)
|
||||
.collect(Collectors.joining(" "));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ public class StructHandler extends ElementHandler {
|
||||
|
||||
@Override
|
||||
public String logInvocation(Element element) {
|
||||
return "dataLogger.log(\""
|
||||
return "backend.log(\""
|
||||
+ loggedName(element)
|
||||
+ "\", "
|
||||
+ elementAccess(element)
|
||||
|
||||
@@ -43,7 +43,7 @@ public class SupplierHandler extends ElementHandler {
|
||||
|
||||
@Override
|
||||
public String logInvocation(Element element) {
|
||||
return "dataLogger.log(\"" + loggedName(element) + "\", " + elementAccess(element) + ")";
|
||||
return "backend.log(\"" + loggedName(element) + "\", " + elementAccess(element) + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,13 @@
|
||||
// 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.epilogue.processor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class CompileTestOptions {
|
||||
public static final int kJavaVersion = 17;
|
||||
public static final List<Object> kJavaVersionOptions =
|
||||
List.of("-source", kJavaVersion, "-target", kJavaVersion);
|
||||
}
|
||||
@@ -6,6 +6,7 @@ package edu.wpi.first.epilogue.processor;
|
||||
|
||||
import static com.google.testing.compile.CompilationSubject.assertThat;
|
||||
import static com.google.testing.compile.Compiler.javac;
|
||||
import static edu.wpi.first.epilogue.processor.CompileTestOptions.kJavaVersionOptions;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import com.google.testing.compile.Compilation;
|
||||
@@ -32,9 +33,19 @@ class EpilogueGeneratorTest {
|
||||
|
||||
import static edu.wpi.first.units.Units.Seconds;
|
||||
|
||||
import edu.wpi.first.hal.FRCNetComm;
|
||||
import edu.wpi.first.hal.HAL;
|
||||
|
||||
import edu.wpi.first.epilogue.ExampleLogger;
|
||||
|
||||
public final class Epilogue {
|
||||
static {
|
||||
HAL.report(
|
||||
FRCNetComm.tResourceType.kResourceType_LoggingFramework,
|
||||
FRCNetComm.tInstances.kLoggingFramework_Epilogue
|
||||
);
|
||||
}
|
||||
|
||||
private static final EpilogueConfiguration config = new EpilogueConfiguration();
|
||||
|
||||
public static final ExampleLogger exampleLogger = new ExampleLogger();
|
||||
@@ -81,9 +92,19 @@ class EpilogueGeneratorTest {
|
||||
|
||||
import static edu.wpi.first.units.Units.Seconds;
|
||||
|
||||
import edu.wpi.first.hal.FRCNetComm;
|
||||
import edu.wpi.first.hal.HAL;
|
||||
|
||||
import edu.wpi.first.epilogue.ExampleLogger;
|
||||
|
||||
public final class Epilogue {
|
||||
static {
|
||||
HAL.report(
|
||||
FRCNetComm.tResourceType.kResourceType_LoggingFramework,
|
||||
FRCNetComm.tInstances.kLoggingFramework_Epilogue
|
||||
);
|
||||
}
|
||||
|
||||
private static final EpilogueConfiguration config = new EpilogueConfiguration();
|
||||
|
||||
public static final ExampleLogger exampleLogger = new ExampleLogger();
|
||||
@@ -125,9 +146,19 @@ class EpilogueGeneratorTest {
|
||||
|
||||
import static edu.wpi.first.units.Units.Seconds;
|
||||
|
||||
import edu.wpi.first.hal.FRCNetComm;
|
||||
import edu.wpi.first.hal.HAL;
|
||||
|
||||
import edu.wpi.first.epilogue.ExampleLogger;
|
||||
|
||||
public final class Epilogue {
|
||||
static {
|
||||
HAL.report(
|
||||
FRCNetComm.tResourceType.kResourceType_LoggingFramework,
|
||||
FRCNetComm.tInstances.kLoggingFramework_Epilogue
|
||||
);
|
||||
}
|
||||
|
||||
private static final EpilogueConfiguration config = new EpilogueConfiguration();
|
||||
|
||||
public static final ExampleLogger exampleLogger = new ExampleLogger();
|
||||
@@ -154,8 +185,8 @@ class EpilogueGeneratorTest {
|
||||
*/
|
||||
public static void update(edu.wpi.first.epilogue.Example robot) {
|
||||
long start = System.nanoTime();
|
||||
exampleLogger.tryUpdate(config.dataLogger.getSubLogger(config.root), robot, config.errorHandler);
|
||||
config.dataLogger.log(\"Epilogue/Stats/Last Run\", (System.nanoTime() - start) / 1e6);
|
||||
exampleLogger.tryUpdate(config.backend.getNested(config.root), robot, config.errorHandler);
|
||||
config.backend.log(\"Epilogue/Stats/Last Run\", (System.nanoTime() - start) / 1e6);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -171,7 +202,7 @@ class EpilogueGeneratorTest {
|
||||
config.loggingPeriod = Seconds.of(robot.getPeriod());
|
||||
}
|
||||
if (config.loggingPeriodOffset == null) {
|
||||
config.loggingPeriodOffset = config.loggingPeriod.divide(2);
|
||||
config.loggingPeriodOffset = config.loggingPeriod.div(2);
|
||||
}
|
||||
|
||||
robot.addPeriodic(() -> {
|
||||
@@ -203,10 +234,20 @@ class EpilogueGeneratorTest {
|
||||
|
||||
import static edu.wpi.first.units.Units.Seconds;
|
||||
|
||||
import edu.wpi.first.hal.FRCNetComm;
|
||||
import edu.wpi.first.hal.HAL;
|
||||
|
||||
import edu.wpi.first.epilogue.AlphaBotLogger;
|
||||
import edu.wpi.first.epilogue.BetaBotLogger;
|
||||
|
||||
public final class Epilogue {
|
||||
static {
|
||||
HAL.report(
|
||||
FRCNetComm.tResourceType.kResourceType_LoggingFramework,
|
||||
FRCNetComm.tInstances.kLoggingFramework_Epilogue
|
||||
);
|
||||
}
|
||||
|
||||
private static final EpilogueConfiguration config = new EpilogueConfiguration();
|
||||
|
||||
public static final AlphaBotLogger alphaBotLogger = new AlphaBotLogger();
|
||||
@@ -234,8 +275,8 @@ class EpilogueGeneratorTest {
|
||||
*/
|
||||
public static void update(edu.wpi.first.epilogue.AlphaBot robot) {
|
||||
long start = System.nanoTime();
|
||||
alphaBotLogger.tryUpdate(config.dataLogger.getSubLogger(config.root), robot, config.errorHandler);
|
||||
config.dataLogger.log(\"Epilogue/Stats/Last Run\", (System.nanoTime() - start) / 1e6);
|
||||
alphaBotLogger.tryUpdate(config.backend.getNested(config.root), robot, config.errorHandler);
|
||||
config.backend.log(\"Epilogue/Stats/Last Run\", (System.nanoTime() - start) / 1e6);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -251,7 +292,7 @@ class EpilogueGeneratorTest {
|
||||
config.loggingPeriod = Seconds.of(robot.getPeriod());
|
||||
}
|
||||
if (config.loggingPeriodOffset == null) {
|
||||
config.loggingPeriodOffset = config.loggingPeriod.divide(2);
|
||||
config.loggingPeriodOffset = config.loggingPeriod.div(2);
|
||||
}
|
||||
|
||||
robot.addPeriodic(() -> {
|
||||
@@ -266,8 +307,8 @@ class EpilogueGeneratorTest {
|
||||
*/
|
||||
public static void update(edu.wpi.first.epilogue.BetaBot robot) {
|
||||
long start = System.nanoTime();
|
||||
betaBotLogger.tryUpdate(config.dataLogger.getSubLogger(config.root), robot, config.errorHandler);
|
||||
config.dataLogger.log(\"Epilogue/Stats/Last Run\", (System.nanoTime() - start) / 1e6);
|
||||
betaBotLogger.tryUpdate(config.backend.getNested(config.root), robot, config.errorHandler);
|
||||
config.backend.log(\"Epilogue/Stats/Last Run\", (System.nanoTime() - start) / 1e6);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -283,7 +324,7 @@ class EpilogueGeneratorTest {
|
||||
config.loggingPeriod = Seconds.of(robot.getPeriod());
|
||||
}
|
||||
if (config.loggingPeriodOffset == null) {
|
||||
config.loggingPeriodOffset = config.loggingPeriod.divide(2);
|
||||
config.loggingPeriodOffset = config.loggingPeriod.div(2);
|
||||
}
|
||||
|
||||
robot.addPeriodic(() -> {
|
||||
@@ -313,7 +354,7 @@ class EpilogueGeneratorTest {
|
||||
public CustomLogger() { super(A.class); }
|
||||
|
||||
@Override
|
||||
public void update(DataLogger logger, A object) {} // implementation is irrelevant
|
||||
public void update(EpilogueBackend backend, A object) {} // implementation is irrelevant
|
||||
}
|
||||
|
||||
@Logged
|
||||
@@ -330,10 +371,20 @@ class EpilogueGeneratorTest {
|
||||
|
||||
import static edu.wpi.first.units.Units.Seconds;
|
||||
|
||||
import edu.wpi.first.hal.FRCNetComm;
|
||||
import edu.wpi.first.hal.HAL;
|
||||
|
||||
import edu.wpi.first.epilogue.ExampleLogger;
|
||||
import edu.wpi.first.epilogue.CustomLogger;
|
||||
|
||||
public final class Epilogue {
|
||||
static {
|
||||
HAL.report(
|
||||
FRCNetComm.tResourceType.kResourceType_LoggingFramework,
|
||||
FRCNetComm.tInstances.kLoggingFramework_Epilogue
|
||||
);
|
||||
}
|
||||
|
||||
private static final EpilogueConfiguration config = new EpilogueConfiguration();
|
||||
|
||||
public static final ExampleLogger exampleLogger = new ExampleLogger();
|
||||
@@ -363,6 +414,7 @@ class EpilogueGeneratorTest {
|
||||
String loggedClassContent, String loggerClassContent) {
|
||||
Compilation compilation =
|
||||
javac()
|
||||
.withOptions(kJavaVersionOptions)
|
||||
.withProcessors(new AnnotationProcessor())
|
||||
.compile(JavaFileObjects.forSourceString("", loggedClassContent));
|
||||
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
|
||||
package edu.wpi.first.epilogue.processor;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertAll;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import java.util.List;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class StringUtilsTest {
|
||||
@@ -16,4 +18,22 @@ class StringUtilsTest {
|
||||
assertEquals("fooBar", StringUtils.lowerCamelCase("FooBar"));
|
||||
assertEquals("allcaps", StringUtils.lowerCamelCase("ALLCAPS"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void splitToWords() {
|
||||
assertAll(
|
||||
() -> assertEquals(List.of("IO", "Logger"), StringUtils.splitToWords("IOLogger")),
|
||||
() -> assertEquals(List.of("LED", "Subsystem"), StringUtils.splitToWords("LEDSubsystem")),
|
||||
() -> assertEquals(List.of("Foo", "Bar"), StringUtils.splitToWords("FooBar")),
|
||||
() -> assertEquals(List.of("ALLCAPS"), StringUtils.splitToWords("ALLCAPS")),
|
||||
() ->
|
||||
assertEquals(List.of("k", "First", "Second"), StringUtils.splitToWords("kFirstSecond")),
|
||||
() ->
|
||||
assertEquals(
|
||||
List.of("there", "Is", "A", "Number", "123", "In", "Here", "VERSION", "456"),
|
||||
StringUtils.splitToWords("thereIsANumber123InHereVERSION456")),
|
||||
() ->
|
||||
assertEquals(
|
||||
List.of("get", "First", "Second"), StringUtils.splitToWords("getFirstSecond")));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
|
||||
package edu.wpi.first.epilogue;
|
||||
|
||||
import edu.wpi.first.epilogue.logging.DataLogger;
|
||||
import edu.wpi.first.epilogue.logging.NTDataLogger;
|
||||
import edu.wpi.first.epilogue.logging.EpilogueBackend;
|
||||
import edu.wpi.first.epilogue.logging.NTEpilogueBackend;
|
||||
import edu.wpi.first.epilogue.logging.errors.ErrorHandler;
|
||||
import edu.wpi.first.epilogue.logging.errors.ErrorPrinter;
|
||||
import edu.wpi.first.networktables.NetworkTableInstance;
|
||||
@@ -18,11 +18,11 @@ import edu.wpi.first.units.measure.Time;
|
||||
@SuppressWarnings("checkstyle:MemberName")
|
||||
public class EpilogueConfiguration {
|
||||
/**
|
||||
* The data logger implementation for Epilogue to use. By default, this will log data directly to
|
||||
* The backend implementation for Epilogue to use. By default, this will log data directly to
|
||||
* NetworkTables. NetworkTable data can be mirrored to a log file on disk by calling {@code
|
||||
* DataLogManager.start()} in your {@code robotInit} method.
|
||||
*/
|
||||
public DataLogger dataLogger = new NTDataLogger(NetworkTableInstance.getDefault());
|
||||
public EpilogueBackend backend = new NTEpilogueBackend(NetworkTableInstance.getDefault());
|
||||
|
||||
/**
|
||||
* The period Epilogue will log at. By default this is the period that the robot runs at. This is
|
||||
|
||||
@@ -89,4 +89,39 @@ public @interface Logged {
|
||||
* @return the importance of the annotated element
|
||||
*/
|
||||
Importance importance() default Importance.DEBUG;
|
||||
|
||||
/**
|
||||
* Different behaviors for how Epilogue will generate the names of logged data points. This only
|
||||
* applies to automatically generated names; any specific name provided with {@link #name()} will
|
||||
* take precedence over an automatically generated name.
|
||||
*/
|
||||
enum Naming {
|
||||
/**
|
||||
* Sets the default naming strategy to use the name of the element as it appears in source code.
|
||||
* For example, a field {@code double m_x} would be labeled as {@code "m_x"} by default, and a
|
||||
* {@code getX()} accessor would be labeled as {@code "getX"}.
|
||||
*/
|
||||
USE_CODE_NAME,
|
||||
|
||||
/**
|
||||
* Sets the default naming strategy to use a human-readable name based on the name of the name
|
||||
* of the element as it appears in source code. For example, a field {@code double m_x} would be
|
||||
* labeled as {@code "X"} by default, and a {@code getX()} accessor would also be labeled as
|
||||
* {@code "X"}. Because logged names must be unique, this configuration would fail to compile
|
||||
* and require either one of the fields to be excluded from logs (which, for simple accessors,
|
||||
* would be ideal to avoid duplicate data), or to rename one or both elements so the logged data
|
||||
* fields would have unique names.
|
||||
*/
|
||||
USE_HUMAN_NAME
|
||||
}
|
||||
|
||||
/**
|
||||
* The default naming behavior to use. Defaults to {@link Naming#USE_CODE_NAME}, which uses the
|
||||
* raw code name directly in logs. Any configuration of the {@link #name()} attribute on logged
|
||||
* fields and methods will take precedence over an automatically generated name.
|
||||
*
|
||||
* @return the naming strategy for and annotated field or method, or the default naming strategy
|
||||
* for all logged fields and methods in an annotated class
|
||||
*/
|
||||
Naming defaultNaming() default Naming.USE_CODE_NAME;
|
||||
}
|
||||
|
||||
@@ -42,26 +42,26 @@ public abstract class ClassSpecificLogger<T> {
|
||||
/**
|
||||
* Updates an object's fields in a data log.
|
||||
*
|
||||
* @param dataLogger the logger to update
|
||||
* @param backend the backend to update
|
||||
* @param object the object to update in the log
|
||||
*/
|
||||
protected abstract void update(DataLogger dataLogger, T object);
|
||||
protected abstract void update(EpilogueBackend backend, T object);
|
||||
|
||||
/**
|
||||
* Attempts to update the data log. Will do nothing if the logger is {@link #disable() disabled}.
|
||||
*
|
||||
* @param dataLogger the logger to log data to
|
||||
* @param backend the backend to log data to
|
||||
* @param object the data object to log
|
||||
* @param errorHandler the handler to use if logging raised an exception
|
||||
*/
|
||||
@SuppressWarnings("PMD.AvoidCatchingGenericException")
|
||||
public final void tryUpdate(DataLogger dataLogger, T object, ErrorHandler errorHandler) {
|
||||
public final void tryUpdate(EpilogueBackend backend, T object, ErrorHandler errorHandler) {
|
||||
if (m_disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
update(dataLogger, object);
|
||||
update(backend, object);
|
||||
} catch (Exception e) {
|
||||
errorHandler.handle(e, this);
|
||||
}
|
||||
@@ -98,10 +98,10 @@ public abstract class ClassSpecificLogger<T> {
|
||||
/**
|
||||
* Logs a sendable type.
|
||||
*
|
||||
* @param dataLogger the logger to log data into
|
||||
* @param backend the backend to log data into
|
||||
* @param sendable the sendable object to log
|
||||
*/
|
||||
protected void logSendable(DataLogger dataLogger, Sendable sendable) {
|
||||
protected void logSendable(EpilogueBackend backend, Sendable sendable) {
|
||||
if (sendable == null) {
|
||||
return;
|
||||
}
|
||||
@@ -110,7 +110,7 @@ public abstract class ClassSpecificLogger<T> {
|
||||
m_sendables.computeIfAbsent(
|
||||
sendable,
|
||||
s -> {
|
||||
var b = new LogBackedSendableBuilder(dataLogger);
|
||||
var b = new LogBackedSendableBuilder(backend);
|
||||
s.initSendable(b);
|
||||
return b;
|
||||
});
|
||||
|
||||
@@ -9,40 +9,40 @@ import edu.wpi.first.units.Unit;
|
||||
import edu.wpi.first.util.struct.Struct;
|
||||
import java.util.Collection;
|
||||
|
||||
/** A data logger is a generic interface for logging discrete data points. */
|
||||
public interface DataLogger {
|
||||
/** A backend is a generic interface for Epilogue to log discrete data points. */
|
||||
public interface EpilogueBackend {
|
||||
/**
|
||||
* Creates a data logger that logs to multiple backends at once. Data reads will still only occur
|
||||
* once; data is passed to all composed loggers at once.
|
||||
* Creates a backend that logs to multiple backends at once. Data reads will still only occur
|
||||
* once; data is passed to all composed backends at once.
|
||||
*
|
||||
* @param loggers the loggers to compose together
|
||||
* @return the multi logger
|
||||
* @param backends the backends to compose together
|
||||
* @return the multi backend
|
||||
*/
|
||||
static DataLogger multi(DataLogger... loggers) {
|
||||
return new MultiLogger(loggers);
|
||||
static EpilogueBackend multi(EpilogueBackend... backends) {
|
||||
return new MultiBackend(backends);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a lazy version of this logger. A lazy logger will only log data to a field when its
|
||||
* Creates a lazy version of this backend. A lazy backend will only log data to a field when its
|
||||
* value changes, which can help keep file size and bandwidth usage in check. However, there is an
|
||||
* additional CPU and memory overhead associated with tracking the current value of every logged
|
||||
* entry. The most surefire way to reduce CPU and memory usage associated with logging is to log
|
||||
* fewer things - which can be done by opting out of logging unnecessary data or increasing the
|
||||
* minimum logged importance level in the Epilogue configuration.
|
||||
*
|
||||
* @return the lazy logger
|
||||
* @return the lazy backend
|
||||
*/
|
||||
default DataLogger lazy() {
|
||||
return new LazyLogger(this);
|
||||
default EpilogueBackend lazy() {
|
||||
return new LazyBackend(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a logger that can be used to log nested data underneath a specific path.
|
||||
* Gets a backend that can be used to log nested data underneath a specific path.
|
||||
*
|
||||
* @param path the path to use for logging nested data under
|
||||
* @return the sub logger
|
||||
* @return the nested backend
|
||||
*/
|
||||
DataLogger getSubLogger(String path);
|
||||
EpilogueBackend getNested(String path);
|
||||
|
||||
/**
|
||||
* Logs a 32-bit integer data point.
|
||||
@@ -26,24 +26,24 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
/** A data logger implementation that saves information to a WPILib {@link DataLog} file on disk. */
|
||||
public class FileLogger implements DataLogger {
|
||||
/** A backend implementation that saves information to a WPILib {@link DataLog} file on disk. */
|
||||
public class FileBackend implements EpilogueBackend {
|
||||
private final DataLog m_dataLog;
|
||||
private final Map<String, DataLogEntry> m_entries = new HashMap<>();
|
||||
private final Map<String, SubLogger> m_subLoggers = new HashMap<>();
|
||||
private final Map<String, NestedBackend> m_subLoggers = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Creates a new file logger.
|
||||
* Creates a new file-based backend.
|
||||
*
|
||||
* @param dataLog the data log to save data to
|
||||
*/
|
||||
public FileLogger(DataLog dataLog) {
|
||||
this.m_dataLog = requireNonNullParam(dataLog, "dataLog", "FileLogger");
|
||||
public FileBackend(DataLog dataLog) {
|
||||
this.m_dataLog = requireNonNullParam(dataLog, "dataLog", "FileBackend");
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataLogger getSubLogger(String path) {
|
||||
return m_subLoggers.computeIfAbsent(path, k -> new SubLogger(k, this));
|
||||
public EpilogueBackend getNested(String path) {
|
||||
return m_subLoggers.computeIfAbsent(path, k -> new NestedBackend(k, this));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -11,36 +11,36 @@ import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* A data logger implementation that only logs data when it changes. Useful for keeping bandwidth
|
||||
* and file sizes down. However, because it still needs to check that data has changed, it cannot
|
||||
* avoid expensive sensor reads.
|
||||
* A backend implementation that only logs data when it changes. Useful for keeping bandwidth and
|
||||
* file sizes down. However, because it still needs to check that data has changed, it cannot avoid
|
||||
* expensive sensor reads.
|
||||
*/
|
||||
public class LazyLogger implements DataLogger {
|
||||
private final DataLogger m_logger;
|
||||
public class LazyBackend implements EpilogueBackend {
|
||||
private final EpilogueBackend m_backend;
|
||||
|
||||
// Keep a record of the most recent value written to each entry
|
||||
// Note that this may duplicate a lot of data, and will box primitives.
|
||||
private final Map<String, Object> m_previousValues = new HashMap<>();
|
||||
private final Map<String, SubLogger> m_subLoggers = new HashMap<>();
|
||||
private final Map<String, NestedBackend> m_subLoggers = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Creates a new lazy logger wrapper around another logger.
|
||||
* Creates a new lazy backend wrapper around another backend.
|
||||
*
|
||||
* @param logger the logger to delegate to
|
||||
* @param backend the backend to delegate to
|
||||
*/
|
||||
public LazyLogger(DataLogger logger) {
|
||||
this.m_logger = logger;
|
||||
public LazyBackend(EpilogueBackend backend) {
|
||||
this.m_backend = backend;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataLogger lazy() {
|
||||
public EpilogueBackend lazy() {
|
||||
// Already lazy, don't need to wrap it again
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataLogger getSubLogger(String path) {
|
||||
return m_subLoggers.computeIfAbsent(path, k -> new SubLogger(k, this));
|
||||
public EpilogueBackend getNested(String path) {
|
||||
return m_subLoggers.computeIfAbsent(path, k -> new NestedBackend(k, this));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -53,7 +53,7 @@ public class LazyLogger implements DataLogger {
|
||||
}
|
||||
|
||||
m_previousValues.put(identifier, value);
|
||||
m_logger.log(identifier, value);
|
||||
m_backend.log(identifier, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -66,7 +66,7 @@ public class LazyLogger implements DataLogger {
|
||||
}
|
||||
|
||||
m_previousValues.put(identifier, value);
|
||||
m_logger.log(identifier, value);
|
||||
m_backend.log(identifier, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -79,7 +79,7 @@ public class LazyLogger implements DataLogger {
|
||||
}
|
||||
|
||||
m_previousValues.put(identifier, value);
|
||||
m_logger.log(identifier, value);
|
||||
m_backend.log(identifier, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -92,7 +92,7 @@ public class LazyLogger implements DataLogger {
|
||||
}
|
||||
|
||||
m_previousValues.put(identifier, value);
|
||||
m_logger.log(identifier, value);
|
||||
m_backend.log(identifier, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -105,7 +105,7 @@ public class LazyLogger implements DataLogger {
|
||||
}
|
||||
|
||||
m_previousValues.put(identifier, value);
|
||||
m_logger.log(identifier, value);
|
||||
m_backend.log(identifier, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -118,7 +118,7 @@ public class LazyLogger implements DataLogger {
|
||||
}
|
||||
|
||||
m_previousValues.put(identifier, value);
|
||||
m_logger.log(identifier, value);
|
||||
m_backend.log(identifier, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -131,7 +131,7 @@ public class LazyLogger implements DataLogger {
|
||||
}
|
||||
|
||||
m_previousValues.put(identifier, value);
|
||||
m_logger.log(identifier, value);
|
||||
m_backend.log(identifier, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -144,7 +144,7 @@ public class LazyLogger implements DataLogger {
|
||||
}
|
||||
|
||||
m_previousValues.put(identifier, value);
|
||||
m_logger.log(identifier, value);
|
||||
m_backend.log(identifier, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -157,7 +157,7 @@ public class LazyLogger implements DataLogger {
|
||||
}
|
||||
|
||||
m_previousValues.put(identifier, value);
|
||||
m_logger.log(identifier, value);
|
||||
m_backend.log(identifier, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -170,7 +170,7 @@ public class LazyLogger implements DataLogger {
|
||||
}
|
||||
|
||||
m_previousValues.put(identifier, value);
|
||||
m_logger.log(identifier, value);
|
||||
m_backend.log(identifier, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -183,7 +183,7 @@ public class LazyLogger implements DataLogger {
|
||||
}
|
||||
|
||||
m_previousValues.put(identifier, value);
|
||||
m_logger.log(identifier, value);
|
||||
m_backend.log(identifier, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -196,7 +196,7 @@ public class LazyLogger implements DataLogger {
|
||||
}
|
||||
|
||||
m_previousValues.put(identifier, value);
|
||||
m_logger.log(identifier, value);
|
||||
m_backend.log(identifier, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -209,7 +209,7 @@ public class LazyLogger implements DataLogger {
|
||||
}
|
||||
|
||||
m_previousValues.put(identifier, value);
|
||||
m_logger.log(identifier, value);
|
||||
m_backend.log(identifier, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -222,7 +222,7 @@ public class LazyLogger implements DataLogger {
|
||||
}
|
||||
|
||||
m_previousValues.put(identifier, value);
|
||||
m_logger.log(identifier, value, struct);
|
||||
m_backend.log(identifier, value, struct);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -235,6 +235,6 @@ public class LazyLogger implements DataLogger {
|
||||
}
|
||||
|
||||
m_previousValues.put(identifier, value);
|
||||
m_logger.log(identifier, value, struct);
|
||||
m_backend.log(identifier, value, struct);
|
||||
}
|
||||
}
|
||||
@@ -18,24 +18,24 @@ import java.util.function.LongConsumer;
|
||||
import java.util.function.LongSupplier;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/** A sendable builder implementation that sends data to a {@link DataLogger}. */
|
||||
@SuppressWarnings("PMD.CouplingBetweenObjects") // most methods simply delegate to the logger
|
||||
/** A sendable builder implementation that sends data to a {@link EpilogueBackend}. */
|
||||
@SuppressWarnings("PMD.CouplingBetweenObjects") // most methods simply delegate to the backend
|
||||
public class LogBackedSendableBuilder implements SendableBuilder {
|
||||
private final DataLogger m_logger;
|
||||
private final EpilogueBackend m_backend;
|
||||
private final Collection<Runnable> m_updates = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Creates a new sendable builder that delegates writes to an underlying data logger.
|
||||
* Creates a new sendable builder that delegates writes to an underlying backend.
|
||||
*
|
||||
* @param logger the data logger to write the sendable data to
|
||||
* @param backend the backend to write the sendable data to
|
||||
*/
|
||||
public LogBackedSendableBuilder(DataLogger logger) {
|
||||
this.m_logger = logger;
|
||||
public LogBackedSendableBuilder(EpilogueBackend backend) {
|
||||
this.m_backend = backend;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSmartDashboardType(String type) {
|
||||
m_logger.log(".type", type);
|
||||
m_backend.log(".type", type);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -50,132 +50,132 @@ public class LogBackedSendableBuilder implements SendableBuilder {
|
||||
|
||||
@Override
|
||||
public void addBooleanProperty(String key, BooleanSupplier getter, BooleanConsumer setter) {
|
||||
m_updates.add(() -> m_logger.log(key, getter.getAsBoolean()));
|
||||
m_updates.add(() -> m_backend.log(key, getter.getAsBoolean()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publishConstBoolean(String key, boolean value) {
|
||||
m_logger.log(key, value);
|
||||
m_backend.log(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addIntegerProperty(String key, LongSupplier getter, LongConsumer setter) {
|
||||
m_updates.add(() -> m_logger.log(key, getter.getAsLong()));
|
||||
m_updates.add(() -> m_backend.log(key, getter.getAsLong()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publishConstInteger(String key, long value) {
|
||||
m_logger.log(key, value);
|
||||
m_backend.log(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addFloatProperty(String key, FloatSupplier getter, FloatConsumer setter) {
|
||||
m_updates.add(() -> m_logger.log(key, getter.getAsFloat()));
|
||||
m_updates.add(() -> m_backend.log(key, getter.getAsFloat()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publishConstFloat(String key, float value) {
|
||||
m_logger.log(key, value);
|
||||
m_backend.log(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addDoubleProperty(String key, DoubleSupplier getter, DoubleConsumer setter) {
|
||||
m_updates.add(() -> m_logger.log(key, getter.getAsDouble()));
|
||||
m_updates.add(() -> m_backend.log(key, getter.getAsDouble()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publishConstDouble(String key, double value) {
|
||||
m_logger.log(key, value);
|
||||
m_backend.log(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addStringProperty(String key, Supplier<String> getter, Consumer<String> setter) {
|
||||
if (getter != null) {
|
||||
m_updates.add(() -> m_logger.log(key, getter.get()));
|
||||
m_updates.add(() -> m_backend.log(key, getter.get()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publishConstString(String key, String value) {
|
||||
m_logger.log(key, value);
|
||||
m_backend.log(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addBooleanArrayProperty(
|
||||
String key, Supplier<boolean[]> getter, Consumer<boolean[]> setter) {
|
||||
if (getter != null) {
|
||||
m_updates.add(() -> m_logger.log(key, getter.get()));
|
||||
m_updates.add(() -> m_backend.log(key, getter.get()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publishConstBooleanArray(String key, boolean[] value) {
|
||||
m_logger.log(key, value);
|
||||
m_backend.log(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addIntegerArrayProperty(
|
||||
String key, Supplier<long[]> getter, Consumer<long[]> setter) {
|
||||
if (getter != null) {
|
||||
m_updates.add(() -> m_logger.log(key, getter.get()));
|
||||
m_updates.add(() -> m_backend.log(key, getter.get()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publishConstIntegerArray(String key, long[] value) {
|
||||
m_logger.log(key, value);
|
||||
m_backend.log(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addFloatArrayProperty(
|
||||
String key, Supplier<float[]> getter, Consumer<float[]> setter) {
|
||||
if (getter != null) {
|
||||
m_updates.add(() -> m_logger.log(key, getter.get()));
|
||||
m_updates.add(() -> m_backend.log(key, getter.get()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publishConstFloatArray(String key, float[] value) {
|
||||
m_logger.log(key, value);
|
||||
m_backend.log(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addDoubleArrayProperty(
|
||||
String key, Supplier<double[]> getter, Consumer<double[]> setter) {
|
||||
if (getter != null) {
|
||||
m_updates.add(() -> m_logger.log(key, getter.get()));
|
||||
m_updates.add(() -> m_backend.log(key, getter.get()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publishConstDoubleArray(String key, double[] value) {
|
||||
m_logger.log(key, value);
|
||||
m_backend.log(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addStringArrayProperty(
|
||||
String key, Supplier<String[]> getter, Consumer<String[]> setter) {
|
||||
if (getter != null) {
|
||||
m_updates.add(() -> m_logger.log(key, getter.get()));
|
||||
m_updates.add(() -> m_backend.log(key, getter.get()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publishConstStringArray(String key, String[] value) {
|
||||
m_logger.log(key, value);
|
||||
m_backend.log(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addRawProperty(
|
||||
String key, String typeString, Supplier<byte[]> getter, Consumer<byte[]> setter) {
|
||||
if (getter != null) {
|
||||
m_updates.add(() -> m_logger.log(key, getter.get()));
|
||||
m_updates.add(() -> m_backend.log(key, getter.get()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publishConstRaw(String key, String typeString, byte[] value) {
|
||||
m_logger.log(key, value);
|
||||
m_backend.log(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
// 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.epilogue.logging;
|
||||
|
||||
import edu.wpi.first.util.struct.Struct;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A backend implementation that delegates to other backends. Helpful for simultaneous logging to
|
||||
* multiple data stores at once.
|
||||
*/
|
||||
public class MultiBackend implements EpilogueBackend {
|
||||
private final List<EpilogueBackend> m_backends;
|
||||
private final Map<String, NestedBackend> m_nestedBackends = new HashMap<>();
|
||||
|
||||
// Use EpilogueBackend.multi(...) instead of instantiation directly
|
||||
MultiBackend(EpilogueBackend... backends) {
|
||||
this.m_backends = List.of(backends);
|
||||
}
|
||||
|
||||
@Override
|
||||
public EpilogueBackend getNested(String path) {
|
||||
return m_nestedBackends.computeIfAbsent(path, k -> new NestedBackend(k, this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, int value) {
|
||||
for (EpilogueBackend backend : m_backends) {
|
||||
backend.log(identifier, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, long value) {
|
||||
for (EpilogueBackend backend : m_backends) {
|
||||
backend.log(identifier, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, float value) {
|
||||
for (EpilogueBackend backend : m_backends) {
|
||||
backend.log(identifier, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, double value) {
|
||||
for (EpilogueBackend backend : m_backends) {
|
||||
backend.log(identifier, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, boolean value) {
|
||||
for (EpilogueBackend backend : m_backends) {
|
||||
backend.log(identifier, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, byte[] value) {
|
||||
for (EpilogueBackend backend : m_backends) {
|
||||
backend.log(identifier, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, int[] value) {
|
||||
for (EpilogueBackend backend : m_backends) {
|
||||
backend.log(identifier, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, long[] value) {
|
||||
for (EpilogueBackend backend : m_backends) {
|
||||
backend.log(identifier, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, float[] value) {
|
||||
for (EpilogueBackend backend : m_backends) {
|
||||
backend.log(identifier, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, double[] value) {
|
||||
for (EpilogueBackend backend : m_backends) {
|
||||
backend.log(identifier, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, boolean[] value) {
|
||||
for (EpilogueBackend backend : m_backends) {
|
||||
backend.log(identifier, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, String value) {
|
||||
for (EpilogueBackend backend : m_backends) {
|
||||
backend.log(identifier, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, String[] value) {
|
||||
for (EpilogueBackend backend : m_backends) {
|
||||
backend.log(identifier, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <S> void log(String identifier, S value, Struct<S> struct) {
|
||||
for (EpilogueBackend backend : m_backends) {
|
||||
backend.log(identifier, value, struct);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <S> void log(String identifier, S[] value, Struct<S> struct) {
|
||||
for (EpilogueBackend backend : m_backends) {
|
||||
backend.log(identifier, value, struct);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
// 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.epilogue.logging;
|
||||
|
||||
import edu.wpi.first.util.struct.Struct;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A data logger implementation that delegates to other loggers. Helpful for simultaneous logging to
|
||||
* multiple data stores at once.
|
||||
*/
|
||||
public class MultiLogger implements DataLogger {
|
||||
private final List<DataLogger> m_loggers;
|
||||
private final Map<String, SubLogger> m_subLoggers = new HashMap<>();
|
||||
|
||||
// Use DataLogger.multi(...) instead of instantiation directly
|
||||
MultiLogger(DataLogger... loggers) {
|
||||
this.m_loggers = List.of(loggers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataLogger getSubLogger(String path) {
|
||||
return m_subLoggers.computeIfAbsent(path, k -> new SubLogger(k, this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, int value) {
|
||||
for (DataLogger logger : m_loggers) {
|
||||
logger.log(identifier, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, long value) {
|
||||
for (DataLogger logger : m_loggers) {
|
||||
logger.log(identifier, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, float value) {
|
||||
for (DataLogger logger : m_loggers) {
|
||||
logger.log(identifier, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, double value) {
|
||||
for (DataLogger logger : m_loggers) {
|
||||
logger.log(identifier, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, boolean value) {
|
||||
for (DataLogger logger : m_loggers) {
|
||||
logger.log(identifier, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, byte[] value) {
|
||||
for (DataLogger logger : m_loggers) {
|
||||
logger.log(identifier, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, int[] value) {
|
||||
for (DataLogger logger : m_loggers) {
|
||||
logger.log(identifier, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, long[] value) {
|
||||
for (DataLogger logger : m_loggers) {
|
||||
logger.log(identifier, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, float[] value) {
|
||||
for (DataLogger logger : m_loggers) {
|
||||
logger.log(identifier, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, double[] value) {
|
||||
for (DataLogger logger : m_loggers) {
|
||||
logger.log(identifier, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, boolean[] value) {
|
||||
for (DataLogger logger : m_loggers) {
|
||||
logger.log(identifier, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, String value) {
|
||||
for (DataLogger logger : m_loggers) {
|
||||
logger.log(identifier, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(String identifier, String[] value) {
|
||||
for (DataLogger logger : m_loggers) {
|
||||
logger.log(identifier, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <S> void log(String identifier, S value, Struct<S> struct) {
|
||||
for (DataLogger logger : m_loggers) {
|
||||
logger.log(identifier, value, struct);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <S> void log(String identifier, S[] value, Struct<S> struct) {
|
||||
for (DataLogger logger : m_loggers) {
|
||||
logger.log(identifier, value, struct);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,27 +24,27 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A data logger implementation that sends data over network tables. Be careful when using this,
|
||||
* since sending too much data may cause bandwidth or CPU starvation.
|
||||
* A backend implementation that sends data over network tables. Be careful when using this, since
|
||||
* sending too much data may cause bandwidth or CPU starvation.
|
||||
*/
|
||||
public class NTDataLogger implements DataLogger {
|
||||
public class NTEpilogueBackend implements EpilogueBackend {
|
||||
private final NetworkTableInstance m_nt;
|
||||
|
||||
private final Map<String, Publisher> m_publishers = new HashMap<>();
|
||||
private final Map<String, SubLogger> m_subLoggers = new HashMap<>();
|
||||
private final Map<String, NestedBackend> m_nestedBackends = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Creates a data logger that sends information to NetworkTables.
|
||||
* Creates a logging backend that sends information to NetworkTables.
|
||||
*
|
||||
* @param nt the NetworkTable instance to use to send data to
|
||||
*/
|
||||
public NTDataLogger(NetworkTableInstance nt) {
|
||||
public NTEpilogueBackend(NetworkTableInstance nt) {
|
||||
this.m_nt = nt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataLogger getSubLogger(String path) {
|
||||
return m_subLoggers.computeIfAbsent(path, k -> new SubLogger(k, this));
|
||||
public EpilogueBackend getNested(String path) {
|
||||
return m_nestedBackends.computeIfAbsent(path, k -> new NestedBackend(k, this));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -9,21 +9,21 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A data logger that logs to an underlying logger, prepending all logged data with a specific
|
||||
* prefix. Useful for logging nested data structures.
|
||||
* A backend that logs to an underlying backend, prepending all logged data with a specific prefix.
|
||||
* Useful for logging nested data structures.
|
||||
*/
|
||||
public class SubLogger implements DataLogger {
|
||||
public class NestedBackend implements EpilogueBackend {
|
||||
private final String m_prefix;
|
||||
private final DataLogger m_impl;
|
||||
private final Map<String, SubLogger> m_subLoggers = new HashMap<>();
|
||||
private final EpilogueBackend m_impl;
|
||||
private final Map<String, NestedBackend> m_nestedBackends = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Creates a new sublogger underneath another logger.
|
||||
* Creates a new nested backed underneath another backend.
|
||||
*
|
||||
* @param prefix the prefix to append to all data logged in the sublogger
|
||||
* @param impl the data logger to log to
|
||||
* @param prefix the prefix to append to all data logged in the nested backend
|
||||
* @param impl the backend to log to
|
||||
*/
|
||||
public SubLogger(String prefix, DataLogger impl) {
|
||||
public NestedBackend(String prefix, EpilogueBackend impl) {
|
||||
// Add a trailing slash if not already present
|
||||
if (prefix.endsWith("/")) {
|
||||
this.m_prefix = prefix;
|
||||
@@ -34,8 +34,8 @@ public class SubLogger implements DataLogger {
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataLogger getSubLogger(String path) {
|
||||
return m_subLoggers.computeIfAbsent(path, k -> new SubLogger(k, this));
|
||||
public EpilogueBackend getNested(String path) {
|
||||
return m_nestedBackends.computeIfAbsent(path, k -> new NestedBackend(k, this));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -6,14 +6,14 @@ package edu.wpi.first.epilogue.logging;
|
||||
|
||||
import edu.wpi.first.util.struct.Struct;
|
||||
|
||||
/** Null data logger implementation that logs nothing. */
|
||||
public class NullLogger implements DataLogger {
|
||||
/** Null backend implementation that logs nothing. */
|
||||
public class NullBackend implements EpilogueBackend {
|
||||
/** Default constructor. */
|
||||
public NullLogger() {}
|
||||
public NullBackend() {}
|
||||
|
||||
@Override
|
||||
public DataLogger getSubLogger(String path) {
|
||||
// Since a sublogger would still log nothing and has no state, we can just return the same
|
||||
public EpilogueBackend getNested(String path) {
|
||||
// Since a nested backend would still log nothing and has no state, we can just return the same
|
||||
// null-logging implementation
|
||||
return this;
|
||||
}
|
||||
@@ -19,10 +19,10 @@ class ClassSpecificLoggerTest {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void update(DataLogger dataLogger, Point2d object) {
|
||||
dataLogger.log("x", object.x);
|
||||
dataLogger.log("y", object.y);
|
||||
dataLogger.log("dim", object.dim);
|
||||
protected void update(EpilogueBackend backend, Point2d object) {
|
||||
backend.log("x", object.x);
|
||||
backend.log("y", object.y);
|
||||
backend.log("dim", object.dim);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,14 +31,14 @@ class ClassSpecificLoggerTest {
|
||||
void testReadPrivate() {
|
||||
var point = new Point2d(1, 4, 2);
|
||||
var logger = new Point2d.Logger();
|
||||
var dataLog = new TestLogger();
|
||||
logger.update(dataLog.getSubLogger("Point"), point);
|
||||
var dataLog = new TestBackend();
|
||||
logger.update(dataLog.getNested("Point"), point);
|
||||
|
||||
assertEquals(
|
||||
List.of(
|
||||
new TestLogger.LogEntry<>("Point/x", 1.0),
|
||||
new TestLogger.LogEntry<>("Point/y", 4.0),
|
||||
new TestLogger.LogEntry<>("Point/dim", 2)),
|
||||
new TestBackend.LogEntry<>("Point/x", 1.0),
|
||||
new TestBackend.LogEntry<>("Point/y", 4.0),
|
||||
new TestBackend.LogEntry<>("Point/dim", 2)),
|
||||
dataLog.getEntries());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,36 +10,36 @@ import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import java.util.List;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class LazyLoggerTest {
|
||||
class LazyBackendTest {
|
||||
@Test
|
||||
void lazyOfLazyReturnsSelf() {
|
||||
var lazy = new LazyLogger(new NullLogger());
|
||||
var lazy = new LazyBackend(new NullBackend());
|
||||
assertSame(lazy, lazy.lazy());
|
||||
}
|
||||
|
||||
@Test
|
||||
void lazyInt() {
|
||||
var logger = new TestLogger();
|
||||
var lazy = new LazyLogger(logger);
|
||||
var backend = new TestBackend();
|
||||
var lazy = new LazyBackend(backend);
|
||||
|
||||
{
|
||||
// First time logging to "int" should go through
|
||||
lazy.log("int", 0);
|
||||
assertEquals(List.of(new TestLogger.LogEntry<>("int", 0)), logger.getEntries());
|
||||
assertEquals(List.of(new TestBackend.LogEntry<>("int", 0)), backend.getEntries());
|
||||
}
|
||||
|
||||
{
|
||||
// Logging the current value shouldn't go through
|
||||
lazy.log("int", 0);
|
||||
assertEquals(List.of(new TestLogger.LogEntry<>("int", 0)), logger.getEntries());
|
||||
assertEquals(List.of(new TestBackend.LogEntry<>("int", 0)), backend.getEntries());
|
||||
}
|
||||
|
||||
{
|
||||
// Logging a new value should go through
|
||||
lazy.log("int", 1);
|
||||
assertEquals(
|
||||
List.of(new TestLogger.LogEntry<>("int", 0), new TestLogger.LogEntry<>("int", 1)),
|
||||
logger.getEntries());
|
||||
List.of(new TestBackend.LogEntry<>("int", 0), new TestBackend.LogEntry<>("int", 1)),
|
||||
backend.getEntries());
|
||||
}
|
||||
|
||||
{
|
||||
@@ -47,10 +47,10 @@ class LazyLoggerTest {
|
||||
lazy.log("int", 0);
|
||||
assertEquals(
|
||||
List.of(
|
||||
new TestLogger.LogEntry<>("int", 0),
|
||||
new TestLogger.LogEntry<>("int", 1),
|
||||
new TestLogger.LogEntry<>("int", 0)),
|
||||
logger.getEntries());
|
||||
new TestBackend.LogEntry<>("int", 0),
|
||||
new TestBackend.LogEntry<>("int", 1),
|
||||
new TestBackend.LogEntry<>("int", 0)),
|
||||
backend.getEntries());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,10 +12,10 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@SuppressWarnings("PMD.TestClassWithoutTestCases") // This is not a test class!
|
||||
public class TestLogger implements DataLogger {
|
||||
public class TestBackend implements EpilogueBackend {
|
||||
public record LogEntry<T>(String identifier, T value) {}
|
||||
|
||||
private final Map<String, SubLogger> m_subLoggers = new HashMap<>();
|
||||
private final Map<String, NestedBackend> m_nestedBackends = new HashMap<>();
|
||||
|
||||
private final List<LogEntry<?>> m_entries = new ArrayList<>();
|
||||
|
||||
@@ -24,8 +24,8 @@ public class TestLogger implements DataLogger {
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataLogger getSubLogger(String path) {
|
||||
return m_subLoggers.computeIfAbsent(path, k -> new SubLogger(k, this));
|
||||
public EpilogueBackend getNested(String path) {
|
||||
return m_nestedBackends.computeIfAbsent(path, k -> new NestedBackend(k, this));
|
||||
}
|
||||
|
||||
@Override
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user