Skip to content

Commit 941ff3c

Browse files
authored
Merge branch 'wpilibsuite:main' into change-swervekinematics-overload
2 parents 237216d + 9a1b424 commit 941ff3c

File tree

77 files changed

+1176
-1068
lines changed

Some content is hidden

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

77 files changed

+1176
-1068
lines changed

Diff for: .github/workflows/comment-command.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ jobs:
4343
distribution: 'temurin'
4444
java-version: 17
4545
- name: Install wpiformat
46-
run: pip3 install wpiformat==2024.45
46+
run: pip3 install wpiformat==2024.50
4747
- name: Run wpiformat
4848
run: wpiformat
4949
- name: Run spotlessApply

Diff for: .github/workflows/gradle.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@ jobs:
1515
- container: wpilib/roborio-cross-ubuntu:2025-22.04
1616
artifact-name: Athena
1717
build-options: "-Ponlylinuxathena"
18-
- container: wpilib/raspbian-cross-ubuntu:bullseye-22.04
18+
- container: wpilib/raspbian-cross-ubuntu:bookworm-22.04
1919
artifact-name: Arm32
2020
build-options: "-Ponlylinuxarm32"
21-
- container: wpilib/aarch64-cross-ubuntu:bullseye-22.04
21+
- container: wpilib/aarch64-cross-ubuntu:bookworm-22.04
2222
artifact-name: Arm64
2323
build-options: "-Ponlylinuxarm64"
2424
- container: wpilib/ubuntu-base:22.04

Diff for: .github/workflows/lint-format.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ jobs:
2727
with:
2828
python-version: '3.12'
2929
- name: Install wpiformat
30-
run: pip3 install wpiformat==2024.45
30+
run: pip3 install wpiformat==2024.50
3131
- name: Run
3232
run: wpiformat
3333
- name: Check output
@@ -66,7 +66,7 @@ jobs:
6666
with:
6767
python-version: '3.12'
6868
- name: Install wpiformat
69-
run: pip3 install wpiformat==2024.45
69+
run: pip3 install wpiformat==2024.50
7070
- name: Create compile_commands.json
7171
run: |
7272
./gradlew generateCompileCommands -Ptoolchain-optional-roboRio

Diff for: .github/workflows/sentinel-build.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ jobs:
1919
- container: wpilib/roborio-cross-ubuntu:2025-22.04
2020
artifact-name: Athena
2121
build-options: "-Ponlylinuxathena"
22-
- container: wpilib/raspbian-cross-ubuntu:bullseye-22.04
22+
- container: wpilib/raspbian-cross-ubuntu:bookworm-22.04
2323
artifact-name: Arm32
2424
build-options: "-Ponlylinuxarm32"
25-
- container: wpilib/aarch64-cross-ubuntu:bullseye-22.04
25+
- container: wpilib/aarch64-cross-ubuntu:bookworm-22.04
2626
artifact-name: Arm64
2727
build-options: "-Ponlylinuxarm64"
2828
- container: wpilib/ubuntu-base:22.04

Diff for: buildSrc/build.gradle

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,5 @@ repositories {
99
}
1010
}
1111
dependencies {
12-
implementation "edu.wpi.first:native-utils:2025.7.1"
12+
implementation "edu.wpi.first:native-utils:2025.9.0"
1313
}

Diff for: developerRobot/README.md

+6-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,12 @@ This command builds everything.
99

1010
## Simulation
1111

12-
This command runs the C++ subproject on desktop.
12+
This command runs the Java project on desktop.
13+
```bash
14+
./gradlew developerRobot:run
15+
```
16+
17+
This command runs the C++ project on desktop.
1318
```bash
1419
./gradlew developerRobot:runCpp
1520
```

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

+38-12
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,12 @@
1111
import java.util.ArrayList;
1212
import java.util.Comparator;
1313
import java.util.HashMap;
14+
import java.util.LinkedHashSet;
1415
import java.util.List;
1516
import java.util.Map;
1617
import java.util.Set;
18+
import java.util.stream.Collectors;
19+
import java.util.stream.Stream;
1720
import javax.annotation.processing.AbstractProcessor;
1821
import javax.annotation.processing.RoundEnvironment;
1922
import javax.annotation.processing.SupportedAnnotationTypes;
@@ -90,15 +93,15 @@ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment
9093
e);
9194
});
9295

96+
var loggedTypes = getLoggedTypes(roundEnv);
97+
9398
// Handlers are declared in order of priority. If an element could be logged in more than one
9499
// way (eg a class implements both Sendable and StructSerializable), the order of the handlers
95100
// in this list will determine how it gets logged.
96101
m_handlers =
97102
List.of(
98103
new LoggableHandler(
99-
processingEnv,
100-
roundEnv.getElementsAnnotatedWith(
101-
Logged.class)), // prioritize epilogue logging over Sendable
104+
processingEnv, loggedTypes), // prioritize epilogue logging over Sendable
102105
new ConfiguredLoggerHandler(
103106
processingEnv, customLoggers), // then customized logging configs
104107
new ArrayHandler(processingEnv),
@@ -118,12 +121,39 @@ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment
118121
.findAny()
119122
.ifPresent(
120123
epilogue -> {
121-
processEpilogue(roundEnv, epilogue);
124+
processEpilogue(roundEnv, epilogue, loggedTypes);
122125
});
123126

124127
return false;
125128
}
126129

130+
/**
131+
* Gets the set of all loggable types in the compilation unit. A type is considered loggable if it
132+
* is directly annotated with {@code @Logged} or contains a field or method with a {@code @Logged}
133+
* annotation.
134+
*
135+
* @param roundEnv the compilation round environment
136+
* @return the set of all loggable types
137+
*/
138+
private Set<TypeElement> getLoggedTypes(RoundEnvironment roundEnv) {
139+
// Fetches everything annotated with @Logged; classes, methods, values, etc.
140+
var annotatedElements = roundEnv.getElementsAnnotatedWith(Logged.class);
141+
return Stream.concat(
142+
// 1. All type elements (classes, interfaces, or enums) with the @Logged annotation
143+
annotatedElements.stream()
144+
.filter(e -> e instanceof TypeElement)
145+
.map(e -> (TypeElement) e),
146+
// 2. All type elements containing a field or method with the @Logged annotation
147+
annotatedElements.stream()
148+
.filter(e -> e instanceof VariableElement || e instanceof ExecutableElement)
149+
.map(Element::getEnclosingElement)
150+
.filter(e -> e instanceof TypeElement)
151+
.map(e -> (TypeElement) e))
152+
.sorted(Comparator.comparing(e -> e.getSimpleName().toString()))
153+
.collect(
154+
Collectors.toCollection(LinkedHashSet::new)); // Collect to a set to avoid duplicates
155+
}
156+
127157
private boolean validateFields(Set<? extends Element> annotatedElements) {
128158
var fields =
129159
annotatedElements.stream()
@@ -340,7 +370,8 @@ private Map<DeclaredType, DeclaredType> processCustomLoggers(
340370
return customLoggers;
341371
}
342372

343-
private void processEpilogue(RoundEnvironment roundEnv, TypeElement epilogueAnnotation) {
373+
private void processEpilogue(
374+
RoundEnvironment roundEnv, TypeElement epilogueAnnotation, Set<TypeElement> loggedTypes) {
344375
var annotatedElements = roundEnv.getElementsAnnotatedWith(epilogueAnnotation);
345376

346377
List<String> loggerClassNames = new ArrayList<>();
@@ -358,12 +389,7 @@ private void processEpilogue(RoundEnvironment roundEnv, TypeElement epilogueAnno
358389
return;
359390
}
360391

361-
var classes =
362-
annotatedElements.stream()
363-
.filter(e -> e instanceof TypeElement)
364-
.map(e -> (TypeElement) e)
365-
.toList();
366-
for (TypeElement clazz : classes) {
392+
for (TypeElement clazz : loggedTypes) {
367393
try {
368394
warnOfNonLoggableElements(clazz);
369395
m_loggerGenerator.writeLoggerFile(clazz);
@@ -391,7 +417,7 @@ private void processEpilogue(RoundEnvironment roundEnv, TypeElement epilogueAnno
391417

392418
private void warnOfNonLoggableElements(TypeElement clazz) {
393419
var config = clazz.getAnnotation(Logged.class);
394-
if (config.strategy() == Logged.Strategy.OPT_IN) {
420+
if (config == null || config.strategy() == Logged.Strategy.OPT_IN) {
395421
// field and method validations will have already checked everything
396422
return;
397423
}

Diff for: epilogue-processor/src/main/java/edu/wpi/first/epilogue/processor/ElementHandler.java

+4-2
Original file line numberDiff line numberDiff line change
@@ -127,8 +127,10 @@ public String elementAccess(Element element) {
127127

128128
private static String fieldAccess(VariableElement field) {
129129
if (field.getModifiers().contains(Modifier.PRIVATE)) {
130-
// (com.example.Foo) $fooField.get(object)
131-
return "(" + field.asType() + ") $" + field.getSimpleName() + ".get(object)";
130+
// ((com.example.Foo) $fooField.get(object))
131+
// Extra parentheses so cast evaluates before appended methods
132+
// (e.g. when appending .getAsDouble())
133+
return "((" + field.asType() + ") $" + field.getSimpleName() + ".get(object))";
132134
} else {
133135
// object.fooField
134136
return "object." + field.getSimpleName();

Diff for: epilogue-processor/src/main/java/edu/wpi/first/epilogue/processor/LoggableHandler.java

+2-5
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
import javax.lang.model.element.ExecutableElement;
1616
import javax.lang.model.element.TypeElement;
1717
import javax.lang.model.element.VariableElement;
18-
import javax.lang.model.type.DeclaredType;
1918
import javax.lang.model.type.TypeKind;
2019
import javax.lang.model.type.TypeMirror;
2120

@@ -35,10 +34,8 @@ protected LoggableHandler(
3534

3635
@Override
3736
public boolean isLoggable(Element element) {
38-
var dataType = dataType(element);
39-
return dataType.getAnnotation(Logged.class) != null
40-
|| dataType instanceof DeclaredType decl
41-
&& decl.asElement().getAnnotation(Logged.class) != null;
37+
return m_processingEnv.getTypeUtils().asElement(dataType(element)) instanceof TypeElement t
38+
&& m_loggedTypes.contains(t);
4239
}
4340

4441
@Override

Diff for: epilogue-processor/src/main/java/edu/wpi/first/epilogue/processor/LoggerGenerator.java

+31
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import edu.wpi.first.epilogue.NotLogged;
1616
import java.io.IOException;
1717
import java.io.PrintWriter;
18+
import java.lang.annotation.Annotation;
1819
import java.util.ArrayDeque;
1920
import java.util.ArrayList;
2021
import java.util.Deque;
@@ -42,6 +43,33 @@ public class LoggerGenerator {
4243
LoggerGenerator::isBuiltInJavaMethod;
4344
private final ProcessingEnvironment m_processingEnv;
4445
private final List<ElementHandler> m_handlers;
46+
private final Logged m_defaultConfig =
47+
new Logged() {
48+
@Override
49+
public Class<? extends Annotation> annotationType() {
50+
return Logged.class;
51+
}
52+
53+
@Override
54+
public String name() {
55+
return "";
56+
}
57+
58+
@Override
59+
public Strategy strategy() {
60+
return Strategy.OPT_IN;
61+
}
62+
63+
@Override
64+
public Importance importance() {
65+
return Importance.DEBUG;
66+
}
67+
68+
@Override
69+
public Naming defaultNaming() {
70+
return Naming.USE_CODE_NAME;
71+
}
72+
};
4573

4674
public LoggerGenerator(ProcessingEnvironment processingEnv, List<ElementHandler> handlers) {
4775
this.m_processingEnv = processingEnv;
@@ -76,6 +104,9 @@ private static boolean isBuiltInJavaMethod(ExecutableElement e) {
76104
*/
77105
public void writeLoggerFile(TypeElement clazz) throws IOException {
78106
var config = clazz.getAnnotation(Logged.class);
107+
if (config == null) {
108+
config = m_defaultConfig;
109+
}
79110
boolean requireExplicitOptIn = config.strategy() == Logged.Strategy.OPT_IN;
80111

81112
Predicate<Element> notSkipped = LoggerGenerator::isNotSkipped;

Diff for: epilogue-processor/src/main/java/edu/wpi/first/epilogue/processor/SendableHandler.java

+30-14
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,32 @@
44

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

7+
import java.util.Optional;
78
import javax.annotation.processing.ProcessingEnvironment;
89
import javax.lang.model.element.Element;
10+
import javax.lang.model.element.TypeElement;
911
import javax.lang.model.type.TypeMirror;
1012

1113
public class SendableHandler extends ElementHandler {
12-
private final TypeMirror m_sendableType;
13-
private final TypeMirror m_commandType;
14-
private final TypeMirror m_subsystemType;
14+
private final Optional<TypeMirror> m_sendableType;
15+
private final Optional<TypeMirror> m_commandType;
16+
private final Optional<TypeMirror> m_subsystemType;
1517

1618
protected SendableHandler(ProcessingEnvironment processingEnv) {
1719
super(processingEnv);
1820

1921
m_sendableType =
20-
lookupTypeElement(processingEnv, "edu.wpi.first.util.sendable.Sendable").asType();
22+
Optional.ofNullable(
23+
lookupTypeElement(processingEnv, "edu.wpi.first.util.sendable.Sendable"))
24+
.map(TypeElement::asType);
2125
m_commandType =
22-
lookupTypeElement(processingEnv, "edu.wpi.first.wpilibj2.command.Command").asType();
26+
Optional.ofNullable(
27+
lookupTypeElement(processingEnv, "edu.wpi.first.wpilibj2.command.Command"))
28+
.map(TypeElement::asType);
2329
m_subsystemType =
24-
lookupTypeElement(processingEnv, "edu.wpi.first.wpilibj2.command.SubsystemBase").asType();
30+
Optional.ofNullable(
31+
lookupTypeElement(processingEnv, "edu.wpi.first.wpilibj2.command.SubsystemBase"))
32+
.map(TypeElement::asType);
2533
}
2634

2735
@Override
@@ -30,20 +38,28 @@ public boolean isLoggable(Element element) {
3038

3139
// Accept any sendable type. However, the log invocation will return null
3240
// for sendable types that should not be logged (commands, subsystems)
33-
return m_processingEnv.getTypeUtils().isAssignable(dataType, m_sendableType);
41+
return m_sendableType
42+
.map(t -> m_processingEnv.getTypeUtils().isAssignable(dataType, t))
43+
.orElse(false);
3444
}
3545

3646
@Override
3747
public String logInvocation(Element element) {
3848
var dataType = dataType(element);
3949

40-
if (m_processingEnv.getTypeUtils().isAssignable(dataType, m_commandType)
41-
|| m_processingEnv.getTypeUtils().isAssignable(dataType, m_subsystemType)) {
42-
// Do not log commands or subsystems via their sendable implementations
43-
// We accept all sendable objects to prevent them from being reported as not loggable,
44-
// but their sendable implementations do not include helpful information.
45-
// Users are free to provide custom logging implementations for commands, and tag their
46-
// subsystems with @Logged to log their contents automatically
50+
// Do not log commands or subsystems via their sendable implementations
51+
// We accept all sendable objects to prevent them from being reported as not loggable,
52+
// but their sendable implementations do not include helpful information.
53+
// Users are free to provide custom logging implementations for commands, and tag their
54+
// subsystems with @Logged to log their contents automatically
55+
if (m_commandType
56+
.map(t -> m_processingEnv.getTypeUtils().isAssignable(dataType, t))
57+
.orElse(false)) {
58+
return null;
59+
}
60+
if (m_subsystemType
61+
.map(t -> m_processingEnv.getTypeUtils().isAssignable(dataType, t))
62+
.orElse(false)) {
4763
return null;
4864
}
4965

Diff for: epilogue-processor/src/main/java/edu/wpi/first/epilogue/processor/StringUtils.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ public static String loggerClassName(TypeElement clazz) {
123123
String packageName = p.getQualifiedName().toString();
124124

125125
String className;
126-
if (config.name().isEmpty()) {
126+
if (config == null || config.name().isEmpty()) {
127127
className = String.join("$", nesting);
128128
} else {
129129
className = capitalize(config.name()).replaceAll(" ", "");

0 commit comments

Comments
 (0)