Skip to content

Commit c7eb394

Browse files
committed
Add client-jvm artifact
1 parent b79b40f commit c7eb394

14 files changed

Lines changed: 279 additions & 35 deletions

File tree

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
/.idea/assetWizardSettings.xml
1010
.DS_Store
1111
/build
12+
/client-jvm/build/
1213
/captures
1314
.externalNativeBuild
1415
.cxx
@@ -19,3 +20,6 @@
1920
/target
2021
/client/src/main/jniLibs
2122
/client/src/main/java/com/etebase/client/*.java
23+
/client-jvm/src/main/java/com/etebase/client/*.java
24+
/client-jvm/src/main/resources/com/etebase/client/native
25+

Cargo.lock

Lines changed: 3 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
2-
name = "etebase-android"
3-
description = "Android bindings to etebase-rs"
2+
name = "etebase-jni"
3+
description = "JNI bindings to etebase-rs (Android and desktop JVM)"
44
homepage = "https://www.etebase.com"
55
repository = "https://github.com/etesync/etebase-java"
66
version = "2.3.1"
@@ -18,12 +18,13 @@ env_logger = "^0.7"
1818

1919
[dependencies]
2020
log = "0.4.6"
21-
log-panics = "2.0"
22-
android_logger = "0.8"
21+
log-panics = { version = "2.0", optional = true }
22+
android_logger = { version = "0.8", optional = true }
2323
jni-sys = "0.3.0"
2424
etebase = { git = "https://github.com/etesync/etebase-rs", rev = "ff14aadd7d5bdefee084e8266f0d2642ed268834", default-features = false }
2525

2626

2727
[features]
2828
default = ["android"]
29-
android = []
29+
android = ["android_logger", "log-panics"]
30+
jvm = []

build.gradle

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
buildscript {
33
repositories {
44
google()
5-
jcenter()
5+
mavenCentral()
66
}
77
dependencies {
88
classpath "com.android.tools.build:gradle:4.0.0"
@@ -15,10 +15,13 @@ buildscript {
1515
allprojects {
1616
repositories {
1717
google()
18-
jcenter()
18+
mavenCentral()
1919
}
20+
group = "com.etebase"
21+
version = "2.3.2"
22+
ext.isSnapshot = version.endsWith("SNAPSHOT")
2023
}
2124

2225
task clean(type: Delete) {
2326
delete rootProject.buildDir
24-
}
27+
}

build.rs

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,40 @@
11
use flapigen::{JavaConfig, LanguageConfig};
2-
use std::{env, path::Path};
2+
use std::{env, path::{Path, PathBuf}};
33

44
fn main() {
55
env_logger::init();
6+
7+
let android = env::var_os("CARGO_FEATURE_ANDROID").is_some();
8+
let jvm = env::var_os("CARGO_FEATURE_JVM").is_some();
9+
if android && jvm {
10+
panic!("The `android` and `jvm` features are mutually exclusive");
11+
}
12+
613
let out_dir = env::var("OUT_DIR").unwrap();
714
let in_src = Path::new("src").join("java_glue.rs.in");
815
let out_src = Path::new(&out_dir).join("java_glue.rs");
9-
//ANCHOR: config
16+
17+
let (java_module, annotation_pkg): (PathBuf, &str) = if jvm {
18+
(PathBuf::from("client-jvm"), "com.etebase.client.annotation")
19+
} else {
20+
(PathBuf::from("client"), "androidx.annotation")
21+
};
22+
23+
let java_out = java_module
24+
.join("src")
25+
.join("main")
26+
.join("java")
27+
.join("com")
28+
.join("etebase")
29+
.join("client");
30+
1031
let swig_gen = flapigen::Generator::new(LanguageConfig::JavaConfig(
11-
JavaConfig::new(
12-
Path::new("client")
13-
.join("src")
14-
.join("main")
15-
.join("java")
16-
.join("com")
17-
.join("etebase")
18-
.join("client"),
19-
"com.etebase.client".into(),
20-
)
21-
.use_null_annotation_from_package("androidx.annotation".into()),
32+
JavaConfig::new(java_out, "com.etebase.client".into())
33+
.use_null_annotation_from_package(annotation_pkg.into()),
2234
))
2335
.merge_type_map("typemaps", include_str!("src/jni_typemaps.rs"))
2436
.remove_not_generated_files_from_output_directory(true)
2537
.rustfmt_bindings(true);
26-
//ANCHOR_END: config
27-
swig_gen.expand("android bindings", &in_src, &out_src);
38+
swig_gen.expand("etebase jni bindings", &in_src, &out_src);
2839
println!("cargo:rerun-if-changed={}", in_src.display());
2940
}

build.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ do
2929
cargo build --target ${target} --release
3030

3131
mkdir -p "$jniDir"
32-
cp "target/$target/release/libetebase_android.so" "$jniDir"
32+
cp "target/$target/release/libetebase_jni.so" "$jniDir"
3333
done
3434

3535
./gradlew clean build

client-jvm/build.gradle

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
apply plugin: 'java-library'
2+
apply plugin: 'maven-publish'
3+
apply plugin: 'signing'
4+
5+
java {
6+
sourceCompatibility = JavaVersion.VERSION_1_8
7+
targetCompatibility = JavaVersion.VERSION_1_8
8+
}
9+
10+
def copySharedJavaSources = tasks.register("copySharedJavaSources", Copy) {
11+
from('../client/src/main/java') {
12+
include 'com/etebase/client/exceptions/**'
13+
include 'com/etebase/client/http_bridge/**'
14+
}
15+
into layout.buildDirectory.dir("generated-src/shared/java")
16+
}
17+
18+
sourceSets {
19+
main {
20+
java {
21+
srcDir copySharedJavaSources
22+
}
23+
}
24+
}
25+
26+
repositories {
27+
mavenCentral()
28+
}
29+
30+
dependencies {
31+
def okhttp3Version = "3.12.1"
32+
api "com.squareup.okhttp3:logging-interceptor:$okhttp3Version"
33+
34+
testImplementation 'junit:junit:4.12'
35+
}
36+
37+
task javadocsJar(type: Jar, dependsOn: javadoc) {
38+
archiveClassifier.set('javadoc')
39+
from javadoc.destinationDir
40+
}
41+
42+
task sourcesJar(type: Jar) {
43+
archiveClassifier.set('sources')
44+
from sourceSets.main.allSource
45+
}
46+
47+
publishing {
48+
publications {
49+
release(MavenPublication) {
50+
from components.java
51+
52+
artifact javadocsJar
53+
artifact sourcesJar
54+
55+
pom {
56+
name = "Etebase JVM"
57+
description = "Etebase API client for desktop JVM (Compose Multiplatform, server, CLI)"
58+
url = "https://www.etebase.com"
59+
licenses {
60+
license {
61+
name = "BSD-3-Clause"
62+
url = "https://spdx.org/licenses/BSD-3-Clause.html"
63+
}
64+
}
65+
developers {
66+
developer {
67+
id = "tasn"
68+
name = "Tom Hacohen"
69+
email = "maven@stosb.com"
70+
}
71+
}
72+
scm {
73+
connection = "scm:git:git://github.com/etesync/etebase-java.git"
74+
developerConnection = "scm:git:ssh://github.com/etesync/etebase-java"
75+
url = "https://github.com/etesync/etebase-java"
76+
}
77+
}
78+
}
79+
}
80+
81+
repositories {
82+
maven {
83+
def releasesRepoUrl = "https://oss.sonatype.org/service/local/staging/deploy/maven2/"
84+
def snapshotsRepoUrl = "https://oss.sonatype.org/content/repositories/snapshots/"
85+
url = isSnapshot ? snapshotsRepoUrl : releasesRepoUrl
86+
credentials {
87+
username = project.findProperty('ossrhUsername') ?: ''
88+
password = project.findProperty('ossrhPassword') ?: ''
89+
}
90+
}
91+
}
92+
}
93+
94+
if (!isSnapshot) {
95+
signing {
96+
useGpgCmd()
97+
sign publishing.publications.release
98+
}
99+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.etebase.client.annotation;
2+
3+
import java.lang.annotation.Documented;
4+
import java.lang.annotation.ElementType;
5+
import java.lang.annotation.Retention;
6+
import java.lang.annotation.RetentionPolicy;
7+
import java.lang.annotation.Target;
8+
9+
@Documented
10+
@Retention(RetentionPolicy.CLASS)
11+
@Target({
12+
ElementType.METHOD,
13+
ElementType.PARAMETER,
14+
ElementType.FIELD,
15+
ElementType.LOCAL_VARIABLE,
16+
})
17+
public @interface NonNull {
18+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.etebase.client.annotation;
2+
3+
import java.lang.annotation.Documented;
4+
import java.lang.annotation.ElementType;
5+
import java.lang.annotation.Retention;
6+
import java.lang.annotation.RetentionPolicy;
7+
import java.lang.annotation.Target;
8+
9+
@Documented
10+
@Retention(RetentionPolicy.CLASS)
11+
@Target({
12+
ElementType.METHOD,
13+
ElementType.PARAMETER,
14+
ElementType.FIELD,
15+
ElementType.LOCAL_VARIABLE,
16+
})
17+
public @interface Nullable {
18+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package com.etebase.client.internal;
2+
3+
import java.io.IOException;
4+
import java.io.InputStream;
5+
import java.nio.file.Files;
6+
import java.nio.file.Path;
7+
import java.nio.file.StandardCopyOption;
8+
import java.util.Locale;
9+
10+
public final class NativeLoader {
11+
private static final String LIB_NAME = "etebase_jni";
12+
private static final String OVERRIDE_PROPERTY = "com.etebase.client.native.path";
13+
14+
private static final Object LOCK = new Object();
15+
private static volatile boolean loaded = false;
16+
17+
private NativeLoader() {
18+
}
19+
20+
public static void load() {
21+
if (loaded) return;
22+
synchronized (LOCK) {
23+
if (loaded) return;
24+
String override = System.getProperty(OVERRIDE_PROPERTY);
25+
if (override != null && !override.isEmpty()) {
26+
System.load(override);
27+
} else {
28+
loadFromClasspath();
29+
}
30+
loaded = true;
31+
}
32+
}
33+
34+
private static void loadFromClasspath() {
35+
String os = detectOs();
36+
String arch = detectArch();
37+
String libFile = libFileFor(os);
38+
String resource = "/com/etebase/client/native/" + os + "-" + arch + "/" + libFile;
39+
40+
try (InputStream in = NativeLoader.class.getResourceAsStream(resource)) {
41+
if (in == null) {
42+
throw new UnsatisfiedLinkError(
43+
"Etebase native library not found on classpath: " + resource
44+
+ " (detected os=" + os + " arch=" + arch + "). "
45+
+ "Either the com.etebase:client-jvm artifact is missing a native binary for this platform, "
46+
+ "or set -D" + OVERRIDE_PROPERTY + "=/absolute/path/to/lib to override."
47+
);
48+
}
49+
Path tmp = Files.createTempFile(LIB_NAME + "-", "-" + libFile);
50+
tmp.toFile().deleteOnExit();
51+
Files.copy(in, tmp, StandardCopyOption.REPLACE_EXISTING);
52+
System.load(tmp.toAbsolutePath().toString());
53+
} catch (IOException e) {
54+
UnsatisfiedLinkError err = new UnsatisfiedLinkError("Failed to extract Etebase native library from " + resource);
55+
err.initCause(e);
56+
throw err;
57+
}
58+
}
59+
60+
private static String detectOs() {
61+
String name = System.getProperty("os.name", "").toLowerCase(Locale.ROOT);
62+
if (name.contains("mac") || name.contains("darwin")) return "macos";
63+
if (name.contains("win")) return "windows";
64+
if (name.contains("nux") || name.contains("nix")) return "linux";
65+
throw new UnsatisfiedLinkError("Unsupported OS for Etebase: " + name);
66+
}
67+
68+
private static String detectArch() {
69+
String arch = System.getProperty("os.arch", "").toLowerCase(Locale.ROOT);
70+
if (arch.equals("amd64") || arch.equals("x86_64")) return "x86_64";
71+
if (arch.equals("aarch64") || arch.equals("arm64")) return "aarch64";
72+
throw new UnsatisfiedLinkError("Unsupported CPU architecture for Etebase: " + arch);
73+
}
74+
75+
private static String libFileFor(String os) {
76+
switch (os) {
77+
case "macos": return "lib" + LIB_NAME + ".dylib";
78+
case "linux": return "lib" + LIB_NAME + ".so";
79+
case "windows": return LIB_NAME + ".dll";
80+
default: throw new AssertionError(os);
81+
}
82+
}
83+
}

0 commit comments

Comments
 (0)