Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ci: rewrite java ci #48

Merged
merged 16 commits into from
Oct 16, 2024
Merged
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
28 changes: 21 additions & 7 deletions .github/workflows/java.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,30 +23,44 @@ jobs:
filename: codemp.dll
- runner: macos-14
target: darwin-arm64
filename: codemp.dylib
filename: libcodemp.dylib
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: arduino/setup-protoc@v3
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
- run: cargo build --release --features=java
- uses: actions/upload-artifact@v4
with:
name: codemp-java-${{ matrix.platform.target }}
path: target/release/${{ matrix.platform.filename }}

publish:
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
path: dist/java/artifacts
pattern: codemp-java-*
merge-multiple: true
- run: tree
working-directory: dist/java
- uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '11'
- uses: gradle/actions/setup-gradle@v4
with:
gradle-version: "8.10" # Quotes required to prevent YAML converting to number
- run: gradle build
gradle-version: "8.10"
working-directory: dist/java
- uses: actions/upload-artifact@v4
with:
name: codemp-java-${{ matrix.platform.target }}
path: dist/java/build/libs
- run: gradle publish
working-directory: dist/java
env:
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }}
ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.MAVEN_CENTRAL_GPG_SECRET_KEY }}
ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.MAVEN_CENTRAL_GPG_PASSWORD }}

1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ dist/java/.gradle/
dist/java/.project
dist/java/.settings/
dist/java/bin/
dist/java/artifacts/

# intellij insists on creating the wrapper every time even if it's not strictly necessary
dist/java/gradle/
Expand Down
16 changes: 11 additions & 5 deletions dist/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,20 @@ Thus, we also provide pre-made Java glue code, wrapping all native calls and def

The Java bindings have no known major quirk. However, here are a list of facts that are useful to know when developing with these:

* Memory management is entirely delegated to the JVM's garbage collector.
* A more elegant solution than `Object.finalize()`, who is deprecated in newer Java versions, may be coming eventually.
* Memory management is entirely delegated to the JVM's garbage collector using the `Cleaner` API.
* Because of this, we require Java 11 as minimum version: `Cleaner` was added in version 9. This should not be a problem, as IDEs tend to run on recent versions, but if there is actual demand for it we may add a Java 8-friendly version using `Object.finalize()` (which is deprecated in modern JDKs).
* Exceptions coming from the native side have generally been made checked to imitate Rust's philosophy with `Result`.
* `JNIException`s are however unchecked: there is nothing you can do to recover from them, as they usually represent a severe error in the glue code. If they arise, it's probably a bug.

### Using
`codemp` **will be available soon** as an artifact on [Maven Central](https://mvnrepository.com)
`codemp` is available on [Maven Central](https://central.sonatype.com/artifact/mp.code/codemp), with each officially supported OS as an archive classifier.

### Building
This is a [Gradle](https://gradle.org/) project: building requires having both Gradle and Cargo installed, as well as the JDK (any non-abandoned version).
Once you have all the requirements, building is as simple as running `gradle build`: the output is going to be a JAR under `build/libs`, which you can import into your classpath with your IDE of choice.
> [!NOTE]
> The following instructions assume `dist/java` as current working directory.

This is a [Gradle](https://gradle.org/) project, so you must have install `gradle` (as well as JDK 11 or higher) in order to build it.
- You can build a JAR without bundling the native library with `gradle build`.
- Otherwise, you can compile the project for your current OS and create a JAR that bundles the resulting binary with `gradle nativeBuild`; do note that this second way of building also requires Cargo and the relevant Rust toolchain.

In both cases, the output is going to be a JAR under `build/libs`, which you can import into your classpath with your IDE of choice.
116 changes: 110 additions & 6 deletions dist/java/build.gradle
Original file line number Diff line number Diff line change
@@ -1,14 +1,104 @@
plugins {
id 'java-library'
id "com.vanniktech.maven.publish" version "0.29.0"
id "com.vanniktech.maven.publish.base" version "0.30.0"
id 'com.google.osdetector' version '1.7.3'
}

group = 'mp.code'
version = '0.7.3'

tasks.register('windowsJar', Jar) {
outputs.upToDateWhen { false }
archiveClassifier = 'windows-x86_64'
from sourceSets.main.runtimeClasspath
from('artifacts') {
include('*.dll')
into('natives/')
}
doFirst {
if(!(new File('artifacts/codemp.dll').exists())) {
throw new GradleException("The required file does not exist!")
}
}
}

tasks.register('macosJar', Jar) {
outputs.upToDateWhen { false }
archiveClassifier = 'osx-aarch_64'
from sourceSets.main.runtimeClasspath
from('artifacts') {
include('*.dylib')
into('natives/')
}
doFirst {
if(!(new File('artifacts/libcodemp.dylib').exists())) {
throw new GradleException("The required file does not exist!")
}
}
}

tasks.register('linuxJar', Jar) {
outputs.upToDateWhen { false }
archiveClassifier = 'linux-x86_64'
from sourceSets.main.runtimeClasspath
from('artifacts') {
include('*.so')
into('natives/')
}
doFirst {
if(!(new File('artifacts/libcodemp.so').exists())) {
throw new GradleException("The required file does not exist! Maybe you need to `cargo build` the main library first?")
}
}
}

tasks.register('multiplatformJar', Jar) {
outputs.upToDateWhen { false }
archiveClassifier = 'all'
from sourceSets.main.runtimeClasspath
from('artifacts') {
include('*')
into('natives/')
}
}

configurations {
windowsJar {
canBeConsumed = true
canBeResolved = false
extendsFrom implementation, runtimeOnly
}
linuxJar {
canBeConsumed = true
canBeResolved = false
extendsFrom implementation, runtimeOnly
}
macosJar {
canBeConsumed = true
canBeResolved = false
extendsFrom implementation, runtimeOnly
}
multiplatformJar {
canBeConsumed = true
canBeResolved = false
extendsFrom implementation, runtimeOnly
}
}

java {
sourceCompatibility = targetCompatibility = JavaVersion.VERSION_11
withSourcesJar()
withJavadocJar()
}

artifacts {
archives jar
archives sourcesJar
archives javadocJar
windowsJar(windowsJar)
macosJar(macosJar)
linuxJar(linuxJar)
multiplatformJar(multiplatformJar)
}

repositories {
Expand All @@ -29,17 +119,17 @@ tasks.register('cargoBuild', Exec) {
commandLine 'cargo', 'build', '--release', '--features=java'
}

jar.archiveClassifier = osdetector.classifier

def rustDir = projectDir.toPath()
.parent
.parent
.resolve('target')
.resolve('release')
.toFile()
processResources {

tasks.register('nativeBuild', Jar) {
archiveClassifier = osdetector.classifier
dependsOn cargoBuild
outputs.upToDateWhen { false } // no caching
from sourceSets.main.runtimeClasspath
from(rustDir) {
include('*.dll')
include('*.so')
Expand All @@ -48,9 +138,23 @@ processResources {
}
}

publishing {
publications {
mavenJava(MavenPublication) {
artifact jar
artifact sourcesJar
artifact javadocJar
artifact windowsJar
artifact linuxJar
artifact macosJar
artifact multiplatformJar
}
}
}

import com.vanniktech.maven.publish.SonatypeHost
mavenPublishing {
publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL, true)
publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL, true)
signAllPublications()
coordinates(project.group, rootProject.name, project.version)

Expand Down
3 changes: 3 additions & 0 deletions dist/java/src/mp/code/BufferController.java
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ public BufferUpdate recv() throws ControllerException {

/**
* Tries to send a {@link TextChange} update.
* @param change the update to send
* @throws ControllerException if the controller was stopped
*/
public void send(TextChange change) throws ControllerException {
Expand All @@ -81,6 +82,8 @@ public void send(TextChange change) throws ControllerException {
/**
* Registers a callback to be invoked whenever a {@link BufferUpdate} occurs.
* This will not work unless a Java thread has been dedicated to the event loop.
* @param cb a {@link Consumer} that receives the controller when the change occurs;
* you should probably spawn a new thread in here, to avoid deadlocking
* @see Extensions#drive(boolean)
*/
public void callback(Consumer<BufferController> cb) {
Expand Down
9 changes: 6 additions & 3 deletions dist/java/src/mp/code/CursorController.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,21 +43,24 @@ public Cursor recv() throws ControllerException {
return recv(this.ptr);
}

private static native void send(long self, Selection cursor) throws ControllerException;
private static native void send(long self, Selection selection) throws ControllerException;

/**
* Tries to send a {@link Selection} update.
* @param selection the update to send
* @throws ControllerException if the controller was stopped
*/
public void send(Selection cursor) throws ControllerException {
send(this.ptr, cursor);
public void send(Selection selection) throws ControllerException {
send(this.ptr, selection);
}

private static native void callback(long self, Consumer<CursorController> cb);

/**
* Registers a callback to be invoked whenever a {@link Cursor} update occurs.
* This will not work unless a Java thread has been dedicated to the event loop.
* @param cb a {@link Consumer} that receives the controller when the change occurs;
* you should probably spawn a new thread in here, to avoid deadlocking
* @see Extensions#drive(boolean)
*/
public void callback(Consumer<CursorController> cb) {
Expand Down
2 changes: 1 addition & 1 deletion dist/java/src/mp/code/Extensions.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public final class Extensions {
* <p>
* You may alternatively call this with true, in a separate and dedicated Java thread;
* it will remain active in the background and act as event loop. Assign it like this:
* <p><code>new Thread(() -> Extensions.drive(true)).start();</code></p>
* <p><code>new Thread(() -&gt; Extensions.drive(true)).start();</code></p>
* @param block true if it should use the current thread
*/
public static native void drive(boolean block);
Expand Down
2 changes: 2 additions & 0 deletions dist/java/src/mp/code/Workspace.java
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,8 @@ public Event recv() throws ControllerException {
/**
* Registers a callback to be invoked whenever a new {@link Event} is ready to be received.
* This will not work unless a Java thread has been dedicated to the event loop.
* @param cb a {@link Consumer} that receives the controller when the change occurs;
* you should probably spawn a new thread in here, to avoid deadlocking
* @see Extensions#drive(boolean)
*/
public void callback(Consumer<Workspace> cb) {
Expand Down
3 changes: 0 additions & 3 deletions dist/java/src/mp/code/data/TextChange.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@
import lombok.EqualsAndHashCode;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import mp.code.Extensions;

import java.util.OptionalLong;

/**
* A data class holding information about a text change.
Expand Down
4 changes: 2 additions & 2 deletions dist/java/src/mp/code/exceptions/ControllerException.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

/**
* An exception that may occur when a {@link mp.code.BufferController} or
* a {@link mp.code.CursorController} perform an illegal operation.
* It may also occur as a result of {@link mp.code.Workspace#event()}.
* a {@link mp.code.CursorController} or {@link mp.code.Workspace} (in the
* receiver part) perform an illegal operation.
*/
public abstract class ControllerException extends Exception {

Expand Down