Shark2-iosx

Build Shark 2.3.4 framework for iOS and OSX

What is Shark?

SHARK provides libraries for the design of adaptive systems, including methods for linear and nonlinear optimization (e.g., evolutionary and gradient-based algorithms), kernel-based algorithms and neural networks, and other machine learning techniques.

Patch Source Code

That's where some fun begins. The original source code was developed with gcc 4.2, while we're trying to compile it with clang 5.0. There's been years of development since then, both in terms of compilers and C++ standards. No wonder clang fails with quite a few errors.

Default Constructor

Let's start with the patch that I believe might have some impact on the way you should use the library. In file ReClam/EarlyStopping.cpp, line 78.

EarlyStopping::EarlyStopping(unsignedsl=5)

Note the use of default value for the only parameter of the constructor. There's an in-depth discussion of this issue on StackOverflow

The fix is to remove default value for sl parameter. Since EarlyStopping constructor is not referenced anywhere in the library source code it is up to you, as a library user, to provide some value to it and not to rely on any default values.

No 'finite' for iOS

Next issue is with finite method. This method is not included for iOS target architectures. You will get this error when building both for iOS Device and Simulator. Here's some referencee to this problem.

The suggested fix is to replace calls to finite with isfinite. The best way to do it is to use #define macro and place it in the SharkDefs.h file.

First, check if __APPLE__ and __MACH___ are defined to detect Apple's OS.

Then include <TargetConditionals.> to enable TARGET_IPHONE_SIMULATOR, TARGET_OS_IPHONE and TARGET_OS_MAC defines.

Finally, use those three defines to redefine finite(x) as isfinite(x) for iOS Device and Simulator targets.

Note the commented line for drem(x, y). This method is not available for iOS targets as well and should be replaced with remainder(x, y). Shark library doesn't use it, but you'll know what to do if you ever have a problem with drem.

FileUtil Fixes

Next comes FileUtil.h. There's a couple of things that clang doesn't like.

First is the use of FileUtil namespace. Clang can't resolve references to iotype type, as well as references to three constants SetDefault, ScanFrom and PrintTo.

Thus the fix is as simple as replacing reference to p with this->p. Note the triple exclamation mark comment in the original code (// !!!), looks like developers knew something could go wrong with this code.

Make It Static

The last patch is actually not a fix. Since we want to build a static library, we need to modify original CMakeList.txt, otherwise the dynamic library is built.

ADD_LIBRARY(sharkSTATIC${SRCS})

If you want to build a dynamic (shared) library, you'll need to use the original line.

ADD_LIBRARY(sharkSHARED${SRCS})

Apply Patch

All the changes described above are available as a patch file shark.patch.
To create patch file use diff utility.

diff -crB shark_orig shark_patched > shark.patch

To apply the patch use patch command.

patch -d src/Shark -p1 --forward -r - -i ../../shark.patch

The --forward flag will disable interactive mode and all the prompts

-d flag is used to specify the source code folder, the utility will cd into specified folder

-r - option specifies that no .rej files should be created if the patch is already applied (or in case of any other error)

note the relative path for -i ../../shark.patch, this is needed because -d flag is used and patch command will be executed in src/Shark folder

Configure and Build

Now when all the compile errors are fixed it's time to build static library for all target platforms.

Before we run make we have to configure the build (that also creates Makefile) and for this purpose we're going to stick with cmake.

We'll have to run cmake and then make 3 times to build 3 static libraries for following targets

iOS Devices (armv7, armv7s, x86_64)

iOS Simulator (i386, x86_64)

Mac OS X (x86_64)

The libraries for iOS Device and Simulator will later be merged into one fat library which you can use for your iOS development and release.

cmake Basics

Each of the build targets needs to be configured differently with cmake.
The following options have to be configured:

C++ Compiler (CMAKE_CXX_COMPILER)

By default the /usr/bin/c++ is used as C++ compiler, but we want to build using clang++ compiler from Xcode toolchain.

C++ Compiler Flags (CMAKE_CXX_FLAGS)

The C++ compiler flags are used to specify target architectures and other important build settings.

C Compiler (CMAKE_C_COMPILER)

Even though there's no plain C code, the C compiler needs to be configured, since the whole project by default is C/C++ project.

System Root (CMAKE_OSX_SYSROOT)

This is where build system will look for all the standard library includes.

Install Prefix (CMAKE_INSTALL_PREFIX)

This is optional, the prefix is used to tell make install where to install your library. Normally it's used when you build a shared library and then want to place it somewhere in your /usr/local/lib.

Build System Generator (-G flag)
We plan to use make utility with Makefile, so this option will be "Unix Makefiles".

cmake Tricks

Compiler Test

Now, if you tried to run any of the above commands you definitely face the "C Compiler Test" error. cmake checks clang compiler by trying to compile some test code and that check fails. I tried to use different approaches to pass the compiler test, such as setting project type to NONE in CMakeLists.txt, but nothing worked for me. So I had to come up with somewhat dirty trick to fix this problem:

Run cmake once with no properties set and no generator option, aka "Initial Run"

This will pick up default C and C++ compilers, pass the compiler test, create CMakeCache.txt and CMakeFiles folder. If you cmake again, the compiler test won't be performed any more.

Changed Parameters

If you just ran cmake for the 2nd time with all the properties set (compilers, system root, flags, etc.) you might think that you're good to go and can just make stuff.

But wait... Run ccmake ../../src/Shark and have a look at all the build settings. You'll notice that C++ Compiler Flags are not set. This is specifics of cmake. If you change some important build settings, like compiler flags, the cmake will detect the change and spit out a message like "C++ Compiler Flags changed, you need to configure build to apply the changes." Well, you won't see this message with cmake, but you can play around with ccmake and see it.

In short, you need to run cmake twice if you change some specific settings. That's why you'll see cmakeRun called twice in build.sh for iOS target. And yes, with the initial run to pass compiler test check, you'll end up calling cmake for up to 3 times.

Make It!

At last you're ready to make it!

At this point it is as simple as

make -j16

The -j16 will parallelize the build and make it way faster than plain make.

It doesn't take long and in the end you'll have libshark.a static library. Check it with file utility to make sure you have all the architectures in place.

Lipo Library

So you have a static library for iOS Devices and another one for iOS Simulator. To have the convenience of using same framework for device and simulator you need to merge these two static libraries into one.

Copy Static Library

Rename static library to Shark and copy it into framework bundle

cp build/ios/libshark.a Shark.framework/Versions/A/Shark

Copy Headers

Copy all the headers to framework bundle Headers folder.

Start with copying all the headers from src/include to framework bundle.
Then remove unused statistics.h header. If you check CMakeLists.txt in the source folder, you'll notice that there's no INSTALL rule for statistics.h header.

Patch Headers

You might think "What's next step?" at this point, but there's some serious patching to be applied to header files.

If you just copy the headers "as is", you'll run into a number of nasty compile errors when including headers from the framework. While using a shared library and running on OS X I could apply some workaround for this issue using Header Search Path and other build settings, but that's not so easy when all the headers come from a framework bundle.

While trying to solve the problem I looked into another well known and well built library - boost. All (well, all that I've seen) the includes in the boost library follow the same convention: in the #include directive the header path starts with boost/, for example