Commit 0866d03a authored by Davis King's avatar Davis King

Added cmake scripts and some related tooling that makes it easy to call C++

code from java.
parent 96881874
cmake_minimum_required (VERSION 2.8.12)
project (myproject)
set(java_package_name net.dlib)
set(source_files
)
include(../cmake_utils/use_cpp_11.cmake)
include(../cmake_utils/release_build_by_default)
include_directories(
.
)
# Additional dependencies
#include(../../dlib/cmake)
#set(additional_link_libraries dlib)
# tell swig to put the output files into the local folder.
set(install_target_output_folder .)
include(cmake_swig_jni)
# This file is used to create SWIG based JNI interfaces to C++ code. You use
# it by defining some CMake variables and then include(cmake_swig_jni). You
# would make a CMakeLists.txt file that looks like the following:
#
# cmake_minimum_required (VERSION 2.8.4)
# project (example)
# set(java_package_name "org.mycompany")
# set(source_files
# your_cpp_source.cpp
# more_cpp_source.cpp
# )
#
# ### We might need to link our code to some other C++ library like dlib. You
# ### can do that by setting additional_link_libraries. Here is an example of
# ### linking to dlib:
# include(../../dlib/dlib/cmake)
# set(additional_link_libraries dlib)
#
# ### tell swig to put the output files into the parent folder of your CMakeLists.txt
# ### file when you run make install.
# set(install_target_output_folder ..)
# include(cmake_swig_jni)
################################################################################
################################################################################
# IMPLEMENTATION DETAILS
################################################################################
################################################################################
cmake_minimum_required (VERSION 2.8.4)
# This block of code tries to figure out what the JAVA_HOME environment
# variable should be by looking at the folder that contains the java
# executable.
if (NOT DEFINED ENV{JAVA_HOME})
message(STATUS "JAVA_HOME environment variable not set, trying to guess it...")
find_program(JAVA_EXECUTABLE java)
# Resolve symbolic links, hopefully this will give us a path in the proper
# java home directory.
get_filename_component(JAVA_EXECUTABLE ${JAVA_EXECUTABLE} REALPATH)
# Pick out the parent directories
get_filename_component(JAVA_PATH1 ${JAVA_EXECUTABLE} PATH)
get_filename_component(JAVA_PATH2 ${JAVA_PATH1} PATH)
get_filename_component(JAVA_PATH3 ${JAVA_PATH2} PATH)
# and search them for include/jni.h. If we find that then we probably have
# a good java home candidate.
find_path(AUTO_JAVA_HOME include/jni.h
PATHS
${JAVA_PATH1}
${JAVA_PATH2}
${JAVA_PATH3}
"C:/Program Files/Java/jdk*"
"C:/Program Files (x86)/Java/jdk*"
)
if (AUTO_JAVA_HOME)
set(ENV{JAVA_HOME} ${AUTO_JAVA_HOME})
message(STATUS "Using JAVA_HOME OF " ${AUTO_JAVA_HOME})
else()
message(FATAL_ERROR "Couldn't find a folder for JAVA_HOME. You must set the JAVA_HOME environment variable before running CMake.")
endif()
endif()
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/lib")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELEASE "${CMAKE_CURRENT_BINARY_DIR}/lib")
find_package(SWIG REQUIRED)
find_package(Java REQUIRED)
find_package(JNI REQUIRED)
include(UseSWIG)
macro (add_global_switch def_name )
if (NOT CMAKE_CXX_FLAGS MATCHES "${def_name}")
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${def_name}"
CACHE STRING "Flags used by the compiler during all C++ builds."
FORCE)
endif ()
if (NOT CMAKE_C_FLAGS MATCHES "${def_name}")
set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${def_name}"
CACHE STRING "Flags used by the compiler during all C builds."
FORCE)
endif ()
endmacro()
# SWIG doesn't work if optimizations are enabled and strict aliasing is not
# turned off. This is a little wonky but it's how SWIG is.
if (CMAKE_COMPILER_IS_GNUCXX)
add_definitions(-fno-strict-aliasing)
endif()
if (UNIX)
# we need to make sure all the code is compiled with -fPIC. In particular,
# it's important that all the code for the whole project is, not just the
# stuff immediately compiled by us in this cmake file. So we add -fPIC to
# the top level cmake flags variables.
add_global_switch(-fPIC)
endif()
string(REGEX REPLACE "\\." "/" package_path ${java_package_name})
string(REGEX REPLACE "\\..*" "" package_root_name ${java_package_name})
set(CMAKE_SWIG_FLAGS -package ${java_package_name})
set(CMAKE_SWIG_OUTDIR ${CMAKE_CURRENT_BINARY_DIR}/lib/java_src/${package_path})
set(output_library_name ${PROJECT_NAME})
# Create the swig.i interface file that swig will run on. We do it here in
# the cmake script because this lets us automatically include the correct
# output library name into the call to System.loadLibrary().
FILE(WRITE ${CMAKE_CURRENT_BINARY_DIR}/swig.i
"
// Put the global functions in our api into a java class called global.
%module global
%{
#include \"swig_api.h\"
#include <exception>
%}
// Convert all C++ exceptions into java.lang.Exception
%exception {
try {
$action
} catch(std::exception& e) {
jclass clazz = jenv->FindClass(\"java/lang/Exception\");
jenv->ThrowNew(clazz, e.what());
return $null;
}
}
%pragma(java) jniclasscode=%{
static {
try {
System.loadLibrary(\"${output_library_name}\");
} catch (UnsatisfiedLinkError e) {
System.err.println(\"Native code library failed to load. \\n\" + e);
System.exit(1);
}
}
%}
%include \"swig_api.h\"
"
)
# There is a bug in CMake's Swig scripts that causes the build to fail if the
# binary folder doesn't contain a folder with the same name as the binary dir.
# So we make a subfolder of the same name to avoid that bug.
get_filename_component(binary_dir_name "${CMAKE_CURRENT_BINARY_DIR}" NAME)
FILE(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${binary_dir_name}")
set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/swig.i PROPERTIES CPLUSPLUS ON)
swig_add_module(${output_library_name} java ${CMAKE_CURRENT_BINARY_DIR}/swig.i ${source_files})
include_directories(${JNI_INCLUDE_DIRS})
swig_link_libraries(${output_library_name} ${additional_link_libraries})
# Things to delete when "make clean" is run.
set(clean_files
${CMAKE_CURRENT_BINARY_DIR}/intermediate_files_compiled
${CMAKE_CURRENT_BINARY_DIR}/lib/java_src
)
set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES "${clean_files}")
# Compile the java files into a jar file and stick it in the lib folder.
add_custom_command(TARGET ${output_library_name}
POST_BUILD
COMMAND cmake -E echo "compiling Java files..."
COMMAND cmake -E make_directory "${CMAKE_CURRENT_BINARY_DIR}/intermediate_files_compiled"
COMMAND ${Java_JAVAC_EXECUTABLE} ${CMAKE_SWIG_OUTDIR}/*.java -d "${CMAKE_CURRENT_BINARY_DIR}/intermediate_files_compiled"
COMMAND cmake -E echo "Making jar file..."
COMMAND ${Java_JAR_EXECUTABLE} cvf "${CMAKE_CURRENT_BINARY_DIR}/lib/${PROJECT_NAME}.jar" -C "${CMAKE_CURRENT_BINARY_DIR}/intermediate_files_compiled" ${package_root_name}
)
#if the including cmake script set the install_target_output_folder variable
#then make it so we install the compiled library and jar into that folder
if (install_target_output_folder)
# Determine the path to our CMakeLists.txt file.
# There is either a bug (or break in compatability maybe) between versions
# of cmake that cause the or expression in this regular expression to be
# necessary.
string(REGEX REPLACE "(cmake_swig_jni|CMakeLists.txt)$" "" base_path ${CMAKE_PARENT_LIST_FILE})
# The directory we will write the output files to.
set(install_dir "${base_path}${install_target_output_folder}")
set(CMAKE_INSTALL_PREFIX "${install_dir}")
set(CMAKE_INSTALL_SYSTEM_RUNTIME_DESTINATION "${install_dir}")
install(TARGETS ${output_library_name}
DESTINATION "${install_dir}"
)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/lib/${PROJECT_NAME}.jar
DESTINATION "${install_dir}"
)
endif()
# Copy any system libraries to the output folder. This really only matters on
# windows where it's good to have the visual studio runtime show up in the lib
# folder so that you don't forget to include it in your binary distribution.
INCLUDE(InstallRequiredSystemLibraries)
foreach (file_i ${CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS})
add_custom_command(TARGET ${output_library_name}
POST_BUILD
COMMAND cmake -E copy ${file_i} "${CMAKE_CURRENT_BINARY_DIR}/lib/"
)
endforeach()
This diff is collapsed.
# build the jar and shared library of C++ code needed by the JVM
mkdir build
cd build
cmake ..
cmake --build . --config Release --target install
cd ..
# setup paths so the JVM can find our jar and shared library.
export LD_LIBRARY_PATH=.
export DYLD_LIBRARY_PATH=.
export CLASSPATH=myproject.jar:.
# Now compile and run our java test that calls our C++ code.
javac swig_test.java
java swig_test
#ifndef EXAMPLE_SWIG_ApI_H_
#define EXAMPLE_SWIG_ApI_H_
// This file is essentially a small unit test for the swig cmake scripts and the jvector
// classes. All it does it define a few simple functions for writing to and summing
// arrays. The swig_test.java file then calls these C++ functions and checks if they work
// correctly.
// Let's use the jvector, a tool for efficiently binding java native arrays to C++ function
// arguments. You do this by putting this pair of include statements in your swig_api.h
// file. Then after that you can use the jvector and jvector_crit classes.
#include "jvector.h"
#ifdef SWIG
%include "jvector.h"
#endif
// ----------------------------------------------------------------------------------------
// SWIG can't expose templated functions to java. We declare these here as helper
// functions to make the non-templated routines swig will expose easier to write. You can
// see these java exposed methods below (i.e. sum(), sum_crit(), assign(), and
// assign_crit()).
template <typename T>
T tsum(const jvector_crit<T>& arr)
{
T s = 0;
for (auto& v : arr)
s += v;
return s;
}
template <typename T>
T tsum(const jvector<T>& arr)
{
T s = 0;
for (auto& v : arr)
s += v;
return s;
}
template <typename T>
void tassign(T& arr)
{
for (size_t i = 0; i < arr.size(); ++i)
arr[i] = i;
}
// ----------------------------------------------------------------------------------------
// Now write some functions SWIG will expose to java. SWIG will automatically expose
// pretty much any non-template C++ code to java. So just by defining these functions here
// we expose them to java.
//
// All global C++ functions will appear in java as static member functions of class called
// "global", which is where these sum and assign routines will appear. You can see
// examples of java code that calls them in swig_test.java.
inline int sum_crit(const jvector_crit<int16_t>& arr) { return tsum(arr); }
inline int sum(const jvector<int16_t>& arr) { return tsum(arr); }
inline void assign_crit(jvector_crit<int16_t>& arr) { tassign(arr); }
inline void assign(jvector<int16_t>& arr) { tassign(arr); }
inline int sum_crit(const jvector_crit<int32_t>& arr) { return tsum(arr); }
inline int sum(const jvector<int32_t>& arr) { return tsum(arr); }
inline void assign_crit(jvector_crit<int32_t>& arr) { tassign(arr); }
inline void assign(jvector<int32_t>& arr) { tassign(arr); }
inline int sum_crit(const jvector_crit<int64_t>& arr) { return tsum(arr); }
inline int sum(const jvector<int64_t>& arr) { return tsum(arr); }
inline void assign_crit(jvector_crit<int64_t>& arr) { tassign(arr); }
inline void assign(jvector<int64_t>& arr) { tassign(arr); }
inline int sum_crit(const jvector_crit<char>& arr) { return tsum(arr); }
inline int sum(const jvector<char>& arr) { return tsum(arr); }
inline void assign_crit(jvector_crit<char>& arr) { tassign(arr); }
inline void assign(jvector<char>& arr) { tassign(arr); }
inline double sum_crit(const jvector_crit<double>& arr) { return tsum(arr); }
inline double sum(const jvector<double>& arr) { return tsum(arr); }
inline void assign_crit(jvector_crit<double>& arr) { tassign(arr); }
inline void assign(jvector<double>& arr) { tassign(arr); }
inline float sum_crit(const jvector_crit<float>& arr) { return tsum(arr); }
inline float sum(const jvector<float>& arr) { return tsum(arr); }
inline void assign_crit(jvector_crit<float>& arr) { tassign(arr); }
inline void assign(jvector<float>& arr) { tassign(arr); }
// ----------------------------------------------------------------------------------------
#endif // EXAMPLE_SWIG_ApI_H_
/*
This file tests all the ways of using jvector and jvector_crit.
*/
import net.dlib.*;
public class swig_test
{
public static int sum(long[] arr)
{
int s = 0;
for (int i = 0; i < arr.length; ++i)
s += arr[i];
return s;
}
public static void zero(long[] arr)
{
for (int i = 0; i < arr.length; ++i)
arr[i] = 0;
}
public static int sum(byte[] arr)
{
int s = 0;
for (int i = 0; i < arr.length; ++i)
s += arr[i];
return s;
}
public static void zero(byte[] arr)
{
for (int i = 0; i < arr.length; ++i)
arr[i] = 0;
}
public static int sum(short[] arr)
{
int s = 0;
for (int i = 0; i < arr.length; ++i)
s += arr[i];
return s;
}
public static void zero(short[] arr)
{
for (int i = 0; i < arr.length; ++i)
arr[i] = 0;
}
public static int sum(int[] arr)
{
int s = 0;
for (int i = 0; i < arr.length; ++i)
s += arr[i];
return s;
}
public static void zero(int[] arr)
{
for (int i = 0; i < arr.length; ++i)
arr[i] = 0;
}
public static void assertIs28(int val)
{
if (val != 28)
{
throw new RuntimeException("Test failed " + val);
}
}
public static double sum(double[] arr)
{
double s = 0;
for (int i = 0; i < arr.length; ++i)
s += arr[i];
return s;
}
public static void zero(double[] arr)
{
for (int i = 0; i < arr.length; ++i)
arr[i] = 0;
}
public static void assertIs28(double val)
{
if (val != 28)
{
throw new RuntimeException("Test failed " + val);
}
}
public static float sum(float[] arr)
{
float s = 0;
for (int i = 0; i < arr.length; ++i)
s += arr[i];
return s;
}
public static void zero(float[] arr)
{
for (int i = 0; i < arr.length; ++i)
arr[i] = 0;
}
public static void assertIs28(float val)
{
if (val != 28)
{
throw new RuntimeException("Test failed " + val);
}
}
public static void main(String[] args)
{
{
float[] arr = new float[8];
for (int round = 0; round < 100; ++round)
{
zero(arr); global.assign(arr);
assertIs28(sum(arr));
zero(arr); global.assign_crit(arr);
assertIs28(sum(arr));
}
for (int round = 0; round < 100; ++round)
{
zero(arr); global.assign(arr);
assertIs28(sum(arr));
assertIs28(global.sum(arr));
zero(arr); global.assign_crit(arr);
assertIs28(sum(arr));
assertIs28(global.sum_crit(arr));
}
}
{
double[] arr = new double[8];
for (int round = 0; round < 100; ++round)
{
zero(arr); global.assign(arr);
assertIs28(sum(arr));
zero(arr); global.assign_crit(arr);
assertIs28(sum(arr));
}
for (int round = 0; round < 100; ++round)
{
zero(arr); global.assign(arr);
assertIs28(sum(arr));
assertIs28(global.sum(arr));
zero(arr); global.assign_crit(arr);
assertIs28(sum(arr));
assertIs28(global.sum_crit(arr));
}
}
{
byte[] arr = new byte[8];
for (int round = 0; round < 100; ++round)
{
zero(arr); global.assign(arr);
assertIs28(sum(arr));
zero(arr); global.assign_crit(arr);
assertIs28(sum(arr));
}
for (int round = 0; round < 100; ++round)
{
zero(arr); global.assign(arr);
assertIs28(sum(arr));
assertIs28(global.sum(arr));
zero(arr); global.assign_crit(arr);
assertIs28(sum(arr));
assertIs28(global.sum_crit(arr));
}
}
{
long[] arr = new long[8];
for (int round = 0; round < 100; ++round)
{
zero(arr); global.assign(arr);
assertIs28(sum(arr));
zero(arr); global.assign_crit(arr);
assertIs28(sum(arr));
}
for (int round = 0; round < 100; ++round)
{
zero(arr); global.assign(arr);
assertIs28(sum(arr));
assertIs28(global.sum(arr));
zero(arr); global.assign_crit(arr);
assertIs28(sum(arr));
assertIs28(global.sum_crit(arr));
}
}
{
short[] arr = new short[8];
for (int round = 0; round < 100; ++round)
{
zero(arr); global.assign(arr);
assertIs28(sum(arr));
zero(arr); global.assign_crit(arr);
assertIs28(sum(arr));
}
for (int round = 0; round < 100; ++round)
{
zero(arr); global.assign(arr);
assertIs28(sum(arr));
assertIs28(global.sum(arr));
zero(arr); global.assign_crit(arr);
assertIs28(sum(arr));
assertIs28(global.sum_crit(arr));
}
}
{
int[] arr = new int[8];
for (int round = 0; round < 100; ++round)
{
zero(arr); global.assign(arr);
assertIs28(sum(arr));
zero(arr); global.assign_crit(arr);
assertIs28(sum(arr));
}
for (int round = 0; round < 100; ++round)
{
zero(arr); global.assign(arr);
assertIs28(sum(arr));
assertIs28(global.sum(arr));
zero(arr); global.assign_crit(arr);
assertIs28(sum(arr));
assertIs28(global.sum_crit(arr));
}
}
System.out.println("\n\n ALL TESTS COMPLETED SUCCESSFULLY\n");
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment