Create New Presets

Samuel Audet edited this page Feb 7, 2016 · 5 revisions

Introduction

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 installating 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:

  1. 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.
  2. Inside this new subdirectory, create new project files as described in detail in the following sections: the cppbuild.sh file, the pom.xml file, and the Java configuration files in the org.bytedeco.javacpp.presets package.
  3. After confirming that everything is in a working order by running both bash cppbuild.sh install and mvn clean install inside the javacpp-presets directory, send a pull request to have your code added to the main repository, and upload binary artifacts for your platform to the Maven Central Repository for others to enjoy!

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.

The cppbuild.sh file

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 MSYS and Cygwin.) Ideally, it should do these three things:

  1. Acquire the source code somehow,
  2. Build the binary files someway, and
  3. Install the header and library files in the cppbuild/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. Eventually we will need to find a better alternative to Bash scripts. Gradle appears to be 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

if [[ $PLATFORM == windows* ]]; then
    ZLIB_VERSION=128
    download http://zlib.net/zlib$ZLIB_VERSION-dll.zip zlib$ZLIB_VERSION-dll.zip
    mkdir -p $PLATFORM
    cd $PLATFORM
    unzip ../zlib$ZLIB_VERSION-dll.zip -d zlib$ZLIB_VERSION-dll
    cd zlib$ZLIB_VERSION-dll
else
    ZLIB_VERSION=1.2.8
    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
fi

case $PLATFORM in
    android-arm)
        CC="$ANDROID_BIN-gcc" CFLAGS="--sysroot=$ANDROID_ROOT -DANDROID -fPIC -ffunction-sections -funwind-tables -fstack-protector -march=armv7-a -mfloat-abi=softfp -mfpu=vfpv3-d16 -fomit-frame-pointer -fstrict-aliasing -funswitch-loops -finline-limit=300" LDFLAGS="-nostdlib -Wl,--fix-cortex-a8" LIBS="-lgcc -ldl -lz -lm -lc" ./configure --prefix=.. --static
        make -j4
        make install
        ;;
    android-x86)
        CC="$ANDROID_BIN-gcc" CFLAGS="--sysroot=$ANDROID_ROOT -DANDROID -fPIC -ffunction-sections -funwind-tables -fstack-protector -mssse3 -mfpmath=sse -fomit-frame-pointer -fstrict-aliasing -funswitch-loops -finline-limit=300" LDFLAGS="-nostdlib" LIBS="-lgcc -ldl -lz -lm -lc" ./configure --prefix=.. --static
        make -j4
        make install
        ;;
    linux-x86)
        CC="gcc -m32 -fPIC" ./configure --prefix=.. --static
        make -j4
        make install
        ;;
    linux-x86_64)
        CC="gcc -m64 -fPIC" ./configure --prefix=.. --static
        make -j4
        make install
        ;;
    macosx-x86_64)
        ./configure --prefix=.. --static
        make -j4
        make install
        ;;
    windows-x86)
        cp -r include ..
        cp -r lib ..
        mkdir -p ../bin
        cp *.dll ../bin
        ;;
    *)
        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.

The pom.xml file

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.2-SNAPSHOT</version>
  </parent>

  <groupId>org.bytedeco.javacpp-presets</groupId>
  <artifactId>zlib</artifactId>
  <version>1.2.8-${project.parent.version}</version>
  <packaging>jar</packaging>
  <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>
        <artifactId>maven-dependency-plugin</artifactId>
      </plugin>
      <plugin>
        <artifactId>maven-source-plugin</artifactId>
      </plugin>
      <plugin>
        <artifactId>maven-javadoc-plugin</artifactId>
      </plugin>
    </plugins>
  </build>

</project>

The Java configuration files

Finally, we need to specify such things as the desired names of the target Java interface files, 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.javacpp.presets package, by convention, under the src/main/java/org/bytedeco/javacpp/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.javacpp.presets;

import org.bytedeco.javacpp.annotation.*;
import org.bytedeco.javacpp.tools.*;

@Properties(target="org.bytedeco.javacpp.zlib", value={@Platform(include="<zlib.h>", link="z@.1"),
    @Platform(value="windows", link="zdll", preload="zlib1")})
public class zlib implements InfoMapper {
    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").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, the API documentation of JavaCPP should come in handy during this phase.

Now, inside the parent directory of javacpp-presets, after adding the module name zlib to the modules list in the parent pom.xml file, we can try to build the project by calling mvn clean install --projects .,zlib. 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 after obtaining an account to access the Maven Central Repository, please consider going through all the steps detailed in the Sonatype OSS Repository Hosting Guide to deploy binaries for your platform. Hopefully other contributors will do the same for your library presets on their platforms.


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.