Merge branch 'dev' into chore/rename

This commit is contained in:
zaaarf 2024-10-16 16:50:21 +02:00 committed by GitHub
commit 9972fc4185
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 157 additions and 27 deletions

View file

@ -23,26 +23,39 @@ jobs:
filename: codemp.dll filename: codemp.dll
- runner: macos-14 - runner: macos-14
target: darwin-arm64 target: darwin-arm64
filename: codemp.dylib filename: libcodemp.dylib
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable - uses: dtolnay/rust-toolchain@stable
- uses: arduino/setup-protoc@v3 - uses: arduino/setup-protoc@v3
with: with:
repo-token: ${{ secrets.GITHUB_TOKEN }} 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 - uses: actions/setup-java@v4
with: with:
distribution: 'temurin' distribution: 'temurin'
java-version: '11' java-version: '11'
- uses: gradle/actions/setup-gradle@v4 - uses: gradle/actions/setup-gradle@v4
with: with:
gradle-version: "8.10" # Quotes required to prevent YAML converting to number gradle-version: "8.10"
- run: gradle build
working-directory: dist/java working-directory: dist/java
- uses: actions/upload-artifact@v4
with:
name: codemp-java-${{ matrix.platform.target }}
path: dist/java/build/libs
- run: gradle publish - run: gradle publish
working-directory: dist/java working-directory: dist/java
env: env:
@ -50,3 +63,4 @@ jobs:
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }}
ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.MAVEN_CENTRAL_GPG_SECRET_KEY }} ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.MAVEN_CENTRAL_GPG_SECRET_KEY }}
ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.MAVEN_CENTRAL_GPG_PASSWORD }} ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.MAVEN_CENTRAL_GPG_PASSWORD }}

1
.gitignore vendored
View file

@ -28,6 +28,7 @@ dist/java/.gradle/
dist/java/.project dist/java/.project
dist/java/.settings/ dist/java/.settings/
dist/java/bin/ dist/java/bin/
dist/java/artifacts/
# intellij insists on creating the wrapper every time even if it's not strictly necessary # intellij insists on creating the wrapper every time even if it's not strictly necessary
dist/java/gradle/ dist/java/gradle/

16
dist/README.md vendored
View file

@ -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: 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. * Memory management is entirely delegated to the JVM's garbage collector using the `Cleaner` API.
* A more elegant solution than `Object.finalize()`, who is deprecated in newer Java versions, may be coming eventually. * 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`. * 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. * `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 ### 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 ### 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). > [!NOTE]
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. > 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
dist/java/build.gradle vendored
View file

@ -1,14 +1,104 @@
plugins { plugins {
id 'java-library' 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' id 'com.google.osdetector' version '1.7.3'
} }
group = 'mp.code' group = 'mp.code'
version = '0.7.3' 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 { java {
sourceCompatibility = targetCompatibility = JavaVersion.VERSION_11 sourceCompatibility = targetCompatibility = JavaVersion.VERSION_11
withSourcesJar()
withJavadocJar()
}
artifacts {
archives jar
archives sourcesJar
archives javadocJar
windowsJar(windowsJar)
macosJar(macosJar)
linuxJar(linuxJar)
multiplatformJar(multiplatformJar)
} }
repositories { repositories {
@ -29,17 +119,17 @@ tasks.register('cargoBuild', Exec) {
commandLine 'cargo', 'build', '--release', '--features=java' commandLine 'cargo', 'build', '--release', '--features=java'
} }
jar.archiveClassifier = osdetector.classifier
def rustDir = projectDir.toPath() def rustDir = projectDir.toPath()
.parent .parent
.parent .parent
.resolve('target') .resolve('target')
.resolve('release') .resolve('release')
.toFile() .toFile()
processResources {
tasks.register('nativeBuild', Jar) {
archiveClassifier = osdetector.classifier
dependsOn cargoBuild dependsOn cargoBuild
outputs.upToDateWhen { false } // no caching from sourceSets.main.runtimeClasspath
from(rustDir) { from(rustDir) {
include('*.dll') include('*.dll')
include('*.so') include('*.so')
@ -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 import com.vanniktech.maven.publish.SonatypeHost
mavenPublishing { mavenPublishing {
publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL, true) publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL, true)
signAllPublications() signAllPublications()
coordinates(project.group, rootProject.name, project.version) coordinates(project.group, rootProject.name, project.version)

View file

@ -70,6 +70,7 @@ public final class BufferController {
/** /**
* Tries to send a {@link TextChange} update. * Tries to send a {@link TextChange} update.
* @param change the update to send
* @throws ControllerException if the controller was stopped * @throws ControllerException if the controller was stopped
*/ */
public void send(TextChange change) throws ControllerException { public void send(TextChange change) throws ControllerException {
@ -81,6 +82,8 @@ public final class BufferController {
/** /**
* Registers a callback to be invoked whenever a {@link BufferUpdate} occurs. * 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. * 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) * @see Extensions#drive(boolean)
*/ */
public void callback(Consumer<BufferController> cb) { public void callback(Consumer<BufferController> cb) {

View file

@ -43,14 +43,15 @@ public final class CursorController {
return recv(this.ptr); 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. * Tries to send a {@link Selection} update.
* @param selection the update to send
* @throws ControllerException if the controller was stopped * @throws ControllerException if the controller was stopped
*/ */
public void send(Selection cursor) throws ControllerException { public void send(Selection selection) throws ControllerException {
send(this.ptr, cursor); send(this.ptr, selection);
} }
private static native void callback(long self, Consumer<CursorController> cb); private static native void callback(long self, Consumer<CursorController> cb);
@ -58,6 +59,8 @@ public final class CursorController {
/** /**
* Registers a callback to be invoked whenever a {@link Cursor} update occurs. * 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. * 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) * @see Extensions#drive(boolean)
*/ */
public void callback(Consumer<CursorController> cb) { public void callback(Consumer<CursorController> cb) {

View file

@ -33,7 +33,7 @@ public final class Extensions {
* <p> * <p>
* You may alternatively call this with true, in a separate and dedicated Java thread; * 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: * 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 * @param block true if it should use the current thread
*/ */
public static native void drive(boolean block); public static native void drive(boolean block);

View file

@ -198,6 +198,8 @@ public final class Workspace {
/** /**
* Registers a callback to be invoked whenever a new {@link Event} is ready to be received. * 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. * 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) * @see Extensions#drive(boolean)
*/ */
public void callback(Consumer<Workspace> cb) { public void callback(Consumer<Workspace> cb) {

View file

@ -3,9 +3,6 @@ package mp.code.data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.ToString; import lombok.ToString;
import mp.code.Extensions;
import java.util.OptionalLong;
/** /**
* A data class holding information about a text change. * A data class holding information about a text change.

View file

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