# Copyright (C) 1995-2019, Rene Brun and Fons Rademakers.
# All rights reserved.
#
# For the licensing terms see $ROOTSYS/LICENSE.
# For the list of contributors see $ROOTSYS/README/CREDITS.

#---------------------------------------------------------------------------------------------------
#  RootMacros.cmake
#---------------------------------------------------------------------------------------------------

if(WIN32)
  set(libprefix lib)
  set(ld_library_path PATH)
  set(libsuffix .dll)
  set(localruntimedir ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
  set(runtimedir ${CMAKE_INSTALL_BINDIR})
elseif(APPLE)
  set(ld_library_path DYLD_LIBRARY_PATH)
  set(ld_preload DYLD_INSERT_LIBRARIES)
  set(libprefix ${CMAKE_SHARED_LIBRARY_PREFIX})
  if(CMAKE_PROJECT_NAME STREQUAL ROOT)
    set(libsuffix .so)
  else()
    set(libsuffix ${CMAKE_SHARED_LIBRARY_SUFFIX})
  endif()
  set(localruntimedir ${CMAKE_LIBRARY_OUTPUT_DIRECTORY})
  set(runtimedir ${CMAKE_INSTALL_PYTHONDIR})
else()
  set(ld_library_path LD_LIBRARY_PATH)
  set(ld_preload LD_PRELOAD)
  set(libprefix ${CMAKE_SHARED_LIBRARY_PREFIX})
  set(libsuffix ${CMAKE_SHARED_LIBRARY_SUFFIX})
  set(localruntimedir ${CMAKE_LIBRARY_OUTPUT_DIRECTORY})
  set(runtimedir ${CMAKE_INSTALL_PYTHONDIR})
endif()

if(CMAKE_GENERATOR MATCHES "Visual Studio")
  set(GeneratorNeedsResourecLock False)
  set(GeneratorNeedsBuildSerialization False)
  if(winrtdebug)
    set(build_config "--config Debug")
  else()
    set(build_config "--config $<IF:$<CONFIG:Debug,RelWithDebInfo>,RelWithDebInfo,Release>")
  endif()
endif()

if(CMAKE_GENERATOR MATCHES Ninja)
  set(GeneratorNeedsResourecLock True)
  set(GeneratorNeedsBuildSerialization True)
endif()

set(ROOT_LIBRARY_PROPERTIES_NO_VERSION ${ROOT_LIBRARY_PROPERTIES_NO_VERSION}
    SUFFIX ${libsuffix}
    PREFIX ${libprefix} )
if(soversion)
  set(ROOT_LIBRARY_PROPERTIES ${ROOT_LIBRARY_PROPERTIES} ${ROOT_LIBRARY_PROPERTIES_NO_VERSION}
      VERSION ${ROOT_VERSION}
      SOVERSION ${ROOT_MAJOR_VERSION}.${ROOT_MINOR_VERSION} )
else()
  set(ROOT_LIBRARY_PROPERTIES ${ROOT_LIBRARY_PROPERTIES} ${ROOT_LIBRARY_PROPERTIES_NO_VERSION} )
endif()

include(CMakeParseArguments)

#---------------------------------------------------------------------------------------------------
#---ROOT_GLOB_FILES( <variable> [REALTIVE path] [FILTER regexp] <sources> ...)
#---------------------------------------------------------------------------------------------------
function(ROOT_GLOB_FILES variable)
  CMAKE_PARSE_ARGUMENTS(ARG "RECURSE" "RELATIVE;FILTER" "" ${ARGN})
  set(_possibly_recurse "")
  if (ARG_RECURSE)
    set(_possibly_recurse "_RECURSE")
  endif()
  if(ARG_RELATIVE)
    file(GLOB${_possibly_recurse} _sources RELATIVE ${ARG_RELATIVE} ${ARG_UNPARSED_ARGUMENTS})
  else()
    file(GLOB${_possibly_recurse} _sources ${ARG_UNPARSED_ARGUMENTS})
  endif()
  if(ARG_FILTER)
    foreach(s ${_sources})
      if(s MATCHES ${ARG_FILTER})
        list(REMOVE_ITEM _sources ${s})
      endif()
    endforeach()
  endif()
  set(${variable} ${_sources} PARENT_SCOPE)
endfunction()

function(ROOT_GLOB_SOURCES variable)
  ROOT_GLOB_FILES(_sources FILTER "(^|/)G__" ${ARGN})
  set(${variable} ${_sources} PARENT_SCOPE)
endfunction()

function(ROOT_GLOB_HEADERS variable)
  ROOT_GLOB_FILES(_sources FILTER "LinkDef" ${ARGN})
  set(${variable} ${_sources} PARENT_SCOPE)
endfunction()

#---------------------------------------------------------------------------------------------------
#---ROOT_GET_SOURCES( <variable> cwd <sources> ...)
#---------------------------------------------------------------------------------------------------
function(ROOT_GET_SOURCES variable cwd )
  set(sources)
  foreach( fp ${ARGN})
    if( IS_ABSOLUTE ${fp})
      file(GLOB files ${fp})
    else()
      if(root7)
        set(root7glob v7/src/${fp})
      endif()
      file(GLOB files RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} ${cwd}/${fp} ${root7glob})
    endif()
    if(files)
      foreach(s ${files})
        if(fp MATCHES "[*]" AND s MATCHES "(^|/)G__") # Eliminate G__* files
        elseif(s MATCHES "${cwd}/G__")
          set(sources ${fp} ${sources})
        else()
          set(sources ${sources} ${s})
        endif()
      endforeach()
    else()
      if(fp MATCHES "(^|/)G__")
        set(sources ${fp} ${sources})
      else()
        set(sources ${sources} ${fp})
      endif()
    endif()
  endforeach()
  set(${variable} ${sources} PARENT_SCOPE)
endfunction()

#---------------------------------------------------------------------------------------------------
#---REFLEX_GENERATE_DICTIONARY( dictionary headerfiles SELECTION selectionfile OPTIONS opt1 opt2 ...
#                               DEPENDS dependency1 dependency2 ...
#                             )
# if dictionary is a TARGET (e.g., created with add_library), we inherit the INCLUDE_DIRECTORES and
# COMPILE_DEFINITIONS properties
#
#---------------------------------------------------------------------------------------------------
function(REFLEX_GENERATE_DICTIONARY dictionary)
  CMAKE_PARSE_ARGUMENTS(ARG "" "SELECTION" "OPTIONS;DEPENDS" ${ARGN})
  #---Get List of header files---------------
  set(headerfiles)
  foreach(fp ${ARG_UNPARSED_ARGUMENTS})
    file(GLOB files inc/${fp})
    if(files)
      foreach(f ${files})
        if(NOT f MATCHES LinkDef)
          set(headerfiles ${headerfiles} ${f})
        endif()
      endforeach()
    elseif(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${fp})
      set(headerfiles ${headerfiles} ${CMAKE_CURRENT_SOURCE_DIR}/${fp})
    else()
      set(headerfiles ${headerfiles} ${fp})
    endif()
  endforeach()
  #---Get Selection file------------------------------------
  if(IS_ABSOLUTE ${ARG_SELECTION})
    set(selectionfile ${ARG_SELECTION})
  else()
    set(selectionfile ${CMAKE_CURRENT_SOURCE_DIR}/${ARG_SELECTION})
  endif()

  set(gensrcdict ${dictionary}.cxx)

  #---roottest compability---------------------------------
  if(CMAKE_ROOTTEST_NOROOTMAP)
    set(rootmapname )
    set(rootmapopts )
  elseif(DEFINED CMAKE_ROOTTEST_NOROOTMAP)  # Follow the roottest dictionary library naming
    set(rootmapname ${dictionary}.rootmap)
    set(rootmapopts --rootmap=${rootmapname} --rootmap-lib=${libprefix}${dictionary}_dictrflx)
  else()
    set(rootmapname ${dictionary}Dict.rootmap)
    set(rootmapopts --rootmap=${rootmapname} --rootmap-lib=${libprefix}${dictionary}Dict)
  endif()

  set(include_dirs ${CMAKE_CURRENT_SOURCE_DIR})
  get_directory_property(incdirs INCLUDE_DIRECTORIES)
  foreach(d ${incdirs})
    if(NOT "${d}" MATCHES "^(AFTER|BEFORE|INTERFACE|PRIVATE|PUBLIC|SYSTEM)$")
      list(APPEND include_dirs ${d})
    endif()
  endforeach()

  get_directory_property(defs COMPILE_DEFINITIONS)
  foreach( d ${defs})
   list(APPEND definitions ${d})
  endforeach()

  IF(TARGET ${dictionary})
    LIST(APPEND include_dirs $<TARGET_PROPERTY:${dictionary},INCLUDE_DIRECTORIES>)
    # The COMPILE_DEFINITIONS list might contain empty elements. These are
    # removed with the FILTER generator expression, excluding elements that
    # match the ^$ regexp (only matches empty strings).
    LIST(APPEND definitions "$<FILTER:$<TARGET_PROPERTY:${dictionary},COMPILE_DEFINITIONS>,EXCLUDE,^$>")
  ENDIF()

  add_custom_command(
    OUTPUT ${gensrcdict} ${rootmapname}
    COMMAND ${ROOT_genreflex_CMD}
    ARGS ${headerfiles} -o ${gensrcdict} ${rootmapopts} --select=${selectionfile}
         ${ARG_OPTIONS}
         "-I$<JOIN:$<REMOVE_DUPLICATES:$<FILTER:${include_dirs},EXCLUDE,^$>>,;-I>"
         "$<$<BOOL:$<JOIN:${definitions},>>:-D$<JOIN:${definitions},;-D>>"
    DEPENDS ${headerfiles} ${selectionfile} ${ARG_DEPENDS}

    COMMAND_EXPAND_LISTS
    )
  IF(TARGET ${dictionary})
    target_sources(${dictionary} PRIVATE ${gensrcdict})
  ENDIF()

  #---roottest compability---------------------------------
  if(CMAKE_ROOTTEST_DICT)
    ROOTTEST_TARGETNAME_FROM_FILE(targetname ${dictionary})

    set(targetname "${targetname}-dictgen")

    add_custom_target(${targetname} DEPENDS ${gensrcdict} ${ROOT_LIBRARIES})
  else()
    set(targetname "${dictionary}-dictgen")
    # Creating this target at ALL level enables the possibility to generate dictionaries (genreflex step)
    # well before the dependent libraries of the dictionary are build
    add_custom_target(${targetname} ALL DEPENDS ${gensrcdict})
  endif()

  # FIXME: Do not set gensrcdict variable to the outer scope but use an argument to
  # REFLEX_GENERATE_DICTIONARY passed from the outside. Note this would be a
  # breaking change for roottest and other external users.
  set(gensrcdict ${dictionary}.cxx PARENT_SCOPE)

endfunction()

#---------------------------------------------------------------------------------------------------
#---ROOT_GET_LIBRARY_OUTPUT_DIR( result_var )
# Returns the path to the .so file or .dll file. In the latter case Windows defines the dll files as
# executables and puts them in the $ROOTSYS/bin folder.
function(ROOT_GET_LIBRARY_OUTPUT_DIR result)
  set(library_output_dir)
  if(MSVC)
    if(DEFINED CMAKE_RUNTIME_OUTPUT_DIRECTORY AND NOT CMAKE_RUNTIME_OUTPUT_DIRECTORY STREQUAL "")
      set(library_output_dir ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
    else()
      set(library_output_dir ${CMAKE_CURRENT_BINARY_DIR})
    endif()
  else()
    if(DEFINED CMAKE_LIBRARY_OUTPUT_DIRECTORY AND NOT CMAKE_LIBRARY_OUTPUT_DIRECTORY STREQUAL "")
      set(library_output_dir ${CMAKE_LIBRARY_OUTPUT_DIRECTORY})
    else()
      set(library_output_dir ${CMAKE_CURRENT_BINARY_DIR})
    endif()
  endif()
  SET(${result} "${library_output_dir}" PARENT_SCOPE)
endfunction(ROOT_GET_LIBRARY_OUTPUT_DIR)

#---------------------------------------------------------------------------------------------------
#---ROOT_GET_INSTALL_DIR( result_var )
# Returns the path to the shared libraries installation directory. On Windows the pcms and rootmap
# files must go in the $ROOTSYS/bin folder.
function(ROOT_GET_INSTALL_DIR result)
  set(shared_lib_install_dir)
  if(MSVC)
    set(shared_lib_install_dir ${CMAKE_INSTALL_BINDIR})
  else()
    set(shared_lib_install_dir ${CMAKE_INSTALL_LIBDIR})
  endif()
  SET(${result} "${shared_lib_install_dir}" PARENT_SCOPE)
endfunction(ROOT_GET_INSTALL_DIR)

#---------------------------------------------------------------------------------------------------
#---ROOT_REPLACE_BUILD_INTERFACE( include_dir_var include_dir )
# Update the `include_dir` variable after resolve the BUILD_INTERFACE
function(ROOT_REPLACE_BUILD_INTERFACE include_dir_var include_dir)
  string(REGEX REPLACE "^[$]<BUILD_INTERFACE:(.+)>" "\\1" include_dir ${include_dir})
  # BUILD_INTERFACE might contain space-separated paths. They are split by
  # foreach, leaving a trailing 'include/something>'. Remove the trailing '>'.
  string(REGEX REPLACE ">$" "" include_dir ${include_dir})
  set(${include_dir_var} ${include_dir} PARENT_SCOPE)
endfunction(ROOT_REPLACE_BUILD_INTERFACE)

#---------------------------------------------------------------------------------------------------
#---ROOT_GENERATE_DICTIONARY( dictionary headerfiles NODEPHEADERS ghdr1 ghdr2 ...
#                                                    MODULE module DEPENDENCIES dep1 dep2
#                                                    BUILTINS dep1 dep2
#                                                    STAGE1 LINKDEF linkdef OPTIONS opt1 opt2 ...)
#
# <dictionary> is the dictionary stem; the macro creates (among other files) the dictionary source as
#   <dictionary>.cxx
# <headerfiles> are "as included"; set appropriate INCLUDE_DIRECTORIES property on the directory.
#   The dictionary target depends on these headers. These files must exist.
# <NODEPHEADERS> same as <headerfiles>. If these files are not found (given the target include path)
#   no error is emitted. The dictionary does not depend on these headers.
#---------------------------------------------------------------------------------------------------
function(ROOT_GENERATE_DICTIONARY dictionary)
  CMAKE_PARSE_ARGUMENTS(ARG "STAGE1;MULTIDICT;NOINSTALL;NO_CXXMODULE"
    "MODULE;LINKDEF" "NODEPHEADERS;OPTIONS;DEPENDENCIES;EXTRA_DEPENDENCIES;BUILTINS" ${ARGN})

  # Check if OPTIONS start with a dash.
  if (ARG_OPTIONS)
    foreach(ARG_O ${ARG_OPTIONS})
      if (NOT ARG_O MATCHES "^-*")
        message(FATAL_ERROR "Wrong rootcling option: ${ARG_OPTIONS}")
      endif()
    endforeach()
  endif(ARG_OPTIONS)

  #---roottest compability---------------------------------
  if(CMAKE_ROOTTEST_DICT)
    set(CMAKE_INSTALL_LIBDIR ${CMAKE_CURRENT_BINARY_DIR})
    set(libprefix "")
  endif()

  # list of include directories for dictionary generation
  set(incdirs)

  if((CMAKE_PROJECT_NAME STREQUAL ROOT) AND (TARGET ${ARG_MODULE}))
    set(headerdirs)

    get_target_property(target_incdirs ${ARG_MODULE} INCLUDE_DIRECTORIES)
    if(target_incdirs)
       foreach(dir ${target_incdirs})
          ROOT_REPLACE_BUILD_INTERFACE(dir ${dir})
          # check that dir not a empty dir like $<BUILD_INTERFACE:>
          if(NOT ${dir} MATCHES "^[$]")
            list(APPEND incdirs ${dir})
            string(FIND ${dir} "${CMAKE_SOURCE_DIR}" src_dir_in_dir)
            if(${src_dir_in_dir} EQUAL 0)
              list(APPEND headerdirs ${dir})
            endif()
          endif()
       endforeach()
    endif()

    # Comments from Vassil:
    # FIXME: We prepend ROOTSYS/include because if we have built a module
    # and try to resolve the 'same' header from a different location we will
    # get a redefinition error.
    # We should remove these lines when the fallback include is removed. Then
    # we will need a module.modulemap file per `inc` directory.
    # Comments from Sergey:
    # Remove all source dirs also while they preserved in root dictionaries and
    # ends in the gInterpreter->GetIncludePath()

    list(FILTER incdirs EXCLUDE REGEX "^${CMAKE_SOURCE_DIR}")
    list(FILTER incdirs EXCLUDE REGEX "^${CMAKE_BINARY_DIR}/ginclude")
    list(FILTER incdirs EXCLUDE REGEX "^${CMAKE_BINARY_DIR}/externals")
    list(FILTER incdirs EXCLUDE REGEX "^${CMAKE_BINARY_DIR}/builtins")
    list(INSERT incdirs 0 ${CMAKE_BINARY_DIR}/include)

    # this instruct rootcling do not store such paths in dictionary
    set(excludepaths ${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR}/ginclude ${CMAKE_BINARY_DIR}/externals ${CMAKE_BINARY_DIR}/builtins)

    set(headerfiles)
    set(_list_of_header_dependencies)
    foreach(fp ${ARG_UNPARSED_ARGUMENTS})
       if(IS_ABSOLUTE ${fp})
          set(headerFile ${fp})
       else()
          find_file(headerFile ${fp}
                  HINTS ${headerdirs}
                  NO_DEFAULT_PATH
                  NO_SYSTEM_ENVIRONMENT_PATH
                  NO_CMAKE_FIND_ROOT_PATH)
       endif()
       if(NOT headerFile)
          message(FATAL_ERROR "Cannot find header ${fp} to generate dictionary ${dictionary} for. Did you forget to set the INCLUDE_DIRECTORIES property for the current directory?")
       endif()
       list(APPEND headerfiles ${fp})
       list(APPEND _list_of_header_dependencies ${headerFile})
       unset(headerFile CACHE) # find_file, forget headerFile!
    endforeach()

    foreach(fp ${ARG_NODEPHEADERS})
      list(APPEND headerfiles ${fp})
      # no dependency - think "vector" etc.
    endforeach()

    if(NOT (headerfiles OR ARG_LINKDEF))
      message(FATAL_ERROR "No headers nor LinkDef.h supplied / found for dictionary ${dictionary}!")
    endif()

  else()

    ####################### old-style includes/headers generation - starts ##################

    #---Get the list of include directories------------------
    get_directory_property(incdirs INCLUDE_DIRECTORIES)
    # rootcling invoked on foo.h should find foo.h in the current source dir,
    # no matter what.
    list(APPEND incdirs ${CMAKE_CURRENT_SOURCE_DIR})

    if(TARGET ${ARG_MODULE})
      get_target_property(target_incdirs ${ARG_MODULE} INCLUDE_DIRECTORIES)
      if(target_incdirs)
        foreach(dir ${target_incdirs})
          ROOT_REPLACE_BUILD_INTERFACE(dir ${dir})
          if(NOT ${dir} MATCHES "^[$]")
            list(APPEND incdirs ${dir})
          endif()
        endforeach()
      endif()
    endif()

    set(headerdirs_dflt)

    if(CMAKE_PROJECT_NAME STREQUAL ROOT)
      if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/inc)
        list(APPEND headerdirs_dflt ${CMAKE_CURRENT_SOURCE_DIR}/inc)
      endif()
      if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/v7/inc)
        list(APPEND headerdirs_dflt ${CMAKE_CURRENT_SOURCE_DIR}/v7/inc)
      endif()
    endif()

    #---Get the list of header files-------------------------
    # CMake needs dependencies from ${CMAKE_CURRENT_SOURCE_DIR} while rootcling wants
    # header files "as included" (and thus as passed as argument to this CMake function).
    set(headerfiles)
    set(_list_of_header_dependencies)
    foreach(fp ${ARG_UNPARSED_ARGUMENTS})
      if(${fp} MATCHES "[*?]") # Is this header a globbing expression?
        file(GLOB files inc/${fp} ${fp}) # Elements of ${fp} have the complete path.
        foreach(f ${files})
          if(NOT f MATCHES LinkDef) # skip LinkDefs from globbing result
            set(add_inc_as_include On)
            string(REGEX REPLACE "^${CMAKE_CURRENT_SOURCE_DIR}/inc/" "" f_no_inc ${f})
            list(APPEND headerfiles ${f_no_inc})
            list(APPEND _list_of_header_dependencies ${f})
          endif()
        endforeach()
      else()
        if(IS_ABSOLUTE ${fp})
          set(headerFile ${fp})
        else()
          set(incdirs_in_build)
          set(incdirs_in_prefix ${headerdirs_dflt})
          foreach(incdir ${incdirs})
            string(FIND ${incdir} "${CMAKE_SOURCE_DIR}" src_dir_in_dir)
            string(FIND ${incdir} "${CMAKE_BINARY_DIR}" bin_dir_in_dir)
            string(FIND ${incdir} "${CMAKE_CURRENT_BINARY_DIR}" cur_dir_in_dir)
            if(NOT IS_ABSOLUTE ${incdir}
               OR ${src_dir_in_dir} EQUAL 0
               OR ${bin_dir_in_dir} EQUAL 0
               OR ${cur_dir_in_dir} EQUAL 0)
              list(APPEND incdirs_in_build ${incdir})
            else()
              list(APPEND incdirs_in_prefix ${incdir})
            endif()
          endforeach()
          if(incdirs_in_build)
            find_file(headerFile ${fp}
              HINTS ${incdirs_in_build}
              NO_DEFAULT_PATH
              NO_SYSTEM_ENVIRONMENT_PATH
              NO_CMAKE_FIND_ROOT_PATH)
          endif()
          # Try this even if NOT incdirs_in_prefix: might not need a HINT.
          if(NOT headerFile)
            find_file(headerFile ${fp}
              HINTS ${incdirs_in_prefix}
              NO_DEFAULT_PATH
              NO_SYSTEM_ENVIRONMENT_PATH)
          endif()
        endif()
        if(NOT headerFile)
          message(FATAL_ERROR "Cannot find header ${fp} to generate dictionary ${dictionary} for. Did you forget to set the INCLUDE_DIRECTORIES property for the current directory?")
        endif()
        list(APPEND headerfiles ${fp})
        list(APPEND _list_of_header_dependencies ${headerFile})
        unset(headerFile CACHE) # find_file, forget headerFile!
      endif()
    endforeach()

    foreach(fp ${ARG_NODEPHEADERS})
      list(APPEND headerfiles ${fp})
      # no dependency - think "vector" etc.
    endforeach()

    if(NOT (headerfiles OR ARG_LINKDEF))
      message(FATAL_ERROR "No headers nor LinkDef.h supplied / found for dictionary ${dictionary}!")
    endif()

    if(CMAKE_PROJECT_NAME STREQUAL ROOT)
      list(APPEND incdirs ${CMAKE_BINARY_DIR}/include)
      list(APPEND incdirs ${CMAKE_BINARY_DIR}/etc/cling) # This is for the RuntimeUniverse
      # list(APPEND incdirs ${CMAKE_SOURCE_DIR})
      set(excludepaths ${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR})
    elseif(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/inc)
      list(APPEND incdirs ${CMAKE_CURRENT_SOURCE_DIR}/inc)
    endif()

    foreach(dep ${ARG_DEPENDENCIES})
      if(TARGET ${dep})
        get_target_property(dep_include_dirs ${dep} INTERFACE_INCLUDE_DIRECTORIES)
        if (NOT dep_include_dirs)
          get_target_property(dep_include_dirs ${dep} INCLUDE_DIRECTORIES)
        endif()
        if (dep_include_dirs)
          foreach(d ${dep_include_dirs})
            list(APPEND incdirs ${d})
          endforeach()
        endif()
      endif()
    endforeach()

    ####################### old-style includes/headers generation - end  ##################
  endif()

  #---Get the list of definitions---------------------------
  get_directory_property(defs COMPILE_DEFINITIONS)
  foreach( d ${defs})
   if((NOT d MATCHES "=") AND (NOT d MATCHES "^[$]<.*>$")) # avoid generator expressions
     set(definitions ${definitions} -D${d})
   endif()
  endforeach()
  #---Get LinkDef.h file------------------------------------
  foreach( f ${ARG_LINKDEF})
    if( IS_ABSOLUTE ${f})
      set(_linkdef ${_linkdef} ${f})
    else()
      if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/inc/${f})
        set(_linkdef ${_linkdef} ${CMAKE_CURRENT_SOURCE_DIR}/inc/${f})
      else()
        set(_linkdef ${_linkdef} ${CMAKE_CURRENT_SOURCE_DIR}/${f})
      endif()
    endif()
  endforeach()

  #---Build the names for library, pcm and rootmap file ----
  set(library_target_name)
  if(dictionary MATCHES "^G__")
    string(REGEX REPLACE "^G__(.*)" "\\1"  library_target_name ${dictionary})
    if (ARG_MULTIDICT)
      string(REGEX REPLACE "(.*)32$" "\\1"  library_target_name ${library_target_name})
    endif (ARG_MULTIDICT)
  else()
    get_filename_component(library_target_name ${dictionary} NAME_WE)
  endif()
  if (ARG_MODULE)
    if (NOT ${ARG_MODULE} STREQUAL ${library_target_name})
#      message(AUTHOR_WARNING "The MODULE argument ${ARG_MODULE} and the deduced library name "
#        "${library_target_name} mismatch. Deduction stem: ${dictionary}.")
      set(library_target_name ${ARG_MODULE})
    endif()
  endif(ARG_MODULE)

  # The output name can be different from the target name, and we want to
  # ensure that the same output name is used for the rootmap and pcm files.
  if(TARGET ${library_target_name})
    get_target_property(library_output_name ${library_target_name} OUTPUT_NAME)
    if (NOT library_output_name)
      # Use target name if OUTPUT_NAME is not set
      set(library_output_name ${library_target_name})
    endif()
  else()
    set(library_output_name ${library_target_name})
  endif()

  #---Set the library output directory-----------------------
  ROOT_GET_LIBRARY_OUTPUT_DIR(library_output_dir)
  set(runtime_cxxmodule_dependencies )
  set(cpp_module)
  set(library_name ${libprefix}${library_output_name}${libsuffix})
  set(newargs -s ${library_output_dir}/${library_name})
  set(rootmap_name ${library_output_dir}/${libprefix}${library_output_name}.rootmap)
  set(pcm_name ${library_output_dir}/${libprefix}${library_output_name}_rdict.pcm)
  if(ARG_MODULE)
    if(ARG_MULTIDICT)
      set(newargs ${newargs} -multiDict)
      set(pcm_name ${library_output_dir}/${libprefix}${library_output_name}_${dictionary}_rdict.pcm)
      set(rootmap_name ${library_output_dir}/${libprefix}${library_output_name}32.rootmap)
    else()
      set(cpp_module ${library_target_name})
    endif(ARG_MULTIDICT)

    if(runtime_cxxmodules)
      # If we specify NO_CXXMODULE we should be able to still install the produced _rdict.pcm file.
      if(NOT ARG_NO_CXXMODULE)
        set(pcm_name)
      endif()
      if(cpp_module)
        set(cpp_module_file ${library_output_dir}/${cpp_module}.pcm)
        # The module depends on its modulemap file.
        if (cpp_module_file AND CMAKE_PROJECT_NAME STREQUAL ROOT)
		set (runtime_cxxmodule_dependencies "${CMAKE_BINARY_DIR}/include/ROOT.modulemap")
        endif()
      endif(cpp_module)
    endif()
  endif()

  # modules.idx deps
  get_property(local_modules_idx_deps GLOBAL PROPERTY modules_idx_deps_property)
  get_property(local_no_cxxmodules GLOBAL PROPERTY no_cxxmodules_property)
  if (ARG_NO_CXXMODULE)
    list(APPEND local_no_cxxmodules ${cpp_module})
    set_property(GLOBAL PROPERTY no_cxxmodules_property "${local_no_cxxmodules}")
    unset(cpp_module)
    unset(cpp_module_file)
  else()
    list(APPEND local_modules_idx_deps ${cpp_module})
    set_property(GLOBAL PROPERTY modules_idx_deps_property "${local_modules_idx_deps}")
  endif(ARG_NO_CXXMODULE)



  if(CMAKE_ROOTTEST_NOROOTMAP OR cpp_module_file)
    set(rootmap_name)
    set(rootmapargs)
  else()
    set(rootmapargs -rml ${library_name} -rmf ${rootmap_name})
  endif()

  #---Get the library and module dependencies-----------------
  if(ARG_DEPENDENCIES)
    foreach(dep ${ARG_DEPENDENCIES})
      if(NOT TARGET G__${dep})
        # This is a library that doesn't come with dictionary/pcm
        continue()
      endif()

      set(dependent_pcm ${libprefix}${dep}_rdict.pcm)
      if (runtime_cxxmodules AND NOT dep IN_LIST local_no_cxxmodules)
        set(dependent_pcm ${dep}.pcm)
        if(TARGET ${dep})
          get_target_property(_dep_pcm_filename ${dep} ROOT_PCM_FILENAME)
          if(_dep_pcm_filename)
            list(APPEND pcm_dependencies ${_dep_pcm_filename})
          endif()
        endif()
      endif()
      set(newargs ${newargs} -m  ${dependent_pcm})
    endforeach()
  endif()

  if(cpp_module_file)
    set(newargs -cxxmodule ${newargs})
  endif()

  #---what rootcling command to use--------------------------
  if(ARG_STAGE1)
    if(APPLE)
      set(command ${CMAKE_COMMAND} -E env SDKROOT=${CMAKE_OSX_SYSROOT} $<TARGET_FILE:rootcling_stage1>)
    else()
      set(command $<TARGET_FILE:rootcling_stage1>)
    endif()
    set(ROOTCLINGDEP rconfigure)
    set(pcm_name)
  else()
    if(CMAKE_PROJECT_NAME STREQUAL ROOT)
      if(MSVC AND CMAKE_ROOTTEST_DICT)
        set(command ${CMAKE_COMMAND} -E env "ROOTIGNOREPREFIX=1" ${CMAKE_BINARY_DIR}/bin/rootcling.exe -rootbuild)
      else()
        if(APPLE)
          set(command ${CMAKE_COMMAND} -E env "ROOTIGNOREPREFIX=1" SDKROOT=${CMAKE_OSX_SYSROOT} $<TARGET_FILE:rootcling> -rootbuild)
        else()
          set(command ${CMAKE_COMMAND} -E env "ROOTIGNOREPREFIX=1" $<TARGET_FILE:rootcling> -rootbuild)
        endif()
        # Modules need RConfigure.h copied into include/.
        set(ROOTCLINGDEP rootcling rconfigure)
      endif()
    elseif(TARGET ROOT::rootcling)
      if(APPLE)
        set(command ${CMAKE_COMMAND} -E env SDKROOT=${CMAKE_OSX_SYSROOT} $<TARGET_FILE:ROOT::rootcling>)
      else()
        set(command $<TARGET_FILE:ROOT::rootcling>)
      endif()
    else()
      if(APPLE)
        set(command ${CMAKE_COMMAND} -E env SDKROOT=${CMAKE_OSX_SYSROOT} rootcling)
      else()
        set(command rootcling)
      endif()
    endif()
  endif()

  #---build the path exclusion switches----------------------
  set(excludepathsargs "")
  foreach(excludepath ${excludepaths})
    set(excludepathsargs ${excludepathsargs} -excludePath ${excludepath})
  endforeach()

  #---build the implicit dependencies arguments
  # NOTE: only the Makefile generator respects this!
  foreach(_dep ${_linkdef} ${_list_of_header_dependencies})
    list(APPEND _implicitdeps CXX ${_dep})
  endforeach()

  if(ARG_MODULE)
    set(MODULE_LIB_DEPENDENCY ${ARG_DEPENDENCIES})

    # get target properties added after call to ROOT_GENERATE_DICTIONARY()
    if(TARGET ${ARG_MODULE})
      # NOTE that module_sysincs is already part of ${module_sysincs}. But -isystem "wins",
      # and list exclusion for generator expressions is too complex.
      set(module_incs $<REMOVE_DUPLICATES:$<TARGET_PROPERTY:${ARG_MODULE},INCLUDE_DIRECTORIES>>)
      set(module_sysincs $<REMOVE_DUPLICATES:$<TARGET_PROPERTY:${ARG_MODULE},INTERFACE_SYSTEM_INCLUDE_DIRECTORIES>>)
      # The COMPILE_DEFINITIONS list might contain empty elements. These are
      # removed with the FILTER generator expression, excluding elements that
      # match the ^$ regexp (only matches empty strings).
      set(module_defs "$<FILTER:$<TARGET_PROPERTY:${ARG_MODULE},COMPILE_DEFINITIONS>,EXCLUDE,^$>")
    endif()
  endif()

  # provide list of includes for dictionary
  set(includedirs)
  if(incdirs)
     list(REMOVE_DUPLICATES incdirs)
     foreach(dir ${incdirs})
        if (NOT ${dir} MATCHES "^\\$<INSTALL_INTERFACE:")
          list(APPEND includedirs -I${dir})
        endif()
     endforeach()
  endif()

  set(compIncPaths)
  foreach(implinc IN LISTS CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES)
    list(APPEND compIncPaths "-compilerI${implinc}")
  endforeach()

  if(cpp_module_file AND TARGET ${ARG_MODULE})
    set_target_properties(${ARG_MODULE} PROPERTIES
      ROOT_PCM_FILENAME "${cpp_module_file}")
  endif()

  #---call rootcling------------------------------------------
  add_custom_command(
    OUTPUT ${dictionary}.cxx ${pcm_name} ${rootmap_name} ${cpp_module_file}
    COMMAND ${command} -v2 -f  ${dictionary}.cxx ${newargs} ${excludepathsargs} ${rootmapargs}
                       ${ARG_OPTIONS}
                       ${definitions} "$<$<BOOL:${module_defs}>:-D$<JOIN:${module_defs},;-D>>"
                       ${compIncPaths}
                       "$<$<BOOL:${module_sysincs}>:-isystem;$<JOIN:${module_sysincs},;-isystem;>>"
                       ${includedirs} "$<$<BOOL:${module_incs}>:-I$<JOIN:${module_incs},;-I>>"
                       ${headerfiles} ${_linkdef}
                       # Add random dummy macro including the CMAKE_CXX_STANDARD variable. This the easiest way to
                       # make the dictionary generation command depend on the C++ standard, ensuring that the
                       # dictionaries will be rebuilt if the C++ standard is changed in an incremental build.
                       -DR__DUMMY_CXX_STANDARD_${CMAKE_CXX_STANDARD}
    IMPLICIT_DEPENDS ${_implicitdeps}
    DEPENDS ${_list_of_header_dependencies} ${_linkdef} ${ROOTCLINGDEP}
            ${pcm_dependencies}
            ${MODULE_LIB_DEPENDENCY} ${ARG_EXTRA_DEPENDENCIES}
            ${runtime_cxxmodule_dependencies}
            ${cxx_std_stamp}
    COMMAND_EXPAND_LISTS
  )

  # If we are adding to an existing target and it's not the dictionary itself,
  # we make an object library and add its output object file as source to the target.
  # This works around bug https://cmake.org/Bug/view.php?id=14633 in CMake by keeping
  # the generated source at the same scope level as its owning target, something that
  # would not happen if we used target_sources() directly with the dictionary source.
  if(TARGET "${ARG_MODULE}" AND NOT "${ARG_MODULE}" STREQUAL "${dictionary}")
    add_library(${dictionary} OBJECT ${dictionary}.cxx)
    set_target_properties(${dictionary} PROPERTIES POSITION_INDEPENDENT_CODE TRUE)
    target_link_libraries(${ARG_MODULE} PRIVATE ${dictionary})

    target_compile_options(${dictionary} PRIVATE
      $<TARGET_PROPERTY:${ARG_MODULE},COMPILE_OPTIONS>)

    target_compile_definitions(${dictionary} PRIVATE
      ${definitions} $<TARGET_PROPERTY:${ARG_MODULE},COMPILE_DEFINITIONS>)

    target_compile_features(${dictionary} PRIVATE
      $<TARGET_PROPERTY:${ARG_MODULE},COMPILE_FEATURES>)

    target_include_directories(${dictionary} PRIVATE ${incdirs} $<TARGET_PROPERTY:${ARG_MODULE},INCLUDE_DIRECTORIES>)

    # Above we are copying all include directories of the module, irrespective of whether they are system includes.
    # CMake copies them as -I even when they should be -isystem.
    # We can fix this for INTERFACE includes by also copying the respective property.
    # For PRIVATE includes this doesn't work. In that case, one needs to link both the library as well as the dictionary explicitly:
    #   target_link_libraries(MODULE PRIVATE dependency)
    #   target_link_libraries(G__MODULE PRIVATE dependency)
    set_property(TARGET ${dictionary} APPEND PROPERTY
      INTERFACE_SYSTEM_INCLUDE_DIRECTORIES $<TARGET_PROPERTY:${ARG_MODULE},INTERFACE_SYSTEM_INCLUDE_DIRECTORIES>)
  else()
    get_filename_component(dictionary_name ${dictionary} NAME)
    add_custom_target(${dictionary_name} DEPENDS ${dictionary}.cxx ${pcm_name} ${rootmap_name} ${cpp_module_file})
  endif()

  if(PROJECT_NAME STREQUAL "ROOT")
    set_property(GLOBAL APPEND PROPERTY ROOT_PCH_DEPENDENCIES ${dictionary})
    set_property(GLOBAL APPEND PROPERTY ROOT_PCH_DICTIONARIES ${CMAKE_CURRENT_BINARY_DIR}/${dictionary}.cxx)
  endif()

  if(ARG_MULTIDICT)
    if(NOT TARGET "G__${ARG_MODULE}")
      message(FATAL_ERROR
        " Target G__${ARG_MODULE} not found!\n"
        " Please create target G__${ARG_MODULE} before using MULTIDICT.")
    endif()
    add_dependencies(G__${ARG_MODULE} ${dictionary})
  endif()

  if(NOT ARG_NOINSTALL AND NOT CMAKE_ROOTTEST_DICT AND DEFINED CMAKE_LIBRARY_OUTPUT_DIRECTORY)
    ROOT_GET_INSTALL_DIR(shared_lib_install_dir)
    # Install the C++ module if we generated one.
    if (cpp_module_file)
      install(FILES ${cpp_module_file}
                    DESTINATION ${shared_lib_install_dir} COMPONENT libraries)
    endif()

    if(ARG_STAGE1)
      install(FILES ${rootmap_name}
                    DESTINATION ${shared_lib_install_dir} COMPONENT libraries)
    else()
      install(FILES ${pcm_name} ${rootmap_name}
                    DESTINATION ${shared_lib_install_dir} COMPONENT libraries)
    endif()
  endif()

  if(ARG_BUILTINS)
    foreach(arg1 ${ARG_BUILTINS})
      if(TARGET ${${arg1}_TARGET})
        add_dependencies(${dictionary} ${${arg1}_TARGET})
      endif()
    endforeach()
  endif()

  # FIXME: Support mulptiple dictionaries. In some cases (libSMatrix and
  # libGenVector) we have to have two or more dictionaries (eg. for math,
  # we need the two for double vs Double32_t template specializations).
  # In some other cases, eg. libTreePlayer.so we add in a separate dictionary
  # files which for some reason (temporarily?) cannot be put in the PCH. Eg.
  # all rest of the first dict is in the PCH but this file is not and it
  # cannot be present in the original dictionary.
  if(cpp_module)
    ROOT_CXXMODULES_APPEND_TO_MODULEMAP("${cpp_module}" "${headerfiles}")
  endif()
endfunction(ROOT_GENERATE_DICTIONARY)

#---------------------------------------------------------------------------------------------------
#---ROOT_CXXMODULES_APPEND_TO_MODULEMAP( library library_headers )
#---------------------------------------------------------------------------------------------------
function (ROOT_CXXMODULES_APPEND_TO_MODULEMAP library library_headers)
  ROOT_FIND_DIRS_WITH_HEADERS(dirs)

  set(found_headers "")
  set(dir_headers "")
  foreach(d ${dirs})
    ROOT_GLOB_FILES(dir_headers
                    RECURSE
                    RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}/${d}
                    FILTER "LinkDef" ${d}/*)
    list(APPEND found_headers "${dir_headers}")
  endforeach()

  set(excluded_headers RConfig.h RVersion.h core/foundation/inc/ROOT/RVersion.hxx RtypesImp.h
                        RtypesCore.h TClassEdit.h
                        TIsAProxy.h TVirtualIsAProxy.h
                        DllImport.h ESTLType.h Varargs.h
                        ThreadLocalStorage.h
                        TBranchProxyTemplate.h
                        snprintf.h strlcpy.h)

   # Deprecated header files.
  set (excluded_headers "${excluded_headers}")

  set(modulemap_entry "module \"${library}\" {")

  # Add a `use` directive to Core/Thread to signal that they use some
  # split out submodules and we pass the rootcling integrity check.
  if ("${library}" STREQUAL Core)
    set (modulemap_entry "${modulemap_entry}\n  use ROOT_Foundation_Stage1_NoRTTI\n")
    set (modulemap_entry "${modulemap_entry}\n  use ROOT_Foundation_C\n")
  elseif ("${library}" STREQUAL Thread)
    set (modulemap_entry "${modulemap_entry}\n  use ROOT_Foundation_C\n")
  endif()

  # For modules GCocoa and GQuartz we need objc and cplusplus context.
  if (NOT ${library} MATCHES "GCocoa")
    set (modulemap_entry "${modulemap_entry}\n  requires cplusplus\n")
  endif()
  if (library_headers)
    set(found_headers ${library_headers})
  endif()
  foreach(header ${found_headers})
    set(textual_header "")
    if (${header} MATCHES ".*\\.icc$")
      set(textual_header "textual ")
    endif()
    # Check if header is in included header list
    set(is_excluded NO)
    foreach(excluded_header ${excluded_headers})
      if(${header} MATCHES ${excluded_header})
        set(is_excluded YES)
        break()
      endif()
    endforeach()
    if(NOT is_excluded)
      set(modulemap_entry "${modulemap_entry}  module \"${header}\" { ${textual_header}header \"${header}\" export * }\n")
    endif()
  endforeach()
  set(modulemap_entry "${modulemap_entry}  link \"${libprefix}${library}${libsuffix}\"\n")
  set(modulemap_entry "${modulemap_entry}  export *\n}\n\n")
  # Non ROOT projects need a modulemap generated for them in the current
  # directory. The same happens with test dictionaries in ROOT which are not
  # exposed via the main modulemap. This is exposed by setting the
  # ROOT_CXXMODULES_WRITE_TO_CURRENT_DIR.
  if (NOT "${CMAKE_PROJECT_NAME}" STREQUAL ROOT OR ROOT_CXXMODULES_WRITE_TO_CURRENT_DIR)
    set(modulemap_output_file "${CMAKE_CURRENT_BINARY_DIR}/module.modulemap")

    # It's possible that multiple modulemaps are needed in the current
    # directory and we need to merge them. As we don't want to have multiple
    # modules in the same moduluemap when rerunning CMake, we do a quick
    # check if the current module is already in the modulemap (in which case
    # we know we rerun CMake at the moment and start writing a new modulemap
    # instead of appending new modules).

    # The string we use to identify if the current module is already in the
    # modulemap.
    set(modulemap_needle "module \"${library}\"")
    # Check if the needle is in the modulemap. If the file doesn't exist
    # we just pretend we didn't found the string in the modulemap.
    set(match_result -1)
    if (EXISTS "${modulemap_output_file}")
      file(READ "${modulemap_output_file}" existing_contents)
      string(FIND "${existing_contents}" "${modulemap_needle}" match_result)
    endif()
    # Append our new module to the existing modulemap containing other modules.
    if(${match_result} EQUAL -1)
      file(APPEND "${modulemap_output_file}" "${modulemap_entry}")
    else()
      file(WRITE "${modulemap_output_file}" "${modulemap_entry}")
    endif()

    # Sanity check that the string we're looking for is actually in the content
    # we're writing to this file.
    string(FIND "${modulemap_entry}" "${modulemap_needle}" match_result)
    if(${match_result} EQUAL -1)
      message(AUTHOR_WARNING "Couldn't find module declaration in modulemap file."
                             "This would break the modulemap generation when "
                             " rerunning CMake. Module needle was "
                             "'${modulemap_needle}' and the content was '${modulemap_entry}'")
    endif()

  else()
    set_property(GLOBAL APPEND PROPERTY ROOT_CXXMODULES_EXTRA_MODULEMAP_CONTENT ${modulemap_entry})
  endif()
endfunction()

#---------------------------------------------------------------------------------------------------
#---ROOT_LINKER_LIBRARY( <name> source1 source2 ...[TYPE STATIC|SHARED] [DLLEXPORT]
#                        [NOINSTALL]
#                        LIBRARIES library1 library2 ... # PRIVATE link dependencies
#                        DEPENDENCIES dep1 dep2          # PUBLIC link dependencies
#                        BUILTINS dep1 dep2              # dependencies to builtins)
#---------------------------------------------------------------------------------------------------
function(ROOT_LINKER_LIBRARY library)
  CMAKE_PARSE_ARGUMENTS(ARG "DLLEXPORT;CMAKENOEXPORT;TEST;NOINSTALL" "TYPE" "LIBRARIES;DEPENDENCIES;BUILTINS"  ${ARGN})
  ROOT_GET_SOURCES(lib_srcs src ${ARG_UNPARSED_ARGUMENTS})
  if(NOT ARG_TYPE)
    set(ARG_TYPE SHARED)
  endif()
  if(ARG_TEST) # we are building a test, so add EXCLUDE_FROM_ALL
    set(_all EXCLUDE_FROM_ALL)
  endif()
  if(TARGET ${library})
    message(FATAL_ERROR "Target ${library} already exists.")
  endif()
  if(WIN32 AND ARG_TYPE STREQUAL SHARED AND NOT ARG_DLLEXPORT)
    #---create a shared library with the .def file------------------------
    add_library(${library} ${_all} SHARED ${lib_srcs})
  else()
    add_library( ${library} ${_all} ${ARG_TYPE} ${lib_srcs})
    if(ARG_TYPE STREQUAL SHARED)
      set_target_properties(${library} PROPERTIES  ${ROOT_LIBRARY_PROPERTIES} )
    endif()
  endif()
  set_target_properties(${library} PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS TRUE)

  if(DEFINED CMAKE_CXX_STANDARD)
    target_compile_features(${library} INTERFACE cxx_std_${CMAKE_CXX_STANDARD})
  endif()

  # Add dependencies passed via LIBRARIES or DEPENDENCIES argument:
  target_link_libraries(${library} PUBLIC ${ARG_DEPENDENCIES})
  target_link_libraries(${library} PRIVATE ${ARG_LIBRARIES})

  if(TARGET G__${library})
    add_dependencies(${library} G__${library})
  endif()
  set_property(GLOBAL APPEND PROPERTY ROOT_EXPORTED_TARGETS ${library})

  ROOT_ADD_INCLUDE_DIRECTORIES(${library})

  if(PROJECT_NAME STREQUAL "ROOT")
    add_dependencies(${library} move_headers)
    if(NOT TARGET ROOT::${library})
      add_library(ROOT::${library} ALIAS ${library})
    endif()
  endif()

  set_target_properties(${library} PROPERTIES
      PREFIX ${libprefix}
      IMPORT_PREFIX ${libprefix} # affects the .lib import library (MSVC)
  )
  target_include_directories(${library} INTERFACE $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>)
  # Do not add -Dname_EXPORTS to the command-line when building files in this
  # target. Doing so is actively harmful for the modules build because it
  # creates extra module variants, and not useful because we don't use these
  # macros.
  set_target_properties(${library} PROPERTIES DEFINE_SYMBOL "")
  if(ARG_BUILTINS)
    foreach(arg1 ${ARG_BUILTINS})
      if(${arg1}_TARGET)
        add_dependencies(${library} ${${arg1}_TARGET})
      endif()
    endforeach()
  endif()

  #----Installation details-------------------------------------------------------
  if(NOT ARG_TEST AND NOT ARG_NOINSTALL AND CMAKE_LIBRARY_OUTPUT_DIRECTORY)
    if(NOT MSVC)
      ROOT_APPEND_LIBDIR_TO_INSTALL_RPATH(${library} ${CMAKE_INSTALL_LIBDIR})
    endif()
    if(ARG_CMAKENOEXPORT)
      install(TARGETS ${library} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT libraries
                                 LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT libraries
                                 ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT libraries)
    else()
      install(TARGETS ${library} EXPORT ${CMAKE_PROJECT_NAME}Exports
                                 RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT libraries
                                 LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT libraries
                                 ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT libraries)
    endif()
    if(WIN32 AND ARG_TYPE STREQUAL SHARED)
      install(FILES ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/lib${library}.pdb
                    CONFIGURATIONS Debug RelWithDebInfo
                    DESTINATION ${CMAKE_INSTALL_BINDIR}
                    COMPONENT libraries)
    endif()
  else()
    # If the target is not installed, it doesn't make sense to build it with the INSTALL_RPATH
    set_property(TARGET ${library} PROPERTY BUILD_WITH_INSTALL_RPATH OFF)
  endif()
endfunction()

#---------------------------------------------------------------------------------------------------
#---ROOT_ADD_INCLUDE_DIRECTORIES(library)
#---------------------------------------------------------------------------------------------------
function(ROOT_ADD_INCLUDE_DIRECTORIES library)

  if(PROJECT_NAME STREQUAL "ROOT")

      if(TARGET Core)
        get_target_property(lib_incdirs Core INCLUDE_DIRECTORIES)
        if(lib_incdirs)
          foreach(dir ${lib_incdirs})
            ROOT_REPLACE_BUILD_INTERFACE(dir ${dir})
            if(NOT ${dir} MATCHES "^[$]")
              target_include_directories(${library} BEFORE PRIVATE ${dir})
            endif()
          endforeach()
        endif()
      endif()

      if(IS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/res)
        target_include_directories(${library} BEFORE PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/res)
      endif()

      if(IS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/inc)
        target_include_directories(${library} BEFORE PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/inc>)
      endif()

      if(root7 AND (IS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/v7/inc))
        target_include_directories(${library} BEFORE PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/v7/inc>)
      endif()

  endif()

endfunction(ROOT_ADD_INCLUDE_DIRECTORIES)

#---------------------------------------------------------------------------------------------------
#---ROOT_OBJECT_LIBRARY( <name> source1 source2 ... BUILTINS dep1 dep2 ...)
#---------------------------------------------------------------------------------------------------
function(ROOT_OBJECT_LIBRARY library)
  CMAKE_PARSE_ARGUMENTS(ARG "" "" "BUILTINS"  ${ARGN})
  ROOT_GET_SOURCES(lib_srcs src ${ARG_UNPARSED_ARGUMENTS})
  add_library( ${library} OBJECT ${lib_srcs})
  if(lib_srcs MATCHES "(^|/)(G__[^.]*)[.]cxx.*")
     add_dependencies(${library} ${CMAKE_MATCH_2})
  endif()
  add_dependencies(${library} move_headers)

  ROOT_ADD_INCLUDE_DIRECTORIES(${library})

  #--- Only for building shared libraries
  set_property(TARGET ${library} PROPERTY POSITION_INDEPENDENT_CODE 1)
  # Do not add -Dname_EXPORTS to the command-line when building files in this
  # target. Doing so is actively harmful for the modules build because it
  # creates extra module variants, and not useful because we don't use these
  # macros.
  set_target_properties(${library} PROPERTIES DEFINE_SYMBOL "")
  set_target_properties(${library} PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS TRUE)

  if(ARG_BUILTINS)
    foreach(arg1 ${ARG_BUILTINS})
      if(${arg1}_TARGET)
        add_dependencies(${library} ${${arg1}_TARGET})
      endif()
    endforeach()
  endif()

  #--- Fill the property OBJECTS with all the object files
  #    This is needed because the generator expression $<TARGET_OBJECTS:target>
  #    does not get expanded when used in custom command dependencies
  get_target_property(sources ${library} SOURCES)
  foreach(s ${sources})
    if(CMAKE_GENERATOR MATCHES Xcode)
      get_filename_component(name ${s} NAME_WE)
      set(obj ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_PROJECT_NAME}.build/${CMAKE_CFG_INTDIR}/${library}.build/Objects-normal/x86_64/${name}${CMAKE_CXX_OUTPUT_EXTENSION})
    else()
      if(IS_ABSOLUTE ${s})
        string(FIND ${s} "${CMAKE_CURRENT_SOURCE_DIR}" src_dir_in_src)
        string(FIND ${s} "${CMAKE_CURRENT_BINARY_DIR}" bin_dir_in_src)
        if(${src_dir_in_src} EQUAL 0)
          string(REPLACE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/${library}.dir src ${s})
        elseif(${bin_dir_in_src} EQUAL 0)
          string(REPLACE ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/${library}.dir src ${s})
        else()
          #message(WARNING "Unknown location of source ${s} for object library ${library}")
        endif()
      else()
        set(src ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/${library}.dir/${s})
      endif()
      set(obj ${src}${CMAKE_CXX_OUTPUT_EXTENSION})
    endif()
    set_property(TARGET ${library} APPEND PROPERTY OBJECTS ${obj})
  endforeach()
endfunction()

#---------------------------------------------------------------------------------------------------
#---ROOT_MODULE_LIBRARY(<library> source1 source2 ... LIBRARIES library1 library2 ...)
#---------------------------------------------------------------------------------------------------
function(ROOT_MODULE_LIBRARY library)
  CMAKE_PARSE_ARGUMENTS(ARG "" "" "LIBRARIES" ${ARGN})
  ROOT_GET_SOURCES(lib_srcs src ${ARG_UNPARSED_ARGUMENTS})
  add_library(${library} SHARED ${lib_srcs})
  add_dependencies(${library} move_headers)
  set_target_properties(${library}  PROPERTIES ${ROOT_LIBRARY_PROPERTIES})
  # Do not add -Dname_EXPORTS to the command-line when building files in this
  # target. Doing so is actively harmful for the modules build because it
  # creates extra module variants, and not useful because we don't use these
  # macros.
  set_target_properties(${library} PROPERTIES DEFINE_SYMBOL "")

  ROOT_ADD_INCLUDE_DIRECTORIES(${library})

  target_link_libraries(${library} PUBLIC ${ARG_LIBRARIES})
  #----Installation details-------------------------------------------------------
  install(TARGETS ${library} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT libraries
                             LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT libraries
                             ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT libraries)
endfunction()

#---------------------------------------------------------------------------------------------------
#---ROOT_GENERATE_ROOTMAP( library LINKDEF linkdef LIBRRARY lib DEPENDENCIES lib1 lib2 )
#---------------------------------------------------------------------------------------------------
function(ROOT_GENERATE_ROOTMAP library)
  return()   #--- No needed anymore
  CMAKE_PARSE_ARGUMENTS(ARG "" "LIBRARY" "LINKDEF;DEPENDENCIES" ${ARGN})
  get_filename_component(libname ${library} NAME_WE)
  get_filename_component(path ${library} PATH)

  #---Set the library output directory-----------------------
  ROOT_GET_LIBRARY_OUTPUT_DIR(library_output_dir)

  set(outfile ${library_output_dir}/${libprefix}${libname}.rootmap)
  foreach( f ${ARG_LINKDEF})
    if( IS_ABSOLUTE ${f})
      set(_linkdef ${_linkdef} ${f})
    else()
      set(_linkdef ${_linkdef} ${CMAKE_CURRENT_SOURCE_DIR}/inc/${f})
    endif()
  endforeach()
  foreach(d ${ARG_DEPENDENCIES})
    get_filename_component(_ext ${d} EXT)
    if(_ext)
      set(_dependencies ${_dependencies} ${d})
    else()
      set(_dependencies ${_dependencies} ${libprefix}${d}${CMAKE_SHARED_LIBRARY_SUFFIX})
    endif()
  endforeach()
  if(ARG_LIBRARY)
    set(_library ${ARG_LIBRARY})
  else()
    set(_library ${libprefix}${library}${CMAKE_SHARED_LIBRARY_SUFFIX})
  endif()
  #---Build the rootmap file--------------------------------------
  #add_custom_command(OUTPUT ${outfile}
  #                   COMMAND ${rlibmap_cmd} -o ${outfile} -l ${_library} -d ${_dependencies} -c ${_linkdef}
  #                   DEPENDS ${_linkdef} ${rlibmap_cmd} )
  add_custom_target( ${libprefix}${library}.rootmap ALL DEPENDS  ${outfile})
  set_target_properties(${libprefix}${library}.rootmap PROPERTIES FOLDER RootMaps )
  #---Install the rootmap file------------------------------------
  install(FILES ${outfile} DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT libraries)
endfunction(ROOT_GENERATE_ROOTMAP)

#---------------------------------------------------------------------------------------------------
#---ROOT_FIND_DIRS_WITH_HEADERS([dir1 dir2 ...] OPTIONS [options])
#---------------------------------------------------------------------------------------------------
function(ROOT_FIND_DIRS_WITH_HEADERS result_dirs)
  set(dirs "")
  if(ARGN)
    set(dirs ${ARGN})
  else()
    if(IS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/inc)
      set(dirs inc/)
    endif()
    if(root7 AND IS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/v7/inc)
      set(dirs ${dirs} v7/inc/)
    endif()
  endif()
  set(${result_dirs} ${dirs} PARENT_SCOPE)
endfunction()

#---------------------------------------------------------------------------------------------------
#---ROOT_INSTALL_HEADERS([dir1 dir2 ...] [FILTER <regex>])
# Glob for headers in the folder where this target is defined, and install them in
# <buildDir>/include
#---------------------------------------------------------------------------------------------------
function(ROOT_INSTALL_HEADERS)
  CMAKE_PARSE_ARGUMENTS(ARG "OPTIONS" "" "FILTER" ${ARGN})
  if (${ARG_OPTIONS})
    message(FATAL_ERROR "ROOT_INSTALL_HEADERS no longer supports the OPTIONS argument. Rewrite using the FILTER argument.")
  endif()
  ROOT_FIND_DIRS_WITH_HEADERS(dirs ${ARG_UNPARSED_ARGUMENTS})
  set (filter "LinkDef")
  set (options REGEX "LinkDef" EXCLUDE)
  foreach (f ${ARG_FILTER})
    set (filter "${filter}|${f}")
    set (options ${options} REGEX "${f}" EXCLUDE)
  endforeach()
  set (filter "(${filter})")
  set(include_files "")
  foreach(d ${dirs})
    install(DIRECTORY ${d} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
                           COMPONENT headers
                           ${options})
    string(REGEX REPLACE "(.*)/$" "\\1" d ${d})
    ROOT_GLOB_FILES(globbed_files
      RECURSE
      RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
      FILTER ${filter}
      ${d}/*.h ${d}/*.hxx ${d}/*.icc )
    list(APPEND include_files ${globbed_files})
  endforeach()

  string(REPLACE ${CMAKE_SOURCE_DIR} "" target_name ${CMAKE_CURRENT_SOURCE_DIR})
  string(REPLACE / _ target_name "copy_header_${target_name}")
  string(REGEX REPLACE "_$" "" target_name ${target_name})

  # Register the files to be copied for each target directory (e.g. include/ include/ROOT include/v7/inc/ ...)
  list(REMOVE_DUPLICATES include_files)
  list(TRANSFORM include_files REPLACE "(.*)/[^/]*" "\\1/" OUTPUT_VARIABLE subdirs)
  list(REMOVE_DUPLICATES subdirs)
  foreach(subdir ${subdirs})
    set(input_files ${include_files})
    list(FILTER input_files INCLUDE REGEX "^${subdir}[^/]*$")
    set(output_files ${input_files})

    string(REGEX REPLACE ".*/*inc/" "" destination ${subdir})

    list(TRANSFORM input_files  PREPEND "${CMAKE_CURRENT_SOURCE_DIR}/")
    list(TRANSFORM output_files REPLACE ".*/" "${CMAKE_BINARY_DIR}/include/${destination}")

    set(destination destination_${destination})

    set_property(GLOBAL APPEND PROPERTY ROOT_HEADER_COPY_LISTS ${destination})
    set_property(GLOBAL APPEND PROPERTY ROOT_HEADER_INPUT_${destination} ${input_files})
    set_property(GLOBAL APPEND PROPERTY ROOT_HEADER_OUTPUT_${destination} ${output_files})
  endforeach()
endfunction()

#---------------------------------------------------------------------------------------------------
#--- ROOT_CREATE_HEADER_COPY_TARGETS
#    Creates a target to copy all headers that have been registered for copy in ROOT_INSTALL_HEADERS
#---------------------------------------------------------------------------------------------------
macro(ROOT_CREATE_HEADER_COPY_TARGETS)
  get_property(HEADER_COPY_LISTS GLOBAL PROPERTY ROOT_HEADER_COPY_LISTS)
  list(REMOVE_DUPLICATES HEADER_COPY_LISTS)
  foreach(copy_list ${HEADER_COPY_LISTS})
    get_property(inputs  GLOBAL PROPERTY ROOT_HEADER_INPUT_${copy_list})
    get_property(outputs GLOBAL PROPERTY ROOT_HEADER_OUTPUT_${copy_list})

    string(REPLACE "destination_" "${CMAKE_BINARY_DIR}/include/" destination ${copy_list})

    list(LENGTH inputs LIST_LENGTH)
    # Windows doesn't support long command lines, so split them in packs:
    # foreach(.. RANGE start stop) is inclusive for both start and stop, so we
    # need to decrement the LIST_LENGTH to get the desired logic.
    math(EXPR LIST_LENGTH_MINUS_ONE "${LIST_LENGTH}-1")
    foreach(range_start RANGE 0 ${LIST_LENGTH_MINUS_ONE} 100)
      list(SUBLIST outputs ${range_start} 100 sub_outputs)
      list(SUBLIST inputs ${range_start} 100 sub_inputs)
      list(LENGTH sub_outputs SUB_LENGTH)
      if(NOT SUB_LENGTH EQUAL 0)
        add_custom_command(OUTPUT ${sub_outputs}
          COMMAND ${CMAKE_COMMAND} -E copy_if_different ${sub_inputs} ${destination}
          COMMENT "Copy headers for ${destination} ${range_start}"
          DEPENDS ${sub_inputs})
      endif()
    endforeach()
    file(MAKE_DIRECTORY ${destination})
    set_property(GLOBAL APPEND PROPERTY ROOT_HEADER_TARGETS ${outputs})
  endforeach()
endmacro()

#---------------------------------------------------------------------------------------------------
#---ROOT_STANDARD_LIBRARY_PACKAGE(libname
#                                 [NO_INSTALL_HEADERS]         : don't install headers for this package
#                                 [STAGE1]                     : use rootcling_stage1 for generating
#                                 HEADERS header1 header2      : relative header path as #included; pass -I to find them. If not specified, globbing for *.h is used
#                                 NODEPHEADERS header1 header2 : like HEADERS, but no dependency is generated
#                                 [NO_HEADERS]                 : don't glob to fill HEADERS variable
#                                 SOURCES source1 source2      : if not specified, globbing for *.cxx is used
#                                 [NO_SOURCES]                 : don't glob to fill SOURCES variable
#                                 [OBJECT_LIBRARY]             : use ROOT_OBJECT_LIBRARY to generate object files
#                                                                and then use those for linking.
#                                 LIBRARIES lib1 lib2          : private arguments for target_link_library()
#                                 DEPENDENCIES lib1 lib2       : PUBLIC arguments for target_link_library() such as Core, MathCore
#                                 BUILTINS builtin1 builtin2   : builtins like xxhash
#                                 LINKDEF LinkDef.h            : linkdef file, default value is "LinkDef.h"
#                                 DICTIONARY_OPTIONS option    : options passed to rootcling
#                                 INSTALL_OPTIONS option       : options passed to install headers
#                                 NO_CXXMODULE                 : don't generate a C++ module for this package
#                                )
#---------------------------------------------------------------------------------------------------
function(ROOT_STANDARD_LIBRARY_PACKAGE libname)
  set(options NO_INSTALL_HEADERS STAGE1 NO_HEADERS NO_SOURCES OBJECT_LIBRARY NO_CXXMODULE)
  set(oneValueArgs LINKDEF)
  set(multiValueArgs DEPENDENCIES HEADERS NODEPHEADERS SOURCES BUILTINS LIBRARIES DICTIONARY_OPTIONS INSTALL_OPTIONS)
  CMAKE_PARSE_ARGUMENTS(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})

  # Check if we have any unparsed arguments
  if(ARG_UNPARSED_ARGUMENTS)
    message(AUTHOR_WARNING "Unparsed arguments for ROOT_STANDARD_LIBRARY_PACKAGE: ${ARG_UNPARSED_ARGUMENTS}")
  endif()
  # Check that the user doesn't pass NO_HEADERS (to disable globbing) and HEADERS at the same time.
  if ((ARG_HEADERS OR ARG_NODEPHEADERS) AND ARG_NO_HEADERS)
    message(AUTHOR_WARNING "HEADERS and NO_HEADERS arguments are mutually exclusive.")
  endif()
  if (ARG_SOURCES AND ARG_NO_SOURCES)
    message(AUTHOR_WARNING "SOURCES and NO_SOURCES arguments are mutually exclusive.")
  endif()

  # Set default values
  # If HEADERS/SOURCES are not parsed, we glob for those files.
  if (NOT (ARG_HEADERS OR ARG_NO_HEADERS OR ARG_NODEPHEADERS))
    set(ARG_HEADERS "*.h")
  endif()
  if (NOT ARG_SOURCES AND NOT ARG_NO_SOURCES)
    set(ARG_SOURCES "*.cxx")
  endif()
  if (NOT ARG_LINKDEF)
    set(ARG_LINKDEF "LinkDef.h")
  endif()

  if (ARG_STAGE1)
    set(STAGE1_FLAG "STAGE1")
  endif()

  if (ARG_NO_CXXMODULE)
    set(NO_CXXMODULE_FLAG "NO_CXXMODULE")
  endif()

  if(ARG_NO_SOURCES)
    # Workaround bug in CMake by adding a dummy source file if all sources are generated, since
    # in that case the initial call to add_library() may not list any sources and CMake complains.
    add_custom_command(OUTPUT dummy.cxx COMMAND ${CMAKE_COMMAND} -E touch dummy.cxx)
  endif()

  if(runtime_cxxmodules)
    # Record ROOT targets to be used as a dependency targets for "onepcm" target.
    set(ROOT_LIBRARY_TARGETS "${ROOT_LIBRARY_TARGETS};${libname}" CACHE STRING "List of ROOT targets generated from ROOT_STANDARD_LIBRARY_PACKAGE()" FORCE)
  endif()

  if (PROJECT_NAME STREQUAL ROOT)
    include_directories(BEFORE "inc")
    if(IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/v7/inc")
      include_directories(BEFORE "v7/inc")
    endif()
  endif()

  if (ARG_OBJECT_LIBRARY)
    ROOT_OBJECT_LIBRARY(${libname}Objs ${ARG_SOURCES}
                        $<$<BOOL:${ARG_NO_SOURCES}>:dummy.cxx>)
    ROOT_LINKER_LIBRARY(${libname} $<TARGET_OBJECTS:${libname}Objs>
                        LIBRARIES ${ARG_LIBRARIES}
                        DEPENDENCIES ${ARG_DEPENDENCIES}
                        BUILTINS ${ARG_BUILTINS}
                       )
  else(ARG_OBJECT_LIBRARY)
    ROOT_LINKER_LIBRARY(${libname} ${ARG_SOURCES}
                        $<$<BOOL:${ARG_NO_SOURCES}>:dummy.cxx>
                        LIBRARIES ${ARG_LIBRARIES}
                        DEPENDENCIES ${ARG_DEPENDENCIES}
                        BUILTINS ${ARG_BUILTINS}
                       )
  endif(ARG_OBJECT_LIBRARY)

  if (NOT (ARG_HEADERS OR ARG_NODEPHEADERS))
    message(AUTHOR_WARNING "Called with no HEADERS and no NODEPHEADER. The generated "
      "dictionary will be empty. Consider using ROOT_LINKER_LIBRARY instead.")
  endif()

  ROOT_GENERATE_DICTIONARY(G__${libname} ${ARG_HEADERS}
                          ${NO_CXXMODULE_FLAG}
                          ${STAGE1_FLAG}
                          MODULE ${libname}
                          LINKDEF ${ARG_LINKDEF}
                          NODEPHEADERS ${ARG_NODEPHEADERS}
                          OPTIONS ${ARG_DICTIONARY_OPTIONS}
                          DEPENDENCIES ${ARG_DEPENDENCIES}
                          BUILTINS ${ARG_BUILTINS}
                          )

  # Dictionary might include things from the current src dir, e.g. tests. Alas
  # there is no way to set the include directory for a source file (except for
  # the generic COMPILE_FLAGS), so this needs to be glued to the target.
  if(NOT (CMAKE_PROJECT_NAME STREQUAL ROOT))
     target_include_directories(${libname} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
  endif()

  # Install headers if we have any headers and if the user didn't explicitly
  # disabled this.
  if (NOT ARG_NO_INSTALL_HEADERS OR ARG_NO_HEADERS)
    ROOT_INSTALL_HEADERS(${ARG_INSTALL_OPTIONS})
  endif()
endfunction()

#---------------------------------------------------------------------------------------------------
#---ROOT_EXECUTABLE( <name> source1 source2 ... LIBRARIES library1 library2 ... BUILTINS dep1 dep2 ...)
#---------------------------------------------------------------------------------------------------
function(ROOT_EXECUTABLE executable)
  CMAKE_PARSE_ARGUMENTS(ARG "CMAKENOEXPORT;NOINSTALL;TEST" "" "LIBRARIES;BUILTINS;ADDITIONAL_COMPILE_FLAGS"  ${ARGN})
  ROOT_GET_SOURCES(exe_srcs src ${ARG_UNPARSED_ARGUMENTS})
  set(executable_name ${executable})
  if(TARGET ${executable})
    message("Target ${executable} already exists. Renaming target name to ${executable}_new")
    set(executable ${executable}_new)
  endif()
  if(ARG_TEST) # we are building a test, so add EXCLUDE_FROM_ALL
    set(_all EXCLUDE_FROM_ALL)
  endif()
  if(NOT (PROJECT_NAME STREQUAL "ROOT"))
    # only for non-ROOT executable use $ROOTSYS/include
    include_directories(BEFORE ${CMAKE_BINARY_DIR}/include)
  elseif(MSVC)
    set(exe_srcs ${exe_srcs} ${ROOT_RC_SCRIPT})
  endif()
  add_executable(${executable} ${_all} ${exe_srcs})
  target_link_libraries(${executable} PRIVATE ${ARG_LIBRARIES})

  if(WIN32 AND ${executable} MATCHES \\.exe)
    set_target_properties(${executable} PROPERTIES SUFFIX "")
  endif()
  set_property(GLOBAL APPEND PROPERTY ROOT_EXPORTED_TARGETS ${executable})
  set_target_properties(${executable} PROPERTIES OUTPUT_NAME ${executable_name})
  if (ARG_ADDITIONAL_COMPILE_FLAGS)
    set_target_properties(${executable} PROPERTIES COMPILE_FLAGS ${ARG_ADDITIONAL_COMPILE_FLAGS})
  endif()
  if(CMAKE_PROJECT_NAME STREQUAL ROOT)
    add_dependencies(${executable} move_headers)
  endif()
  if(ARG_BUILTINS)
    foreach(arg1 ${ARG_BUILTINS})
      if(${arg1}_TARGET)
        add_dependencies(${executable} ${${arg1}_TARGET})
      endif()
    endforeach()
  endif()
  if(TARGET ROOT::ROOTStaticSanitizerConfig)
    set_property(TARGET ${executable}
      APPEND PROPERTY LINK_LIBRARIES ROOT::ROOTStaticSanitizerConfig)
  endif()
  #----Installation details------------------------------------------------------
  if(NOT ARG_NOINSTALL AND CMAKE_RUNTIME_OUTPUT_DIRECTORY)
    if(NOT MSVC)
      ROOT_APPEND_LIBDIR_TO_INSTALL_RPATH(${executable} ${CMAKE_INSTALL_BINDIR})

      # The rootcling executable needs to run during build, so even if
      # CMAKE_BUILD_WITH_INSTALL_RPATH=ON was specified at config time, we have to
      # override it. Otherwise, the RPATH of rootcling inside the build tree is wrong
      # if the install tree doesn't mirror the build tree (e.g. for gnuinstall=ON).
      # All the other executables need the correct path in the build tree too, for testing.
      set_property(TARGET ${executable} PROPERTY BUILD_WITH_INSTALL_RPATH OFF)

    endif()
    if(ARG_CMAKENOEXPORT)
      install(TARGETS ${executable} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT applications)
    else()
      install(TARGETS ${executable} EXPORT ${CMAKE_PROJECT_NAME}Exports RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT applications)
    endif()
    if(WIN32)
      install(FILES ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${executable}.pdb
              CONFIGURATIONS Debug RelWithDebInfo
              DESTINATION ${CMAKE_INSTALL_BINDIR}
              COMPONENT applications)
    endif()
  else()
    # If the target is not installed, it doesn't make sense to build it with the INSTALL_RPATH
    set_property(TARGET ${executable} PROPERTY BUILD_WITH_INSTALL_RPATH OFF)
  endif()
endfunction()

#---------------------------------------------------------------------------------------------------
#---REFLEX_BUILD_DICTIONARY( dictionary headerfiles selectionfile OPTIONS opt1 opt2 ...  LIBRARIES lib1 lib2 ... )
#---------------------------------------------------------------------------------------------------
function(REFLEX_BUILD_DICTIONARY dictionary headerfiles selectionfile )
  CMAKE_PARSE_ARGUMENTS(ARG "" "" "LIBRARIES;OPTIONS" ${ARGN})
  REFLEX_GENERATE_DICTIONARY(${dictionary} ${headerfiles} SELECTION ${selectionfile} OPTIONS ${ARG_OPTIONS})
  add_library(${dictionary}Dict MODULE ${gensrcdict})
  target_link_libraries(${dictionary}Dict ${ARG_LIBRARIES} ${ROOT_Reflex_LIBRARY})
  #----Installation details-------------------------------------------------------
  install(TARGETS ${dictionary}Dict LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
  set(mergedRootMap ${CMAKE_INSTALL_PREFIX}/${lib}/${CMAKE_PROJECT_NAME}Dict.rootmap)
  set(srcRootMap ${CMAKE_CURRENT_BINARY_DIR}/${rootmapname})
  install(CODE "EXECUTE_PROCESS(COMMAND ${merge_rootmap_cmd} --do-merge --input-file ${srcRootMap} --merged-file ${mergedRootMap})")
endfunction()

#---------------------------------------------------------------------------------------------------
#---ROOT_PYTHON_PACKAGE(pkgname
#                       SOURCES source1.py source2.py
#                       [SUBDIR subdirectory]
#                      )
#
# Define a CMake target that copies Python sources to the build directory,
# compiles them to byte code, and installs both sources and bytecode into the
# configured Python install directory. You can optionally pass a subdirectory
# name if you don't want to install the package directly in the Python prefix.
# This is useful if the Python package is a subpackage of another one.
#---------------------------------------------------------------------------------------------------
function(ROOT_PYTHON_PACKAGE pkgname)
  CMAKE_PARSE_ARGUMENTS(ARG "" "SUBDIR" "SOURCES" ${ARGN})

  if(ARG_SUBDIR)
    set(pkg_path_build "${localruntimedir}/${ARG_SUBDIR}")
    set(pkg_path_install "${CMAKE_INSTALL_PYTHONDIR}/${ARG_SUBDIR}")
  else()
    set(pkg_path_build "${localruntimedir}")
    set(pkg_path_install "${CMAKE_INSTALL_PYTHONDIR}")
  endif()

  # Ensure output directory exists
  file(MAKE_DIRECTORY ${pkg_path_build})

  set(copy_commands)
  set(py_sources_in_source_dir)
  set(py_sources_in_build_tree)
  set(bytecode_dirs)

  foreach(py_source ${ARG_SOURCES})
    set(src ${CMAKE_CURRENT_SOURCE_DIR}/${py_source})
    set(tgt ${pkg_path_build}/${py_source})

    list(APPEND copy_commands COMMAND ${CMAKE_COMMAND} -E copy_if_different ${src} ${tgt})

    list(APPEND py_sources_in_source_dir ${src})
    list(APPEND py_sources_in_build_tree ${tgt})

    get_filename_component(pydir ${tgt} DIRECTORY)

    # According to PEP 3147 (https://peps.python.org/pep-3147/), the byte code
    # cache files are always in the __pycache__ directory.
    list(APPEND bytecode_dirs ${pydir}/__pycache__)
  endforeach()

  list(REMOVE_DUPLICATES bytecode_dirs)

  add_custom_command(
    OUTPUT ${py_sources_in_build_tree}
    ${copy_commands}
    DEPENDS ${py_sources_in_source_dir}
    COMMENT "Copying ${pkgname} Python sources"
  )

  # Compile .py files

  # Stamp file so CMake knows it doesn't need to re-compile. We can't set the
  # actual bytecode files as the OUTPUT of the custom command, because their
  # names are CPython implementation dependent and therefore not reliable.
  set(bytecode_stamp ${pkg_path_build}/${pkgname}-pybytecode.stamp)

  # It's 10x faster to compile all in one go than in single invocations
  add_custom_command(
    OUTPUT ${bytecode_stamp}
    COMMAND ${Python3_EXECUTABLE} -m py_compile ${py_sources_in_build_tree}
    COMMAND ${Python3_EXECUTABLE} -O -m py_compile ${py_sources_in_build_tree}
    COMMAND ${CMAKE_COMMAND} -E touch ${bytecode_stamp}
    DEPENDS ${py_sources_in_build_tree}
    COMMENT "Compiling ${pkgname} Python sources"
  )

  add_custom_target(${pkgname}Python ALL DEPENDS ${bytecode_stamp})

  # So that `make clean` / `ninja clean` picks up the bytecode
  set_property(TARGET ${pkgname}Python APPEND PROPERTY ADDITIONAL_CLEAN_FILES ${bytecode_dirs})

  # Install Python sources and bytecode
  install(DIRECTORY ${pkg_path_build}/${pkgname}
          DESTINATION ${pkg_path_install}
          COMPONENT libraries
          FILES_MATCHING
            PATTERN "*.py"
            PATTERN "*.pyc"
  )

endfunction()

# Need to set this outside of the function so that ${CMAKE_CURRENT_LIST_DIR}
# is for RootMacros.cmake and not for the file currently calling the function.
set(ROOT_TEST_DRIVER ${CMAKE_CURRENT_LIST_DIR}/RootTestDriver.cmake)

#----------------------------------------------------------------------------
# function ROOT_ADD_TEST( <name> COMMAND cmd [arg1... ]
#                        [PRECMD cmd [arg1...]] [POSTCMD cmd [arg1...]]
#                        [OUTPUT outfile] [ERROR errfile] [INPUT infile]
#                        [ENVIRONMENT var1=val1 var2=val2 ...
#                        [DEPENDS test1 ...]
#                        [RUN_SERIAL]
#                        [TIMEOUT seconds]
#                        [DEBUG]
#                        [SOURCE_DIR dir] [BINARY_DIR dir]
#                        [WORKING_DIR dir] [COPY_TO_BUILDDIR files]
#                        [BUILD target] [PROJECT project]
#                        [PASSREGEX exp] [FAILREGEX epx]
#                        [PASSRC code]
#                        [RESOURCE_LOCK lock]
#                        [FIXTURES_SETUP ...] [FIXTURES_CLEANUP ...] [FIXTURES_REQUIRED ...]
#                        [LABELS label1 label2]
#                        [PYTHON_DEPS numpy numba keras torch ...] # List of python packages required to run this test.
#                                                              A fixture will be added the tries to import them before the test starts.
#                        [PROPERTIES prop1 value1 prop2 value2...]
#                       )
#
function(ROOT_ADD_TEST test)
  CMAKE_PARSE_ARGUMENTS(ARG "DEBUG;WILLFAIL;CHECKOUT;CHECKERR;RUN_SERIAL"
                            "TIMEOUT;BUILD;INPUT;OUTPUT;ERROR;SOURCE_DIR;BINARY_DIR;WORKING_DIR;PROJECT;PASSRC;RESOURCE_LOCK"
                            "COMMAND;COPY_TO_BUILDDIR;DIFFCMD;OUTCNV;OUTCNVCMD;PRECMD;POSTCMD;ENVIRONMENT;DEPENDS;PASSREGEX;OUTREF;ERRREF;FAILREGEX;LABELS;PYTHON_DEPS;FIXTURES_SETUP;FIXTURES_CLEANUP;FIXTURES_REQUIRED;PROPERTIES"
                            ${ARGN})

  #- Handle COMMAND argument
  list(LENGTH ARG_COMMAND _len)
  if(_len LESS 1)
    if(NOT ARG_BUILD)
      message(FATAL_ERROR "ROOT_ADD_TEST: command is mandatory (without build)")
    endif()
  else()
    list(GET ARG_COMMAND 0 _prg)
    list(REMOVE_AT ARG_COMMAND 0)

    if(TARGET ${_prg})                                 # if command is a target, get the actual executable
      set(_prg "$<TARGET_FILE:${_prg}>")
      set(_cmd ${_prg} ${ARG_COMMAND})
    else()
      find_program(_exe ${_prg})
      if(_exe)                                         # if the command is found in the system, use it
        set(_cmd ${_exe} ${ARG_COMMAND})
      else()                                           # take as it is
        set(_cmd ${_prg} ${ARG_COMMAND})
      endif()
      unset(_exe CACHE)
    endif()

    string(REPLACE ";" "^" _cmd "${_cmd}")
  endif()

  set(_command ${CMAKE_COMMAND} -DCMD=${_cmd})

  #- Handle PRE and POST commands
  if(ARG_PRECMD)
    string(REPLACE ";" "^" _pre "${ARG_PRECMD}")
    set(_command ${_command} -DPRE=${_pre})
  endif()

  if(ARG_POSTCMD)
    string(REPLACE ";" "^" _post "${ARG_POSTCMD}")
    set(_command ${_command} -DPOST=${_post})
  endif()

  #- Handle INPUT, OUTPUT, ERROR, DEBUG arguments
  if(ARG_INPUT)
    set(_command ${_command} -DIN=${ARG_INPUT})
  endif()

  if(ARG_OUTPUT)
    set(_command ${_command} -DOUT=${ARG_OUTPUT})
  endif()

  if(ARG_OUTREF)
    set(_command ${_command} -DOUTREF=${ARG_OUTREF})
  endif()

  if(ARG_ERRREF)
    set(_command ${_command} -DERRREF=${ARG_ERRREF})
  endif()

  if(ARG_ERROR)
    set(_command ${_command} -DERR=${ARG_ERROR})
  endif()

  if(ARG_WORKING_DIR)
    set(_command ${_command} -DCWD=${ARG_WORKING_DIR})
  endif()

  if(ARG_DEBUG)
    set(_command ${_command} -DDBG=ON)
  endif()

  if(ARG_PASSRC)
    set(_command ${_command} -DRC=${ARG_PASSRC})
  endif()

  if(ARG_OUTCNVCMD)
    string(REPLACE ";" "^" _outcnvcmd "${ARG_OUTCNVCMD}")
    string(REPLACE "=" "@" _outcnvcmd "${_outcnvcmd}")
    set(_command ${_command} -DCNVCMD=${_outcnvcmd})
  endif()

  if(ARG_OUTCNV)
    string(REPLACE ";" "^" _outcnv "${ARG_OUTCNV}")
    set(_command ${_command} -DCNV=${_outcnv})
  endif()

  if(ARG_DIFFCMD)
    string(REPLACE ";" "^" _diff_cmd "${ARG_DIFFCMD}")
    set(_command ${_command} -DDIFFCMD=${_diff_cmd})

    if(TARGET ROOT::ROOTStaticSanitizerConfig)
      # We have to set up leak sanitizer such that it doesn't report on suppressed
      # leaks. Otherwise, all diffs will fail.
      set(LSAN_OPT ARG_ENVIRONMENT)
      list(FILTER LSAN_OPT INCLUDE REGEX LSAN_OPTIONS=[^;]+)
      if(NOT LSAN_OPT MATCHES LSAN_OPTIONS=.*)
        set(LSAN_OPT LSAN_OPTIONS=)
      endif()
      string(APPEND LSAN_OPT ":print_suppressions=0")
      list(FILTER ARG_ENVIRONMENT EXCLUDE REGEX LSAN_OPTIONS.*)
      list(APPEND ARG_ENVIRONMENT ${LSAN_OPT})
    endif()
  endif()

  if(ARG_CHECKOUT)
    set(_command ${_command} -DCHECKOUT=true)
  endif()

  if(ARG_CHECKERR)
    set(_command ${_command} -DCHECKERR=true)
  endif()

  set(_command ${_command} -DSYS=${ROOTSYS})

  #- Handle ENVIRONMENT argument
  if(ASAN_EXTRA_LD_PRELOAD)
    # Address sanitizer runtime needs to be preloaded in all python tests
    # Check now if the -DCMD= contains "python[0-9.] ", but exclude helper
    # scripts such as roottest/root/meta/genreflex/XMLParsing/parseXMLs.py
    # and roottest/root/rint/driveTabCom.py
    set(theCommand ${_command})
    list(FILTER theCommand INCLUDE REGEX "^-DCMD=.*python[0-9.]*[\\^]")
    if((theCommand AND
        NOT (_command MATCHES XMLParsing/parseXMLs.py OR
             _command MATCHES roottest/root/rint/driveTabCom.py))
       OR (_command MATCHES roottest/python/cmdLineUtils AND
           NOT _command MATCHES MakeNameCyclesRootmvInput))
      set(_command ${_command} -DCMD_ENV=${ld_preload}=${ASAN_EXTRA_LD_PRELOAD})
    endif()
  endif()

  if(ARG_ENVIRONMENT)
    string(REPLACE ";" "#" _env "${ARG_ENVIRONMENT}")
    set(_command ${_command} -DENV=${_env})
  endif()

  #- Copy files to the build directory.
  if(ARG_COPY_TO_BUILDDIR)
    string(REPLACE ";" "^" _copy_files "${ARG_COPY_TO_BUILDDIR}")
    set(_command ${_command} -DCOPY=${_copy_files})
  endif()

  set(_command ${_command} -P ${ROOT_TEST_DRIVER})

  if(ARG_WILLFAIL)
    set(test ${test}_WILL_FAIL)
  endif()

  #- Now we can actually add the test
  if(ARG_BUILD)
    if(NOT ARG_SOURCE_DIR)
      set(ARG_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR})
    endif()
    if(NOT ARG_BINARY_DIR)
      set(ARG_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR})
    endif()
    if(NOT ARG_PROJECT)
       if(NOT PROJECT_NAME STREQUAL "ROOT")
         set(ARG_PROJECT ${PROJECT_NAME})
       else()
         set(ARG_PROJECT ${ARG_BUILD})
       endif()
    endif()
    add_test(NAME ${test} COMMAND ${CMAKE_CTEST_COMMAND}
      --build-and-test  ${ARG_SOURCE_DIR} ${ARG_BINARY_DIR}
      --build-generator ${CMAKE_GENERATOR}
      --build-makeprogram ${CMAKE_MAKE_PROGRAM}
      --build-target ${ARG_BUILD}
      --build-project ${ARG_PROJECT}
      --build-config $<CONFIGURATION>
      --build-noclean
      --test-command ${_command} )
    set_property(TEST ${test} PROPERTY ENVIRONMENT ROOT_DIR=${CMAKE_BINARY_DIR})
  else()
    add_test(NAME ${test} COMMAND ${_command})
    if (gnuinstall)
      set_property(TEST ${test} PROPERTY ENVIRONMENT ROOTIGNOREPREFIX=1)
    endif()
  endif()

  #- provided fixtures and resource lock are set here
  if (ARG_FIXTURES_SETUP)
    set_property(TEST ${test} PROPERTY
      FIXTURES_SETUP ${ARG_FIXTURES_SETUP})
  endif()

  if (ARG_FIXTURES_CLEANUP)
    set_property(TEST ${test} PROPERTY
      FIXTURES_CLEANUP ${ARG_FIXTURES_CLEANUP})
  endif()

  if (ARG_FIXTURES_REQUIRED)
    set_property(TEST ${test} PROPERTY
      FIXTURES_REQUIRED ${ARG_FIXTURES_REQUIRED})
  endif()

  if (ARG_RESOURCE_LOCK)
    set_property(TEST ${test} PROPERTY
      RESOURCE_LOCK ${ARG_RESOURCE_LOCK})
  endif()

  set_property(TEST ${test} APPEND PROPERTY ENVIRONMENT ROOT_HIST=0)

  #- Handle TIMEOUT and DEPENDS arguments
  if(ARG_TIMEOUT)
    set_property(TEST ${test} PROPERTY TIMEOUT ${ARG_TIMEOUT})
  endif()

  if(ARG_DEPENDS)
    set_property(TEST ${test} PROPERTY DEPENDS ${ARG_DEPENDS})
  endif()

  if(ARG_PASSREGEX)
    set_property(TEST ${test} PROPERTY PASS_REGULAR_EXPRESSION ${ARG_PASSREGEX})
  endif()

  if(ARG_FAILREGEX)
    set_property(TEST ${test} PROPERTY FAIL_REGULAR_EXPRESSION ${ARG_FAILREGEX})
  endif()

  if(ARG_WILLFAIL)
    set_property(TEST ${test} PROPERTY WILL_FAIL true)
  endif()

  if(ARG_LABELS)
    set_tests_properties(${test} PROPERTIES LABELS "${ARG_LABELS}")
  endif()

  foreach(python_dep ${ARG_PYTHON_DEPS})
    ROOT_FIND_PYTHON_MODULE(${python_dep})
    if(NOT ROOT_${python_dep}_FOUND)
      set_property(TEST ${test} PROPERTY DISABLED True)
      continue()
    endif()
  endforeach()

  if(ARG_RUN_SERIAL)
    set_property(TEST ${test} PROPERTY RUN_SERIAL true)
  endif()

  # Pass PROPERTIES argument to the set_tests_properties as-is
  if(ARG_PROPERTIES)
    set_tests_properties(${test} PROPERTIES ${ARG_PROPERTIES})
  endif()

endfunction()

#----------------------------------------------------------------------------
# ROOT_PATH_TO_STRING( <variable> path PATH_SEPARATOR_REPLACEMENT replacement )
#
# Mangle the path to a string.
#----------------------------------------------------------------------------
function(ROOT_PATH_TO_STRING resultvar path)
  # FIXME: Copied and modified from ROOTTEST_TARGETNAME_FROM_FILE. We should find a common place for that code.
  # FIXME: ROOTTEST_TARGETNAME_FROM_FILE could be replaced by just a call to string(MAKE_C_IDENTIFIER)...
  CMAKE_PARSE_ARGUMENTS(ARG "" "" "PATH_SEPARATOR_REPLACEMENT" ${ARGN})

  set(sep_replacement "")
  if (ARG_PATH_SEPARATOR_REPLACEMENT)
    set(sep_replacement ${ARG_PATH_SEPARATOR_REPLACEMENT})
  endif()

  get_filename_component(realfp ${path} ABSOLUTE)
  get_filename_component(filename_we ${path} NAME_WE)

  string(REPLACE "${CMAKE_SOURCE_DIR}" "" relativepath ${realfp})
  string(REPLACE "${path}" "" relativepath ${relativepath})

  string(MAKE_C_IDENTIFIER ${relativepath}${filename_we} mangledname)
  string(REPLACE "_" "${sep_replacement}" mangledname ${mangledname})

  set(${resultvar} "${mangledname}" PARENT_SCOPE)
endfunction(ROOT_PATH_TO_STRING)

#----------------------------------------------------------------------------
# ROOT_ADD_UNITTEST_DIR(<libraries ...>)
#----------------------------------------------------------------------------
function(ROOT_ADD_UNITTEST_DIR)
  ROOT_GLOB_FILES(test_files ${CMAKE_CURRENT_SOURCE_DIR}/*.cxx)
  # Get the component from the path. Eg. core to form coreTests test suite name.
  ROOT_PATH_TO_STRING(test_name ${CMAKE_CURRENT_SOURCE_DIR}/)
  ROOT_ADD_GTEST(${test_name}Unit ${test_files} LIBRARIES ${ARGN})
endfunction()

#----------------------------------------------------------------------------
# function ROOT_ADD_GTEST(<testsuite> source1 source2...
#                        [WILLFAIL] Negate output of test
#                        [TIMEOUT seconds]
#                        [COPY_TO_BUILDDIR file1 file2] Copy listed files when ctest invokes the test.
#                        [LIBRARIES lib1 lib2...] -- Libraries to link against
#                        [FIXTURES_SETUP ...] [FIXTURES_CLEANUP ...] [FIXTURES_REQUIRED ...]
#                        [LABELS label1 label2...] -- Labels to annotate the test
#                        [INCLUDE_DIRS label1 label2...] -- Extra target include directories
#                        [REPEATS number] -- Repeats testsuite `number` times, stopping at the first failure.
#                        [FAILREGEX ...] Fail test if this regex matches.
#                        [ENVIRONMENT var1=val1 var2=val2 ...
# Creates a new googletest exectuable, and registers it as a test.
#----------------------------------------------------------------------------
function(ROOT_ADD_GTEST test_suite)
  cmake_parse_arguments(ARG
    "WILLFAIL"
    "TIMEOUT;REPEATS;FAILREGEX"
    "COPY_TO_BUILDDIR;LIBRARIES;LABELS;FIXTURES_SETUP;FIXTURES_CLEANUP;FIXTURES_REQUIRED;INCLUDE_DIRS;ENVIRONMENT" ${ARGN})

  ROOT_GET_SOURCES(source_files . ${ARG_UNPARSED_ARGUMENTS})
  # Note we cannot use ROOT_EXECUTABLE without user-specified set of LIBRARIES to link with.
  # The test suites should choose this in their specific CMakeLists.txt file.
  # FIXME: For better coherence we could restrict the libraries the test suite could link
  # against. For example, tests in Core should link only against libCore. This could be tricky
  # to implement because some ROOT components create more than one library.
  ROOT_EXECUTABLE(${test_suite} ${source_files} LIBRARIES ${ARG_LIBRARIES})
  target_link_libraries(${test_suite} PRIVATE GTest::gtest GTest::gmock GTest::gtest_main GTest::gmock_main)
  if(TARGET ROOT::TestSupport)
    target_link_libraries(${test_suite} PRIVATE ROOT::TestSupport)
  else()
    message(WARNING "ROOT_ADD_GTEST(${test_suite} ...): The target ROOT::TestSupport is missing. It looks like the test is declared against a ROOT build that is configured with -Dtesting=OFF.
            If this test sends warning or error messages, this will go unnoticed.")
  endif()
  target_include_directories(${test_suite} PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
  if (ARG_INCLUDE_DIRS)
    target_include_directories(${test_suite} PRIVATE ${ARG_INCLUDE_DIRS})
  endif(ARG_INCLUDE_DIRS)

  if(MSVC)
    set(test_exports "/EXPORT:_Init_thread_abort /EXPORT:_Init_thread_epoch \
        /EXPORT:_Init_thread_footer /EXPORT:_Init_thread_header /EXPORT:_tls_index")
    set_property(TARGET ${test_suite} APPEND_STRING PROPERTY LINK_FLAGS ${test_exports})
    ROOT_SET_OUTPUT_DIRECTORIES(${test_suite})
  endif()

  if(ARG_WILLFAIL)
    set(willfail WILLFAIL)
  endif()

  if(ARG_REPEATS)
    set(extra_command --gtest_repeat=${ARG_REPEATS} --gtest_break_on_failure)
  endif()

  if (ARG_FIXTURES_SETUP)
    set(fixtures_setup ${ARG_FIXTURES_SETUP})
  endif()

  if (ARG_FIXTURES_CLEANUP)
    set(fixtures_cleanup ${ARG_FIXTURES_CLEANUP})
  endif()

  if (ARG_FIXTURES_REQUIRED)
    set(fixtures_required ${ARG_FIXTURES_REQUIRED})
  endif()

  ROOT_PATH_TO_STRING(name_with_path ${test_suite} PATH_SEPARATOR_REPLACEMENT "-")
  string(REPLACE "-test-" "-" clean_name_with_path ${name_with_path})
  ROOT_ADD_TEST(
    gtest${clean_name_with_path}
    COMMAND ${test_suite} ${extra_command}
    WORKING_DIR ${CMAKE_CURRENT_BINARY_DIR}
    COPY_TO_BUILDDIR "${ARG_COPY_TO_BUILDDIR}"
    ${willfail}
    TIMEOUT "${ARG_TIMEOUT}"
    LABELS "${ARG_LABELS}"
    FIXTURES_SETUP ${fixtures_setup}
    FIXTURES_CLEANUP ${fixtures_cleanup}
    FIXTURES_REQUIRED ${fixtures_required}
    FAILREGEX "${ARG_FAILREGEX}"
    ENVIRONMENT "${ARG_ENVIRONMENT}"
  )
endfunction()


#----------------------------------------------------------------------------
# ROOT_ADD_TEST_SUBDIRECTORY( <name> )
#----------------------------------------------------------------------------
function(ROOT_ADD_TEST_SUBDIRECTORY subdir)
  set(fullsubdir ${CMAKE_CURRENT_SOURCE_DIR}/${subdir})
  cmake_path(RELATIVE_PATH fullsubdir BASE_DIRECTORY ${CMAKE_SOURCE_DIR} OUTPUT_VARIABLE subdir)
  set_property(GLOBAL APPEND PROPERTY ROOT_TEST_SUBDIRS ${subdir})
endfunction()

#----------------------------------------------------------------------------
# ROOT_ADD_PYUNITTESTS( <name> )
#----------------------------------------------------------------------------
function(ROOT_ADD_PYUNITTESTS name)

  set(pythonpaths ${localruntimedir} $ENV{PYTHONPATH})
  cmake_path(CONVERT "${pythonpaths}" TO_NATIVE_PATH_LIST pythonpaths_native)

  string(REGEX REPLACE "[_]" "-" good_name "${name}")
  ROOT_ADD_TEST(pyunittests-${good_name}
                COMMAND ${Python3_EXECUTABLE} -B -m unittest discover -s ${CMAKE_CURRENT_SOURCE_DIR} -p "*.py" -v
                ENVIRONMENT PYTHONPATH=${pythonpaths_native})
endfunction()

#----------------------------------------------------------------------------
# ROOT_ADD_PYUNITTEST( <name> <file>
#                     [WILLFAIL]
#                     [GENERIC] # Run a generic Python command `python <file>` to run the test.
#                     [COPY_TO_BUILDDIR copy_file1 copy_file1 ...]
#                     [ENVIRONMENT var1=val1 var2=val2 ...]
#                     [PYTHON_DEPS dep_x dep_y ...] # Communicate that this test requires python packages. A fixture checking for these will be run before the test.)
#                     [FIXTURES_SETUP ...] [FIXTURES_CLEANUP ...] [FIXTURES_REQUIRED ...]
#                     [PRECMD cmd [arg1...]] [POSTCMD cmd [arg1...]]
#----------------------------------------------------------------------------
function(ROOT_ADD_PYUNITTEST name file)
  CMAKE_PARSE_ARGUMENTS(ARG
      "WILLFAIL;GENERIC"
      ""
      "COPY_TO_BUILDDIR;ENVIRONMENT;PYTHON_DEPS;FIXTURES_SETUP;FIXTURES_CLEANUP;FIXTURES_REQUIRED;PRECMD;POSTCMD"
      ${ARGN}
  )

  set(pythonpaths ${localruntimedir} $ENV{PYTHONPATH})
  cmake_path(CONVERT "${pythonpaths}" TO_NATIVE_PATH_LIST pythonpaths_native)

  string(REGEX REPLACE "[_]" "-" good_name "${name}")
  get_filename_component(file_name ${file} NAME)
  get_filename_component(file_dir ${file} DIRECTORY)

  # Execute a custom command before executing the test.
  if(ARG_PRECMD)
    set(precmd PRECMD ${ARG_PRECMD})
  endif()

  if(ARG_COPY_TO_BUILDDIR)
    foreach(copy_file ${ARG_COPY_TO_BUILDDIR})
      get_filename_component(abs_path ${copy_file} ABSOLUTE)
      set(copy_files ${copy_files} ${abs_path})
    endforeach()
    set(copy_to_builddir COPY_TO_BUILDDIR ${copy_files})
  endif()

  # Execute a custom command after executing the test.
  if(ARG_POSTCMD)
    set(postcmd POSTCMD ${ARG_POSTCMD})
  endif()

  if(ARG_WILLFAIL)
    set(will_fail WILLFAIL)
  endif()

  list(APPEND labels python)
  if(ARG_PYTHON_DEPS)
    list(APPEND labels python_runtime_deps)
  endif()

  ROOT_PATH_TO_STRING(name_with_path ${good_name} PATH_SEPARATOR_REPLACEMENT "-")
  string(REPLACE "-test-" "-" clean_name_with_path ${name_with_path})

  if(ARG_GENERIC)
    set(test_cmd COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/${file_name})
  else()
    set(test_cmd COMMAND ${Python3_EXECUTABLE} -B -m unittest discover -s ${CMAKE_CURRENT_SOURCE_DIR}/${file_dir} -p ${file_name} -v)
  endif()

  set(test_name pyunittests${clean_name_with_path})
  ROOT_ADD_TEST(${test_name}
              ${test_cmd}
              ENVIRONMENT PYTHONPATH=${pythonpaths_native} ${ARG_ENVIRONMENT}
              LABELS ${labels}
              ${copy_to_builddir}
              ${will_fail}
              ${precmd}
              ${postcmd}
              PYTHON_DEPS ${ARG_PYTHON_DEPS})

  if (ARG_FIXTURES_SETUP)
    set_property(TEST ${test_name} PROPERTY
      FIXTURES_SETUP ${ARG_FIXTURES_SETUP})
  endif()

  if (ARG_FIXTURES_CLEANUP)
    set_property(TEST ${test_name} PROPERTY
      FIXTURES_CLEANUP ${ARG_FIXTURES_CLEANUP})
  endif()

  if (ARG_FIXTURES_REQUIRED)
    set_property(TEST ${test_name} PROPERTY
      FIXTURES_REQUIRED ${ARG_FIXTURES_REQUIRED})
  endif()

endfunction()

#----------------------------------------------------------------------------
# ROOT_ADD_CXX_FLAG(var flag)
#----------------------------------------------------------------------------
function(ROOT_ADD_CXX_FLAG var flag)
  string(REGEX REPLACE "[-.+/:= ]" "_" flag_esc "${flag}")
  CHECK_CXX_COMPILER_FLAG("-Werror ${flag}" CXX_HAS${flag_esc})
  if(CXX_HAS${flag_esc})
    set(${var} "${${var}} ${flag}" PARENT_SCOPE)
  endif()
endfunction()
#----------------------------------------------------------------------------
# ROOT_ADD_C_FLAG(var flag)
#----------------------------------------------------------------------------
function(ROOT_ADD_C_FLAG var flag)
  string(REGEX REPLACE "[-.+/:= ]" "_" flag_esc "${flag}")
  CHECK_C_COMPILER_FLAG("-Werror ${flag}" C_HAS${flag_esc})
  if(C_HAS${flag_esc})
    set(${var} "${${var}} ${flag}" PARENT_SCOPE)
  endif()
endfunction()

#----------------------------------------------------------------------------
# ROOT_ADD_COMPILE_OPTIONS(flags)
#----------------------------------------------------------------------------
macro(ROOT_ADD_COMPILE_OPTIONS flags)
  foreach(__flag ${flags})
    check_cxx_compiler_flag("-Werror ${__flag}" __result)
    if(__result)
      add_compile_options(${__flag})
    endif()
  endforeach()
  unset(__flag)
  unset(__result)
endmacro()

#----------------------------------------------------------------------------
# ROOT_FIND_PYTHON_MODULE(module [REQUIRED] [QUIET])
# Try importing the python dependency and cache the result in
# ROOT_TEST_<MODULE> (all upper case).
# Also set ROOT_<MODULE>_FOUND and ROOT_<module>_FOUND (the original spelling)
# in the parent scope for convenient testing in subsequent if() statements.
# Additionally, sets ROOT_<MODULE>_VERSION (and ROOT_<module>_VERSION)
# if the version could be determined.
#----------------------------------------------------------------------------
function(ROOT_FIND_PYTHON_MODULE module)
  CMAKE_PARSE_ARGUMENTS(ARG "REQUIRED;QUIET" "" "" ${ARGN})
  string(TOUPPER ${module} module_upper)
  set(CACHE_VAR ROOT_TEST_${module_upper})
  set(CACHE_VAR_VERSION "${CACHE_VAR}_VERSION")

  if(NOT DEFINED ${CACHE_VAR})
    execute_process(COMMAND "${Python3_EXECUTABLE}" "-c"
                            "import ${module}; print(getattr(${module}, '__version__', 'unknown'))"
      RESULT_VARIABLE status
      OUTPUT_VARIABLE module_version
      OUTPUT_STRIP_TRAILING_WHITESPACE
      ERROR_QUIET)

    if(${status} EQUAL 0)
      set(${CACHE_VAR} ON CACHE BOOL "Enable tests depending on '${module}'")
      # Only cache a non-empty, non-'unknown' version string.
      if(module_version AND NOT module_version STREQUAL "unknown")
        set(${CACHE_VAR_VERSION} "${module_version}" CACHE STRING "Detected version of python module ${module}")
      else()
        # ensure no stale version remains in cache
        if(DEFINED ${CACHE_VAR_VERSION})
          unset(${CACHE_VAR_VERSION} CACHE)
        endif()
        unset(module_version)
      endif()
    else()
      set(${CACHE_VAR} OFF CACHE BOOL "Enable tests depending on '${module}'")
      # ensure version cache entry is removed on failure
      if(DEFINED ${CACHE_VAR_VERSION})
        unset(${CACHE_VAR_VERSION} CACHE)
      endif()
      unset(module_version)
    endif()

    if(NOT ARG_QUIET)
      if(${CACHE_VAR})
        message(STATUS "Found Python module ${module} (found version \"${module_version}\")")
      else()
        message(STATUS "Could NOT find Python module ${module}. Corresponding tests will be disabled.")
      endif()
    endif()
  else()
    # Cache exists: if a cached version string exists, read it into module_version.
    if(DEFINED ${CACHE_VAR_VERSION})
      set(module_version ${${CACHE_VAR_VERSION}})
    endif()
  endif()

  # Set the ROOT_xxx_FOUND to the (cached) result of the search:
  set(ROOT_${module_upper}_FOUND ${${CACHE_VAR}} PARENT_SCOPE)
  set(ROOT_${module}_FOUND ${${CACHE_VAR}} PARENT_SCOPE)

  # Expose version only if module was found and a version string is available.
  if(${CACHE_VAR})
    if(DEFINED module_version AND NOT module_version STREQUAL "" AND NOT module_version STREQUAL "unknown")
      set(ROOT_${module_upper}_VERSION "${module_version}" PARENT_SCOPE)
      set(ROOT_${module}_VERSION "${module_version}" PARENT_SCOPE)
    endif()
  endif()

  if(ARG_REQUIRED AND NOT ${CACHE_VAR})
    message(FATAL_ERROR "Python module ${module} is required.")
  endif()
endfunction()

#----------------------------------------------------------------------------
# generateHeader(target input output)
# Generate a help header file with cmake/scripts/argparse2help.py script
# The 1st argument is the target to which the custom command will be attached
# The 2nd argument is the path to the python argparse input file
# The 3rd argument is the path to the output header file
#----------------------------------------------------------------------------
function(generateHeader target input output)
  add_custom_command(OUTPUT ${output}
    MAIN_DEPENDENCY
      ${input}
    DEPENDS
      ${CMAKE_SOURCE_DIR}/cmake/scripts/argparse2help.py
    COMMAND
      ${Python3_EXECUTABLE} -B ${CMAKE_SOURCE_DIR}/cmake/scripts/argparse2help.py ${input} ${output}
  )
  target_sources(${target} PRIVATE ${output})
endfunction()

#----------------------------------------------------------------------------
# Generate and install manual page with cmake/scripts/argparse2help.py script
# The 1st argument is the name of the manual page
# The 2nd argument is the path to the python argparse input file
# The 3rd argument is the path to the output manual page
#----------------------------------------------------------------------------
function(generateManual name input output)
  add_custom_target(${name} ALL DEPENDS ${output})

  add_custom_command(OUTPUT ${output}
    MAIN_DEPENDENCY
      ${input}
    DEPENDS
      ${CMAKE_SOURCE_DIR}/cmake/scripts/argparse2help.py
    COMMAND
      ${Python3_EXECUTABLE} -B ${CMAKE_SOURCE_DIR}/cmake/scripts/argparse2help.py ${input} ${output}
  )

  install(FILES ${output} DESTINATION ${CMAKE_INSTALL_MANDIR}/man1)
endfunction()

#----------------------------------------------------------------------------
# --- ROOT_APPEND_LIBDIR_TO_INSTALL_RPATH(target install_dir)
#
# Sets the INSTALL_RPATH for a given target so that it can find the ROOT shared
# libraries at runtime. The RPATH is set relative to the target's own location
# using $ORIGIN (or @loader_path on macOS).
#
# Arguments:
#   target       - The CMake target (e.g., a shared library or executable)
#   install_dir  - The install subdirectory relative to CMAKE_INSTALL_PREFIX,
#                  or an absolute directory.
#----------------------------------------------------------------------------
function(ROOT_APPEND_LIBDIR_TO_INSTALL_RPATH target install_dir)

  # Check if install_dir is absolute
  if(IS_ABSOLUTE "${install_dir}")
    set(base_dir "${install_dir}")
  else()
    set(base_dir "${CMAKE_INSTALL_PREFIX}/${install_dir}")
  endif()

  cmake_path(RELATIVE_PATH CMAKE_INSTALL_FULL_LIBDIR BASE_DIRECTORY "${base_dir}" OUTPUT_VARIABLE to_libdir)

  # New path
  if(APPLE)
    set(new_rpath "@loader_path/${to_libdir}")
  else()
    set(new_rpath "$ORIGIN/${to_libdir}")
  endif()

  # Append to existing RPATH
  set_property(TARGET ${target} APPEND PROPERTY INSTALL_RPATH "${new_rpath}")
endfunction()

#----------------------------------------------------------------------------
# If path is a system include path, set the return variable
# is_system_include_path to true.
# The 1st argument is the path that should be checked
# The 2nd argument is the return value
#----------------------------------------------------------------------------
function (IS_SYSTEM_INCLUDE_PATH path is_system_include_path)
  foreach (dir ${CMAKE_SYSTEM_INCLUDE_PATH})
    if ("${path}" STREQUAL "${dir}")
      set(${is_system_include_path} TRUE PARENT_SCOPE)
      return()
    endif()
  endforeach()
  set(${is_system_include_path} FALSE PARENT_SCOPE)
endfunction()

#----------------------------------------------------------------------------
# Include paths of third-party libraries stored outside of system directories
# must be added to the DEFAULT_ROOT_INCLUDE_PATH variable to ensure that the
# C++ interpreter 'cling' can locate these header files at runtime.
# Use this function to add these paths to DEFAULT_ROOT_INCLUDE_PATH.
#----------------------------------------------------------------------------
function (BUILD_ROOT_INCLUDE_PATH path)
  # filter out paths into the ROOT src, build, and install directories
  if((${path} MATCHES "${CMAKE_SOURCE_DIR}(/.*)?") OR
     (${path} MATCHES "${CMAKE_BINARY_DIR}(/.*)?") OR
     (${path} MATCHES "${CMAKE_INSTALL_PREFIX}(/.*)?"))
    return()
  endif()
  IS_SYSTEM_INCLUDE_PATH("${path}" is_system_include_path)
  if (NOT is_system_include_path)
    if ("${DEFAULT_ROOT_INCLUDE_PATH}" STREQUAL "")
      set(DEFAULT_ROOT_INCLUDE_PATH "${path}" PARENT_SCOPE)
    else()
      if(WIN32)
        set(ROOT_PATH_SEPARATOR ";")
      elseif(UNIX)
        set(ROOT_PATH_SEPARATOR ":")
      endif()
      set(DEFAULT_ROOT_INCLUDE_PATH "${DEFAULT_ROOT_INCLUDE_PATH}${ROOT_PATH_SEPARATOR}${path}" PARENT_SCOPE)
    endif()
  endif()
endfunction()

#-------------------------------------------------------------------------------
#
#  Former RoottestMacros.cmake starts here
#
#-------------------------------------------------------------------------------

if(CMAKE_GENERATOR MATCHES Makefiles)
  set(fast /fast)
  set(always-make --always-make)
endif()
if(MSVC AND NOT CMAKE_GENERATOR MATCHES Ninja)
  set(always-make -v:m -clp:Summary -p:BuildProjectReferences=false)
endif()
#-------------------------------------------------------------------------------
#
#  function ROOTTEST_ADD_TESTDIRS([EXCLUDED_DIRS] dir)
#
#  Scans all subdirectories for CMakeLists.txt files. Each subdirectory that
#  contains a CMakeLists.txt file is then added as a subdirectory.
#-------------------------------------------------------------------------------
function(ROOTTEST_ADD_TESTDIRS)

  set(dirs "")
  CMAKE_PARSE_ARGUMENTS(ARG "" "" "EXCLUDED_DIRS" ${ARGN})
  set(curdir ${CMAKE_CURRENT_SOURCE_DIR})

  file(GLOB found_dirs ${curdir} ${curdir}/*)

  # If there are excluded directories through EXCLUDED_DIRS,
  # add_subdirectory() for them will not be applied
  if(ARG_EXCLUDED_DIRS)
    foreach(excluded_dir ${ARG_EXCLUDED_DIRS})
      list(REMOVE_ITEM found_dirs "${CMAKE_CURRENT_SOURCE_DIR}/${excluded_dir}")
    endforeach()
  endif()

  foreach(f ${found_dirs})
    if(IS_DIRECTORY ${f})
      if(EXISTS "${f}/CMakeLists.txt" AND NOT ${f} STREQUAL ${curdir})
        list(APPEND dirs ${f})
      endif()
    endif()
  endforeach()

  list(SORT dirs)

  foreach(d ${dirs})
    string(REPLACE "${curdir}/" "" d ${d})
    add_subdirectory(${d})
    # create .rootrc in binary directory to avoid filling $HOME/.root_hist
    if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${d}/.rootrc")
      configure_file(${CMAKE_CURRENT_SOURCE_DIR}/${d}/.rootrc ${CMAKE_CURRENT_BINARY_DIR}/${d} COPYONLY)
    else()
      file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/${d}/.rootrc "
Rint.History:  .root_hist
ACLiC.LinkLibs:  1
")
    endif()
  endforeach()

endfunction()

#-------------------------------------------------------------------------------
#
#  function ROOTTEST_SET_TESTOWNER(owner)
#
#  Specify the owner of the tests in the current directory. Note, that the owner
#  can be specified for each test individually, as well.
#
#-------------------------------------------------------------------------------
function(ROOTTEST_SET_TESTOWNER owner)
  set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
               PROPERTY ROOTTEST_TEST_OWNER ${owner})
endfunction(ROOTTEST_SET_TESTOWNER)

#-------------------------------------------------------------------------------
#
# function ROOTTEEST_TARGETNAME_FROM_FILE(<resultvar> <filename>)
#
# Construct a target name for a given file <filename> and store its name into
# <resultvar>. The target name is of the form:
#
#   <directorypath>-<filename_WE>
#
#-------------------------------------------------------------------------------
function(ROOTTEST_TARGETNAME_FROM_FILE resultvar filename)

  get_filename_component(realfp ${filename} ABSOLUTE)
  get_filename_component(filename_we ${filename} NAME_WE)

  string(REPLACE "${CMAKE_SOURCE_DIR}/" "" relativepath ${realfp})
  string(REPLACE "${filename}"     "" relativepath ${relativepath})

  string(REPLACE "/" "-" targetname ${relativepath}${filename_we})
  set(${resultvar} "${targetname}" PARENT_SCOPE)

endfunction(ROOTTEST_TARGETNAME_FROM_FILE)

#-------------------------------------------------------------------------------
#
# function ROOTTEST_ADD_AUTOMACROS(DEPENDS [dependencies ...]
#                                  [FIXTURES_SETUP ...] [FIXTURES_CLEANUP ...] [FIXTURES_REQUIRED ...])
#
# Automatically adds all macros in the current source directory to the list of
# tests that follow the naming scheme:
#
#   run*.C, run*.cxx, assert*.C, assert*.cxx, exec*.C, exec*.cxx
#
#-------------------------------------------------------------------------------
function(ROOTTEST_ADD_AUTOMACROS)
  CMAKE_PARSE_ARGUMENTS(ARG "" "" "DEPENDS;WILLFAIL;EXCLUDE;FIXTURES_SETUP;FIXTURES_CLEANUP;FIXTURES_REQUIRED" ${ARGN})

  file(GLOB automacros run*.C run*.cxx assert*.C assert*.cxx exec*.C exec*.cxx)

  foreach(dep ${ARG_DEPENDS})
    if(${dep} MATCHES "[.]C" OR ${dep} MATCHES "[.]cxx" OR ${dep} MATCHES "[.]h")
      ROOTTEST_COMPILE_MACRO(${dep})
      list(APPEND auto_depends ${COMPILE_MACRO_TEST})
    else()
      list(APPEND auto_depends ${dep})
    endif()
  endforeach()

  foreach(am ${automacros})
    get_filename_component(auto_macro_filename ${am} NAME)
    get_filename_component(auto_macro_name  ${am} NAME_WE)
    if(${auto_macro_name} MATCHES "^run")
      string(REPLACE run "" auto_macro_subname ${auto_macro_name})
    elseif(${auto_macro_name} MATCHES "^exec")
      string(REPLACE exec "" auto_macro_subname ${auto_macro_name})
    else()
      set(auto_macro_subname ${auto_macro_name})
    endif()

    if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${auto_macro_name}.ref)
      set(outref OUTREF ${auto_macro_name}.ref)
    elseif(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${auto_macro_subname}.ref)
      set(outref OUTREF ${auto_macro_subname}.ref)
    else()
      set(outref "")
    endif()

    ROOTTEST_TARGETNAME_FROM_FILE(targetname ${auto_macro_filename})

    foreach(wf ${ARG_WILLFAIL})
      if(${auto_macro_name} MATCHES ${wf})
        set(arg_wf WILLFAIL)
      endif()
    endforeach()

    set(selected 1)
    foreach(excl ${ARG_EXCLUDE})
      if(${auto_macro_name} MATCHES ${excl})
        set(selected 0)
        break()
      endif()
    endforeach()

    if (ARG_FIXTURES_SETUP)
      set(fixtures_setup ${ARG_FIXTURES_SETUP})
    endif()

    if (ARG_FIXTURES_CLEANUP)
      set(fixtures_cleanup ${ARG_FIXTURES_CLEANUP})
    endif()

    if (ARG_FIXTURES_REQUIRED)
      set(fixtures_required ${ARG_FIXTURES_REQUIRED})
    endif()

    if(selected)
      ROOTTEST_ADD_TEST(${targetname}-auto
                        MACRO ${auto_macro_filename}${${auto_macro_name}-suffix}
                        ${outref}
                        ${arg_wf}
                        FIXTURES_SETUP ${fixtures_setup}
                        FIXTURES_CLEANUP ${fixtures_cleanup}
                        FIXTURES_REQUIRED ${fixtures_required}
                        DEPENDS ${auto_depends})
    endif()
  endforeach()

endfunction(ROOTTEST_ADD_AUTOMACROS)

#-------------------------------------------------------------------------------
#
# macro ROOTTEST_COMPILE_MACRO(<filename> [BUILDOBJ object] [BUILDLIB lib]
#                                         [FIXTURES_SETUP ...] [FIXTURES_CLEANUP ...] [FIXTURES_REQUIRED ...])
#
# This macro creates and loads a shared library containing the code from
# the file <filename>. A test that performs the compilation is created.
# The target name of the created test is stored in the variable
# COMPILE_MACRO_TEST which can be accessed by the calling CMakeLists.txt in
# order to manage dependencies.
#
#-------------------------------------------------------------------------------
function(ROOTTEST_COMPILE_MACRO filename)
  CMAKE_PARSE_ARGUMENTS(ARG "" "BUILDOBJ;BUILDLIB" "FIXTURES_SETUP;FIXTURES_CLEANUP;FIXTURES_REQUIRED"  ${ARGN})

  # Add defines to root_compile_macro, in order to have out-of-source builds
  # when using the scripts/build.C macro.
  get_directory_property(DirDefs COMPILE_DEFINITIONS)

  unset(RootMacroDirDefines)
  foreach(d ${DirDefs})
    if(d MATCHES "_WIN32" OR d MATCHES "_XKEYCHECK_H" OR d MATCHES "NOMINMAX")
      continue()
    endif()
    list(APPEND RootMacroDirDefines "-e;#define ${d}")
  endforeach()

  cmake_path(CONVERT "${CMAKE_CURRENT_BINARY_DIR}" TO_NATIVE_PATH_LIST NATIVE_BINARY_DIR)
  set(root_compile_macro ${CMAKE_COMMAND} -E env
      ROOT_LIBRARY_PATH=${NATIVE_BINARY_DIR}
      ROOT_INCLUDE_PATH=${CMAKE_CURRENT_BINARY_DIR}:${DEFAULT_ROOT_INCLUDE_PATH}
      ${ROOT_root_CMD}
      -e "gSystem->SetBuildDir(\"${CMAKE_CURRENT_BINARY_DIR}\", true)"
      ${RootMacroDirDefines}
      -q -b
  )

  get_filename_component(realfp ${filename} ABSOLUTE)
  if(MSVC)
    string(REPLACE "/" "\\\\" realfp ${realfp})
  endif()

  set(BuildScriptFile ${ROOT_SOURCE_DIR}/roottest/scripts/build.C)

  set(BuildScriptArg \(\"${realfp}\",\"${ARG_BUILDLIB}\",\"${ARG_BUILDOBJ}\"\))

  set(compile_macro_command ${root_compile_macro}
                            ${BuildScriptFile}${BuildScriptArg}
                            WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})

  ROOTTEST_TARGETNAME_FROM_FILE(COMPILE_MACRO_TEST ${filename})

  set(COMPILE_MACRO_TEST ${COMPILE_MACRO_TEST}-build)
  set(COMPILE_MACRO_TEST ${COMPILE_MACRO_TEST} PARENT_SCOPE)

  add_test(NAME ${COMPILE_MACRO_TEST} COMMAND ${compile_macro_command})
  if(NOT MSVC OR win_broken_tests)
    set_property(TEST ${COMPILE_MACRO_TEST} PROPERTY FAIL_REGULAR_EXPRESSION "Warning in")
  endif()
  set_property(TEST ${COMPILE_MACRO_TEST} PROPERTY ENVIRONMENT ${ROOTTEST_ENVIRONMENT})
  if (ARG_FIXTURES_SETUP)
    set_property(TEST ${COMPILE_MACRO_TEST} PROPERTY
      FIXTURES_SETUP ${ARG_FIXTURES_SETUP})
  endif()
  if (ARG_FIXTURES_CLEANUP)
    set_property(TEST ${COMPILE_MACRO_TEST} PROPERTY
      FIXTURES_CLEANUP ${ARG_FIXTURES_CLEANUP})
  endif()

  if (ARG_FIXTURES_REQUIRED)
    set_property(TEST ${COMPILE_MACRO_TEST} PROPERTY
      FIXTURES_REQUIRED ${ARG_FIXTURES_REQUIRED})
  endif()

endfunction(ROOTTEST_COMPILE_MACRO)

#-------------------------------------------------------------------------------
#
# macro ROOTTEST_GENERATE_DICTIONARY(<dictname>
#                                    [LINKDEF linkdef]
#                                    [DEPENDS deps]
#                                    [OPTIONS opts]
#                                    [FIXTURES_SETUP ...] [FIXTURES_CLEANUP ...] [FIXTURES_REQUIRED ...]
#                                    [files ...]      )
#
# This macro generates a dictionary <dictname> from the provided <files>.
# A test that performs the dictionary generation is created.  The target name of
# the created test is stored in the variable GENERATE_DICTIONARY_TEST which can
# be accessed by the calling CMakeLists.txt in order to manage dependencies.
#
#-------------------------------------------------------------------------------
macro(ROOTTEST_GENERATE_DICTIONARY dictname)
  CMAKE_PARSE_ARGUMENTS(ARG "NO_ROOTMAP;NO_CXXMODULE" "FIXTURES_SETUP;FIXTURES_CLEANUP;FIXTURES_REQUIRED" "LINKDEF;SOURCES;DEPENDS;OPTIONS;COMPILE_OPTIONS" ${ARGN})

  set(CMAKE_ROOTTEST_DICT ON)

  if(ARG_NO_ROOTMAP)
    set(CMAKE_ROOTTEST_NOROOTMAP ON)
  endif()
  if(ARG_NO_CXXMODULE)
    set(EXTRA_ARGS NO_CXXMODULE)
  endif()

  # roottest dictionaries do not need to be relocatable. Instead, allow
  # dictionaries to find the input headers even from the source directory
  # - without ROOT_INCLUDE_PATH - by passing the full path to rootcling:
  set(FULL_PATH_HEADERS )
  foreach(hdr ${ARG_UNPARSED_ARGUMENTS})
    if(IS_ABSOLUTE ${hdr})
      list(APPEND FULL_PATH_HEADERS ${hdr})
    else()
      list(APPEND FULL_PATH_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/${hdr})
    endif()
  endforeach()

  ROOT_GENERATE_DICTIONARY(${dictname} ${FULL_PATH_HEADERS}
                           ${EXTRA_ARGS}
                           MODULE ${dictname}
                           LINKDEF ${ARG_LINKDEF}
                           OPTIONS ${ARG_OPTIONS}
                           DEPENDENCIES ${ARG_DEPENDS})

  ROOTTEST_TARGETNAME_FROM_FILE(GENERATE_DICTIONARY_TEST ${dictname})

  set(GENERATE_DICTIONARY_TEST ${GENERATE_DICTIONARY_TEST}-build)

  set(targetname_libgen ${dictname}libgen)

  add_library(${targetname_libgen} EXCLUDE_FROM_ALL SHARED ${dictname}.cxx)
  set_property(TARGET ${targetname_libgen} PROPERTY BUILD_WITH_INSTALL_RPATH OFF) # will never be installed anyway

  if(ARG_SOURCES)
    target_sources(${targetname_libgen} PUBLIC ${ARG_SOURCES})
  endif()

  if(ARG_COMPILE_OPTIONS)
    target_compile_options(${targetname_libgen} PRIVATE ${ARG_COMPILE_OPTIONS})
  endif()

  set_target_properties(${targetname_libgen} PROPERTIES ${ROOT_LIBRARY_PROPERTIES})
  set_target_properties(${targetname_libgen} PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS TRUE)
  ROOT_SET_OUTPUT_DIRECTORIES(${targetname_libgen})

  target_link_libraries(${targetname_libgen} ${ROOT_LIBRARIES})

  set_target_properties(${targetname_libgen} PROPERTIES PREFIX "")

  set_target_properties(${targetname_libgen} PROPERTIES OUTPUT_NAME ${dictname})

  set_property(TARGET ${targetname_libgen} APPEND PROPERTY INCLUDE_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR})

  add_dependencies(${targetname_libgen} ${dictname})

  # We use the /fast variant of targetname_libgen, so we won't automatically
  # build dependencies. Still, the dictname target is a clear dependency (see
  # line above), so we have to explicilty build it too.
  add_test(NAME ${GENERATE_DICTIONARY_TEST}
           COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR}
                                    ${build_config}
                                    --target ${dictname}${fast} ${targetname_libgen}${fast}
                                    -- ${always-make})

  set_property(TEST ${GENERATE_DICTIONARY_TEST} PROPERTY ENVIRONMENT ${ROOTTEST_ENVIRONMENT})
  if(GeneratorNeedsBuildSerialization)
    if(GeneratorNeedsResourecLock)
      set_property(TEST ${GENERATE_DICTIONARY_TEST} APPEND PROPERTY RESOURCE_LOCK CMAKE_BUILD)
    endif()
    set_property(TEST ${GENERATE_DICTIONARY_TEST} APPEND PROPERTY FIXTURES_REQUIRED CMAKE_BUILD_ALL)
  endif()

  if (ARG_FIXTURES_SETUP)
    set_property(TEST ${GENERATE_DICTIONARY_TEST} PROPERTY
      FIXTURES_SETUP ${ARG_FIXTURES_SETUP})
  endif()

  if (ARG_FIXTURES_CLEANUP)
    set_property(TEST ${GENERATE_DICTIONARY_TEST} PROPERTY
      FIXTURES_CLEANUP ${ARG_FIXTURES_CLEANUP})
  endif()

  if (ARG_FIXTURES_REQUIRED)
    set_property(TEST ${GENERATE_DICTIONARY_TEST} PROPERTY
      FIXTURES_REQUIRED ${ARG_FIXTURES_REQUIRED})
  endif()

endmacro(ROOTTEST_GENERATE_DICTIONARY)

#-------------------------------------------------------------------------------
#
# macro ROOTTEST_GENERATE_REFLEX_DICTIONARY(<targetname> <dictionary>
#                                              [SELECTION sel...]
#                                              [headerfiles...]
#                                              [LIBNAME lib...]
#                                              [FIXTURES_SETUP ...] [FIXTURES_CLEANUP ...] [FIXTURES_REQUIRED ...]
#                                              [LIBRARIES lib1 lib2 ...]
#                                              [OPTIONS opt1 opt2 ...])
#
# This macro generates a reflexion dictionary and creates a shared library.
# A test that performs the dictionary generation is created.  The target name of
# the created test is stored in the variable GENERATE_REFLEX_TEST which can
# be accessed by the calling CMakeLists.txt in order to manage dependencies.
#
#-------------------------------------------------------------------------------
macro(ROOTTEST_GENERATE_REFLEX_DICTIONARY dictionary)
  CMAKE_PARSE_ARGUMENTS(ARG "NO_ROOTMAP" "SELECTION;LIBNAME;FIXTURES_SETUP;FIXTURES_CLEANUP;FIXTURES_REQUIRED" "LIBRARIES;OPTIONS;COMPILE_OPTIONS" ${ARGN})

  set(CMAKE_ROOTTEST_DICT ON)

  if(ARG_NO_ROOTMAP)
    set(CMAKE_ROOTTEST_NOROOTMAP ON)
  else()
    set(CMAKE_ROOTTEST_NOROOTMAP OFF)
  endif()

  set(ROOT_genreflex_CMD ${ROOT_BINDIR}/genreflex)

  ROOTTEST_TARGETNAME_FROM_FILE(targetname ${dictionary})

  set(targetname_libgen ${targetname}-libgen)

  # targetname_dictgen is the targetname constructed by the
  # REFLEX_GENERATE_DICTIONARY macro and is used as a dependency.
  set(targetname_dictgen ${targetname}-dictgen)

  if(ARG_OPTIONS)
    set(reflex_pass_options OPTIONS ${ARG_OPTIONS})
  endif()

  REFLEX_GENERATE_DICTIONARY(${dictionary} ${ARG_UNPARSED_ARGUMENTS}
                             SELECTION ${ARG_SELECTION}
                             ${reflex_pass_options})

  add_library(${targetname_libgen} EXCLUDE_FROM_ALL SHARED ${dictionary}.cxx)
  set_target_properties(${targetname_libgen} PROPERTIES  ${ROOT_LIBRARY_PROPERTIES} )
  set_property(TARGET ${targetname_libgen} PROPERTY BUILD_WITH_INSTALL_RPATH OFF) # will never be installed anyway
  set_target_properties(${targetname_libgen} PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS TRUE)
  ROOT_SET_OUTPUT_DIRECTORIES(${targetname_libgen})

  if(ARG_LIBNAME)
    set_target_properties(${targetname_libgen} PROPERTIES PREFIX "")
    set_property(TARGET ${targetname_libgen}
                 PROPERTY OUTPUT_NAME ${ARG_LIBNAME})
  else()
    set_property(TARGET ${targetname_libgen}
                 PROPERTY OUTPUT_NAME ${dictionary}_dictrflx)
  endif()

  if(ARG_COMPILE_OPTIONS)
    target_compile_options(${targetname_libgen} PRIVATE ${ARG_COMPILE_OPTIONS})
  endif()

  add_dependencies(${targetname_libgen}
                   ${targetname_dictgen})

  target_link_libraries(${targetname_libgen}
                        ${ARG_LIBRARIES}
                        ${ROOT_LIBRARIES})

  set_property(TARGET ${targetname_libgen}
               APPEND PROPERTY INCLUDE_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR})

  set(GENERATE_REFLEX_TEST ${targetname_libgen}-build)

  # We use the /fast variant of targetname_libgen, so we won't automatically
  # build dependencies. Still, the targetname_dictgen is a clear dependency
  # (see line above), so we have to explicilty build it too.
  add_test(NAME ${GENERATE_REFLEX_TEST}
           COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR}
                                    ${build_config}
                                    --target ${targetname_dictgen}${fast} ${targetname_libgen}${fast}
                                    -- ${always-make})

  set_property(TEST ${GENERATE_REFLEX_TEST} PROPERTY ENVIRONMENT ${ROOTTEST_ENVIRONMENT})
  if(GeneratorNeedsBuildSerialization)
    if(GeneratorNeedsResourecLock)
      set_property(TEST ${GENERATE_REFLEX_TEST} APPEND PROPERTY RESOURCE_LOCK CMAKE_BUILD)
    endif()
    set_property(TEST ${GENERATE_REFLEX_TEST} APPEND PROPERTY FIXTURES_REQUIRED CMAKE_BUILD_ALL)
  endif()

  if (ARG_FIXTURES_SETUP)
    set(more_fixtures ${ARG_FIXTURES_SETUP})
  endif()
  set_property(TEST ${GENERATE_REFLEX_TEST} PROPERTY
    FIXTURES_SETUP ${GENERATE_REFLEX_TEST}-fixture ${more_fixtures})

  if (ARG_FIXTURES_CLEANUP)
    set_property(TEST ${GENERATE_REFLEX_TEST} PROPERTY
      FIXTURES_CLEANUP ${ARG_FIXTURES_CLEANUP})
  endif()

  if (ARG_FIXTURES_REQUIRED)
    set_property(TEST ${GENERATE_REFLEX_TEST} PROPERTY
      FIXTURES_REQUIRED ${ARG_FIXTURES_REQUIRED})
  endif()

endmacro(ROOTTEST_GENERATE_REFLEX_DICTIONARY)

#-------------------------------------------------------------------------------
#
# macro ROOTTEST_GENERATE_EXECUTABLE(<executable>
#                                    [LIBRARIES lib1 lib2 ...]
#                                    [COMPILE_FLAGS flag1 flag2 ...]
#                                    [DEPENDS ...]
#                                    [RESOURCE_LOCK lock]
#                                    [FIXTURES_SETUP ...] [FIXTURES_CLEANUP ...] [FIXTURES_REQUIRED ...])
# This macro generates an executable the the building of it becames a test
#
#-------------------------------------------------------------------------------
macro(ROOTTEST_GENERATE_EXECUTABLE executable)
  CMAKE_PARSE_ARGUMENTS(ARG "" "RESOURCE_LOCK" "LIBRARIES;COMPILE_FLAGS;DEPENDS;FIXTURES_SETUP;FIXTURES_CLEANUP;FIXTURES_REQUIRED" ${ARGN})

  add_executable(${executable} EXCLUDE_FROM_ALL ${ARG_UNPARSED_ARGUMENTS})
  set_target_properties(${executable} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
  set_property(TARGET ${executable} PROPERTY BUILD_WITH_INSTALL_RPATH OFF) # will never be installed anyway

  set_property(TARGET ${executable}
               APPEND PROPERTY INCLUDE_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR})

  if(ARG_DEPENDS)
    add_dependencies(${executable} ${ARG_DEPENDS})
  endif()

  if(ARG_LIBRARIES)
    if(MSVC AND NOT CMAKE_GENERATOR MATCHES Ninja)
      foreach(library ${ARG_LIBRARIES})
        if("${library}" MATCHES "ROOT::")
          string(REPLACE "ROOT::" "" library ${library})
        endif()
        if(${library} MATCHES "[::]")
          set(libraries ${libraries} ${library})
        elseif(NOT ${library} MATCHES "^lib" AND NOT ${library} MATCHES "^gtest" AND NOT ${library} MATCHES "^gmock")
          set(libraries ${libraries} lib${library})
        else()
          set(libraries ${libraries} ${library})
        endif()
      endforeach()
      target_link_libraries(${executable} ${libraries})
    else()
      target_link_libraries(${executable} ${ARG_LIBRARIES})
    endif()
  endif()
  if(MSVC AND DEFINED ROOT_SOURCE_DIR)
    if(TARGET ROOTStaticSanitizerConfig)
      target_link_libraries(${executable} ROOTStaticSanitizerConfig)
    endif()
  else()
    if(TARGET ROOT::ROOTStaticSanitizerConfig)
      target_link_libraries(${executable} ROOT::ROOTStaticSanitizerConfig)
    endif()
  endif()

  if(ARG_COMPILE_FLAGS)
    set_target_properties(${executable} PROPERTIES COMPILE_FLAGS ${ARG_COMPILE_FLAGS})
  endif()

  ROOTTEST_TARGETNAME_FROM_FILE(GENERATE_EXECUTABLE_TEST ${executable})

  set(GENERATE_EXECUTABLE_TEST ${GENERATE_EXECUTABLE_TEST}-build)

  add_test(NAME ${GENERATE_EXECUTABLE_TEST}
           COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR}
                                    ${build_config}
                                    --target ${executable}${fast}
                                    -- ${always-make})
  set_property(TEST ${GENERATE_EXECUTABLE_TEST} PROPERTY ENVIRONMENT ${ROOTTEST_ENVIRONMENT})

  #- provided fixtures and resource lock are set here
  if (ARG_FIXTURES_SETUP)
    set_property(TEST ${GENERATE_EXECUTABLE_TEST} PROPERTY
      FIXTURES_SETUP ${ARG_FIXTURES_SETUP})
  endif()

  if (ARG_FIXTURES_CLEANUP)
    set_property(TEST ${GENERATE_EXECUTABLE_TEST} PROPERTY
      FIXTURES_CLEANUP ${ARG_FIXTURES_CLEANUP})
  endif()

  if (ARG_FIXTURES_REQUIRED)
    set_property(TEST ${GENERATE_EXECUTABLE_TEST} PROPERTY
      FIXTURES_REQUIRED ${ARG_FIXTURES_REQUIRED})
  endif()

  if (ARG_RESOURCE_LOCK)
    set_property(TEST ${GENERATE_EXECUTABLE_TEST} PROPERTY
      RESOURCE_LOCK ${ARG_RESOURCE_LOCK})
  endif()

  if(GeneratorNeedsBuildSerialization)
    if(GeneratorNeedsResourecLock)
      set_property(TEST ${GENERATE_EXECUTABLE_TEST} APPEND PROPERTY RESOURCE_LOCK CMAKE_BUILD)
    endif()
    set_property(TEST ${GENERATE_EXECUTABLE_TEST} APPEND PROPERTY FIXTURES_REQUIRED CMAKE_BUILD_ALL)
  endif()

  if(MSVC AND NOT CMAKE_GENERATOR MATCHES Ninja)
    add_custom_command(TARGET ${executable} POST_BUILD
       COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/$<CONFIG>/${executable}.exe
                                        ${CMAKE_CURRENT_BINARY_DIR}/${executable}.exe)
  endif()

endmacro()

#-------------------------------------------------------------------------------
#
# function ROOTTEST_ADD_OLDTEST()
#
# This function defines a single tests in the current directory that calls the legacy
# make system to run the defined tests.
#
#-------------------------------------------------------------------------------

find_program(ROOT_GMAKE_PROGRAM gmake)
if (${ROOT_GMAKE_PROGRAM} MATCHES NOTFOUND)
  set(ROOT_GMAKE_PROGRAM make)
endif()

function(ROOTTEST_ADD_OLDTEST)
  CMAKE_PARSE_ARGUMENTS(ARG "" "" "LABELS;TIMEOUT" ${ARGN})

  ROOTTEST_ADD_TEST( make
                     COMMAND ${ROOT_GMAKE_PROGRAM} cleantest ${ROOTTEST_PARALLEL_MAKE}
                     WORKING_DIR ${CMAKE_CURRENT_SOURCE_DIR}
                     DEPENDS roottest-root-io-event
                     FIXTURES_REQUIRED UtilsLibraryBuild
                     LABELS ${ARG_LABELS} TIMEOUT ${ARG_TIMEOUT})
  if(MSVC)
    ROOTTEST_TARGETNAME_FROM_FILE(testprefix .)
    set(fulltestname "${testprefix}-make")
    set_property(TEST ${fulltestname} PROPERTY DISABLED true)
  endif()
endfunction()

#-------------------------------------------------------------------------------
# macro ROOTTEST_SETUP_MACROTEST()
#
# A helper macro to define the command to run a ROOT macro (.C, .C+ or .py)
#-------------------------------------------------------------------------------
macro(ROOTTEST_SETUP_MACROTEST)

  get_directory_property(DirDefs COMPILE_DEFINITIONS)

  foreach(d ${DirDefs})
    if(d MATCHES "_WIN32" OR d MATCHES "_XKEYCHECK_H" OR d MATCHES "NOMINMAX")
      continue()
    endif()
    list(APPEND RootExeDefines "-e;#define ${d}")
  endforeach()

  set(root_cmd ${ROOT_root_CMD} ${RootExeDefines}
               -e "gSystem->SetBuildDir(\"${CMAKE_CURRENT_BINARY_DIR}\",true)"
               -e "gSystem->AddDynamicPath(\"${CMAKE_CURRENT_BINARY_DIR}\")"
               -e "gROOT->SetMacroPath(\"${CMAKE_CURRENT_SOURCE_DIR}\")"
               -e "gInterpreter->AddIncludePath(\"-I${CMAKE_CURRENT_BINARY_DIR}\")"
               -e "gSystem->AddIncludePath(\"-I${CMAKE_CURRENT_BINARY_DIR}\")"
               ${ARG_ROOTEXE_OPTS}
               -q -b)

  set(root_buildcmd ${ROOT_root_CMD} ${RootExeDefines} -q -b)

  # Compile macro, then add to CTest.
  if(ARG_MACRO MATCHES "[.]C\\+" OR ARG_MACRO MATCHES "[.]cxx\\+" OR ARG_MACRO MATCHES "[.]cpp\\+" OR ARG_MACRO MATCHES "[.]cc\\+")
    string(REPLACE "+" "" compile_name "${ARG_MACRO}")
    get_filename_component(realfp ${compile_name} REALPATH)

    if(DEFINED ARG_MACROARG)
      set(command ${root_cmd} "${realfp}+(${ARG_MACROARG})")
    else()
      set(command ${root_cmd} "${realfp}+")
    endif()

  # Add interpreted macro to CTest.
  elseif(ARG_MACRO MATCHES "[.]C" OR ARG_MACRO MATCHES "[.]cxx" OR ARG_MACRO MATCHES "[.]cpp" OR ARG_MACRO MATCHES "[.]cc")
    get_filename_component(realfp ${ARG_MACRO} REALPATH)
    if(DEFINED ARG_MACROARG)
      set(realfp "${realfp}(${ARG_MACROARG})")
    endif()

    set(command ${root_cmd} ${realfp})

  # Add python script to CTest.
  elseif(ARG_MACRO MATCHES "[.]py")
    get_filename_component(realfp ${ARG_MACRO} REALPATH)
    set(command ${Python3_EXECUTABLE} ${realfp} ${PYROOT_EXTRAFLAGS})

  elseif(DEFINED ARG_MACRO)
    set(command ${root_cmd} ${ARG_MACRO})
  endif()

  # Check for assert prefix -- only log stderr.
  if(ARG_MACRO MATCHES "^assert")
    set(checkstdout "")
    set(checkstderr CHECKERR)
  else()
    set(checkstdout CHECKOUT)
    set(checkstderr CHECKERR)
  endif()

endmacro(ROOTTEST_SETUP_MACROTEST)

#-------------------------------------------------------------------------------
# macro ROOTTEST_SETUP_EXECTEST()
#
# A helper macro to define the command to run an executable
#-------------------------------------------------------------------------------
macro(ROOTTEST_SETUP_EXECTEST)

  find_program(realexec ${ARG_EXEC}
               HINTS $ENV{PATH}
               PATH ${CMAKE_CURRENT_BINARY_DIR}
               PATH ${CMAKE_CURRENT_SOURCE_DIR})

  # If no program was found, take it as is.
  if(NOT realexec)
    set(realexec ${ARG_EXEC})
  endif()

  if(MSVC)
    if(${realexec} MATCHES "[.]py" AND NOT ${realexec} MATCHES "[.]exe")
      set(realexec ${Python3_EXECUTABLE} ${realexec})
    else()
      set(realexec ${realexec})
    endif()
  endif()

  set(command ${realexec})

  unset(realexec CACHE)

  set(checkstdout CHECKOUT)
  set(checkstderr CHECKERR)

endmacro(ROOTTEST_SETUP_EXECTEST)

#-------------------------------------------------------------------------------
#
# function ROOTTEST_ADD_TEST(testname
#                            MACRO|EXEC macro_or_command
#                            [MACROARG args1 arg2 ...]
#                            [ROOTEXE_OPTS opt1 opt2 ...]
#                            [INPUT infile]
#                            [ENABLE_IF root-feature]
#                            [DISABLE_IF root-feature]
#                            [WILLFAIL]
#                            [OUTREF stdout_reference]
#                            [ERRREF stderr_reference]
#                            [WORKING_DIR dir]
#                            [TIMEOUT tmout]
#                            [RESOURCE_LOCK lock]
#                            [FIXTURES_SETUP ...] [FIXTURES_CLEANUP ...] [FIXTURES_REQUIRED ...]
#                            [COPY_TO_BUILDDIR file1 file2 ...])
#                            [ENVIRONMENT ENV_VAR1=value1;ENV_VAR2=value2; ...]
#                            [PROPERTIES prop1 value1 prop2 value2...]
#                           )
#
# This function defines a roottest test. It adds a number of additional
# options on top of the ROOT defined ROOT_ADD_TEST.
#
#-------------------------------------------------------------------------------
function(ROOTTEST_ADD_TEST testname)
  CMAKE_PARSE_ARGUMENTS(ARG "WILLFAIL;RUN_SERIAL;STOREOUT"
                            "OUTREF;ERRREF;OUTREF_CINTSPECIFIC;OUTCNV;PASSRC;MACROARG;WORKING_DIR;INPUT;ENABLE_IF;DISABLE_IF;TIMEOUT;RESOURCE_LOCK"
                            "TESTOWNER;COPY_TO_BUILDDIR;MACRO;ROOTEXE_OPTS;EXEC;COMMAND;PRECMD;POSTCMD;OUTCNVCMD;FAILREGEX;PASSREGEX;DEPENDS;OPTS;LABELS;ENVIRONMENT;FIXTURES_SETUP;FIXTURES_CLEANUP;FIXTURES_REQUIRED;PROPERTIES;PYTHON_DEPS"
                            ${ARGN})

  # Test name
  ROOTTEST_TARGETNAME_FROM_FILE(testprefix .)
  if(testname MATCHES "^roottest-")
    set(fulltestname ${testname})
  else()
    set(fulltestname ${testprefix}-${testname})
  endif()

  if (ARG_ENABLE_IF OR ARG_DISABLE_IF)
    # Turn the output into a cmake list which is easier to work with.
    set(ROOT_ENABLED_FEATURES ${_root_enabled_options})
    set(ROOT_ALL_FEATURES ${_root_all_options})
    if ("${ARG_ENABLE_IF}" STREQUAL "" AND "${ARG_DISABLE_IF}" STREQUAL "")
      message(FATAL_ERROR "ENABLE_IF/DISABLE_IF switch requires a feature.")
    endif()
    if(ARG_ENABLE_IF)
      if(NOT "${ARG_ENABLE_IF}" IN_LIST ROOT_ENABLED_FEATURES)
        list(APPEND CTEST_CUSTOM_TESTS_IGNORE ${fulltestname})
        return()
      endif()
      if(NOT "${ARG_ENABLE_IF}" IN_LIST ROOT_ALL_FEATURES)
        message(FATAL_ERROR "Specified feature ${ARG_ENABLE_IF} not found.")
      endif()
    elseif(ARG_DISABLE_IF)
      if("${ARG_DISABLE_IF}" IN_LIST ROOT_ENABLED_FEATURES)
        list(APPEND CTEST_CUSTOM_TESTS_IGNORE ${fulltestname})
        return()
      endif()
      if(NOT "${ARG_DISABLE_IF}" IN_LIST ROOT_ALL_FEATURES)
        message(FATAL_ERROR "Specified feature ${ARG_DISABLE_IF} not found.")
      endif()
    endif()
  endif()

  # Setup macro test.
  if(ARG_MACRO)
   ROOTTEST_SETUP_MACROTEST()
  endif()

  # Setup executable test.
  if(ARG_EXEC)
    ROOTTEST_SETUP_EXECTEST()
  endif()

  if(ARG_COMMAND)
    set(command ${ARG_COMMAND})
    if(ARG_OUTREF)
      set(checkstdout CHECKOUT)
      set(checkstderr CHECKERR)
    endif()
  endif()
  if(ARG_STOREOUT)
    set(checkstdout CHECKOUT)
    set(checkstderr CHECKERR)
  endif()

  # Reference output given?
  if(ARG_OUTREF_CINTSPECIFIC)
    set(ARG_OUTREF ${ARG_OUTREF_CINTSPECIFIC})
  endif()

  if(ARG_OUTREF)
    get_filename_component(OUTREF_PATH ${ARG_OUTREF} ABSOLUTE)

    if(DEFINED 64BIT)
      set(ROOTBITS 64)
    elseif(DEFINED 32BIT)
      set(ROOTBITS 32)
    else()
      set(ROOTBITS "")
    endif()

    if(ARG_OUTREF_CINTSPECIFIC)
      if(EXISTS ${OUTREF_PATH}${ROOTBITS}-${CINT_VERSION})
        set(OUTREF_PATH ${OUTREF_PATH}${ROOTBITS}-${CINT_VERSION})
      elseif(EXISTS ${OUTREF_PATH}-${CINT_VERSION})
        set(OUTREF_PATH ${OUTREF_PATH}-${CINT_VERSION})
      elseif(EXISTS ${OUTREF_PATH}${ROOTBITS})
        set(OUTREF_PATH ${OUTREF_PATH}${ROOTBITS})
      endif()
    else()
      if(EXISTS ${OUTREF_PATH}${ROOTBITS})
        set(OUTREF_PATH ${OUTREF_PATH}${ROOTBITS})
      endif()
    endif()
    set(outref OUTREF ${OUTREF_PATH})
  endif()

  if(ARG_ERRREF)
    get_filename_component(ERRREF_PATH ${ARG_ERRREF} ABSOLUTE)
    set(errref ERRREF ${ERRREF_PATH})
  endif()

  # Get the real path to the output conversion script.
  if(ARG_OUTCNV)
    get_filename_component(OUTCNV ${ARG_OUTCNV} ABSOLUTE)
    set(outcnv OUTCNV ${OUTCNV})
  endif()

  # Setup the output conversion command.
  if(ARG_OUTCNVCMD)
    set(outcnvcmd OUTCNVCMD ${ARG_OUTCNVCMD})
  endif()

  # Mark the test as known to fail.
  if(ARG_WILLFAIL)
    set(willfail WILLFAIL)
  endif()

  # List of python packages required to run this test.
  if(ARG_PYTHON_DEPS)
    set(pythondeps ${ARG_PYTHON_DEPS})
  endif()

  # Add ownership and test labels.
  get_property(testowner DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
                         PROPERTY ROOTTEST_TEST_OWNER)

  if(ARG_TESTOWNER)
    set(testowner ${ARG_TESTOWNER})
  endif()

  if(ARG_LABELS)
    set(labels LABELS ${ARG_LABELS})
    if(testowner)
      set(labels ${labels} ${testowner})
    endif()
  else()
    if(testowner)
      set(labels LABELS ${testowner})
    endif()
  endif()

  # Test will pass for a custom return value.
  if(ARG_PASSRC)
    set(passrc PASSRC ${ARG_PASSRC})
  endif()

  # Pass options to the command.
  if(ARG_OPTS)
    set(command ${command} ${ARG_OPTS})
  endif()

  # Execute a custom command before executing the test.
  if(ARG_PRECMD)
    set(precmd PRECMD ${ARG_PRECMD})
  endif()

  # Copy files into the build directory first.
  if(ARG_COPY_TO_BUILDDIR)
    foreach(copyfile ${ARG_COPY_TO_BUILDDIR})
      get_filename_component(absfilep ${copyfile} ABSOLUTE)
      set(copy_files ${copy_files} ${absfilep})
    endforeach()
    set(copy_to_builddir COPY_TO_BUILDDIR ${copy_files})
  endif()

  # Execute a custom command after executing the test.
  if(ARG_POSTCMD)
    set(postcmd POSTCMD ${ARG_POSTCMD})
  endif()

  if(MSVC)
    if(ARG_MACRO)
      if(ARG_MACRO MATCHES "[.]C\\+" OR ARG_MACRO MATCHES "[.]cxx\\+")
        string(REPLACE "+" "" macro_name "${ARG_MACRO}")
        get_filename_component(fpath ${macro_name} REALPATH)
        get_filename_component(fext ${fpath} EXT)
        string(REPLACE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} fpath ${fpath})
        string(REPLACE ${fext} "" fpath ${fpath})
        string(REPLACE "." "" fext ${fext})
        cmake_path(CONVERT "${fpath}" TO_NATIVE_PATH_LIST fpath)
        set(postcmd POSTCMD cmd /c if exist ${fpath}_${fext}.rootmap del ${fpath}_${fext}.rootmap)
      endif()
    endif()
  endif()

  # Add dependencies. If the test depends on a macro file, the macro
  # will be compiled and the dependencies are set accordingly.
  if(ARG_DEPENDS)
    foreach(dep ${ARG_DEPENDS})
      if(${dep} MATCHES "[.]C" OR ${dep} MATCHES "[.]cxx" OR ${dep} MATCHES "[.]h")
        ROOTTEST_COMPILE_MACRO(${dep} FIXTURES_SETUP compile-macro-${dep}-fixture)
        list(APPEND deplist ${COMPILE_MACRO_TEST})
      elseif(NOT ${dep} MATCHES "^roottest-")
        list(APPEND deplist ${testprefix}-${dep})
      else()
        list(APPEND deplist ${dep})
      endif()
    endforeach()
    # Now extract all fixtures from the dependencies, so the fixture-providing
    # tests get started even if using --rerun-failed, or -R <testName> or similar
    foreach(dep ${deplist})
      if(TEST ${dep})
        get_test_property(${dep} FIXTURES_SETUP test_fixtures)
        if(NOT test_fixtures STREQUAL "NOTFOUND")
          list(APPEND fixtures_from_depends_argument ${test_fixtures})
        endif()
      endif()
    endforeach()
  endif(ARG_DEPENDS)

  if(ARG_FAILREGEX)
    set(failregex FAILREGEX ${ARG_FAILREGEX})
  endif()

  if(ARG_PASSREGEX)
    set(passregex PASSREGEX ${ARG_PASSREGEX})
  endif()

  if(ARG_RUN_SERIAL)
    set(run_serial RUN_SERIAL ${ARG_RUN_SERIAL})
  endif()

  set(pythonpaths ${localruntimedir} $ENV{PYTHONPATH})
  cmake_path(CONVERT "${pythonpaths}" TO_NATIVE_PATH_LIST pythonpaths_native)

  set(environment ENVIRONMENT
                  ${ROOTTEST_ENV_EXTRA}
                  ${ARG_ENVIRONMENT}
                  PYTHONPATH=${pythonpaths_native})

  if(ARG_WORKING_DIR)
    get_filename_component(test_working_dir ${ARG_WORKING_DIR} ABSOLUTE)
  else()
    get_filename_component(test_working_dir ${CMAKE_CURRENT_BINARY_DIR} ABSOLUTE)
  endif()

  get_filename_component(logfile "${CMAKE_CURRENT_BINARY_DIR}/${testname}.log" ABSOLUTE)
  if(ARG_ERRREF)
    get_filename_component(errfile "${CMAKE_CURRENT_BINARY_DIR}/${testname}.err" ABSOLUTE)
    set(errfile ERROR ${errfile})
  endif()

  if(ARG_INPUT)
    get_filename_component(infile_path ${ARG_INPUT} ABSOLUTE)
    set(infile INPUT ${infile_path})
  endif()

  if(ARG_TIMEOUT)
    set(timeout ${ARG_TIMEOUT})
  else()
    if("${ARG_LABELS}" MATCHES "longtest")
      set(timeout 1800)
    else()
      set(timeout 300)
    endif()
  endif()

  if(TIMEOUT_BINARY AND NOT MSVC)
    # It takes up to 30seconds to get the back trace!
    # And we want the backtrace before CTest sends kill -9.
    math(EXPR timeoutTimeout "${timeout}-30")
    set(command "${TIMEOUT_BINARY}^-s^USR2^${timeoutTimeout}s^${command}")
  endif()

  if (ARG_FIXTURES_SETUP)
    set(fixtures_setup ${ARG_FIXTURES_SETUP})
  endif()

  if (ARG_FIXTURES_CLEANUP)
    set(fixtures_cleanup ${ARG_FIXTURES_CLEANUP})
  endif()

  if (ARG_FIXTURES_REQUIRED)
    set(fixtures_required ${ARG_FIXTURES_REQUIRED})
  endif()
  list(APPEND fixtures_required ${fixtures_from_depends_argument})

  if (ARG_RESOURCE_LOCK)
    set(resource_lock ${ARG_RESOURCE_LOCK})
  endif()

  if (ARG_PROPERTIES)
    set(properties ${ARG_PROPERTIES})
  endif()

  ROOT_ADD_TEST(${fulltestname} COMMAND ${command}
                        OUTPUT ${logfile}
                        ${infile}
                        ${errfile}
                        ${outcnv}
                        ${outcnvcmd}
                        ${outref}
                        ${errref}
                        WORKING_DIR ${test_working_dir}
                        DIFFCMD ${Python3_EXECUTABLE} ${ROOT_SOURCE_DIR}/roottest/scripts/custom_diff.py
                        TIMEOUT ${timeout}
                        ${environment}
                        ${build}
                        ${checkstdout}
                        ${checkstderr}
                        ${willfail}
                        ${compile_macros}
                        ${labels}
                        ${passrc}
                        ${precmd}
                        ${postcmd}
                        ${run_serial}
                        ${failregex}
                        ${passregex}
                        ${copy_to_builddir}
                        PYTHON_DEPS ${pythondeps}
                        DEPENDS ${deplist}
                        FIXTURES_SETUP ${fixtures_setup}
                        FIXTURES_CLEANUP ${fixtures_cleanup}
                        FIXTURES_REQUIRED ${fixtures_required}
                        RESOURCE_LOCK ${resource_lock}
                        PROPERTIES ${properties})

  if(MSVC)
    if (ARG_OUTCNV OR ARG_OUTCNVCMD)
      set_property(TEST ${fulltestname} PROPERTY DISABLED true)
    endif()
    if(ARG_COMMAND)
      string(FIND "${ARG_COMMAND}" ".sh" APOS)
      if( NOT ("${APOS}" STREQUAL "-1") )
        set_property(TEST ${fulltestname} PROPERTY DISABLED true)
      endif()
      string(FIND "${ARG_COMMAND}" "grep " APOS)
      if( NOT ("${APOS}" STREQUAL "-1") )
        set_property(TEST ${fulltestname} PROPERTY DISABLED true)
      endif()
      string(FIND "${ARG_COMMAND}" "make " APOS)
      if( NOT ("${APOS}" STREQUAL "-1") )
        set_property(TEST ${fulltestname} PROPERTY DISABLED true)
      endif()
    endif()
    if(ARG_PRECMD)
      string(FIND "${ARG_PRECMD}" "sh " APOS)
      if( NOT ("${APOS}" STREQUAL "-1") )
        set_property(TEST ${fulltestname} PROPERTY DISABLED true)
      endif()
      string(FIND "${ARG_PRECMD}" ".sh" APOS)
      if( NOT ("${APOS}" STREQUAL "-1") )
        set_property(TEST ${fulltestname} PROPERTY DISABLED true)
      endif()
    endif()
  endif()

endfunction(ROOTTEST_ADD_TEST)

#-------------------------------------------------------------------------------
#
# function ROOTTEST_ADD_UNITTEST_DIR(libraries...)
#
# This function defines a roottest unit test using Google Test.
# All files in this directory will end up in a unit test binary and run as a
# single test.
#
#-------------------------------------------------------------------------------

function(ROOTTEST_ADD_UNITTEST_DIR)
  CMAKE_PARSE_ARGUMENTS(ARG
    "WILLFAIL"
    ""
    "COPY_TO_BUILDDIR;DEPENDS;OPTS;LABELS;ENVIRONMENT"
    ${ARGN})

  # Test name
  ROOTTEST_TARGETNAME_FROM_FILE(testprefix .)
  set(fulltestname ${testprefix}_unittests)
  set(binary ${testprefix}_exe)
  file(GLOB unittests_SRC
    "*.h"
    "*.hh"
    "*.hpp"
    "*.hxx"
    "*.cpp"
    "*.cxx"
    "*.cc"
    "*.C"
    )

  if(MSVC AND NOT CMAKE_GENERATOR MATCHES Ninja)
    foreach(library ${ARG_UNPARSED_ARGUMENTS})
      if(${library} MATCHES "[::]")
        set(libraries ${libraries} ${library})
      else()
        set(libraries ${libraries} lib${library})
      endif()
    endforeach()
  else()
    set (libraries ${ARG_UNPARSED_ARGUMENTS})
  endif()

  add_executable(${binary} ${unittests_SRC})
  target_link_libraries(${binary} PRIVATE GTest::gtest GTest::gtest_main ${libraries})
  set_property(TARGET ${binary} PROPERTY BUILD_WITH_INSTALL_RPATH OFF) # will never be installed anyway

  if(MSVC AND DEFINED ROOT_SOURCE_DIR)
    if(TARGET ROOTStaticSanitizerConfig)
      target_link_libraries(${binary} ROOTStaticSanitizerConfig)
    endif()
  else()
    if(TARGET ROOT::ROOTStaticSanitizerConfig)
      target_link_libraries(${binary} PRIVATE ROOT::ROOTStaticSanitizerConfig)
    endif()
  endif()

  # Mark the test as known to fail.
  if(ARG_WILLFAIL)
    set(willfail WILLFAIL)
  endif()

  if(ARG_LABELS)
    set(labels LABELS ${ARG_LABELS})
    if(testowner)
      set(labels ${labels} ${testowner})
    endif()
  else()
    if(testowner)
      set(labels LABELS ${testowner})
    endif()
  endif()

  # Add ownership and test labels.
  get_property(testowner DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
                         PROPERTY ROOTTEST_TEST_OWNER)

  if(ARG_TESTOWNER)
    set(testowner ${ARG_TESTOWNER})
  endif()

  if(ARG_LABELS)
    set(labels LABELS ${ARG_LABELS})
    if(testowner)
      set(labels ${labels} ${testowner})
    endif()
  else()
    if(testowner)
      set(labels LABELS ${testowner})
    endif()
  endif()

  # Copy files into the build directory first.
  if(ARG_COPY_TO_BUILDDIR)
    foreach(copyfile ${ARG_COPY_TO_BUILDDIR})
      get_filename_component(absfilep ${copyfile} ABSOLUTE)
      set(copy_files ${copy_files} ${absfilep})
    endforeach()
    set(copy_to_builddir COPY_TO_BUILDDIR ${copy_files})
  endif()

  # Add dependencies. If the test depends on a macro file, the macro
  # will be compiled and the dependencies are set accordingly.
  if(ARG_DEPENDS)
    foreach(dep ${ARG_DEPENDS})
      if(${dep} MATCHES "[.]C" OR ${dep} MATCHES "[.]cxx" OR ${dep} MATCHES "[.]h")
        ROOTTEST_COMPILE_MACRO(${dep})
        list(APPEND deplist ${COMPILE_MACRO_TEST})
      elseif(NOT ${dep} MATCHES "^roottest-")
        list(APPEND deplist ${testprefix}-${dep})
      else()
        list(APPEND deplist ${dep})
      endif()
    endforeach()
  endif(ARG_DEPENDS)

  set(pythonpaths ${localruntimedir} $ENV{PYTHONPATH})
  cmake_path(CONVERT "${pythonpaths}" TO_NATIVE_PATH_LIST pythonpaths_native)

  set(environment ENVIRONMENT
                  ${ROOTTEST_ENV_EXTRA}
                  ${ARG_ENVIRONMENT}
                  PYTHONPATH=${pythonpaths_native})

  ROOT_ADD_TEST(${fulltestname} COMMAND ${binary}
    ${environment}
    ${willfail}
    ${labels}
    ${copy_to_builddir}
    TIMEOUT 600
    DEPENDS ${deplist}
    )
endfunction(ROOTTEST_ADD_UNITTEST_DIR)

#----------------------------------------------------------------------------
# find_python_module(module [REQUIRED] [QUIET])
#----------------------------------------------------------------------------
function(find_python_module module)
   CMAKE_PARSE_ARGUMENTS(ARG "REQUIRED;QUIET" "" "" ${ARGN})
   string(TOUPPER ${module} module_upper)
   if(NOT PY_${module_upper})
      if(ARG_REQUIRED)
         set(py_${module}_FIND_REQUIRED TRUE)
      endif()
      if(ARG_QUIET)
         set(py_${module}_FIND_QUIETLY TRUE)
      endif()
      # A module's location is usually a directory, but for binary modules
      # it's a .so file.
      execute_process(COMMAND "${Python3_EXECUTABLE}" "-c"
         "import re, ${module}; print(re.compile('/__init__.py.*').sub('',${module}.__file__))"
         RESULT_VARIABLE _${module}_status
         OUTPUT_VARIABLE _${module}_location
         ERROR_VARIABLE _${module}_error
         OUTPUT_STRIP_TRAILING_WHITESPACE
         ERROR_STRIP_TRAILING_WHITESPACE)
      if(NOT _${module}_status)
         set(PY_${module_upper} ${_${module}_location} CACHE STRING "Location of Python module ${module}")
         mark_as_advanced(PY_${module_upper})
      else()
         if(NOT ARG_QUIET)
            message(STATUS "Failed to find Python module ${module}: ${_${module}_error}")
          endif()
      endif()
   endif()
   find_package_handle_standard_args(py_${module} DEFAULT_MSG PY_${module_upper})
   set(PY_${module_upper}_FOUND ${PY_${module_upper}_FOUND} PARENT_SCOPE)
endfunction()

#---------------------------------------------------------------------------------------------------
# function ROOT_SET_OUTPUT_DIRECTORIES( <name> )
#
# this function simply sets the output directories from the standard outupt directory
# (CMAKE_CURRENT_BINARY_DIR/$<CONFIG>) to its parent directory (CMAKE_CURRENT_BINARY_DIR) on Windows
#
#---------------------------------------------------------------------------------------------------
function(ROOT_SET_OUTPUT_DIRECTORIES library)
   if(MSVC AND NOT CMAKE_GENERATOR MATCHES Ninja)
      foreach(OUTPUTCONFIG ${CMAKE_CONFIGURATION_TYPES})
        string(TOUPPER ${OUTPUTCONFIG} OUTPUTCONFIG)
        set_target_properties(${library} PROPERTIES RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG} ${CMAKE_CURRENT_BINARY_DIR})
        set_target_properties(${library} PROPERTIES LIBRARY_OUTPUT_DIRECTORY_${OUTPUTCONFIG} ${CMAKE_CURRENT_BINARY_DIR})
        set_target_properties(${library} PROPERTIES ARCHIVE_OUTPUT_DIRECTORY_${OUTPUTCONFIG} ${CMAKE_CURRENT_BINARY_DIR})
      endforeach(OUTPUTCONFIG CMAKE_CONFIGURATION_TYPES)
   endif()
endfunction()

#---------------------------------------------------------------------------------------------------
# function ROOTTEST_LINKER_LIBRARY( <name> source1 source2 ...[TYPE STATIC|SHARED] [DLLEXPORT]
#                                   [NOINSTALL] LIBRARIES library1 library2 ...
#                                   DEPENDENCIES dep1 dep2
#                                   BUILTINS dep1 dep2)
#
# this function simply calls the ROOT function ROOT_LINKER_LIBRARY, and sets the output directories
# from the standard output directory (CMAKE_CURRENT_BINARY_DIR/$<CONFIG>) to its parent directory
# (CMAKE_CURRENT_BINARY_DIR) on Windows
#
#---------------------------------------------------------------------------------------------------
function(ROOTTEST_LINKER_LIBRARY library)
   ROOT_LINKER_LIBRARY(${ARGV})
   ROOT_SET_OUTPUT_DIRECTORIES(${library})
endfunction()

#---------------------------------------------------------------------------------------------------
# ROOT_GET_CLANG_LIBRARIES( clang_libraries )
#
# this function is used to collect the required libraries when building ROOT with external
# LLVM & Clang, like in Conda for example.
#---------------------------------------------------------------------------------------------------
function (ROOT_GET_CLANG_LIBRARIES clang_libraries)
  set(found_libraries "")
  FILE(GLOB clangLibs ${LLVM_LIBRARY_DIR}/clang*.lib)
  foreach(lib_path IN LISTS clangLibs)
    get_filename_component(lib_name ${lib_path} NAME)
    if (NOT ${lib_name} IN_LIST found_libraries)
      list(APPEND found_libraries ${lib_name})
    endif()
  endforeach(lib_path)
  foreach(extra_lib "LLVMFrontendDriver.lib" "LLVMFrontendHLSL.lib" "Version.lib")
    if (NOT ${extra_lib} IN_LIST found_libraries)
      list(APPEND found_libraries ${extra_lib})
    endif()
  endforeach(extra_lib)
  SET(${clang_libraries} "${found_libraries}" PARENT_SCOPE)
endfunction(ROOT_GET_CLANG_LIBRARIES)
