Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashSet;
Expand Down Expand Up @@ -43,6 +44,8 @@ public class AnnotationProcessor extends AbstractProcessor {
private static final String kClassSpecificLoggerFqn =
"org.wpilib.epilogue.logging.ClassSpecificLogger";
private static final String kLoggedFqn = "org.wpilib.epilogue.Logged";
public static final String V3_SCHEDULER_CLASS = "org.wpilib.command3.Scheduler";
public static final String V2_SCHEDULER_CLASS = "org.wpilib.command2.CommandScheduler";

private EpilogueGenerator m_epilogueGenerator;
private LoggerGenerator m_loggerGenerator;
Expand Down Expand Up @@ -115,13 +118,14 @@ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment

m_epilogueGenerator = new EpilogueGenerator(processingEnv, customLoggers);
m_loggerGenerator = new LoggerGenerator(processingEnv, m_handlers);
var commandFrameworks = checkLoadedCommandFrameworks();

annotations.stream()
.filter(ann -> kLoggedFqn.contentEquals(ann.getQualifiedName()))
.findAny()
.ifPresent(
epilogue -> {
processEpilogue(roundEnv, epilogue, loggedTypes);
processEpilogue(roundEnv, epilogue, loggedTypes, commandFrameworks);
});

return false;
Expand Down Expand Up @@ -377,7 +381,10 @@ private Map<DeclaredType, DeclaredType> processCustomLoggers(
}

private void processEpilogue(
RoundEnvironment roundEnv, TypeElement epilogueAnnotation, Set<TypeElement> loggedTypes) {
RoundEnvironment roundEnv,
TypeElement epilogueAnnotation,
Set<TypeElement> loggedTypes,
Collection<CommandFramework> commandFrameworks) {
var annotatedElements = roundEnv.getElementsAnnotatedWith(epilogueAnnotation);

List<String> loggerClassNames = new ArrayList<>();
Expand Down Expand Up @@ -418,7 +425,7 @@ private void processEpilogue(

// Sort alphabetically
mainRobotClasses.sort(Comparator.comparing(c -> c.getSimpleName().toString()));
m_epilogueGenerator.writeEpilogueFile(loggerClassNames, mainRobotClasses);
m_epilogueGenerator.writeEpilogueFile(loggerClassNames, mainRobotClasses, commandFrameworks);
}

private void warnOfNonLoggableElements(TypeElement clazz) {
Expand Down Expand Up @@ -452,4 +459,19 @@ private void warnOfNonLoggableElements(TypeElement clazz) {
}
}
}

private Collection<CommandFramework> checkLoadedCommandFrameworks() {
List<CommandFramework> commandFrameworks = new ArrayList<>();
if (processingEnv.getElementUtils().getTypeElement(V3_SCHEDULER_CLASS) != null) {
// Special case: allow the default v3 scheduler to be automatically logged.
// If the project has both v3 and v2
commandFrameworks.add(CommandFramework.V3);
} else if (processingEnv.getElementUtils().getTypeElement(V2_SCHEDULER_CLASS) != null) {
// Special case: allow the default v2 scheduler to be automatically logged
commandFrameworks.add(CommandFramework.V2);
} else {
// Neither command framework found, no automatic logging.
}
return commandFrameworks;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// 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 org.wpilib.epilogue.processor;

/**
* Represents the different WPILib command frameworks. Epilogue will detect the presence of these
* frameworks at compile time to add special handling, such as automatic logging of the scheduler.
*/
public enum CommandFramework {
/** The Commands V2 framework. */
V2,

/** The Commands V3 framework. */
V3,
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ public EpilogueGenerator(
*/
@SuppressWarnings("checkstyle:LineLength") // Source code templates exceed the line length limit
public void writeEpilogueFile(
List<String> loggerClassNames, Collection<TypeElement> mainRobotClasses) {
List<String> loggerClassNames,
Collection<TypeElement> mainRobotClasses,
Collection<CommandFramework> commandFrameworks) {
try {
var centralStore =
m_processingEnv.getFiler().createSourceFile("org.wpilib.epilogue.Epilogue");
Expand Down Expand Up @@ -167,6 +169,20 @@ public static boolean shouldLog(Logged.Importance importance) {
" "
+ StringUtils.loggerFieldName(mainRobotClass)
+ ".tryUpdate(config.backend.getNested(config.root), robot, config.errorHandler);");

if (commandFrameworks.contains(CommandFramework.V3)) {
// Special case: automatically log the default commands v3 scheduler object.
// Otherwise, users would need to either manually log it or store it in a logged
// field, which is bad UX.
// Note that the v2 scheduler isn't loggable, so we don't generate anything
out.println(
"""
if (config.automaticallyLogCommandScheduler) {
config.backend.getNested(config.root).log("Command Scheduler", org.wpilib.command3.Scheduler.getDefault(), org.wpilib.command3.Scheduler.proto);
}
""");
}

out.println(
" config.backend.log(\"Epilogue/Stats/Last Run\", (System.nanoTime() - start) / 1e6);");
out.println(" }");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import static com.google.testing.compile.CompilationSubject.assertThat;
import static com.google.testing.compile.Compiler.javac;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.wpilib.epilogue.processor.CompileTestOptions.kJavaVersionOptions;

import com.google.testing.compile.Compilation;
Expand Down Expand Up @@ -390,6 +391,79 @@ public static boolean shouldLog(Logged.Importance importance) {
assertGeneratedEpilogueContents(source, expected);
}

@Test
void commandsv3Scheduler() {
String schedulerSource =
"""
package org.wpilib.command3;

import org.wpilib.util.protobuf.*;
import us.hebi.quickbuf.*;

// Stub the scheduler and its protobuf logging so the shape is correct at compile time.
// We don't care about runtime behavior because we are only testing the contents of the
// generated code.
public interface Scheduler extends ProtobufSerializable {
static class ProtoScheduler extends ProtoMessage<ProtoScheduler> implements Cloneable {
@Override public ProtoScheduler clone() { return null; }
@Override public boolean equals(Object other) { return false; }
@Override public ProtoScheduler copyFrom(ProtoScheduler other) { return null; }
@Override public ProtoScheduler mergeFrom(ProtoSource other) { return null; }
@Override public ProtoScheduler clear() { return null; }
@Override public int computeSerializedSize() { return 0; }
@Override public void writeTo(ProtoSink output) {}
}

static Protobuf<Scheduler, ProtoScheduler> proto = null;

static Scheduler getDefault() {
return null;
}
}
""";

String robotSource =
"""
package org.wpilib.epilogue;

@Logged
public class Robot extends org.wpilib.framework.TimedRobot {}
""";

Compilation compilation =
javac()
.withOptions(kJavaVersionOptions)
.withProcessors(new AnnotationProcessor())
.compile(
JavaFileObjects.forSourceString("org.wpilib.epilogue.Robot", robotSource),
JavaFileObjects.forSourceString("org.wpilib.command3.Scheduler", schedulerSource));

assertThat(compilation).succeededWithoutWarnings();
var generatedFiles = compilation.generatedSourceFiles();
var epilogueFile =
generatedFiles.stream()
.filter(jfo -> jfo.getName().contains("Epilogue"))
.findFirst()
.orElseThrow(() -> new IllegalStateException("Epilogue file was not generated!"));

try {
var content = epilogueFile.getCharContent(false);

assertTrue(
content
.toString()
.contains(
"""
if (config.automaticallyLogCommandScheduler) {
config.backend.getNested(config.root).log("Command Scheduler", org.wpilib.command3.Scheduler.getDefault(), org.wpilib.command3.Scheduler.proto);
}
"""),
"Generated file did not contain the expected scheduler logging code:\n\n" + content);
} catch (IOException e) {
throw new RuntimeException(e);
}
}

private void assertGeneratedEpilogueContents(
String loggedClassContent, String loggerClassContent) {
Compilation compilation =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,16 @@ public class EpilogueConfiguration {
*/
public String root = "Robot";

/**
* Whether to automatically log the default commands v3 scheduler. If set to {@code true}, the
* scheduler will be logged under the "Command Scheduler" indentifier in the {@link #root root
* namespace}. If you want to manually log the scheduler with a different identifier, set this to
* {@code false} to avoid duplicating logged data.
*
* <p>Has no effect if the CommandsV3 vendordep is not present.
*/
public boolean automaticallyLogCommandScheduler = true;

/** Default constructor. */
public EpilogueConfiguration() {}
}
Loading