From 20f5c465076ab6e51c67642ba98cc5a6267f6d62 Mon Sep 17 00:00:00 2001 From: Andrew Melim Date: Wed, 17 Dec 2014 10:44:56 -0800 Subject: [PATCH] Reworked python directory structure. Added readme on constructing python module. Added first unit test for point2. Everything needed to get it passing is also here, including some renaming of variables and emitted library names Conflicts: cmake/GtsamPythonWrap.cmake python/handwritten/examples/OdometeryExample.py wrap/Module.cpp --- cmake/GtsamPythonWrap.cmake | 52 +++++++++++++----- python/README.txt | 12 ++++ python/gtsam/__init__.py | 2 +- python/gtsam/libgtsam_python.so | Bin 0 -> 63367 bytes python/gtsam_tests/testPoint2.py | 13 +++++ python/{ => handwritten}/CMakeLists.txt | 0 .../handwritten/examples/OdometeryExample.py | 7 +++ python/{ => handwritten}/exportgtsam.cpp | 0 python/{ => handwritten}/geometry/Point2.cpp | 0 python/{ => handwritten}/geometry/Pose2.cpp | 0 python/{ => handwritten}/geometry/Rot2.cpp | 0 .../{ => handwritten}/linear/NoiseModel.cpp | 0 .../nonlinear/LevenbergMarquardtOptimizer.cpp | 0 .../nonlinear/NonlinearFactorGraph.cpp | 0 python/{ => handwritten}/nonlinear/Values.cpp | 0 .../{ => handwritten}/slam/BearingFactor.cpp | 0 .../{ => handwritten}/slam/BetweenFactor.cpp | 0 python/{ => handwritten}/slam/PriorFactor.cpp | 0 python/setup.py | 2 +- 19 files changed, 71 insertions(+), 17 deletions(-) create mode 100644 python/README.txt create mode 100755 python/gtsam/libgtsam_python.so create mode 100644 python/gtsam_tests/testPoint2.py rename python/{ => handwritten}/CMakeLists.txt (100%) create mode 100644 python/handwritten/examples/OdometeryExample.py rename python/{ => handwritten}/exportgtsam.cpp (100%) rename python/{ => handwritten}/geometry/Point2.cpp (100%) rename python/{ => handwritten}/geometry/Pose2.cpp (100%) rename python/{ => handwritten}/geometry/Rot2.cpp (100%) rename python/{ => handwritten}/linear/NoiseModel.cpp (100%) rename python/{ => handwritten}/nonlinear/LevenbergMarquardtOptimizer.cpp (100%) rename python/{ => handwritten}/nonlinear/NonlinearFactorGraph.cpp (100%) rename python/{ => handwritten}/nonlinear/Values.cpp (100%) rename python/{ => handwritten}/slam/BearingFactor.cpp (100%) rename python/{ => handwritten}/slam/BetweenFactor.cpp (100%) rename python/{ => handwritten}/slam/PriorFactor.cpp (100%) mode change 100644 => 100755 python/setup.py diff --git a/cmake/GtsamPythonWrap.cmake b/cmake/GtsamPythonWrap.cmake index fbad77a82..581b068ad 100644 --- a/cmake/GtsamPythonWrap.cmake +++ b/cmake/GtsamPythonWrap.cmake @@ -36,24 +36,46 @@ function(wrap_python TARGET_NAME PYTHON_MODULE_DIRECTORY) ENDIF() ENDIF(APPLE) + if(MSVC) + add_library(${moduleName}_python MODULE ${ARGN}) + set_target_properties(${moduleName}_python PROPERTIES + OUTPUT_NAME ${moduleName}_python + CLEAN_DIRECT_OUTPUT 1 + VERSION 1 + SOVERSION 0 + SUFFIX ".pyd") + target_link_libraries(${moduleName}_python ${Boost_PYTHON_LIBRARY} ${PYTHON_LIBRARY} ${gtsamLib}) #temp - # Create a static library version - add_library(${TARGET_NAME} SHARED ${ARGN}) + set(PYLIB_OUTPUT_FILE $) + message(${PYLIB_OUTPUT_FILE}) + get_filename_component(PYLIB_OUTPUT_NAME ${PYLIB_OUTPUT_FILE} NAME_WE) + set(PYLIB_SO_NAME ${PYLIB_OUTPUT_NAME}.pyd) - target_link_libraries(${TARGET_NAME} ${Boost_PYTHON_LIBRARY} ${PYTHON_LIBRARY} gtsam-shared) - set_target_properties(${TARGET_NAME} PROPERTIES - OUTPUT_NAME ${TARGET_NAME} - CLEAN_DIRECT_OUTPUT 1 - VERSION 1 - SOVERSION 0) + ELSE() + # Create a shared library + add_library(${moduleName}_python SHARED ${generated_cpp_file}) + set_target_properties(${moduleName}_python PROPERTIES + OUTPUT_NAME ${moduleName}_python + CLEAN_DIRECT_OUTPUT 1) + target_link_libraries(${moduleName}_python ${Boost_PYTHON_LIBRARY} ${PYTHON_LIBRARY} ${gtsamLib}) #temp + # On OSX and Linux, the python library must end in the extension .so. Build this + # filename here. + get_property(PYLIB_OUTPUT_FILE TARGET ${moduleName}_python PROPERTY LOCATION) + set(PYLIB_OUTPUT_FILE $) + message(${PYLIB_OUTPUT_FILE}) + get_filename_component(PYLIB_OUTPUT_NAME ${PYLIB_OUTPUT_FILE} NAME_WE) + set(PYLIB_SO_NAME lib${moduleName}_python.so) + ENDIF(MSVC) - # On OSX and Linux, the python library must end in the extension .so. Build this - # filename here. - #get_property(PYLIB_OUTPUT_FILE TARGET ${TARGET_NAME} PROPERTY LOCATION) - set(PYLIB_OUTPUT_FILE $) - get_filename_component(PYLIB_OUTPUT_NAME ${PYLIB_OUTPUT_FILE} NAME_WE) - set(PYLIB_SO_NAME ${PYLIB_OUTPUT_NAME}.so) + # Installs the library in the gtsam folder, which is used by setup.py to create the gtsam package + set(PYTHON_MODULE_DIRECTORY ${CMAKE_SOURCE_DIR}/python/gtsam) + # Cause the library to be output in the correct directory. + add_custom_command(TARGET ${moduleName}_python + POST_BUILD + COMMAND cp -v ${PYLIB_OUTPUT_FILE} ${PYTHON_MODULE_DIRECTORY}/${PYLIB_SO_NAME} + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + COMMENT "Copying library files to python directory" ) # Cause the library to be output in the correct directory. add_custom_command(TARGET ${TARGET_NAME} @@ -65,4 +87,4 @@ function(wrap_python TARGET_NAME PYTHON_MODULE_DIRECTORY) get_directory_property(AMCF ADDITIONAL_MAKE_CLEAN_FILES) list(APPEND AMCF ${PYTHON_MODULE_DIRECTORY}/${PYLIB_SO_NAME}) set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES "${AMCF}") -endfunction(wrap_python) \ No newline at end of file +endfunction(wrap_python) diff --git a/python/README.txt b/python/README.txt new file mode 100644 index 000000000..5235300c2 --- /dev/null +++ b/python/README.txt @@ -0,0 +1,12 @@ +This directory contains the basic setup script and directory structure for the gtsam python module. +During the build of gtsam, when GTSAM_BUILD_PYTHON is enabled, the following instructions will run. +* Wrap parses gtsam.h and constructs a cpp file called ${moduleName}_python.cpp +* This file is then compiled and linked with BoostPython, generating a shared library which can then be imported by python +* The shared library is then copied to python/gtsam +* The user can use the setup.py script to build and install a python package, allowing easy importing into a python project. Examples: + * python setup.py sdist ---- Builds a tarball of the python package which can then be distributed + * python setup.py install ---- Installs the package into the python dist-packages folder. Can then be imported from any python file. +* To run the unit tests, you must first install the package on your path (TODO: Make this easier) + + +TODO: There are many issues with this build system, but these are the basics. diff --git a/python/gtsam/__init__.py b/python/gtsam/__init__.py index 2e9b00af1..f1b07905b 100644 --- a/python/gtsam/__init__.py +++ b/python/gtsam/__init__.py @@ -1 +1 @@ -from libgtsam import * \ No newline at end of file +from libgtsam_python import * diff --git a/python/gtsam/libgtsam_python.so b/python/gtsam/libgtsam_python.so new file mode 100755 index 0000000000000000000000000000000000000000..bd87730d7ac1534f9e835fa6997a6b0e77af8485 GIT binary patch literal 63367 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