Custom Library in Java

Hi all,
This year, my team has made some pretty nifty stuff with software that we definitely want to use in the future. However, I want to avoid setting us up to just copy/paste code from GitHub to a new repo. I’m looking at building a library that could be shared and installed just like a regular vendordep. I’ve generated a new gradle java library project, but my major roadblock so far has been figuring out how to connect WPILib to the project correctly.

My code is all available here, and here’s a brief overview of my problem:

The LSP I use seems to recognize that I’ve got WPILib-related tools installed. It automatically imports classes like Rotation2d, and can show the tooltip javadocs for them. So it is (at least partially) connected to WPILib. The major issues come at runtime: I can use class constructors like new Rotation2d(x, y) and methods like Rotation2d.getCos(), but I can’t use constants like Rotation2d.kZero. I get an issue that the symbol is not found, but - just like the other methods/constructors - I still get an indication from the LSP that those constants are there. I get autocompletion with their names, and javadocs on hover.

I was trying originally to follow along with the posts from this thread on using the vendordep template, and they seem to have solved the problem. The only issue? The repository that was linked is no longer available :frowning:. All other repos that I’ve seen in my search are quite old, and don’t work anymore when I clone the repo and run ./gradlew build.

I’m definitely looking to hear any and all suggestions, but if you or your team have used a similar solution that works, please share. I should mention I’m writing code in Java, but I don’t know if this matters with dependencies or whatnots. Thanks!

2 Likes

Does this work?

You can delete all the native and FFI stuff if you don’t need it.

1 Like

That compiles, but I wasn’t sure if that was what I was “supposed” to use. @Thad_House mentioned in the aforementioned thread that it isn’t a great solution for the problem (if I’m reading that right). I also wasn’t sure what I was good to remove seeing as I won’t be writing any code in cpp. I guess I could technically run with it, but if I just have cpp files present that serve no purpose but to appease the compiler, it feels like I’m not going about this right.

1 Like

I can’t tell you anything about what you’re supposed to use, but you can get this working without any extraneous files, like this repo:

You can ignore the actual code inside the Java file - this was a half-completed project - but I was able to get it publishing to GitHub packages and then use the vendordep on another computer. Feel free to copy my work, just remember to go through files and change the project name and things like that (I don’t remember exactly where as this was a few months ago).

2 Likes

I’ll definitely run that, but just a question: what’s the purpose of including the ‘cpp’ plugin if you’re not writing any c++ code? Is it necessary to run the JNI stuff with wpilib?
I removed that plugin then deleted all the stuff that broke because of it which probably contributed to the whole “some stuff doesn’t work” thing. (I have no idea what I’m doing when it comes to gradle.)

1 Like

Jitpack works well for team libraries.

5 Likes

After dealing with a library for us in the past year (through Jitpack), I’d suggest to just go with a lib folder and copy it each time, or a GitHub template.
The hassle and wait each time you need to ship a new update to the library far outweigh the small effort of copying it over.

4 Likes

… I don’t know.
I tried to make minimum changes to the Gradle config because I don’t know that much about Gradle either, while still removing all the fluff outside the configuration files. You can try it without that plugin or I might just try it now and see what happens.

1 Like

Could you elaborate on what you mean? What’s so slow/difficult about a separate library, after regular set up?

1 Like

I’m talking specifically for our experience with Jitpack, though I’d assume it’d be similar in other ways.

Our general workflow for every small or urgent update to the library was something like this:

  • Create a PR
  • Commit changes
  • Merge
  • Create a new release
  • Wait about 1-2 minutes for Jitpack to build
  • Wait about 1 minute for the new build to download onto our computer
  • Deploy

This gets progressively harder if you need to test something in the library, and ever time create some small change to test with. You’d have to wait about 3 minutes for every single test.
Not to mention how much harder this could be if a change was needed mid-competition, without WiFi or time.
Perhaps there’s a better way I don’t know of.

1 Like

Only jitpack things that don’t change often enough for this to be a problem.

If none of your code qualifies, then you don’t need to jitpack anything!

1 Like

Agreed.
Though even upgrading libraries is an annoying task with Jitpack IMO. You gotta do it for both the Jitpack library and real code in the same release.

This is just how it goes when you have a separate dependency. Formally decoupling things only make sense if they really are changing at substantially different rates.

1 Like

Okay, so I think I’m definitely not understanding something here. To start from a blank slate, I figured I’d reclone the vendor template. This builds and tests just fine.

I then added this to the end of VendorJNI.java, after initialize():

/**
 * Runs a constructor that should access the Rotation2d class
 */
public static void tryRotation() {
  Rotation2d rotation2d = new Rotation2d();
}

I then added this to the end of the VendorJNITest.java’s jniLinkTest:

@Test
public void testRotation() {
  VendorJNI.tryRotation();
}

The code builds and runs tests, but the testRotation test fails:

VendorJNITest > rotationTest() FAILED
    java.lang.NoClassDefFoundError: us/hebi/quickbuf/ProtoMessage
        at edu.wpi.first.math.geometry.Rotation2d.<clinit>(Rotation2d.java:384)
        at com.vendor.jni.VendorJNI.tryRotation(VendorJNI.java:66)
        at com.vendor.jni.VendorJNITest.rotationTest(VendorJNITest.java:15)

        Caused by:
        java.lang.ClassNotFoundException: us.hebi.quickbuf.ProtoMessage
            at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
            at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
            at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:525)
            ... 3 more

For reference, here’s lines 383-384 of Rotation2d.java:

383  /** Rotation2d protobuf for serialization. */
384  public static final Rotation2dProto proto = new Rotation2dProto();

Apart from the aforementioned additions to VendorJNI.java and VendorJNITest.java, no code was changed to get these results, making me think that I’m definitely misusing this setup. On the other hand, the readme says:

A java library is built. This has access to all of wpilib, and also can JNI load the driver library.

It sure seems like I should be able to use WPILib classes, like Rotation2d. What am I missing here?

1 Like

I’ve looked into doing something similar a couple times and this is always where I landed, FWIW

2 Likes

And that’s probably what I’ll end up doing, to be honest; I’m just curious as to how I could do this. I’m not the most busy with school and figured I’d give it a go to see what I could make - just for the “learning experience”.

1 Like

This is too often true and I’ve been seduced by the ease of copying but in almost all instances, in the long run, multiple copies of anything creates much more pain.

Always make a link to reference something (code, procedures, documentation, etc.), if at all possible. Finding and maintaining all the copies is a maintenance nightmare not to mention the problem that “if data are in two (or more) places, one or the other or both are wrong.”

1 Like

I’d suggest getting a separate team lib working on jitpack, then, and not bothering with the vendordep template. This’ll give you the learning experience most relevant to projects in general (apart from WPILib extensions). The vendordep template is designed specifically to account for WPILib’s multi-language nature, but I doubt you’ll be writing a C++ version of your team lib.

1 Like

team100 also have an evergreen library. we manage it as a separate wpi/gradle project (and vscode folder) within a single monorepo. to use the library, we use the vscode “workspace” method to use two folders at the same time: one is the project folder, one is the lib folder. you also need to tell the project gradle about the src to build.

this way, we can have lots of little independent projects that all use the same lib, and since everything is in one repo, nothing is ever out of sync.

I’d recommend this method over the WPI vendordep method.

here’s an example little project, illustrating the workspace and gradle changes.

Update: After looking at PathPlanner’s build.gradle for reference, I found this line that wasn’t in the vendor-template build.gradle (and therefore not mine):

(in the dependencies section)

testRuntimeOnly 'us.hebi.quickbuf:quickbuf-runtime:1.3.2'

The version I copied has the newest version, 1.4 instead of 1.3.2

This worked like a charm and the tests work as they should. Should this be included in the vendor-template build.gradle by default, because it seems like using WPILib types in tests is a pretty common scenario?

I’m now going to try and make both a jitpack build as well as a vendor-template version now that I’ve got them working, because why not. Thanks to everybody who gave advice here so far!

2 Likes