[wpimath] Use jinja for codegen (#3574)

While not really needed for wpimath, it will make more complex codegen
in the future significantly easier.
This commit is contained in:
Peter Johnson
2021-09-17 00:10:29 -07:00
committed by GitHub
parent 725251d294
commit 263a248119
11 changed files with 143 additions and 124 deletions

View File

@@ -10,7 +10,7 @@ jobs:
include:
- os: ubuntu-latest
name: Linux
container: wpilib/roborio-cross-ubuntu:2021-18.04
container: wpilib/roborio-cross-ubuntu:2021-20.04
flags: ""
- os: macos-latest
name: macOS
@@ -26,6 +26,12 @@ jobs:
if [ "$RUNNER_OS" == "macOS" ]; then
brew install opencv
fi
- name: Set up Python 3.8
uses: actions/setup-python@v2
with:
python-version: 3.8
- name: Install jinja
run: python -m pip install jinja2
- name: configure
run: mkdir build && cd build && cmake ${{ matrix.flags }} ..
- name: build

View File

@@ -63,6 +63,8 @@ jobs:
sudo apt-get install -y clang-tidy-12 clang-format-12
- name: Install wpiformat
run: pip3 install wpiformat
- name: Install jinja
run: python -m pip install jinja2
- name: Create compile_commands.json
run: mkdir build-cmake && cd build-cmake && cmake -DWITH_OLD_COMMANDS=ON -DWITH_EXAMPLES=ON -DCMAKE_EXPORT_COMPILE_COMMANDS=YES ..
- name: List changed files

View File

@@ -16,7 +16,7 @@ jobs:
flags: "-DCMAKE_BUILD_TYPE=Ubsan"
name: "${{ matrix.name }}"
runs-on: ubuntu-latest
container: wpilib/roborio-cross-ubuntu:2021-18.04
container: wpilib/roborio-cross-ubuntu:2021-20.04
steps:
- uses: actions/checkout@v2
- name: Install Dependencies
@@ -27,6 +27,12 @@ jobs:
--install /usr/bin/gcc gcc /usr/bin/gcc-11 11 \
--slave /usr/bin/g++ g++ /usr/bin/g++-11
sudo update-alternatives --set gcc /usr/bin/gcc-11
- name: Set up Python 3.8
uses: actions/setup-python@v2
with:
python-version: 3.8
- name: Install jinja
run: python -m pip install jinja2
- name: configure
run: mkdir build && cd build && cmake ${{ matrix.flags }} ..
- name: build

View File

@@ -1,5 +1,14 @@
import edu.wpi.first.toolchain.*
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.hubspot.jinjava:jinjava:2.5.8'
}
}
plugins {
id 'base'
id 'edu.wpi.first.wpilib.versioning.WPILibVersioningPlugin' version '4.1.0'

View File

@@ -1,3 +1,6 @@
import com.hubspot.jinjava.Jinjava;
import com.hubspot.jinjava.JinjavaConfig;
ext {
useJava = true
useCpp = true
@@ -66,9 +69,8 @@ dependencies {
api "com.fasterxml.jackson.core:jackson-databind:2.10.0"
}
def wpilibNumberFileInput = file("src/generate/GenericNumber.java.in")
def natFileInput = file("src/generate/Nat.java.in")
def natGetterInput = file("src/generate/NatGetter.java.in")
def wpilibNumberFileInput = file("src/generate/GenericNumber.java.jinja")
def natFileInput = file("src/generate/Nat.java.jinja")
def wpilibNumberFileOutputDir = file("$buildDir/generated/java/edu/wpi/first/math/numbers")
def wpilibNatFileOutput = file("$buildDir/generated/java/edu/wpi/first/math/Nat.java")
def maxNum = 20
@@ -86,10 +88,17 @@ task generateNumbers() {
}
wpilibNumberFileOutputDir.mkdirs()
def config = new JinjavaConfig()
def jinjava = new Jinjava(config)
def template = wpilibNumberFileInput.text
for(i in 0..maxNum) {
def outputFile = new File(wpilibNumberFileOutputDir, "N${i}.java")
def read = wpilibNumberFileInput.text.replace('${num}', i.toString())
outputFile.write(read)
def replacements = new HashMap<String,?>()
replacements.put("num", i)
def output = jinjava.render(template, replacements)
outputFile.write(output)
}
}
}
@@ -97,7 +106,7 @@ task generateNumbers() {
task generateNat() {
description = "Generates Nat.java"
group = "WPILib"
inputs.files([natFileInput, natGetterInput])
inputs.file natFileInput
outputs.file wpilibNatFileOutput
dependsOn generateNumbers
@@ -106,19 +115,16 @@ task generateNat() {
wpilibNatFileOutput.delete()
}
def template = natFileInput.text + "\n"
def config = new JinjavaConfig()
def jinjava = new Jinjava(config)
def importsString = "";
def template = natFileInput.text
for(i in 0..maxNum) {
importsString += "import edu.wpi.first.math.numbers.N${i};\n"
template += natGetterInput.text.replace('${num}', i.toString()) + "\n"
}
template += "}\n" // Close the class body
def replacements = new HashMap<String,?>()
replacements.put("nums", 0..maxNum)
template = template.replace('{{REPLACEWITHIMPORTS}}', importsString)
wpilibNatFileOutput.write(template)
def output = jinjava.render(template, replacements)
wpilibNatFileOutput.write(output)
}
}

View File

@@ -1,5 +1,26 @@
# 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.
import os
import sys
from jinja2 import Environment, FileSystemLoader
def output(outPath, outfn, contents):
if not os.path.exists(outPath):
os.makedirs(outPath)
outpathname = f"{outPath}/{outfn}"
if os.path.exists(outpathname):
with open(outpathname, "r") as f:
if f.read() == contents:
return
# File either doesn't exist or has different contents
with open(outpathname, "w") as f:
f.write(contents)
def main():
@@ -8,51 +29,21 @@ def main():
dirname, _ = os.path.split(os.path.abspath(__file__))
cmake_binary_dir = sys.argv[1]
with open(f"{dirname}/src/generate/GenericNumber.java.in",
"r") as templateFile:
template = templateFile.read()
rootPath = f"{cmake_binary_dir}/generated/main/java/edu/wpi/first/math/numbers"
env = Environment(loader=FileSystemLoader(f"{dirname}/src/generate"),
autoescape=False,
keep_trailing_newline=True)
if not os.path.exists(rootPath):
os.makedirs(rootPath)
template = env.get_template("GenericNumber.java.jinja")
rootPath = f"{cmake_binary_dir}/generated/main/java/edu/wpi/first/math/numbers"
for i in range(MAX_NUM + 1):
new_contents = template.replace("${num}", str(i))
for i in range(MAX_NUM + 1):
contents = template.render(num=i)
output(rootPath, f"N{i}.java", contents)
if os.path.exists(f"{rootPath}/N{i}.java"):
with open(f"{rootPath}/N{i}.java", "r") as f:
if f.read() == new_contents:
continue
# File either doesn't exist or has different contents
with open(f"{rootPath}/N{i}.java", "w") as f:
f.write(new_contents)
with open(f"{dirname}/src/generate/Nat.java.in", "r") as templateFile:
template = templateFile.read()
outputPath = f"{cmake_binary_dir}/generated/main/java/edu/wpi/first/math/Nat.java"
with open(f"{dirname}/src/generate/NatGetter.java.in",
"r") as getterFile:
getter = getterFile.read()
importsString = ""
for i in range(MAX_NUM + 1):
importsString += f"import edu.wpi.first.math.numbers.N{i};\n"
template += getter.replace("${num}", str(i))
template += "}\n"
template = template.replace('{{REPLACEWITHIMPORTS}}', importsString)
if os.path.exists(outputPath):
with open(outputPath, "r") as f:
if f.read() == template:
return 0
# File either doesn't exist or has different contents
with open(outputPath, "w") as f:
f.write(template)
template = env.get_template("Nat.java.jinja")
rootPath = f"{cmake_binary_dir}/generated/main/java/edu/wpi/first/math"
contents = template.render(nums=range(MAX_NUM + 1))
output(rootPath, "Nat.java", contents)
if __name__ == "__main__":

View File

@@ -1,34 +0,0 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
package edu.wpi.first.math.numbers;
import edu.wpi.first.math.Nat;
import edu.wpi.first.math.Num;
/**
* A class representing the number ${num}.
*/
public final class N${num} extends Num implements Nat<N${num}> {
private N${num}() {
}
/**
* The integer this class represents.
*
* @return The literal number ${num}.
*/
@Override
public int getNum() {
return ${num};
}
/**
* The singleton instance of this class.
*/
public static final N${num} instance = new N${num}();
}

View File

@@ -0,0 +1,31 @@
// 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.math.numbers;
import edu.wpi.first.math.Nat;
import edu.wpi.first.math.Num;
/**
* A class representing the number {{ num }}.
*/
public final class N{{ num }} extends Num implements Nat<N{{ num }}> {
private N{{ num }}() {
}
/**
* The integer this class represents.
*
* @return The literal number {{ num }}.
*/
@Override
public int getNum() {
return {{ num }};
}
/**
* The singleton instance of this class.
*/
public static final N{{ num }} instance = new N{{ num }}();
}

View File

@@ -1,27 +0,0 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
package edu.wpi.first.math;
//CHECKSTYLE.OFF: ImportOrder
{{REPLACEWITHIMPORTS}}
//CHECKSTYLE.ON
/**
* A natural number expressed as a java class.
* The counterpart to {@link Num} that should be used as a concrete value.
*
* @param <T> The {@link Num} this represents.
*/
@SuppressWarnings({"MethodName", "unused"})
public interface Nat<T extends Num> {
/**
* The number this interface represents.
*
* @return The number backing this value.
*/
int getNum();

View File

@@ -0,0 +1,32 @@
// 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.math;
//CHECKSTYLE.OFF: ImportOrder
{% for num in nums %}
import edu.wpi.first.math.numbers.N{{ num }};
{%- endfor %}
//CHECKSTYLE.ON
/**
* A natural number expressed as a java class.
* The counterpart to {@link Num} that should be used as a concrete value.
*
* @param <T> The {@link Num} this represents.
*/
@SuppressWarnings({"MethodName", "unused"})
public interface Nat<T extends Num> {
/**
* The number this interface represents.
*
* @return The number backing this value.
*/
int getNum();
{% for num in nums %}
static Nat<N{{ num }}> N{{ num }}() {
return N{{ num }}.instance;
}
{% endfor %}
}

View File

@@ -1,3 +0,0 @@
static Nat<N${num}> N${num}() {
return N${num}.instance;
}