# SPDX-FileCopyrightText: 2019-2026 Connor McLaughlin # SPDX-License-Identifier: CC-BY-NC-ND-4.0 + Packaging Restriction # # NOTE: In addition to the terms of CC-BY-NC-ND-4.0, you may not use this file to create # packages or build recipes without explicit permission from the copyright holder. include(CheckSourceCompiles) function(disable_compiler_warnings_for_target target) target_compile_options(${target} PRIVATE "-w") endfunction() function(detect_operating_system) message(STATUS "CMake Version: ${CMAKE_VERSION}") message(STATUS "CMake System Name: ${CMAKE_SYSTEM_NAME}") # LINUX wasn't added until CMake 3.25. if (CMAKE_VERSION VERSION_LESS 3.25.0 AND CMAKE_SYSTEM_NAME MATCHES "Linux") # Have to make it visible in this scope as well for below. set(LINUX TRUE PARENT_SCOPE) set(LINUX TRUE) endif() if(WIN32) message(FATAL_ERROR "Building for Windows with CMake is not supported. " "Open the solution file with Visual Studio.") elseif(APPLE AND NOT IOS) message(STATUS "Building for MacOS.") elseif(LINUX) message(STATUS "Building for Linux.") elseif(BSD) message(STATUS "Building for *BSD.") else() message(FATAL_ERROR "Unsupported platform.") endif() endfunction() function(detect_compiler) if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") set(COMPILER_CLANG TRUE PARENT_SCOPE) set(IS_SUPPORTED_COMPILER TRUE PARENT_SCOPE) message(STATUS "Building with Clang/LLVM.") elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") set(COMPILER_GCC TRUE PARENT_SCOPE) set(IS_SUPPORTED_COMPILER FALSE PARENT_SCOPE) message(STATUS "Building with GNU GCC.") else() message(FATAL_ERROR "Unknown compiler: ${CMAKE_CXX_COMPILER_ID}") endif() endfunction() function(detect_architecture) if(APPLE AND NOT "${CMAKE_OSX_ARCHITECTURES}" STREQUAL "") # Universal binaries. if("x86_64" IN_LIST CMAKE_OSX_ARCHITECTURES) message(STATUS "Building x86_64 MacOS binaries.") set(CPU_ARCH_X64 TRUE PARENT_SCOPE) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Xarch_x86_64 -msse4.1" PARENT_SCOPE) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Xarch_x86_64 -msse4.1" PARENT_SCOPE) endif() if("arm64" IN_LIST CMAKE_OSX_ARCHITECTURES) message(STATUS "Building ARM64 MacOS binaries.") set(CPU_ARCH_ARM64 TRUE PARENT_SCOPE) endif() elseif(("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "x86_64" OR "${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "amd64" OR "${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "AMD64" OR "${CMAKE_OSX_ARCHITECTURES}" STREQUAL "x86_64") AND CMAKE_SIZEOF_VOID_P EQUAL 8) message(STATUS "Building x86_64 binaries.") set(CPU_ARCH_X64 TRUE PARENT_SCOPE) if(NOT DISABLE_SSE4) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -msse4.1" PARENT_SCOPE) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -msse4.1" PARENT_SCOPE) endif() elseif(("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "aarch64" OR "${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "arm64") AND CMAKE_SIZEOF_VOID_P EQUAL 8) # Might have an A64 kernel, e.g. Raspbian. message(STATUS "Building ARM64 binaries.") set(CPU_ARCH_ARM64 TRUE PARENT_SCOPE) elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "arm" OR "${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "armv7-a" OR "${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "armv7l" OR (("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "aarch64" OR "${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "arm64") AND CMAKE_SIZEOF_VOID_P EQUAL 4)) message(STATUS "Building ARM32 binaries.") set(CPU_ARCH_ARM32 TRUE PARENT_SCOPE) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -marm -march=armv7-a -mfpu=neon-vfpv4" PARENT_SCOPE) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -marm -march=armv7-a -mfpu=neon-vfpv4" PARENT_SCOPE) elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "riscv64") message(STATUS "Building RISC-V 64 binaries.") set(CPU_ARCH_RISCV64 TRUE PARENT_SCOPE) # Don't want function calls for atomics. if(COMPILER_GCC) set(EXTRA_CFLAGS "${EXTRA_CFLAGS} -finline-atomics") # Still need this, apparently. link_libraries("-latomic") endif() if(NOT "${CMAKE_BUILD_TYPE}" STREQUAL "Debug") # Frame pointers generate an annoying amount of code on leaf functions. set(EXTRA_CFLAGS "${EXTRA_CFLAGS} -fomit-frame-pointer") endif() set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${EXTRA_CFLAGS}" PARENT_SCOPE) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${EXTRA_CFLAGS}" PARENT_SCOPE) elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "loongarch64") message(STATUS "Building LoongArch 64 binaries.") set(CPU_ARCH_LOONGARCH64 TRUE PARENT_SCOPE) else() message(FATAL_ERROR "Unknown system processor: ${CMAKE_SYSTEM_PROCESSOR}") endif() endfunction() function(detect_page_size) # This is only needed for ARM64, or if the user hasn't overridden it explicitly. # For universal Apple builds, we use preprocessor macros to determine page size. # Similar for Windows, except it's always 4KB. if(NOT CPU_ARCH_ARM64 OR NOT LINUX) unset(HOST_PAGE_SIZE CACHE) unset(HOST_PAGE_SIZE PARENT_SCOPE) unset(HOST_MIN_PAGE_SIZE CACHE) unset(HOST_MIN_PAGE_SIZE PARENT_SCOPE) unset(HOST_MAX_PAGE_SIZE CACHE) unset(HOST_MAX_PAGE_SIZE PARENT_SCOPE) return() elseif(DEFINED HOST_PAGE_SIZE) return() endif() if(DEFINED HOST_MIN_PAGE_SIZE OR DEFINED HOST_MAX_PAGE_SIZE) if(NOT DEFINED HOST_MIN_PAGE_SIZE OR NOT DEFINED HOST_MAX_PAGE_SIZE) message(FATAL_ERROR "Both HOST_MIN_PAGE_SIZE and HOST_MAX_PAGE_SIZE must be defined.") endif() return() endif() if(CMAKE_CROSSCOMPILING) message(WARNING "Cross-compiling and can't determine page size, assuming default.") return() endif() message(STATUS "Determining host page size") set(detect_page_size_file ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/src.c) file(WRITE ${detect_page_size_file} " #include #include #include int main() { int res = sysconf(_SC_PAGESIZE); printf(\"%d\", res); return (res > 0) ? EXIT_SUCCESS : EXIT_FAILURE; }") try_run( detect_page_size_run_result detect_page_size_compile_result ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY} ${detect_page_size_file} RUN_OUTPUT_VARIABLE detect_page_size_output) if(NOT detect_page_size_compile_result OR NOT detect_page_size_run_result EQUAL 0) message(FATAL_ERROR "Could not determine host page size.") else() message(STATUS "Host page size: ${detect_page_size_output}") set(HOST_PAGE_SIZE ${detect_page_size_output} CACHE STRING "Reported host page size") endif() endfunction() function(detect_cache_line_size) # This is only needed for ARM64, or if the user hasn't overridden it explicitly. if(NOT CPU_ARCH_ARM64 OR HOST_CACHE_LINE_SIZE) unset(HOST_CACHE_LINE_SIZE CACHE) unset(HOST_CACHE_LINE_SIZE PARENT_SCOPE) return() endif() if(NOT LINUX) # For universal Apple builds, we use preprocessor macros to determine page size. # Similar for Windows, except it's always 64 bytes. return() endif() if(CMAKE_CROSSCOMPILING) message(WARNING "Cross-compiling and can't determine cache line size, assuming default.") return() endif() message(STATUS "Determining host cache line size") set(detect_cache_line_size_file ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/src.c) file(WRITE ${detect_cache_line_size_file} " #include #include #include int main() { int l1i = sysconf(_SC_LEVEL1_DCACHE_LINESIZE); int l1d = sysconf(_SC_LEVEL1_ICACHE_LINESIZE); int res = (l1i > l1d) ? l1i : l1d; for (int index = 0; index < 16; index++) { char buf[128]; snprintf(buf, sizeof(buf), \"/sys/devices/system/cpu/cpu0/cache/index%d/coherency_line_size\", index); FILE* fp = fopen(buf, \"rb\"); if (!fp) break; fread(buf, sizeof(buf), 1, fp); fclose(fp); int val = atoi(buf); res = (val > res) ? val : res; } printf(\"%d\", res); return (res > 0) ? EXIT_SUCCESS : EXIT_FAILURE; }") try_run( detect_cache_line_size_run_result detect_cache_line_size_compile_result ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY} ${detect_cache_line_size_file} RUN_OUTPUT_VARIABLE detect_cache_line_size_output) if(NOT detect_cache_line_size_compile_result OR NOT detect_cache_line_size_run_result EQUAL 0) message(FATAL_ERROR "Could not determine host cache line size.") else() message(STATUS "Host cache line size: ${detect_cache_line_size_output}") set(HOST_CACHE_LINE_SIZE ${detect_cache_line_size_output} CACHE STRING "Reported host cache line size") endif() endfunction() function(get_scm_version) if(SCM_VERSION) return() endif() find_package(Git) if(EXISTS "${PROJECT_SOURCE_DIR}/.git" AND GIT_FOUND) execute_process( COMMAND ${GIT_EXECUTABLE} describe --dirty WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" OUTPUT_VARIABLE LOCAL_SCM_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE ) endif() if(NOT LOCAL_SCM_VERSION) set(SCM_VERSION "unknown" PARENT_SCOPE) else() set(SCM_VERSION ${LOCAL_SCM_VERSION} PARENT_SCOPE) endif() endfunction() function(add_debug_symbol_flag var) # CMake's regex engine is missing so many features... set(value "${${var}}") if (NOT " ${value} " MATCHES " -g[1-3]? ") message(STATUS "Adding -g1 to ${var}.") set(${var} "${value} -g1" PARENT_SCOPE) endif() endfunction() function(check_cpp_feature MACRO MINIMUM_VALUE) set(CACHE_VAR "CHECK_CPP_FEATURE_${MACRO}") if(NOT DEFINED ${CACHE_VAR}) # Create a small source code snippet that fails to compile if the feature is not available. set(TEMP_FILE "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/src.cpp") file(WRITE "${TEMP_FILE}" "#include #if !defined(${MACRO}) || ${MACRO} < ${MINIMUM_VALUE}L #error Missing feature #endif ") set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) try_compile(HAS_FEATURE ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY} "${TEMP_FILE}" CXX_STANDARD 23 CXX_STANDARD_REQUIRED TRUE ) set(${CACHE_VAR} ${HAS_FEATURE} CACHE INTERNAL "Cached feature test result for ${MACRO}") endif() if(NOT HAS_FEATURE) message(FATAL_ERROR "${MACRO} is not supported by your compiler, at least ${MINIMUM_VALUE} is required.") endif() endfunction() function(check_cpp_attribute ATTRIBUTE MINIMUM_VALUE) set(CACHE_VAR "CHECK_CPP_ATTRIBUTE_${MACRO}") if(NOT DEFINED ${CACHE_VAR}) set(TEMP_FILE "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/src.cpp") file(WRITE "${TEMP_FILE}" "#include #if !defined(__has_cpp_attribute) || __has_cpp_attribute(${ATTRIBUTE}) < ${MINIMUM_VALUE}L #error Missing feature #endif ") set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) try_compile(HAS_FEATURE ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY} "${TEMP_FILE}" CXX_STANDARD 23 CXX_STANDARD_REQUIRED TRUE ) set(${CACHE_VAR} ${HAS_FEATURE} CACHE INTERNAL "Cached attribute test result for ${MACRO}") endif() if(NOT HAS_FEATURE) message(FATAL_ERROR "${ATTRIBUTE} is not supported by your compiler, at least ${MINIMUM_VALUE} is required.") endif() endfunction() if(APPLE) function(add_metal_sources target sources library_name metal_std) set(air_files) set(compile_flags -std=${metal_std} -ffast-math) foreach(source IN LISTS sources) get_filename_component(source_name ${source} NAME) set(air_file ${CMAKE_CURRENT_BINARY_DIR}/${library_name}/${source_name}.air) list(APPEND air_files ${air_file}) add_custom_command( OUTPUT ${air_file} COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/${library_name} COMMAND xcrun metal ${compile_flags} -o ${air_file} -c ${source} DEPENDS ${source} COMMENT "Compiling Metal shader ${source_name}" ) endforeach() set(metallib_file ${CMAKE_CURRENT_BINARY_DIR}/${library_name}.metallib) add_custom_command( OUTPUT ${metallib_file} COMMAND xcrun metallib -o ${metallib_file} ${air_files} DEPENDS ${air_files} COMMENT "Linking Metal library ${library_name}.metallib" ) target_sources(${target} PRIVATE ${metallib_file}) set_source_files_properties(${metallib_file} PROPERTIES MACOSX_PACKAGE_LOCATION Resources) endfunction() endif() function(add_resources TARGET DEST_SUBDIR SOURCE_DIR) # Recursively find all files in the source directory file(GLOB_RECURSE SOURCE_FILES CONFIGURE_DEPENDS "${SOURCE_DIR}/*") foreach(SOURCE_FILE IN LISTS SOURCE_FILES) # Skip directories if(IS_DIRECTORY "${SOURCE_FILE}") continue() endif() # Get the path relative to SOURCE_DIR file(RELATIVE_PATH REL_PATH "${SOURCE_DIR}" "${SOURCE_FILE}") # Get the subdirectory portion (if any) get_filename_component(REL_SUBDIR "${REL_PATH}" DIRECTORY) if(APPLE) # On macOS, add as source with MACOSX_PACKAGE_LOCATION target_sources(${TARGET} PRIVATE "${SOURCE_FILE}") if(REL_SUBDIR) set_source_files_properties("${SOURCE_FILE}" PROPERTIES MACOSX_PACKAGE_LOCATION "Resources/${REL_SUBDIR}") else() set_source_files_properties("${SOURCE_FILE}" PROPERTIES MACOSX_PACKAGE_LOCATION "Resources") endif() else() # On other platforms, use custom command to copy files if(REL_SUBDIR) set(DEST_PATH "$/${DEST_SUBDIR}/${REL_SUBDIR}") else() set(DEST_PATH "$/${DEST_SUBDIR}") endif() add_custom_command(TARGET ${TARGET} POST_BUILD COMMAND ${CMAKE_COMMAND} -E make_directory "${DEST_PATH}" COMMAND ${CMAKE_COMMAND} -E copy_if_different "${SOURCE_FILE}" "${DEST_PATH}/" ) endif() endforeach() endfunction() function(add_runtime_libraries TARGET) unset(LIBRARY_SOURCES) unset(LIBRARY_RPATHS) foreach(NAME IN LISTS ARGN) get_target_property(dyn_lib_path ${NAME} IMPORTED_LOCATION_RELEASE) get_target_property(dyn_lib_soname ${NAME} IMPORTED_SONAME_RELEASE) if(dyn_lib_soname) string(REPLACE "@rpath/" "" dyn_lib_soname_filename "${dyn_lib_soname}") get_filename_component(dyn_lib_dir "${dyn_lib_path}" DIRECTORY) set(dyn_lib_major_path "${dyn_lib_dir}/${dyn_lib_soname_filename}") if(EXISTS "${dyn_lib_major_path}") set(dyn_lib_path "${dyn_lib_major_path}") else() message(WARNING "Could not find major-versioned symlink for ${NAME}.") endif() else() message(FATAL_ERROR "Could not find ${NAME}.") endif() get_filename_component(dyn_lib_dir "${dyn_lib_path}" DIRECTORY) if(APPLE AND NOT CMAKE_GENERATOR STREQUAL "Xcode" AND NOT SKIP_POSTPROCESS_BUNDLE) # For normal macOS bundle generators, put the dylib into Contents/Frameworks. message(STATUS "Bundling imported library ${dyn_lib_soname}") target_sources(${TARGET} PRIVATE "${dyn_lib_path}") set_source_files_properties( "${dyn_lib_path}" PROPERTIES MACOSX_PACKAGE_LOCATION Frameworks ) else() # Linux, and macOS when using the Xcode generator/not using bundles. list(APPEND LIBRARY_RPATHS "${dyn_lib_dir}") endif() endforeach() # Add the required library directories to the target's existing # build RPATH, without adding duplicate entries. if(LIBRARY_RPATHS) get_target_property(existing_rpath ${TARGET} BUILD_RPATH) if(NOT existing_rpath) set(existing_rpath "") endif() list(APPEND existing_rpath ${LIBRARY_RPATHS}) list(REMOVE_DUPLICATES existing_rpath) set_target_properties( ${TARGET} PROPERTIES BUILD_RPATH "${existing_rpath}" ) endif() endfunction()