Skip to content
Merged
16 changes: 16 additions & 0 deletions .github/workflows/release_java.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,12 @@ jobs:
include:
- os: ubuntu-latest
classifier: linux-x86_64
- os: ubuntu-latest
classifier: linux-x86_64-musl
- os: ubuntu-24.04-arm
classifier: linux-aarch_64
- os: ubuntu-24.04-arm
classifier: linux-aarch_64-musl
- os: windows-latest
classifier: windows-x86_64
- os: macos-latest
Expand Down Expand Up @@ -135,11 +139,21 @@ jobs:
with:
name: linux-x86_64-local-staging
path: ~/linux-x86_64-local-staging
- name: Download linux x86_64 (musl) staging directory
uses: actions/download-artifact@v5
with:
name: linux-x86_64-musl-local-staging
path: ~/linux-x86_64-musl-local-staging
- name: Download linux aarch_64 staging directory
uses: actions/download-artifact@v5
with:
name: linux-aarch_64-local-staging
path: ~/linux-aarch_64-local-staging
- name: Download linux aarch_64 (musl) staging directory
uses: actions/download-artifact@v5
with:
name: linux-aarch_64-musl-local-staging
path: ~/linux-aarch_64-musl-local-staging
- name: Download darwin staging directory
uses: actions/download-artifact@v5
with:
Expand All @@ -160,7 +174,9 @@ jobs:
python ./scripts/merge_local_staging.py $LOCAL_STAGING_DIR/staging \
~/windows-x86_64-local-staging/staging \
~/linux-x86_64-local-staging/staging \
~/linux-x86_64-musl-local-staging/staging \
~/linux-aarch_64-local-staging/staging \
~/linux-aarch_64-musl-local-staging/staging \
~/osx-x86_64-local-staging/staging \
~/osx-aarch_64-local-staging/staging

Expand Down
55 changes: 52 additions & 3 deletions bindings/java/src/main/java/org/apache/opendal/Environment.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Properties;

/**
Expand All @@ -31,6 +33,7 @@ public enum Environment {
INSTANCE;

public static final String UNKNOWN = "<unknown>";
private static final String LIBC_PROPERTY = "org.apache.opendal.libc";
private String classifier = UNKNOWN;
private String projectVersion = UNKNOWN;

Expand All @@ -44,8 +47,18 @@ public enum Environment {
throw new UncheckedIOException("cannot load environment properties file", e);
}

INSTANCE.classifier = detectClassifier(System.getProperty("os.name"), System.getProperty("os.arch"));
}

static String detectClassifier(String osName, String osArch) {
final String os = osName == null ? "" : osName.toLowerCase();
final String arch = osArch == null ? "" : osArch.toLowerCase();
final boolean musl = !os.startsWith("windows") && !os.startsWith("mac") && isMusl(arch);
return buildClassifier(os, arch, musl);
}

static String buildClassifier(String os, String arch, boolean musl) {
final StringBuilder classifier = new StringBuilder();
final String os = System.getProperty("os.name").toLowerCase();
if (os.startsWith("windows")) {
classifier.append("windows");
} else if (os.startsWith("mac")) {
Expand All @@ -54,13 +67,49 @@ public enum Environment {
classifier.append("linux");
}
classifier.append("-");
final String arch = System.getProperty("os.arch").toLowerCase();
if (arch.equals("aarch64")) {
classifier.append("aarch_64");
} else {
classifier.append("x86_64");
}
INSTANCE.classifier = classifier.toString();
if (classifier.toString().startsWith("linux-") && musl) {
classifier.append("-musl");
}
return classifier.toString();
}

static boolean isMusl(String osArch) {
final String override = System.getProperty(LIBC_PROPERTY);
if (override != null) {
final String libc = override.trim().toLowerCase();
if (libc.equals("musl")) {
return true;
}
if (libc.equals("gnu") || libc.equals("glibc")) {
return false;
}
}

final String loader = muslLoaderName(osArch);
if (loader == null) {
return false;
}
return Files.exists(Paths.get("/lib", loader)) || Files.exists(Paths.get("/usr/lib", loader));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you test this line works?

I suspect that with bundled jar the file doesn't exist on /lib but in the JAR package.

}

private static String muslLoaderName(String osArch) {
if (osArch == null) {
return null;
}
switch (osArch) {
case "aarch64":
return "ld-musl-aarch64.so.1";
case "x86_64":
case "amd64":
return "ld-musl-x86_64.so.1";
default:
return null;
}
}

/**
Expand Down
96 changes: 84 additions & 12 deletions bindings/java/src/main/java/org/apache/opendal/NativeLibrary.java
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ private enum LibraryState {
* <ul>
* <li>org.apache.opendal:opendal-{version}-linux-x86_64</li>
* <li>org.apache.opendal:opendal-{version}-linux-aarch_64</li>
* <li>org.apache.opendal:opendal-{version}-linux-x86_64-musl</li>
* <li>org.apache.opendal:opendal-{version}-linux-aarch_64-musl</li>
* <li>org.apache.opendal:opendal-{version}-osx-x86_64</li>
* <li>org.apache.opendal:opendal-{version}-osx-aarch_64</li>
* <li>org.apache.opendal:opendal-{version}-windows-x86_64</li>
Expand All @@ -77,6 +79,9 @@ public static void loadLibrary() {
} catch (IOException e) {
libraryLoaded.set(LibraryState.NOT_LOADED);
throw new UncheckedIOException("Unable to load the OpenDAL shared library", e);
} catch (RuntimeException | Error e) {
libraryLoaded.set(LibraryState.NOT_LOADED);
throw e;
Comment thread
Xuanwo marked this conversation as resolved.
Outdated
}
libraryLoaded.set(LibraryState.LOADED);
return;
Expand All @@ -103,22 +108,89 @@ private static void doLoadLibrary() throws IOException {
}

private static void doLoadBundledLibrary() throws IOException {
final String libraryPath = bundledLibraryPath();
try (final InputStream is = NativeObject.class.getResourceAsStream(libraryPath)) {
if (is == null) {
throw new IOException("cannot find " + libraryPath);
final String libraryName = System.mapLibraryName("opendal_java");
final String[] libraryPaths = bundledLibraryPaths(libraryName);

final UnsatisfiedLinkError[] linkErrors = new UnsatisfiedLinkError[libraryPaths.length];
for (int i = 0; i < libraryPaths.length; i++) {
final String libraryPath = libraryPaths[i];
try (final InputStream is = NativeObject.class.getResourceAsStream(libraryPath)) {
if (is == null) {
continue;
}

final File tmpFile = createTempLibraryFile(libraryName);
tmpFile.deleteOnExit();
Files.copy(is, tmpFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
try {
System.load(tmpFile.getAbsolutePath());
return;
} catch (UnsatisfiedLinkError e) {
linkErrors[i] = e;
}
}
}

final StringBuilder attempted = new StringBuilder();
for (int i = 0; i < libraryPaths.length; i++) {
if (i > 0) {
attempted.append(", ");
}
attempted.append(libraryPaths[i]);
}

final UnsatisfiedLinkError last = lastNonNull(linkErrors);
if (last != null) {
final UnsatisfiedLinkError e = new UnsatisfiedLinkError(
"Unable to load the OpenDAL shared library from classpath. Tried: " + attempted);
for (UnsatisfiedLinkError err : linkErrors) {
if (err != null) {
e.addSuppressed(err);
}
}
final int dot = libraryPath.indexOf('.');
final File tmpFile = File.createTempFile(libraryPath.substring(0, dot), libraryPath.substring(dot));
tmpFile.deleteOnExit();
Files.copy(is, tmpFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
System.load(tmpFile.getAbsolutePath());
throw e;
}
throw new IOException("cannot find bundled OpenDAL shared library in classpath. Tried: " + attempted);
}

private static String bundledLibraryPath() {
private static String[] bundledLibraryPaths(String libraryName) {
final String classifier = Environment.getClassifier();
final String libraryName = System.mapLibraryName("opendal_java");
return "/native/" + classifier + "/" + libraryName;
if (classifier.startsWith("linux-") && classifier.endsWith("-musl")) {
final String gnu = classifier.substring(0, classifier.length() - "-musl".length());
return new String[] {
"/native/" + classifier + "/" + libraryName,
"/native/" + gnu + "/" + libraryName,
};
}
if (classifier.startsWith("linux-")) {
return new String[] {
"/native/" + classifier + "/" + libraryName,
"/native/" + classifier + "-musl/" + libraryName,
};
}
return new String[] {"/native/" + classifier + "/" + libraryName};
}

private static File createTempLibraryFile(String libraryName) throws IOException {
final int dot = libraryName.lastIndexOf('.');
final String prefix;
final String suffix;
if (dot >= 0) {
prefix = libraryName.substring(0, dot);
suffix = libraryName.substring(dot);
} else {
prefix = libraryName;
suffix = null;
}
return File.createTempFile(prefix + "-", suffix);
}

private static UnsatisfiedLinkError lastNonNull(UnsatisfiedLinkError[] errors) {
for (int i = errors.length - 1; i >= 0; i--) {
if (errors[i] != null) {
return errors[i];
}
}
return null;
}
}
Comment thread
Xuanwo marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package org.apache.opendal;

import static org.assertj.core.api.Assertions.assertThat;

import org.junit.jupiter.api.Test;

public class EnvironmentTest {
@Test
public void testBuildClassifierLinuxGnuX8664() {
assertThat(Environment.buildClassifier("linux", "x86_64", false)).isEqualTo("linux-x86_64");
}

@Test
public void testBuildClassifierLinuxMuslX8664() {
assertThat(Environment.buildClassifier("linux", "x86_64", true)).isEqualTo("linux-x86_64-musl");
}

@Test
public void testBuildClassifierLinuxMuslAarch64() {
assertThat(Environment.buildClassifier("linux", "aarch64", true)).isEqualTo("linux-aarch_64-musl");
}

@Test
public void testBuildClassifierNonLinuxIgnoreMusl() {
assertThat(Environment.buildClassifier("mac os x", "x86_64", true)).isEqualTo("osx-x86_64");
assertThat(Environment.buildClassifier("windows", "x86_64", true)).isEqualTo("windows-x86_64");
}

@Test
public void testIsMuslOverride() {
final String key = "org.apache.opendal.libc";
final String previous = System.getProperty(key);
try {
System.setProperty(key, "musl");
assertThat(Environment.isMusl("x86_64")).isTrue();

System.setProperty(key, "gnu");
assertThat(Environment.isMusl("x86_64")).isFalse();
} finally {
if (previous == null) {
System.clearProperty(key);
} else {
System.setProperty(key, previous);
}
}
}
}

11 changes: 9 additions & 2 deletions bindings/java/tools/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,12 @@ def classifier_to_target(classifier: str) -> str:
return "x86_64-apple-darwin"
if classifier == "linux-aarch_64":
return "aarch64-unknown-linux-gnu"
if classifier == "linux-aarch_64-musl":
return "aarch64-unknown-linux-musl"
if classifier == "linux-x86_64":
return "x86_64-unknown-linux-gnu"
if classifier == "linux-x86_64-musl":
return "x86_64-unknown-linux-musl"
if classifier == "windows-x86_64":
return "x86_64-pc-windows-msvc"
raise Exception(f"Unsupported classifier: {classifier}")
Expand Down Expand Up @@ -82,8 +86,11 @@ def get_cargo_artifact_name(classifier: str) -> str:
cmd += ["--features", args.features]

if enable_zigbuild:
# Pin glibc to 2.17 if zigbuild has been enabled.
cmd += ["--target", f"{target}.2.17"]
# Pin glibc to 2.17 for gnu builds.
#
# Note: The `.2.17` suffix is a zig target detail and is only valid for gnu.
zig_target = f"{target}.2.17" if target.endswith("-gnu") else target
cmd += ["--target", zig_target]
else:
cmd += ["--target", target]

Expand Down
Loading