-
Notifications
You must be signed in to change notification settings - Fork 741
Create New Presets
So you have a C/C++ library you would like to use from inside Java, but it is not yet available as part of the JavaCPP Presets? You have come to the right place!
Since all JavaCPP Presets are currently defined using a set of cppbuild.sh
Bash scripts and pom.xml
Maven build files, the recommended way to get started while obtaining the latest versions of the parent script and build files is by first cloning the Git repositories and installing JavaCPP itself:
$ git clone https://github.com/bytedeco/javacpp.git
$ git clone https://github.com/bytedeco/javacpp-presets.git
$ cd javacpp
$ mvn clean install
Please make sure to install all the required software as indicated in their README.md
files and on the Build Environments page. Then, to add new presets for a library inside the javacpp-presets
directory:
- Create a new subdirectory whose name corresponds to the desired final name of the JAR file and artifact, which should be all lowercase by convention.
- Inside this new subdirectory, create new project files as described in detail in the following sections: the
cppbuild.sh
file, thepom.xml
file, and the Java configuration files in theorg.bytedeco.<moduleName>.presets
package, as well as at least oneplatform/pom.xml
file. - After confirming that everything is in a working order by running
mvn clean install
inside thejavacpp-presets
directory, add the necessary files to the.github/workflows
directory for the CI servers, send a pull request to have your code added to the main repository, and check the status of your builds to make sure everything passes.
To make the explanations clearer, the sample content below was actually designed to build and wrap a small, simple, but popular library: zlib. (In fact, it comes bundled with the JDK as part of the java.util.zip package.) To get a quick impression of the whole procedure, please feel free to copy/paste into the appropriate files and try it out that way. There is also a helloworld
sample project in the helloworld
branch ready to be built:
For convenience, the zlib sample presets below is also available on this branch ready to be built:
The Bash script is the agent that takes care of building the native library itself. (To use Bash under Windows, we can install environments such as MSYS2.) Ideally, it should do these three things:
- Acquire the source code somehow,
- Build the binary files someway, and
- Install the header and library files in the
cppbuild/<javacpp.platform>
subdirectory.
Note that if the library can be installed in a portable fashion in some other way, we can use that method instead of hacking a script file together. Unfortunately, this is rarely the case, but Bash scripts have proven to be quite an effective solution that happens to have become an industry standard. Bazel or Gradle may eventually become a better alternative as a good portable candidate that could not only obviate the need for Bash, but that could also take over the whole build process, including Java files currently assembled by Maven.
Before this becomes a reality though, we are going to make do with cppbuild.sh
files. For our small demo zlib
library, it could look like this:
#!/bin/bash
# This file is meant to be included by the parent cppbuild.sh script
if [[ -z "$PLATFORM" ]]; then
pushd ..
bash cppbuild.sh "$@" zlib
popd
exit
fi
ZLIB_VERSION=1.2.11
download http://zlib.net/zlib-$ZLIB_VERSION.tar.gz zlib-$ZLIB_VERSION.tar.gz
mkdir -p $PLATFORM
cd $PLATFORM
tar -xzvf ../zlib-$ZLIB_VERSION.tar.gz
cd zlib-$ZLIB_VERSION
case $PLATFORM in
android-arm)
CC="$ANDROID_CC" CFLAGS="$ANDROID_FLAGS" ./configure --prefix=.. --static
make -j $MAKEJ
make install
;;
android-arm64)
CC="$ANDROID_CC" CFLAGS="$ANDROID_FLAGS" ./configure --prefix=.. --static
make -j $MAKEJ
make install
;;
android-x86)
CC="$ANDROID_CC" CFLAGS="$ANDROID_FLAGS" ./configure --prefix=.. --static
make -j $MAKEJ
make install
;;
android-x86_64)
CC="$ANDROID_CC" CFLAGS="$ANDROID_FLAGS" ./configure --prefix=.. --static
make -j $MAKEJ
make install
;;
linux-x86)
CC="gcc -m32 -fPIC" ./configure --prefix=.. --static
make -j $MAKEJ
make install
;;
linux-x86_64)
CC="gcc -m64 -fPIC" ./configure --prefix=.. --static
make -j $MAKEJ
make install
;;
macosx-x86_64)
./configure --prefix=.. --static
make -j $MAKEJ
make install
;;
windows-x86)
nmake -f win32/Makefile.msc zlib.lib
mkdir -p ../include ../lib
cp zconf.h zlib.h ../include/
cp zlib.lib ../lib/
;;
windows-x86_64)
nmake -f win32/Makefile.msc zlib.lib
mkdir -p ../include ../lib
cp zconf.h zlib.h ../include/
cp zlib.lib ../lib/
;;
*)
echo "Error: Platform \"$PLATFORM\" is not supported"
;;
esac
cd ../..
After calling bash cppbuild.sh install zlib
from inside the parent directory of javacpp-presets
it should successfully download, build, and install the library in the cppbuild
subdirectory, as desired.
Most of the pom.xml
file is boilerplate that we cannot abstract away with Maven via the parent pom.xml
file. Simply adjusting the content below to the appropriate names and versions of the libraries should suffice:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.bytedeco</groupId>
<artifactId>javacpp-presets</artifactId>
<version>1.5.5-SNAPSHOT</version>
</parent>
<groupId>org.bytedeco</groupId>
<artifactId>zlib</artifactId>
<version>1.2.11-${project.parent.version}</version>
<name>JavaCPP Presets for zlib</name>
<dependencies>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacpp</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.bytedeco</groupId>
<artifactId>javacpp</artifactId>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.moditect</groupId>
<artifactId>moditect-maven-plugin</artifactId>
</plugin>
<plugin>
<artifactId>maven-dependency-plugin</artifactId>
</plugin>
<plugin>
<artifactId>maven-source-plugin</artifactId>
</plugin>
<plugin>
<artifactId>maven-javadoc-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Although not crucial, the calls to moditect-maven-plugin
create for us module-info.class
files that are required by the JPMS, in which case we need to create an additional src/main/java9/module-info.java
file such as the following:
module org.bytedeco.zlib {
requires transitive org.bytedeco.javacpp;
exports org.bytedeco.zlib;
exports org.bytedeco.zlib.global;
exports org.bytedeco.zlib.presets;
}
Next, we need to specify such things as the desired names of the target Java interface files and packages, as well as the ones of C/C++ header files and native library files previously built and installed by the cppbuild.sh
scripts. We place that information in Java source code files inside the org.bytedeco.<moduleName>.presets
package, by convention, under the src/main/java/org/bytedeco/<moduleName>/presets
subdirectory. For consistency, we should create one configuration file per native library file. In the case of the zlib
library, which comes with only one library file, we could be satisfied with the following alone:
package org.bytedeco.zlib.presets;
import org.bytedeco.javacpp.*;
import org.bytedeco.javacpp.annotation.*;
import org.bytedeco.javacpp.tools.*;
@Properties(
value = {
@Platform(include = "<zlib.h>", link = "z@.1#"),
@Platform(value = "windows", link = "zlib#")
},
target = "org.bytedeco.zlib",
global = "org.bytedeco.zlib.global.zlib"
)
public class zlib implements InfoMapper {
static { Loader.checkVersion("org.bytedeco", "zlib"); }
public void map(InfoMap infoMap) {
infoMap.put(new Info("ZEXTERN", "ZEXPORT", "z_const", "zlib_version").cppTypes().annotations())
.put(new Info("FAR").cppText("#define FAR"))
.put(new Info("OF").cppText("#define OF(args) args"))
.put(new Info("Z_ARG").cppText("#define Z_ARG(args) args"))
.put(new Info("Byte", "Bytef", "charf").cast().valueTypes("byte").pointerTypes("BytePointer"))
.put(new Info("uInt", "uIntf").cast().valueTypes("int").pointerTypes("IntPointer"))
.put(new Info("uLong", "uLongf", "z_crc_t", "z_off_t", "z_size_t").cast().valueTypes("long").pointerTypes("CLongPointer"))
.put(new Info("z_off64_t").cast().valueTypes("long").pointerTypes("LongPointer"))
.put(new Info("voidp", "voidpc", "voidpf").valueTypes("Pointer"))
.put(new Info("gzFile_s").pointerTypes("gzFile"))
.put(new Info("gzFile").valueTypes("gzFile"))
.put(new Info("Z_LARGE64", "!defined(ZLIB_INTERNAL) && defined(Z_WANT64)").define(false))
.put(new Info("inflateGetDictionary", "gzopen_w", "gzvprintf").skip());
}
}
That file also contains information useful to the Parser
specified via the Info
configuration objects added to an InfoMap
container through the InfoMapper
interface. To understand how to use the annotations and configuration objects, please refer to the Mapping Recipes for C/C++ Libraries, while the API documentation of JavaCPP should also come in handy during this phase.
Finally, we should create at least one pom.xml
as below that depends on the artifacts for all platforms, but that users can also configure transitively at runtime as described on the Reducing the Number of Dependencies page. We can install those platform pom.xml
files with mvn clean install -Djavacpp.platform.host
, which prevents Maven from trying to download dependencies that might not have been deployed from all platforms yet.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.bytedeco</groupId>
<artifactId>javacpp-presets</artifactId>
<version>1.5.5-SNAPSHOT</version>
<relativePath>../../</relativePath>
</parent>
<groupId>org.bytedeco</groupId>
<artifactId>zlib-platform</artifactId>
<version>1.2.11-${project.parent.version}</version>
<name>JavaCPP Presets Platform for zlib</name>
<properties>
<javacpp.moduleId>zlib</javacpp.moduleId>
</properties>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>${javacpp.moduleId}</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>${javacpp.moduleId}</artifactId>
<version>${project.version}</version>
<classifier>${javacpp.platform.android-arm}</classifier>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>${javacpp.moduleId}</artifactId>
<version>${project.version}</version>
<classifier>${javacpp.platform.android-arm64}</classifier>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>${javacpp.moduleId}</artifactId>
<version>${project.version}</version>
<classifier>${javacpp.platform.android-x86}</classifier>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>${javacpp.moduleId}</artifactId>
<version>${project.version}</version>
<classifier>${javacpp.platform.android-x86_64}</classifier>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>${javacpp.moduleId}</artifactId>
<version>${project.version}</version>
<classifier>${javacpp.platform.linux-x86}</classifier>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>${javacpp.moduleId}</artifactId>
<version>${project.version}</version>
<classifier>${javacpp.platform.linux-x86_64}</classifier>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>${javacpp.moduleId}</artifactId>
<version>${project.version}</version>
<classifier>${javacpp.platform.macosx-x86_64}</classifier>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>${javacpp.moduleId}</artifactId>
<version>${project.version}</version>
<classifier>${javacpp.platform.windows-x86}</classifier>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>${javacpp.moduleId}</artifactId>
<version>${project.version}</version>
<classifier>${javacpp.platform.windows-x86_64}</classifier>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<executions>
<execution>
<id>default-jar</id>
<configuration>
<archive>
<manifestEntries>
<Class-Path>${javacpp.moduleId}.jar ${javacpp.moduleId}-linux-x86.jar ${javacpp.moduleId}-linux-x86_64.jar ${javacpp.moduleId}-macosx-x86_64.jar ${javacpp.moduleId}-windows-x86.jar ${javacpp.moduleId}-windows-x86_64.jar</Class-Path>
</manifestEntries>
</archive>
</configuration>
</execution>
<execution>
<id>empty-javadoc-jar</id>
<goals>
<goal>jar</goal>
</goals>
<configuration>
<classifier>javadoc</classifier>
</configuration>
</execution>
<execution>
<id>empty-sources-jar</id>
<goals>
<goal>jar</goal>
</goals>
<configuration>
<classifier>sources</classifier>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.moditect</groupId>
<artifactId>moditect-maven-plugin</artifactId>
<executions>
<execution>
<id>add-module-infos</id>
<phase>none</phase>
</execution>
<execution>
<id>add-platform-module-info</id>
<phase>package</phase>
<goals>
<goal>add-module-info</goal>
</goals>
<configuration>
<modules>
<module>
<file>${project.build.directory}/${project.artifactId}.jar</file>
<moduleInfoSource>
module org.bytedeco.${javacpp.moduleId}.platform {
requires static org.bytedeco.${javacpp.moduleId}.android.arm;
requires static org.bytedeco.${javacpp.moduleId}.android.arm64;
requires static org.bytedeco.${javacpp.moduleId}.android.x86;
requires static org.bytedeco.${javacpp.moduleId}.android.x86_64;
requires static org.bytedeco.${javacpp.moduleId}.linux.x86;
requires static org.bytedeco.${javacpp.moduleId}.linux.x86_64;
requires static org.bytedeco.${javacpp.moduleId}.macosx.x86_64;
requires static org.bytedeco.${javacpp.moduleId}.windows.x86;
requires static org.bytedeco.${javacpp.moduleId}.windows.x86_64;
}
</moduleInfoSource>
</module>
</modules>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Now, inside the parent directory of javacpp-presets
, after adding the module name zlib
to the module lists in the parent pom.xml
file, and the corresponding path in the parent platform/pom.xml
one, we can try to build the project by calling mvn clean install --projects .,zlib
followed by mvn clean install -f platform --projects ../zlib/platform -Djavacpp.platform.host
. If all goes well, we should see lines among the output of Maven that confirm that the target class and the native wrapping library get created. If you are having trouble getting JavaCPP to parse the header files or to generate proper interfaces for your libraries, please open a new issue about that so we can fix it.
When satisfied with the overall result, please send a pull request with your changes! Moreover, since portability is one of Java's main benefits, we should offer binaries for as many platforms as possible, so please consider offering builds for platforms other than your own, which we can easily test and debug remotely with GitHub Actions. Nevertheless, other contributors will hopefully do the same for your library presets on platforms that interest them.
If you are looking for a more "turnkey solution", please refer to these commercial services offered on xs:code:
Thank you very much for your interest in this project, and please feel free to post your questions on the mailing list and open new issues to communicate your suggestions.