Skip to content

Commit ef676ac

Browse files
committed
Merge branch 'main' into 2027
2 parents cf4f6ce + 6b8be31 commit ef676ac

File tree

93 files changed

+941
-387
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

93 files changed

+941
-387
lines changed

.clang-tidy

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ Checks:
4545
-clang-diagnostic-#warnings,
4646
-clang-diagnostic-pedantic,
4747
clang-analyzer-*,
48+
-clang-analyzer-optin.cplusplus.UninitializedObject,
49+
-clang-analyzer-security.FloatLoopCounter,
4850
cppcoreguidelines-slicing,
4951
google-build-namespaces,
5052
google-explicit-constructor,

benchmark/src/main/native/cpp/Main.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ void BM_Transform(benchmark::State& state) {
2222
auto transform = pose2 - pose1;
2323
return units::math::hypot(transform.X(), transform.Y()).value();
2424
}};
25+
// NOLINTNEXTLINE(clang-analyzer-deadcode.DeadStores)
2526
for (auto _ : state) {
2627
traveler.Solve(poses, iterations);
2728
}
@@ -33,6 +34,7 @@ void BM_Twist(benchmark::State& state) {
3334
auto twist = (pose2 - pose1).Log();
3435
return units::math::hypot(twist.dx, twist.dy).value();
3536
}};
37+
// NOLINTNEXTLINE(clang-analyzer-deadcode.DeadStores)
3638
for (auto _ : state) {
3739
traveler.Solve(poses, iterations);
3840
}

build.gradle

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,14 @@ plugins {
1313
id 'edu.wpi.first.wpilib.versioning.WPILibVersioningPlugin' version '2023.0.1'
1414
id 'edu.wpi.first.wpilib.repositories.WPILibRepositoriesPlugin' version '2025.0'
1515
id 'edu.wpi.first.NativeUtils' apply false
16-
id 'edu.wpi.first.GradleJni' version '1.1.0'
16+
id 'edu.wpi.first.GradleJni' version '1.2.0'
1717
id 'edu.wpi.first.GradleVsCode'
1818
id 'idea'
1919
id 'visual-studio'
2020
id 'net.ltgt.errorprone' version '4.3.0' apply false
21-
id 'com.gradleup.shadow' version '9.0.0' apply false
22-
id 'com.diffplug.spotless' version '7.2.1' apply false
23-
id 'com.github.spotbugs' version '6.2.3' apply false
21+
id 'com.gradleup.shadow' version '9.1.0' apply false
22+
id 'com.diffplug.spotless' version '8.0.0' apply false
23+
id 'com.github.spotbugs' version '6.4.2' apply false
2424
}
2525

2626
wpilibVersioning.buildServerMode = project.hasProperty('buildServer')
@@ -40,11 +40,11 @@ allprojects {
4040
}
4141
}
4242

43-
buildScan {
44-
termsOfServiceUrl = 'https://gradle.com/terms-of-service'
45-
termsOfServiceAgree = 'yes'
46-
47-
publishAlways()
43+
develocity {
44+
buildScan {
45+
termsOfUseUrl = "https://gradle.com/help/legal-terms-of-use"
46+
termsOfUseAgree = "yes"
47+
}
4848
}
4949

5050
import com.github.spotbugs.snom.Effort

cscore/examples/usbcvstream/usbcvstream.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
#include <opencv2/core/core.hpp>
66
#include <wpi/print.h>
77

8-
#include "cscore.h"
98
#include "cscore_cv.h"
109

1110
int main() {

cscore/src/main/native/cpp/cscore_c.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ static O* ConvertToC(std::vector<I>&& in, int* count) {
4444
// retain vector at end of returned array
4545
alignas(T) unsigned char buf[sizeof(T)];
4646
new (buf) T(std::move(in));
47-
std::memcpy(out + size * sizeof(O), buf, sizeof(T));
47+
std::memcpy(out + size, buf, sizeof(T));
4848

4949
return out;
5050
}
@@ -392,7 +392,7 @@ void CS_FreeEvents(CS_Event* arr, int count) {
392392
// destroy vector saved at end of array
393393
using T = std::vector<cs::RawEvent>;
394394
alignas(T) unsigned char buf[sizeof(T)];
395-
std::memcpy(buf, arr + count * sizeof(CS_Event), sizeof(T));
395+
std::memcpy(buf, arr + count, sizeof(T));
396396
reinterpret_cast<T*>(buf)->~T();
397397

398398
std::free(arr);

cscore/src/main/native/windows/UsbCameraImpl.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -707,8 +707,9 @@ void UsbCameraImpl::DeviceCacheProperty(
707707
}
708708

709709
NotifyPropertyCreated(*rawIndex, *rawPropPtr);
710-
if (perPropPtr && perIndex)
710+
if (perPropPtr && perIndex) {
711711
NotifyPropertyCreated(*perIndex, *perPropPtr);
712+
}
712713
}
713714

714715
CS_StatusValue UsbCameraImpl::DeviceProcessCommand(

epilogue-processor/src/main/java/edu/wpi/first/epilogue/processor/AnnotationProcessor.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,8 @@ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment
112112
new MeasureHandler(processingEnv),
113113
new PrimitiveHandler(processingEnv),
114114
new SupplierHandler(processingEnv),
115-
new StructHandler(processingEnv), // prioritize struct over sendable
115+
new StructHandler(processingEnv), // prioritize struct over sendable and protobuf
116+
new ProtobufHandler(processingEnv), // then protobuf
116117
new SendableHandler(processingEnv));
117118

118119
m_epiloguerGenerator = new EpilogueGenerator(processingEnv, customLoggers);
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// Copyright (c) FIRST and other WPILib contributors.
2+
// Open Source Software; you can modify and/or share it under the terms of
3+
// the WPILib BSD license file in the root directory of this project.
4+
5+
package edu.wpi.first.epilogue.processor;
6+
7+
import java.util.Set;
8+
import javax.annotation.processing.ProcessingEnvironment;
9+
import javax.lang.model.element.Element;
10+
import javax.lang.model.element.Modifier;
11+
import javax.lang.model.element.TypeElement;
12+
import javax.lang.model.element.VariableElement;
13+
import javax.lang.model.type.TypeMirror;
14+
import javax.lang.model.util.Elements;
15+
import javax.lang.model.util.Types;
16+
17+
/**
18+
* Supports protobuf serializable types. Protobuf-serializable types are loggable if they have a
19+
* public static final {@code proto} field of a type that inherits from {@code Protobuf}.
20+
*/
21+
public class ProtobufHandler extends ElementHandler {
22+
private final TypeMirror m_serializable;
23+
private final TypeElement m_protobufType;
24+
private final Types m_typeUtils;
25+
private final Elements m_elementUtils;
26+
27+
protected ProtobufHandler(ProcessingEnvironment processingEnv) {
28+
super(processingEnv);
29+
30+
m_serializable =
31+
processingEnv
32+
.getElementUtils()
33+
.getTypeElement("edu.wpi.first.util.protobuf.ProtobufSerializable")
34+
.asType();
35+
m_protobufType =
36+
processingEnv.getElementUtils().getTypeElement("edu.wpi.first.util.protobuf.Protobuf");
37+
m_typeUtils = processingEnv.getTypeUtils();
38+
m_elementUtils = processingEnv.getElementUtils();
39+
}
40+
41+
@Override
42+
public boolean isLoggable(Element element) {
43+
return isLoggableType(dataType(element));
44+
}
45+
46+
/**
47+
* Checks if a type is protobuf-serializable: implements the ProtobufSerializable marker interface
48+
* and has a `public static final proto` field of a type that inherits from Protobuf with a
49+
* compatible generic type bound.
50+
*
51+
* @param type The type to check
52+
* @return true if the type is protobuf-serializable, false otherwise
53+
*/
54+
public boolean isLoggableType(TypeMirror type) {
55+
var serializableType = m_typeUtils.erasure(type);
56+
var typeElement = m_elementUtils.getTypeElement(serializableType.toString());
57+
if (typeElement == null) {
58+
return false;
59+
}
60+
61+
// eg `Protobuf<Rotation2d, ?>` instead of the raw `Protobuf` type. The message type doesn't
62+
// really matter here; we can leave it as a wildcard.
63+
var sharpProtobufType =
64+
m_typeUtils.getDeclaredType(
65+
m_protobufType,
66+
typeElement.asType(), // the serializable type
67+
m_typeUtils.getWildcardType(
68+
m_elementUtils.getTypeElement("us.hebi.quickbuf.ProtoMessage").asType(), null));
69+
70+
boolean hasProto =
71+
typeElement.getEnclosedElements().stream()
72+
.filter(e -> e instanceof VariableElement)
73+
.map(e -> (VariableElement) e)
74+
.anyMatch(
75+
field -> {
76+
var nameMatch = field.getSimpleName().contentEquals("proto");
77+
var modifiersMatch =
78+
field
79+
.getModifiers()
80+
.containsAll(Set.of(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL));
81+
var typeMatch =
82+
m_typeUtils.isAssignable(
83+
m_typeUtils.erasure(field.asType()), sharpProtobufType);
84+
return nameMatch && modifiersMatch && typeMatch;
85+
});
86+
return m_typeUtils.isAssignable(type, m_serializable) && hasProto;
87+
}
88+
89+
public String protoAccess(TypeMirror serializableType) {
90+
var className = m_typeUtils.erasure(serializableType).toString();
91+
return className + ".proto";
92+
}
93+
94+
@Override
95+
public String logInvocation(Element element, TypeElement loggedClass) {
96+
return "backend.log(\"%s\", %s, %s)"
97+
.formatted(
98+
loggedName(element),
99+
elementAccess(element, loggedClass),
100+
protoAccess(dataType(element)));
101+
}
102+
}

epilogue-processor/src/main/java/edu/wpi/first/epilogue/processor/StructHandler.java

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,26 @@
44

55
package edu.wpi.first.epilogue.processor;
66

7+
import java.util.Set;
78
import javax.annotation.processing.ProcessingEnvironment;
89
import javax.lang.model.element.Element;
10+
import javax.lang.model.element.Modifier;
911
import javax.lang.model.element.TypeElement;
12+
import javax.lang.model.element.VariableElement;
13+
import javax.lang.model.type.ArrayType;
1014
import javax.lang.model.type.TypeMirror;
15+
import javax.lang.model.util.Elements;
1116
import javax.lang.model.util.Types;
1217

18+
/**
19+
* Supports struct serializable types. Struct-serializable types are loggable if they have a public
20+
* static final {@code struct} field of a type that inherits from {@code Struct}.
21+
*/
1322
public class StructHandler extends ElementHandler {
1423
private final TypeMirror m_serializable;
24+
private final TypeElement m_structType;
1525
private final Types m_typeUtils;
26+
private final Elements m_elementUtils;
1627

1728
protected StructHandler(ProcessingEnvironment processingEnv) {
1829
super(processingEnv);
@@ -21,16 +32,57 @@ protected StructHandler(ProcessingEnvironment processingEnv) {
2132
.getElementUtils()
2233
.getTypeElement("edu.wpi.first.util.struct.StructSerializable")
2334
.asType();
35+
m_structType =
36+
processingEnv.getElementUtils().getTypeElement("edu.wpi.first.util.struct.Struct");
2437
m_typeUtils = processingEnv.getTypeUtils();
38+
m_elementUtils = processingEnv.getElementUtils();
2539
}
2640

2741
@Override
2842
public boolean isLoggable(Element element) {
29-
return m_typeUtils.isAssignable(dataType(element), m_serializable);
43+
return isLoggableType(dataType(element));
3044
}
3145

46+
/**
47+
* Checks if a type is struct-serializable: implements the StructSerializable marker interface and
48+
* has a `public static final struct` field of a type that inherits from Struct with a compatible
49+
* generic type bound.
50+
*
51+
* @param type The type to check
52+
* @return true if the type is struct-serializable, false otherwise
53+
*/
3254
public boolean isLoggableType(TypeMirror type) {
33-
return m_typeUtils.isAssignable(type, m_serializable);
55+
TypeMirror serializableType;
56+
if (type instanceof ArrayType arr) {
57+
serializableType = arr.getComponentType();
58+
} else {
59+
serializableType = m_typeUtils.erasure(type);
60+
}
61+
var typeElement = m_elementUtils.getTypeElement(serializableType.toString());
62+
if (typeElement == null) {
63+
return false;
64+
}
65+
66+
// eg `Struct<Rotation2d>` instead of the raw `Struct` type
67+
var sharpStructType = m_typeUtils.getDeclaredType(m_structType, typeElement.asType());
68+
69+
boolean hasStruct =
70+
typeElement.getEnclosedElements().stream()
71+
.filter(e -> e instanceof VariableElement)
72+
.map(e -> (VariableElement) e)
73+
.anyMatch(
74+
field -> {
75+
var nameMatch = field.getSimpleName().contentEquals("struct");
76+
var modifiersMatch =
77+
field
78+
.getModifiers()
79+
.containsAll(Set.of(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL));
80+
var typeMatch =
81+
m_typeUtils.isAssignable(
82+
m_typeUtils.erasure(field.asType()), sharpStructType);
83+
return nameMatch && modifiersMatch && typeMatch;
84+
});
85+
return m_typeUtils.isAssignable(type, m_serializable) && hasStruct;
3486
}
3587

3688
public String structAccess(TypeMirror serializableType) {

epilogue-processor/src/test/java/edu/wpi/first/epilogue/processor/AnnotationProcessorTest.java

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1141,6 +1141,76 @@ public void update(EpilogueBackend backend, Example object) {
11411141
assertLoggerGenerates(source, expectedGeneratedSource);
11421142
}
11431143

1144+
@Test
1145+
void protobuf() {
1146+
String source =
1147+
"""
1148+
package edu.wpi.first.epilogue;
1149+
1150+
import edu.wpi.first.util.protobuf.Protobuf;
1151+
import edu.wpi.first.util.protobuf.ProtobufSerializable;
1152+
import java.util.List;
1153+
import us.hebi.quickbuf.*;
1154+
1155+
class ProtobufType implements ProtobufSerializable {
1156+
// Message type is necessary - Epilogue can't log with a wildcard message type in the proto
1157+
public static final Protobuf<ProtobufType, Message> proto = null; // value doesn't matter
1158+
1159+
static class Message extends ProtoMessage<Message> {
1160+
// Implement stubs for the abstract base class.
1161+
// This code never runs so actual implementations are unnecessary.
1162+
@Override
1163+
public Message copyFrom(Message other) { return null; }
1164+
@Override
1165+
public Message clear() { return null; }
1166+
@Override
1167+
public int computeSerializedSize() { return 0; }
1168+
@Override
1169+
public void writeTo(ProtoSink output) {}
1170+
@Override
1171+
public Message mergeFrom(ProtoSource input) { return null; }
1172+
@Override
1173+
public boolean equals(Object obj) { return false; }
1174+
@Override
1175+
public Message clone() { return null; }
1176+
}
1177+
}
1178+
1179+
@Logged
1180+
class Example {
1181+
ProtobufType x; // Should be logged
1182+
ProtobufType[] arr1; // Should not be logged
1183+
ProtobufType[][] arr2; // Should not be logged
1184+
List<ProtobufType> list; // Should not be logged
1185+
}
1186+
""";
1187+
1188+
String expectedGeneratedSource =
1189+
"""
1190+
package edu.wpi.first.epilogue;
1191+
1192+
import edu.wpi.first.epilogue.Logged;
1193+
import edu.wpi.first.epilogue.Epilogue;
1194+
import edu.wpi.first.epilogue.logging.ClassSpecificLogger;
1195+
import edu.wpi.first.epilogue.logging.EpilogueBackend;
1196+
1197+
public class ExampleLogger extends ClassSpecificLogger<Example> {
1198+
public ExampleLogger() {
1199+
super(Example.class);
1200+
}
1201+
1202+
@Override
1203+
public void update(EpilogueBackend backend, Example object) {
1204+
if (Epilogue.shouldLog(Logged.Importance.DEBUG)) {
1205+
backend.log("x", object.x, edu.wpi.first.epilogue.ProtobufType.proto);
1206+
}
1207+
}
1208+
}
1209+
""";
1210+
1211+
assertLoggerGenerates(source, expectedGeneratedSource);
1212+
}
1213+
11441214
@Test
11451215
void lists() {
11461216
String source =

0 commit comments

Comments
 (0)