/cmake-swig

Modern CMake C++ SWIG Sample

Primary LanguageCMakeApache License 2.0Apache-2.0

Build Status Build status

Introduction

This is an example of how to create a Modern CMake C++ Project with the SWIG code generator to generate package for Python, .Net and Java.

This project should run on GNU/Linux, MacOS and Windows.

Wrapper/Package Status

Python

  • GNU/Linux wrapper
  • MacOS wrapper
  • Windows wrapper

Dotnet

  • GNU/Linux wrapper
  • MacOS wrapper
  • Windows wrapper

Java

  • GNU/Linux wrapper
  • MacOS wrapper
  • Windows wrapper

CMake Dependencies Tree

To complexify a little, the CMake project is composed of three libraries (Foo, Bar and FooBar) with the following dependencies:

Foo:
Bar:
FooBar: PRIVATE Foo Bar

Project directory layout

Thus the project layout is as follow:

 CMakeLists.txt // meta CMake doing the orchestration and python packaging
 Foo
 ├── CMakeLists.txt
 ├── include
 │   └── foo
 │       └── Foo.hpp
 ├── python
 │   ├── CMakeLists.txt
 │   └── foo.i
 └── src
     └── Foo.cpp
 Bar
 ├── CMakeLists.txt
 ├── include
 │   └── bar
 │       └── Bar.hpp
 ├── python
 │   ├── bar.i
 │   └── CMakeLists.txt
 └── src
     └── Bar.cpp
 FooBar
 ├── CMakeLists.txt
 ├── include
 │   └── foobar
 │       └── FooBar.hpp
 ├── python
 │   ├── CMakeLists.txt
 │   └── foobar.i
 └── src
     ├── FooBar.cpp
     └── main.cpp

C++ Project Build

To build the C++ project, as usual:

mkdir build && cd build
cmake ..
make

note: SWIG automatically put its target(s) in all, thus make will also call swig and generate _module.so.

Python

Build directory layout

Since we want to use the CMAKE_BINARY_DIR to generate the python binary package.
We want this layout (tree build --prune -P ".py|.so"):

 Bar
 ├── __init__.py
 ├── libBar.so
 ├── pyBar.py
 └── _pyBar.so
 Foo
 ├── __init__.py
 ├── libFoo.so
 ├── pyFoo.py
 └── _pyFoo.so
 FooBar
 ├── __init__.py
 ├── libFooBar.so
 ├── pyFooBar.py
 └── _pyFooBar.so

Build the Binary Package

To build the python package, simply run:

make bdist

Tips

While running swig to generate Python wrapper it's easy thanks to UseSWIG module.

Creating a Python binary package containing all .py and .so (with good rpath) is not so easy...

Managing SWIG generated files

Since python use the directory name where __init__.py file is located.
We would like to have pyFoo.py generated file in build/Foo and not in build/Foo/python.
You can use CMAKE_SWIG_DIR to change the output directory for the .py file e.g.:

set(CMAKE_SWIG_OUTDIR ${CMAKE_CURRENT_BINARY_DIR}/..)

And you can use CMAKE_LIBRARY_OUTPUT_DIRECTORY to change the output directory for the .so file e.g.:

set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/..)

[optional]You can use SWIG_OUTFILE_DIR to change the output directory for the .cxx file e.g.:

set(SWIG_OUTFILE_DIR ${CMAKE_CURRENT_BINARY_DIR}/..)

Then you only need to create a __init__.py file in build/Foo to be able to use the build directory to generate the Python package.

Managing RPATH

Since we want to use the CMAKE_BINARY_DIR to generate the python binary package. We need to enable:

set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE)

And have a finely tailored rpath for each library.

For Foo/CMakeLists.txt (which depend on nothing):

set(CMAKE_INSTALL_RPATH "$ORIGIN/")

For FooBar/CMakeLists.txt (which depend on Foo & Bar):

set(CMAKE_INSTALL_RPATH "$ORIGIN/../Foo:$ORIGIN/../Bar:$ORIGIN/")

note: you allways need $ORIGIN/ since _pyFoo.so will depend on libFoo.so (which will be built in the same directory see above).

Why setup.py has to be generated

To avoid to put hardcoded path to SWIG .so generated files, we could use $<TARGET_FILE:tgt> to retrieve the file (and also deal with Mac/Windows suffix, and target dependencies).
In order for setup.py to use cmake generator expression (e.g. $<TARGET_FILE:_pyFoo>). We need to generate it at build time (e.g. using add_custom_command()).
note: This will also add automatically a dependency between the command and the TARGET !

Testing Python

Testing using virtualenv

Testing using the CMAKE_BINARY_DIR

Contributing

The CONTRIBUTING.md file contains instructions on how to file the Contributor License Agreement before sending any pull requests (PRs). Of course, if you're new to the project, it's usually best to discuss any proposals and reach consensus before sending your first PR.

License

Apache 2. See the LICENSE file for details.

Disclaimer

This is not an official Google product, it is just code that happens to be owned by Google.