Files
TheRock/cmake/therock_subproject.cmake
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

1913 lines
85 KiB
CMake

# Copyright Advanced Micro Devices, Inc.
# SPDX-License-Identifier: MIT
# therock_subproject.cmake
# Facilities for defining build subprojects. This has some similarity to the
# built-in ExternalProject and FetchContent facilities, but it is intended to
# be performant and ergonomic for a super project of our scale where the sources
# of the subprojects are expected to be modified as part of the super-project
# development flow.
include(ExternalProject)
# Global properties.
# THEROCK_DEFAULT_CMAKE_VARS:
# List of CMake variables that will be injected by default into the
# project_init.cmake file of each subproject.
set_property(GLOBAL PROPERTY THEROCK_DEFAULT_CMAKE_VARS
CMAKE_PROGRAM_PATH
CMAKE_PLATFORM_NO_VERSIONED_SONAME
Python3_EXECUTABLE
THEROCK_SOURCE_DIR
THEROCK_BINARY_DIR
THEROCK_ROCM_LIBRARIES_SOURCE_DIR
THEROCK_ROCM_SYSTEMS_SOURCE_DIR
THEROCK_BUILD_TESTING
THEROCK_ENABLE_LLVM_TESTS
LLVM_LIT_ARGS
THEROCK_USE_SAFE_DEPENDENCY_PROVIDER
ROCM_SYMLINK_LIBS
# RPATH handling.
THEROCK_NO_INSTALL_RPATH
THEROCK_PRIVATE_INSTALL_RPATH_DIRS
THEROCK_INSTALL_RPATH_EXECUTABLE_DIR
THEROCK_INSTALL_RPATH_LIBRARY_DIR
# Debug info handling.
THEROCK_SPLIT_DEBUG_INFO
)
# Whenever a new package is advertised by the super-project, it is added here.
# This is used by the sub-project dependency resolver to error if a package
# dep is added but not declared (i.e. if it would fall back to the system
# resolver).
set_property(GLOBAL PROPERTY THEROCK_ALL_PROVIDED_PACKAGES)
set_property(GLOBAL PROPERTY THEROCK_SUBPROJECT_COMPILE_COMMANDS_FILES)
if(CMAKE_C_VISIBILITY_PRESET)
list(APPEND THEROCK_DEFAULT_CMAKE_VARS ${CMAKE_C_VISIBILITY_PRESET})
endif()
if(CMAKE_CXX_VISIBILITY_PRESET)
list(APPEND THEROCK_DEFAULT_CMAKE_VARS ${CMAKE_CXX_VISIBILITY_PRESET})
endif()
if(Python3_FIND_VIRTUALENV)
list(APPEND THEROCK_DEFAULT_CMAKE_VARS Python3_FIND_VIRTUALENV)
endif()
# CXX flags that we hard-code in the toolchain file when building projects
# that use amd-llvm. In these cases, because it is our built toolchain, we
# don't need to probe warning flag availability and just have a hard-coded
# list. We only squelch warnings here that do not signal code correctness
# issues.
# TODO: Clean up warning flags (https://github.com/ROCm/TheRock/issues/47)
set(THEROCK_AMD_LLVM_DEFAULT_CXX_FLAGS
-Wno-documentation-unknown-command
-Wno-documentation-pedantic
-Wno-unused-command-line-argument
# There are real issues here but rocBLAS and rocSPARSE generate 300-400MB of
# warning logs with this enabled, practically breaking build tooling.
-Wno-explicit-specialization-storage-class
)
if(WIN32)
# TODO(#36): Could we fix these warnings as part of enabling shared library builds?
# These are frequently set in subproject toolchain-windows.cmake files.
# Warning example:
# __declspec attribute 'dllexport' is not supported
list(APPEND THEROCK_AMD_LLVM_DEFAULT_CXX_FLAGS -Wno-ignored-attributes)
# Warning example:
# unknown attribute '__dllimport__' ignored
list(APPEND THEROCK_AMD_LLVM_DEFAULT_CXX_FLAGS -Wno-unknown-attributes)
# Warning example:
# duplicate 'static' declaration specifier
list(APPEND THEROCK_AMD_LLVM_DEFAULT_CXX_FLAGS -Wno-duplicate-decl-specifier)
endif()
# Generates a command prefix that can be prepended to any custom command line
# to perform log/console redirection and pretty printing.
# LOG_FILE: If given, command output will also be sent to this log file. If
# not absolute, it will be made absolute relative to logs/ in the project
# binary directory.
# OUTPUT_ON_FAILURE: Boolean value to indicate whether output should go to the
# console only on failure.
# LABEL: Label to prefix console output with.
#
# This uses the build_tools/teatime.py script for output management. See that
# script for further details. One thing to note: if TEATIME_LABEL_GH_GROUP=1
# in the environment, console output will be formatted with GitHub action
# begin/end group markers vs log line prefixes. Generally, you want to set
# this in CI jobs.
function(therock_subproject_log_command out_var)
cmake_parse_arguments(
PARSE_ARGV 1 ARG
""
"LOG_FILE;LABEL;OUTPUT_ON_FAILURE"
""
)
set(command
"${Python3_EXECUTABLE}"
"${THEROCK_SOURCE_DIR}/build_tools/teatime.py"
"--log-timestamps"
)
if(ARG_LABEL)
list(APPEND command "--label" "${ARG_LABEL}")
endif()
if(ARG_OUTPUT_ON_FAILURE)
list(APPEND command "--no-interactive")
else()
list(APPEND command "--interactive")
endif()
if(ARG_LOG_FILE)
cmake_path(ABSOLUTE_PATH ARG_LOG_FILE BASE_DIRECTORY "${THEROCK_BINARY_DIR}/logs")
list(APPEND command "${ARG_LOG_FILE}")
endif()
list(APPEND command "--")
set("${out_var}" "${command}" PARENT_SCOPE)
endfunction()
# therock_subproject_fetch
# Fetches arbitrary content. This mostly defers to ExternalProject_Add to get
# content but it performs no actual building.
# All unrecognized options are passed to ExternalProject_Add.
# This can interoperate with therock_cmake_subproject_declare by adding the
# CMAKE_PROJECT option, which makes the CMakeLists.txt in the archive visible
# to CMake (which the subproject depends on). Additional touch byproducts
# can be generated with TOUCH.
function(therock_subproject_fetch target_name)
cmake_parse_arguments(
PARSE_ARGV 1 ARG
"CMAKE_PROJECT"
"SOURCE_DIR;PREFIX;EXCLUDE_FROM_ALL"
"TOUCH"
)
if(NOT DEFINED ARG_EXCLUDE_FROM_ALL)
set(ARG_EXCLUDE_FROM_ALL TRUE)
endif()
if(NOT DEFINED ARG_PREFIX)
set(ARG_PREFIX "${CMAKE_CURRENT_BINARY_DIR}/${target_name}_fetch")
endif()
if(NOT DEFINED ARG_SOURCE_DIR)
set(ARG_SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/source")
endif()
set(_extra)
# In order to interop with therock_cmake_subproject_declare, the CMakeLists.txt
# file must exist so we mark this as a by-product. This serves as the dependency
# anchor and causes proper ordering of fetch->configure.
if(ARG_CMAKE_PROJECT)
list(APPEND ARG_TOUCH "${ARG_SOURCE_DIR}/CMakeLists.txt")
endif()
if(ARG_TOUCH)
list(APPEND _extra
BUILD_COMMAND "${CMAKE_COMMAND}" -E touch ${ARG_TOUCH}
BUILD_BYPRODUCTS ${ARG_TOUCH}
)
else()
list(APPEND _extra "BUILD_COMMAND" "")
endif()
ExternalProject_Add(
"${target_name}"
EXCLUDE_FROM_ALL "${ARG_EXCLUDE_FROM_ALL}"
PREFIX "${ARG_PREFIX}"
DOWNLOAD_NO_PROGRESS ON
LOG_DOWNLOAD ON
LOG_MERGED_STDOUTERR ON
LOG_OUTPUT_ON_FAILURE ON
SOURCE_DIR "${ARG_SOURCE_DIR}"
CONFIGURE_COMMAND ""
INSTALL_COMMAND ""
TEST_COMMAND ""
${_extra}
${ARG_UNPARSED_ARGUMENTS}
)
# Write a .smrev file that is used to compute fingerprints. This matches the
# logic for the source code control system when it is providing a stable hash
# for patched source checkouts.
cmake_path(GET ARG_SOURCE_DIR PARENT_PATH _source_parent)
cmake_path(GET ARG_SOURCE_DIR FILENAME _source_basename)
set(_smrev_file "${_source_parent}/.${_source_basename}.smrev")
file(WRITE "${_smrev_file}" "${_extra};${ARG_UNPARSED_ARGUMENTS}")
endfunction()
# therock_cmake_subproject_declare
# This declares a cmake based subproject by setting a number of key properties
# and setting up boiler-plate targets.
#
# Arguments:
# NAME: Globally unique subproject name. This will become the stem of various
# targets and therefore must be unique (even for nested projects) and a valid
# target identifier.
# ACTIVATE: Option to signify that this call should end by calling
# therock_cmake_subproject_activate. Do not specify this option if wishing to
# further configure the sub-project.
# USE_DIST_AMDGPU_TARGETS: Use the dist GPU targets (THEROCK_DIST_AMDGPU_TARGETS)
# vs the shard specific GPU targets. Typically this is set on runtime components
# that are intended to work for all supported targets, whereas it is omitted for
# components which are meant to be built only for the given targets (typically
# for kernel libraries).
# USE_TEST_AMDGPU_TARGETS: Use the test GPU targets (THEROCK_TEST_AMDGPU_TARGETS)
# which default to all available architectures. Set this on test subprojects whose
# artifacts are marked TARGET_NEUTRAL so that the resulting _generic artifact
# contains device code for every architecture, making upload races in classic CI
# harmless. Does not affect dist_info.json (unlike USE_DIST_AMDGPU_TARGETS).
# DISABLE_AMDGPU_TARGETS: Do not set any GPU_TARGETS or AMDGPU_TARGETS variables
# in the project. This is largely used for broken projects that cannot
# build with an explicit target list.
# DEFAULT_GPU_TARGETS: List of GPU targets to use as a fallback when all targets
# are excluded via EXCLUDE_TARGET_PROJECTS in therock_amdgpu_targets.cmake.
# Most projects should NOT set this and will default to an empty list. Only
# set this for projects that cannot build with an empty target list. This
# should only be needed during bringup of new targets and is not intended
# for production use.
# NO_MERGE_COMPILE_COMMANDS: Option to disable merging of this project's
# compile_commands.json into the overall project. This is useful for
# third-party projects that are excluded from all as it eliminates a
# dependency that forces them to be downloaded/built.
# SOURCE_DIR: Absolute path to the external source directory.
# DIR_PREFIX: By default, directories named "build", "stage", "stamp" are
# created. But if there are multiple sub-projects in a parent dir, then they
# all must have a distinct prefix (not recommended).
# INSTALL_DESTINATION: Sub-directory within the stage/dist directory where this
# sub-project installs. Defaults to empty, meaning that it installs at the top
# of the namespace.
# CMAKE_ARGS: Additional CMake configure arguments.
# CMAKE_INCLUDES: Additional CMake files to include at the top level.
# BUILD_DEPS: Projects which must build and provide their packages prior to this
# one.
# RUNTIME_DEPS: Projects which must build prior to this one and whose install
# files must be distributed with this project's artifacts in order to
# function.
# INTERFACE_INCLUDE_DIRS: Relative paths within the install tree which dependent
# sub-projects must have their CPP include path set to include. Use of this
# is strongly discouraged (deps should be on proper CMake libraries), but
# some old projects still need this.
# INTERFACE_LINK_DIRS: Relative paths within the install tree which dependent
# sub-projects must add to their runtime link library path.
# INTERFACE_PROGRAM_DIRS: Relative paths within the install tree which
# dependent sub-projects must add to their program search path.
# INTERFACE_PKG_CONFIG_DIRS: Relative paths within the install tree which
# dependent sub-projects must add to their PKG_CONFIG_PATH.
# IGNORE_PACKAGES: List of find_package package names to ignore, even if they
# are advertised by the super-project. These packages will always fall-through
# to the system resolver.
# COMPILER_TOOLCHAIN: Uses a built compiler toolchain instead of the
# super-project specified C/C++ compiler. This will add an implicit dep on
# the named compiler sub-project and reconfigure CMAKE_C(XX)_COMPILER options.
# Only a fixed set of supported toolchains are supported (currently
# "amd-llvm" and "amd-hip").
# BACKGROUND_BUILD: Option to indicate that the subproject does low concurrency,
# high latency build steps. It will be run in the background in a job pool that
# allows some overlapping of work (controlled by
# THEROCK_BACKGROUND_BUILD_JOBS).
# CMAKE_LISTS_RELPATH: Relative path within the source directory to the
# CMakeLists.txt.
# EXTRA_DEPENDS: Extra target dependencies to add to the configure command.
# OUTPUT_ON_FAILURE: If given, build commands will produce no output unless if
# it fails (logs will still be written). While generally not good to squelch a
# "chatty" build, some third party libraries are hopeless and provide little
# signal.
# LOGICAL_TARGET_NAME: If conditional coding is used to alias the actual target
# name (i.e. foobar-old) but configuration files and directories should be
# named relative to some other name, then that can be specified here. This
# affects hooks and default choices of source and binary directories.
#
# RPATH handling:
# Each subproject has default logic injected which configures the INSTALL_RPATH
# of any defined executable or shared library targets. This default behavior can
# be disabled by setting NO_INSTALL_RPATH.
# *IMPORTANT:* This *overrides* any project local install RPATH configuration.
# While it may seem that this is heavy handed, in practice, almost no library
# on its own does the right thing. If it does, opt it out.
#
# In order to compute a correct RPATH (which we always set as relative to
# the installation prefix), it must be possible to determine each target's
# ORIGIN, as the OS will see it in the install tree. CMake does not have a
# way to determine this, so we use the following heuristic:
# * Use THEROCK_INSTALL_RPATH_ORIGIN target property if defined.
# * For executables, use the project level THEROCK_INSTALL_RPATH_EXECUTABLE_DIR.
# * For shared libraries, use the project level THEROCK_INSTALL_RPATH_LIBRARY_DIR.
#
# We then get the computed private install RPATH dirs (which is the combination
# of any transitive INTERFACE_INSTALL_RPATH_DIRS) and transform them to an
# origin relative path and set them on the INSTALL_RPATH property.
#
# INSTALL_RPATH_DIRS: Install-tree relative paths to runtime shared library
# dependencies to be used for targets defined directly in this project.
# INTERFACE_INSTALL_RPATH_DIRS: Like INSTALL_RPATH_DIRS but advertises these
# directories to dependent projects (but does not use them itself).
# INSTALL_RPATH_EXECUTABLE_DIR: Default install-tree relative path to assume
# that all executables are installed to. Defaults to INSTALL_DESTINATION/bin.
# Can be overriden on a per target basis by setting
# THEROCK_INSTALL_RPATH_EXECUTABLE_DIR.
# INSTALL_RPATH_LIBRARY_DIR: Default install-tree relative path to assume
# that all shared libraries are installed to. Defaults to
# INSTALL_DESTINATION/lib. Can be overriden on a per target basis by setting
# THEROCK_INSTALL_RPATH_LIBRARY_DIR.
# INSTALL_OPTIONAL_COMPONENTS: List of CMake component names to pass to cmake
# --install via --component flags alongside default (all) target. Use this
# when a project marks certain components as EXCLUDE_FROM_ALL and you need to
# explicitly request them.
#
# Note that all transitive keywords (i.e. "INTERFACE_" prefixes) only consider
# transitive deps along their RUNTIME_DEPS edges, not BUILD_DEPS.
#
# Fingerprinting:
# Various build system caching approaches require each sub-project to be able
# to compute a stable fingerprint (fprint). For normal git-based sub-projects,
# this requires no further configuration. But for exotic situations, more control
# is available:
#
# FPRINT_SOURCE_DIR: Derive the source directory fingerprint from an alternative
# directory. This uses the usual heuristics of looking for a ".{basename}.smrev"
# file as a stable fingerprint (this is populated by some source systems and from
# therock_subproject_fetch). If not given, then the expolitic EXTERNAL_SOURCE_DIR
# or implicit source dir is used.
# FPRINT_FILE_GLOBS: List of additional file globs to include in the fingerprint.
# FPRINT_SOURCE_HASH: If present, indicates that the source directory should have
# all files hashed instead of querying the source control system. This is used for
# certain small, in-tree directories where we don't want them fingerprinted from
# TheRock's commit but based on content.
function(therock_cmake_subproject_declare target_name)
cmake_parse_arguments(
PARSE_ARGV 1 ARG
"ACTIVATE;USE_DIST_AMDGPU_TARGETS;USE_TEST_AMDGPU_TARGETS;DISABLE_AMDGPU_TARGETS;EXCLUDE_FROM_ALL;BACKGROUND_BUILD;NO_MERGE_COMPILE_COMMANDS;OUTPUT_ON_FAILURE;NO_INSTALL_RPATH;FPRINT_SOURCE_HASH"
"EXTERNAL_SOURCE_DIR;BINARY_DIR;DIR_PREFIX;INSTALL_DESTINATION;COMPILER_TOOLCHAIN;INTERFACE_PROGRAM_DIRS;CMAKE_LISTS_RELPATH;INTERFACE_PKG_CONFIG_DIRS;INSTALL_RPATH_EXECUTABLE_DIR;INSTALL_RPATH_LIBRARY_DIR;LOGICAL_TARGET_NAME;FPRINT_SOURCE_DIR"
"BUILD_DEPS;RUNTIME_DEPS;CMAKE_ARGS;TEST_SUBPROJECTS;CMAKE_INCLUDES;INTERFACE_INCLUDE_DIRS;INTERFACE_LINK_DIRS;IGNORE_PACKAGES;EXTRA_DEPENDS;INSTALL_RPATH_DIRS;INTERFACE_INSTALL_RPATH_DIRS;DEFAULT_GPU_TARGETS;FPRINT_FILE_GLOBS;INSTALL_OPTIONAL_COMPONENTS"
)
if(TARGET "${target_name}")
message(FATAL_ERROR "Cannot declare subproject '${target_name}': a target with that name already exists")
endif()
if(NOT ARG_LOGICAL_TARGET_NAME)
set(ARG_LOGICAL_TARGET_NAME "${target_name}")
endif()
cmake_path(IS_ABSOLUTE ARG_EXTERNAL_SOURCE_DIR _source_is_absolute)
if(_source_is_absolute)
if(NOT ARG_BINARY_DIR)
# TODO: Swap these lines once moved.
set(ARG_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}")
# message(FATAL_ERROR "If specifying an absolute SOURCE_DIR, BINARY_DIR must be specified")
endif()
else()
if(NOT ARG_BINARY_DIR)
set(ARG_BINARY_DIR "${ARG_EXTERNAL_SOURCE_DIR}")
endif()
cmake_path(ABSOLUTE_PATH ARG_EXTERNAL_SOURCE_DIR)
cmake_path(ABSOLUTE_PATH ARG_BINARY_DIR BASE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}")
endif()
set(_cmake_source_dir "${ARG_EXTERNAL_SOURCE_DIR}")
if(ARG_CMAKE_LISTS_RELPATH)
cmake_path(APPEND _cmake_source_dir "${ARG_CMAKE_LISTS_RELPATH}")
endif()
message(STATUS "Including subproject ${target_name} (from ${_cmake_source_dir})")
add_custom_target("${target_name}" COMMENT "Top level target to build the ${target_name} sub-project")
# Build directory.
set(_binary_dir "${ARG_BINARY_DIR}/${ARG_DIR_PREFIX}build")
make_directory("${_binary_dir}")
# Stage directory.
set(_stage_dir "${ARG_BINARY_DIR}/${ARG_DIR_PREFIX}stage")
make_directory("${_stage_dir}")
file(TOUCH "${ARG_BINARY_DIR}/.${ARG_DIR_PREFIX}stage.marker")
# Dist directory.
set(_dist_dir "${ARG_BINARY_DIR}/${ARG_DIR_PREFIX}dist")
make_directory("${_dist_dir}")
# Stamp directory.
set(_stamp_dir "${ARG_BINARY_DIR}/${ARG_DIR_PREFIX}stamp")
make_directory("${_stamp_dir}")
# Prefix directory.
set(_prefix_dir "${ARG_BINARY_DIR}/${ARG_DIR_PREFIX}prefix")
make_directory("${_prefix_dir}")
# Collect transitive requirements from runtime and build deps.
# Include, link, program, pkgconfig and configure depends are derived transitively
# from build and runtime deps. RPATH and transitive runtime deps are only
# collected from runtime deps.
_therock_cmake_subproject_collect_build_deps(
_private_include_dirs
_private_link_dirs
_private_program_dirs
_private_pkg_config_dirs
_transitive_configure_depend_files
${ARG_RUNTIME_DEPS} ${ARG_BUILD_DEPS})
_therock_cmake_subproject_collect_runtime_deps(
_interface_install_rpath_dirs
_transitive_runtime_deps
${ARG_RUNTIME_DEPS})
# Include dirs
set(_declared_include_dirs "${ARG_INTERFACE_INCLUDE_DIRS}")
_therock_cmake_subproject_absolutize(_declared_include_dirs "${_stage_dir}")
set(_interface_include_dirs ${_private_include_dirs} ${_declared_include_dirs})
# Link dirs
set(_declared_link_dirs "${ARG_INTERFACE_LINK_DIRS}")
_therock_cmake_subproject_absolutize(_declared_link_dirs "${_stage_dir}")
# The link dirs that we advertise combine interface link dirs of runtime deps
# and any that we declared.
set(_interface_link_dirs ${_private_link_dirs} ${_declared_link_dirs})
# Program dirs
# Collect program dirs from explicit args and RUNTIME_DEPS.
set(_declared_program_dirs "${ARG_INTERFACE_PROGRAM_DIRS}")
_therock_cmake_subproject_absolutize(_declared_program_dirs "${_dist_dir}")
# The program dirs that we advertise combine interface program dirs of
# runtime deps and any that we declared.
set(_interface_program_dirs ${_private_program_dirs} ${_declared_program_dirs})
# PkgConfig dirs
set(_declared_pkg_config_dirs "${ARG_INTERFACE_PKG_CONFIG_DIRS}")
_therock_cmake_subproject_absolutize(_declared_pkg_config_dirs "${_stage_dir}")
set(_interface_pkg_config_dirs ${_private_pkg_config_dirs} ${_declared_pkg_config_dirs})
# Install RPATH dirs.
set(_interface_install_rpath_dirs ${_interface_install_rpath_dirs} ${ARG_INTERFACE_INSTALL_RPATH_DIRS})
set(_private_install_rpath_dirs ${ARG_INSTALL_RPATH_DIRS} ${_interface_install_rpath_dirs})
# Dedup transitives.
list(REMOVE_DUPLICATES _private_include_dirs)
list(REMOVE_DUPLICATES _interface_include_dirs)
list(REMOVE_DUPLICATES _private_link_dirs)
list(REMOVE_DUPLICATES _interface_link_dirs)
list(REMOVE_DUPLICATES _private_program_dirs)
list(REMOVE_DUPLICATES _interface_program_dirs)
list(REMOVE_DUPLICATES _transitive_runtime_deps)
list(REMOVE_DUPLICATES _transitive_configure_depend_files)
list(REMOVE_DUPLICATES _private_pkg_config_dirs)
list(REMOVE_DUPLICATES _interface_pkg_config_dirs)
list(REMOVE_DUPLICATES _interface_install_rpath_dirs)
list(REMOVE_DUPLICATES _private_install_rpath_dirs)
# RPATH Executable and Library dir.
if(NOT ARG_INSTALL_RPATH_EXECUTABLE_DIR)
if(ARG_INSTALL_DESTINATION)
set(ARG_INSTALL_RPATH_EXECUTABLE_DIR ARG_INSTALL_DESTINATION)
cmake_path(APPEND ARG_INSTALL_RPATH_EXECUTABLE_DIR "bin")
else()
set(ARG_INSTALL_RPATH_EXECUTABLE_DIR "bin")
endif()
endif()
if(NOT ARG_INSTALL_RPATH_LIBRARY_DIR)
if(ARG_INSTALL_DESTINATION)
set(ARG_INSTALL_RPATH_LIBRARY_DIR ARG_INSTALL_DESTINATION)
cmake_path(APPEND ARG_INSTALL_RPATH_EXECUTABLE_DIR "lib")
else()
set(ARG_INSTALL_RPATH_LIBRARY_DIR "lib")
endif()
endif()
# Build pool determination.
set(_build_pool)
if(ARG_BACKGROUND_BUILD)
set(_build_pool "therock_background")
endif()
# GPU Targets.
if(ARG_DISABLE_AMDGPU_TARGETS)
set(_gpu_targets)
elseif(ARG_USE_TEST_AMDGPU_TARGETS)
set(_gpu_targets "${THEROCK_TEST_AMDGPU_TARGETS}")
elseif(ARG_USE_DIST_AMDGPU_TARGETS)
set(_gpu_targets "${THEROCK_DIST_AMDGPU_TARGETS}")
else()
set(_gpu_targets "${THEROCK_AMDGPU_TARGETS}")
endif()
set_target_properties("${target_name}" PROPERTIES
THEROCK_LOGICAL_TARGET_NAME "${ARG_LOGICAL_TARGET_NAME}"
THEROCK_SUBPROJECT cmake
THEROCK_BUILD_POOL "${_build_pool}"
THEROCK_AMDGPU_TARGETS "${_gpu_targets}"
THEROCK_DEFAULT_GPU_TARGETS "${ARG_DEFAULT_GPU_TARGETS}"
THEROCK_DISABLE_AMDGPU_TARGETS "${ARG_DISABLE_AMDGPU_TARGETS}"
THEROCK_EXCLUDE_FROM_ALL "${ARG_EXCLUDE_FROM_ALL}"
THEROCK_NO_MERGE_COMPILE_COMMANDS "${ARG_NO_MERGE_COMPILE_COMMANDS}"
THEROCK_EXTERNAL_SOURCE_DIR "${ARG_EXTERNAL_SOURCE_DIR}"
THEROCK_BINARY_DIR "${_binary_dir}"
THEROCK_DIST_DIR "${_dist_dir}"
THEROCK_STAGE_DIR "${_stage_dir}"
THEROCK_INSTALL_DESTINATION "${ARG_INSTALL_DESTINATION}"
THEROCK_STAMP_DIR "${_stamp_dir}"
THEROCK_PREFIX_DIR "${_prefix_dir}"
THEROCK_CMAKE_SOURCE_DIR "${_cmake_source_dir}"
THEROCK_CMAKE_PROJECT_INIT_FILE "${ARG_BINARY_DIR}/${ARG_BUILD_DIR}_init.cmake"
THEROCK_CMAKE_PROJECT_TOOLCHAIN_FILE "${ARG_BINARY_DIR}/${ARG_BUILD_DIR}_toolchain.cmake"
THEROCK_CMAKE_ARGS "${ARG_CMAKE_ARGS}"
THEROCK_CMAKE_INCLUDES "${ARG_CMAKE_INCLUDES}"
# Non-transitive build deps.
THEROCK_BUILD_DEPS "${ARG_BUILD_DEPS}"
# Transitive runtime deps.
THEROCK_RUNTIME_DEPS "${_transitive_runtime_deps}"
# Optional override for test dependencies (subproject names to test when this changes).
THEROCK_TEST_SUBPROJECTS "${ARG_TEST_SUBPROJECTS}"
# Include dirs that this project compiles with.
THEROCK_PRIVATE_INCLUDE_DIRS "${_private_include_dirs}"
# Include dirs that are advertised to dependents.
THEROCK_INTERFACE_INCLUDE_DIRS "${_interface_include_dirs}"
# That this project compiles with.
THEROCK_PRIVATE_LINK_DIRS "${_private_link_dirs}"
# Link dirs that are advertised to dependents
THEROCK_INTERFACE_LINK_DIRS "${_interface_link_dirs}"
# PkgConfig directories that this project must use.
THEROCK_PRIVATE_PKG_CONFIG_DIRS "${_private_pkg_config_dirs}"
# Directories that sub-projects must add to their PKG_CONFIG_PATH.
THEROCK_INTERFACE_PKG_CONFIG_DIRS "${_interface_pkg_config_dirs}"
# Program dirs that this sub-project must configure with.
THEROCK_PRIVATE_PROGRAM_DIRS "${_private_program_dirs}"
# Program dirs that are advertised to dependents.
THEROCK_INTERFACE_PROGRAM_DIRS "${_interface_program_dirs}"
THEROCK_IGNORE_PACKAGES "${ARG_IGNORE_PACKAGES}"
THEROCK_COMPILER_TOOLCHAIN "${ARG_COMPILER_TOOLCHAIN}"
# Any extra depend files that must be added to the configure phase of dependents.
THEROCK_INTERFACE_CONFIGURE_DEPEND_FILES "${_transitive_configure_depend_files}"
THEROCK_EXTRA_DEPENDS "${ARG_EXTRA_DEPENDS}"
THEROCK_OUTPUT_ON_FAILURE "${ARG_OUTPUT_ON_FAILURE}"
THEROCK_OPTIONAL_INSTALL_COMPONENTS "${ARG_INSTALL_OPTIONAL_COMPONENTS}"
# RPATH
THEROCK_NO_INSTALL_RPATH "${ARG_NO_INSTALL_RPATH}"
THEROCK_INTERFACE_INSTALL_RPATH_DIRS "${_interface_install_rpath_dirs}"
THEROCK_PRIVATE_INSTALL_RPATH_DIRS "${_private_install_rpath_dirs}"
THEROCK_INSTALL_RPATH_EXECUTABLE_DIR "${ARG_INSTALL_RPATH_EXECUTABLE_DIR}"
THEROCK_INSTALL_RPATH_LIBRARY_DIR "${ARG_INSTALL_RPATH_LIBRARY_DIR}"
# Fingeprinting
THEROCK_FPRINT_SOURCE_DIR "${ARG_FPRINT_SOURCE_DIR}"
THEROCK_FPRINT_FILE_GLOBS "${ARG_FPRINT_FILE_GLOBS}"
THEROCK_FPRINT_SOURCE_HASH "${ARG_FPRINT_SOURCE_HASH}"
)
if(ARG_ACTIVATE)
therock_cmake_subproject_activate("${target_name}")
endif()
endfunction()
# therock_cmake_subproject_provide_package
# Declares that a subproject provides a given package which should be findable
# with `find_package(package_name)` at the given path relative to its install
# directory.
function(therock_cmake_subproject_provide_package target_name package_name relative_path)
string(APPEND CMAKE_MESSAGE_INDENT " ")
get_property(existing_packages GLOBAL PROPERTY THEROCK_ALL_PROVIDED_PACKAGES)
if("${package_name}" IN_LIST existing_packages)
message(SEND_ERROR "Duplicate package provided by ${target_name}: ${package_name}")
endif()
set_property(GLOBAL APPEND PROPERTY THEROCK_ALL_PROVIDED_PACKAGES "${package_name}")
get_target_property(_existing_packages "${target_name}" THEROCK_PROVIDE_PACKAGES)
if(${package_name} IN_LIST _existing_packages)
message(FATAL_ERROR "Package defined multiple times on sub-project ${target_name}: ${package_name}")
endif()
set_property(TARGET "${target_name}" APPEND PROPERTY THEROCK_PROVIDE_PACKAGES "${package_name}")
set(_relpath_name THEROCK_PACKAGE_RELPATH_${package_name})
set_property(TARGET "${target_name}" PROPERTY ${_relpath_name} "${relative_path}")
if(THEROCK_VERBOSE)
message(STATUS "PROVIDE ${package_name} = ${relative_path} (from ${target_name})")
endif()
endfunction()
# therock_cmake_subproject_require_program
# Requires that find_program finds a named program at super-project configure time.
# This is to avoid downstream errors that would otherwise only show up at build time.
# Only to be used by programs that must exist on the host in order to build. Programs
# that are built as part of the project are resolved internally.
function(therock_cmake_subproject_require_host_program target_name)
_therock_assert_is_cmake_subproject("${target_name}")
foreach(prog IN LISTS ARGN)
find_program(found "${prog}" OPTIONAL)
if(NOT found)
message(FATAL_ERROR "Building sub-project ${target_name} requires program '${prog}' on the system path but it is not found")
endif()
endforeach()
endfunction()
# therock_cmake_subproject_activate
# If using multi-step setup (i.e. without 'ACTIVATE' on the declare), then this
# must be called once all configuration is complete.
function(therock_cmake_subproject_activate target_name)
_therock_assert_is_cmake_subproject("${target_name}")
# Get properties.
get_target_property(_binary_dir "${target_name}" THEROCK_BINARY_DIR)
get_target_property(_build_deps "${target_name}" THEROCK_BUILD_DEPS)
get_target_property(_build_pool "${target_name}" THEROCK_BUILD_POOL)
get_target_property(_compiler_toolchain "${target_name}" THEROCK_COMPILER_TOOLCHAIN)
get_target_property(_transitive_configure_depend_files "${target_name}" THEROCK_INTERFACE_CONFIGURE_DEPEND_FILES)
get_target_property(_dist_dir "${target_name}" THEROCK_DIST_DIR)
get_target_property(_runtime_deps "${target_name}" THEROCK_RUNTIME_DEPS)
get_target_property(_cmake_args "${target_name}" THEROCK_CMAKE_ARGS)
get_target_property(_cmake_includes "${target_name}" THEROCK_CMAKE_INCLUDES)
get_target_property(_cmake_project_init_file "${target_name}" THEROCK_CMAKE_PROJECT_INIT_FILE)
get_target_property(_cmake_project_toolchain_file "${target_name}" THEROCK_CMAKE_PROJECT_TOOLCHAIN_FILE)
get_target_property(_cmake_source_dir "${target_name}" THEROCK_CMAKE_SOURCE_DIR)
get_target_property(_exclude_from_all "${target_name}" THEROCK_EXCLUDE_FROM_ALL)
get_target_property(_external_source_dir "${target_name}" THEROCK_EXTERNAL_SOURCE_DIR)
get_target_property(_extra_depends "${target_name}" THEROCK_EXTRA_DEPENDS)
get_target_property(_ignore_packages "${target_name}" THEROCK_IGNORE_PACKAGES)
get_target_property(_install_destination "${target_name}" THEROCK_INSTALL_DESTINATION)
get_target_property(_no_merge_compile_commands "${target_name}" THEROCK_NO_MERGE_COMPILE_COMMANDS)
get_target_property(_private_include_dirs "${target_name}" THEROCK_PRIVATE_INCLUDE_DIRS)
get_target_property(_private_link_dirs "${target_name}" THEROCK_PRIVATE_LINK_DIRS)
get_target_property(_private_pkg_config_dirs "${target_name}" THEROCK_PRIVATE_PKG_CONFIG_DIRS)
get_target_property(_private_program_dirs "${target_name}" THEROCK_PRIVATE_PROGRAM_DIRS)
get_target_property(_stage_dir "${target_name}" THEROCK_STAGE_DIR)
get_target_property(_sources "${target_name}" SOURCES)
get_target_property(_stamp_dir "${target_name}" THEROCK_STAMP_DIR)
get_target_property(_prefix_dir "${target_name}" THEROCK_PREFIX_DIR)
get_target_property(_output_on_failure "${target_name}" THEROCK_OUTPUT_ON_FAILURE)
get_target_property(_logical_target_name "${target_name}" THEROCK_LOGICAL_TARGET_NAME)
get_target_property(_optional_install_components "${target_name}" THEROCK_OPTIONAL_INSTALL_COMPONENTS)
# RPATH properties: just mirror these to same named variables because we just
# mirror them syntactically into the subprojet..
get_target_property(THEROCK_NO_INSTALL_RPATH "${target_name}" THEROCK_NO_INSTALL_RPATH)
get_target_property(THEROCK_INTERFACE_INSTALL_RPATH_DIRS "${target_name}" THEROCK_INTERFACE_INSTALL_RPATH_DIRS)
get_target_property(THEROCK_PRIVATE_INSTALL_RPATH_DIRS "${target_name}" THEROCK_PRIVATE_INSTALL_RPATH_DIRS)
get_target_property(THEROCK_INSTALL_RPATH_EXECUTABLE_DIR "${target_name}" THEROCK_INSTALL_RPATH_EXECUTABLE_DIR)
get_target_property(THEROCK_INSTALL_RPATH_LIBRARY_DIR "${target_name}" THEROCK_INSTALL_RPATH_LIBRARY_DIR)
# Fingerprint properties.
get_target_property(_fprint_source_dir "${target_name}" THEROCK_FPRINT_SOURCE_DIR)
get_target_property(_fprint_file_globs "${target_name}" THEROCK_FPRINT_FILE_GLOBS)
get_target_property(_fprint_source_hash "${target_name}" THEROCK_FPRINT_SOURCE_HASH)
if(NOT _fprint_source_dir)
set(_fprint_source_dir "${_external_source_dir}")
endif()
# Stamp file paths.
set(_configure_stamp_file "${_stamp_dir}/configure.stamp")
set(_build_stamp_file "${_stamp_dir}/build.stamp")
set(_stage_stamp_file "${_stamp_dir}/stage.stamp")
# Handle optional properties.
if(NOT _sources)
set(_sources)
endif()
# Defaults.
set(_configure_comment_suffix)
set(_build_comment_suffix)
# Fingerprint management.
# List of files and content that participates in fingerprinting.
set(_fprint_is_valid TRUE)
set(_fprint_files)
set(_fprint_content)
if(NOT _fprint_source_hash)
_therock_subproject_fprint_source_dir(_fprint_src "${_fprint_source_dir}")
if(_fprint_src)
list(APPEND _fprint_content "SOURCE=${_fprint_src}")
else()
set(_fprint_is_valid FALSE)
endif()
else()
file(GLOB_RECURSE _fprint_source_globs "${_fprint_source_dir}/*")
list(APPEND _fprint_files ${_fprint_source_globs})
endif()
if(_fprint_file_globs)
file(GLOB_RECURSE _fprint_file_globs_resolved ${_fprint_file_globs})
list(APPEND _fprint_files ${_fprint_file_globs_resolved})
endif()
# Detect pre/post hooks.
set(_pre_hook_path "${CMAKE_CURRENT_SOURCE_DIR}/pre_hook_${_logical_target_name}.cmake")
if(EXISTS "${_pre_hook_path}")
list(APPEND _fprint_files "${_pre_hook_path}")
else()
set(_pre_hook_path)
endif()
set(_post_hook_path "${CMAKE_CURRENT_SOURCE_DIR}/post_hook_${_logical_target_name}.cmake")
if(EXISTS "${_post_hook_path}")
list(APPEND _fprint_files "${_post_hook_path}")
else()
set(_post_hook_path)
endif()
# Report transitive runtime deps.
if(_runtime_deps AND THEROCK_VERBOSE)
list(JOIN _runtime_deps " " _runtime_deps_pretty)
message(STATUS " RUNTIME_DEPS: ${_runtime_deps_pretty}")
endif()
get_property(_mirror_cmake_vars GLOBAL PROPERTY THEROCK_DEFAULT_CMAKE_VARS)
# Pairs of arguments for a `cmake -E env` command to run before each
# subproject build or configure command.
# https://cmake.org/cmake/help/latest/manual/cmake.1.html#cmdoption-cmake-E-arg-env
# TODO: split into 'build' and 'configure'? Keeping them in sync seems useful.
_therock_cmake_subproject_build_env_pairs(_build_env_pairs)
# Handle compiler toolchain.
set(_compiler_toolchain_addl_depends)
set(_compiler_toolchain_init_contents)
_therock_cmake_subproject_setup_toolchain("${target_name}"
"${_compiler_toolchain}" "${_cmake_project_toolchain_file}")
list(APPEND _fprint_files "${_cmake_project_toolchain_file}")
# Customize any other super-project CMake variables that are captured by
# _init.cmake.
if(_private_program_dirs)
set(CMAKE_PROGRAM_PATH ${_private_program_dirs} ${CMAKE_PROGRAM_PATH})
if(THEROCK_VERBOSE)
foreach(_message_contents ${_private_program_dirs})
message(STATUS " PROGRAM_DIR: ${_message_contents}")
endforeach()
endif()
endif()
# Generate the project_init.cmake
set(_dep_provider_file)
if(_build_deps OR _runtime_deps)
set(_dep_provider_file "${THEROCK_SOURCE_DIR}/cmake/therock_subproject_dep_provider.cmake")
list(APPEND _fprint_files "${_dep_provider_file}")
# Get sub-project dep content.
foreach(_dep_target ${_build_deps} ${_runtime_deps})
get_target_property(_dep_fprint "${_dep_target}" THEROCK_FPRINT)
if(_dep_fprint)
list(APPEND _fprint_content "DEP ${_dep_target}=${_dep_fprint}")
else()
set(_fprint_is_valid FALSE)
if(THEROCK_VERBOSE)
message(STATUS " DEP FPRINT INVALID: ${_dep_target}")
endif()
endif()
endforeach()
endif()
set(_init_contents)
string(APPEND _init_contents "set(THEROCK_PRIVATE_BUILD_RPATH_DIRS)\n")
string(APPEND _init_contents "set(THEROCK_BUILD_STAMP_FILE \"@_build_stamp_file@\")\n")
string(APPEND _init_contents "set(THEROCK_STAGE_STAMP_FILE \"@_stage_stamp_file@\")\n")
string(APPEND _init_contents "set(THEROCK_SUBPROJECT_TARGET \"@target_name@\")\n")
# Support generator expressions in install CODE
# We rely on this for debug symbol separation and some of our very old projects
# have a CMake minver < 3.14, defaulting them to OLD. Unfortunately, this policy
# must be set globally vs in a block scope to have effect.
string(APPEND _init_contents "cmake_policy(SET CMP0087 NEW)\n")
foreach(_var_name ${_mirror_cmake_vars})
string(APPEND _init_contents "set(${_var_name} \"@${_var_name}@\" CACHE STRING \"\" FORCE)\n")
endforeach()
# Process dependencies. We process runtime deps first so that they take precedence
# over build deps (first wins). Both come from the dist directory because if
# build tools are needed from them, only the dist dir is guaranteed to have
# all runtime deps met.
string(APPEND _init_contents "set(THEROCK_PROVIDED_PACKAGES)\n")
set(_deps_contents)
set(_deps_provided)
_therock_cmake_subproject_setup_deps(_deps_contents _deps_provided THEROCK_DIST_DIR ${_runtime_deps})
_therock_cmake_subproject_setup_deps(_deps_contents _deps_provided THEROCK_DIST_DIR ${_build_deps})
string(APPEND _init_contents "${_deps_contents}")
string(APPEND _init_contents "set(THEROCK_IGNORE_PACKAGES \"@_ignore_packages@\")\n")
string(APPEND _init_contents "list(PREPEND CMAKE_MODULE_PATH \"${THEROCK_SOURCE_DIR}/cmake/finders\")\n")
string(APPEND _init_contents "list(PREPEND CMAKE_PREFIX_PATH \"@_prefix_dir@\")\n")
get_property(_all_provided_packages GLOBAL PROPERTY THEROCK_ALL_PROVIDED_PACKAGES)
string(APPEND _init_contents "set(THEROCK_STRICT_PROVIDED_PACKAGES \"@_all_provided_packages@\")\n")
# Disable the CMake package registry within sub-projects. Correct super-project
# resolution goes through the dependency provider and trampoline configs on
# CMAKE_PREFIX_PATH, never the registry. Leaving the registry enabled lets an
# undeclared find_package() latch onto a stray build tree recorded by a sibling
# sub-project's export(PACKAGE) (see the system-fallback path in
# therock_subproject_dep_provider.cmake). Gated on the same switch as the safe
# dependency provider so that disabling it restores full stock find_package()
# behavior.
string(APPEND _init_contents "if(THEROCK_USE_SAFE_DEPENDENCY_PROVIDER)\n")
string(APPEND _init_contents " set(CMAKE_FIND_USE_PACKAGE_REGISTRY OFF)\n")
string(APPEND _init_contents " set(CMAKE_FIND_USE_SYSTEM_PACKAGE_REGISTRY OFF)\n")
string(APPEND _init_contents " set(CMAKE_EXPORT_NO_PACKAGE_REGISTRY ON)\n")
string(APPEND _init_contents "endif()\n")
# Include dirs.
foreach(_private_include_dir ${_private_include_dirs})
if(THEROCK_VERBOSE)
message(STATUS " INCLUDE_DIR: ${_private_include_dir}")
endif()
string(APPEND _init_contents "include_directories(BEFORE \"${_private_include_dir}\")\n")
string(APPEND _init_contents "list(PREPEND CMAKE_REQUIRED_INCLUDES \"${_private_include_dir}\")\n")
string(APPEND _init_contents "list(APPEND THEROCK_SUPERPROJECT_INCLUDE_DIRS \"${_private_include_dir}\")\n")
endforeach()
# Link dirs.
foreach(_private_link_dir ${_private_link_dirs})
if(THEROCK_VERBOSE)
message(STATUS " LINK_DIR: ${_private_link_dir}")
endif()
# Make the link dir visible to CMake find_library.
string(APPEND _init_contents "list(APPEND CMAKE_LIBRARY_PATH \"${_private_link_dir}\")\n")
if(NOT MSVC)
# The normal way.
string(APPEND _init_contents "string(APPEND CMAKE_EXE_LINKER_FLAGS \" -L${_private_link_dir} -Wl,-rpath-link,${_private_link_dir}\")\n")
string(APPEND _init_contents "string(APPEND CMAKE_SHARED_LINKER_FLAGS \" -L${_private_link_dir} -Wl,-rpath-link,${_private_link_dir}\")\n")
elseif(_compiler_toolchain STREQUAL "amd-llvm" OR _compiler_toolchain STREQUAL "amd-hip")
# The Windows but using a clang-based toolchain way.
# Working around "lld-link: warning: ignoring unknown argument '-rpath-link'"
string(APPEND _init_contents "string(APPEND CMAKE_EXE_LINKER_FLAGS \" -L${_private_link_dir} \")\n")
string(APPEND _init_contents "string(APPEND CMAKE_SHARED_LINKER_FLAGS \" -L${_private_link_dir} \")\n")
else()
# The MSVC way.
string(APPEND _init_contents "string(APPEND CMAKE_EXE_LINKER_FLAGS \" /LIBPATH:${_private_link_dir}\")\n")
string(APPEND _init_contents "string(APPEND CMAKE_SHARED_LINKER_FLAGS \" /LIBPATH:${_private_link_dir}\")\n")
endif()
endforeach()
if(THEROCK_VERBOSE AND _private_pkg_config_dirs)
message(STATUS " PKG_CONFIG_DIRS: ${_private_pkg_config_dirs}")
endif()
string(APPEND _init_contents "set(THEROCK_PKG_CONFIG_DIRS \"@_private_pkg_config_dirs@\")\n")
string(APPEND _init_contents "${_compiler_toolchain_init_contents}")
# Enable reproducible builds on Windows. /Brepro makes the linker (both
# MSVC link.exe and lld-link) zero out timestamps in PE headers, producing
# deterministic output.
if(WIN32)
string(APPEND _init_contents "add_link_options(\"LINKER:/Brepro\")\n")
endif()
if(_dep_provider_file)
string(APPEND _init_contents "include(${_dep_provider_file})\n")
endif()
if(_pre_hook_path)
string(APPEND _init_contents "include(@_pre_hook_path@)\n")
endif()
string(APPEND _init_contents "set(THEROCK_USER_POST_HOOK)\n")
if(_post_hook_path)
string(APPEND _init_contents "set(THEROCK_USER_POST_HOOK \"@_post_hook_path@\")\n")
endif()
set(_global_post_include "${THEROCK_SOURCE_DIR}/cmake/therock_global_post_subproject.cmake")
string(APPEND _init_contents "cmake_language(DEFER CALL include \"@_global_post_include@\")\n")
foreach(_addl_cmake_include ${_cmake_includes})
if(NOT IS_ABSOLUTE)
find_path(_addl_cmake_include_path "${addl_cmake_include}" NO_CACHE NO_DEFAULT_PATH PATHS ${CMAKE_MODULE_PATH} REQUIRED)
cmake_path(ABSOLUTE_PATH _addl_cmake_include BASE_DIRECTORY "${_addl_cmake_include_path}")
endif()
string(APPEND _init_contents "include(\"${_addl_cmake_include}\")\n")
endforeach()
# Flag system: inject flag-controlled variables and defines.
_therock_get_flag_init_contents(_flag_init_contents "${target_name}")
string(APPEND _init_contents "${_flag_init_contents}")
file(CONFIGURE OUTPUT "${_cmake_project_init_file}" CONTENT "${_init_contents}" @ONLY ESCAPE_QUOTES)
list(APPEND _fprint_files "${_cmake_project_init_file}")
# Transform build and run deps from target form (i.e. 'ROCR-Runtime' to a dependency
# on the stage.stamp file). These are a dependency for configure.
set(_configure_dep_stamps)
_therock_cmake_subproject_deps_to_stamp(_configure_dep_stamps stage.stamp ${_build_deps} ${_runtime_deps})
# Target flags.
set(_all_option)
if(NOT _exclude_from_all)
set(_all_option "ALL")
endif()
# Detect whether the stage dir has been pre-built.
set(_prebuilt_file "${_stage_dir}.prebuilt")
list(APPEND _cmake_args "${${target_name}_CMAKE_ARGS}")
# Derive the CMAKE_BUILD_TYPE from either {project}_BUILD_TYPE or the global
# CMAKE_BUILD_TYPE.
set(_cmake_build_type "${${target_name}_BUILD_TYPE}")
if(NOT _cmake_build_type)
set(_cmake_build_type "${CMAKE_BUILD_TYPE}")
else()
message(STATUS " PROJECT SPECIFIC CMAKE_BUILD_TYPE=${_cmake_build_type}")
endif()
set(_fileset_tool "${THEROCK_SOURCE_DIR}/build_tools/fileset_tool.py")
list(APPEND _fprint_files "${_fileset_tool}")
_therock_cmake_subproject_get_stage_dirs(
_dist_source_dirs "${target_name}" ${_runtime_deps})
# Map THEROCK_VERBOSE to fileset_tool.py
set(_fileset_verbose_arg "")
if(THEROCK_VERBOSE)
set(_fileset_verbose_arg --verbose)
endif()
if(EXISTS "${_prebuilt_file}")
# If pre-built, just touch the stamp files, conditioned on the prebuilt
# marker file (which may just be a stamp file or may contain a unique hash
# for this part of the build).
message(STATUS " DISABLING BUILD: Marked as pre-built")
add_custom_command(
OUTPUT "${_configure_stamp_file}"
COMMAND "${CMAKE_COMMAND}" -E touch "${_configure_stamp_file}"
DEPENDS "${_prebuilt_file}"
)
add_custom_command(
OUTPUT "${_build_stamp_file}"
COMMAND "${CMAKE_COMMAND}" -E touch "${_build_stamp_file}"
DEPENDS "${_prebuilt_file}"
)
add_custom_command(
OUTPUT "${_stage_stamp_file}"
# Populate local dist directory with this+all transitive stage installs.
COMMAND "${Python3_EXECUTABLE}" "${_fileset_tool}" copy ${_fileset_verbose_arg} "${_dist_dir}" ${_dist_source_dirs}
COMMAND "${CMAKE_COMMAND}" -E touch "${_stage_stamp_file}"
DEPENDS
"${_prebuilt_file}"
"${_fileset_tool}"
)
else()
# Not pre-built: normal configure/build/stage install.
# configure target
if(THEROCK_VERBOSE)
message(STATUS " CONFIGURE_DEPENDS: ${_transitive_configure_depend_files} ")
endif()
set(_configure_comment_suffix " (in background)")
set(_terminal_option)
set(_build_terminal_option "USES_TERMINAL")
if("$ENV{THEROCK_INTERACTIVE}")
set(_terminal_option "USES_TERMINAL")
set(_configure_comment_suffix)
elseif(_build_pool)
if(THEROCK_VERBOSE)
message(STATUS " JOB_POOL: ${_build_pool}")
endif()
set(_build_terminal_option JOB_POOL "${_build_pool}")
set(_build_comment_suffix " (in background)")
endif()
set(_stage_destination_dir "${_stage_dir}")
if(_install_destination)
cmake_path(APPEND _stage_destination_dir "${_install_destination}")
endif()
set(_compile_commands_file "${PROJECT_BINARY_DIR}/compile_commands_fragment_${target_name}.json")
therock_subproject_log_command(_configure_log_prefix
LOG_FILE "${target_name}_configure.log"
LABEL "${target_name} configure"
OUTPUT_ON_FAILURE "${_output_on_failure}"
)
# Finger-print vital configure content.
cmake_path(RELATIVE_PATH _stage_dir BASE_DIRECTORY "${THEROCK_BINARY_DIR}" OUTPUT_VARIABLE _rel_stage_dir)
cmake_path(RELATIVE_PATH _stage_destination_dir BASE_DIRECTORY "${THEROCK_BINARY_DIR}" OUTPUT_VARIABLE _rel_stage_destination_dir)
cmake_path(RELATIVE_PATH _cmake_source_dir BASE_DIRECTORY "${THEROCK_SOURCE_DIR}" OUTPUT_VARIABLE _rel_cmake_source_dir)
list(APPEND _fprint_content
"CONFIGURE"
"CMAKE_BUILD_TYPE=${_cmake_build_type}"
"CMAKE_SOURCE_DIR=${_rel_cmake_source_dir}"
"STAGE_DIR=${_rel_stage_dir}"
"STAGE_DESTINATION_DIR=${_rel_stage_destination_dir}"
)
# Configure command.
add_custom_command(
OUTPUT "${_configure_stamp_file}"
COMMAND
${_configure_log_prefix}
"${CMAKE_COMMAND}" -E env ${_build_env_pairs} --
"${CMAKE_COMMAND}"
"-G${CMAKE_GENERATOR}"
"-B${_binary_dir}"
"-S${_cmake_source_dir}"
"-DCMAKE_BUILD_TYPE=${_cmake_build_type}"
"-DCMAKE_INSTALL_PREFIX=${_stage_destination_dir}"
"-DTHEROCK_STAGE_INSTALL_ROOT=${_stage_dir}"
"-DCMAKE_TOOLCHAIN_FILE=${_cmake_project_toolchain_file}"
"-DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=${_cmake_project_init_file}"
${_cmake_args}
# CMake doesn't always generate a compile_commands.json so touch one to keep
# the build graph sane.
COMMAND "${CMAKE_COMMAND}" -E touch "${_binary_dir}/compile_commands.json"
COMMAND "${CMAKE_COMMAND}" -E touch "${_configure_stamp_file}"
COMMAND "${CMAKE_COMMAND}" -E copy "${_binary_dir}/compile_commands.json" "${_compile_commands_file}"
WORKING_DIRECTORY "${_binary_dir}"
COMMENT "Configure sub-project ${target_name}${_configure_comment_suffix}"
${_terminal_option}
BYPRODUCTS
"${_binary_dir}/CMakeCache.txt"
"${_binary_dir}/cmake_install.cmake"
"${_compile_commands_file}"
DEPENDS
"${_cmake_source_dir}/CMakeLists.txt"
"${_cmake_project_toolchain_file}"
"${_cmake_project_init_file}"
"${_global_post_include}"
${_extra_depends}
${_dep_provider_file}
${_configure_dep_stamps}
${_pre_hook_path}
${_post_hook_path}
${_compiler_toolchain_addl_depends}
${_transitive_configure_depend_files}
# TODO: Have a mechanism for adding more depends for better rebuild ergonomics
)
add_custom_target(
"${target_name}+configure"
${_all_option}
DEPENDS "${_configure_stamp_file}"
)
add_dependencies("${target_name}" "${target_name}+configure")
if(NOT _no_merge_compile_commands)
set_property(GLOBAL APPEND PROPERTY THEROCK_SUBPROJECT_COMPILE_COMMANDS_FILES
"${_compile_commands_file}"
)
endif()
# build target.
therock_subproject_log_command(_build_log_prefix
LOG_FILE "${target_name}_build.log"
LABEL "${target_name}"
OUTPUT_ON_FAILURE "${_output_on_failure}"
)
add_custom_command(
OUTPUT "${_build_stamp_file}"
COMMAND
${_build_log_prefix}
"${CMAKE_COMMAND}" -E env ${_build_env_pairs} --
"${CMAKE_COMMAND}" "--build" "${_binary_dir}"
COMMAND "${CMAKE_COMMAND}" -E touch "${_build_stamp_file}"
WORKING_DIRECTORY "${_binary_dir}"
COMMENT "Building sub-project ${target_name}${_build_comment_suffix}"
${_build_terminal_option}
DEPENDS
"${_configure_stamp_file}"
${_sources}
)
add_custom_target(
"${target_name}+build"
${_all_option}
DEPENDS
"${_build_stamp_file}"
)
add_dependencies("${target_name}" "${target_name}+build")
# stage install target.
set(_install_strip_option)
if(THEROCK_SPLIT_DEBUG_INFO)
set(_install_strip_option "--strip")
endif()
# Set up install command(s) for optional components. If INSTALL_COMPONENTS is specified, run cmake
# --install once for each component, for some reason CMake doesn't support
# multiple --component flags in one call.
# example commands with 2 INSTALL_COMPONENTS:
# COMMAND;python;teatime.py;--log-timestamps;--label;
# ${target_name} ${_comp1} install;--interactive;path/logs/${target_name}_${comp1}_install.log;--;
# cmake;--install;build;--component;${_comp1};--strip;
# COMMAND;python;teatime.py;--log-timestamps;--label;
# ${target_name} ${comp2} install;--interactive;path/logs/${target_name}_${comp2}_install.log;--;
# cmake;--install;build;--component;${comp2};--strip
set(_optional_component_install_commands)
if(_optional_install_components)
foreach(_comp IN LISTS _optional_install_components)
therock_subproject_log_command(_install_log_prefix
LOG_FILE "${target_name}_${_comp}_install.log"
LABEL "${target_name} ${_comp} install"
# While useful for debugging, stage install logs are almost pure noise
# for interactive use.
OUTPUT_ON_FAILURE "${THEROCK_QUIET_INSTALL}"
)
# install component to stage directory.
list(APPEND _optional_component_install_commands
COMMAND ${_install_log_prefix} "${CMAKE_COMMAND}" --install "${_binary_dir}" --component "${_comp}" ${_install_strip_option}
)
endforeach()
endif()
# Setup prefix for default component.
therock_subproject_log_command(_install_log_prefix
LOG_FILE "${target_name}_install.log"
LABEL "${target_name} install"
# While useful for debugging, stage install logs are almost pure noise
# for interactive use.
OUTPUT_ON_FAILURE "${THEROCK_QUIET_INSTALL}"
)
add_custom_command(
OUTPUT "${_stage_stamp_file}"
# Install default (all target) to stage directory.
COMMAND ${_install_log_prefix} "${CMAKE_COMMAND}" --install "${_binary_dir}" ${_install_strip_option}
# Expand optional components _install command(s).
${_optional_component_install_commands}
# Populate local dist directory with this+all transitive stage installs.
COMMAND "${Python3_EXECUTABLE}" "${_fileset_tool}" copy ${_fileset_verbose_arg} "${_dist_dir}" ${_dist_source_dirs}
COMMAND "${CMAKE_COMMAND}" -E touch "${_stage_stamp_file}"
WORKING_DIRECTORY "${_binary_dir}"
COMMENT "Stage installing sub-project ${target_name}"
${_terminal_option}
DEPENDS
"${_build_stamp_file}"
"${_fileset_tool}"
)
endif() # Split between pre-built and build mode
add_custom_target(
"${target_name}+stage"
${_all_option}
DEPENDS
"${_stage_stamp_file}"
)
add_dependencies("${target_name}" "${target_name}+stage")
# Create a +dist target that can have dependencies added to it which will also
# populate combined artifacts and distributions. By default this does nothing,
# but when artifacts and distributions depend on the target, they will be
# added as dependencies here.
add_custom_target("${target_name}+dist")
# expunge target
add_custom_target(
"${target_name}+expunge"
COMMAND
${CMAKE_COMMAND} -E rm -rf "${_binary_dir}" "${_stage_dir}" "${_stamp_dir}" "${_dist_dir}"
)
add_dependencies(therock-expunge "${target_name}+expunge")
# Process fingerprints.
_therock_subproject_fprint_files(_fprint_content "${_fprint_files}")
set(_fprint)
if(_fprint_is_valid)
string(SHA256 _fprint "${_fprint_content}")
endif()
set_target_properties("${target_name}" PROPERTIES
THEROCK_FPRINT "${_fprint}"
)
if(THEROCK_VERBOSE)
message(STATUS " FPRINT = ${_fprint}")
message(STATUS " FPRINT CONTENT = ${_fprint_content}")
endif()
endfunction()
# therock_cmake_subproject_build_test
# Defines build-tree tests for a CMake subproject. Tests are run from the
# subproject build directory after its build stamp is up to date. They are not
# part of ALL and are reached through build-test-${target_name} and the
# build-tests aggregate target when testing is enabled.
function(therock_cmake_subproject_build_test target_name)
if(NOT THEROCK_BUILD_TESTING)
return()
endif()
_therock_assert_is_cmake_subproject("${target_name}")
get_target_property(_binary_dir "${target_name}" THEROCK_BINARY_DIR)
get_target_property(_output_on_failure "${target_name}" THEROCK_OUTPUT_ON_FAILURE)
get_target_property(_prefix_dir "${target_name}" THEROCK_PREFIX_DIR)
get_target_property(_stamp_dir "${target_name}" THEROCK_STAMP_DIR)
get_target_property(_stage_dir "${target_name}" THEROCK_STAGE_DIR)
# Match the subproject prebuilt behavior: when the stage is imported from
# artifacts, there is no local build tree to test.
set(_prebuilt_file "${_stage_dir}.prebuilt")
if(EXISTS "${_prebuilt_file}")
return()
endif()
if(TARGET "build-test-${target_name}")
message(FATAL_ERROR "Build tests already declared for subproject '${target_name}'")
endif()
if(NOT ARGN)
message(FATAL_ERROR "Build tests for '${target_name}' require at least one COMMAND")
endif()
set(_command_count 0)
set(_current_command)
set(_expect_command TRUE)
foreach(_arg IN LISTS ARGN)
if(_arg STREQUAL "COMMAND")
if(_current_command)
math(EXPR _command_count "${_command_count} + 1")
set("_test_command_${_command_count}" "${_current_command}")
set(_current_command)
elseif(NOT _expect_command)
message(FATAL_ERROR "Empty COMMAND in build tests for '${target_name}'")
endif()
set(_expect_command FALSE)
else()
if(_expect_command)
message(FATAL_ERROR
"Expected COMMAND in therock_cmake_subproject_build_test(${target_name} ...)")
endif()
list(APPEND _current_command "${_arg}")
endif()
endforeach()
if(_current_command)
math(EXPR _command_count "${_command_count} + 1")
set("_test_command_${_command_count}" "${_current_command}")
elseif(NOT _command_count)
message(FATAL_ERROR "Build tests for '${target_name}' require at least one non-empty COMMAND")
elseif(NOT _expect_command)
message(FATAL_ERROR "Empty COMMAND in build tests for '${target_name}'")
endif()
# Generate a runner script that executes all test commands independently,
# so a failure in one does not prevent the others from running.
_therock_cmake_subproject_build_env_pairs(_build_env_pairs)
set(_runner_script "${_prefix_dir}/build-test-runner.cmake")
set(_runner_content "set(_any_failed FALSE)\n\n")
foreach(_command_index RANGE 1 ${_command_count})
# Use numbered labels/files only when there are multiple commands;
# a single command gets the plain name without a suffix.
set(_log_file "${target_name}_build_test.log")
set(_log_label "${target_name} build-test")
if(_command_count GREATER 1)
set(_log_file "${target_name}_build_test_${_command_index}.log")
set(_log_label "${target_name} build-test ${_command_index}")
endif()
therock_subproject_log_command(_test_log_prefix
LOG_FILE "${_log_file}"
LABEL "${_log_label}"
OUTPUT_ON_FAILURE "${_output_on_failure}"
)
set(_test_command_var "_test_command_${_command_index}")
set(_full_cmd
${_test_log_prefix}
"${CMAKE_COMMAND}" -E env ${_build_env_pairs} --
${${_test_command_var}}
)
# Serialize the command list into a quoted string for the script.
set(_cmd_str "")
foreach(_arg IN LISTS _full_cmd)
string(APPEND _cmd_str " \"${_arg}\"")
endforeach()
if(_command_index GREATER 1)
string(APPEND _runner_content "message(STATUS \"\")\n")
endif()
string(APPEND _runner_content "message(STATUS \"----------------------------------------\")\n")
string(APPEND _runner_content "message(STATUS \"Running: ${_log_label}\")\n")
string(APPEND _runner_content "message(STATUS \"----------------------------------------\")\n")
string(APPEND _runner_content "execute_process(\n")
string(APPEND _runner_content " COMMAND${_cmd_str}\n")
string(APPEND _runner_content " WORKING_DIRECTORY \"${_binary_dir}\"\n")
string(APPEND _runner_content " RESULT_VARIABLE _rc${_command_index}\n")
string(APPEND _runner_content ")\n")
string(APPEND _runner_content "if(_rc${_command_index})\n")
string(APPEND _runner_content " set(_any_failed TRUE)\n")
string(APPEND _runner_content "endif()\n\n")
endforeach()
string(APPEND _runner_content "message(STATUS \"\")\n")
string(APPEND _runner_content "message(STATUS \"========================================\")\n")
string(APPEND _runner_content "message(STATUS \"BUILD TEST SUMMARY for ${target_name}\")\n")
string(APPEND _runner_content "message(STATUS \"========================================\")\n")
foreach(_command_index RANGE 1 ${_command_count})
set(_log_label "${target_name} build-test")
if(_command_count GREATER 1)
set(_log_label "${target_name} build-test ${_command_index}")
endif()
string(APPEND _runner_content "if(_rc${_command_index})\n")
string(APPEND _runner_content " message(STATUS \" ${_log_label}: FAILED (exit code \${_rc${_command_index}})\")\n")
string(APPEND _runner_content "else()\n")
string(APPEND _runner_content " message(STATUS \" ${_log_label}: PASSED\")\n")
string(APPEND _runner_content "endif()\n")
endforeach()
string(APPEND _runner_content "message(STATUS \"========================================\")\n")
string(APPEND _runner_content "if(_any_failed)\n")
string(APPEND _runner_content " message(FATAL_ERROR \"One or more build test commands failed for ${target_name}\")\n")
string(APPEND _runner_content "else()\n")
string(APPEND _runner_content " message(STATUS \"All build tests passed for ${target_name}\")\n")
string(APPEND _runner_content "endif()\n")
file(GENERATE OUTPUT "${_runner_script}" CONTENT "${_runner_content}")
set(_build_stamp_file "${_stamp_dir}/build.stamp")
set(_build_test_stamp_file "${_stamp_dir}/build-test.stamp")
add_custom_command(
OUTPUT "${_build_test_stamp_file}"
COMMAND "${CMAKE_COMMAND}" -P "${_runner_script}"
COMMAND "${CMAKE_COMMAND}" -E touch "${_build_test_stamp_file}"
WORKING_DIRECTORY "${_binary_dir}"
COMMENT "Running build tests for sub-project ${target_name}"
USES_TERMINAL
DEPENDS
"${_build_stamp_file}"
VERBATIM
)
add_custom_target("build-test-${target_name}"
DEPENDS "${_build_test_stamp_file}"
)
if(TARGET therock-build-tests)
add_dependencies(therock-build-tests "build-test-${target_name}")
endif()
endfunction()
# therock_cmake_subproject_glob_c_sources
# Adds C/C++ sources from given project subdirectories to the list of sources for
# a sub-project. This allows the super-project build system to know when to
# re-trigger the build step of the sub-project. There are many issues with globs
# in CMake, but as an ergonomic win, this is deemed an acceptable compromise
# to a large degree of explicitness.
function(therock_cmake_subproject_glob_c_sources target_name)
cmake_parse_arguments(
PARSE_ARGV 1 ARG
""
""
"SUBDIRS"
)
get_target_property(_project_source_dir "${target_name}" THEROCK_EXTERNAL_SOURCE_DIR)
set(_globs)
foreach(_subdir ${ARG_SUBDIRS})
set(_s "${_project_source_dir}/${_subdir}")
list(APPEND _globs
"${_s}/*.h"
"${_s}/*.hh"
"${_s}/*.hpp"
"${_s}/*.inc"
"${_s}/*.cc"
"${_s}/*.cpp"
"${_s}/*.c"
)
endforeach()
file(GLOB_RECURSE _files LIST_DIRECTORIES FALSE
CONFIGURE_DEPENDS
${_globs}
)
target_sources("${target_name}" PRIVATE ${_files})
endfunction()
# Gets the dist/ directory for a sub-project.
function(therock_cmake_subproject_dist_dir out_var target_name)
_therock_assert_is_cmake_subproject("${target_name}")
get_target_property(_dir "${target_name}" THEROCK_DIST_DIR)
set("${out_var}" "${_dir}" PARENT_SCOPE)
endfunction()
# Merges all compile_commands.json files and exports them.
function(therock_subproject_merge_compile_commands)
if(NOT CMAKE_EXPORT_COMPILE_COMMANDS)
return()
endif()
message(STATUS "Setting up compile_commands.json export")
get_property(_fragment_files GLOBAL PROPERTY THEROCK_SUBPROJECT_COMPILE_COMMANDS_FILES)
if(EXISTS "${CMAKE_BINARY_DIR}/compile_commands.json")
list(APPEND _fragment_files "${CMAKE_BINARY_DIR}/compile_commands.json")
endif()
set(_merged_file "${PROJECT_BINARY_DIR}/compile_commands_merged.json")
set(_merged_file_in_source_dir "${PROJECT_SOURCE_DIR}/compile_commands.json")
set(_merge_script "${THEROCK_SOURCE_DIR}/build_tools/merge_compile_commands.py")
add_custom_command(
OUTPUT "${_merged_file}"
COMMENT "Merging compile_commands.json"
COMMAND "${Python3_EXECUTABLE}"
"${_merge_script}" "${_merged_file}" ${_fragment_files}
COMMAND "${CMAKE_COMMAND}" -E copy "${_merged_file}" "${_merged_file_in_source_dir}"
BYPRODUCTS
"${_merged_file_in_source_dir}"
DEPENDS
"${_merge_script}"
${_fragment_files}
)
add_custom_target(therock_merged_compile_commands ALL
DEPENDS
"${_merged_file}"
"${_merge_script}"
${_fragment_files}
)
endfunction()
function(_therock_assert_is_cmake_subproject target_name)
# Make sure it is a sub-project.
get_target_property(_is_subproject "${target_name}" THEROCK_SUBPROJECT)
if(NOT _is_subproject STREQUAL "cmake")
message(FATAL_ERROR "Target ${target_name} is not a sub-project")
endif()
endfunction()
function(_therock_cmake_subproject_build_env_pairs out_var)
set(_build_env_pairs)
# All project dependencies are managed within the super-project so we don't
# want subprojects reaching outside of the sandbox and building against
# uncontrolled (and likely incompatible) sources.
#
# These environment variables have been used by some subprojects to discover
# preexisting ROCm/HIP SDK installs. If detected, these subprojects then do
# things like:
# * Append `${HIP_PATH}/cmake` to `CMAKE_MODULE_PATH`
# * Use `${HIP_PATH}` as a hint for `find_package()` calls
# We unset both the CMake and environment variables with these names.
# See also https://github.com/ROCm/TheRock/issues/670.
list(APPEND _build_env_pairs "--unset=ROCM_PATH")
list(APPEND _build_env_pairs "--unset=ROCM_DIR")
list(APPEND _build_env_pairs "--unset=HIP_PATH")
list(APPEND _build_env_pairs "--unset=HIP_DIR")
set("${out_var}" "${_build_env_pairs}" PARENT_SCOPE)
endfunction()
# Builds a CMake language fragment to set up a dependency provider such that
# it handles super-project provided dependencies locally.
function(_therock_cmake_subproject_setup_deps out_contents out_provided dep_dir_property)
string(APPEND CMAKE_MESSAGE_INDENT " ")
set(_contents "${${out_contents}}")
set(_already_provided ${${out_provided}})
foreach(dep_target ${ARGN})
_therock_assert_is_cmake_subproject("${dep_target}")
get_target_property(_provides "${dep_target}" THEROCK_PROVIDE_PACKAGES)
if(_provides)
foreach(_package_name ${_provides})
if(_package_name IN_LIST _already_provided)
continue()
endif()
list(APPEND _already_provided "${_package_name}")
get_target_property(_dep_dir "${dep_target}" "${dep_dir_property}")
set(_relpath_name THEROCK_PACKAGE_RELPATH_${_package_name})
get_target_property(_relpath "${dep_target}" ${_relpath_name})
if(NOT _dep_dir OR NOT _relpath)
message(FATAL_ERROR "Missing package info props for ${_package_name} on ${dep_target}: '${_dep_dir}' ${_relpath_name}='${_relpath}'")
endif()
set(_find_package_path "${_dep_dir}")
cmake_path(APPEND _find_package_path "${_relpath}")
if(THEROCK_VERBOSE)
message(STATUS "INJECT ${_package_name} = ${_find_package_path} (from ${dep_target})")
endif()
string(APPEND _contents "set(THEROCK_PACKAGE_DIR_${_package_name} \"${_find_package_path}\")\n")
string(APPEND _contents "list(APPEND THEROCK_PROVIDED_PACKAGES ${_package_name})\n")
# Now write a trampoline package config file into the prefix so that normal
# prefix based find_package() will resolve properly.
string(TOLOWER "${_package_name}" _package_name_lower)
set(_config_file "${_prefix_dir}/${_package_name}Config.cmake")
configure_file(
"${THEROCK_SOURCE_DIR}/cmake/templates/find_config.tmpl.cmake"
"${_config_file}" @ONLY
)
set(_version_file "${_prefix_dir}/${_package_name}ConfigVersion.cmake")
configure_file(
"${THEROCK_SOURCE_DIR}/cmake/templates/find_config_version.tmpl.cmake"
"${_version_file}" @ONLY
)
endforeach()
endif()
endforeach()
set("${out_contents}" "${_contents}" PARENT_SCOPE)
set("${out_provided}" "${_already_provided}" PARENT_SCOPE)
endfunction()
# Gets the staging install directories for a list of subproject deps.
function(_therock_cmake_subproject_get_stage_dirs out_dirs)
set(_dirs)
foreach(target_name ${ARGN})
get_target_property(_stage_dir "${target_name}" THEROCK_STAGE_DIR)
if(NOT _stage_dir)
message(FATAL_ERROR "Sub-project target ${target_name} does not have a stage install dir")
endif()
list(APPEND _dirs "${_stage_dir}")
endforeach()
set(${out_dirs} "${_dirs}" PARENT_SCOPE)
endfunction()
# Transforms a list of sub-project targets to corresponding stamp files of
# `stamp_name`. These are the actual build system deps that are encoded in the
# commands (whereas the target names are just for humans).
function(_therock_cmake_subproject_deps_to_stamp out_stamp_files stamp_name)
set(_stamp_files ${${out_stamp_files}})
foreach(target_name ${ARGN})
_therock_assert_is_cmake_subproject("${target_name}")
get_target_property(_stamp_dir "${target_name}" THEROCK_STAMP_DIR)
if(NOT _stamp_dir)
message(FATAL_ERROR "Sub-project is missing a stamp dir: ${target_name}")
endif()
list(APPEND _stamp_files "${_stamp_dir}/${stamp_name}")
endforeach()
set(${out_stamp_files} "${_stamp_files}" PARENT_SCOPE)
endfunction()
# For a list of targets, resolves several transitive properties relavent to build
# and runtime deps.
function(_therock_cmake_subproject_collect_build_deps
out_include_dirs
out_link_dirs
out_program_dirs
out_pkg_config_dirs
out_transitive_configure_depend_files)
set(_include_dirs)
set(_link_dirs)
set(_program_dirs)
set(_pkg_config_dirs)
set(_transitive_configure_depend_files)
foreach(target_name ${ARGN})
_therock_assert_is_cmake_subproject("${target_name}")
get_target_property(_declared_configure_depend_files "${target_name}" THEROCK_INTERFACE_CONFIGURE_DEPEND_FILES)
list(APPEND _transitive_configure_depend_files ${_declared_configure_depend_files})
get_target_property(_stamp_dir "${target_name}" THEROCK_STAMP_DIR)
# Include dirs.
get_target_property(_include_dir "${target_name}" THEROCK_INTERFACE_INCLUDE_DIRS)
list(APPEND _include_dirs ${_include_dir})
# Link dirs.
get_target_property(_link_dir "${target_name}" THEROCK_INTERFACE_LINK_DIRS)
list(APPEND _link_dirs ${_link_dir})
# Depend on stage installation.
get_target_property(_program_dir "${target_name}" THEROCK_INTERFACE_PROGRAM_DIRS)
if(_program_dir)
list(APPEND _program_dirs ${_program_dir})
list(APPEND _transitive_configure_depend_files "${_stamp_dir}/stage.stamp")
endif()
# PkgConfig dirs.
get_target_property(_pkg_config_dir "${target_name}" THEROCK_INTERFACE_PKG_CONFIG_DIRS)
if(_pkg_config_dir)
list(APPEND _pkg_config_dirs ${_pkg_config_dir})
endif()
endforeach()
set("${out_include_dirs}" "${_include_dirs}" PARENT_SCOPE)
set("${out_link_dirs}" "${_link_dirs}" PARENT_SCOPE)
set("${out_program_dirs}" "${_program_dirs}" PARENT_SCOPE)
set("${out_pkg_config_dirs}" "${_pkg_config_dirs}" PARENT_SCOPE)
set("${out_transitive_configure_depend_files}" "${_transitive_configure_depend_files}" PARENT_SCOPE)
endfunction()
# For a list of targets, resolves transitive properties relavent only to runtime
# deps.
function(_therock_cmake_subproject_collect_runtime_deps
out_install_rpath_dirs
out_transitive_deps)
set(_install_rpath_dirs)
set(_transitive_deps)
foreach(target_name ${ARGN})
_therock_assert_is_cmake_subproject("${target_name}")
# Transitive runtime target deps.
get_target_property(_deps "${target_name}" THEROCK_RUNTIME_DEPS)
list(APPEND _transitive_deps ${_deps} ${target_name})
# RPATH dirs.
get_target_property(_install_rpath_dir "${target_name}" THEROCK_INTERFACE_INSTALL_RPATH_DIRS)
if(_install_rpath_dir)
list(APPEND _install_rpath_dirs ${_install_rpath_dir})
endif()
endforeach()
set("${out_install_rpath_dirs}" "${_install_rpath_dirs}" PARENT_SCOPE)
set("${out_transitive_deps}" "${_transitive_deps}" PARENT_SCOPE)
endfunction()
# Transforms a list to be absolute paths if not already.
function(_therock_cmake_subproject_absolutize list_var relative_to)
set(_dirs "${${list_var}}")
set(_abs_dirs)
foreach(_dir ${_dirs})
cmake_path(ABSOLUTE_PATH _dir BASE_DIRECTORY "${relative_to}" NORMALIZE)
list(APPEND _abs_dirs "${_dir}")
endforeach()
set("${list_var}" "${_abs_dirs}" PARENT_SCOPE)
endfunction()
# Filters a list of AMDGPU targets against the project's EXCLUDE_TARGET_PROJECTS
# entries from therock_add_amdgpu_target. Silent — callable before the subproject
# target exists (e.g. to decide whether to declare it at all). For the warning
# variant used during subproject activation, see _therock_filter_project_gpu_targets.
function(therock_filter_amdgpu_targets out_var project_name)
set(_filtered ${ARGN})
get_property(_excludes GLOBAL PROPERTY "THEROCK_AMDGPU_PROJECT_TARGET_EXCLUDES_${project_name}")
list(REMOVE_ITEM _filtered ${_excludes})
set("${out_var}" "${_filtered}" PARENT_SCOPE)
endfunction()
# Filters the target's THEROCK_AMDGPU_TARGETS property based on global settings for the project.
function(_therock_filter_project_gpu_targets out_var target_name)
get_property(_excludes GLOBAL PROPERTY "THEROCK_AMDGPU_PROJECT_TARGET_EXCLUDES_${target_name}")
get_target_property(_gpu_targets "${target_name}" THEROCK_AMDGPU_TARGETS)
set(_filtered ${_gpu_targets})
if(_excludes)
foreach(exclude IN LISTS _excludes)
if("${exclude}" IN_LIST _filtered)
message(WARNING
"Excluding support for ${exclude} in ${target_name} because it was "
"manually marked for exclusion in therock_amdgpu_targets.cmake. This "
"warning should never be issued for production/supported gfx targets.")
list(REMOVE_ITEM _filtered "${exclude}")
endif()
endforeach()
endif()
if(NOT _filtered)
get_target_property(_default_gpu_targets "${target_name}" THEROCK_DEFAULT_GPU_TARGETS)
if(_default_gpu_targets)
set(_filtered ${_default_gpu_targets})
message(WARNING
"Project ${target_name} cannot build with no gpu targets but was "
"instructed to do so. Overriding to the project-specific default ${_filtered}. "
"This message should never appear for production/supported gfx targets."
)
endif()
endif()
set("${out_var}" "${_filtered}" PARENT_SCOPE)
endfunction()
# Writes a toolchain file and sets variables in the parent scope of the
# therock_cmake_subproject_activate prior to initializing sub-project arguments
# to configure the toolchain based on the user-provided COMPILER_TOOLCHAIN.
#
# Toolchain menemonics:
# * amd-llvm: Locally build compiler/amd-llvm toolchain as a standalone
# tool. While this can compiler HIP code, it does not natively have access
# to a ROCM installation for headers, etc.
# * amd-hip: Extends the amd-llvm toolchain to also depend on HIP, making
# it ready to use to compile HIP code.
function(_therock_cmake_subproject_setup_toolchain
target_name compiler_toolchain toolchain_file)
string(APPEND CMAKE_MESSAGE_INDENT " ")
set(_build_env_pairs "${_build_env_pairs}")
set(_toolchain_contents)
get_target_property(_disable_amdgpu_targets "${target_name}" THEROCK_DISABLE_AMDGPU_TARGETS)
set(_filtered_gpu_targets)
if(NOT _disable_amdgpu_targets)
_therock_filter_project_gpu_targets(_filtered_gpu_targets "${target_name}")
# Check for sentinel values indicating no targets were configured.
# This is a lazy error - we only fail when actually building a subproject
# that needs targets, not at top-level configuration time.
if("${_filtered_gpu_targets}" MATCHES "-NOTFOUND$")
if("${_filtered_gpu_targets}" STREQUAL "THEROCK_AMDGPU_TARGETS-NOTFOUND")
message(FATAL_ERROR
"Subproject ${target_name} requires AMDGPU targets but none were selected. "
"Set THEROCK_AMDGPU_FAMILIES or THEROCK_AMDGPU_TARGETS.")
elseif("${_filtered_gpu_targets}" STREQUAL "THEROCK_DIST_AMDGPU_TARGETS-NOTFOUND")
message(FATAL_ERROR
"Subproject ${target_name} requires dist AMDGPU targets but none were set. "
"Set THEROCK_DIST_AMDGPU_FAMILIES.")
elseif("${_filtered_gpu_targets}" STREQUAL "THEROCK_TEST_AMDGPU_TARGETS-NOTFOUND")
message(FATAL_ERROR
"Subproject ${target_name} requires test AMDGPU targets but none are available. "
"This is unexpected since test targets default to all registered targets.")
else()
message(FATAL_ERROR
"Internal error: Subproject ${target_name} received unexpected NOTFOUND sentinel "
"'${_filtered_gpu_targets}'. This is a bug in TheRock's AMDGPU target handling.")
endif()
endif()
# TODO: AMDGPU_TARGETS is being deprecated. For now we set both.
string(APPEND _toolchain_contents "set(AMDGPU_TARGETS @_filtered_gpu_targets@ CACHE STRING \"From super-project\" FORCE)\n")
string(APPEND _toolchain_contents "set(GPU_TARGETS @_filtered_gpu_targets@ CACHE STRING \"From super-project\" FORCE)\n")
string(APPEND _toolchain_contents "set(CMAKE_HIP_ARCHITECTURES @_filtered_gpu_targets@ CACHE STRING \"From super-project\" FORCE)\n")
endif()
# General settings applicable to all toolchains.
string(APPEND _toolchain_contents "set(CMAKE_EXPORT_COMPILE_COMMANDS ON)\n")
string(APPEND _toolchain_contents "set(CMAKE_INSTALL_LIBDIR @CMAKE_INSTALL_LIBDIR@)\n")
string(APPEND _toolchain_contents "set(CMAKE_PLATFORM_NO_VERSIONED_SONAME @CMAKE_PLATFORM_NO_VERSIONED_SONAME@)\n")
# Propagate super-project flags to the sub-project by default.
string(APPEND _toolchain_contents "set(CMAKE_C_COMPILER \"@CMAKE_C_COMPILER@\")\n")
string(APPEND _toolchain_contents "set(CMAKE_CXX_COMPILER \"@CMAKE_CXX_COMPILER@\")\n")
string(APPEND _toolchain_contents "set(CMAKE_LINKER \"@CMAKE_LINKER@\")\n")
if(WIN32)
string(APPEND _toolchain_contents "set(CMAKE_RC_COMPILER \"@CMAKE_RC_COMPILER@\")\n")
string(APPEND _toolchain_contents "set(CMAKE_MT \"@CMAKE_MT@\")\n")
endif()
string(APPEND _toolchain_contents "set(CMAKE_C_COMPILER_LAUNCHER \"@CMAKE_C_COMPILER_LAUNCHER@\")\n")
string(APPEND _toolchain_contents "set(CMAKE_CXX_COMPILER_LAUNCHER \"@CMAKE_CXX_COMPILER_LAUNCHER@\")\n")
string(APPEND _toolchain_contents "set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT \"@CMAKE_MSVC_DEBUG_INFORMATION_FORMAT@\")\n")
if(MSVC AND compiler_toolchain)
# The system compiler and the toolchain compiler are incompatible, so we
# define flags from scratch for the toolchain compiler.
#
# Each ROCm project typically has its own `toolchain-windows.cmake` file
# that we are bypassing here. Some projects additionally set platform or
# configuration-specific options in `rmake.py`. If any flags are load
# bearing we can either add them to all projects or source those flags from
# the projects themselves more locally.
string(APPEND _toolchain_contents "set(CMAKE_C_FLAGS_INIT )\n")
string(APPEND _toolchain_contents "set(CMAKE_CXX_FLAGS_INIT \"-DWIN32 -DWIN32_LEAN_AND_MEAN -D_CRT_SECURE_NO_WARNINGS -DNOMINMAX -fms-extensions -fms-compatibility -D_ENABLE_EXTENDED_ALIGNED_STORAGE \")\n")
string(APPEND _toolchain_contents "set(CMAKE_EXE_LINKER_FLAGS_INIT )\n")
string(APPEND _toolchain_contents "set(CMAKE_SHARED_LINKER_FLAGS_INIT )\n")
else()
# The system compiler and the toolchain compiler are compatible, so we can
# simply forward flags from the system compiler to the toolchain compiler.
string(APPEND _toolchain_contents "set(CMAKE_C_FLAGS_INIT \"@CMAKE_C_FLAGS@\")\n")
string(APPEND _toolchain_contents "set(CMAKE_CXX_FLAGS_INIT \"@CMAKE_CXX_FLAGS@\")\n")
string(APPEND _toolchain_contents "set(CMAKE_EXE_LINKER_FLAGS_INIT \"@CMAKE_EXE_LINKER_FLAGS@\")\n")
string(APPEND _toolchain_contents "set(CMAKE_SHARED_LINKER_FLAGS_INIT \"@CMAKE_SHARED_LINKER_FLAGS@\")\n")
endif()
# Customize debug info generation.
if(THEROCK_MINIMAL_DEBUG_INFO)
# System toolchain can be either MSVC or another system compiler.
if(MSVC AND NOT compiler_toolchain)
# Set MSVC style debug options.
# TODO: For now, just set the default.
elseif((NOT compiler_toolchain AND (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU"))
OR compiler_toolchain)
# If the system compiler and GCC/clang or an explicit toolchain (which are all
# LLVM based).
string(APPEND _toolchain_contents "string(APPEND CMAKE_CXX_FLAGS_DEBUG \" -g1 -gz\")\n")
string(APPEND _toolchain_contents "string(APPEND CMAKE_CXX_FLAGS_RELWITHDEBINFO \" -g1 -gz\")\n")
string(APPEND _toolchain_contents "string(APPEND CMAKE_C_FLAGS_DEBUG \" -g1 -gz\")\n")
string(APPEND _toolchain_contents "string(APPEND CMAKE_C_FLAGS_RELWITHDEBINFO \" -g1 -gz\")\n")
else()
message(WARNING "Cannot setup THEROCK_MINIMAL_DEBUG_INFO mode for unknown compiler")
endif()
endif()
# Configure sanitizer.
set(_sanitizer_stanza)
set(_sanitizer_enabled)
if(NOT compiler_toolchain)
# Make any additional customizations if no toolchain specified.
therock_sanitizer_configure(
_sanitizer_stanza
_sanitizer_enabled
"${CMAKE_CXX_COMPILER}"
"${compiler_toolchain}"
"${target_name}")
elseif(compiler_toolchain STREQUAL "amd-llvm" OR compiler_toolchain STREQUAL "amd-hip")
# The "amd-llvm" and "amd-hip" toolchains are configured very similarly so
# we commingle them, but they are different:
# "amd-llvm": Just the base LLVM compiler and device libraries. This
# doesn't know anything about hip (i.e. it doesn't have hipconfig, etc).
# "amd-hip": Superset of "amd-llvm" which also includes hipcc, hip headers,
# and hip version info. This has hipconfig in it.
# The main difference is that for "amd-llvm", we derive the configuration from
# the amd-llvm project's dist/ tree. And for "amd-hip", from the hip-clr
# project (which has runtime dependencies on the underlying toolchain).
if(compiler_toolchain STREQUAL "amd-hip")
set(_toolchain_subproject "hip-clr")
else()
set(_toolchain_subproject "amd-llvm")
endif()
_therock_assert_is_cmake_subproject("${_toolchain_subproject}")
get_target_property(_amd_llvm_dist_dir "${_toolchain_subproject}" THEROCK_DIST_DIR)
get_target_property(_amd_llvm_stamp_dir "${_toolchain_subproject}" THEROCK_STAMP_DIR)
# Add a dependency on the toolchain's dist
set(AMD_LLVM_C_COMPILER "${_amd_llvm_dist_dir}/lib/llvm/bin/clang${CMAKE_EXECUTABLE_SUFFIX}")
set(AMD_LLVM_CXX_COMPILER "${_amd_llvm_dist_dir}/lib/llvm/bin/clang++${CMAKE_EXECUTABLE_SUFFIX}")
set(AMD_LLVM_LINKER "${_amd_llvm_dist_dir}/lib/llvm/bin/lld${CMAKE_EXECUTABLE_SUFFIX}")
set(_amd_llvm_cxx_flags_spaces )
string(JOIN " " _amd_llvm_cxx_flags_spaces ${THEROCK_AMD_LLVM_DEFAULT_CXX_FLAGS})
list(APPEND _compiler_toolchain_addl_depends "${_amd_llvm_stamp_dir}/stage.stamp")
# We inject a toolchain root into the subproject so that magic overrides can
# use it (i.e. for old projects that require path munging, etc).
string(APPEND _toolchain_contents "set(THEROCK_TOOLCHAIN_ROOT \"${_amd_llvm_dist_dir}\")\n")
string(APPEND _toolchain_contents "set(CMAKE_C_COMPILER \"@AMD_LLVM_C_COMPILER@\")\n")
string(APPEND _toolchain_contents "set(CMAKE_CXX_COMPILER \"@AMD_LLVM_CXX_COMPILER@\")\n")
string(APPEND _toolchain_contents "set(CMAKE_LINKER \"@AMD_LLVM_LINKER@\")\n")
# Explicitly set clang's resource directory using the toolchain path rather
# than letting clang auto-detect it. On Windows CI, B:\ is a volume mount
# to C:\{GUID}\ and clang resolves its binary path through the mount when
# computing the resource dir. This embeds the GUID in include paths, which
# defeats ccache. Passing -resource-dir with the unresolved path avoids this.
string(APPEND _toolchain_contents "file(GLOB _therock_clang_resource_dirs \"@_amd_llvm_dist_dir@/lib/llvm/lib/clang/*\")\n")
string(APPEND _toolchain_contents "list(GET _therock_clang_resource_dirs 0 _therock_clang_resource_dir)\n")
string(APPEND _toolchain_contents "string(APPEND CMAKE_C_FLAGS_INIT \" -resource-dir \${_therock_clang_resource_dir}\")\n")
string(APPEND _toolchain_contents "string(APPEND CMAKE_CXX_FLAGS_INIT \" -resource-dir \${_therock_clang_resource_dir}\")\n")
string(APPEND _toolchain_contents "string(APPEND CMAKE_CXX_FLAGS_INIT \" ${_amd_llvm_cxx_flags_spaces}\")\n")
therock_sanitizer_configure(
_sanitizer_stanza
_sanitizer_enabled
"${AMD_LLVM_CXX_COMPILER}"
"${compiler_toolchain}"
"${target_name}")
if(THEROCK_VERBOSE)
string(JOIN " " _filtered_gpu_targets_spaces ${_filtered_gpu_targets})
message(STATUS "Compiler toolchain ${compiler_toolchain}:")
string(APPEND CMAKE_MESSAGE_INDENT " ")
message(STATUS "CMAKE_C_COMPILER = ${AMD_LLVM_C_COMPILER}")
message(STATUS "CMAKE_CXX_COMPILER = ${AMD_LLVM_CXX_COMPILER}")
message(STATUS "CMAKE_LINKER = ${AMD_LLVM_LINKER}")
message(STATUS "GPU_TARGETS = ${_filtered_gpu_targets_spaces}")
if(_sanitizer_enabled)
message(STATUS "SANITIZER = ${_sanitizer_enabled}")
endif()
endif()
else()
message(FATAL_ERROR "Unsupported COMPILER_TOOLCHAIN = ${compiler_toolchain} (supported: 'amd-llvm' or none)")
endif()
# Configure additional HIP dependencies.
if (compiler_toolchain STREQUAL "amd-hip")
_therock_assert_is_cmake_subproject("hip-clr")
get_target_property(_hip_dist_dir hip-clr THEROCK_DIST_DIR)
get_target_property(_hip_stamp_dir hip-clr THEROCK_STAMP_DIR)
# Add a dependency on HIP's stamp.
set(_amd_llvm_device_lib_path "${_amd_llvm_dist_dir}/lib/llvm/amdgcn/bitcode")
list(APPEND _compiler_toolchain_addl_depends "${_hip_stamp_dir}/stage.stamp")
string(APPEND _toolchain_contents "string(APPEND CMAKE_CXX_FLAGS_INIT \" --hip-path=@_hip_dist_dir@\")\n")
string(APPEND _toolchain_contents "string(APPEND CMAKE_CXX_FLAGS_INIT \" --hip-device-lib-path=@_amd_llvm_device_lib_path@\")\n")
if(THEROCK_VERBOSE)
message(STATUS "HIP_DIR = ${_hip_dist_dir}")
endif()
endif()
string(APPEND _toolchain_contents "${_sanitizer_stanza}")
set(_compiler_toolchain_addl_depends "${_compiler_toolchain_addl_depends}" PARENT_SCOPE)
set(_compiler_toolchain_init_contents "${_compiler_toolchain_init_contents}" PARENT_SCOPE)
set(_build_env_pairs "${_build_env_pairs}" PARENT_SCOPE)
file(CONFIGURE OUTPUT "${toolchain_file}" CONTENT "${_toolchain_contents}" @ONLY ESCAPE_QUOTES)
endfunction()
# Fingerprints a source directory. If the source directory is considered dirty
# then the output fingerprint will be the empty string (invalid).
function(_therock_subproject_fprint_source_dir out_fprint dir)
set(fprint)
cmake_path(GET dir PARENT_PATH _source_parent)
cmake_path(GET dir FILENAME _source_basename)
set(_smrev_file "${_source_parent}/.${_source_basename}.smrev")
if(EXISTS "${_smrev_file}")
# The source control system has hard-coded a stable fingerprint that should
# be taken verbatim.
file(READ "${_smrev_file}" fprint)
endif()
# Determine if a git repo.
execute_process(
COMMAND git rev-parse --git-dir
WORKING_DIRECTORY "${dir}"
RESULT_VARIABLE GIT_CHECK_RESULT
OUTPUT_QUIET
ERROR_QUIET
)
if(GIT_CHECK_RESULT EQUAL 0)
set(IN_GIT_REPO TRUE)
else()
set(IN_GIT_REPO FALSE)
endif()
# Handle git repo.
if(IN_GIT_REPO)
if(NOT fprint)
# Process as git directory.
# Get the HEAD commit hash
execute_process(
COMMAND git rev-parse HEAD
WORKING_DIRECTORY "${dir}"
OUTPUT_VARIABLE GIT_COMMIT_HASH
OUTPUT_STRIP_TRAILING_WHITESPACE
RESULT_VARIABLE GIT_HASH_RESULT
ERROR_QUIET
)
if(GIT_HASH_RESULT EQUAL 0)
set(fprint "${GIT_COMMIT_HASH}")
else()
if(THEROCK_VERBOSE)
message(STATUS " FPRINT: Could not read git commit")
endif()
set(fprint)
endif()
endif()
# Check if working tree is pristine (no modified tracked files)
# TODO: Consider changing this incantation to instead consider the
# sub-tree, not the whole repo. See comments on:
# https://github.com/ROCm/TheRock/pull/2432
execute_process(
COMMAND git diff --quiet --ignore-submodules HEAD
WORKING_DIRECTORY "${dir}"
RESULT_VARIABLE GIT_DIFF_RESULT
)
if(NOT GIT_DIFF_RESULT EQUAL 0)
if(THEROCK_VERBOSE)
message(STATUS " FPRINT: Git directory is dirty: ${dir}")
endif()
set(fprint)
endif()
endif()
if(THEROCK_VERBOSE)
message(STATUS " FPRINT: '${fprint}'")
endif()
set("${out_fprint}" "${fprint}" PARENT_SCOPE)
endfunction()
function(_therock_subproject_fprint_files out_content files)
set(content "${${out_content}}")
foreach(file ${files})
file(SHA256 "${file}" fprint)
cmake_path(GET file FILENAME basename)
list(APPEND content "${basename}=${fprint}")
endforeach()
set("${out_content}" "${content}" PARENT_SCOPE)
endfunction()