# Location of the opensim python package in the build directory, for testing. if(MSVC OR XCODE) # Multi-configuration generators like MSVC and XCODE use one build tree for # all configurations. set(OPENSIM_PYTHON_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}") else() set(OPENSIM_PYTHON_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_BUILD_TYPE}") endif() # Avoid excessive compiler warnings. # ================================== # We set these COMPILE_OPTIONS in the root CMakeLists.txt. set_directory_properties(PROPERTIES COMPILE_OPTIONS "") # Helper macros. # ============== # Helper function to for copying files into the python package. macro(OpenSimPutFileInPythonPackage source_full_path relative_dest_dir) # Python package in the build tree. # --------------------------------- get_filename_component(file_name "${source_full_path}" NAME) set(binary_dest_full_path "${OPENSIM_PYTHON_BINARY_DIR}/${relative_dest_dir}/${file_name}") add_custom_command( DEPENDS "${source_full_path}" OUTPUT "${binary_dest_full_path}" COMMAND ${CMAKE_COMMAND} -E copy "${source_full_path}" "${binary_dest_full_path}" COMMENT "Copying ${source_full_path} to python package in build directory" VERBATIM ) # This list is used to specify dependencies for the PythonBindings target. list(APPEND OPENSIM_PYTHON_PACKAGE_FILES "${binary_dest_full_path}") # Python package in the installation. # ----------------------------------- install(FILES "${source_full_path}" DESTINATION "${OPENSIM_INSTALL_PYTHONDIR}/${relative_dest_dir}") endmacro() # Generates source code for python module and then compiles it. # Here are the arguments: # MODULE: Name of python module. The module is build with the interface file # named ${MODULE}_python.i. # DEPENDS: Names of other python modules on which this module depends. macro(OpenSimAddPythonModule) # Parse arguments. # ---------------- # http://www.cmake.org/cmake/help/v2.8.9/cmake.html#module:CMakeParseArguments set(options) set(oneValueArgs MODULE) set(multiValueArgs DEPENDS) cmake_parse_arguments( OSIMSWIGPY "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) # Generate source code for wrapper using SWIG. # -------------------------------------------- set(_output_file_prefix "${CMAKE_CURRENT_BINARY_DIR}/python_${OSIMSWIGPY_MODULE}_wrap") set(_output_cxx_file "${_output_file_prefix}.cxx") set(_output_header_file "${_output_file_prefix}.h") set(_interface_file "${CMAKE_CURRENT_SOURCE_DIR}/swig/python_${OSIMSWIGPY_MODULE}.i") # We run swig once to get dependencies and then again to actually generate # the wrappers. This variable holds the parts of the swig command that # are shared between both invocations. set(_swig_common_args -c++ -python -I${OpenSim_SOURCE_DIR} -I${OpenSim_SOURCE_DIR}/Bindings/ -I${Simbody_INCLUDE_DIR} -v ${SWIG_FLAGS} ${_interface_file} ) # Assemble dependencies. This macro runs a command during CMake's # configure step and fills the first argument with a list of the # dependencies. OpenSimFindSwigFileDependencies(_${OSIMSWIGPY_MODULE}_dependencies "${OSIMSWIGPY_MODULE} (Python)" "${_swig_common_args}") # Run swig. add_custom_command( OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${OSIMSWIGPY_MODULE}.py" ${_output_cxx_file} ${_output_header_file} COMMAND ${SWIG_EXECUTABLE} #-debug-tmused # Which typemaps were used? -v # verbose -o ${_output_cxx_file} ${SWIG_DOXYGEN_STRING} -outdir "${CMAKE_CURRENT_BINARY_DIR}" ${_swig_common_args} DEPENDS ${_${OSIMSWIGPY_MODULE}_dependencies} COMMENT "Generating python bindings source code with SWIG: ${OSIMSWIGPY_MODULE} module." ) # Compile python wrapper files into a library. # -------------------------------------------- set(_libname _${OSIMSWIGPY_MODULE}) # Used for specifying dependencies for PythonBindings. list(APPEND OPENSIM_PYTHON_PACKAGE_LIBRARY_TARGETS ${_libname}) # We purposefully wrap deprecated functions, so no need to see such # warnings. if(${CMAKE_CXX_COMPILER_ID} MATCHES "GNU" OR ${CMAKE_CXX_COMPILER_ID} MATCHES "Clang") # Turn off optimization for SWIG wrapper code. Optimization slows down # compiling and also requires lots of memory. Also, there's not much to # gain from an optimized wrapper file. # Note that the last optimization flag is what counts for GCC. So an -O0 # later on the command line overrides a previous -O2. set(_COMPILE_FLAGS "-O0 -Wno-deprecated-declarations") elseif(${CMAKE_CXX_COMPILER_ID} MATCHES "MSVC") # TODO disable optimization on Windows. # Don't warn about: # 4996: deprecated functions. # 4114: "const const T" set(_COMPILE_FLAGS "/wd4996 /wd4114") endif() if(${CMAKE_CXX_COMPILER_ID} MATCHES "Clang" AND NOT (${CMAKE_CXX_COMPILER_VERSION} VERSION_LESS "10")) # SWIG uses the register keyword, which is deprecated in C++17. set(_COMPILE_FLAGS "${_COMPILE_FLAGS} -Wno-deprecated-register") endif() # SWIG_PYTHON was added to account for generated cxx files failing to compile # due to operators defined in simbody (removed by this symbol) -Ayman 3/25 set_source_files_properties("${_output_cxx_file}" PROPERTIES COMPILE_FLAGS "${_COMPILE_FLAGS} -DSWIG_PYTHON") add_library(${_libname} SHARED ${_output_cxx_file} ${_output_header_file}) target_include_directories(${_libname} PRIVATE "${Python3_INCLUDE_DIRS}" "${Python3_NumPy_INCLUDE_DIRS}") # On macOS, the python interpreter might link to the python library # statically, in which case we do not want to link dynamically to the # python library. This situation occurs with Anaconda's python. # In this situation, we must add a linker flag to mark all undefined # symbols as having to be looked up at runtime, and we ignore # PYTHON_LIBRARIES. # https://github.com/opensim-org/opensim-core/issues/2771 # https://stackoverflow.com/questions/25421479/clang-and-undefined-symbols-when-building-a-library # https://github.com/lisitsyn/shogun/commit/961f6109c2a8abab4941bcdde1a19dfe70c440f1 # https://github.com/shogun-toolbox/shogun/issues/4068 execute_process(COMMAND "${Python3_EXECUTABLE}" -c "import sysconfig; print(sysconfig.get_config_var('LDSHARED'))" OUTPUT_VARIABLE PYTHON_LDSHARED OUTPUT_STRIP_TRAILING_WHITESPACE) if("${PYTHON_LDSHARED}" MATCHES "dynamic_lookup") set_target_properties(${_libname} PROPERTIES LINK_FLAGS "-undefined dynamic_lookup") else() target_link_libraries(${_libname} ${Python3_LIBRARIES}) endif() target_link_libraries(${_libname} osimTools osimExampleComponents osimMoco) # Set target properties for various platforms. # -------------------------------------------- # Resulting library must be named with .so on Unix, .pyd on Windows. set_target_properties(${_libname} PROPERTIES PROJECT_LABEL "Python - ${_libname}" FOLDER "Bindings" PREFIX "" ) if(WIN32) set_target_properties(${_libname} PROPERTIES SUFFIX ".pyd") elseif(APPLE) # Defaults to .dylib; change to .so. set_target_properties(${_libname} PROPERTIES SUFFIX ".so") endif() # RPATH: We always set a relative RPATH but only use an absolute RPATH if # the python package is not standalone, as the libraries are not copied # into the python package. If Simbody libraries are not installed in the # same folder as the OpenSim libraries, then we must also provide the path # to the Simbody libraries within OpenSim's installation. OpenSimAddInstallRPATHSelf(TARGET ${_libname}) OpenSimAddInstallRPATH(TARGET ${_libname} LOADER FROM "${OPENSIM_INSTALL_PYTHONDIR}/opensim" TO "${CMAKE_INSTALL_LIBDIR}") if(NOT OPENSIM_PYTHON_STANDALONE) OpenSimAddInstallRPATHAbsolute(TARGET ${_libname} TO "${CMAKE_INSTALL_LIBDIR}") endif() OpenSimAddInstallRPATHSimbody(TARGET ${_libname} LOADER FROM "${CMAKE_INSTALL_LIBDIR}/opensim") if(NOT OPENSIM_PYTHON_STANDALONE) OpenSimAddInstallRPATHSimbody(TARGET ${_libname} ABSOLUTE) endif() # Copy files into the build tree python package. # ---------------------------------------------- add_custom_command(TARGET ${_libname} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy "$" "${OPENSIM_PYTHON_BINARY_DIR}/opensim/$" COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_BINARY_DIR}/${OSIMSWIGPY_MODULE}.py" "${OPENSIM_PYTHON_BINARY_DIR}/opensim/${OSIMSWIGPY_MODULE}.py" COMMENT "Copying ${OSIMSWIGPY_MODULE}.py and ${_libname} to python package in build directory." VERBATIM ) # Install the python module and compiled library. # ----------------------------------------------- # For the shared library, it's important that we use install(TARGETS) # because this causes CMake to remove the build-tree RPATH from the library # (which is set temporarily for libraries in the build tree). install(TARGETS ${_libname} DESTINATION "${OPENSIM_INSTALL_PYTHONDIR}/opensim") install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${OSIMSWIGPY_MODULE}.py" DESTINATION "${OPENSIM_INSTALL_PYTHONDIR}/opensim") endmacro() # Build python modules (generate binding source code and compile it). # =================================================================== OpenSimAddPythonModule(MODULE simbody) OpenSimAddPythonModule(MODULE common) OpenSimAddPythonModule(MODULE simulation) OpenSimAddPythonModule(MODULE actuators) OpenSimAddPythonModule(MODULE analyses) OpenSimAddPythonModule(MODULE tools) OpenSimAddPythonModule(MODULE examplecomponents) OpenSimAddPythonModule(MODULE moco) # Copy files to create complete package in the build tree. # ======================================================== # This allows us to test the python package with ctest. # Note: some of the commands to do this copying (for the swig-generated py # files) appear above. # Configure version.py. configure_file(${CMAKE_CURRENT_SOURCE_DIR}/version.py.in "${CMAKE_CURRENT_BINARY_DIR}/version.py" @ONLY) # Copy the configured version.py for each build configuration. OpenSimPutFileInPythonPackage("${CMAKE_CURRENT_BINARY_DIR}/version.py" opensim) # Copy setup.py for each build configuration. OpenSimPutFileInPythonPackage("${CMAKE_CURRENT_SOURCE_DIR}/setup.py" ".") # __init__.py. OpenSimPutFileInPythonPackage("${CMAKE_CURRENT_SOURCE_DIR}/__init__.py" opensim) OpenSimPutFileInPythonPackage("${CMAKE_CURRENT_SOURCE_DIR}/report.py" opensim) # Test files. If you require more test resource files, list them here. foreach(test_file "${CMAKE_CURRENT_SOURCE_DIR}/tests/storage.sto" "${CMAKE_CURRENT_SOURCE_DIR}/tests/gait2392_setup_forward_empty_model.xml" "${CMAKE_CURRENT_SOURCE_DIR}/tests/gait2392_cmc_actuators_empty_model.xml" "${OPENSIM_SHARED_TEST_FILES_DIR}/arm26.osim" "${OPENSIM_SHARED_TEST_FILES_DIR}/gait10dof18musc_subject01.osim" "${CMAKE_SOURCE_DIR}/OpenSim/Sandbox/futureOrientationInverseKinematics.trc" "${CMAKE_SOURCE_DIR}/OpenSim/Common/Test/dataWithNaNsOfDifferentCases.trc" "${CMAKE_SOURCE_DIR}/Applications/Analyze/test/subject02_grf_HiFreq.mot" "${CMAKE_SOURCE_DIR}/Applications/IK/test/std_subject01_walk1_ik.mot" "${CMAKE_SOURCE_DIR}/OpenSim/Tests/shared/walking2.c3d" "${CMAKE_SOURCE_DIR}/OpenSim/Tests/shared/walking5.c3d" "${CMAKE_SOURCE_DIR}/OpenSim/Tests/shared/orientation_quats.sto" "${CMAKE_SOURCE_DIR}/OpenSim/Tests/shared/calibrated_model_imu.osim" ) OpenSimPutFileInPythonPackage("${test_file}" opensim/tests) endforeach() # Umbrella target for assembling the python bindings in the build tree. # ===================================================================== # This command must come *after* all calls to OpenSimPutFileInPythonPackage, as # that macro assembles the OPENSIM_PYTHON_PACKAGE_FILES list. add_custom_target(PythonBindings ALL DEPENDS ${OPENSIM_PYTHON_PACKAGE_FILES}) # Require the libraries to be built. add_dependencies(PythonBindings ${OPENSIM_PYTHON_PACKAGE_LIBRARY_TARGETS}) set_target_properties(PythonBindings PROPERTIES PROJECT_LABEL "Python - umbrella target" FOLDER "Bindings") # Test. # ===== # This test runs all the python tests in the tests directory from the # source tree. It's important to run the tests in the source tree so that # one can edit the tests and immediately re-run the tests without any # intermediate file copying. # It so happens that ${CMAKE_CURRENT_BINARY_DIR}/$ is the same as # ${OPENSIM_PYTHON_BINARY_DIR}, but the former avoids an `if(MSVC OR XCODE)`. add_test(NAME python_tests WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/$" COMMAND "${Python3_EXECUTABLE}" -m unittest discover --start-directory "${CMAKE_CURRENT_SOURCE_DIR}/tests" --verbose ) # List the examples we want to test. Some examples might run too long for # testing, or might require additional libraries. # Leave off the .py extension; here, we are listing module names. set(PYTHON_EXAMPLES_UNITTEST_ARGS build_simple_arm_model extend_OpenSim_Vec3_class posthoc_StatesTrajectory_example wiring_inputs_and_outputs_with_TableReporter ) if(OPENSIM_WITH_CASADI) list(APPEND PYTHON_EXAMPLES_UNITTEST_ARGS Moco.exampleKinematicConstraints Moco.exampleSlidingMass Moco.exampleOptimizeMass Moco.examplePredictAndTrack ) endif() # Similar as above, but for the example files. These files aren't named as # test_*.py, so we must specify a more general search pattern. add_test(NAME python_examples WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/$" COMMAND "${Python3_EXECUTABLE}" -m unittest ${PYTHON_EXAMPLES_UNITTEST_ARGS} --verbose ) set_tests_properties(python_examples PROPERTIES ENVIRONMENT "PYTHONPATH=${CMAKE_CURRENT_SOURCE_DIR}/examples") foreach(folder tests examples) set_property(TEST python_${folder} APPEND PROPERTY ENVIRONMENT "OPENSIM_USE_VISUALIZER=0") endforeach() if(WIN32) # On Windows, CMake cannot use RPATH to hard code the location of libraries # in the binary directory (DLL's don't have RPATH), so we must set PATH to # find the right libraries. The location of the libraries depends on the # build configuration, which is filled in for `$`. We also don't # want to accidentally use a different OpenSim build/installation somewhere # on the machine. foreach(folder tests examples) set_property(TEST python_${folder} APPEND PROPERTY ENVIRONMENT "PATH=${CMAKE_BINARY_DIR}/$") endforeach() endif() # Allow MSVC users to run only the python tests directly from the MSVC GUI. # The python tests are run from RUN_TESTS, so no need to run this target as # part of `BUILD_ALL` (e.g, in MSVC). Might need to set # EXCLUDE_FROM_DEFAULT_BUILD to achieve this? add_custom_target(RunPythonTests COMMAND ${CMAKE_CTEST_COMMAND} --tests-regex python ${OPENSIM_TEST_BUILD_CONFIG} --extra-verbose) set_target_properties(RunPythonTests PROPERTIES PROJECT_LABEL "Python - run tests" FOLDER "Bindings") add_dependencies(RunPythonTests PythonBindings) # Install python package. # ======================= # Most of the files are installed via the above macros. # Install the test scripts. install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/tests" DESTINATION "${OPENSIM_INSTALL_PYTHONDIR}/opensim") # Install example files (not installed next to the python package). install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/examples/" DESTINATION "${OPENSIM_INSTALL_PYTHONEXDIR}") install(DIRECTORY ../Java/Matlab/OpenSenseExample DESTINATION "${OPENSIM_INSTALL_PYTHONEXDIR}" # Exclude Matlab m files PATTERN "*.m" EXCLUDE) install(DIRECTORY ../Java/Matlab/examples/Moco/exampleEMGTracking DESTINATION "${OPENSIM_INSTALL_PYTHONEXDIR}/Moco" # Exclude Matlab m files PATTERN "*.m" EXCLUDE) install(DIRECTORY ../Java/Matlab/examples/Moco/exampleSquatToStand DESTINATION "${OPENSIM_INSTALL_PYTHONEXDIR}/Moco" # Exclude Matlab m files PATTERN "*.m" EXCLUDE) install(DIRECTORY OpenSenseExample DESTINATION "${OPENSIM_INSTALL_PYTHONEXDIR}") # Install the Geometry folder to the Moco examples. install(DIRECTORY ../Java/Matlab/OpenSenseExample/Geometry/ DESTINATION "${OPENSIM_INSTALL_PYTHONEXDIR}/Moco/example3DWalking/Geometry/") install(DIRECTORY ../Java/Matlab/OpenSenseExample/Geometry/ DESTINATION "${OPENSIM_INSTALL_PYTHONEXDIR}/Moco/exampleEMGTracking/Geometry/") install(DIRECTORY ../Java/Matlab/OpenSenseExample/Geometry/ DESTINATION "${OPENSIM_INSTALL_PYTHONEXDIR}/Moco/exampleSquatToStand/Geometry/") install(DIRECTORY ../Java/Matlab/OpenSenseExample/Geometry/ DESTINATION "${OPENSIM_INSTALL_PYTHONEXDIR}/Moco/exampleSquatToStand/exampleIMUTracking/Geometry/")