blob: 38d39a87e147203f33287f171b0f428566ece974 [file] [log] [blame]
# Copyright 2020 The Dawn & Tint Authors
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# Check for Jinja2
if (NOT DAWN_JINJA2_DIR)
message(STATUS "Dawn: Using system jinja2")
execute_process(
COMMAND ${Python3_EXECUTABLE} -c "import jinja2"
RESULT_VARIABLE RET
)
if (NOT RET EQUAL 0)
message(FATAL_ERROR "Dawn: Missing dependencies for code generation, please ensure you have python-jinja2 installed.")
endif()
else()
message(STATUS "Dawn: using jinja2 at ${DAWN_JINJA2_DIR}")
message(STATUS "Dawn: using markupsafe at ${DAWN_MARKUPSAFE_DIR}")
endif()
# Function to invoke a generator_lib.py generator.
# - SCRIPT is the name of the script to call
# - OUTPUT_HEADERS will be modified to contain the list of header files (*.h, *.hpp) generated by this generator
# - OUTPUT_SOURCES will be modified to contain the list of all other files generated by this generator
# - ARGS are the extra arguments to pass to the script in addition to the base generator_lib.py arguments
# - PRINT_NAME is the name to use when outputting status or errors
function(DawnGenerator)
cmake_parse_arguments(PARSE_ARGV 0 arg
""
"SCRIPT;OUTPUT_HEADERS;OUTPUT_SOURCES;PRINT_NAME"
"EXTRA_PARAMETERS"
)
message(STATUS "Dawn: Configuring DawnGenerator for ${arg_PRINT_NAME}.")
if (arg_UNPARSED_ARGUMENTS)
message(FATAL_ERROR
"Unparsed arguments for DawnGenerator: "
"${arg_UNPARSED_ARGUMENTS}")
endif ()
# Build the set of args common to all invocation of that generator.
set(BASE_ARGS
${Python3_EXECUTABLE}
${arg_SCRIPT}
--template-dir
"${DAWN_TEMPLATE_DIR}"
--root-dir
"${Dawn_SOURCE_DIR}"
--output-dir
"${DAWN_BUILD_GEN_DIR}"
${arg_EXTRA_PARAMETERS}
)
if (DAWN_JINJA2_DIR)
list(APPEND BASE_ARGS --jinja2-path ${DAWN_JINJA2_DIR})
endif()
if (DAWN_MARKUPSAFE_DIR)
list(APPEND BASE_ARGS --markupsafe-path ${DAWN_MARKUPSAFE_DIR})
endif()
# Call the generator to get the list of its dependencies.
execute_process(
COMMAND ${BASE_ARGS} --print-cmake-dependencies
OUTPUT_VARIABLE DEPENDENCIES
RESULT_VARIABLE RET
)
if (NOT RET EQUAL 0)
message(FATAL_ERROR "Dawn: Failed to get the dependencies for ${arg_PRINT_NAME}. Base args are '${BASE_ARGS}'.")
endif()
# Ask CMake to re-run if any of the dependencies changed as it might modify the build graph.
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${DEPENDENCIES})
# Call the generator to get the list of its outputs.
execute_process(
COMMAND ${BASE_ARGS} --print-cmake-outputs
OUTPUT_VARIABLE OUTPUTS
RESULT_VARIABLE RET
)
if (NOT RET EQUAL 0)
message(FATAL_ERROR "Dawn: Failed to get the outputs for ${arg_PRINT_NAME}. Base args are '${BASE_ARGS}'.")
endif()
# Add the custom command that calls the generator.
add_custom_command(
COMMAND ${BASE_ARGS}
DEPENDS ${DEPENDENCIES}
OUTPUT ${OUTPUTS}
COMMENT "Dawn: Generating files for ${arg_PRINT_NAME}."
)
# Populate the list of outputs.
set(headers)
set(sources)
foreach (filename IN LISTS OUTPUTS)
get_filename_component(extension "${filename}" EXT)
if (extension MATCHES "(h|hpp)")
list(APPEND headers "${filename}")
else ()
list(APPEND sources "${filename}")
endif ()
endforeach ()
if (sources AND NOT arg_OUTPUT_SOURCES)
message(FATAL_ERROR
"This function generated source files, although an output variable was not defined!"
"Please provide a variable name for OUTPUT_SOURCES.")
endif ()
if (headers AND NOT arg_OUTPUT_HEADERS)
message(FATAL_ERROR
"This function generated header files, although an output variable was not defined!"
"Please provide a variable name for OUTPUT_HEADERS.")
endif ()
set(${arg_OUTPUT_SOURCES} ${sources} PARENT_SCOPE)
set(${arg_OUTPUT_HEADERS} ${headers} PARENT_SCOPE)
# Prior to CMake 3.20 the GENERATED property is local to a directory which means that putting
# generated headers in INTERFACE properties causes dependent targets to complain that they
# cannot find the file. (because they don't see it as generated and want to check is is
# actually on the filesystem).
# Work around this by generating the files once if they aren't present at configuration time.
set(needToGenerate OFF)
foreach(path ${OUTPUTS})
if (NOT EXISTS ${path})
set(needToGenerate ON)
endif()
endforeach()
if (${needToGenerate})
message(STATUS "Dawn: Generating initial versions of files for ${arg_PRINT_NAME}.")
execute_process(COMMAND ${BASE_ARGS} RESULT_VARIABLE RET)
if (NOT RET EQUAL 0)
message(FATAL_ERROR "Dawn: Failed to generate the initial version of files for ${arg_PRINT_NAME}. Base args are '${BASE_ARGS}'.")
endif()
endif()
endfunction()
# Helper function to call dawn_generator.py:
# - TARGET is the generator target to build
# - OUTPUT_HEADERS, OUTPUT_SOURCES, and PRINT_NAME are like for DawnGenerator
function(DawnJSONGenerator)
cmake_parse_arguments(PARSE_ARGV 0 arg
""
"TARGET;OUTPUT_SOURCES;OUTPUT_HEADERS;PRINT_NAME"
""
)
DawnGenerator(
SCRIPT "${Dawn_SOURCE_DIR}/generator/dawn_json_generator.py"
PRINT_NAME "${arg_PRINT_NAME}"
OUTPUT_HEADERS HEADERS
OUTPUT_SOURCES SOURCES
EXTRA_PARAMETERS
--dawn-json
"${Dawn_SOURCE_DIR}/src/dawn/dawn.json"
--wire-json
"${Dawn_SOURCE_DIR}/src/dawn/dawn_wire.json"
--kotlin-json
"${Dawn_SOURCE_DIR}/src/dawn/dawn_kotlin.json"
--targets
${arg_TARGET}
${arg_UNPARSED_ARGUMENTS}
)
if (SOURCES AND NOT arg_OUTPUT_SOURCES)
message(FATAL_ERROR
"This function generated source files, although an output variable was not defined!"
"Please provide a variable name for OUTPUT_SOURCES.")
elseif (arg_OUTPUT_SOURCES AND NOT SOURCES)
message(WARNING
"This function did not generate source files. The OUTPUT_SOURCES argument is unnecessary.")
endif ()
if (HEADERS AND NOT arg_OUTPUT_HEADERS)
message(FATAL_ERROR
"This function generated header files, although an output variable was not defined!"
"Please provide a variable name for OUTPUT_HEADERS.")
elseif (arg_OUTPUT_HEADERS AND NOT HEADERS)
message(WARNING
"This function did not generate header files. The OUTPUT_HEADERS argument is unnecessary.")
endif ()
# Forward the result up one more scope
set(${arg_OUTPUT_SOURCES} ${SOURCES} PARENT_SCOPE)
set(${arg_OUTPUT_HEADERS} ${HEADERS} PARENT_SCOPE)
endfunction()