CMake と SWIG を使って C++ と Python と ROOT に対応させる
やりたいこと
CTA の焦点面カメラの開発に必要なソフトを新たに書く必要があり、以下の条件を満たす必要があります。
- 速度重視の用途に耐えるため、また multi thread に対応するため、中身は C++ で書かれていること
- 速度を重視しない場合や簡単な試験にすぐ使えるように、Python からも利用可能なこと
- C++ を使えない人にも使いやすいこと (これもすなわち、Python で動くこと)
- ROOT からも動くこと
- OS X でも Linux でも動くこと
- Engineer の人でも使えるよう、将来的に Windows 対応もできること
- ROOT に不慣れな人、ROOT を install していない人でも動かせること
- PyROOT ではなく、純粋に Python のみで動くこと
つまり、標準では C++ の shared library を生成し、必要に応じて Python 用の library も生成し、さらに必要に応じて ROOT 用の library も生成するものが必要です。動作環境も全部載せです。
解決策
結論として、CMake と SWIG を使うことにしました。
まず、Autoconf を今更勉強したくなかった*1ので、CMake にしました。それと、CMake のほうが文法が簡単そうというのも理由です*2。また、最近の Geant4 では CMake が標準になったのも理由です。ROOT も新しいものは CMake に対応しています。Geant4 と ROOT が CMake に移行しているのだから、CMake の勉強しておけば役に立つだろう、と。
次に、C++ で書かれた code を Python に持って行くには、やはり SWIG が標準的だそうで、SWIG を使うことにしました。CMake から SWIG を呼び出すのも簡単なことが分かったのも理由です。
簡単と言えば簡単なのですが、CMake の分かりやすい解説はそれほど整備されていません。数日間にわたる Google 検索と試行錯誤の結果をここにまとめます。
使い方
階層
どのように directory や file を置くかは好みですが、自分の試した構成は次の通りです。実際にはもっと inc と src の中身は増えます。
target+ |-CMakeLists.txt |-inc+ | |-BasePacket.h | |-CommandPacket.h | |-ResponsePacket.h | |-LinkDef.h | |-src+ |-BasePacket.cxx |-CommandPacket.cxx |-ResponsePacket.cxx |-CMakeLists.txt |-target.i
それぞれの中身
cmake_minimum_required(VERSION 2.6 FATAL_ERROR) project(TARGET) set(TARGET_VERSION_MAJOR 1) set(TARGET_VERSION_MINOR 0) option(PYTHON "Build the Python version of TARGET library" OFF) option(ROOT "Build the ROOT version of TARGET library" OFF) subdirs(src) include_directories("${TARGET_SOURCE_DIR}/inc")▲ target/CMakeLists.txt
aux_source_directory(. SOURCES) add_library(TARGET SHARED ${SOURCES}) install(TARGETS TARGET DESTINATION ${CMAKE_INSTALL_PREFIX}/lib) if(ROOT) message("ROOT support is added") find_package(ROOT REQUIRED) include(${ROOT_USE_FILE}) include_directories(${ROOT_INCLUDE_DIRS}) file(GLOB INCS "${TARGET_SOURCE_DIR}/inc/*h") file(GLOB LINKDEF_H "${TARGET_SOURCE_DIR}/inc/LinkDef.h") list(REMOVE_ITEM INCS ${LINKDEF_H}) ROOT_GENERATE_DICTIONARY(RTARGETDict ${INCS} LINKDEF ${LINKDEF_H}) ROOT_LINKER_LIBRARY(RTARGET ${SOURCES} RTARGETDict.cxx LIBRARIES Hist MathCore) ROOT_GENERATE_ROOTMAP(RTARGET LINKDEF ${LINKDEF_H} DEPENDENCIES Hist MathCore) endif(ROOT) if(PYTHON) message("Python support is added") find_package(SWIG REQUIRED) find_package(PythonLibs REQUIRED) include(${SWIG_USE_FILE}) set(CMAKE_SWIG_FLAGS "") include_directories(${PYTHON_INCLUDE_DIRS}) set_source_files_properties(target.i PROPERTIES CPLUSPLUS ON) set_source_files_properties(target.i PROPERTIES SWIG_FLAGS "-includeall") swig_add_module(target python target.i ${SOURCES}) swig_link_libraries(target ${PYTHON_LIBRARIES}) execute_process(COMMAND python -c "from distutils.sysconfig import get_python_lib; print get_python_lib()" OUTPUT_VARIABLE PYTHON_SITE_PACKAGES OUTPUT_STRIP_TRAILING_WHITESPACE) install(TARGETS _target DESTINATION ${PYTHON_SITE_PACKAGES}) install(FILES ${CMAKE_BINARY_DIR}/src/target.py DESTINATION ${PYTHON_SITE_PACKAGES}) endif(PYTHON)▲ src/CMakeLists.txt
#ifndef TARGET_BASE_PACKET_H #define TARGET_BASE_PACKET_H namespace TARGET { class BasePacket { private: public: BasePacket(); virtual ~BasePacket(); }; } // TARGET #endif // TARGET_BASE_PACKET_H▲ target/inc/BasePacket.h
#include "BasePacket.h" namespace TARGET { BasePacket::BasePacket() { } //______________________________________________________________________________ BasePacket::~BasePacket() { } } // TARGET▲ target/src/BasePacket.cxx (中身はまだ空です)
#ifdef __CINT__ #pragma link off all globals; #pragma link off all classes; #pragma link off all functions; #pragma namespace TARGET; #pragma link C++ class TARGET::BasePacket; #pragma link C++ class TARGET::CommandPacket; #pragma link C++ class TARGET::ResponsePacket; #endif▲ inc/LinkDef.h
%module target %{ #include "BasePacket.h" #include "CommandPacket.h" #include "ResponsePacket.h" %} %include "BasePacket.h" %include "CommandPacket.h" %include "ResponsePacket.h"▲ src/target.i
Build
$HOME に target という directory があると仮定します。
$ mkdir build $ cd build $ cmake ~/target -DPYTHON=ON -DROOT=ON (snip) $ make (snip) $ sudo make install [ 35%] Built target RTARGET [ 57%] Built target TARGET [ 92%] Built target _target [100%] Built target libRTARGET.rootmap Install the project... -- Install configuration: "RelWithDebInfo" -- Installing: /usr/local/lib/libTARGET.dylib -- Installing: /usr/local/lib/libRTARGET.so -- Installing: /usr/local/lib/libRTARGET.rootmap -- Installing: /Library/Python/2.7/site-packages/_target.so -- Installing: /Library/Python/2.7/site-packages/target.py
これで、/usr/local/lib に C++ 用の library である libTARGET.dylib、ROOT 用の libRTARGET.so と libRTARGET.rootmap、また /Library/Python/2.7/site-packages に Python 用の _target.so と target.py が install されました。
実際に使ってみる
>>> import target
>>> base = target.BasePacket()
root [0] TARGET::BasePacket* base = new TARGET::BasePacket()
動作試験環境
Linux
- Scientific Linux 6.3
- GCC 4.4.6
- Python 2.6.6 (yum で python-devel を入れる必要あり)
- ROOT 5.34/04
- SWIG 1.3.40 (yum)
- CMake 2.8.10.2 (yum で導入可能な 2.6.4 では、ROOT の build でこけるので、2.8 以上を自分で導入する必要あり)
ROOT と CMake 以外は全て yum で取ってこられる最新のもの。
ただし、Scientific Linux の環境では /usr/include/python2.6/Python.h を見つけてくれなかったため、次のように実行しました。CMake はこれくらい自動で見つけてほしいですが…。
$ cmake ~/target -DPYTHON=ON -DPYTHON_INCLUDE_DIRS=/usr/include/python2.6 -DROOT=ON