cmake_minimum_required(VERSION 3.5...3.29)

if(NOT CLANG_TIDY)
  find_package(Python3)
endif()

if(POLICY CMP0026)
  cmake_policy(SET CMP0026 NEW)
endif()

if(POLICY CMP0051)
  cmake_policy(SET CMP0051 NEW)
endif()

if(POLICY CMP0054)
  cmake_policy(SET CMP0054 NEW)
endif()

if(POLICY CMP0063)
  cmake_policy(SET CMP0063 NEW)
endif()

project(DuckDB)

find_package(Threads REQUIRED)

set(DUCKDB_MODULE_BASE_DIR "${CMAKE_CURRENT_LIST_DIR}")

set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

set(CMAKE_CXX_STANDARD "11" CACHE STRING "C++ standard to enforce")

set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

set(CMAKE_VERBOSE_MAKEFILE OFF)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
set(CMAKE_MACOSX_RPATH 1)

find_program(CCACHE_PROGRAM ccache)
if(CCACHE_PROGRAM)
  set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CCACHE_PROGRAM}")
else()
  find_program(CCACHE_PROGRAM sccache)
  if(CCACHE_PROGRAM)
    set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CCACHE_PROGRAM}")
  endif()
endif()

# Determine install paths
# default to gnu standard installation directories (lib, bin, include)
# https://cmake.org/cmake/help/latest/module/GNUInstallDirs.html
include(GNUInstallDirs)
set(INSTALL_LIB_DIR
    ${CMAKE_INSTALL_LIBDIR}
    CACHE PATH "Installation directory for libraries")
set(INSTALL_BIN_DIR
    ${CMAKE_INSTALL_BINDIR}
    CACHE PATH "Installation directory for executables")
set(INSTALL_INCLUDE_DIR
    ${CMAKE_INSTALL_INCLUDEDIR}
    CACHE PATH "Installation directory for header files")
if(WIN32 AND NOT CYGWIN)
  set(DEF_INSTALL_CMAKE_DIR cmake)
else()
  set(DEF_INSTALL_CMAKE_DIR ${INSTALL_LIB_DIR}/cmake/DuckDB)
endif()
set(INSTALL_CMAKE_DIR
    ${DEF_INSTALL_CMAKE_DIR}
    CACHE PATH "Installation directory for CMake files")
set(DUCKDB_EXPORT_SET "DuckDBExports")

# This option allows --gc-sections flag during extension linking to discard any unused functions or data
if (EXTENSION_STATIC_BUILD AND "${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
  if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
    set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -ffunction-sections -fdata-sections")
  elseif(WIN32 AND MVSC)
    set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Gy")
  endif()
endif()

option(SHADOW_FORBIDDEN_FUNCTIONS "Compile time test on usage of deprecated functions" FALSE)
if (SHADOW_FORBIDDEN_FUNCTIONS)
  set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -include 'duckdb/common/shadow_forbidden_functions.hpp'")
endif()

option(DISABLE_UNITY "Disable unity builds." FALSE)
option(USE_WASM_THREADS "Should threads be used" FALSE)
if (${USE_WASM_THREADS})
   set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pthread")
   set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread")
   set(WASM_THREAD_FLAGS
       -pthread
       -sSHARED_MEMORY=1
   )
endif()

option(FORCE_COLORED_OUTPUT
       "Always produce ANSI-colored output (GNU/Clang only)." FALSE)
if(${FORCE_COLORED_OUTPUT})
  if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
    add_compile_options(-fdiagnostics-color=always)
  elseif("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang$")
    add_compile_options(-fcolor-diagnostics)
  endif()
endif()

if (DUCKDB_EXPLICIT_PLATFORM)
    add_definitions(-DDUCKDB_CUSTOM_PLATFORM=${DUCKDB_EXPLICIT_PLATFORM})
endif()

option (BUILD_COMPLETE_EXTENSION_SET "Whether we need to actually build the complete set" TRUE)
if (DEFINED ENV{BUILD_COMPLETE_EXTENSION_SET})
     set(BUILD_COMPLETE_EXTENSION_SET "$ENV{BUILD_COMPLETE_EXTENSION_SET}")
endif()

option (WASM_ENABLED "Are DuckDB-Wasm extensions build enabled" FALSE)
if (DEFINED ENV{WASM_EXTENSIONS})
     set(WASM_ENABLED "$ENV{WASM_EXTENSIONS}")
endif()
option (MUSL_ENABLED "Are Musl extensions build enabled" FALSE)
if (DEFINED ENV{DUCKDB_PLATFORM})
     if ("$ENV{DUCKDB_PLATFORM}" STREQUAL "linux_amd64_musl")
          set(MUSL_ENABLED ON)
     endif()
endif()

if (MUSL_ENABLED)
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D__MUSL_ENABLED__")
endif()

set(M32_FLAG "")
if(FORCE_32_BIT)
  set(M32_FLAG " -m32 ")
endif()

set(OS_NAME "unknown")
set(OS_ARCH "amd64")

string(REGEX MATCH "(arm64|aarch64)" IS_ARM "${CMAKE_SYSTEM_PROCESSOR}")
if(IS_ARM)
  set(OS_ARCH "arm64")
elseif(FORCE_32_BIT)
  set(OS_ARCH "i386")
endif()

if(APPLE)
  set(OS_NAME "osx")
endif()
if(WIN32)
  set(OS_NAME "windows")
endif()
if(UNIX AND NOT APPLE)
  set(OS_NAME "linux") # sorry BSD
endif()

option(FORCE_WARN_UNUSED "Unused code objects lead to compiler warnings." FALSE)

option(ENABLE_EXTENSION_AUTOLOADING "Enable extension auto-loading by default." FALSE)
option(ENABLE_EXTENSION_AUTOINSTALL "Enable extension auto-installing by default." FALSE)
option(EXTENSION_TESTS_ONLY "Only load the tests for extensions, don't actually build them; useful for testing loadable extensions" FALSE)
option(WASM_LOADABLE_EXTENSIONS "WebAssembly build with loadable extensions." FALSE)
option(ENABLE_SANITIZER "Enable address sanitizer." TRUE)
option(ENABLE_THREAD_SANITIZER "Enable thread sanitizer." FALSE)
option(ENABLE_UBSAN "Enable undefined behavior sanitizer." TRUE)
option(DISABLE_VPTR_SANITIZER "Disable vptr sanitizer; work-around for sanitizer false positive on Macbook M1" FALSE)
option(STANDALONE_DEBUG "add clang compile option -fstandalone-debug" FALSE)

if(${ENABLE_THREAD_SANITIZER})
  if(${ENABLE_SANITIZER})
    message(
      WARNING
        "Both thread and address sanitizers are enabled. This is not supported. The address sanitizer will be disabled, and we will run with only the thread sanitizer."
    )
  endif()
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DDUCKDB_THREAD_SANITIZER")
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread")
elseif(${ENABLE_SANITIZER})
  if(FORCE_ASSERT)
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address")
  else()
    set(CXX_EXTRA_DEBUG "${CXX_EXTRA_DEBUG} -fsanitize=address")
  endif()
endif()

if (${DISABLE_VPTR_SANITIZER})
else()
  if(APPLE AND CMAKE_SYSTEM_PROCESSOR MATCHES "arm64")
    if("${CMAKE_CXX_COMPILER_VERSION}" VERSION_GREATER 14.0)
      message(
        WARNING
          "Not disabling vptr sanitizer on M1 Macbook - set DISABLE_VPTR_SANITIZER manually if you run into issues with false positives in the sanitizer"
      )
    else()
    set(DISABLE_VPTR_SANITIZER TRUE)
    endif()
  endif()
endif()

if(${ENABLE_UBSAN})
  if(${ENABLE_THREAD_SANITIZER})
    message(
      WARNING
        "Both thread and undefined sanitizers are enabled. This is not supported. The undefined sanitizer will be disabled, and we will run with only the thread sanitizer."
    )
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DDUCKDB_THREAD_SANITIZER")
  else()
    if(FORCE_ASSERT)
      set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined -fno-sanitize-recover=all")
      if (${DISABLE_VPTR_SANITIZER})
        set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-sanitize=vptr")
      endif()
    else()
      set(CXX_EXTRA_DEBUG "${CXX_EXTRA_DEBUG} -fsanitize=undefined -fno-sanitize-recover=all")
      if (${DISABLE_VPTR_SANITIZER})
        set(CXX_EXTRA_DEBUG "${CXX_EXTRA_DEBUG} -fno-sanitize=vptr")
      endif()
    endif()
  endif()
endif()

option(EXPLICIT_EXCEPTIONS "Explicitly enable C++ exceptions." FALSE)
if(${EXPLICIT_EXCEPTIONS})
  set(CXX_EXTRA "${CXX_EXTRA} -fexceptions")
endif()

option(EXPORT_DYNAMIC_SYMBOLS "Export dynamic symbols." FALSE)
if(${EXPORT_DYNAMIC_SYMBOLS})
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -rdynamic")
endif()

set(VERSIONING_TAG_MATCH "v*.*.*")
######
# MAIN_BRANCH_VERSIONING default should be 'TRUE' for main branch and feature branches
# MAIN_BRANCH_VERSIONING default should be 'FALSE' for release branches
# MAIN_BRANCH_VERSIONING default value needs to keep in sync between:
# - CMakeLists.txt
# - scripts/amalgamation.py
# - scripts/package_build.py
######
option(MAIN_BRANCH_VERSIONING "Versioning scheme for main branch" TRUE)
if(${MAIN_BRANCH_VERSIONING})
  set(VERSIONING_TAG_MATCH "v*.*.0")
endif()

if (UNSAFE_NUMERIC_CAST)
  message(status "UNSAFE_NUMERIC_CAST")
  add_definitions(-DUNSAFE_NUMERIC_CAST=1)
endif()
if (ENABLE_EXTENSION_AUTOLOADING)
  add_definitions(-DDUCKDB_EXTENSION_AUTOLOAD_DEFAULT=1)
endif()
if (ENABLE_EXTENSION_AUTOINSTALL)
  add_definitions(-DDUCKDB_EXTENSION_AUTOINSTALL_DEFAULT=1)
endif()

option(OSX_BUILD_UNIVERSAL "Build both architectures on OSX and create a single binary containing both." FALSE)
if (OSX_BUILD_UNIVERSAL)
  if (NOT APPLE)
    message(FATAL_ERROR, "This only makes sense on OSX")
  endif()
  SET(CMAKE_OSX_ARCHITECTURES "x86_64;arm64" CACHE STRING "Build architectures for Mac OS X" FORCE)
  set(CMAKE_OSX_DEPLOYMENT_TARGET 11.0 CACHE STRING "Minimum OS X deployment version" FORCE)
endif()

if (OSX_BUILD_ARCH)
  message(STATUS "building for OSX architecture: ${OSX_BUILD_ARCH}")
  if (NOT APPLE)
    message(FATAL_ERROR, "This only makes sense on OSX")
  endif()
  SET(CMAKE_OSX_ARCHITECTURES "${OSX_BUILD_ARCH}" CACHE STRING "Build architectures for Mac OS X" FORCE)
  set(CMAKE_OSX_DEPLOYMENT_TARGET 11.0 CACHE STRING "Minimum OS X deployment version" FORCE)
endif()

set(SUN FALSE)
if(${CMAKE_SYSTEM_NAME} STREQUAL "SunOS")
  set(CXX_EXTRA "${CXX_EXTRA} -mimpure-text")
  add_definitions(-DSUN=1)
  set(SUN TRUE)
endif()

if (OVERRIDE_GIT_DESCRIBE MATCHES "^v[0-9]+\.[0-9]+\.[0-9]+\-rc[0-9]+$")
  if (DUCKDB_EXPLICIT_VERSION)
    if (DUCKDB_EXPLICIT_PLATFORM STREQUAL DUCKDB_EXPLICIT_PLATFORM)
      message(FATAL_ERROR "Provided OVERRIDE_GIT_DESCRIBE '${OVERRIDE_GIT_DESCRIBE}' and DUCKDB_EXPLICIT_PLATFORM '${DUCKDB_EXPLICIT_PLATFORM}' are both set and different")
    endif()
  endif()
  set (DUCKDB_EXPLICIT_VERSION "${OVERRIDE_GIT_DESCRIBE}")
  unset (OVERRIDE_GIT_DESCRIBE CACHE)
endif()

if (OVERRIDE_GIT_DESCRIBE)
  if (OVERRIDE_GIT_DESCRIBE MATCHES "^v[0-9]+\.[0-9]+\.[0-9]+\-[0-9]+\-g[a-f0-9]+$")
    set(GIT_DESCRIBE "${OVERRIDE_GIT_DESCRIBE}")
  elseif(OVERRIDE_GIT_DESCRIBE MATCHES "^v[0-9]+\.[0-9]+\.[0-9]+$")
    find_package(Git)
    if(Git_FOUND)
      execute_process(
            COMMAND ${GIT_EXECUTABLE} log -1 --format=%h
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
            RESULT_VARIABLE GIT_RESULT
            OUTPUT_VARIABLE GIT_COMMIT_HASH
            OUTPUT_STRIP_TRAILING_WHITESPACE)
        set(GIT_DESCRIBE "${OVERRIDE_GIT_DESCRIBE}-0-g${GIT_COMMIT_HASH}")
        if (GIT_RESULT)
          message(WARNING "git is available (at ${GIT_EXECUTABLE}) but has failed to execute 'log -1 --format=%h'. Consider providing explicit GIT_COMMIT_HASH")
          set(GIT_DESCRIBE "${OVERRIDE_GIT_DESCRIBE}-0-g0123456789")
    endif()
    else()
      set(GIT_DESCRIBE "${OVERRIDE_GIT_DESCRIBE}-0-g0123456789")
    endif()
  else()
    message(FATAL_ERROR "Provided OVERRIDE_GIT_DESCRIBE '${OVERRIDE_GIT_DESCRIBE}' do not match supported versions, either fully specified 'vX.Y.Z-N-gGITHASH123' or version only 'vX.Y.Z' or rc like 'vX.Y.Z-rcW")
  endif()
else()
  find_package(Git)
  if(Git_FOUND)
     if (NOT DEFINED GIT_COMMIT_HASH)
     execute_process(
            COMMAND ${GIT_EXECUTABLE} log -1 --format=%h
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
            RESULT_VARIABLE GIT_RESULT
            OUTPUT_VARIABLE GIT_COMMIT_HASH
            OUTPUT_STRIP_TRAILING_WHITESPACE)
        if (GIT_RESULT)
          message(WARNING "git is available (at ${GIT_EXECUTABLE}) but has failed to execute 'log -1 --format=%h'. Consider providing explicit GIT_COMMIT_HASH or OVERRIDE_GIT_DESCRIBE")
          set(GIT_COMMIT_HASH "0123456789")
    endif()
    endif()
    execute_process(
          COMMAND ${GIT_EXECUTABLE} describe --tags --long --match "${VERSIONING_TAG_MATCH}"
          WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
          RESULT_VARIABLE GIT_RESULT
          OUTPUT_VARIABLE GIT_DESCRIBE
          OUTPUT_STRIP_TRAILING_WHITESPACE)
    if (GIT_RESULT)
      message(WARNING "git is available (at ${GIT_EXECUTABLE}) but has failed to execute 'describe --tags --long', likely due to shallow clone. Consider providing explicit OVERRIDE_GIT_DESCRIBE or clone with tags. Continuing with dummy version v0.0.1")
      set(GIT_DESCRIBE "v0.0.1-0-g${GIT_COMMIT_HASH}")
    endif()
  else()
    message(WARNING "Git NOT FOUND and EXTERNAL_GIT_DESCRIBE not provided, continuing with dummy version v0.0.1")
    set(GIT_DESCRIBE "v0.0.1-0-g0123456789")
  endif()
endif()

if (NOT GIT_DESCRIBE MATCHES "^v[0-9]+\.[0-9]+\.[0-9]+\-[0-9]+\-g[a-f0-9]+$")
  message(FATAL_ERROR "Computed GIT_DESCRIBE '${GIT_DESCRIBE}' is not in the expected form 'vX.Y.Z-N-gGITHASH123'. Consider providing OVERRIDE_GIT_DESCRIBE explicitly to CMake")
endif()

string(REGEX REPLACE "v([0-9]+)\.[0-9]+\.[0-9]+\-.*" "\\1" DUCKDB_MAJOR_VERSION "${GIT_DESCRIBE}")
string(REGEX REPLACE "v[0-9]+\.([0-9]+)\.[0-9]+\-.*" "\\1" DUCKDB_MINOR_VERSION "${GIT_DESCRIBE}")
string(REGEX REPLACE "v[0-9]+\.[0-9]+\.([0-9]+)\-.*" "\\1" DUCKDB_PATCH_VERSION "${GIT_DESCRIBE}")
string(REGEX REPLACE "v[0-9]+\.[0-9]+\.[0-9]+\-([0-9]+)\-g.*" "\\1" DUCKDB_DEV_ITERATION "${GIT_DESCRIBE}")
if (NOT DEFINED GIT_COMMIT_HASH)
  string(REGEX REPLACE "v[0-9]+\.[0-9]+\.[0-9]+\-[0-9]+\-g([a-f0-9]+)" "\\1" GIT_COMMIT_HASH "${GIT_DESCRIBE}")
endif()

set(DUCKDB_VERSION_NUMBER "${DUCKDB_MAJOR_VERSION}.${DUCKDB_MINOR_VERSION}.${DUCKDB_PATCH_VERSION}")

string(LENGTH "${GIT_COMMIT_HASH}" LENGTH_GIT_COMMIT_HASH)
if (NOT ${LENGTH_GIT_COMMIT_HASH} EQUAL 10)
   message(STATUS "GIT_COMMIT_HASH has length ${LENGTH_GIT_COMMIT_HASH} different than the expected 10")
endif()

string(SUBSTRING "${GIT_COMMIT_HASH}" 0 10 GIT_COMMIT_HASH)

if(DUCKDB_EXPLICIT_VERSION)
  # Use with care, this forces the version to the provided string, potentially breaking invariants in the process
  set(DUCKDB_VERSION "${DUCKDB_EXPLICIT_VERSION}")
elseif(DUCKDB_DEV_ITERATION EQUAL 0)
  # on a tag; directly use the version
  set(DUCKDB_VERSION "v${DUCKDB_MAJOR_VERSION}.${DUCKDB_MINOR_VERSION}.${DUCKDB_PATCH_VERSION}")
else()
  # not on a tag, increment the patch version by one and add a -devX suffix
  if(${MAIN_BRANCH_VERSIONING})
    math(EXPR DUCKDB_MINOR_VERSION "${DUCKDB_MINOR_VERSION}+1")
  else()
    math(EXPR DUCKDB_PATCH_VERSION "${DUCKDB_PATCH_VERSION}+1")
  endif()
  set(DUCKDB_VERSION "v${DUCKDB_MAJOR_VERSION}.${DUCKDB_MINOR_VERSION}.${DUCKDB_PATCH_VERSION}-dev${DUCKDB_DEV_ITERATION}")
endif()

string(REGEX MATCH ".*dev.*" DUCKDB_EXTENSION_FOLDER_IS_VERSION "${DUCKDB_VERSION}")

if(DUCKDB_EXPLICIT_VERSION)
  set(DUCKDB_NORMALIZED_VERSION "${DUCKDB_EXPLICIT_VERSION}")
elseif(DUCKDB_EXTENSION_FOLDER_IS_VERSION AND NOT GIT_COMMIT_HASH STREQUAL "")
  set(DUCKDB_NORMALIZED_VERSION "${GIT_COMMIT_HASH}")
else()
  set(DUCKDB_NORMALIZED_VERSION "${DUCKDB_VERSION}")
endif()

if(EMSCRIPTEN)
  set(EXTENSION_POSTFIX ".wasm")
else()
  set(EXTENSION_POSTFIX "")
endif()

message(STATUS "git hash ${GIT_COMMIT_HASH}, version ${DUCKDB_VERSION}, extension folder ${DUCKDB_NORMALIZED_VERSION}")

option(AMALGAMATION_BUILD
       "Build from the amalgamation files, rather than from the normal sources."
       FALSE)

option(BUILD_MAIN_DUCKDB_LIBRARY
        "Build the main duckdb library and executable."
        TRUE)
option(EXTENSION_STATIC_BUILD
        "Extension build linking statically with DuckDB. Required for building linux loadable extensions."
        FALSE)

if(WIN32 OR ZOS)
  set(EXTENSION_STATIC_BUILD TRUE)
  add_definitions(-D_SILENCE_ALL_MS_EXT_DEPRECATION_WARNINGS=1)
endif()

option(BUILD_EXTENSIONS_ONLY "Build all extension as linkable, overriding DONT_LINK, and don't build core." FALSE)
option(BUILD_BENCHMARKS "Enable building of the benchmark suite." FALSE)
option(BUILD_TPCE "Enable building of the TPC-E tool." FALSE)
option(DISABLE_BUILTIN_EXTENSIONS "Disable linking extensions." FALSE)
option(GENERATE_EXTENSION_ENTRIES "Build for generating extension_entries.hpp" FALSE)
option(FORCE_QUERY_LOG "If enabled, all queries will be logged to the specified path" OFF)
option(BUILD_SHELL "Build the DuckDB Shell and SQLite API Wrappers" TRUE)
option(DISABLE_THREADS "Disable support for multi-threading" FALSE)
option(DISABLE_EXTENSION_LOAD "Disable support for loading and installing extensions" FALSE)
option(DISABLE_STR_INLINE "Debug setting: disable inlining of strings" FALSE)
option(DISABLE_MEMORY_SAFETY "Debug setting: disable memory access checks at runtime" FALSE)
option(DISABLE_ASSERTIONS "Debug setting: disable assertions" FALSE)
option(ALTERNATIVE_VERIFY "Debug setting: use alternative verify mode" FALSE)
option(DISABLE_POINTER_SALT "Debug setting: verify correct results without pointer salt" FALSE)
option(HASH_ZERO "Debug setting: verify hash collision resolution by setting all hashes to 0" FALSE)
option(RUN_SLOW_VERIFIERS "Debug setting: enable a more extensive set of verifiers" FALSE)
option(DESTROY_UNPINNED_BLOCKS "Debug setting: destroy unpinned buffer-managed blocks" FALSE)
option(FORCE_ASYNC_SINK_SOURCE "Debug setting: forces sinks/sources to block the first 2 times they're called" FALSE)
option(DEBUG_ALLOCATION "Debug setting: keep track of outstanding allocations to detect memory leaks" FALSE)
option(DEBUG_STACKTRACE "Debug setting: print a stracktrace on asserts and when testing crashes" FALSE)
option(DEBUG_MOVE "Debug setting: Ensure std::move is being used" FALSE)
option(VERIFY_VECTOR "Debug setting: verify vectors (options: dictionary_expression, dictionary_operator, constant_operator, sequence_operator, nested_shuffle, variant_vector)" "none")
option(CLANG_TIDY "Enable build for clang-tidy, this disables all source files excluding the core database. This does not produce a working build." FALSE)
option(BUILD_UNITTESTS "Build the unittest runner." TRUE)
option(ENABLE_UNITTEST_CPP_TESTS "Build the C++ Unit Tests." TRUE)
option(EXTENSION_CONFIG_BUILD "Produce extension configuration artifacts instead of building. (such as shared vcpkg.json, extensions.txt)" FALSE)
option(CUSTOM_LINKER "Use a custom linker program" "")
option(CRASH_ON_ASSERT "Trigger a sigabort on an assert failing, instead of throwing an exception" FALSE)
option(FORCE_ASSERT "Enable checking of assertions, even in release mode" FALSE)
option(FORCE_DEBUG "Force adding a debug define, even in release mode" FALSE)

option(TREAT_WARNINGS_AS_ERRORS "Treat warnings as errors" FALSE)
option(EXPORT_DLL_SYMBOLS "Export dll symbols on Windows, else import" TRUE)
option(BUILD_RDTSC "Enable the rdtsc instruction." FALSE)
option(SMALLER_BINARY "Produce a smaller binary by trimming specialized code paths. This can negatively affect performance." FALSE)
option(NATIVE_ARCH "Compile targeting the native architecture" FALSE)
option(OVERRIDE_NEW_DELETE "Override C++ new/delete (only when jemalloc is enabled)" FALSE)

if(${BUILD_RDTSC})
  add_compile_definitions(RDTSC)
endif()

if(BUILD_EXTENSIONS_ONLY)
  set(BUILD_MAIN_DUCKDB_LIBRARY FALSE)
endif()

if(EXTENSION_CONFIG_BUILD)
  set(BUILD_MAIN_DUCKDB_LIBRARY FALSE)
endif()

if (NOT BUILD_MAIN_DUCKDB_LIBRARY)
  set(BUILD_UNITTESTS FALSE)
  set(BUILD_SHELL FALSE)
  set(DISABLE_BUILTIN_EXTENSIONS TRUE)
endif()

if (GENERATE_EXTENSION_ENTRIES)
  set(DISABLE_BUILTIN_EXTENSIONS TRUE)
endif()

if(TREAT_WARNINGS_AS_ERRORS)
  message("Treating warnings as errors.")
endif()

if(NATIVE_ARCH)
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native")
endif()

if(OVERRIDE_NEW_DELETE)
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DDUCKDB_OVERRIDE_NEW_DELETE")
endif()

if(CRASH_ON_ASSERT)
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DDUCKDB_CRASH_ON_ASSERT")
endif()

if(DISABLE_STR_INLINE)
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DDUCKDB_DEBUG_NO_INLINE")
endif()

if(DISABLE_MEMORY_SAFETY)
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DDUCKDB_DEBUG_NO_SAFETY")
endif()

if(DISABLE_ASSERTIONS)
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DDISABLE_ASSERTIONS")
endif()

if(DESTROY_UNPINNED_BLOCKS)
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DDUCKDB_DEBUG_DESTROY_BLOCKS")
endif()

if(FORCE_ASYNC_SINK_SOURCE)
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DDUCKDB_DEBUG_ASYNC_SINK_SOURCE")
endif()

if(RUN_SLOW_VERIFIERS)
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DDUCKDB_RUN_SLOW_VERIFIERS")
endif()

if(ALTERNATIVE_VERIFY)
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DDUCKDB_ALTERNATIVE_VERIFY")
endif()

if(DISABLE_POINTER_SALT)
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DDUCKDB_DISABLE_POINTER_SALT")
endif()

if(HASH_ZERO)
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DDUCKDB_HASH_ZERO")
endif()

if(LATEST_STORAGE)
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DDUCKDB_LATEST_STORAGE")
endif()

if(BLOCK_VERIFICATION)
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DDUCKDB_BLOCK_VERIFICATION")
endif()

if(DEBUG_ALLOCATION)
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DDUCKDB_DEBUG_ALLOCATION")
endif()

if(DEBUG_MOVE)
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DDUCKDB_DEBUG_MOVE")
endif()

if (CLANG_TIDY)
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DDUCKDB_CLANG_TIDY")
endif()

if (SMALLER_BINARY)
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DDUCKDB_SMALLER_BINARY")
endif()

if(FORCE_ASSERT)
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DDUCKDB_FORCE_ASSERT")
endif()

if(FORCE_DEBUG)
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DDEBUG")
endif()

if(STANDALONE_DEBUG)
  if (NOT CMAKE_BUILD_TYPE STREQUAL "Debug" OR NOT "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang$")
    message(FATAL_ERROR "Only clang compiler supports -fstandalone-debug")
  endif()
  add_compile_options(-fstandalone-debug)
endif()

function(is_number input_string return_var)
    if("${input_string}" MATCHES "^[0-9]+$")
        set(${return_var} TRUE PARENT_SCOPE)
    else()
        set(${return_var} FALSE PARENT_SCOPE)
    endif()
endfunction()

set(STANDARD_VECTOR_SIZE "" CACHE STRING "Set a custom STANDARD_VECTOR_SIZE at compile time")

if(DEFINED STANDARD_VECTOR_SIZE AND NOT STANDARD_VECTOR_SIZE STREQUAL "")
    is_number(${STANDARD_VECTOR_SIZE} is_number_result)
    if(is_number_result)
        set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DSTANDARD_VECTOR_SIZE=${STANDARD_VECTOR_SIZE}")
        message(STATUS "STANDARD_VECTOR_SIZE is set to ${STANDARD_VECTOR_SIZE}")
    else()
        message(FATAL_ERROR "STANDARD_VECTOR_SIZE must be a number, not ${STANDARD_VECTOR_SIZE}")
    endif()
endif()

if(CUSTOM_LINKER)
  set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fuse-ld=${CUSTOM_LINKER}")
  set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fuse-ld=${CUSTOM_LINKER}")
endif()

if(NOT MSVC)
  if(${FORCE_WARN_UNUSED})
    set(CXX_EXTRA "${CXX_EXTRA} -Wunused")
  endif()
  if(TREAT_WARNINGS_AS_ERRORS)
    set(CXX_EXTRA "${CXX_EXTRA} -Werror")
  endif()
  set(CMAKE_CXX_FLAGS_DEBUG
      "${CMAKE_CXX_FLAGS_DEBUG} -g -O0 -DDEBUG -Wall ${M32_FLAG} ${CXX_EXTRA}")
  set(CMAKE_CXX_FLAGS_RELEASE
      "${CMAKE_CXX_FLAGS_RELEASE} -O3 -DNDEBUG ${M32_FLAG} ${CXX_EXTRA}")
  if("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang$" AND CMAKE_LTO)
    set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -flto=${CMAKE_LTO}")
  elseif (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_LTO)
    set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -flto")
  endif()
  set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELEASE} -g")

  set(CXX_EXTRA_DEBUG
      "${CXX_EXTRA_DEBUG} -Wunused -Werror=vla -Wnarrowing -pedantic"
  )

  if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION
                                                   VERSION_GREATER 8.0)
    set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${CXX_EXTRA_DEBUG}")
  elseif("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang$"
         AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 9.0)
    set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${CXX_EXTRA_DEBUG}")
  else()
    message(WARNING "Please use a recent compiler for debug builds")
  endif()
else()
  # we don't use "constexpr static std::mutex", so we can use the
  # _DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR definition that is required
  # to keep the compatibility with C++ stdlib version 14.29 from MSVC 2019
  set(CMAKE_CXX_WINDOWS_FLAGS
      "/wd4244 /wd4267 /wd4200 /wd26451 /wd26495 /D_CRT_SECURE_NO_WARNINGS /D_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR /utf-8")
  if(TREAT_WARNINGS_AS_ERRORS)
    set(CMAKE_CXX_WINDOWS_FLAGS "${CMAKE_CXX_WINDOWS_FLAGS} /WX")
  endif()
  # remove warning from CXX flags
  string(REGEX REPLACE "/W[0-4]" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
  # add to-be-ignored warnings
  set(CMAKE_CXX_FLAGS
      "${CMAKE_CXX_FLAGS} ${CMAKE_CXX_WINDOWS_FLAGS}"
  )
endif()

# todo use CHECK_CXX_COMPILER_FLAG(-fsanitize=address SUPPORTS_SANITIZER) etc.

set(CMAKE_C_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}")
set(CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}")

if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
  set(DEFAULT_BUILD_TYPE "Release")
  message(STATUS "Setting build type to '${DEFAULT_BUILD_TYPE}'.")
  set(CMAKE_BUILD_TYPE
      "${DEFAULT_BUILD_TYPE}"
      CACHE STRING "Choose the type of build." FORCE)
endif()

if(OS_NAME STREQUAL "windows")
  list (FIND DUCKDB_EXTENSION_NAMES jemalloc _index)
  if (${_index} GREATER -1)
    # have to throw an error because this will crash at runtime
    message(FATAL_ERROR "The jemalloc extension is not supported on Windows")
  endif()
endif()

include_directories(src/include)
include_directories(third_party/fsst)
include_directories(third_party/fmt/include)
include_directories(third_party/hyperloglog)
include_directories(third_party/fastpforlib)
include_directories(third_party/skiplist)
include_directories(third_party/ska_sort)
include_directories(third_party/fast_float)
include_directories(third_party/re2)
include_directories(third_party/miniz)
include_directories(third_party/utf8proc/include)
include_directories(third_party/concurrentqueue)
include_directories(third_party/pcg)
include_directories(third_party/pdqsort)
include_directories(third_party/tdigest)
include_directories(third_party/mbedtls/include)
include_directories(third_party/jaro_winkler)
include_directories(third_party/vergesort)
include_directories(third_party/yyjson/include)
include_directories(third_party/zstd/include)

# todo only regenerate ub file if one of the input files changed hack alert
function(enable_unity_build UB_SUFFIX SOURCE_VARIABLE_NAME)
  set(files ${${SOURCE_VARIABLE_NAME}})

  # Generate a unique filename for the unity build translation unit
  set(unit_build_file ${CMAKE_CURRENT_BINARY_DIR}/ub_${UB_SUFFIX}.cpp)
  set(temp_unit_build_file ${CMAKE_CURRENT_BINARY_DIR}/ub_${UB_SUFFIX}.cpp.tmp)
  # Exclude all translation units from compilation
  set_source_files_properties(${files} PROPERTIES HEADER_FILE_ONLY true)

  set(rebuild FALSE)
  # check if any of the source files have changed
  foreach(source_file ${files})
    if(${CMAKE_CURRENT_SOURCE_DIR}/${source_file} IS_NEWER_THAN
       ${unit_build_file})
      set(rebuild TRUE)
    endif()
  endforeach(source_file)
  # write a temporary file
  file(WRITE ${temp_unit_build_file} "// Unity Build generated by CMake\n")
  foreach(source_file ${files})
    file(
      APPEND ${temp_unit_build_file}
      "#include <${CMAKE_CURRENT_SOURCE_DIR}/${source_file}>\n"
    )
  endforeach(source_file)

  execute_process(
    COMMAND ${CMAKE_COMMAND} -E compare_files ${unit_build_file}
            ${temp_unit_build_file}
    RESULT_VARIABLE compare_result
    OUTPUT_VARIABLE bla
    ERROR_VARIABLE bla)
  if(compare_result EQUAL 0)
    # files are identical: do nothing
  elseif(compare_result EQUAL 1)
    # files are different: rebuild
    set(rebuild TRUE)
  else()
    # error while compiling: rebuild
    set(rebuild TRUE)
  endif()

  if(${rebuild})
    file(WRITE ${unit_build_file} "// Unity Build generated by CMake\n")
    foreach(source_file ${files})
      file(
        APPEND ${unit_build_file}
        "#include <${CMAKE_CURRENT_SOURCE_DIR}/${source_file}>\n"
      )
    endforeach(source_file)
  endif()

  # Complement list of translation units with the name of ub
  set(${SOURCE_VARIABLE_NAME}
      ${${SOURCE_VARIABLE_NAME}} ${unit_build_file}
      PARENT_SCOPE)
endfunction(enable_unity_build)

function(add_library_unity NAME MODE)
  set(SRCS ${ARGN})
  if(NOT DISABLE_UNITY)
    enable_unity_build(${NAME} SRCS)
  endif()
  add_library(${NAME} OBJECT ${SRCS})
  if(MSVC)
    target_compile_options(${NAME} PRIVATE /bigobj)
  endif()
endfunction()

function(disable_target_warnings NAME)
  if(MSVC)
    target_compile_options(${NAME} PRIVATE "/W0")
  elseif("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang$"
         OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
    target_compile_options(${NAME} PRIVATE "-w")
  endif()
endfunction()

function(add_extension_definitions)
  include_directories(${PROJECT_SOURCE_DIR}/extension)
  if(NOT "${TEST_WITH_LOADABLE_EXTENSION}" STREQUAL "")
    string(REPLACE ";"  "," COMMA_SEPARATED_EXTENSIONS "${TEST_WITH_LOADABLE_EXTENSION}")
    # Note: weird commas are for easy substring matching in c++
    add_definitions(-DDUCKDB_EXTENSIONS_TEST_WITH_LOADABLE=\",${COMMA_SEPARATED_EXTENSIONS},\")
    add_definitions(-DDUCKDB_EXTENSIONS_BUILD_PATH="${CMAKE_BINARY_DIR}/extension")
  endif()

  if(${DISABLE_BUILTIN_EXTENSIONS})
    add_definitions(-DDISABLE_BUILTIN_EXTENSIONS=${DISABLE_BUILTIN_EXTENSIONS})
  endif()

  # Include paths for any registered out-of-tree extensions
  foreach(EXT_NAME IN LISTS DUCKDB_EXTENSION_NAMES)
    string(TOUPPER ${EXT_NAME} EXT_NAME_UPPERCASE)
    if(${DUCKDB_EXTENSION_${EXT_NAME_UPPERCASE}_SHOULD_LINK})
      add_definitions(-DDUCKDB_EXTENSION_${EXT_NAME_UPPERCASE}_LINKED=1)
      if (DEFINED DUCKDB_EXTENSION_${EXT_NAME_UPPERCASE}_INCLUDE_PATH)
        include_directories("${DUCKDB_EXTENSION_${EXT_NAME_UPPERCASE}_INCLUDE_PATH}")
      else()
        # We try the default locations for headers
        include_directories("${PROJECT_SOURCE_DIR}/extension_external/${EXT_NAME}/src/include")
        include_directories("${PROJECT_SOURCE_DIR}/extension_external/${EXT_NAME}/include")
      endif()
    endif()
  endforeach()
endfunction()

function(add_extension_dependencies LIBRARY)
  foreach(EXT_NAME IN LISTS DUCKDB_EXTENSION_NAMES)
    string(TOUPPER ${EXT_NAME} EXTENSION_NAME_UPPERCASE)
    if (DUCKDB_EXTENSION_${EXTENSION_NAME_UPPERCASE}_SHOULD_LINK)
      add_dependencies(${LIBRARY} ${EXT_NAME}_extension)
    endif()
  endforeach()
endfunction()

function(get_statically_linked_extensions DUCKDB_EXTENSION_NAMES OUT_VARIABLE)
  if(NOT ${DISABLE_BUILTIN_EXTENSIONS})
      set(${OUT_VARIABLE} ${DUCKDB_EXTENSION_NAMES} PARENT_SCOPE)
  elseif(${GENERATE_EXTENSION_ENTRIES})
      set(${OUT_VARIABLE} "" PARENT_SCOPE)
  else()
      set(${OUT_VARIABLE} "" PARENT_SCOPE)
      foreach(EXT_NAME IN LISTS DUCKDB_EXTENSION_NAMES)
          if(${EXT_NAME} STREQUAL "core_functions")
              set(${OUT_VARIABLE} "core_functions" PARENT_SCOPE)
          endif()
      endforeach()
  endif()
endfunction()

function(link_extension_libraries LIBRARY LINKAGE)
  get_statically_linked_extensions("${DUCKDB_EXTENSION_NAMES}" STATICALLY_LINKED_EXTENSIONS)
  # Now link against any registered out-of-tree extensions
  foreach(EXT_NAME IN LISTS STATICALLY_LINKED_EXTENSIONS)
    string(TOUPPER ${EXT_NAME} EXT_NAME_UPPERCASE)
    if (${DUCKDB_EXTENSION_${EXT_NAME_UPPERCASE}_SHOULD_LINK})
      target_link_libraries(${LIBRARY} ${LINKAGE} ${EXT_NAME}_extension)
    endif()
  endforeach()
endfunction()

function(link_threads LIBRARY LINKAGE)
    target_link_libraries(${LIBRARY} ${LINKAGE} Threads::Threads)
endfunction()

# Deploys extensions to a local repository (a folder structure that contains the duckdb version + binary arch)
if ("${LOCAL_EXTENSION_REPO}" STREQUAL "")
  set(LOCAL_EXTENSION_REPO_DIR ${CMAKE_BINARY_DIR}/repository)
else()
  if (NOT Python3_FOUND)
    MESSAGE(FATAL_ERROR "Could not find python3 executable, when providing LOCAL_EXTENSION_REPO this is compulsory")
  endif()
  set(LOCAL_EXTENSION_REPO_DIR ${LOCAL_EXTENSION_REPO})
endif()

set(LOCAL_EXTENSION_REPO FALSE)
if (NOT EXTENSION_CONFIG_BUILD AND NOT ${EXTENSION_TESTS_ONLY} AND NOT CLANG_TIDY)
  if (NOT Python3_FOUND)
    add_custom_target(
          duckdb_local_extension_repo ALL)
    MESSAGE(STATUS "Could not find python3, create extension directory step will be skipped")
  else()
    add_custom_target(
          duckdb_local_extension_repo ALL
          COMMAND
          ${Python3_EXECUTABLE} scripts/create_local_extension_repo.py "${DUCKDB_NORMALIZED_VERSION}" "${CMAKE_CURRENT_BINARY_DIR}/duckdb_platform_out" "${CMAKE_CURRENT_BINARY_DIR}" "${LOCAL_EXTENSION_REPO_DIR}" "duckdb_extension${EXTENSION_POSTFIX}"
          WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
          COMMENT Create local extension repository)
    add_dependencies(duckdb_local_extension_repo duckdb_platform)
    set(LOCAL_EXTENSION_REPO TRUE)
    message(STATUS "Extensions will be deployed to: ${LOCAL_EXTENSION_REPO_DIR}")
  endif()
endif()

function(build_loadable_extension_directory NAME ABI_TYPE OUTPUT_DIRECTORY EXTENSION_VERSION CAPI_VERSION PARAMETERS)
  set(TARGET_NAME ${NAME}_loadable_extension)
  if (LOCAL_EXTENSION_REPO)
    add_dependencies(duckdb_local_extension_repo ${NAME}_loadable_extension)
  endif()
  # all parameters after output_directory
  set(FILES "${ARGV}")
  # remove name, abi_type, output_directory, extension_version, capi_version, parameters
  list(REMOVE_AT FILES 0 1 2 3 4 5)

  # parse parameters
  string(FIND "${PARAMETERS}" "-no-warnings" IGNORE_WARNINGS)

  string(TOUPPER ${NAME} EXTENSION_NAME_UPPERCASE)

  if(EMSCRIPTEN)
     add_library(${TARGET_NAME} STATIC ${FILES})
  else()
     add_library(${TARGET_NAME} SHARED ${FILES})
  endif()
  # this disables the -Dsome_target_EXPORTS define being added by cmake which otherwise trips clang-tidy (yay)
  set_target_properties(${TARGET_NAME} PROPERTIES DEFINE_SYMBOL "")
  set_target_properties(${TARGET_NAME} PROPERTIES OUTPUT_NAME ${NAME})
  set_target_properties(${TARGET_NAME} PROPERTIES PREFIX "")
  if(${IGNORE_WARNINGS} GREATER -1)
    disable_target_warnings(${TARGET_NAME})
  endif()
  # loadable extension binaries can be built two ways:
  # 1. EXTENSION_STATIC_BUILD=1
  #    DuckDB is statically linked into each extension binary. This increases portability because in several situations
  #    DuckDB itself may have been loaded with RTLD_LOCAL. This is currently the main way we distribute the loadable
  #    extension binaries
  # 2. EXTENSION_STATIC_BUILD=0
  #    The DuckDB symbols required by the loadable extensions are left unresolved. This will reduce the size of the binaries
  #    and works well when running the DuckDB cli directly. For windows this uses delay loading. For MacOS and linux the
  #    dynamic loader will look up the missing symbols when the extension is dlopen-ed.
  if(WASM_LOADABLE_EXTENSIONS)
    set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -sSIDE_MODULE=1 -DWASM_LOADABLE_EXTENSIONS")
  elseif(${ABI_TYPE} STREQUAL "C_STRUCT" OR ${ABI_TYPE} STREQUAL "C_STRUCT_UNSTABLE")
    # TODO strip all symbols except the capi init
  elseif (EXTENSION_STATIC_BUILD)
    if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
      if (APPLE)
        set_target_properties(${TARGET_NAME} PROPERTIES CXX_VISIBILITY_PRESET hidden)
        # Note that on MacOS we need to use the -exported_symbol whitelist feature due to a lack of -exclude-libs flag in mac's ld variant
        set(WHITELIST "-Wl,-exported_symbol,_${NAME}_duckdb_cpp_init")
        target_link_libraries(${TARGET_NAME} duckdb_static ${DUCKDB_EXTRA_LINK_FLAGS} -Wl,-dead_strip ${WHITELIST})
      elseif (ZOS)
        target_link_libraries(${TARGET_NAME} duckdb_static ${DUCKDB_EXTRA_LINK_FLAGS})
      else()
        # For GNU we rely on fvisibility=hidden to hide the extension symbols and use -exclude-libs to hide the duckdb symbols
        set_target_properties(${TARGET_NAME} PROPERTIES CXX_VISIBILITY_PRESET hidden)
        target_link_libraries(${TARGET_NAME} duckdb_static ${DUCKDB_EXTRA_LINK_FLAGS} -Wl,--gc-sections -Wl,--exclude-libs,ALL)
      endif()
    elseif (WIN32)
      target_link_libraries(${TARGET_NAME} duckdb_static ${DUCKDB_EXTRA_LINK_FLAGS})
    else()
      message(FATAL_ERROR, "EXTENSION static build is only intended for Linux and Windows on MVSC")
    endif()
  else()
    if (WIN32)
      target_link_libraries(${TARGET_NAME} duckdb ${DUCKDB_EXTRA_LINK_FLAGS})
    elseif("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang$")
      if (APPLE)
        set_target_properties(${TARGET_NAME} PROPERTIES LINK_FLAGS "-undefined dynamic_lookup")
      endif()
    endif()
  endif()


  target_compile_definitions(${TARGET_NAME} PUBLIC -DDUCKDB_BUILD_LOADABLE_EXTENSION)
  set_target_properties(${TARGET_NAME} PROPERTIES SUFFIX
          ".duckdb_extension")

  if(MSVC)
    set_target_properties(
            ${TARGET_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY_DEBUG
            "${CMAKE_BINARY_DIR}/${OUTPUT_DIRECTORY}")
    set_target_properties(
            ${TARGET_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY_RELEASE
            "${CMAKE_BINARY_DIR}/${OUTPUT_DIRECTORY}")
  endif()

  if(EMSCRIPTEN)
    # Compile the library into the actual wasm file
    string(TOUPPER ${NAME} EXTENSION_NAME_UPPERCASE)
    set(TO_BE_LINKED ${DUCKDB_EXTENSION_${EXTENSION_NAME_UPPERCASE}_LINKED_LIBS} )
    separate_arguments(TO_BE_LINKED)
    if (${ABI_TYPE} STREQUAL "CPP")
      set(EXPORTED_FUNCTIONS "_${NAME}_duckdb_cpp_init")
    elseif (${ABI_TYPE} STREQUAL "C_STRUCT" OR ${ABI_TYPE} STREQUAL "C_STRUCT_UNSTABLE")
      set(EXPORTED_FUNCTIONS "_${NAME}_init_c_api")
    endif()
    add_custom_command(
      TARGET ${TARGET_NAME}
      POST_BUILD
      COMMAND emcc $<TARGET_FILE:${TARGET_NAME}> -o $<TARGET_FILE:${TARGET_NAME}>.wasm -O3 -sSIDE_MODULE=2 -sEXPORTED_FUNCTIONS="${EXPORTED_FUNCTIONS}" ${WASM_THREAD_FLAGS} ${TO_BE_LINKED}
      )
  endif()

  if (${ABI_TYPE} STREQUAL "CPP")
    set(FOOTER_VERSION_VALUE ${DUCKDB_NORMALIZED_VERSION})
  elseif (${ABI_TYPE} STREQUAL "C_STRUCT_UNSTABLE")
    set(FOOTER_VERSION_VALUE ${DUCKDB_NORMALIZED_VERSION})
  elseif (${ABI_TYPE} STREQUAL "C_STRUCT")
    set(FOOTER_VERSION_VALUE ${CAPI_VERSION})
  endif()

  add_custom_command(
    TARGET ${TARGET_NAME}
    POST_BUILD
    COMMAND
    ${CMAKE_COMMAND} -DABI_TYPE=${ABI_TYPE} -DEXTENSION=$<TARGET_FILE:${TARGET_NAME}>${EXTENSION_POSTFIX} -DPLATFORM_FILE=${DuckDB_BINARY_DIR}/duckdb_platform_out -DVERSION_FIELD="${FOOTER_VERSION_VALUE}" -DEXTENSION_VERSION="${EXTENSION_VERSION}" -DNULL_FILE=${DUCKDB_MODULE_BASE_DIR}/scripts/null.txt -P ${DUCKDB_MODULE_BASE_DIR}/scripts/append_metadata.cmake
    )
  add_dependencies(${TARGET_NAME} duckdb_platform)
  if (NOT EXTENSION_CONFIG_BUILD AND NOT ${EXTENSION_TESTS_ONLY} AND NOT CLANG_TIDY)
    add_dependencies(duckdb_local_extension_repo ${TARGET_NAME})
  endif()
endfunction()

function(build_loadable_extension NAME PARAMETERS)
  # all parameters after name
  set(FILES "${ARGV}")
  list(REMOVE_AT FILES 0 1)
  string(TOUPPER ${NAME} EXTENSION_NAME_UPPERCASE)

  build_loadable_extension_directory(${NAME} "CPP" "extension/${NAME}" "${DUCKDB_EXTENSION_${EXTENSION_NAME_UPPERCASE}_EXT_VERSION}" "" "${PARAMETERS}" ${FILES})
endfunction()

function(build_loadable_extension_capi NAME CAPI_VERSION_MAJOR CAPI_VERSION_MINOR CAPI_VERSION_PATCH PARAMETERS)
  set(FILES "${ARGV}")
  list(REMOVE_AT FILES 0 1 2 3)
  set(CAPI_VERSION v${CAPI_VERSION_MAJOR}.${CAPI_VERSION_MINOR}.${CAPI_VERSION_PATCH})
  build_loadable_extension_capi_internal(${NAME} ${CAPI_VERSION} "C_STRUCT" ${FILES})
  target_compile_definitions(${NAME}_loadable_extension PRIVATE DUCKDB_EXTENSION_API_VERSION_MAJOR=${CAPI_VERSION_MAJOR})
  target_compile_definitions(${NAME}_loadable_extension PRIVATE DUCKDB_EXTENSION_API_VERSION_MINOR=${CAPI_VERSION_MINOR})
  target_compile_definitions(${NAME}_loadable_extension PRIVATE DUCKDB_EXTENSION_API_VERSION_PATCH=${CAPI_VERSION_PATCH})
  target_compile_definitions(${NAME}_loadable_extension PRIVATE DUCKDB_EXTENSION_NAME=${NAME})
endfunction()

function(build_loadable_extension_capi_unstable NAME PARAMETERS)
  set(FILES "${ARGV}")
  list(REMOVE_AT FILES 0)
  build_loadable_extension_capi_internal(${NAME} "" "C_STRUCT_UNSTABLE" ${FILES})
  target_compile_definitions(${NAME}_loadable_extension PRIVATE DUCKDB_EXTENSION_API_VERSION_UNSTABLE=${DUCKDB_NORMALIZED_VERSION})
  target_compile_definitions(${NAME}_loadable_extension PRIVATE DUCKDB_EXTENSION_NAME=${NAME})
endfunction()

function(build_loadable_extension_capi_internal NAME VERSION ABI_TYPE PARAMETERS)
  # all parameters after name
  set(FILES "${ARGV}")
  list(REMOVE_AT FILES 0 1 2)
  string(TOUPPER ${NAME} EXTENSION_NAME_UPPERCASE)

  build_loadable_extension_directory(${NAME} ${ABI_TYPE} "extension/${NAME}" "${DUCKDB_EXTENSION_${EXTENSION_NAME_UPPERCASE}_EXT_VERSION}" "${VERSION}" "${PARAMETERS}" ${FILES})
endfunction()

function(build_static_extension NAME PARAMETERS)
  # all parameters after name
  set(FILES "${ARGV}")
  list(REMOVE_AT FILES 0)
  add_library(${NAME}_extension STATIC ${FILES})
  target_link_libraries(${NAME}_extension duckdb_static)
endfunction()

# Internal extension register function
function(register_extension NAME DONT_LINK DONT_BUILD LOAD_TESTS PATH INCLUDE_PATH TEST_PATH LINKED_LIBS EXTENSION_VERSION)
  string(TOLOWER ${NAME} EXTENSION_NAME_LOWERCASE)
  string(TOUPPER ${NAME} EXTENSION_NAME_UPPERCASE)

  set(DUCKDB_EXTENSION_NAMES ${DUCKDB_EXTENSION_NAMES} ${EXTENSION_NAME_LOWERCASE} PARENT_SCOPE)

  if ("${LOAD_TESTS}")
    set(DUCKDB_EXTENSION_${EXTENSION_NAME_UPPERCASE}_LOAD_TESTS TRUE PARENT_SCOPE)
  else()
    set(DUCKDB_EXTENSION_${EXTENSION_NAME_UPPERCASE}_LOAD_TESTS FALSE PARENT_SCOPE)
  endif()
  set(LINK_EXTENSION TRUE)
  if (NOT ${BUILD_EXTENSIONS_ONLY})
    if (${DONT_LINK})
      set(LINK_EXTENSION FALSE)
    endif()
    if(DISABLE_BUILTIN_EXTENSIONS)
      if(${GENERATE_EXTENSION_ENTRIES})
        set(LINK_EXTENSION FALSE)
      elseif(${EXTENSION_NAME_UPPERCASE} STREQUAL "CORE_FUNCTIONS")
      else()
        set(LINK_EXTENSION FALSE)
      endif()
    endif()
  endif()
  set(DUCKDB_EXTENSION_${EXTENSION_NAME_UPPERCASE}_SHOULD_LINK ${LINK_EXTENSION} PARENT_SCOPE)

  set(DUCKDB_EXTENSION_${EXTENSION_NAME_UPPERCASE}_LINKED_LIBS "${LINKED_LIBS}" PARENT_SCOPE)

  # Allows explicitly disabling extensions that may be specified in other configurations
  if (NOT ${DONT_BUILD} AND NOT ${EXTENSION_TESTS_ONLY})
    set(DUCKDB_EXTENSION_${EXTENSION_NAME_UPPERCASE}_SHOULD_BUILD TRUE PARENT_SCOPE)
  elseif(NOT ${GENERATE_EXTENSION_ENTRIES} AND ${EXTENSION_NAME_UPPERCASE} STREQUAL "CORE_FUNCTIONS")
    set(DUCKDB_EXTENSION_${EXTENSION_NAME_UPPERCASE}_SHOULD_BUILD TRUE PARENT_SCOPE)
  else()
    set(DUCKDB_EXTENSION_${EXTENSION_NAME_UPPERCASE}_SHOULD_BUILD FALSE PARENT_SCOPE)
    set(DUCKDB_EXTENSION_${EXTENSION_NAME_UPPERCASE}_SHOULD_LINK FALSE PARENT_SCOPE)
  endif()

  if ("${PATH}" STREQUAL "")
    message(FATAL_ERROR "Invalid path set for extension '${NAME}' : '${INCLUDE}'")
  endif()
  if ("${INCLUDE_PATH}" STREQUAL "")
    message(FATAL_ERROR "Invalid include path for extension '${NAME}' : '${INCLUDE_PATH}'")
  endif()
  if ("${TEST_PATH}" STREQUAL "" AND "${LOAD_TESTS}")
    message(FATAL_ERROR "Invalid include path for extension '${NAME}' : '${INCLUDE_PATH}'")
  endif()

  set(DUCKDB_EXTENSION_${EXTENSION_NAME_UPPERCASE}_PATH ${PATH} PARENT_SCOPE)
  set(DUCKDB_EXTENSION_${EXTENSION_NAME_UPPERCASE}_INCLUDE_PATH ${INCLUDE_PATH} PARENT_SCOPE)
  set(DUCKDB_EXTENSION_${EXTENSION_NAME_UPPERCASE}_TEST_PATH ${TEST_PATH} PARENT_SCOPE)
  set(DUCKDB_EXTENSION_${EXTENSION_NAME_UPPERCASE}_EXT_VERSION ${EXTENSION_VERSION} PARENT_SCOPE)
endfunction()

# Downloads the external extension repo at the specified commit and calls register_extension
macro(register_external_extension NAME URL COMMIT DONT_LINK DONT_BUILD LOAD_TESTS PATH INCLUDE_PATH TEST_PATH APPLY_PATCHES LINKED_LIBS SUBMODULES EXTENSION_VERSION)
  include(FetchContent)
  if (${APPLY_PATCHES})
    set(PATCH_COMMAND ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/scripts/apply_extension_patches.py ${CMAKE_SOURCE_DIR}/.github/patches/extensions/${NAME}/)
  endif()
  FETCHCONTENT_DECLARE(
          ${NAME}_extension_fc
          GIT_REPOSITORY ${URL}
          GIT_TAG ${COMMIT}
          GIT_SUBMODULES "${SUBMODULES}"
          PATCH_COMMAND ${PATCH_COMMAND}
  )
  FETCHCONTENT_POPULATE(${NAME}_EXTENSION_FC)

  # Autogenerate version tag if not provided
  if ("${EXTENSION_VERSION}" STREQUAL "")
    duckdb_extension_generate_version(EXTERNAL_EXTENSION_VERSION ${${NAME}_extension_fc_SOURCE_DIR})
  else()
    set(EXTERNAL_EXTENSION_VERSION "${EXTENSION_VERSION}")
  endif()

  message(STATUS "Load extension '${NAME}' from ${URL} @ ${EXTERNAL_EXTENSION_VERSION}")

  string(TOUPPER ${NAME} EXTENSION_NAME_UPPERCASE)
  set(DUCKDB_EXTENSION_${EXTENSION_NAME_UPPERCASE}_EXT_VERSION "${EXTERNAL_EXTENSION_VERSION}" PARENT_SCOPE)

  if ("${INCLUDE_PATH}" STREQUAL "")
    set(INCLUDE_FULL_PATH "${${NAME}_extension_fc_SOURCE_DIR}/src/include")
  else()
    set(INCLUDE_FULL_PATH "${${NAME}_extension_fc_SOURCE_DIR}/${INCLUDE_PATH}")
  endif()

  if ("${TEST_PATH}" STREQUAL "")
    set(TEST_FULL_PATH "${${NAME}_extension_fc_SOURCE_DIR}/test/sql")
  else()
    set(TEST_FULL_PATH "${${NAME}_extension_fc_SOURCE_DIR}/${TEST_PATH}")
  endif()

  register_extension(${NAME} ${DONT_LINK} ${DONT_BUILD} ${LOAD_TESTS} ${${NAME}_extension_fc_SOURCE_DIR}/${PATH} "${INCLUDE_FULL_PATH}" "${TEST_FULL_PATH}" "${LINKED_LIBS}" "${EXTERNAL_EXTENSION_VERSION}")
endmacro()

# This function sets OUTPUT_VAR to the VERSION using DuckDB's standard versioning convention (using WORKING_DIR)
# TODO: unify this with the base DuckDB logic (note that this is slightly different as it has ReleaseCandidate tag support
function(duckdb_extension_generate_version OUTPUT_VAR WORKING_DIR)
  find_package(Git)
  if(Git_FOUND)
    execute_process(
            COMMAND ${GIT_EXECUTABLE} rev-parse --is-inside-work-tree
            WORKING_DIRECTORY ${WORKING_DIR}
            OUTPUT_VARIABLE IS_IN_GIT_DIR
            ERROR_QUIET
    )
  endif()
  if (IS_IN_GIT_DIR)
    execute_process(
            COMMAND ${GIT_EXECUTABLE} log -1 --format=%h
            WORKING_DIRECTORY ${WORKING_DIR}
            RESULT_VARIABLE GIT_RESULT
            OUTPUT_VARIABLE GIT_COMMIT_HASH
            OUTPUT_STRIP_TRAILING_WHITESPACE)
    if (GIT_RESULT)
      message(FATAL_ERROR "git is available (at ${GIT_EXECUTABLE}) but has failed to execute 'log -1 --format=%h'.")
    endif()
    execute_process(
            COMMAND ${GIT_EXECUTABLE} describe --tags --always --match '${VERSIONING_TAG_MATCH}'
            WORKING_DIRECTORY ${WORKING_DIR}
            RESULT_VARIABLE GIT_RESULT
            OUTPUT_VARIABLE GIT_DESCRIBE
            OUTPUT_STRIP_TRAILING_WHITESPACE)
    if (GIT_RESULT)
      set(VERSION "${GIT_COMMIT_HASH}")
    elseif (GIT_DESCRIBE MATCHES "^v[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9\.]+)?$")
      # We are on a valid SemVer version in the format v{MAJOR}.{MINOR}.{PATH}(-{RC})
      set(VERSION "${GIT_DESCRIBE}")
    else()
      set(VERSION "${GIT_COMMIT_HASH}")
    endif()
  else()
    # No git found, we set empty string
    set(VERSION "")
  endif()

  # Propagate the version
  set(${OUTPUT_VAR} ${VERSION} PARENT_SCOPE)
endfunction()

function(duckdb_extension_load NAME)
  # Parameter parsing
  set(options DONT_LINK DONT_BUILD LOAD_TESTS APPLY_PATCHES)
  set(oneValueArgs SOURCE_DIR INCLUDE_DIR TEST_DIR GIT_URL GIT_TAG SUBMODULES EXTENSION_VERSION LINKED_LIBS)
  cmake_parse_arguments(duckdb_extension_load "${options}" "${oneValueArgs}" "" ${ARGN})

  string(TOLOWER ${NAME} EXTENSION_NAME_LOWERCASE)
  string(TOUPPER ${NAME} EXTENSION_NAME_UPPERCASE)

  # If extension was set already, we ignore subsequent calls
  list (FIND DUCKDB_EXTENSION_NAMES ${EXTENSION_NAME_LOWERCASE} _index)
  if (${_index} GREATER -1)
    return()
  endif()

  list (FIND SKIP_EXTENSIONS ${EXTENSION_NAME_LOWERCASE} _index)
  if (${_index} GREATER -1)
    return()
  endif()

  # Remote Git extension
  if (${duckdb_extension_load_DONT_BUILD})
    register_extension(${NAME} "${duckdb_extension_load_DONT_LINK}" "${duckdb_extension_load_DONT_BUILD}" "" "" "" "" "" "${duckdb_extension_load_EXTENSION_VERSION}")
  elseif (NOT "${duckdb_extension_load_GIT_URL}" STREQUAL "")
    if ("${duckdb_extension_load_GIT_TAG}" STREQUAL "")
      message(FATAL_ERROR, "Git URL specified but no valid GIT_TAG was found for ${NAME} extension")
    endif()
    register_external_extension(${NAME} "${duckdb_extension_load_GIT_URL}" "${duckdb_extension_load_GIT_TAG}" "${duckdb_extension_load_DONT_LINK}" "${duckdb_extension_load_DONT_BUILD}" "${duckdb_extension_load_LOAD_TESTS}" "${duckdb_extension_load_SOURCE_DIR}" "${duckdb_extension_load_INCLUDE_DIR}" "${duckdb_extension_load_TEST_DIR}" "${duckdb_extension_load_APPLY_PATCHES}" "${duckdb_extension_load_LINKED_LIBS}" "${duckdb_extension_load_SUBMODULES}" "${duckdb_extension_load_EXTENSION_VERSION}")
    if (NOT "${duckdb_extension_load_EXTENSION_VERSION}" STREQUAL "")
      set(DUCKDB_EXTENSION_${EXTENSION_NAME_UPPERCASE}_EXT_VERSION "${duckdb_extension_load_EXTENSION_VERSION}" PARENT_SCOPE)
    endif()
  elseif (NOT "${duckdb_extension_load_SOURCE_DIR}" STREQUAL "")
    # Version detection
    if ("${duckdb_extension_load_EXTENSION_VERSION}" STREQUAL "")
      duckdb_extension_generate_version(EXT_VERSION ${duckdb_extension_load_SOURCE_DIR})
    else()
      set(EXT_VERSION ${duckdb_extension_load_EXTENSION_VERSION})
    endif()

    # Local extension, custom path
    message(STATUS "Load extension '${NAME}' from '${duckdb_extension_load_SOURCE_DIR}' @ ${EXT_VERSION}")

    # If no include path specified, use default
    if ("${duckdb_extension_load_INCLUDE_DIR}" STREQUAL "")
      set(INCLUDE_PATH_DEFAULT "${duckdb_extension_load_SOURCE_DIR}/src/include")
    else()
      set(INCLUDE_PATH_DEFAULT ${duckdb_extension_load_INCLUDE_DIR})
    endif()

    # If no test path specified, use default
    if ("${duckdb_extension_load_TEST_DIR}" STREQUAL "")
      set(TEST_PATH_DEFAULT "${duckdb_extension_load_SOURCE_DIR}/test/sql")
    else()
      set(TEST_PATH_DEFAULT ${duckdb_extension_load_TEST_DIR})
    endif()

    register_extension(${NAME} "${duckdb_extension_load_DONT_LINK}" "${duckdb_extension_load_DONT_BUILD}" "${duckdb_extension_load_LOAD_TESTS}" "${duckdb_extension_load_SOURCE_DIR}" "${INCLUDE_PATH_DEFAULT}" "${TEST_PATH_DEFAULT}" "${duckdb_extension_load_LINKED_LIBS}" "${EXT_VERSION}")
  elseif(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/extension_external/${NAME})
    # Local extension, default path
    message(STATUS "Load extension '${NAME}' from '${CMAKE_CURRENT_SOURCE_DIR}/extension_external' @ ${duckdb_extension_load_EXTENSION_VERSION}")
    register_extension(${NAME} ${duckdb_extension_load_DONT_LINK} "${duckdb_extension_load_DONT_BUILD}" "${duckdb_extension_load_LOAD_TESTS}"  "${CMAKE_CURRENT_SOURCE_DIR}/extension_external/${NAME}" "${CMAKE_CURRENT_SOURCE_DIR}/extension_external/${NAME}/src/include" "${CMAKE_CURRENT_SOURCE_DIR}/extension_external/${NAME}/test/sql" "${duckdb_extension_load_LINKED_LIBS}" "${duckdb_extension_load_EXTENSION_VERSION}")
  else()
    # For in-tree extensions of the default path, we set the extension version to GIT_COMMIT_HASH by default
    if ("${duckdb_extension_load_EXTENSION_VERSION}" STREQUAL "")
      set(duckdb_extension_load_EXTENSION_VERSION ${DUCKDB_NORMALIZED_VERSION})
    endif()

    # Local extension, default path
    message(STATUS "Load extension '${NAME}' from '${CMAKE_CURRENT_SOURCE_DIR}/extensions' @ ${duckdb_extension_load_EXTENSION_VERSION}")

    register_extension(${NAME} ${duckdb_extension_load_DONT_LINK} "${duckdb_extension_load_DONT_BUILD}" "${duckdb_extension_load_LOAD_TESTS}" "${CMAKE_CURRENT_SOURCE_DIR}/extension/${NAME}" "${CMAKE_CURRENT_SOURCE_DIR}/extension/${NAME}/include" "${CMAKE_CURRENT_SOURCE_DIR}/extension/${NAME}/test/sql" "${duckdb_extension_load_LINKED_LIBS}" "${duckdb_extension_load_EXTENSION_VERSION}")
  endif()

  # Propagate variables set by register_extension
  set(DUCKDB_EXTENSION_NAMES ${DUCKDB_EXTENSION_NAMES} PARENT_SCOPE)
  set(DUCKDB_EXTENSION_${EXTENSION_NAME_UPPERCASE}_SHOULD_BUILD ${DUCKDB_EXTENSION_${EXTENSION_NAME_UPPERCASE}_SHOULD_BUILD} PARENT_SCOPE)
  set(DUCKDB_EXTENSION_${EXTENSION_NAME_UPPERCASE}_SHOULD_LINK ${DUCKDB_EXTENSION_${EXTENSION_NAME_UPPERCASE}_SHOULD_LINK} PARENT_SCOPE)
  set(DUCKDB_EXTENSION_${EXTENSION_NAME_UPPERCASE}_LOAD_TESTS ${DUCKDB_EXTENSION_${EXTENSION_NAME_UPPERCASE}_LOAD_TESTS} PARENT_SCOPE)
  set(DUCKDB_EXTENSION_${EXTENSION_NAME_UPPERCASE}_PATH ${DUCKDB_EXTENSION_${EXTENSION_NAME_UPPERCASE}_PATH} PARENT_SCOPE)
  set(DUCKDB_EXTENSION_${EXTENSION_NAME_UPPERCASE}_INCLUDE_PATH ${DUCKDB_EXTENSION_${EXTENSION_NAME_UPPERCASE}_INCLUDE_PATH} PARENT_SCOPE)
  set(DUCKDB_EXTENSION_${EXTENSION_NAME_UPPERCASE}_TEST_PATH ${DUCKDB_EXTENSION_${EXTENSION_NAME_UPPERCASE}_TEST_PATH} PARENT_SCOPE)
  set(DUCKDB_EXTENSION_${EXTENSION_NAME_UPPERCASE}_LINKED_LIBS ${DUCKDB_EXTENSION_${EXTENSION_NAME_UPPERCASE}_LINKED_LIBS} PARENT_SCOPE)
  set(DUCKDB_EXTENSION_${EXTENSION_NAME_UPPERCASE}_EXT_VERSION "${DUCKDB_EXTENSION_${EXTENSION_NAME_UPPERCASE}_EXT_VERSION}" PARENT_SCOPE)
endfunction()

if(${EXPORT_DLL_SYMBOLS})
  # For Windows DLL export symbols
  add_definitions(-DDUCKDB_BUILD_LIBRARY)
endif()

# Log extensions that are built by directly passing cmake variables
foreach(EXT IN LISTS DUCKDB_EXTENSION_NAMES)
  if (NOT "${EXT}" STREQUAL "")
    string(TOUPPER ${EXT} EXTENSION_NAME_UPPERCASE)
    message(STATUS "Load extension '${EXT}' from '${DUCKDB_EXTENSION_${EXTENSION_NAME_UPPERCASE}_PATH}'")
  endif()
endforeach()

set(EXTENSION_CONFIG_BASE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/.github/config/extensions/")

if(DEFINED CORE_EXTENSIONS)
  message(DEPRECATION "CORE_EXTENSIONS is deprecated. Use BUILD_EXTENSIONS instead.")
  if(NOT DEFINED BUILD_EXTENSIONS)
    set(BUILD_EXTENSIONS ${CORE_EXTENSIONS})
  else()
    list(APPEND BUILD_EXTENSIONS ${CORE_EXTENSIONS})
  endif()
endif()

# Load extensions passed through cmake config var
foreach(EXT IN LISTS BUILD_EXTENSIONS)
  if(NOT "${EXT}" STREQUAL "")
    if (EXISTS "${EXTENSION_CONFIG_BASE_DIR}/${EXT}.cmake")
      # out-of-tree extension: load cmake file
      include("${EXTENSION_CONFIG_BASE_DIR}/${EXT}.cmake")
    else()
      # in-tree or non-existent extension: load it
      duckdb_extension_load(${EXT})
    endif()
  endif()
endforeach()

# Custom extension configs passed in DUCKDB_EXTENSION_CONFIGS parameter
foreach(DUCKDB_EXTENSION_CONFIG IN LISTS DUCKDB_EXTENSION_CONFIGS)
  if (NOT "${DUCKDB_EXTENSION_CONFIG}" STREQUAL "")
    include(${DUCKDB_EXTENSION_CONFIG})
  endif()
endforeach()

# Local extension config
if (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/extension/extension_config_local.cmake)
  include(${CMAKE_CURRENT_SOURCE_DIR}/extension/extension_config_local.cmake)
endif()

# Load base extension config
include(${CMAKE_CURRENT_SOURCE_DIR}/extension/extension_config.cmake)

# For extensions whose tests were loaded, but not linked into duckdb, we need to ensure they are registered to have
# the sqllogictest "require" statement load the loadable extensions instead of the baked in static one
foreach(EXT_NAME IN LISTS DUCKDB_EXTENSION_NAMES)
  string(TOUPPER ${EXT_NAME} EXT_NAME_UPPERCASE)
  if (NOT "${DUCKDB_EXTENSION_${EXT_NAME_UPPERCASE}_SHOULD_LINK}" AND "${DUCKDB_EXTENSION_${EXT_NAME_UPPERCASE}_LOAD_TESTS}")
    list(APPEND TEST_WITH_LOADABLE_EXTENSION ${EXT_NAME})
  endif()
endforeach()

if (BUILD_MAIN_DUCKDB_LIBRARY)
  add_subdirectory(src)
  add_subdirectory(tools)
endif()

# Add subdirectories for registered extensions
foreach(EXT_NAME IN LISTS DUCKDB_EXTENSION_NAMES)
  string(TOUPPER ${EXT_NAME} EXT_NAME_UPPERCASE)

  if (NOT DEFINED DUCKDB_EXTENSION_${EXT_NAME_UPPERCASE}_SHOULD_BUILD)
    set(DUCKDB_EXTENSION_${EXT_NAME_UPPERCASE}_SHOULD_BUILD TRUE)
  endif()

  # Skip explicitly disabled extensions
  if (NOT ${DUCKDB_EXTENSION_${EXT_NAME_UPPERCASE}_SHOULD_BUILD} OR ${EXTENSION_CONFIG_BUILD})
    continue()
  endif()

  # Warning for trying to load vcpkg extensions without having VCPKG_BUILD SET
  if (EXISTS "${DUCKDB_EXTENSION_${EXT_NAME_UPPERCASE}_PATH}/vcpkg.json" AND NOT DEFINED VCPKG_BUILD)
    message(WARNING "Extension '${EXT_NAME}' has a vcpkg.json, but build was not run with VCPKG. If build fails, check out VCPKG build instructions in 'duckdb/extension/README.md' or try manually installing the dependencies in ${DUCKDB_EXTENSION_${EXT_NAME_UPPERCASE}_PATH}/vcpkg.json")
  endif()

  if (NOT "${DUCKDB_EXTENSION_${EXT_NAME_UPPERCASE}_EXT_VERSION}" STREQUAL "")
    add_definitions(-DEXT_VERSION_${EXT_NAME_UPPERCASE}="${DUCKDB_EXTENSION_${EXT_NAME_UPPERCASE}_EXT_VERSION}")
  endif()

  if (DEFINED DUCKDB_EXTENSION_${EXT_NAME_UPPERCASE}_PATH)
    add_subdirectory(${DUCKDB_EXTENSION_${EXT_NAME_UPPERCASE}_PATH} extension/${EXT_NAME})
  else()
    message(FATAL_ERROR "No path found for registered extension '${EXT_NAME}'")
  endif()

  if (NOT "${DUCKDB_EXTENSION_${EXT_NAME_UPPERCASE}_EXT_VERSION}" STREQUAL "")
    remove_definitions(-DEXT_VERSION_${EXT_NAME_UPPERCASE}="${DUCKDB_EXTENSION_${EXT_NAME_UPPERCASE}_EXT_VERSION}")
  endif()
endforeach()

# Output the extensions that we linked into DuckDB for some nice build logs
set(LINKED_EXTENSIONS "")
set(NONLINKED_EXTENSIONS "")
set(SKIPPED_EXTENSIONS "")
set(TEST_LOADED_EXTENSIONS "")
foreach(EXT_NAME IN LISTS DUCKDB_EXTENSION_NAMES)
  string(TOUPPER ${EXT_NAME} EXT_NAME_UPPERCASE)
  if (NOT ${DUCKDB_EXTENSION_${EXT_NAME_UPPERCASE}_SHOULD_BUILD})
    list(APPEND SKIPPED_EXTENSIONS ${EXT_NAME})
  elseif (${DUCKDB_EXTENSION_${EXT_NAME_UPPERCASE}_SHOULD_LINK})
    list(APPEND LINKED_EXTENSIONS ${EXT_NAME})
  else()
    list(APPEND NONLINKED_EXTENSIONS ${EXT_NAME})
  endif()

  if (${DUCKDB_EXTENSION_${EXT_NAME_UPPERCASE}_LOAD_TESTS})
    list(APPEND TEST_LOADED_EXTENSIONS ${EXT_NAME})
  endif()
endforeach()

if(NOT "${LINKED_EXTENSIONS}" STREQUAL "")
  string(REPLACE ";"  ", " EXT_LIST_DEBUG_MESSAGE "${LINKED_EXTENSIONS}")
  message(STATUS "Extensions linked into DuckDB: [${EXT_LIST_DEBUG_MESSAGE}]")
endif()
if(NOT "${NONLINKED_EXTENSIONS}" STREQUAL "")
  string(REPLACE ";"  ", " EXT_LIST_DEBUG_MESSAGE "${NONLINKED_EXTENSIONS}")
  message(STATUS "Extensions built but not linked: [${EXT_LIST_DEBUG_MESSAGE}]")
endif()
if(NOT "${SKIPPED_EXTENSIONS}" STREQUAL "")
  string(REPLACE ";"  ", " EXT_LIST_DEBUG_MESSAGE "${SKIPPED_EXTENSIONS}")
  message(STATUS "Extensions explicitly skipped: [${EXT_LIST_DEBUG_MESSAGE}]")
endif()
if(NOT "${TEST_LOADED_EXTENSIONS}" STREQUAL "")
  string(REPLACE ";"  ", " EXT_LIST_DEBUG_MESSAGE "${TEST_LOADED_EXTENSIONS}")
  message(STATUS "Tests loaded for extensions: [${EXT_LIST_DEBUG_MESSAGE}]")
endif()

# Special build where instead of building duckdb, we produce several artifact that require parsing the
# extension config, such as a merged vcpg.json file for extension dependencies.
if(${EXTENSION_CONFIG_BUILD})
  set(VCPKG_PATHS "")
  set(VCPKG_NAMES "")
  foreach(EXT_NAME IN LISTS DUCKDB_EXTENSION_NAMES)
    string(TOUPPER ${EXT_NAME} EXT_NAME_UPPERCASE)
    if (EXISTS "${DUCKDB_EXTENSION_${EXT_NAME_UPPERCASE}_PATH}/vcpkg.json")
      list(APPEND VCPKG_NAMES ${EXT_NAME})
      list(APPEND VCPKG_PATHS ${DUCKDB_EXTENSION_${EXT_NAME_UPPERCASE}_PATH}/vcpkg.json)
    endif()
  endforeach()

  add_custom_target(
          duckdb_merge_vcpkg_manifests ALL
          COMMAND  ${Python3_EXECUTABLE} scripts/merge_vcpkg_deps.py ${VCPKG_PATHS} ${EXT_NAMES}
          WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
          COMMENT Generates a shared vcpkg manifest from the individual extensions)
  string(REPLACE ";"  ", " VCPKG_NAMES_COMMAS "${VCPKG_NAMES}")
  Message(STATUS "Combined vcpkg manifest created from extensions: ${VCPKG_NAMES_COMMAS}")

  # Write linked extensions that will be built to extensions_linked.txt
  FILE(WRITE ${CMAKE_BINARY_DIR}/extensions.csv "")
  FILE(APPEND ${CMAKE_BINARY_DIR}/extensions.csv "name, version\r")
  foreach(EXT_NAME IN LISTS DUCKDB_EXTENSION_NAMES)
    string(TOUPPER ${EXT_NAME} EXT_NAME_UPPERCASE)
    if (${DUCKDB_EXTENSION_${EXT_NAME_UPPERCASE}_SHOULD_BUILD})
      FILE(APPEND ${CMAKE_BINARY_DIR}/extensions.csv "${EXT_NAME}, \"${DUCKDB_EXTENSION_${EXT_NAME_UPPERCASE}_EXT_VERSION}\"\r")
    endif()
  endforeach()

  return()
endif()

if(NOT DUCKDB_EXPLICIT_PLATFORM)
  set(VERSION_SOURCES tools/utils/test_platform.cpp)

  add_executable(duckdb_platform_binary ${VERSION_SOURCES})
  link_threads(duckdb_platform_binary "")

  set_target_properties(duckdb_platform_binary PROPERTIES OUTPUT_NAME duckdb_platform_binary)
  set_target_properties(duckdb_platform_binary PROPERTIES RUNTIME_OUTPUT_DIRECTORY
                                         ${PROJECT_BINARY_DIR})
  add_custom_command(
          OUTPUT ${PROJECT_BINARY_DIR}/duckdb_platform_out
          DEPENDS duckdb_platform_binary
	  COMMAND $<TARGET_FILE:duckdb_platform_binary> > ${PROJECT_BINARY_DIR}/duckdb_platform_out || ( echo "Provide explicit DUCKDB_PLATFORM=your_target_arch to avoid build-type detection of the platform" && exit 1 )
          WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
          )
else()
  add_custom_command(
          OUTPUT ${PROJECT_BINARY_DIR}/duckdb_platform_out
          COMMAND
          ${CMAKE_COMMAND} -E echo_append \"${DUCKDB_EXPLICIT_PLATFORM}\" > ${PROJECT_BINARY_DIR}/duckdb_platform_out
          WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
          )
endif()
add_custom_target(duckdb_platform DEPENDS ${PROJECT_BINARY_DIR}/duckdb_platform_out)

if(NOT CLANG_TIDY)
  if(${BUILD_UNITTESTS})
    add_subdirectory(test)
    if(NOT WIN32
      AND NOT SUN
      AND ${BUILD_BENCHMARKS})
      add_subdirectory(benchmark)
    endif()
  endif()
  if (NOT EXTENSION_CONFIG_BUILD)
    add_subdirectory(third_party)
  endif()
endif()

# Write the export set for build and install tree
install(EXPORT "${DUCKDB_EXPORT_SET}" DESTINATION "${INSTALL_CMAKE_DIR}")
export(EXPORT "${DUCKDB_EXPORT_SET}"
       FILE "${PROJECT_BINARY_DIR}/${DUCKDB_EXPORT_SET}.cmake")

# Only write the cmake package configuration if the templates exist
set(CMAKE_CONFIG_TEMPLATE "${CMAKE_CURRENT_SOURCE_DIR}/DuckDBConfig.cmake.in")
set(CMAKE_CONFIG_VERSION_TEMPLATE
    "${CMAKE_CURRENT_SOURCE_DIR}/DuckDBConfigVersion.cmake.in")
if(EXISTS ${CMAKE_CONFIG_TEMPLATE} AND EXISTS ${CMAKE_CONFIG_VERSION_TEMPLATE})

  # Configure cmake package config for the build tree
  set(CONF_INCLUDE_DIRS "${PROJECT_SOURCE_DIR}/src/include")
  configure_file(${CMAKE_CONFIG_TEMPLATE}
                 "${PROJECT_BINARY_DIR}/DuckDBConfig.cmake" @ONLY)

  # Configure cmake package config for the install tree
  file(RELATIVE_PATH REL_INCLUDE_DIR "${CMAKE_INSTALL_PREFIX}/${INSTALL_CMAKE_DIR}"
    "${CMAKE_INSTALL_PREFIX}/${INSTALL_INCLUDE_DIR}")
  set(CONF_INCLUDE_DIRS "\${DuckDB_CMAKE_DIR}/${REL_INCLUDE_DIR}")
  configure_file(
    ${CMAKE_CONFIG_TEMPLATE}
    "${PROJECT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/DuckDBConfig.cmake" @ONLY)

  # Configure cmake package version for build and install tree
  configure_file(${CMAKE_CONFIG_VERSION_TEMPLATE}
                 "${PROJECT_BINARY_DIR}/DuckDBConfigVersion.cmake" @ONLY)

  # Install the cmake package
  install(
    FILES "${PROJECT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/DuckDBConfig.cmake"
          "${PROJECT_BINARY_DIR}/DuckDBConfigVersion.cmake"
    DESTINATION "${INSTALL_CMAKE_DIR}")
endif()
