diff --git a/Bindings/Python/CMakeLists.txt b/Bindings/Python/CMakeLists.txt index 7759ab208f..222c4d8a9c 100644 --- a/Bindings/Python/CMakeLists.txt +++ b/Bindings/Python/CMakeLists.txt @@ -2,16 +2,6 @@ # Find python. # ============ -# PythonInterp is supposed to come before PythonLibs. -find_package(PythonInterp 2.7 REQUIRED) -find_package(PythonLibs 2.7 REQUIRED) - -# We may need to update the python install dir to include the python version, -# now that we know it. We replace the token "VERSION" with the actual python -# version. -string(REPLACE "VERSION" "${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}" - OPENSIM_INSTALL_PYTHONDIR "${OPENSIM_INSTALL_PYTHONDIR}") - # 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 @@ -160,11 +150,21 @@ macro(OpenSimAddPythonModule) endif() if(${OPENSIM_USE_INSTALL_RPATH}) + # TODO @loader_path only makes sense on macOS, so we need to revisit + # for Linux (use $ORIGIN). + # 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. + # TODO consider using a relative path from the python package directory + # to the OpenSim library directory. + set(run_path_list "\@loader_path/") + if(NOT OPENSIM_PYTHON_STANDALONE) + list(APPEND run_path_list + "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}") + endif() # Use APPEND to include the BTK RPATH, set in CMAKE_INSTALL_RPATH. - # TODO test that NOT using APPEND causes BTK to not be found. set_property(TARGET ${_libname} APPEND PROPERTY - INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}" - ) + INSTALL_RPATH "${run_path_list}") endif() # Copy files into the build tree python package. diff --git a/Bindings/Python/setup.py b/Bindings/Python/setup.py index cb1bb17439..431552d5fb 100644 --- a/Bindings/Python/setup.py +++ b/Bindings/Python/setup.py @@ -15,7 +15,9 @@ url='http://opensim.stanford.edu/', license='Apache 2.0', packages=['opensim'], - package_data={'opensim': ['_*.*']}, + # The last 3 entries are for if OPENSIM_PYTHON_STANDALONE is ON. + # The asterisk after the extension is to handle version numbers on Linux. + package_data={'opensim': ['_*.*', '*.dylib', '*.dll', '*.so*']}, include_package_data=True, classifiers=[ 'Intended Audience :: Science/Research', diff --git a/CMakeLists.txt b/CMakeLists.txt index 8860e53bc6..3bf1c93b80 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -101,6 +101,20 @@ for both OpenSim and Simbody. To make a distributable installation on macOS, this should be set to ON to avoid using absolute paths for RPATHs to Simbody libraries." ON) +option(OPENSIM_PYTHON_STANDALONE "Make the Python package standalone, meaning +the OpenSim (and Simbody and BTK) shared libraries it depends on are copied +into the package. If you are building OpenSim on the same machine on which you +plan to use it, you can leave this OFF. If you are distributing OpenSim to +other computers, you should turn this ON. If macOS, this option affects how +RPATH is set for the SWIG-generated shared libraries. This variable has no +effect if BUILD_PYTHON_WRAPPING is OFF." OFF) +# There are many potential ways to distribute the python bindings; here's how +# you might want to set the above option for different ways of distributing: +# simtk.org GUI distribution, macOS: ON (since no env. vars are set) +# simtk.org GUI distrubution, Windows: OFF (PATH is set) +# pypi: ON (there is no way to install the C++ libraries otherwise) +# debian, homebrew, conda: OFF (can have a separate C++ library package) + option(BUILD_API_ONLY "Build/install only headers, libraries, wrapping, tests; not applications (opensim, ik, rra, etc.)." OFF) @@ -431,6 +445,21 @@ if(${BUILD_JAVA_WRAPPING}) endif() endif() +if(${BUILD_PYTHON_WRAPPING}) + # PythonInterp is supposed to come before PythonLibs. + find_package(PythonInterp 2.7 REQUIRED) + find_package(PythonLibs 2.7 REQUIRED) + + # We update the python install dir to include the python version, + # now that we know it. We replace the token "VERSION" with the actual python + # version. + string(REPLACE "VERSION" "${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}" + OPENSIM_INSTALL_PYTHONDIR "${OPENSIM_INSTALL_PYTHONDIR}") + # This must be done before adding the OpenSim libraries, since + # OPENSIM_INSTALL_PYTHONDIR is used in OpenSimAddLibrary (in + # OpenSimMacros.cmake). +endif() + if(WIN32) add_definitions(-D_CRT_SECURE_NO_DEPRECATE) endif() @@ -461,7 +490,7 @@ endif() ## RPATH -# If it is necesasry to not put RPATHS in the installed binaries, set +# If it is necessary to not put RPATHS in the installed binaries, set # set CMAKE_SKIP_INSTALL_RPATH to OFF. if(APPLE) # CMake 2.8.12 introduced the ability to set RPATH for shared libraries @@ -526,6 +555,10 @@ if(WITH_BTK) include(${BTK_USE_FILE}) add_definitions(-DWITH_BTK) CopyDependencyDLLsForWin(BTK ${BTK_INSTALL_PREFIX}) + if(BUILD_PYTHON_WRAPPING AND OPENSIM_PYTHON_STANDALONE) + OpenSimInstallDependencyLibraries(BTK "${BTK_INSTALL_PREFIX}" + "${BTK_LIBRARY_DIRS}" "${OPENSIM_INSTALL_PYTHONDIR}/opensim") + endif() # TODO this should be handled separately between macOS and Linux. set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_RPATH};${BTK_LIBRARY_DIRS}") endif() @@ -624,12 +657,11 @@ if(${OPENSIM_COPY_SIMBODY}) # is only needed for programmers goes in sdk/lib. # TODO use SimbodyConfig.cmake variables instead of these globs. if(WIN32) - file(GLOB SIMBODY_BIN_FILES - ${Simbody_BIN_DIR}/*.dll - ${Simbody_BIN_DIR}/*.exe) - file(GLOB SIMBODY_ARCHIVE_FILES - ${Simbody_LIB_DIR}/*.lib) + file(GLOB SIMBODY_BIN_FILES ${Simbody_BIN_DIR}/*.dll + ${Simbody_BIN_DIR}/*.exe) + file(GLOB SIMBODY_ARCHIVE_FILES ${Simbody_LIB_DIR}/*.lib) else() + # TODO simbody-visualizer executable probably isn't even there. file(GLOB SIMBODY_BIN_FILES ${Simbody_BIN_DIR}/simbody*) # executables only (e.g., visualizer). # If the LIB_DIR is some common place for libraries (e.g., /usr/lib/), @@ -690,6 +722,10 @@ if(${OPENSIM_COPY_SIMBODY}) endforeach() endif() endif() +if(BUILD_PYTHON_WRAPPING AND OPENSIM_PYTHON_STANDALONE) + OpenSimInstallDependencyLibraries(SimTK "${Simbody_BIN_DIR}" + "${Simbody_LIB_DIR}" "${OPENSIM_INSTALL_PYTHONDIR}/opensim") +endif() # Other than Windows we can debug without debuggable SimTK libraries diff --git a/OpenSim/Utilities/simmFileWriterDLL/CMakeLists.txt b/OpenSim/Utilities/simmFileWriterDLL/CMakeLists.txt index 4fe254f24d..3d4c5c11d9 100644 --- a/OpenSim/Utilities/simmFileWriterDLL/CMakeLists.txt +++ b/OpenSim/Utilities/simmFileWriterDLL/CMakeLists.txt @@ -5,6 +5,7 @@ file(GLOB INCLUDES *.h) OpenSimAddLibrary( KIT SimmFileWriter AUTHORS "Peter_Loan" + EXCLUDEFROMPYTHON LINKLIBS osimCommon osimSimulation osimActuators ${Simbody_LIBRARIES} INCLUDES ${INCLUDES} SOURCES ${SOURCES} diff --git a/cmake/OpenSimMacros.cmake b/cmake/OpenSimMacros.cmake index a1891d23d3..3434a8199c 100644 --- a/cmake/OpenSimMacros.cmake +++ b/cmake/OpenSimMacros.cmake @@ -5,6 +5,7 @@ include(CMakeParseArguments) # argument. Otherwise, omit. # LOWERINCLUDEDIRNAME: When installing the headers for this library, make the # name of the library all lower-case (e.g., Lepton -> lepton). +# EXCLUDEFROMPYTHON: Do not install this library into the python package. # KIT: Name of the library (e.g., Common). # AUTHORS: A string listing authors of the library. # LINKLIBS: List of libraries (targets) to link against. @@ -33,7 +34,7 @@ function(OpenSimAddLibrary) # Parse arguments. # ---------------- # http://www.cmake.org/cmake/help/v2.8.9/cmake.html#module:CMakeParseArguments - set(options VENDORLIB LOWERINCLUDEDIRNAME) + set(options VENDORLIB LOWERINCLUDEDIRNAME EXCLUDEFROMPYTHON) set(oneValueArgs KIT AUTHORS) set(multiValueArgs LINKLIBS INCLUDES SOURCES TESTDIRS INCLUDEDIRS) cmake_parse_arguments( @@ -97,6 +98,16 @@ function(OpenSimAddLibrary) RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" ARCHIVE DESTINATION "${OPENSIM_INSTALL_ARCHIVEDIR}") + if(BUILD_PYTHON_WRAPPING AND OPENSIM_PYTHON_STANDALONE + AND NOT OSIMADDLIB_EXCLUDEFROMPYTHON) + if (WIN32) + install(TARGETS ${OSIMADDLIB_LIBRARY_NAME} + RUNTIME DESTINATION "${OPENSIM_INSTALL_PYTHONDIR}/opensim") + else() + install(TARGETS ${OSIMADDLIB_LIBRARY_NAME} + LIBRARY DESTINATION "${OPENSIM_INSTALL_PYTHONDIR}/opensim") + endif() + endif() # Install headers. # ---------------- @@ -150,6 +161,8 @@ function(OpenSimAddLibrary) # list(APPEND run_path_list # "\@loader_path/${lib_dir_to_simbody_lib_dir}") #endif() + # It is possible that on UNIX (APPLE and Linux), we will install + # Simbody and BTK beside OpenSim, rather than in their own directories. # Use APPEND to include the BTK RPATH, set in CMAKE_INSTALL_RPATH. set_property(TARGET ${OSIMADDLIB_LIBRARY_NAME} APPEND PROPERTY INSTALL_RPATH "${run_path_list}" @@ -304,6 +317,38 @@ function(OpenSimAddApplication) endfunction() +# Function to install shared libraries (any platform) from a dependency install +# directory into the OpenSim installation. One use case is to install libraries +# into the python package. +# PREFIX: A common part of the library file names (e.g., 'SimTK' or 'BTK'). +# This is to avoid copying unrelated files from a folder like /usr/lib. +# DEP_LIBS_DIR_WIN: Directory to search for the dependency's library, on +# Windows. +# DEP_LIBS_DIR_UNIX: Directory to search for the dependency's library, on +# UNIX (APPLE and Linux). Specify only the lib directory to avoid +# searching all of /usr/local (if the dependency is installed to a +# system location like this). +# OSIM_DESTINATION: Destination of the libraries within OpenSim's installation. +function(OpenSimInstallDependencyLibraries PREFIX DEP_LIBS_DIR_WIN + DEP_LIBS_DIR_UNIX OSIM_DESTINATION) + if(WIN32) + file(GLOB_RECURSE LIBS "${DEP_LIBS_DIR_WIN}/${PREFIX}*.dll") + else() + if(APPLE) + set(lib_ext "dylib") + else() + set(lib_ext "so*") # Trailing * for version #s. + endif() + file(GLOB_RECURSE LIBS "${DEP_LIBS_DIR_UNIX}/lib${PREFIX}*.${lib_ext}") + endif() + if(NOT LIBS) + message(FATAL_ERROR "Zero shared libraries found in directory " + "${DEP_INSTALL_DIR}.") + endif() + install(FILES ${LIBS} DESTINATION "${OSIM_DESTINATION}") +endfunction() + + # Function to copy DLL files from dependency install directory into OpenSim # build and install directories. This is a Windows specific function enabled # only for Windows platform. Intention is to allow runtime loader to find all