Files
CodeGen/cmake/Modules/codegen.cmake
T

270 lines
9.4 KiB
CMake
Raw Normal View History

cmake_minimum_required(VERSION 3.12)
function(add_codegen_targets
source_files
generated_files_var
input_root
output_root
include_directories
)
set(cache_path "${CMAKE_CURRENT_BINARY_DIR}/codegen_cache")
#
# Find our python interpreter, and set up some python related variables.
#
find_package(PythonInterp 3.6 REQUIRED)
set(venv_path "${CMAKE_CURRENT_BINARY_DIR}/codegen_venv")
set(venv_dummy "${CMAKE_CURRENT_BINARY_DIR}/codegen_venv_dummy")
set(package_dummy "${CMAKE_CURRENT_BINARY_DIR}/codegen_package_dummy")
set(missing_requirements FALSE)
#
# Find libclang.
#
if (WIN32)
set(CMAKE_FIND_LIBRARY_SUFFIXES .dll)
endif()
if (APPLE)
execute_process(COMMAND xcodebuild -find-library clang
OUTPUT_VARIABLE CLANG_LIBRARY OUTPUT_STRIP_TRAILING_WHITESPACE)
else()
find_library(CLANG_LIBRARY NAMES clang libclang REQUIRED)
endif()
if ("${CLANG_LIBRARY}" STREQUAL "CLANG_LIBRARY-NOTFOUND")
message(SEND_ERROR "libclang not found")
set(missing_requirements TRUE)
endif()
#
# Find the codegen python package file
#
if ("${CODEGEN_PACKAGE}" STREQUAL "")
foreach (prefix ${CMAKE_PREFIX_PATH})
set(codegen_package_candidate "${prefix}/share/codegen/codegen-0.1.0.tar.gz")
if (EXISTS "${codegen_package_candidate}")
set(CODEGEN_PACKAGE "${codegen_package_candidate}")
break()
endif()
endforeach()
if ("${CODEGEN_PACKAGE}" STREQUAL "")
set(CODEGEN_PACKAGE "CODEGEN_PACKAGE-NOTFOUND" CACHE FILEPATH)
endif()
endif()
if ("${CODEGEN_PACKAGE}" STREQUAL "CODEGEN_PACKAGE-NOTFOUND")
message(SEND_ERROR "codegen package not found")
set(missing_requirements TRUE)
endif()
if (missing_requirements)
message(FATAL_ERROR "Missing requirements")
endif()
#
# Get name of python executable. We gotta add a '.exe' prefix if we're on windows, obviously.
#
if (WIN32)
set(sep ";")
set(python_executable_name "python.exe")
else()
set(sep ":")
set(python_executable_name "python")
endif()
#
# Determine the path of the virtual env python wrapper. It's different on Windows.
#
set(venv_python_executable_path "bin/${python_executable_name}")
if (NOT EXISTS "${venv_python_executable_path}" AND WIN32)
set(venv_python_executable_path "Scripts/${python_executable_name}")
endif()
#
# Setup the virtual env for the codegen tool to use if it hasn't been set up.
#
#
# TODO: Instead of just skipping over venv creation if a directory exists, we should ensure the venv is valid and
# the codegen package is up to date.
#
if (NOT EXISTS "${venv_dummy}")
message(STATUS "Creating virtual env at ${venv_path}")
execute_process(
COMMAND "${PYTHON_EXECUTABLE}" -m ensurepip
RESULT_VARIABLE update_venv_result
)
if (NOT update_venv_result EQUAL 0)
# ensurepip module failed not installed. Check if pip is installed, and cause an error if it's not installed.
execute_process(
COMMAND "${PYTHON_EXECUTABLE}" -m pip --version
RESULT_VARIABLE pip_version_result
)
if (NOT pip_version_result EQUAL 0)
message(FATAL_ERROR "Failed to run ensurepip module, and pip is not installed. Please install pip manually.")
endif()
endif()
execute_process(
2019-05-24 21:46:55 -10:00
COMMAND "${PYTHON_EXECUTABLE}" -m pip install --user virtualenv
RESULT_VARIABLE update_venv_result
)
if (NOT update_venv_result EQUAL 0)
message(FATAL_ERROR "Failed to install virtualenv with pip: result: ${update_venv_result}.")
endif()
execute_process(
COMMAND "${PYTHON_EXECUTABLE}" -m virtualenv "${venv_path}"
RESULT_VARIABLE update_venv_result
)
if (NOT update_venv_result EQUAL 0)
message(FATAL_ERROR "Cannot update codegen tool venv: result: ${update_venv_result}.")
endif()
#
# Install the codegen package with pip
#
execute_process(
COMMAND
2019-05-24 21:46:55 -10:00
"${venv_path}/${venv_python_executable_path}" -m pip
install "${CODEGEN_PACKAGE}"
RESULT_VARIABLE pip_result
)
if (NOT pip_result EQUAL 0)
message(FATAL_ERROR "Failed to install codegen packages into codegen virtualenv. result: ${pip_result}.")
endif()
file(TOUCH "${package_dummy}")
#
# Create the dummy file to signify that we have successfully created the venv.
#
file(TOUCH "${venv_dummy}")
endif()
#
# Add build-time target to update the codegen package if it's touched.
#
add_custom_command(
OUTPUT
"${package_dummy}"
COMMAND
2019-05-24 21:46:55 -10:00
"${venv_path}/${venv_python_executable_path}" -m pip
install "${CODEGEN_PACKAGE}"
COMMAND
"${CMAKE_COMMAND}" "-E" "touch" "${package_dummy}"
DEPENDS
"${CODEGEN_PACKAGE}"
)
set(include_directories_arguments "")
2019-05-24 21:46:55 -10:00
foreach(include_directory ${include_directories})
list(APPEND include_directories_arguments "-I")
list(APPEND include_directories_arguments "${include_directory}")
endforeach()
message(STATUS "Updating codegen targets")
#
# Set up targets for all files which will be generated by the codegen tool.
#
set(all_output_files "")
foreach(current_source_file ${source_files})
#
# Determine which files are generated by this source file at configure time
#
file(RELATIVE_PATH source_file_relative "${input_root}" "${current_source_file}")
set(cached_source_outputs_filename "${cache_path}/${source_file_relative}.outputs")
set(cached_outputs_are_ok false)
if (EXISTS "${cached_source_outputs_filename}")
# Get the timestamp of the cache file and the source file
file(TIMESTAMP "${cached_source_outputs_filename}" cached_outputs_timestamp "%s")
file(TIMESTAMP "${current_source_file}" source_file_timestamp "%s")
if (cached_outputs_timestamp GREATER source_file_timestamp)
# Cache hit
file(READ "${cached_source_outputs_filename}" current_output_files)
set(cached_outputs_are_ok true)
endif()
endif()
if (NOT "${cached_outputs_are_ok}")
execute_process(
COMMAND
"${venv_path}/${venv_python_executable_path}" "-m" "codegen"
"get_output_files"
"${current_source_file}"
${include_directories_arguments}
"--libclangpath" "${CLANG_LIBRARY}"
"--source-root" "${input_root}"
"--output-root" "${output_root}"
"--cache-path" "${cache_path}"
OUTPUT_VARIABLE current_output_files
RESULT_VARIABLE tool_result
)
if (NOT tool_result EQUAL 0)
message(SEND_ERROR "Error running codegen tool. result: ${tool_result}.")
continue()
endif()
endif()
#
# Set up a build target for the outputs given to us by the above commands.
#
string(STRIP "${current_output_files}" current_output_files)
list(LENGTH current_output_files current_output_files_len)
if ("${current_output_files_len}" EQUAL 0)
continue()
endif()
foreach(current_output_file ${current_output_files})
list(APPEND all_output_files "${current_output_file}")
endforeach()
add_custom_command(
OUTPUT "${current_output_files}"
COMMAND
"${venv_path}/${venv_python_executable_path}" "-m" "codegen"
"generate"
"${current_source_file}"
${include_directories_arguments}
"--libclangpath" "${CLANG_LIBRARY}"
"--source-root" "${input_root}"
"--output-root" "${output_root}"
DEPENDS "${current_source_file}" "${package_dummy}"
)
endforeach()
#
# "Return" a list of all files that will be generated during build time.
#
set("${generated_files_var}" "${all_output_files}" PARENT_SCOPE)
endfunction()
#
# gather_include_directories recursively builds a list of include directories
# across all dependencies.
#
function(_gather_include_directories_impl target_name)
get_target_property(target_dependencies ${target_name} INTERFACE_LINK_LIBRARIES)
foreach(dep ${target_dependencies})
if(TARGET ${dep})
get_target_property(dep_includes ${dep} INTERFACE_INCLUDE_DIRECTORIES)
list(APPEND target_includes ${dep_includes})
_gather_include_directories_impl(${dep})
endif()
endforeach()
set(target_includes ${target_includes} PARENT_SCOPE)
endfunction()
function(gather_include_directories var target_name)
get_target_property(target_includes ${target_name} INTERFACE_INCLUDE_DIRECTORIES)
_gather_include_directories_impl(${target_name})
list(REMOVE_DUPLICATES target_includes)
set(${var} ${target_includes} PARENT_SCOPE)
endfunction()