diff --git a/cmake/macros/Python.cmake b/cmake/macros/Python.cmake index ac30965400..4b8f20f440 100644 --- a/cmake/macros/Python.cmake +++ b/cmake/macros/Python.cmake @@ -80,9 +80,12 @@ function(add_python_target target) endfunction(add_python_target) # -# Function: install_python_package(path [ARCH/LIB] [COMPONENT component]) +# Function: install_python_package(name [SOURCE path] [ARCH/LIB] [COMPONENT component]) # -# Installs the Python package which was built at `path`. +# Installs the Python package `name` (which may have its source at `path`). +# +# The package is copied to (or created in) the build directory so that the user +# may import it before the install step. # # Note that this handles more than just installation; it will also invoke # Python's compileall utility to pregenerate .pyc/.pyo files. This will only @@ -96,10 +99,11 @@ endfunction(add_python_target) # documentation for more information on what this does). The default is # "Python". # -function(install_python_package path) +function(install_python_package package_name) set(type "LIB") + unset(keyword) set(component "Python") - set(component_keyword OFF) + unset(src_path) foreach(arg ${ARGN}) if(arg STREQUAL "ARCH") set(type "ARCH") @@ -108,11 +112,18 @@ function(install_python_package path) set(type "LIB") elseif(arg STREQUAL "COMPONENT") - set(component_keyword ON) + set(keyword "${arg}") - elseif(component_keyword) + elseif(keyword STREQUAL "COMPONENT") set(component "${arg}") - set(component_keyword OFF) + unset(keyword) + + elseif(arg STREQUAL "SOURCE") + set(keyword "${arg}") + + elseif(keyword STREQUAL "SOURCE") + set(src_path "${arg}") + unset(keyword) else() message(FATAL_ERROR "install_python_package got unexpected argument: ${ARGN}") @@ -120,26 +131,21 @@ function(install_python_package path) endif() endforeach(arg) - get_filename_component(package_name "${path}" NAME) - set(custom_target "bytecompile_${package_name}") + set(path "${PROJECT_BINARY_DIR}/${package_name}") - file(RELATIVE_PATH relpath "${PROJECT_BINARY_DIR}" "${path}") - - if(PYTHON_EXECUTABLE) - add_custom_target(${custom_target} ALL) - add_custom_command( - TARGET ${custom_target} - WORKING_DIRECTORY "${PROJECT_BINARY_DIR}" - COMMAND "${PYTHON_EXECUTABLE}" -m compileall -q "${relpath}") - add_custom_command( - TARGET ${custom_target} - WORKING_DIRECTORY "${PROJECT_BINARY_DIR}" - COMMAND "${PYTHON_EXECUTABLE}" -OO -m compileall -q "${relpath}") + set(args -D "OUTPUT_DIR=${path}") + if(src_path) + list(APPEND args -D "SOURCE_DIR=${src_path}") endif() + if(PYTHON_EXECUTABLE) + list(APPEND args -D "PYTHON_EXECUTABLES=${PYTHON_EXECUTABLE}") + endif() + add_custom_target(${package_name} ALL + COMMAND ${CMAKE_COMMAND} + ${args} + -P "${CMAKE_SOURCE_DIR}/cmake/scripts/CopyPython.cmake") - ensure_python_init("${path}") - - set(dir ${PYTHON_${type}_INSTALL_DIR}) + set(dir "${PYTHON_${type}_INSTALL_DIR}") if(dir) install(DIRECTORY "${path}" DESTINATION "${dir}" COMPONENT "${component}" diff --git a/cmake/scripts/CopyPython.cmake b/cmake/scripts/CopyPython.cmake new file mode 100644 index 0000000000..d1b786402a --- /dev/null +++ b/cmake/scripts/CopyPython.cmake @@ -0,0 +1,78 @@ +# Filename: CopyPython.cmake +# +# Description: When run, copies Python files from a source directory to a +# build directory located somewhere in the project's binary path. If +# PYTHON_EXECUTABLES is provided, it will also invoke compileall in the +# destination dir(s) to precache .pyc/.pyo files. +# +# Usage: +# This script is invoked via add_custom_target, like this: +# cmake -D OUTPUT_DIR="/out/dir/Release" +# -D SOURCE_DIR="/panda3d/direct/src/" +# -D PYTHON_EXECUTABLES="/usr/bin/python3.6" +# -P CopyPython.cmake +# + +if(NOT CMAKE_SCRIPT_MODE_FILE) + message(FATAL_ERROR "CopyPython.cmake should not be included but run in script mode.") + return() +endif() + +if(NOT DEFINED OUTPUT_DIR) + message(FATAL_ERROR "OUTPUT_DIR should be defined when running CopyPython.cmake!") + return() +endif() + +# Ensure OUTPUT_DIR exists +file(MAKE_DIRECTORY ${OUTPUT_DIR}) + +# If there's a SOURCE_DIR, glob for .py files and copy +# (this is done by hand to avoid the CMake bug where it creates empty dirs) +if(DEFINED SOURCE_DIR) + file(GLOB_RECURSE py_files RELATIVE "${SOURCE_DIR}" "${SOURCE_DIR}/*.py") + foreach(py_file ${py_files}) + get_filename_component(py_file_parent "${py_file}" DIRECTORY) + if(NOT EXISTS "${SOURCE_DIR}/${py_file_parent}/__init__.py") + # The source file isn't part of a Python package, even though it is a .py + # file. Skip it. + continue() + endif() + + file(TIMESTAMP "${SOURCE_DIR}/${py_file}" src_stamp) + file(TIMESTAMP "${OUTPUT_DIR}/${py_file}" dst_stamp) + + if(NOT src_stamp STREQUAL dst_stamp) + file(COPY "${SOURCE_DIR}/${py_file}" DESTINATION "${OUTPUT_DIR}/${py_file_parent}") + set(changed YES) + endif() + + endforeach(py_file) + +else() + # No SOURCE_DIR; assume we're outdated since our caller might be populating + # the OUTPUT_DIR themselves + set(changed YES) + +endif() + +# Make sure Python recognizes OUTPUT_DIR as a package +if(NOT EXISTS "${OUTPUT_DIR}/__init__.py") + file(WRITE "${OUTPUT_DIR}/__init__.py" "") + set(changed YES) +endif() + +# Generate .pyc files for each Python version, if our caller wants +if(changed AND DEFINED PYTHON_EXECUTABLES) + foreach(interp ${PYTHON_EXECUTABLES}) + execute_process( + COMMAND "${interp}" -m compileall . + OUTPUT_QUIET + WORKING_DIRECTORY "${OUTPUT_DIR}") + + execute_process( + COMMAND "${interp}" -OO -m compileall . + OUTPUT_QUIET + WORKING_DIRECTORY "${OUTPUT_DIR}") + + endforeach(interp) +endif() diff --git a/direct/CMakeLists.txt b/direct/CMakeLists.txt index 1606057711..18847d4b9b 100644 --- a/direct/CMakeLists.txt +++ b/direct/CMakeLists.txt @@ -42,21 +42,7 @@ if(HAVE_PYTHON) p3motiontrail p3showbase LINK p3direct IMPORT panda3d.core COMPONENT Direct) # Copy Python source files into the build directory - set(DIRECT_INSTALL_DIRECTORIES - actor cluster controls directbase directdevices directnotify directscripts - directtools directutil dist distributed extensions_native filter - fsm gui interval leveleditor motiontrail p3d particles physics - showbase showutil stdpy task tkpanels tkwidgets wxwidgets) - - foreach(dir ${DIRECT_INSTALL_DIRECTORIES}) - file(COPY - DIRECTORY "src/${dir}" - DESTINATION "${PROJECT_BINARY_DIR}/direct" - FILES_MATCHING PATTERN "*.py") - endforeach() - - # Install all files - install_python_package("${PROJECT_BINARY_DIR}/direct" LIB COMPONENT Direct) + install_python_package(direct SOURCE "${CMAKE_CURRENT_SOURCE_DIR}/src" LIB COMPONENT Direct) # This bit is to generate the 'pandac' compatibility shim. It's deprecated now, # but in older versions of Panda3D, one would use @@ -64,13 +50,13 @@ if(HAVE_PYTHON) # instead of # from panda3d.FOO import * # Generate PandaModules: - file(WRITE "${PROJECT_BINARY_DIR}/pandac/PandaModules.py" + file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/pandac/PandaModules.py" "\"This module is deprecated. Import from panda3d.core and other panda3d.* modules instead.\" print(\"Warning: pandac.PandaModules is deprecated, import from panda3d.core instead\")\n") foreach(module ${ALL_INTERROGATE_MODULES}) - file(APPEND "${PROJECT_BINARY_DIR}/pandac/PandaModules.py" " + file(APPEND "${CMAKE_CURRENT_BINARY_DIR}/pandac/PandaModules.py" " try: from ${module} import * except ImportError as err: @@ -80,7 +66,7 @@ except ImportError as err: endforeach() # Now install ourselves: - install_python_package("${PROJECT_BINARY_DIR}/pandac" LIB COMPONENT Direct) + install_python_package(pandac SOURCE "${CMAKE_CURRENT_BINARY_DIR}/pandac" LIB COMPONENT Direct) endif() # "Direct" component contains both the Python stuff and the non-Python stuff, diff --git a/panda/CMakeLists.txt b/panda/CMakeLists.txt index 55de86dac9..a223e3e25f 100644 --- a/panda/CMakeLists.txt +++ b/panda/CMakeLists.txt @@ -107,7 +107,7 @@ if(INTERROGATE_PYTHON_INTERFACE) add_python_module(panda3d.core ${CORE_MODULE_COMPONENTS} LINK panda) if(BUILD_SHARED_LIBS) - install_python_package("${PROJECT_BINARY_DIR}/panda3d" ARCH) + install_python_package(panda3d ARCH) endif() if(HAVE_BULLET)