panda3d/cmake/macros/Interrogate.cmake
2019-06-27 17:38:18 -06:00

369 lines
13 KiB
CMake

# Filename: Interrogate.cmake
#
# Description: This file contains macros and functions that are used to invoke
# interrogate, to generate wrappers for Python and/or other languages.
#
# Functions:
# target_interrogate(target [ALL] [source1 [source2 ...]])
# add_python_module(module [lib1 [lib2 ...]])
# add_python_target(target [source1 [source2 ...]])
#
set(IGATE_FLAGS -DCPPPARSER -D__cplusplus -Dvolatile -Dmutable)
# In addition, Interrogate needs to know if this is a 64-bit build:
include(CheckTypeSize)
check_type_size(long CMAKE_SIZEOF_LONG)
if(CMAKE_SIZEOF_LONG EQUAL 8)
list(APPEND IGATE_FLAGS "-D_LP64")
endif()
# This is a list of regexes that are applied to every filename. If one of the
# regexes matches, that file will not be passed to Interrogate.
set(INTERROGATE_EXCLUDE_REGEXES
".*\\.I$"
".*\\.N$"
".*\\.c$"
".*\\.lxx$"
".*\\.yxx$"
".*_src\\..*"
)
if(WIN32)
list(APPEND IGATE_FLAGS -D_X86_ -D__STDC__=1 -DWIN32_VC -D "_declspec(param)=" -D "__declspec(param)=" -D_near -D_far -D__near -D__far -D_WIN32 -D__stdcall -DWIN32)
endif()
if(INTERROGATE_VERBOSE)
list(APPEND IGATE_FLAGS "-v")
endif()
set(IMOD_FLAGS -python-native)
# This stores the names of every module added to the Interrogate system:
set(ALL_INTERROGATE_MODULES CACHE INTERNAL "Internal variable")
#
# Function: target_interrogate(target [ALL] [source1 [source2 ...]])
# NB. This doesn't actually invoke interrogate, but merely adds the
# sources to the list of scan sources associated with the target.
# Interrogate will be invoked when add_python_module is called.
# If ALL is specified, all of the sources from the associated
# target are added.
#
function(target_interrogate target)
set(sources)
set(extensions)
set(want_all OFF)
set(extensions_keyword OFF)
foreach(arg ${ARGN})
if(arg STREQUAL "ALL")
set(want_all ON)
elseif(arg STREQUAL "EXTENSIONS")
set(extensions_keyword ON)
elseif(extensions_keyword)
list(APPEND extensions "${arg}")
else()
list(APPEND sources "${arg}")
endif()
endforeach()
# If ALL was specified, pull in all sources from the target.
if(want_all)
get_target_property(target_sources "${target}" SOURCES)
list(APPEND sources ${target_sources})
endif()
list(REMOVE_DUPLICATES sources)
# Now let's get everything's absolute path, so that it can be passed
# through a property while still preserving the reference.
set(absolute_sources)
foreach(source ${sources})
get_source_file_property(exclude "${source}" WRAP_EXCLUDE)
if(NOT exclude)
get_source_file_property(location "${source}" LOCATION)
list(APPEND absolute_sources ${location})
endif()
endforeach(source)
set(absolute_extensions)
foreach(extension ${extensions})
get_source_file_property(location "${extension}" LOCATION)
list(APPEND absolute_extensions ${location})
endforeach(extension)
set_target_properties("${target}" PROPERTIES
IGATE_SOURCES "${absolute_sources}")
set_target_properties("${target}" PROPERTIES
IGATE_EXTENSIONS "${absolute_extensions}")
# CMake has no property for determining the source directory where the
# target was originally added. interrogate_sources makes use of this
# property (if it is set) in order to make all paths on the command-line
# relative to it, thereby shortening the command-line even more.
# Since this is not an Interrogate-specific property, it is not named with
# an IGATE_ prefix.
set_target_properties("${target}" PROPERTIES
TARGET_SRCDIR "${CMAKE_CURRENT_SOURCE_DIR}")
# Also store where the build files are kept, so the Interrogate output can go
# there as well.
set_target_properties("${target}" PROPERTIES
TARGET_BINDIR "${CMAKE_CURRENT_BINARY_DIR}/${PANDA_CFG_INTDIR}")
endfunction(target_interrogate)
#
# Function: interrogate_sources(target output database language_flags module)
#
# This function actually runs a component-level interrogation against 'target'.
# It generates the outfile.cxx (output) and dbfile.in (database) files, which
# can then be used during the interrogate_module step to produce language
# bindings.
#
# The target must first have had sources selected with target_interrogate.
# Failure to do so will result in an error.
#
function(interrogate_sources target output database language_flags)
get_target_property(sources "${target}" IGATE_SOURCES)
get_target_property(extensions "${target}" IGATE_EXTENSIONS)
if(NOT sources)
message(FATAL_ERROR
"Cannot interrogate ${target} unless it's run through target_interrogate first!")
endif()
get_target_property(srcdir "${target}" TARGET_SRCDIR)
if(NOT srcdir)
# No TARGET_SRCDIR was set, so we'll do everything relative to our
# current binary dir instead:
set(srcdir "${CMAKE_CURRENT_BINARY_DIR}")
endif()
set(scan_sources)
set(nfiles)
foreach(source ${sources})
get_filename_component(source_basename "${source}" NAME)
# Only certain sources should actually be scanned by Interrogate. The
# rest are merely dependencies. This uses the exclusion regex above in
# order to determine what files are okay:
set(exclude OFF)
foreach(regex ${INTERROGATE_EXCLUDE_REGEXES})
if("${source_basename}" MATCHES "${regex}")
set(exclude ON)
endif()
endforeach(regex)
if(NOT exclude)
# This file is to be scanned by Interrogate. In order to avoid
# cluttering up the command line, we should first make it relative:
file(RELATIVE_PATH rel_source "${srcdir}" "${source}")
list(APPEND scan_sources "${rel_source}")
# Also see if this file has a .N counterpart, which has directives
# specific for Interrogate. If there is a .N file, we add it as a dep,
# so that CMake will rerun Interrogate if the .N files are modified:
get_filename_component(source_path "${source}" PATH)
get_filename_component(source_name_we "${source}" NAME_WE)
set(nfile "${source_path}/${source_name_we}.N")
if(EXISTS "${nfile}")
list(APPEND nfiles "${nfile}")
endif()
endif()
endforeach(source)
# Also add extensions, in relative-path form
foreach(extension ${extensions})
file(RELATIVE_PATH rel_extension "${srcdir}" "${extension}")
list(APPEND scan_sources "${rel_extension}")
endforeach(extension)
# Interrogate also needs the include paths, so we'll extract them from the
# target. These are available via a generator expression.
# When we read the INTERFACE_INCLUDE_DIRECTORIES property, we need to read it
# from a target that has the IS_INTERROGATE=1 property.
# (See PackageConfig.cmake for an explanation why.)
# The problem is, custom commands are not targets, so we can't put target
# properties on them. And if you try to use the $<TARGET_PROPERTY:prop>
# generator expression from the context of a custom command, it'll instead
# read the property from the most recent actual target. As a workaround for
# this, we create a fake target with the IS_INTERROGATE property set and pull
# the INTERFACE_INCLUDE_DIRECTORIES property out through that.
# I hate it, but such is CMake.
add_custom_target(${target}_igate_internal)
set_target_properties(${target}_igate_internal PROPERTIES
IS_INTERROGATE 1
INTERFACE_INCLUDE_DIRECTORIES "$<TARGET_PROPERTY:${target},INTERFACE_INCLUDE_DIRECTORIES>")
# Note, the \t is a workaround for a CMake bug where using a plain space in
# a JOIN will cause it to be escaped. Tabs are not escaped and will
# separate correctly.
set(include_flags "-I$<JOIN:$<TARGET_PROPERTY:${target}_igate_internal,INTERFACE_INCLUDE_DIRECTORIES>,\t-I>")
# Get the compiler definition flags. These must be passed to Interrogate
# in the same way that they are passed to the compiler so that Interrogate
# will preprocess each file in the same way.
set(_compile_defs "$<TARGET_PROPERTY:${target},COMPILE_DEFINITIONS>")
if(NOT CMAKE_HOST_WIN32)
# Win32's command-line parser doesn't understand "'"
# that's fine, it also ignores '"'
set(_q "'")
endif()
set(define_flags "$<$<BOOL:${_compile_defs}>:-D${_q}$<JOIN:${_compile_defs},${_q}\t-D${_q}>${_q}>")
# If this is a release build that has NDEBUG defined, we need that too:
foreach(build_type ${CMAKE_CONFIGURATION_TYPES} ${CMAKE_BUILD_TYPE})
string(TOUPPER "${build_type}" build_type)
if(CMAKE_CXX_FLAGS_${build_type} MATCHES ".*NDEBUG.*")
list(APPEND define_flags "$<$<CONFIG:${build_type}>:-DNDEBUG>")
endif()
endforeach(build_type)
get_filename_component(output_directory "${output}" DIRECTORY)
get_filename_component(database_directory "${database}" DIRECTORY)
add_custom_command(
OUTPUT "${output}" "${database}"
COMMAND ${CMAKE_COMMAND} -E
make_directory "${output_directory}"
COMMAND ${CMAKE_COMMAND} -E
make_directory "${database_directory}"
COMMAND host_interrogate
-oc "${output}"
-od "${database}"
-srcdir "${srcdir}"
-library ${target}
${INTERROGATE_OPTIONS}
${IGATE_FLAGS}
${language_flags}
${define_flags}
-S "${PROJECT_SOURCE_DIR}/dtool/src/interrogatedb"
-S "${PROJECT_SOURCE_DIR}/dtool/src/parser-inc"
-S "${PYTHON_INCLUDE_DIRS}"
${include_flags}
${scan_sources}
DEPENDS host_interrogate ${sources} ${extensions} ${nfiles}
COMMENT "Interrogating ${target}")
# Propagate the target's compile definitions to the output file
set_source_files_properties("${output}" PROPERTIES
COMPILE_DEFINITIONS "$<TARGET_PROPERTY:${target},INTERFACE_COMPILE_DEFINITIONS>")
endfunction(interrogate_sources)
#
# Function: add_python_module(module [lib1 [lib2 ...]] [LINK lib1 ...]
# [IMPORT mod1 ...])
# Uses interrogate to create a Python module. If the LINK keyword is specified,
# the Python module is linked against the specified libraries instead of those
# listed before. The IMPORT keyword makes the output module import another
# Python module when it's initialized.
#
function(add_python_module module)
if(NOT INTERROGATE_PYTHON_INTERFACE)
return()
endif()
set(targets)
set(component "Python")
set(link_targets)
set(import_flags)
set(infiles_rel)
set(infiles_abs)
set(sources_abs)
set(extensions)
set(keyword)
foreach(arg ${ARGN})
if(arg STREQUAL "LINK" OR arg STREQUAL "IMPORT" OR arg STREQUAL "COMPONENT")
set(keyword "${arg}")
elseif(keyword STREQUAL "LINK")
list(APPEND link_targets "${arg}")
set(keyword)
elseif(keyword STREQUAL "IMPORT")
list(APPEND import_flags "-import" "${arg}")
set(keyword)
elseif(keyword STREQUAL "COMPONENT")
set(component "${arg}")
set(keyword)
else()
list(APPEND targets "${arg}")
endif()
endforeach(arg)
if(NOT link_targets)
set(link_targets ${targets})
endif()
string(REGEX REPLACE "^.*\\." "" modname "${module}")
foreach(target ${targets})
get_target_property(workdir_abs "${target}" TARGET_BINDIR)
if(NOT workdir_abs)
# No TARGET_BINDIR was set, so we'll just use our current directory:
set(workdir_abs "${CMAKE_CURRENT_BINARY_DIR}/${PANDA_CFG_INTDIR}")
endif()
# Keep command lines short
file(RELATIVE_PATH workdir_rel "${CMAKE_CURRENT_BINARY_DIR}" "${workdir_abs}")
interrogate_sources(${target}
"${workdir_abs}/${target}_igate.cxx"
"${workdir_abs}/${target}.in"
"-python-native;-module;${module}")
get_target_property(target_extensions "${target}" IGATE_EXTENSIONS)
list(APPEND infiles_rel "${workdir_rel}/${target}.in")
list(APPEND infiles_abs "${workdir_abs}/${target}.in")
list(APPEND sources_abs "${workdir_abs}/${target}_igate.cxx")
list(APPEND extensions ${target_extensions})
endforeach(target)
add_custom_command(
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${PANDA_CFG_INTDIR}/${module}_module.cxx"
COMMAND ${CMAKE_COMMAND} -E
make_directory "${CMAKE_CURRENT_BINARY_DIR}/${PANDA_CFG_INTDIR}"
COMMAND host_interrogate_module
-oc "${CMAKE_CURRENT_BINARY_DIR}/${PANDA_CFG_INTDIR}/${module}_module.cxx"
-module ${modname} -library ${modname}
${import_flags}
${INTERROGATE_MODULE_OPTIONS}
${IMOD_FLAGS} ${infiles_rel}
DEPENDS host_interrogate_module ${infiles_abs}
COMMENT "Generating module ${module}")
add_python_target(${module} COMPONENT "${component}" EXPORT "${component}"
"${CMAKE_CURRENT_BINARY_DIR}/${PANDA_CFG_INTDIR}/${module}_module.cxx"
${sources_abs} ${extensions})
target_link_libraries(${module} ${link_targets})
if(CMAKE_VERSION VERSION_LESS "3.11")
# CMake <3.11 doesn't allow generator expressions on source files, so we
# need to copy them to our target, which does allow them.
foreach(source ${sources_abs})
get_source_file_property(compile_definitions "${source}" COMPILE_DEFINITIONS)
if(compile_definitions)
set_property(TARGET ${module} APPEND PROPERTY
COMPILE_DEFINITIONS ${compile_definitions})
set_source_files_properties("${source}" PROPERTIES COMPILE_DEFINITIONS "")
endif()
endforeach(source)
endif()
list(APPEND ALL_INTERROGATE_MODULES "${module}")
set(ALL_INTERROGATE_MODULES "${ALL_INTERROGATE_MODULES}" CACHE INTERNAL "Internal variable")
endfunction(add_python_module)