Skip to content

Added kotlin support and docs#112

Open
Daniel1464 wants to merge 8 commits intoMechanical-Advantage:mainfrom
Daniel1464:kotlin-docs
Open

Added kotlin support and docs#112
Daniel1464 wants to merge 8 commits intoMechanical-Advantage:mainfrom
Daniel1464:kotlin-docs

Conversation

@Daniel1464
Copy link

@Daniel1464 Daniel1464 commented Nov 9, 2024

Adds a "kotlin support" page within the docs that provides a kotlin replacement for the AutoLog annotation. Resolves #64 and #86.

Note: this probably should be merged after the 2025-docs-dev branch is merged.

jwbonner and others added 6 commits November 1, 2024 12:06
Adding the `new` keyword helps newer students read the code examples, especially considering how anonymous inner classes are a more obscure feature.
@Daniel1464 Daniel1464 changed the title Added docs for kotlin support Added kotlin support and docs Nov 9, 2024
@reecelikesramen
Copy link

reecelikesramen commented Mar 23, 2025

I was able to get Kotlin to work with the annotations and minimal changes with KAPT and this build.gradle:

plugins {
    id "java"
    id "edu.wpi.first.GradleRIO" version "2025.1.1"
    id "com.diffplug.spotless" version "7.0.1" // formatting
    id "org.jetbrains.kotlin.jvm" version "2.1.10" // kotlin support
    id "com.peterabeles.gversion" version "1.10" // for advantagekit
    // id "com.google.devtools.ksp" version "2.1.10-1.0.31" // for annotation processing
    id "org.jetbrains.kotlin.kapt" version "2.1.10"
}

java {
    sourceCompatibility = JavaVersion.VERSION_17
    targetCompatibility = JavaVersion.VERSION_17
}

def ROBOT_MAIN_CLASS = "com.eaganrobotics.frc.Main"

// Define my targets (RoboRIO) and artifacts (deployable files)
// This is added by GradleRIO"s backing project DeployUtils.
deploy {
    targets {
        roborio(getTargetTypeClass("RoboRIO")) {
            // Team number is loaded either from the .wpilib/wpilib_preferences.json
            // or from command line. If not found an exception will be thrown.
            // You can use getTeamOrDefault(team) instead of getTeamNumber if you
            // want to store a team number in this file.
            team = project.frc.getTeamNumber()
            debug = project.frc.getDebugOrDefault(false)

            artifacts {
                // First part is artifact name, 2nd is artifact type
                // getTargetTypeClass is a shortcut to get the class type using a string

                frcJava(getArtifactTypeClass("FRCJavaArtifact")) {
                    jvmArgs.add("-XX:+UnlockExperimentalVMOptions")
                    jvmArgs.add("-XX:GCTimeRatio=5")
                    jvmArgs.add("-XX:+UseSerialGC")
                    jvmArgs.add("-XX:MaxGCPauseMillis=50")

                    final MAX_JAVA_HEAP_SIZE_MB = 100;
                    jvmArgs.add("-Xmx" + MAX_JAVA_HEAP_SIZE_MB + "M")
                    jvmArgs.add("-Xms" + MAX_JAVA_HEAP_SIZE_MB + "M")
                    jvmArgs.add("-XX:+AlwaysPreTouch")
                }

                // Static files artifact
                frcStaticFileDeploy(getArtifactTypeClass("FileTreeArtifact")) {
                    files = project.fileTree("src/main/deploy")
                    directory = "/home/lvuser/deploy"
                    deleteOldFiles = true
                }
            }
        }
    }
}

def deployArtifact = deploy.targets.roborio.artifacts.frcJava

// Set to true to use debug for JNI.
wpi.java.debugJni = false

// Set this to true to enable desktop support.
def includeDesktopSupport = true

// Defining my dependencies. In this case, WPILib (+ friends), and vendor libraries.
// Also defines JUnit 5.
dependencies {
    annotationProcessor wpi.java.deps.wpilibAnnotations()
    implementation wpi.java.deps.wpilib()
    implementation wpi.java.vendor.java()

    roborioDebug wpi.java.deps.wpilibJniDebug(wpi.platforms.roborio)
    roborioDebug wpi.java.vendor.jniDebug(wpi.platforms.roborio)

    roborioRelease wpi.java.deps.wpilibJniRelease(wpi.platforms.roborio)
    roborioRelease wpi.java.vendor.jniRelease(wpi.platforms.roborio)

    nativeDebug wpi.java.deps.wpilibJniDebug(wpi.platforms.desktop)
    nativeDebug wpi.java.vendor.jniDebug(wpi.platforms.desktop)
    simulationDebug wpi.sim.enableDebug()

    nativeRelease wpi.java.deps.wpilibJniRelease(wpi.platforms.desktop)
    nativeRelease wpi.java.vendor.jniRelease(wpi.platforms.desktop)
    simulationRelease wpi.sim.enableRelease()

    testImplementation "org.junit.jupiter:junit-jupiter:5.10.1"
    testRuntimeOnly "org.junit.platform:junit-platform-launcher"

    implementation "org.jetbrains.kotlin:kotlin-reflect"
    implementation "org.jetbrains.kotlin:kotlin-stdlib"

    // advantagekit
    def akitJson = new groovy.json.JsonSlurper().parseText(new File(projectDir.getAbsolutePath() + "/vendordeps/AdvantageKit.json").text)
    // implementation "org.littletonrobotics.akit:akit-autolog:$akitJson.version"
    kapt "org.littletonrobotics.akit:akit-autolog:$akitJson.version"
}

test {
    useJUnitPlatform()
    systemProperty "junit.jupiter.extensions.autodetection.enabled", "true"
}

// Simulation configuration (e.g. environment variables).
wpi.sim.addGui().defaultEnabled = providers.environmentVariable("AKIT_SIM_MODE").map(env -> env.equals("SIM")).orElse(true)
wpi.sim.addDriverstation()

// Setting up my Jar File. In this case, adding all libraries into the main jar ("fat jar")
// in order to make them all available at runtime. Also adding the manifest so WPILib
// knows where to look for our Robot Class.
jar {
    from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } }
    from sourceSets.main.allSource
    manifest edu.wpi.first.gradlerio.GradleRIOPlugin.javaManifest(ROBOT_MAIN_CLASS)
    duplicatesStrategy = DuplicatesStrategy.INCLUDE
}

// Configure jar and deploy tasks
deployArtifact.jarTask = jar
wpi.java.configureExecutableTasks(jar)
wpi.java.configureTestTasks(test)

// Configure string concat to always inline compile
tasks.withType(JavaCompile) {
    options.compilerArgs.add "-XDstringConcat=inline"
}

// advantagekit replay watch
task(replayWatch, type: JavaExec) {
    mainClass = "org.littletonrobotics.junction.ReplayWatch"
    classpath = sourceSets.main.runtimeClasspath
}

// version control for advantagekit for event autodeploy
project.compileJava.dependsOn(createVersionFile)
gversion {
  srcDir       = "src/main/kotlin/"
  classPackage = "com.eaganrobotics.frc"
  className    = "BuildConstants"
  dateFormat   = "yyyy-MM-dd HH:mm:ss z"
  timeZone     = "America/Central" // Use preferred time zone
  indent       = "  "
  language     = "kotlin"
}

// event auto commit when working in branches prefixed with "event/"
task(eventDeploy) {
    doLast {
        if (project.gradle.startParameter.taskNames.any({ it.toLowerCase().contains("deploy") })) {
            def branchPrefix = "event/"
            def branch = "git branch --show-current".execute().text.trim()
            def commitMessage = "Update at "${new Date().toString()}""

            if (branch.startsWith(branchPrefix)) {
                exec {
                    workingDir(projectDir)
                    executable "git"
                    args "add", "-A"
                }
                exec {
                    workingDir(projectDir)
                    executable "git"
                    args "commit", "-m", commitMessage
                    ignoreExitValue = true
                }

                println "Committed to branch: "$branch""
                println "Commit message: "$commitMessage""
            } else {
                println "Not on an event branch, skipping commit"
            }
        } else {
            println "Not running deploy task, skipping commit"
        }
    }
}
createVersionFile.dependsOn(eventDeploy)

// formatting task
spotless {
  java {
    googleJavaFormat()
    formatJavadoc(true)
    reorderImports(true)
    targetExclude("**/build/**", "**/build-*/**", "src/main/java/frc/robot/BuildConstants.java")
    removeUnusedImports()
    trimTrailingWhitespace()
    endWithNewline()
    licenseHeader("// Copyright (c) 2025 FRC 2220\n// http://github.com/EaganRobotics\n//\n// Use of this source code is governed by an MIT-style\n// license that can be found in the LICENSE file at\n// the root directory of this project.\n\n")
    manageTrailingCommas(true)
  }
  kotlin {
    // version, style and all configurations here are optional
    ktfmt("0.54") {
      googleStyle()
      formatJavadoc(true)
      reorderImports(true)
      targetExclude("**/build/**", "**/build-*/**", "src/main/kotlin/com/eaganarobotics/frc/BuildConstants.kt")
      removeUnusedImports()
      trimTrailingWhitespace()
      endWithNewline()
      licenseHeader("// Copyright (c) 2025 FRC 2220\n// http://github.com/EaganRobotics\n//\n// Use of this source code is governed by an MIT-style\n// license that can be found in the LICENSE file at\n// the root directory of this project.\n\n")
      manageTrailingCommas(true)
    }
  }
  groovyGradle {
      target fileTree(".") {
          include "**/*.gradle"
          exclude "**/build/**", "**/build-*/**", "northstar/**"
      }
      greclipse()
      indentWithSpaces(4)
      trimTrailingWhitespace()
      endWithNewline()
  }
  json {
      target fileTree(".") {
          include "**/*.json"
          exclude "**/build/**", "**/build-*/**", "northstar/**"
      }
      gson().indentWithSpaces(2)
  }
  enforceCheck = false
}

and here's an IO class example:

interface LiftIO {

  companion object {
    @AutoLog
    open class LiftIOInputs {
      @JvmField var lowerLimit = false
      @JvmField var winchPosition = Radians.of(0.0)
      @JvmField var winchVelocity = RadiansPerSecond.of(0.0)
      @JvmField var winchAppliedVoltage = Volts.of(0.0)
      @JvmField var winchCurrent = Amps.of(0.0)
    }
  }

  fun updateInputs(inputs: LiftIOInputs): Unit {}

  fun setWinchOpenLoop(output: Voltage): Unit {}

  fun setWinchClosedLoop(position: Angle): Unit {}

  fun zeroWinchEncoder(): Unit {}
}

Just @ me if there are any questions! We're switching to kotlin for next season and AKit support was a hard stop for us.

@Daniel1464
Copy link
Author

Daniel1464 commented Mar 25, 2025

Yeah I was aware of the workaround where u use kapt and @JvmField your properties. I don't like it as much though because typing out @JvmField for everything feels annoying(and might not be intuitive for new teams) but it would be supported better.

Second thing: your inputs class shouldn't be in a companion object. Classes defined directly within other classes are static by default, so you can do this:

interface LiftIO {
   @AutoLog
   open class LiftIOInputs {
      @JvmField var lowerLimit = false
      @JvmField var winchPosition = Radians.of(0.0)
      @JvmField var winchVelocity = RadiansPerSecond.of(0.0)
      @JvmField var winchAppliedVoltage = Volts.of(0.0)
      @JvmField var winchCurrent = Amps.of(0.0)
   }

  fun updateInputs(inputs: LiftIOInputs) {}

  fun setWinchOpenLoop(output: Voltage) {}

  fun setWinchClosedLoop(position: Angle) {}

  fun zeroWinchEncoder() {}
}

(you also don't need the Unit return type since its basically 'void' in java, and it is returned by default if you dont specify a return type).
I personally prefer the style where the lift inputs and lift io are separate classes defined within the same file, so

@AutoLog
open class LiftIOInputs {
  @JvmField var lowerLimit = false
  @JvmField var winchPosition = Radians.of(0.0)
  @JvmField var winchVelocity = RadiansPerSecond.of(0.0)
  @JvmField var winchAppliedVoltage = Volts.of(0.0)
  @JvmField var winchCurrent = Amps.of(0.0)
}

interface LiftIO {
  fun updateInputs(inputs: LiftIOInputs) {}

  fun setWinchOpenLoop(output: Voltage) {}

  fun setWinchClosedLoop(position: Angle) {}

  fun zeroWinchEncoder() {}
}

@katzuv
Copy link
Contributor

katzuv commented Apr 4, 2025

Thank you for the work on that; it has been helpful to us.
I was wondering if there's a way to enable @AutoLogOutput for variables created in files outside classes?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AutoLog Annotation Kotlin Support

5 participants