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.
- GNU/Linux wrapper
- MacOS wrapper
- Windows wrapper
- GNU/Linux wrapper
- MacOS wrapper
- Windows wrapper
- GNU/Linux wrapper
- MacOS wrapper
- Windows wrapper
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
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
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
.
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
To build the python package, simply run:
make bdist
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...
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.
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).
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 !
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.
Apache 2. See the LICENSE file for details.
This is not an official Google product, it is just code that happens to be owned by Google.