From 059c78bade1bd1fe06ec9d80d37b88862d50f335 Mon Sep 17 00:00:00 2001 From: Sam Edwards Date: Wed, 1 May 2019 18:13:30 -0600 Subject: [PATCH] CMake: Restructure multi-configuration binary directory layout This changes the structure to use paths like `build/Release/lib` instead of `build/lib/Release`, so that the `build/Release` directory more closely mimics the structure of `build` when in single-configuration mode. --- CMakeLists.txt | 13 ++- cmake/macros/Python.cmake | 143 +++-------------------- cmake/templates/win32_python/__init__.py | 20 ++++ dtool/CompilerFlags.cmake | 25 +++- dtool/LocalSetup.cmake | 4 +- dtool/src/dtoolbase/CMakeLists.txt | 2 +- 6 files changed, 70 insertions(+), 137 deletions(-) create mode 100644 cmake/templates/win32_python/__init__.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 7293d84d14..1eef5292b0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,9 +34,6 @@ include(Interrogate) # Defines target_interrogate AND add_python_module include(RunPzip) # Defines run_pzip function include(Versioning) # Hooks 'add_library' to apply VERSION/SOVERSION -# Add the include path for source and header files generated by CMake -include_directories("${PROJECT_BINARY_DIR}/include/${CMAKE_CFG_INTDIR}") - # Determine which trees to build. option(BUILD_DTOOL "Build the dtool source tree." ON) option(BUILD_PANDA "Build the panda source tree." ON) @@ -90,7 +87,15 @@ if(INTERROGATE_PYTHON_INTERFACE) # for pytest before adding this test. If the user doesn't have pytest, we'd # like for the tests to fail. - add_test(pytest "${PYTHON_EXECUTABLE}" -m pytest "${PROJECT_SOURCE_DIR}/tests") + if(CMAKE_CFG_INTDIR STREQUAL ".") + set(_workdir "${PROJECT_BINARY_DIR}") + else() + set(_workdir "${PROJECT_BINARY_DIR}/$") + endif() + + add_test(NAME pytest + COMMAND "${PYTHON_EXECUTABLE}" -m pytest "${PROJECT_SOURCE_DIR}/tests" + WORKING_DIRECTORY "${_workdir}") endif() # Generate the Panda3DConfig.cmake file so find_package(Panda3D) works, and diff --git a/cmake/macros/Python.cmake b/cmake/macros/Python.cmake index 4b8f20f440..cb8edad227 100644 --- a/cmake/macros/Python.cmake +++ b/cmake/macros/Python.cmake @@ -6,7 +6,6 @@ # Functions: # add_python_target(target [source1 [source2 ...]]) # install_python_package(path [ARCH/LIB]) -# ensure_python_init(path [ARCH] [ROOT] [OVERWRITE]) # # @@ -52,12 +51,21 @@ function(add_python_target target) target_link_libraries(${target} PKG::PYTHON) if(BUILD_SHARED_LIBS) + set(_outdir "${PROJECT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/${slash_namespace}") + set_target_properties(${target} PROPERTIES - LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/${slash_namespace}" + LIBRARY_OUTPUT_DIRECTORY "${_outdir}" OUTPUT_NAME "${basename}" PREFIX "" SUFFIX "${PYTHON_EXTENSION_SUFFIX}") + # This is explained over in CompilerFlags.cmake + foreach(_config ${CMAKE_CONFIGURATION_TYPES}) + string(TOUPPER "${_config}" _config) + set_target_properties(${target} PROPERTIES + LIBRARY_OUTPUT_DIRECTORY_${_config} "${_outdir}") + endforeach(_config) + if(PYTHON_ARCH_INSTALL_DIR) install(TARGETS ${target} EXPORT "${export}" COMPONENT "${component}" DESTINATION "${PYTHON_ARCH_INSTALL_DIR}/${slash_namespace}") endif() @@ -71,12 +79,6 @@ function(add_python_target target) endif() - set(keywords OVERWRITE ARCH) - if(NOT slash_namespace MATCHES ".*/.*") - list(APPEND keywords ROOT) - endif() - ensure_python_init("${PROJECT_BINARY_DIR}/${slash_namespace}" ${keywords}) - endfunction(add_python_target) # @@ -131,7 +133,13 @@ function(install_python_package package_name) endif() endforeach(arg) - set(path "${PROJECT_BINARY_DIR}/${package_name}") + if(NOT DEFINED src_path AND type STREQUAL "ARCH" AND WIN32 AND NOT CYGWIN) + # Win32 needs a special fixup so the DLLs in "bin" can be on the path; + # let's set src_path to the directory containing our fixup __init__.py + set(src_path "${CMAKE_SOURCE_DIR}/cmake/templates/win32_python") + endif() + + set(path "${PROJECT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/${package_name}") set(args -D "OUTPUT_DIR=${path}") if(src_path) @@ -153,120 +161,3 @@ function(install_python_package package_name) endif() endfunction(install_python_package) - -# -# Function: ensure_python_init(path [ARCH] [ROOT] [OVERWRITE]) -# -# Makes sure that the directory - at `path` - contains a file named -# '__init__.py', which is necessary for Python to recognize the directory as a -# package. -# -# ARCH, if specified, means that this is a binary package, and the build tree -# might contain configuration-specific subdirectories. The __init__.py will be -# generated with a function that ensures that the appropriate configuration -# subdirectory is in the path. -# -# ROOT, if specified, means that the directory may sit directly adjacent to a -# 'bin' directory, which should be added to the DLL search path on Windows. -# -# OVERWRITE causes the __init__.py file to be overwritten if one is already -# present. -# -function(ensure_python_init path) - set(arch OFF) - set(root OFF) - set(overwrite OFF) - - foreach(arg ${ARGN}) - if(arg STREQUAL "ARCH") - set(arch ON) - - elseif(arg STREQUAL "ROOT") - set(root ON) - - elseif(arg STREQUAL "OVERWRITE") - set(overwrite ON) - - else() - message(FATAL_ERROR "ensure_python_init got unexpected argument: ${arg}") - - endif() - endforeach(arg) - - set(init_filename "${path}/__init__.py") - if(EXISTS "${init_filename}" AND NOT overwrite) - return() - endif() - - file(WRITE "${init_filename}" "") - - if(arch AND NOT "${CMAKE_CFG_INTDIR}" STREQUAL ".") - # ARCH set, and this is a multi-configuration generator - - set(configs "${CMAKE_CONFIGURATION_TYPES}") - - # Debug should be at the end (highest preference) - list(REMOVE_ITEM configs "Debug") - list(APPEND configs "Debug") - - string(REPLACE ";" "', '" configs "${configs}") - - file(APPEND "${init_filename}" " -def _fixup_path(): - try: - path = __path__[0] - except (NameError, IndexError): - return # Not a package, or not on filesystem - - import os - abspath = os.path.abspath(path) - - newpath = None - for config in ['${configs}']: - cfgpath = os.path.join(abspath, config) - if not os.path.isdir(cfgpath): - continue - - newpath = cfgpath - - if config.lower() == os.environ.get('CMAKE_CONFIGURATION', '').lower(): - break - - if newpath: - __path__.insert(0, newpath) - -_fixup_path() -del _fixup_path -") - - endif() - - if(root AND WIN32 AND NOT CYGWIN) - # ROOT set, and this is Windows - - file(APPEND "${init_filename}" " -def _fixup_dlls(): - try: - path = __path__[0] - except (NameError, IndexError): - return # Not a package, or not on filesystem - - import os - - relpath = os.path.relpath(path, __path__[-1]) - dll_path = os.path.abspath(os.path.join(__path__[-1], '../bin', relpath)) - if not os.path.isdir(dll_path): - return - - os_path = os.environ.get('PATH', '') - os_path = os_path.split(os.pathsep) if os_path else [] - os_path.insert(0, dll_path) - os.environ['PATH'] = os.pathsep.join(os_path) - -_fixup_dlls() -del _fixup_dlls -") - - endif() - -endfunction(ensure_python_init) diff --git a/cmake/templates/win32_python/__init__.py b/cmake/templates/win32_python/__init__.py new file mode 100644 index 0000000000..8228db8f46 --- /dev/null +++ b/cmake/templates/win32_python/__init__.py @@ -0,0 +1,20 @@ +def _fixup_dlls(): + try: + path = __path__[0] + except (NameError, IndexError): + return # Not a package, or not on filesystem + + import os + + relpath = os.path.relpath(path, __path__[-1]) + dll_path = os.path.abspath(os.path.join(__path__[-1], '../bin', relpath)) + if not os.path.isdir(dll_path): + return + + os_path = os.environ.get('PATH', '') + os_path = os_path.split(os.pathsep) if os_path else [] + os_path.insert(0, dll_path) + os.environ['PATH'] = os.pathsep.join(os_path) + +_fixup_dlls() +del _fixup_dlls diff --git a/dtool/CompilerFlags.cmake b/dtool/CompilerFlags.cmake index 2ee0ed02eb..644135dd72 100644 --- a/dtool/CompilerFlags.cmake +++ b/dtool/CompilerFlags.cmake @@ -29,9 +29,9 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON) # Set up the output directory structure, mimicking that of makepanda set(CMAKE_BINARY_DIR "${CMAKE_BINARY_DIR}/cmake") -set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/bin") -set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/lib") -set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/lib") +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/bin") +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/lib") +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/lib") set(MODULE_DESTINATION "lib") # Runtime code assumes that dynamic modules have a "lib" prefix; Windows @@ -44,12 +44,29 @@ if(WIN32) set(CMAKE_STATIC_LIBRARY_PREFIX "lib") # On Windows, modules (DLLs) are located in bin; lib is just for .lib files - set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/bin") + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/bin") if(BUILD_SHARED_LIBS) set(MODULE_DESTINATION "bin") endif() endif() +# Since we're using CMAKE_CFG_INTDIR to put everything in a +# configuration-specific subdirectory when building on a multi-config +# generator, we need to suppress the usual configuration name appending +# behavior of CMake. In CMake 3.4+, it will suppress this behavior +# automatically if the *_OUTPUT_DIRECTORY property contains a generator +# expresssion, but: +# a) As of this writing we support as early as CMake 3.0.2 +# b) ${CMAKE_CFG_INTDIR} doesn't actually expand to a generator expression +# +# So, to solve both of these, let's just do this: +foreach(_type RUNTIME ARCHIVE LIBRARY) + foreach(_config ${CMAKE_CONFIGURATION_TYPES}) + string(TOUPPER "${_config}" _config) + set(CMAKE_${_type}_OUTPUT_DIRECTORY_${_config} "${CMAKE_${_type}_OUTPUT_DIRECTORY}") + endforeach(_config) +endforeach(_type) + # Set warning levels if(MSVC) string(APPEND CMAKE_C_FLAGS " /W3") diff --git a/dtool/LocalSetup.cmake b/dtool/LocalSetup.cmake index 6f526d98c2..aa0b5f2dad 100644 --- a/dtool/LocalSetup.cmake +++ b/dtool/LocalSetup.cmake @@ -193,8 +193,8 @@ else() set(intdir "${CMAKE_BUILD_TYPE}") endif() -configure_file(dtool_config.h.in "${PROJECT_BINARY_DIR}/include/${intdir}/dtool_config.h") -install(FILES "${PROJECT_BINARY_DIR}/include/${intdir}/dtool_config.h" +configure_file(dtool_config.h.in "${PROJECT_BINARY_DIR}/${intdir}/include/dtool_config.h") +install(FILES "${PROJECT_BINARY_DIR}/${intdir}/include/dtool_config.h" COMPONENT CoreDevel DESTINATION include/panda3d) diff --git a/dtool/src/dtoolbase/CMakeLists.txt b/dtool/src/dtoolbase/CMakeLists.txt index 4e3c4b94f6..344dce51fd 100644 --- a/dtool/src/dtoolbase/CMakeLists.txt +++ b/dtool/src/dtoolbase/CMakeLists.txt @@ -91,7 +91,7 @@ add_component_library(p3dtoolbase NOINIT SYMBOL BUILDING_DTOOL_DTOOLBASE # Help other libraries find the autogenerated headers target_include_directories(p3dtoolbase PUBLIC $ - $) + $/include) target_link_libraries(p3dtoolbase PKG::EIGEN PKG::THREADS) target_interrogate(p3dtoolbase ${P3DTOOLBASE_SOURCES} EXTENSIONS ${P3DTOOLBASE_IGATEEXT})