Skip to content

Commit

Permalink
package native libraries to jar using maven shade plugin
Browse files Browse the repository at this point in the history
Co-authored-by: niyid <neeyeed@gmail.com>
Co-authored-by: Sidney <j.argenio@studenti.unipi.it>
  • Loading branch information
3 people committed Mar 31, 2024
1 parent d65afb4 commit 45360d1
Show file tree
Hide file tree
Showing 10 changed files with 186 additions and 63 deletions.
25 changes: 21 additions & 4 deletions .classpath
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,40 @@
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
<classpathentry excluding="**" kind="src" output="target/classes" path="lib">
<attributes>
<attribute name="org.eclipse.jdt.launching.CLASSPATH_ATTR_LIBRARY_PATH_ENTRY" value="monero-java/build"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="optional" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<classpathentry kind="src" path="target/generated-sources/annotations">
<attributes>
<attribute name="ignore_optional_problems" value="true"/>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="m2e-apt" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
<classpathentry kind="src" output="target/test-classes" path="target/generated-test-sources/test-annotations">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="ignore_optional_problems" value="true"/>
<attribute name="m2e-apt" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
<classpathentry kind="output" path="target/classes"/>
</classpath>
3 changes: 1 addition & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
/lib/
/target/
/build/
**/pom.xml.versionsBackup
**/logs
/test_wallets
/external-libs/
/log_java_tests.txt
104 changes: 53 additions & 51 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,6 @@ A Java library for creating Monero applications using RPC and JNI bindings to [m
* Receive notifications when blocks are added to the chain or when wallets sync, send, or receive.
* Over 300 passing JUnit tests.

## Table of contents

* [Architecture](#architecture)
* [Sample code](#sample-code)
* [Documentation](#documentation)
* [Using monero-java in your project](#using-monero-java-in-your-project)
* [Building JNI shared libraries from source](#building-jni-shared-libraries-from-source)
* [Running JUnit tests](#running-junit-tests)
* [Related projects](#related-projects)
* [License](#license)
* [Donations](#donations)

## Architecture

<p align="center">
Expand Down Expand Up @@ -97,14 +85,6 @@ assertTrue(FUNDS_RECEIVED);
walletFull.close(true);
```

## Documentation

* [Javadoc](https://woodser.github.io/monero-java/allclasses.html)
* [API and model overview with visual diagrams](https://woodser.github.io/monero-java/monero-spec.pdf)
* [JUnit tests](src/test/java)
* [Using TOR](docs/tor.md)
* [monero-ts documentation](https://github.com/woodser/monero-ts#documentation) provides additional documentation which translates to monero-java

## Using monero-java in your project

#### For Maven, add to pom.xml:
Expand All @@ -121,21 +101,26 @@ walletFull.close(true);

`compile 'io.github.woodser:monero-java:0.8.14'`

You are now ready to use this library with [monerod](https://getmonero.org/resources/developer-guides/daemon-rpc.html) and [monero-wallet-rpc](https://getmonero.org/resources/developer-guides/wallet-rpc.html) endpoints.
> [!NOTE]
> If you're on Windows and want to use native wallets instead of monero-wallet-rpc, or if you want to process binary data, first install MSYS2:
> 1. Install [MSYS2](https://www.msys2.org/).
> 2. Environment variables > System variables > Path > Edit > New > C:\msys64\mingw64\bin
If you want to use client-side wallets, first [build the JNI shared libraries](#building-jni-shared-libraries-from-source).
## Documentation

#### If using RPC servers:
* [Javadoc](https://woodser.github.io/monero-java/allclasses.html)
* [API and model overview with visual diagrams](https://woodser.github.io/monero-java/monero-spec.pdf)
* [JUnit tests](src/test/java)
* [Using TOR](docs/tor.md)
* [monero-ts documentation](https://github.com/woodser/monero-ts#documentation) provides additional documentation which translates to monero-java

1. Download and install [Monero CLI](https://web.getmonero.org/downloads/).
2. Start monerod, e.g.: `./monerod --stagenet` (or use a remote daemon).
3. Start monero-wallet-rpc, e.g.: `./monero-wallet-rpc --daemon-address http://localhost:38081 --stagenet --rpc-bind-port 38083 --rpc-login rpc_user:abc123 --wallet-dir ./`
## Building native libraries from source

## Building JNI shared libraries from source
If you want to use native wallets instead of monero-wallet-rpc, or if you want to process binary data, native libraries must be used for your specific platform.

If you want to process binary data or use a client-side wallet instead of RPC, shared libraries must be built for your specific platform for this Java library to use. This project uses a C++ counterpart library, [monero-cpp](https://github.com/woodser/monero-cpp), to support JNI, which is included as a submodule in ./external/monero-cpp.
For convenience, native libraries for Linux, macOS, and Windows are distributed with monero-java, but they can be built independently from source:

### macOS & Linux
### Linux and macOS

1. Install [maven](https://maven.apache.org/download.cgi) for your system.
2. Install a Java JDK for your system, for example:<br>
Expand All @@ -147,11 +132,8 @@ If you want to process binary data or use a client-side wallet instead of RPC, s
4. `cd ./monero-java`
5. Install Maven dependencies: `mvn install`
6. Update submodules: `./bin/update_submodules.sh`
7. Build the monero-cpp submodule (located at ./external/monero-cpp) as a shared library by following [instructions](https://github.com/woodser/monero-cpp#using-monero-cpp-in-your-project) for your system.
8. Build shared libraries to ./build/: `./bin/build_libmonero_java.sh`
9. Run TestMoneroUtils.java JUnit tests to verify the shared libraries are working with Java JNI.
10. Add the shared libraries within ./build/ to your application's classpath.
7. Build the monero-cpp submodule (located at ./external/monero-cpp) as a native library by following [instructions](https://github.com/woodser/monero-cpp#using-monero-cpp-in-your-project) for your system.
8. Build native libraries to ./build/: `./bin/build_libmonero_java.sh`
### Windows
Expand All @@ -161,29 +143,49 @@ If you want to process binary data or use a client-side wallet instead of RPC, s
a. Download binary zip archive from https://maven.apache.org/download.cgi<br>
b. Unpack to C:\msys64\usr\local
4. Start MSYS2 MINGW64 or MSYS MINGW32 depending on your system.
5. Update packages: `pacman -Syu` and confirm at the prompts.
6. Install dependencies. During installation, use default=all by leaving the input blank and pressing enter.
4. Environment variables > System variables > Path > Edit > New > C:\msys64\mingw64\bin
5. Start MSYS2 MINGW64 or MSYS MINGW32 depending on your system and use for the following steps.
6. Update packages: `pacman -Syu` and confirm at the prompts.
7. Install dependencies. During installation, use default=all by leaving the input blank and pressing enter.
64-bit: `pacman -S mingw-w64-x86_64-toolchain make mingw-w64-x86_64-cmake git`
32-bit: `pacman -S mingw-w64-i686-toolchain make mingw-w64-i686-cmake git`
8. Install Java JDK:
```
curl -s "https://get.sdkman.io" | bash
sdk install java 21.0.2.fx-librca
```
9. Set environment variables (replace with your paths):
```
export MAVEN_HOME=/usr/local/apache-maven-3.x.x/
export PATH=$PATH:$JAVA_HOME/bin/:$MAVEN_HOME/bin/
```
10. Clone the project repository: `git clone https://github.com/woodser/monero-java.git`
11. `cd ./monero-java`
12. Install Maven dependencies: `mvn install`
13. Update submodules: `./bin/update_submodules.sh`
14. Build the monero-cpp submodule (located at ./external/monero-cpp) as a native library by following [instructions](https://github.com/woodser/monero-cpp#windows) for Windows.
15. Build native libraries to ./build/: `./bin/build_libmonero_java.sh`
### Loading native libraries
After building the native libraries to the ./build folder, add them to `PATH`, your application's classpath, or explictly load them in Java by calling:
```
System.load(/absolute/path/to/libmonero-cpp.dll);
System.load(/absolute/path/to/libmonero-java.dll);
```
Alternatively, you can bundle the libraries into monero-java's JAR:
7. Set environment variables:
1. Copy the libraries to their respective folder in ./lib.
2. `mvn install`
3. Force update Maven snapshots: `mvn clean install -U`
`export JAVA_HOME=/path/to/jdk/` (if not set during installation)<br>
`export MAVEN_HOME=/usr/local/apache-maven-3.x.x/`<br>
`export PATH=$PATH:$JAVA_HOME/bin/:$MAVEN_HOME/bin/`<br>
8. Clone the project repository: `git clone https://github.com/woodser/monero-java.git`
9. `cd ./monero-java`
10. Install Maven dependencies: `mvn install`
11. Update submodules: `./bin/update_submodules.sh`
12. Build the monero-cpp submodule (located at ./external/monero-cpp) as a shared library by following [instructions](https://github.com/woodser/monero-cpp#windows) for Windows.
13. Build shared libraries to ./build/: `./bin/build_libmonero_java.sh`
15. Run TestMoneroUtils.java JUnit tests to verify the shared libraries are working with Java JNI.
16. Add the shared libraries within ./build/ to your application's classpath.
You can verify the native libraries are working by running TestMoneroUtils.java.
### Memory Growth
## Memory Growth
If you see unrestricted memory growth using native bindings, consider applying [jemalloc](https://jemalloc.net/) to improve memory management with `malloc`. In many cases, this can completely resolve the memory growth.
Expand All @@ -193,7 +195,7 @@ For example: `export LD_PRELOAD=/path/to/libjemalloc.a` then run your app.
1. Clone the project repository: `git clone https://github.com/woodser/monero-java.git`
2. `cd monero-java`
3. If using JNI, [build JNI shared libraries from source](#building-jni-shared-libraries-from-source).
3. If using native libraries, first [build native libraries from source](#building-native-libraries-from-source).
3. Start RPC servers:
1. Download and install [Monero CLI](https://web.getmonero.org/downloads/).
2. Start monerod, e.g.: `./monerod --stagenet` (or use a remote daemon).
Expand Down
Empty file added lib/linux-arm64/.gitignore
Empty file.
Empty file added lib/linux-x86_64/.gitignore
Empty file.
Empty file added lib/mac-arm64/.gitignore
Empty file.
Empty file added lib/mac-x86_64/.gitignore
Empty file.
Empty file added lib/windows/.gitignore
Empty file.
27 changes: 27 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@
</properties>
<build>
<testSourceDirectory>${project.basedir}/src/test/java</testSourceDirectory>
<resources>
<resource>
<directory>lib</directory>
</resource>
</resources>
</build>

<dependencies>
Expand Down Expand Up @@ -201,6 +206,28 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.2</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<shadedClassifierName>shaded</shadedClassifierName>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>/lib/**</resource>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
Expand Down
90 changes: 84 additions & 6 deletions src/main/java/monero/common/MoneroUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,14 @@
import common.utils.GenUtils;
import common.utils.JsonUtils;
import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URI;
import java.nio.file.AccessDeniedException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
Expand Down Expand Up @@ -45,16 +50,89 @@ public static String getVersion() {
public static void tryLoadNativeLibrary() {
if (!MoneroUtils.isNativeLibraryLoaded()) {
try { MoneroUtils.loadNativeLibrary(); }
catch (UnsatisfiedLinkError e) { }
catch (Exception | UnsatisfiedLinkError e) { }
}
}

/**
* Load the native library.
*/
public static void loadNativeLibrary() {
String libName = (System.getProperty("os.name").toLowerCase().contains("windows") ? "lib" : "") + "monero-java";
System.loadLibrary(libName);

// try to load from java library path
try {
String libName = (System.getProperty("os.name").toLowerCase().contains("windows") ? "lib" : "") + "monero-java";
System.loadLibrary(libName);
} catch (Exception | UnsatisfiedLinkError e) {
// ignore error
}

// try to load from resources (e.g. in jar)
try {

// get system info
String osName = System.getProperty("os.name").toLowerCase();
String osArch = System.getProperty("os.arch").toLowerCase();

// get library file names and paths
String libraryPath = "/";
String libraryCppFile = null;
String libraryJavaFile = null;
String[] libraryFiles = null;
if (osName.contains("windows")) {
libraryPath += "windows/";
libraryFiles = new String[] { "libmonero-cpp.dll", "libmonero-cpp.dll.a", "libmonero-java.dll", "libmonero-java.dll.a" };
libraryCppFile = "libmonero-cpp.dll";
libraryJavaFile = "libmonero-java.dll";
} else if (osName.contains("linux")) {
libraryFiles = new String[] { "libmonero-cpp.so", "libmonero-java.so" };
libraryCppFile = "libmonero-cpp.so";
libraryJavaFile = "libmonero-java.so";
if (osArch.contains("x86_64")) {
libraryPath += "linux-x86_64/";
} else if (osArch.contains("aarch64")) {
libraryPath += "linux-arm64/";
}
} else if (osName.contains("mac")) {
libraryFiles = new String[] { "libmonero-cpp.dylib", "libmonero-java.dylib" };
libraryCppFile = "libmonero-cpp.dylib";
libraryJavaFile = "libmonero-java.dylib";
if (osArch.contains("x86_64")) {
libraryPath += "mac-x86_64/";
} else if (osArch.contains("aarch64")) {
libraryPath += "mac-arm64/";
}
} else {
throw new UnsupportedOperationException("Unsupported operating system: " + osName);
}

// copy all library files to temp directory
Path tempDirectory = Files.createTempDirectory("libmonero");
for (String libraryFile : libraryFiles) {
try (InputStream inputStream = MoneroUtils.class.getResourceAsStream(libraryPath + libraryFile); OutputStream outputStream = Files.newOutputStream(tempDirectory.resolve(libraryFile))) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
}
}

// load native libraries
try {
System.load(tempDirectory.resolve(libraryCppFile).toString());
System.load(tempDirectory.resolve(libraryJavaFile).toString());
} catch (Exception | UnsatisfiedLinkError e) {
e.printStackTrace();
}

// try to delete temporary files and folder
try {
for (String libraryFile : libraryFiles) Files.delete(tempDirectory.resolve(libraryFile));
Files.delete(tempDirectory);
} catch (AccessDeniedException e) {
//e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
}
}

/**
Expand Down

0 comments on commit 45360d1

Please sign in to comment.