diff --git a/.travis b/.travis index c6f1a3d39037be5dfc98d0d1d67db7dfe0141863..4e78d6f1ece19639c6ebdda18b2f081c784e3d04 160000 --- a/.travis +++ b/.travis @@ -1 +1 @@ -Subproject commit c6f1a3d39037be5dfc98d0d1d67db7dfe0141863 +Subproject commit 4e78d6f1ece19639c6ebdda18b2f081c784e3d04 diff --git a/CMakeLists.txt b/CMakeLists.txt index ae3402828411787d13eba17f128b2bf36ae0d283..cfb625daafb9ca4f349461b76b20a83d26c870e9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,9 +48,6 @@ PKG_CONFIG_APPEND_LIBS("dynamic-graph-python") SET(BOOST_COMPONENTS python filesystem system thread program_options unit_test_framework) SEARCH_FOR_BOOST() -# Make sure Boost.Filesystem v2 is used. -ADD_DEFINITIONS(-DBOOST_FILESYSTEM_VERSION=2) - ADD_SUBDIRECTORY(src) ADD_SUBDIRECTORY(include) ADD_SUBDIRECTORY(doc) diff --git a/README.md b/README.md index 0fa53da64bbdaf0b73e73e018d8857298a76c7ef..d82ec78ae4ab66aa824db05a1195335a2cb81ce1 100644 --- a/README.md +++ b/README.md @@ -4,4 +4,118 @@ dynamic-graph-python [](https://travis-ci.org/stack-of-tasks/dynamic-graph-python) [](https://coveralls.io/r/stack-of-tasks/dynamic-graph-python) -Python bindings for dynamic-graph. \ No newline at end of file +Python bindings for dynamic-graph. + + +**Warning:** this repository contains [Git +submodules][git-submodules]. Please clone this repository using the +`git clone --recursive` command. If you already have cloned the +repository, you can run `git submodule init && git submodule update` +to retrieve the submodules. + + +Documentation +------------- + +To get started with this library, please read the [online Doxygen +documentation][doxygen-documentation]. + +It can also be generated locally by running the `make doc` +command. After the package is installed, the documentation will be +located in the `$prefix/share/doc/dynamic-graph` directoy where +`$prefix` is your installation prefix (`/usr/local` by default). + + +Getting Help +------------ + +Support is provided through: + * the HPP mailing-list: hpp@laas.fr + * the following HipChat room: http://www.hipchat.com/gh4wQrZeV + + +How can I install dynamic-graph? +-------------------------------- + +### Installing dependencies + +The matrix abstract layer depends on several packages which +have to be available on your machine. + + - Libraries: + - [Boost][] (>= 1.40) + Its detection is controled by the `BOOST_ROOT` variable, see next section + for more information. + - [Lapack][] library + Use the generic purpose `CMAKE_CXX_FLAGS` and `CMAKE_EXE_LINKER_FLAGS` + to insert the flags required for the compiler to find your Lapack library + if it is installed in a non-standard directory. + - [jrl-mal][] library + - [dynamic-graph][] library + - System tools: + - [CMake][] (>=2.6) + - [pkg-config][] + - usual compilation tools (GCC/G++, make, etc.) + If you are using Ubuntu, these tools are gathered in the `build-essential` package. + + + +### Compiling and installing the package + +The manual compilation requires two steps: + + 1. configuration of the build and generation of the build files + 2. compilation of the sources and installation of the package + +dynamic-graph uses [CMake][] to generate build files. It is +recommended to create a separate build directory: + +```sh +mkdir _build # (1) Create a build directory +cd _build # (2) Go to the newly created build directory +cmake [options] .. # (3) Generate the build files +``` + +Options which can be passed to CMake are detailed in the next section. + +```sh +make # (4) Compile the package +make test # (5) Execute the package tests +make install # (6) Install the package into the prefix (see step 3) +``` + + +### Options + +Additional options can be set on the command line through the +following command: `-D<option>=<value>`. + +For instance: `cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo ..` will set +the `CMAKE_BUILD_TYPE` option to the value `RelWithDebInfo`. + + +Available options are: + +- `CMAKE_BUILD_TYPE` set the build profile that should be used (debug, + release, etc.). We recommend `RelWithDebInfo` as it will provide + performances while keeping debugging symbols enabled. +- `CMAKE_INSTALL_PREFIX` set the installation prefix (the directory + where the software will be copied to after it has been compiled). + + +### Running the test suite + +The test suite can be run from your build directory by running: + +```sh + make test +``` + +Please open a ticket if some tests are failing on your computer, it +should not be the case. + +Credits +------- + +This package authors are credited in the [AUTHORS](AUTHORS) file. + diff --git a/include/dynamic-graph/python/interpreter.hh b/include/dynamic-graph/python/interpreter.hh index 5d2e4f353a9920d77e5fc368b5ef5733697038d0..2c49695c5121e62841a16b4d119f4d23ed97ea69 100644 --- a/include/dynamic-graph/python/interpreter.hh +++ b/include/dynamic-graph/python/interpreter.hh @@ -41,7 +41,7 @@ namespace dynamicgraph { /// \brief Method to start python interperter. /// \param command string to execute /// Method deprecated, you *SHOULD* handle error messages. - std::string python( const std::string& command ) DYNAMIC_GRAPH_PYTHON_DEPRECATED; + DYNAMIC_GRAPH_PYTHON_DEPRECATED std::string python( const std::string& command ); /// \brief Method to start python interperter. /// \param command string to execute, result, stdout, stderr strings diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index cbedc3e7e8ca29ca5f5a5b9bdd8502e30c97be82..79acbd22c0423b470a9b8a12fb557315a084e785 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -37,7 +37,9 @@ ADD_LIBRARY(${LIBRARY_NAME} TARGET_LINK_LIBRARIES(${LIBRARY_NAME} ${PYTHON_LIBRARY}) -TARGET_LINK_LIBRARIES(${LIBRARY_NAME} ${Boost_LIBRARIES}) +IF(UNIX) + TARGET_LINK_LIBRARIES(${LIBRARY_NAME} ${Boost_LIBRARIES}) +ENDIF(UNIX) SET_TARGET_PROPERTIES(${LIBRARY_NAME} PROPERTIES SOVERSION ${PROJECT_VERSION}) PKG_CONFIG_USE_DEPENDENCY(${LIBRARY_NAME} dynamic-graph) diff --git a/src/interpreter.cc b/src/interpreter.cc index 0b97823b9cd3552e8e7053940ce51e1b7530a460..373d5baab27159ac43c7561e2eaaa79a433829bd 100644 --- a/src/interpreter.cc +++ b/src/interpreter.cc @@ -23,8 +23,11 @@ #include <boost/python/object.hpp> #include <boost/python/handle.hpp> #include <boost/python/extract.hpp> +#include <boost/python/str.hpp> +#include <boost/python/import.hpp> using namespace boost::python; +namespace py=boost::python; std::ofstream dg_debugfile( "/tmp/dynamic-graph-traces.txt", std::ios::trunc&std::ios::out ); @@ -53,6 +56,10 @@ static const std::string pythonPrefix[5] = { namespace dynamicgraph { namespace python { +// Parse a python exception and return the corresponding error message +// http://thejosephturner.com/blog/post/embedding-python-in-c-applications-with-boostpython-part-2/ +std::string parse_python_exception(); + bool HandleErr(std::string & err, PyObject * traceback_format_exception, PyObject * globals_, @@ -63,7 +70,6 @@ bool HandleErr(std::string & err, bool lres=false; if (PyErr_Occurred()) { - PyObject *ptype, *pvalue, *ptraceback, *pyerr; PyErr_Fetch(&ptype, &pvalue, &ptraceback); if (ptraceback == NULL) { @@ -242,60 +248,24 @@ void Interpreter::runPythonFile( std::string filename ) runPythonFile(filename, err); } - void Interpreter::runPythonFile( std::string filename, std::string& err) { + FILE* pFile = fopen( filename.c_str(),"r" ); + if (pFile==0x0) + { + err = filename + " cannot be open"; + return; + } + err = ""; PyObject* pymainContext = globals_; - PyObject* run = PyRun_FileExFlags(fopen( filename.c_str(),"r" ), filename.c_str(), + PyObject* run = PyRun_FileExFlags(pFile, filename.c_str(), Py_file_input, pymainContext,pymainContext, true, NULL); + if (PyErr_Occurred()) { - PyObject *ptype, *pvalue, *ptraceback; - PyErr_Fetch(&ptype, &pvalue, &ptraceback); - - handle<> hTraceback(ptraceback); - object traceback(hTraceback); - - //Extract error message - std::string strErrorMessage = extract<std::string>(pvalue); - std::ostringstream errstream; - - //TODO does not work for now for a single command. - do - { - //Extract line number (top entry of call stack) - // if you want to extract another levels of call stack - // also process traceback.attr("tb_next") recurently - long lineno = extract<long> (traceback.attr("tb_lineno")); - std::string filename = extract<std::string> - (traceback.attr("tb_frame").attr("f_code").attr("co_filename")); - std::string funcname = extract<std::string> - (traceback.attr("tb_frame").attr("f_code").attr("co_name")); - errstream << " File \"" << filename <<"\", line " - << lineno << ", in "<< funcname << std::endl; - - // get the corresponding line. - std::ostringstream cmd; - cmd << "linecache.getline('"<<filename<<"', "<<lineno <<")"; - PyObject* line_obj = PyRun_String(cmd.str().c_str(), - Py_eval_input, globals_, globals_); - std::string line = PyString_AsString(line_obj); - Py_DecRef(line_obj); - - // remove the spaces at the beginning of the line. - size_t index = line.find_first_not_of (" \t"); - errstream << " " << line.substr(index, line.size()-index); - - // go to the next line. - traceback = traceback.attr("tb_next"); - } - while (traceback); - - // recreate the error message - errstream << strErrorMessage << std::endl; - err =errstream.str(); - std::cerr << err; + err = parse_python_exception(); + std::cerr << err << std::endl;; } Py_DecRef(run); } @@ -323,5 +293,49 @@ std::string Interpreter::processStream(std::istream& stream, std::ostream& os) #endif return command; } + +std::string parse_python_exception() +{ + PyObject *type_ptr = NULL, *value_ptr = NULL, *traceback_ptr = NULL; + PyErr_Fetch(&type_ptr, &value_ptr, &traceback_ptr); + std::string ret("Unfetchable Python error"); + + if(type_ptr != NULL) + { + py::handle<> h_type(type_ptr); + py::str type_pstr(h_type); + py::extract<std::string> e_type_pstr(type_pstr); + if(e_type_pstr.check()) + ret = e_type_pstr(); + else + ret = "Unknown exception type"; + } + + if(value_ptr != NULL) + { + py::handle<> h_val(value_ptr); + py::str a(h_val); + py::extract<std::string> returned(a); + if(returned.check()) + ret += ": " + returned(); + else + ret += std::string(": Unparseable Python error: "); + } + + if(traceback_ptr != NULL) + { + py::handle<> h_tb(traceback_ptr); + py::object tb(py::import("traceback")); + py::object fmt_tb(tb.attr("format_tb")); + py::object tb_list(fmt_tb(h_tb)); + py::object tb_str(py::str("\n").join(tb_list)); + py::extract<std::string> returned(tb_str); + if(returned.check()) + ret += ": " + returned(); + else + ret += std::string(": Unparseable Python traceback"); + } + return ret; +} } //namespace python } // namespace dynamicgraph diff --git a/unitTesting/CMakeLists.txt b/unitTesting/CMakeLists.txt index 79a32e53c85648d7317bd38bd92a941a989dc349..c2e2872325063e182309b5670f48fa8f8bb0f7a0 100644 --- a/unitTesting/CMakeLists.txt +++ b/unitTesting/CMakeLists.txt @@ -38,9 +38,11 @@ TARGET_LINK_LIBRARIES(${EXECUTABLE_NAME} dynamic-graph-python) ADD_TEST(${EXECUTABLE_NAME} ${EXECUTABLE_NAME}) ADD_CUSTOM_COMMAND(TARGET interpreter-test-runfile POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/unitTesting/test_python_ok.py + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/unitTesting/test_python-ok.py ${CMAKE_BINARY_DIR}/unitTesting - COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/unitTesting/test_python_error.py + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/unitTesting/test_python-name_error.py + ${CMAKE_BINARY_DIR}/unitTesting + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/unitTesting/test_python-syntax_error.py ${CMAKE_BINARY_DIR}/unitTesting ) diff --git a/unitTesting/interpreter-test-runfile.cc b/unitTesting/interpreter-test-runfile.cc index e1e508209a4d12b432aa2bea3092acdff511a8e0..8b8a854e7ca9f51f1bb5b1595038b3af76f4cb2e 100644 --- a/unitTesting/interpreter-test-runfile.cc +++ b/unitTesting/interpreter-test-runfile.cc @@ -4,6 +4,25 @@ #include "dynamic-graph/python/interpreter.hh" +bool testFile(const std::string & filename, + const std::string & expectedOutput, + int numTest) +{ + std::string err = ""; + dynamicgraph::python::Interpreter interp; + for (int i=0; i<numTest; ++i) + { + interp.runPythonFile(filename, err); + if (err != expectedOutput) + { + std::cerr << "At iteration " << i << ", the output was not the one expected:" << std::endl; + std::cerr << " expected: " << expectedOutput << std::endl; + std::cerr << " err: " << err << std::endl; + return false; + } + } + return true; +} int main(int argc, char ** argv) { @@ -14,35 +33,18 @@ int main(int argc, char ** argv) if (argc > 1) numTest = atoi(argv[1]); - std::string empty_err = ""; - dynamicgraph::python::Interpreter interp; - - for (int i=0; i<numTest; ++i) - { - interp.runPythonFile("test_python_ok.py", empty_err); - if (empty_err != "") - { - std::cerr << "At iteration " << i << ", the error was not empty:" << std::endl; - std::cerr << " err " << empty_err << std::endl; - return -1; - } - } - - // check that the error remains the same, despite of the number of executions - std::string old_err; - interp.runPythonFile("test_python_error.py", old_err); - std::string new_err = old_err; - for (int i=0; i<numTest; ++i) - { - interp.runPythonFile("test_python_error.py", new_err); - if (old_err != new_err) - { - std::cerr << "At iteration " << i << ", the error changed:" << std::endl; - std::cerr << " old " << old_err << std::endl; - std::cerr << " new " << new_err << std::endl; - return -1; - } - } - - return 0; + bool res = true; + res = testFile("test_python-ok.py", "", numTest) && res; + res = testFile("unexistant_file.py", + "unexistant_file.py cannot be open", + numTest) && res; + res = testFile("test_python-name_error.py", + std::string("<type 'exceptions.NameError'>: name 're' is not defined:")+ + " File \"test_python-name_error.py\", line 6, in <module>\n" + + " pathList = re.split(':', pkgConfigPath)\n", numTest) && res; + res = testFile("test_python-syntax_error.py", + std::string("<type 'exceptions.SyntaxError'>: ('invalid syntax', ")+ + "('test_python-syntax_error.py', 1, 11, "+ + "'hello world\\n'))", numTest) && res; + return (res?0:1); } diff --git a/unitTesting/test_python_error.py b/unitTesting/test_python-name_error.py similarity index 100% rename from unitTesting/test_python_error.py rename to unitTesting/test_python-name_error.py diff --git a/unitTesting/test_python_ok.py b/unitTesting/test_python-ok.py similarity index 100% rename from unitTesting/test_python_ok.py rename to unitTesting/test_python-ok.py diff --git a/unitTesting/test_python-syntax_error.py b/unitTesting/test_python-syntax_error.py new file mode 100644 index 0000000000000000000000000000000000000000..3b18e512dba79e4c8300dd08aeb37f8e728b8dad --- /dev/null +++ b/unitTesting/test_python-syntax_error.py @@ -0,0 +1 @@ +hello world