WiscSort / EMS / header / ips4o / extern / cmake-modules / TargetTools.cmake
TargetTools.cmake
Raw
# ============================================================================
# Copyright (c) 2011-2012 University of Pennsylvania
# Copyright (c) 2013-2014 Andreas Schuh
# All rights reserved.
#
# See COPYING file for license information or visit
# http://opensource.andreasschuh.com/cmake-basis/download.html#license
# ============================================================================

##############################################################################
# @file  TargetTools.cmake
# @brief Functions and macros to add executable and library targets.
#
# @ingroup CMakeTools
##############################################################################

if (__BASIS_TARGETTOOLS_INCLUDED)
  return ()
else ()
  set (__BASIS_TARGETTOOLS_INCLUDED TRUE)
endif ()


## @addtogroup CMakeUtilities
#  @{


# ============================================================================
# properties
# ============================================================================

# ----------------------------------------------------------------------------
## @brief Set properties on a target.
#
# This function replaces CMake's
# <a href="http://www.cmake.org/cmake/help/cmake-2-8-docs.html#command:set_target_properties">
# set_target_properties()</a> command and extends its functionality.
# In particular, it maps the given target names to the corresponding target UIDs.
#
# @note If @c BASIS_USE_TARGET_UIDS is @c OFF and is not required by a project,
#       it is recommended to use set_target_properties() instead (note that
#       set_target_properties is overriden by the ImportTools.cmake module of BASIS).
#       This will break the build configuration scripts when @c BASIS_USE_TARGET_UIDS
#       is set to @c ON later. It should thus only be used if the project will
#       never use the target UID feature of BASIS. A project can possibly define
#       a global macro which either calls set_target_properties or
#       basis_set_target_properties. But be aware of the related CMake bugs
#       which prevent basis_set_target_properties to do the same already.
#       ARGV/ARGN do not preserve empty arguments nor list arguments!
#
# @note Due to a bug in CMake (http://www.cmake.org/Bug/view.php?id=12303),
#       except of the first property given directly after the @c PROPERTIES keyword,
#       only properties listed in @c BASIS_PROPERTIES_ON_TARGETS can be set.
#
# @param [in] ARGN List of arguments. See
#                  <a href="http://www.cmake.org/cmake/help/cmake-2-8-docs.html#command:set_target_properties">
#                  set_target_properties()</a>.
#
# @returns Sets the specified properties on the given target.
#
# @sa http://www.cmake.org/cmake/help/cmake-2-8-docs.html#command:set_target_properties
#
# @ingroup CMakeAPI
function (basis_set_target_properties)
  # convert target names to UIDs
  set (TARGET_UIDS)
  list (LENGTH ARGN N)
  if (N EQUAL 0)
    message (FATAL_ERROR "basis_set_target_properties(): Missing arguments!")
  endif ()
  list (GET ARGN 0 ARG)
  while (NOT ARG MATCHES "^PROPERTIES$")
    basis_get_target_uid (TARGET_UID "${ARG}")
    list (APPEND TARGET_UIDS "${TARGET_UID}")
    list (REMOVE_AT ARGN 0)
    list (LENGTH ARGN N)
    if (N EQUAL 0)
      break ()
    else ()
      list (GET ARGN 0 ARG)
    endif ()
  endwhile ()
  if (NOT ARG MATCHES "^PROPERTIES$")
    message (FATAL_ERROR "Missing PROPERTIES argument!")
  elseif (NOT TARGET_UIDS)
    message (FATAL_ERROR "No target specified!")
  endif ()
  # remove PROPERTIES keyword
  list (REMOVE_AT ARGN 0)
  math (EXPR N "${N} - 1")
  # set targets properties
  #
  # Note: By iterating over the properties, the empty property values
  #       are correctly passed on to CMake's set_target_properties()
  #       command, while
  #       _set_target_properties(${TARGET_UIDS} PROPERTIES ${ARGN})
  #       (erroneously) discards the empty elements in ARGN.
  if (BASIS_DEBUG)
    message ("** basis_set_target_properties:")
    message ("**   Target(s):  ${TARGET_UIDS}")
    message ("**   Properties: [${ARGN}]")
  endif ()
  while (N GREATER 1)
    list (GET ARGN 0 PROPERTY)
    list (GET ARGN 1 VALUE)
    list (REMOVE_AT ARGN 0 1)
    list (LENGTH ARGN N)
    # The following loop is only required b/c CMake's ARGV and ARGN
    # lists do not support arguments which are themselves lists.
    # Therefore, we need a way to decide when the list of values for a
    # property is terminated. Hence, we only allow known properties
    # to be set, except for the first property where the name follows
    # directly after the PROPERTIES keyword.
    while (N GREATER 0)
      list (GET ARGN 0 ARG)
      if (ARG MATCHES "${BASIS_PROPERTIES_ON_TARGETS_RE}")
        break ()
      endif ()
      list (APPEND VALUE "${ARG}")
      list (REMOVE_AT ARGN 0)
      list (LENGTH ARGN N)
    endwhile ()
    if (BASIS_DEBUG)
      message ("**   -> ${PROPERTY} = [${VALUE}]")
    endif ()
    # check property name
    if (PROPERTY MATCHES "^$") # remember: STREQUAL is buggy and evil!
      message (FATAL_ERROR "Empty property name given!")
    endif ()
    # set target property
    if (COMMAND _set_target_properties) # i.e. ImportTools.cmake included
      _set_target_properties (${TARGET_UIDS} PROPERTIES ${PROPERTY} "${VALUE}")
    else ()
      set_target_properties (${TARGET_UIDS} PROPERTIES ${PROPERTY} "${VALUE}")
    endif ()
  endwhile ()
  # make sure that every property had a corresponding value
  if (NOT N EQUAL 0)
    message (FATAL_ERROR "No value given for target property ${ARGN}")
  endif ()
endfunction ()

# ----------------------------------------------------------------------------
## @brief Get value of property set on target.
#
# This function replaces CMake's
# <a href="http://www.cmake.org/cmake/help/cmake-2-8-docs.html#command:set_target_properties">
# get_target_properties()</a> command and extends its functionality.
# In particular, it maps the given @p TARGET_NAME to the corresponding target UID.
#
# @param [out] VAR         Name of output variable.
# @param [in]  TARGET_NAME Name of build target.
# @param [in]  ARGN        Remaining arguments for
#                          <a href="http://www.cmake.org/cmake/help/cmake-2-8-docs.html#command:get_target_properties">
#                          get_target_properties()</a>.
#
# @returns Sets @p VAR to the value of the requested property.
#
# @sa http://www.cmake.org/cmake/help/cmake-2-8-docs.html#command:get_target_property
#
# @ingroup CMakeAPI
function (basis_get_target_property VAR TARGET_NAME)
  basis_get_target_uid (TARGET_UID "${TARGET_NAME}")
  get_target_property (VALUE "${TARGET_UID}" ${ARGN})
  set (${VAR} "${VALUE}" PARENT_SCOPE)
endfunction ()

# ============================================================================
# definitions
# ============================================================================

# ----------------------------------------------------------------------------
## @brief Add compile definitions.
#
# This function replaces CMake's
# <a href="http://www.cmake.org/cmake/help/cmake-2-8-docs.html#command:add_definitions">
# add_definitions()</a> command.
#
# @param [in] ARGN List of arguments for
#                  <a href="http://www.cmake.org/cmake/help/cmake-2-8-docs.html#command:add_definitions">
#                  add_definitions()</a>.
#
# @returns Adds the given definitions.
#
# @sa http://www.cmake.org/cmake/help/cmake-2-8-docs.html#command:add_definitions
#
# @ingroup CMakeAPI
function (basis_add_definitions)
  add_definitions (${ARGN})
endfunction ()

# ----------------------------------------------------------------------------
## @brief Remove previously added compile definitions.
#
# This function replaces CMake's
# <a href="http://www.cmake.org/cmake/help/cmake-2-8-docs.html#command:remove_definitions">
# remove_definitions()</a> command.
#
# @param [in] ARGN List of arguments for
#                  <a href="http://www.cmake.org/cmake/help/cmake-2-8-docs.html#command:remove_definitions">
#                  remove_definitions()</a>.
#
# @returns Removes the specified definitions.
#
# @sa http://www.cmake.org/cmake/help/cmake-2-8-docs.html#command:remove_definition
#
# @ingroup CMakeAPI
function (basis_remove_definitions)
  remove_definitions (${ARGN})
endfunction ()

# ============================================================================
# directories
# ============================================================================

# ----------------------------------------------------------------------------
## @brief Add directories to search path for include files.
#
# Overwrites CMake's
# <a href="http://www.cmake.org/cmake/help/cmake-2-8-docs.html#command:include_directories">
# include_directories()</a> command. This is required because the
# basis_include_directories() function is not used by other projects in their
# package use files. Therefore, this macro is an alias for
# basis_include_directories().
#
# @param [in] ARGN List of arguments for basis_include_directories().
#
# @returns Adds the given paths to the search path for include files.
#
# @sa http://www.cmake.org/cmake/help/cmake-2-8-docs.html#command:include_directories
macro (include_directories)
  basis_include_directories (${ARGN})
endmacro ()

# ----------------------------------------------------------------------------
## @brief Add directories to search path for include files.
#
# This function replaces CMake's
# <a href="http://www.cmake.org/cmake/help/cmake-2-8-docs.html#command:include_directories">
# include_directories()</a> command. Besides invoking CMake's internal command
# with the given arguments, it updates the @c PROJECT_INCLUDE_DIRECTORIES
# property on the current project (see basis_set_project_property()). This list
# contains a list of all include directories used by a project, regardless of
# the directory in which the basis_include_directories() function was used.
#
# @param ARGN List of arguments for
#             <a href="http://www.cmake.org/cmake/help/cmake-2-8-docs.html#command:include_directories">
#             include_directories()</a> command.
#
# @returns Nothing.
#
# @sa http://www.cmake.org/cmake/help/cmake-2-8-docs.html#command:include_directories
#
# @ingroup CMakeAPI
function (basis_include_directories)
  # CMake's include_directories ()
  _include_directories (${ARGN})

  # parse arguments
  CMAKE_PARSE_ARGUMENTS (ARGN "AFTER;BEFORE;SYSTEM" "" "" ${ARGN})

  # make relative paths absolute
  set (DIRS)
  foreach (P IN LISTS ARGN_UNPARSED_ARGUMENTS)
    if (NOT P MATCHES "^\\$<") # preserve generator expressions
      get_filename_component (P "${P}" ABSOLUTE)
    endif ()
    list (APPEND DIRS "${P}")
  endforeach ()

  if (DIRS)
    # append directories to "global" list of include directories
    basis_get_project_property (INCLUDE_DIRS PROPERTY PROJECT_INCLUDE_DIRS)
    if (BEFORE)
      list (INSERT INCLUDE_DIRS 0 ${DIRS})
    else ()
      list (APPEND INCLUDE_DIRS ${DIRS})
    endif ()
    if (INCLUDE_DIRS)
      list (REMOVE_DUPLICATES INCLUDE_DIRS)
    endif ()
    if (BASIS_DEBUG)
      message ("** basis_include_directories():")
      if (BEFORE)
        message ("**    Add before:  ${DIRS}")
      else ()
        message ("**    Add after:   ${DIRS}")
      endif ()
      if (BASIS_VERBOSE)
        message ("**    Directories: ${INCLUDE_DIRS}")
      endif ()
    endif ()
    basis_set_project_property (PROPERTY PROJECT_INCLUDE_DIRS ${INCLUDE_DIRS})
  endif ()
endfunction ()

# ----------------------------------------------------------------------------
## @brief Add directories to search path for libraries.
#
# Overwrites CMake's
# <a href="http://www.cmake.org/cmake/help/cmake-2-8-docs.html#command:link_directories">
# link_directories()</a> command. This is required because the
# basis_link_directories() function is not used by other projects in their
# package use files. Therefore, this macro is an alias for
# basis_link_directories().
#
# @param [in] ARGN List of arguments for basis_link_directories().
#
# @returns Adds the given paths to the search path for libraries.
#
# @sa http://www.cmake.org/cmake/help/cmake-2-8-docs.html#command:link_directories
macro (link_directories)
  basis_link_directories (${ARGN})
endmacro ()

# ----------------------------------------------------------------------------
## @brief Add directories to search path for libraries.
#
# This function replaces CMake's
# <a href="http://www.cmake.org/cmake/help/cmake-2-8-docs.html#command:link_directories">
# link_directories()</a> command. Even though this function yet only invokes
# CMake's internal command, it should be used in BASIS projects to enable the
# extension of this command's functionality as part of BASIS if required.
#
# @param [in] ARGN List of arguments for
#                  <a href="http://www.cmake.org/cmake/help/cmake-2-8-docs.html#command:link_directories">
#                  link_directories()</a>.
#
# @returns Adds the given paths to the search path for libraries.
#
# @sa http://www.cmake.org/cmake/help/cmake-2-8-docs.html#command:link_directories
#
# @ingroup CMakeAPI
function (basis_link_directories)
  # CMake's link_directories() command
  _link_directories (${ARGN})
  # make relative paths absolute
  set (DIRS)
  foreach (P IN LISTS ARGN)
    if (NOT P MATCHES "^\\$<") # preserve generator expressions
      get_filename_component (P "${P}" ABSOLUTE)
    endif ()
    list (APPEND DIRS "${P}")
  endforeach ()
  if (DIRS)
    # append directories to "global" list of link directories
    basis_get_project_property (LINK_DIRS PROPERTY PROJECT_LINK_DIRS)
    list (APPEND LINK_DIRS ${DIRS})
    if (LINK_DIRS)
      list (REMOVE_DUPLICATES LINK_DIRS)
    endif ()
    if (BASIS_DEBUG)
      message ("** basis_link_directories():")
      message ("**    Add:         [${DIRS}]")
      if (BASIS_VERBOSE)
        message ("**    Directories: [${LINK_DIRS}]")
      endif ()
    endif ()
    basis_set_project_property (PROPERTY PROJECT_LINK_DIRS "${LINK_DIRS}")
    # if the directories are added by an external project's <Pkg>Use.cmake
    # file which is part of the same superbuild as this project, add the
    # directories further to the list of directories that may be added to
    # the RPATH. see basis_set_target_install_rpath().
    if (BUNDLE_PROJECT) # set in basis_use_package()
      basis_get_project_property (LINK_DIRS PROPERTY BUNDLE_LINK_DIRS)
      list (APPEND LINK_DIRS ${DIRS})
      if (LINK_DIRS)
        list (REMOVE_DUPLICATES LINK_DIRS)
      endif ()
      basis_set_project_property (PROPERTY BUNDLE_LINK_DIRS "${LINK_DIRS}")
    endif ()
  endif ()
endfunction ()

# ============================================================================
# dependencies
# ============================================================================

# ----------------------------------------------------------------------------
## @brief Add dependencies to build target.
#
# This function replaces CMake's
# <a href="http://www.cmake.org/cmake/help/cmake-2-8-docs.html#command:add_dependencies">
# add_dependencies()</a> command and extends its functionality.
# In particular, it maps the given target names to the corresponding target UIDs.
#
# @param [in] ARGN Arguments for
#                  <a href="http://www.cmake.org/cmake/help/cmake-2-8-docs.html#command:add_dependencies">
#                  add_dependencies()</a>.
#
# @returns Adds the given dependencies of the specified build target.
#
# @sa http://www.cmake.org/cmake/help/cmake-2-8-docs.html#command:add_dependencies
#
# @ingroup CMakeAPI
if (BASIS_USE_TARGET_UIDS)
  function (basis_add_dependencies)
    set (ARGS)
    foreach (ARG ${ARGN})
      basis_get_target_uid (UID "${ARG}")
      if (TARGET "${UID}")
        list (APPEND ARGS "${UID}")
      else ()
        list (APPEND ARGS "${ARG}")
      endif ()
    endforeach ()
    add_dependencies (${ARGS})
  endfunction ()
else ()
  macro (basis_add_dependencies)
    add_dependencies (${ARGV})
  endmacro ()
endif ()

# ----------------------------------------------------------------------------
## @brief Add link dependencies to build target.
#
# This function replaces CMake's
# <a href="http://www.cmake.org/cmake/help/cmake-2-8-docs.html#command:target_link_libraries">
# target_link_libraries()</a> command.
#
# The main reason for replacing this function is to treat libraries such as
# MEX-files which are supposed to be compiled into a MATLAB executable added
# by basis_add_executable() special. In this case, these libraries are added
# to the LINK_DEPENDS property of the given MATLAB Compiler target. Similarly,
# executable scripts and modules written in a scripting language may depend
# on other modules.
#
# Another reason is the mapping of build target names to fully-qualified
# build target names as used by BASIS (see basis_get_target_uid()).
#
# Only link dependencies added with this function are considered for the setting
# of the INSTALL_RPATH of executable targets (see basis_set_target_install_rpath()).
#
# Example:
# @code
# basis_add_library (MyMEXFunc MEX myfunc.c)
# basis_add_executable (MyMATLABApp main.m)
# basis_target_link_libraries (MyMATLABApp MyMEXFunc OtherMEXFunc.mexa64)
# @endcode
#
# @param [in] TARGET_NAME Name of the target.
# @param [in] ARGN        Link libraries.
#
# @returns Adds link dependencies to the specified build target.
#          For custom targets, the given libraries are added to the
#          @c LINK_DEPENDS property of these targets, in particular.
#
# @sa http://www.cmake.org/cmake/help/cmake-2-8-docs.html#command:target_link_libraries
#
# @ingroup CMakeAPI
function (basis_target_link_libraries TARGET_NAME)
  basis_get_target_uid (TARGET_UID "${TARGET_NAME}")
  if (NOT TARGET "${TARGET_UID}")
    message (FATAL_ERROR "basis_target_link_libraries(): Unknown target ${TARGET_UID}.")
  endif ()
  # get type of named target
  get_target_property (BASIS_TYPE ${TARGET_UID} BASIS_TYPE)
  # substitute non-fully qualified target names
  set (ARGS)
  foreach (ARG ${ARGN})
    if ("^${ARG}$" STREQUAL "^basis$")
      get_target_property (LANGUAGE ${TARGET_UID} LANGUAGE)
      if (NOT LANGUAGE OR "^${LANGUAGE}$" STREQUAL "^UNKNOWN$")
        message (FATAL_ERROR "Target ${TARGET_UID} is of unknown LANGUAGE! Cannot add dependency on \"basis\" utilities.")
      endif ()
      basis_add_utilities_library (BASIS_UTILITIES_TARGET ${LANGUAGE})
      list (APPEND ARGS ${BASIS_UTILITIES_TARGET})
      set_target_properties (${TARGET_UID} PROPERTIES BASIS_UTILITIES TRUE)
    else ()
      basis_get_target_uid (UID "${ARG}")
      if (TARGET "${UID}")
        if ("^${UID}$" STREQUAL "^${TARGET_UID}$")
          message (FATAL_ERROR "Cannot add link library ${UID} as dependency of itself!")
        endif ()
        list (APPEND ARGS "${UID}")
      else ()
        list (APPEND ARGS "${ARG}")
      endif ()
    endif ()
  endforeach ()
  # get current link libraries
  if (BASIS_TYPE MATCHES "^EXECUTABLE$|^(SHARED|STATIC|MODULE)_LIBRARY$")
    get_target_property (DEPENDS ${TARGET_UID} BASIS_LINK_DEPENDS)
  else ()
    get_target_property (DEPENDS ${TARGET_UID} LINK_DEPENDS)
  endif ()
  if (NOT DEPENDS)
    set (DEPENDS)
  endif ()
  # note that MCC does itself a dependency check and in case of scripts
  # the basis_get_target_link_libraries() function is used
  if (BASIS_TYPE MATCHES "MCC|SCRIPT")
    list (APPEND DEPENDS ${ARGS})
  # otherwise
  else ()
    list (APPEND DEPENDS ${ARGS})
    # pull implicit dependencies (e.g., ITK uses this)
    set (DEPENDENCY_ADDED 1)
    while (DEPENDENCY_ADDED)
      set (DEPENDENCY_ADDED 0)
      foreach (LIB IN LISTS DEPENDS)
        foreach (LIB_DEPEND IN LISTS ${LIB}_LIB_DEPENDS)
          if (NOT LIB_DEPEND MATCHES "^$|^general$")
            string (REGEX REPLACE "^-l" "" LIB_DEPEND "${LIB_DEPEND}")
            list (FIND DEPENDS ${LIB_DEPEND} IDX)
            if (IDX EQUAL -1)
              list (APPEND DEPENDS ${LIB_DEPEND})
              set (DEPENDENCY_ADDED 1)
            endif ()
          endif ()
        endforeach ()
      endforeach ()
    endwhile ()
  endif ()
  # update LINK_DEPENDS
  if (BASIS_TYPE MATCHES "^EXECUTABLE$|^(SHARED|STATIC|MODULE)_LIBRARY$")
    set_target_properties (${TARGET_UID} PROPERTIES BASIS_LINK_DEPENDS "${DEPENDS}")
    target_link_libraries (${TARGET_UID} ${ARGS})
  else ()
    # FIXME cannot use LINK_DEPENDS for CMake targets as these depends are
    #       otherwise added as is to the Makefile. therefore, consider renaming
    #       LINK_DEPENDS in general to BASIS_LINK_DEPENDS.
    set_target_properties (${TARGET_UID} PROPERTIES LINK_DEPENDS "${DEPENDS}")
  endif ()
endfunction ()

# ============================================================================
# add targets
# ============================================================================

# ----------------------------------------------------------------------------
## @brief Add custom target.
macro (basis_add_custom_target TARGET_NAME)
  basis_check_target_name ("${TARGET_NAME}")
  basis_make_target_uid (_UID "${TARGET_NAME}")
  add_custom_target (${_UID} ${ARGN})
  unset (_UID)
endmacro ()

# ----------------------------------------------------------------------------
## @brief Determine language of source files.
# @sa basis_add_executable(), basis_add_library()
macro (_basis_target_source_language)
  if (NOT ARGN_LANGUAGE)
    basis_get_source_language (ARGN_LANGUAGE ${SOURCES})
    if (ARGN_LANGUAGE MATCHES "AMBIGUOUS|UNKNOWN")
      set (_FILES)
      foreach (SOURCE IN LISTS SOURCES)
        set (_FILES "${_FILES}\n  ${SOURCE}")
      endforeach ()
      if (ARGN_LANGUAGE MATCHES "AMBIGUOUS")
        message (FATAL_ERROR "Target ${TARGET_UID}: Ambiguous source code files! Try to set LANGUAGE manually and make sure that no unknown option was given:${_FILES}")
      elseif (ARGN_LANGUAGE MATCHES "UNKNOWN")
        message (FATAL_ERROR "Target ${TARGET_UID}: Unknown source code language! Try to set LANGUAGE manually and make sure that no unknown option was given:${_FILES}")
      endif ()
    endif ()
  else ()
    string (TOUPPER "${ARGN_LANGUAGE}" ARGN_LANGUAGE)
  endif ()
endmacro ()

# ----------------------------------------------------------------------------
## @brief Add executable target.
#
# This is the main function to add an executable target to the build system,
# where an executable can be a binary file or a script written in a scripting
# language. In general we refer to any output file which is part of the software
# (i.e., excluding configuration files) and which can be executed
# (e.g., a binary file in the ELF format) or interpreted (e.g., a Python script)
# directly, as executable file. Natively, CMake supports only executables built
# from C/C++ source code files. This function extends CMake's capabilities
# by adding custom build commands for non-natively supported programming
# languages and further standardizes the build of executable targets.
# For example, by default, it is not necessary to specify installation rules
# separately as these are added by this function already (see below).
#
# @par Programming languages
# Besides adding usual executable targets build by the set <tt>C/CXX</tt>
# language compiler, this function inspects the list of source files given and
# detects whether this list contains sources which need to be build using a
# different compiler. In particular, it supports the following languages:
# @n
# <table border="0">
#   <tr>
#     @tp @b CXX @endtp
#     <td>The default behavior, adding an executable target build from C/C++
#         source code. The target is added via CMake's add_executable() command.</td>
#   </tr>
#   <tr>
#     @tp <b>PYTHON</b>|<b>JYTHON</b>|<b>PERL</b>|<b>BASH</b> @endtp
#     <td>Executables written in one of the named scripting languages are built by
#         configuring and/or copying the script files to the build tree and
#         installation tree, respectively. During the build step, certain strings
#         of the form \@VARIABLE\@ are substituted by the values set during the
#         configure step. How these CMake variables are set is specified by a
#         so-called script configuration, which itself is either a CMake script
#         file or a string of CMake code set as value of the @c SCRIPT_DEFINITIONS
#         property of the executable target.</td>
#   </tr>
#   <tr>
#     @tp @b MATLAB @endtp
#     <td>Standalone application built from MATLAB sources using the
#         MATLAB Compiler (mcc). This language option is used when the list
#         of source files contains one or more *.m files. A custom target is
#         added which depends on custom command(s) that build the executable.</td>
#         @n@n
#         Attention: The *.m file with the entry point/main function of the
#                    executable has to be given before any other *.m file.
#   </tr>
# </table>
#
# @par Helper functions
# If the programming language of the input source files is not specified
# explicitly by providing the @p LANGUAGE argument, the extensions of the
# source files and if necessary the first line of script files are inspected
# by the basis_get_source_language() function. Once the programming language is
# known, this function invokes the proper subcommand which adds the respective
# build target. In particular, it calls basis_add_executable_target() for C++
# sources (.cxx), basis_add_mcc_target() for MATLAB scripts (.m), and
# basis_add_script() for all other source files.
#
# @note DO NOT use the mentioned subcommands directly. Always use
#       basis_add_executable() to add an executable target to your project.
#       Only refer to the documentation of the subcommands to learn about the
#       available options of the particular subcommand and considered target
#       properties.
#
# @par Output directories
# The built executable file is output to the @c BINARY_RUNTIME_DIR or
# @c BINARY_LIBEXEC_DIR if the @p LIBEXEC option is given.
# If this function is used within the @c PROJECT_TESTING_DIR, however,
# the built executable is output to the @c TESTING_RUNTIME_DIR or
# @c TESTING_LIBEXEC_DIR instead.
#
# @par Installation
# An install command for the added executable target is added by this function
# as well. The executable will be installed as part of the specified @p COMPONENT
# in the directory @c INSTALL_RUNTIME_DIR or @c INSTALL_LIBEXEC_DIR if the option
# @p LIBEXEC is given. Executable targets are exported by default such that they
# can be imported by other CMake-aware projects by including the CMake
# configuration file of this package (&lt;Package&gt;Config.cmake file).
# No installation rules are added, however, if this function is used within the
# @c PROJECT_TESTING_DIR or if "none" (case-insensitive) is given as
# @p DESTINATION. Test executables are further only exported as part of the
# build tree, but not the installation as they are by default not installed.
#
# @param [in] TARGET_NAME Name of the target. If an existing source file is given
#                         as first argument, it is added to the list of source files
#                         and the build target name is derived from the name of this file.
# @param [in] ARGN        This argument list is parsed and the following
#                         arguments are extracted, all other arguments are passed
#                         on to add_executable() or the respective custom
#                         commands used to add an executable build target.
# @par
# <table border="0">
#   <tr>
#     @tp @b COMPONENT name @endtp
#     <td>Name of component as part of which this executable will be installed
#         if the specified @c DESTINATION is not "none".
#         (default: @c BASIS_RUNTIME_COMPONENT)</td>
#   </tr>
#   <tr>
#     @tp @b DESTINATION dir @endtp
#     <td>Installation directory relative to @c CMAKE_INSTALL_PREFIX.
#         If "none" (case-insensitive) is given as argument, no default
#         installation rules are added for this executable target.
#         (default: @c INSTALL_RUNTIME_DIR or @c INSTALL_LIBEXEC_DIR
#         if the @p LIBEXEC option is given)</td>
#   </tr>
#   <tr>
#     @tp @b LANGUAGE lang @endtp
#     <td>Programming language in which source files are written (case-insensitive).
#         If not specified, the programming language is derived from the file name
#         extensions of the source files and, if applicable, the shebang directive
#         on the first line of the script file. If the programming language could
#         not be detected automatically, check the file name extensions of the
#         source files and whether no unrecognized additional arguments were given
#         or specify the programming language using this option.
#         (default: auto-detected)</td>
#   </tr>
#   <tr>
#     @tp @b LIBEXEC @endtp
#     <td>Specifies that the built executable is an auxiliary executable which
#         is only called by other executables. (default: @c FALSE)</td>
#   </tr>
#   <tr>
#     @tp @b [NO]EXPORT @endtp
#     <td>Whether to export this target. (default: @c TRUE)</td>
#   </tr>
#   <tr>
#     @tp @b NO_BASIS_UTILITIES @endtp
#     <td>Specify that the BASIS utilities are not used by this executable and hence
#         no link dependency on the BASIS utilities shall be added.
#         (default: @c NOT @c BASIS_UTILITIES)</td>
#   </tr>
#   <tr>
#     @tp @b USE_BASIS_UTILITIES @endtp
#     <td>Specify that the BASIS utilities are used and required by this executable
#         and hence a link dependency on the BASIS utilities has to be added.
#         (default: @c BASIS_UTILITIES)</td>
#   </tr>
# </table>
#
# @returns Adds an executable build target. In case of an executable which is
#          not build from C++ source files, the function basis_finalize_targets()
#          has to be invoked to finalize the addition of the custom build target.
#          This is done by the basis_project_end() macro.
#
# @sa basis_add_executable_target()
# @sa basis_add_script()
# @sa basis_add_mcc_target()
#
# @ingroup CMakeAPI
function (basis_add_executable TARGET_NAME)
  # --------------------------------------------------------------------------
  # parse arguments
  CMAKE_PARSE_ARGUMENTS (
    ARGN
      "EXECUTABLE;LIBEXEC;NO_BASIS_UTILITIES;USE_BASIS_UTILITIES;EXPORT;NOEXPORT"
      "COMPONENT;DESTINATION;LANGUAGE"
      ""
    ${ARGN}
  )
  # derive target name from path if existing source path is given as first argument instead
  # and get list of library source files
  get_filename_component (S "${TARGET_NAME}" ABSOLUTE)
  if (IS_DIRECTORY "${S}" AND NOT ARGN_UNPARSED_ARGUMENTS)
    set (SOURCES "${S}")
    basis_get_source_target_name (TARGET_NAME "${TARGET_NAME}" NAME_WE)
  elseif (EXISTS "${S}" AND NOT IS_DIRECTORY "${S}" OR (NOT S MATCHES "\\.in$" AND EXISTS "${S}.in" AND NOT IS_DIRECTORY "${S}.in"))
    set (SOURCES "${S}")
    basis_get_source_target_name (TARGET_NAME "${TARGET_NAME}" NAME_WE)
  else ()
    set (SOURCES)
  endif ()
  if (ARGN_UNPARSED_ARGUMENTS)
    list (APPEND SOURCES ${ARGN_UNPARSED_ARGUMENTS})
  endif ()
  if (NOT SOURCES)
    message (FATAL_ERROR "basis_add_executable called with only one argument which does however not"
                         " appear to be a file name. Note that the filename extension must be"
                         " included if the target name should be derived from the base filename"
                         " of the source file.")
  endif ()
  # --------------------------------------------------------------------------
  # make target UID
  basis_check_target_name ("${TARGET_NAME}")
  basis_make_target_uid (TARGET_UID "${TARGET_NAME}")
  # --------------------------------------------------------------------------
  # process globbing expressions to get complete list of source files
  basis_add_glob_target (${TARGET_UID} SOURCES ${SOURCES})
  # --------------------------------------------------------------------------
  # determine programming language
  _basis_target_source_language ()
  # --------------------------------------------------------------------------
  # prepare arguments for subcommand
  foreach (ARG IN LISTS ARGN_UNPARSED_ARGUMENTS)
    list (REMOVE_ITEM ARGN "${ARG}")
  endforeach ()
  list (APPEND ARGN ${SOURCES})
  # --------------------------------------------------------------------------
  # C++
  if (ARGN_LANGUAGE MATCHES "CXX")
    basis_add_executable_target (.${TARGET_UID} ${ARGN})
  # --------------------------------------------------------------------------
  # MATLAB
  elseif (ARGN_LANGUAGE MATCHES "MATLAB")
    if (ARGN_LIBEXEC)
      list (REMOVE_ITEM ARGN LIBEXEC)
      basis_add_mcc_target (.${TARGET_UID} LIBEXEC ${ARGN})
    else ()
      list (REMOVE_ITEM ARGN EXECUTABLE)
      basis_add_mcc_target (.${TARGET_UID} EXECUTABLE ${ARGN})
    endif ()
  # --------------------------------------------------------------------------
  # others
  else ()
    if (ARGN_LIBEXEC)
      list (REMOVE_ITEM ARGN LIBEXEC)
      basis_add_script (.${TARGET_UID} LIBEXEC ${ARGN})
    else ()
      list (REMOVE_ITEM ARGN EXECUTABLE)
      basis_add_script (.${TARGET_UID} EXECUTABLE ${ARGN})
    endif ()
  endif ()
  # --------------------------------------------------------------------------
  # re-glob source files before each build (if necessary)
  if (TARGET __${TARGET_UID})
    if (TARGET _${TARGET_UID})
      add_dependencies (_${TARGET_UID} __${TARGET_UID})
    endif ()
    add_dependencies (${TARGET_UID} __${TARGET_UID})
  endif ()
endfunction ()

# ----------------------------------------------------------------------------
## @brief Add library target.
#
# This is the main function to add a library target to the build system, where
# a library can be a binary archive, shared library, a MEX-file or module(s)
# written in a scripting language. In general we refer to any output file which
# is part of the software (i.e., excluding configuration files), but cannot be
# executed (e.g., a binary file in the ELF format) or interpreted
# (e.g., a Python module) directly, as library file. Natively, CMake supports only
# libraries built from C/C++ source code files. This function extends CMake's
# capabilities by adding custom build commands for non-natively supported
# programming languages and further standardizes the build of library targets.
# For example, by default, it is not necessary to specify installation rules
# separately as these are added by this function already (see below).
#
# @par Programming languages
# Besides adding usual library targets built from C/C++ source code files,
# this function can also add custom build targets for libraries implemented
# in other programming languages. It therefore tries to detect the programming
# language of the given source code files and delegates the addition of the
# build target to the proper helper functions. It in particular supports the
# following languages:
# @n
# <table border="0">
#   <tr>
#     @tp @b CXX @endtp
#     <td>Source files written in C/C++ are by default built into either
#         @p STATIC, @p SHARED, or @p MODULE libraries. If the @p MEX option
#         is given, however, a MEX-file (a shared library) is build using
#         the MEX script instead of using the default C++ compiler directly.</td>
#   </tr>
#   <tr>
#     @tp <b>PYTHON</b>|<b>JYTHON</b>|<b>PERL</b>|<b>BASH</b> @endtp
#     <td>Modules written in one of the named scripting languages are built similar
#         to executable scripts except that the file name extension is preserved
#         and no executable file permission is set on Unix. These modules are
#         intended for import/inclusion in other modules or executables written
#         in the particular scripting language only.</td>
#   </tr>
#   <tr>
#     @tp @b MATLAB @endtp
#     <td>Libraries of M-files or shared libraries built using the MATLAB Compiler (mcc).
#         This language option is used when the list of source files contains one or
#         more *.m files. A custom target is added which depends on custom command(s)
#         that build the library. If the type of the library is @c SHARED, a shared
#         library is build using the MATLAB Compiler. Otherwise, the M-files are
#         configured and installed such that they can be used in MATLAB.</td>
#   </tr>
# </table>
#
# @par Helper functions
# If the programming language of the input source files is not specified
# explicitly by providing the @p LANGUAGE argument, the extensions of the
# source files are inspected using basis_get_source_language(). Once the
# programming language is known, this function invokes the proper subcommand.
# In particular, it calls basis_add_library_target() for C++ sources (.cxx)
# if the target is not a MEX-file target, basis_add_mex_file() for C++ sources
# if the @p MEX option is given, basis_add_mcc_target() for MATLAB scripts (.m),
# and basis_add_script_library() for all other source files.
#
# @note DO NOT use the mentioned subcommands directly. Always use
#       basis_add_library() to add a library target to your project. Only refer
#       to the documentation of the subcommands to learn about the available
#       options of the particular subcommand and the considered target properties.
#
# @par Output directories
# In case of modules written in a scripting language, the libraries are output to
# the <tt>BINARY_&lt;LANGUAGE&gt;_LIBRARY_DIR</tt> if defined. Otherwise,
# the built libraries are output to the @c BINARY_RUNTIME_DIR, @c BINARY_LIBRARY_DIR,
# and/or @c BINARY_ARCHIVE_DIR. If this command is used within the @c PROJECT_TESTING_DIR,
# however, the files are output to the corresponding directories in the testing tree,
# instead.
#
# @par Installation
# An installation rule for the added library target is added by this function
# if the destination is not "none" (case-insensitive). Runtime libraries are
# installed as part of the @p RUNTIME_COMPONENT to the @p RUNTIME_DESTINATION.
# Library components are installed as part of the @p LIBRARY_COMPONENT to the
# @p LIBRARY_DESTINATION. Library targets are further exported such that they
# can be imported by other CMake-aware projects by including the CMake
# configuration file of this package (&lt;Package&gt;Config.cmake file).
# If this function is used within the @c PROJECT_TESTING_DIR, however, no
# installation rules are added. Test library targets are further only exported
# as part of the build tree.
#
# @par Example
# @code
# basis_add_library (MyLib1 STATIC mylib.cxx)
# basis_add_library (MyLib2 STATIC mylib.cxx COMPONENT dev)
#
# basis_add_library (
#   MyLib3 SHARED mylib.cxx
#   RUNTIME_COMPONENT bin
#   LIBRARY_COMPONENT dev
# )
#
# basis_add_library (MyMex MEX mymex.cxx)
# basis_add_library (PythonModule MyModule.py.in)
# basis_add_library (ShellModule MODULE MyModule.sh.in)
# @endcode
#
# @param [in] TARGET_NAME Name of build target. If an existing file is given as
#                         argument, it is added to the list of source files and
#                         the target name is derived from the name of this file.
# @param [in] ARGN        This argument list is parsed and the following
#                         arguments are extracted. All unparsed arguments are
#                         treated as source files.
# @par
# <table border="0">
#   <tr>
#     @tp <b>STATIC</b>|<b>SHARED</b>|<b>MODULE</b>|<b>MEX</b> @endtp
#     <td>Type of the library. (default: @c SHARED for C++ libraries if
#         @c BUILD_SHARED_LIBS evaluates to true or @c STATIC otherwise,
#         and @c MODULE in all other cases)</td>
#   </tr>
#   <tr>
#     @tp @b COMPONENT name @endtp
#     <td>Name of component as part of which this library will be installed
#         if the @c RUNTIME_DESTINATION or @c LIBRARY_DESTINATION is not "none".
#         Used only if @p RUNTIME_COMPONENT or @p LIBRARY_COMPONENT not specified.
#         (default: see @p RUNTIME_COMPONENT and @p LIBRARY_COMPONENT)</td>
#   </tr>
#   <tr>
#     @tp @b DESTINATION dir @endtp
#     <td>Installation directory for runtime and library component relative
#         to @c CMAKE_INSTALL_PREFIX. Used only if @p RUNTIME_DESTINATION or
#         @p LIBRARY_DESTINATION not specified. If "none" (case-insensitive)
#         is given as argument, no default installation rules are added.
#         (default: see @p RUNTIME_DESTINATION and @p LIBRARY_DESTINATION)</td>
#   </tr>
#   <tr>
#     @tp @b LANGUAGE lang @endtp
#     <td>Programming language in which source files are written (case-insensitive).
#         If not specified, the programming language is derived from the file name
#         extensions of the source files and, if applicable, the shebang directive
#         on the first line of the script file. If the programming language could
#         not be detected automatically, check the file name extensions of the
#         source files and whether no unrecognized additional arguments were given
#         or specify the programming language using this option.
#         (default: auto-detected)</td>
#   </tr>
#   <tr>
#     @tp @b LIBRARY_COMPONENT name @endtp
#     <td>Name of component as part of which import/static library will be intalled
#         if @c LIBRARY_DESTINATION is not "none".
#         (default: @c COMPONENT if specified or @c BASIS_LIBRARY_COMPONENT otherwise)</td>
#   </tr>
#   <tr>
#     @tp @b LIBRARY_DESTINATION dir @endtp
#     <td>Installation directory of the library component relative to
#         @c CMAKE_INSTALL_PREFIX. If "none" (case-insensitive) is given as argument,
#         no installation rule for the library component is added.
#         (default: @c INSTALL_ARCHIVE_DIR)</td>
#   </tr>
#   <tr>
#     @tp @b RUNTIME_COMPONENT name @endtp
#     <td>Name of component as part of which runtime library will be installed
#         if @c RUNTIME_DESTINATION is not "none".
#         (default: @c COMPONENT if specified or @c BASIS_RUNTIME_COMPONENT otherwise)</td>
#   </tr>
#   <tr>
#     @tp @b RUNTIME_DESTINATION dir @endtp
#     <td>Installation directory of the runtime component relative to
#         @c CMAKE_INSTALL_PREFIX. If "none" (case-insensitive) is given as argument,
#         no installation rule for the runtime library is added.
#         (default: @c INSTALL_LIBRARY_DIR on Unix or @c INSTALL_RUNTIME_DIR Windows)</td>
#   </tr>
#   <tr>
#     @tp @b [NO]EXPORT @endtp
#     <td>Whether to export this target. (default: @c TRUE)</td>
#   </tr>
#   <tr>
#     @tp @b NO_BASIS_UTILITIES @endtp
#     <td>Specify that the BASIS utilities are not used by this executable and hence
#         no link dependency on the BASIS utilities shall be added.
#         (default: @c NOT @c BASIS_UTILITIES)</td>
#   </tr>
#   <tr>
#     @tp @b USE_BASIS_UTILITIES @endtp
#     <td>Specify that the BASIS utilities are used and required by this executable
#         and hence a link dependency on the BASIS utilities has to be added.
#         (default: @c BASIS_UTILITIES)</td>
#   </tr>
# </table>
#
# @returns Adds a library build target. In case of a library not written in C++
#          or MEX-file targets, basis_finalize_targets() has to be invoked
#          to finalize the addition of the build target(s). This is done
#          by the basis_project_end() macro.
#
# @sa basis_add_library_target()
# @sa basis_add_script_library()
# @sa basis_add_mex_file()
# @sa basis_add_mcc_target()
#
# @ingroup CMakeAPI
function (basis_add_library TARGET_NAME)
  # --------------------------------------------------------------------------
  # parse arguments
  CMAKE_PARSE_ARGUMENTS (
    ARGN
      "STATIC;SHARED;MODULE;MEX;USE_BASIS_UTILITIES;NO_BASIS_UTILITIES;EXPORT;NOEXPORT"
      "COMPONENT;RUNTIME_COMPONENT;LIBRARY_COMPONENT;DESTINATION;RUNTIME_DESTINATION;LIBRARY_DESTINATION;LANGUAGE"
      ""
    ${ARGN}
  )
  # derive target name from path if existing source path is given as first argument instead
  # and get list of library source files
  get_filename_component (S "${TARGET_NAME}" ABSOLUTE)
  if (IS_DIRECTORY "${S}" AND NOT ARGN_UNPARSED_ARGUMENTS)
    set (SOURCES "${S}")
    basis_get_source_target_name (TARGET_NAME "${TARGET_NAME}" NAME)
  elseif (EXISTS "${S}" AND NOT IS_DIRECTORY "${S}" OR (NOT S MATCHES "\\.in$" AND EXISTS "${S}.in" AND NOT IS_DIRECTORY "${S}.in"))
    set (SOURCES "${S}")
    if (ARGN_MEX)
      basis_get_source_target_name (TARGET_NAME "${TARGET_NAME}" NAME_WE)
    else ()
      _basis_target_source_language ()
      if ("$${ARGN_LANGUAGE}" STREQUAL "$CXX")
        basis_get_source_target_name (TARGET_NAME "${TARGET_NAME}" NAME_WE)
      else ()
        basis_get_source_target_name (TARGET_NAME "${TARGET_NAME}" NAME)
      endif ()
    endif ()
  else ()
    set (SOURCES)
  endif ()
  if (ARGN_UNPARSED_ARGUMENTS)
    list (APPEND SOURCES ${ARGN_UNPARSED_ARGUMENTS})
  endif ()
  # --------------------------------------------------------------------------
  # make target UID
  basis_check_target_name ("${TARGET_NAME}")
  basis_make_target_uid (TARGET_UID "${TARGET_NAME}")
  # --------------------------------------------------------------------------
  # process globbing expressions to get complete list of source files
  basis_add_glob_target (${TARGET_UID} SOURCES ${SOURCES})
  # --------------------------------------------------------------------------
  # determine programming language
  _basis_target_source_language ()
  # --------------------------------------------------------------------------
  # prepare arguments for subcommand
  foreach (ARG IN LISTS ARGN_UNPARSED_ARGUMENTS)
    list (REMOVE_ITEM ARGN "${ARG}")
  endforeach ()
  list (APPEND ARGN ${SOURCES})
  # --------------------------------------------------------------------------
  # C++
  if (ARGN_LANGUAGE MATCHES "CXX")
    # MEX-file
    if (ARGN_MEX)
      if (ARGN_STATIC)
        message (FATAL_ERROR "Target ${TARGET_UID}: Invalid library type! Only modules or shared libraries can be built by the MEX script.")
      endif ()
      list (REMOVE_ITEM ARGN MODULE)
      list (REMOVE_ITEM ARGN SHARED)
      list (REMOVE_ITEM ARGN MEX)
      basis_add_mex_file (.${TARGET_UID} ${ARGN})
    # library
    else ()
      basis_add_library_target (.${TARGET_UID} ${ARGN})
    endif ()
  # --------------------------------------------------------------------------
  # MATLAB
  elseif (ARGN_LANGUAGE MATCHES "MATLAB")
    if (ARGN_STATIC OR ARGN_MEX)
      message (FATAL_ERROR "Target ${TARGET_UID}: Invalid library type! Only shared libraries can be built by the MATLAB Compiler.")
    endif ()
    if (ARGN_SHARED)
      list (REMOVE_ITEM ARGN SHARED)
      basis_add_mcc_target (.${TARGET_UID} SHARED ${ARGN})
    else ()
      list (REMOVE_ITEM ARGN MODULE) # optional
      basis_add_script_library (.${TARGET_UID} ${ARGN})
    endif ()
  # --------------------------------------------------------------------------
  # other
  else ()
    if (ARGN_STATIC OR ARGN_SHARED OR ARGN_MEX)
      message (FATAL_ERROR "Target ${TARGET_UID}: Invalid library type! Only modules can be built from scripts.")
    endif ()
    list (REMOVE_ITEM ARGN MODULE)
    basis_add_script_library (.${TARGET_UID} ${ARGN})
  endif ()
  # --------------------------------------------------------------------------
  # re-glob source files before each build (if necessary)
  if (TARGET __${TARGET_UID})
    if (TARGET _${TARGET_UID})
      add_dependencies (_${TARGET_UID} __${TARGET_UID})
    endif ()
    add_dependencies (${TARGET_UID} __${TARGET_UID})
  endif ()
endfunction ()

# ----------------------------------------------------------------------------
## @brief Add single arbitrary or executable script.
#
# @note This function should not be used directly for executable scripts or
#       module libraries. Use basis_add_executable() or basis_add_library()
#       in such (most) cases instead.
#
# This function can be used to add a single arbitrary script file (i.e., any
# text file which is input to a program), such as a CTest script for example,
# to the build if neither basis_add_executable() nor basis_add_library() are
# appropriate choices. In all other cases, either basis_add_executable() or
# basis_add_library() should be used. Note that the script file is by default
# not considered to be an executable. Instead it is assumed that the program
# which interprets/processes the script must be executed explicitly with this
# script as argument. Only scripts built with the @p EXECUTABLE or @p LIBEXEC
# type option are treated as executable files, where in case of Unix a shebang
# directive implicitly states the program used by the shell to interpret the
# script and on Windows a Windows Command which imitates the behavior of Unix
# shells is generated by BASIS. Do not use these type options, however, but
# only use the default @p MODULE option. The basis_add_executable() function
# should be used instead to add an executable script. The basis_add_script()
# function shall only be used for none-executable arbitrary script files which
# cannot be built by basis_add_executable() or basis_add_library().
#
# If the script name ends in <tt>.in</tt>, the <tt>.in</tt> suffix is removed
# from the output name. Further, in case of executable scripts, the file name
# extension is removed from the output file name. Instead, a shebang directive
# is added on Unix to the built script. In order to enable the convenient
# execution of Python and Perl scripts also on Windows without requiring the
# user to setup a proper associate between the filename extension with the
# corresponding interpreter executable, a few lines of Batch code are added at
# the top and bottom of executable Python and Perl scripts. This Batch code
# invokes the configured interpreter with the script file and the given script
# arguments as command-line arguments. Note that both the original script source
# code and the Batch code are stored within the single file. The file name
# extension of such modified scripts is by default set to <tt>.cmd</tt>, the
# common extension for Windows NT Command Scripts. Scripts in other languages
# are not modified and the extension of the original scripts script file is
# preserved on Windows in this case. In case of non-executable scripts, the
# file name extension is kept in any case.
#
# Certain CMake variables within the source file are replaced during the
# built of the script. See the
# <a href="http://opensource.andreasschuh.com/cmake-basis/scripttargets/>
# Build System Standard</a> for details.
# Note, however, that source files are only configured if the file name
# ends in the <tt>.in</tt> suffix.
#
# A custom CMake build target with the following properties is added by this
# function to the build system. These properties are used by basis_build_script()
# to generate a build script written in CMake code which is executed by a custom
# CMake command. Before the invokation of basis_build_script(), the target
# properties can be modified using basis_set_target_properties().
#
# @note Custom BASIS build targets are finalized by BASIS using basis_project_end(),
#       i.e., the end of the root CMake configuration file of the (sub-)project.
#
# @par Properties on script targets
# <table border=0>
#   <tr>
#     @tp @b BASIS_TYPE @endtp
#     <td>Read-only property with value "SCRIPT_FILE" for arbitrary scripts,
#         "SCRIPT_EXECUTABLE" for executable scripts, and "SCRIPT_LIBEXEC" for
#          auxiliary executable scripts.
#          (default: see @p MODULE, @p EXECUTABLE, @p LIBEXEC options)</td>
#   </tr>
#   <tr>
#     @tp @b BASIS_UTILITIES @endtp
#     <td>Whether the BASIS utilities are used by this script. For the supported
#         scripting languages for which BASIS utilities are implemented, BASIS
#         will in most cases automatically detect whether these utilities are
#         used by a script or not. Otherwise, set this property manually or use
#         either the @p USE_BASIS_UTILITIES or the @p NO_BASIS_UTILITIES option
#         when adding the script target. (default: auto-detected or @c UNKNOWN)</td>
#   </tr>
#   <tr>
#     @tp @b BINARY_DIRECTORY @endtp
#     <td>Build tree directory of this target. (default: @c CMAKE_CURRENT_BINARY_DIR)</td>
#   </tr>
#   <tr>
#     @tp @b COMPILE @endtp
#     <td>Whether to compile the script if the programming language allows such
#         pre-compilation as in case of Python, for example. If @c TRUE, only the
#         compiled file is installed. (default: @c BASIS_COMPILE_SCRIPTS)</td>
#   </tr>
#   <tr>
#     @tp @b SCRIPT_DEFINITIONS @endtp
#     <td>CMake code which is evaluated after the inclusion of the default script
#         configuration files. This code can be used to set the replacement text of the
#         CMake variables ("@VAR@" patterns) used in the source file.
#         See <a href="http://opensource.andreasschuh.com/cmake-basis/standard/scripttargets.html#script-configuration">
#         Build System Standard</a> for details. (default: "")</td>
#   </tr>
#   <tr>
#     @tp @b SCRIPT_DEFINITIONS_FILE @endtp
#     <td>CMake script file with compile definitions, also referred to as script
#         configuration file. The named files are included after the default BASIS
#         script configuration and before the @c SCRIPT_DEFINITIONS code is being
#         evaluated. (default: @c BINARY_CONFIG_DIR/ScriptConfig.cmake)</td>
#   </tr>
#   <tr>
#     @tp @b COMPONENT @endtp
#     <td>Name of component as part of which this script is installed if
#         @c INSTALL_DIRECTORY is not set to "none".
#         (default: see @p COMPONENT argument)</td>
#   </tr>
#   <tr>
#     @tp @b EXPORT @endtp
#     <td>Whether to export this build target in which case an import library
#         target is added to the custom exports file with the path to the
#         built/installed script set as @c IMPORT_LOCATION. (default: @c TRUE)</td>
#   </tr>
#   <tr>
#     @tp @b INSTALL_DIRECTORY @endtp
#     <td>Installation directory of script file configured for use in installation tree
#         relative to @c CMAKE_INSTALL_PREFIX. Set to "none" (case-insensitive) to skip the
#         addition of an installation rule. (default: see @p DESTINATION argument)</td>
#   </tr>
#   <tr>
#     @tp @b LANGUAGE @endtp
#     <td>Read-only property of programming language of script file in uppercase letters.
#         (default: see @p LANGUAGE argument)</td>
#   </tr>
#   <tr>
#     @tp @b LINK_DEPENDS @endtp
#     <td>Paths or target names of script modules and libraries used by this script.
#         In case of an (auxiliary) executable script, the directories of these modules
#         are added to the search path for modules of the given programming language
#         if such search paths are supported by the language and BASIS knows how to set
#         these (as in case of Python/Jython, Perl, and MATLAB, in particular).
#         Moreover, for each listed build target a dependency is added between this
#         script target and the named build targets. Use basis_target_link_libraries()
#         to add additional link dependencies.
#         (default: BASIS utilities module if used or empty list otherwise)</td>
#   </tr>
#   <tr>
#     @tp @b OUTPUT_DIRECTORY @endtp
#     <td>Output directory for built script file configured for use in build tree.
#         (default: @c BINARY_LIBRARY_DIR for arbitrary scripts, @c BINARY_RUNTIME_DIR
#         for executable scripts, and @c BINARY_LIBEXEC_DIR for auxiliary executables)</td>
#   </tr>
#   <tr>
#     @tp @b OUTPUT_NAME @endtp
#     <td>Name of built script file including file name extension (if any).
#         (default: basename of script file for arbitrary scripts, without extension
#         for executable scripts on Unix, and <tt>.cmd</tt> extension on Windows
#         in case of executable Python/Jython or Perl script)</td>
#   </tr>
#   <tr>
#     @tp @b SOURCE_DIRECTORY @endtp
#     <td>Source directory of this target. (default: @c CMAKE_CURRENT_SOURCE_DIR)</td>
#   </tr>
#   <tr>
#     @tp @b SOURCES @endtp
#     <td>Read-only property which lists the source file of this script target.
#         Note that the first element in this list actually names a directory
#         in the build, the one where the build script for this target is located
#         instead of a source file and thus should be ignored. The second entry
#         corresponds to the source file of this script target.</td>
#   </tr>
# </table>
#
# @attention Properties documented as read-only must not be modified.
#
# @note If this function is used within the @c PROJECT_TESTING_DIR, the built
#       executable is output to the @c BINARY_TESTING_DIR directory tree instead.
#       Moreover, no installation rules are added. Test executables are further
#       not exported, regardless of the @c EXPORT property.
#
# @param [in] TARGET_NAME Name of build target. If an existing file is given as
#                         argument, it is added to the list of source files and
#                         the target name is derived from the name of this file.
# @param [in] ARGN        The remaining arguments are parsed and the following arguments
#                         recognized. All unparsed arguments are treated as source files,
#                         where in particular exactly one source file is required if the
#                         @p TARGET_NAME argument does not name an existing source file.
# @par
# <table border=0>
#   <tr>
#     @tp <b>MODULE</b>|<b>EXECUTABLE</b>|<b>LIBEXEC</b> @endtp
#     <td>Type of script to built, i.e., either arbitrary module script which
#         cannot be executed directly, an executable script with proper shebang
#         directive and execute permissions on Unix or Windows Command on Windows,
#         or an auxiliary executable. The type of the script mainly changes the
#         default values of the target properties such as the output and installation
#         directories. To add an (auxiliary) executable script, use
#         basis_add_executable(), however, instead of this function.
#         The @c EXECUTABLE and @c LIBEXEC options are only intended for
#         internal use by BASIS. (default: MODULE)</td>
#   </tr>
#   <tr>
#     @tp @b COMPONENT name @endtp
#     <td>Name of installation component as part of which this script is being
#         installed if the @c INSTALL_DIRECTORY property is not "none".
#         (default: @c BASIS_LIBRARY_COMPONENT for arbitrary scripts or
#         @c BASIS_RUNTIME_COMPONENT for executable scripts)</td>
#   </tr>
#   <tr>
#     @tp @b DESTINATION dir @endtp
#     <td>Installation directory for script file relative to @c CMAKE_INSTALL_PREFIX.
#         If an absolute path is given as argument, it is made relative to the
#         configured installation prefix.
#         (default: @c INSTALL_LIBRARY_DIR for arbitrary scripts,
#         @c INSTALL_RUNTIME_DIR for executable scripts, and @c INSTALL_LIBEXEC_DIR
#         for auxiliary executable scripts)</td>
#   </tr>
#   <tr>
#     @tp @b LANGUAGE lang @endtp
#     <td>Programming language in which script file is written (case-insensitive).
#         If not specified, the programming language is derived from the file name
#         extension of the source file and the shebang directive on the first line
#         of the script if any. If the programming language could not be detected
#         automatically, the @c LANGUAGE property is set to @c UNKNOWN. Note that
#         for arbitrary script targets, the script file will still be built correctly
#         even if the scripting language was not recognized. The automatic detection
#         whether the BASIS utilities are used and required will fail, however.
#         In this case, specify the programming language using this option.
#         (default: auto-detected or @c UNKNOWN)</td>
#   </tr>
#   <tr>
#     @tp @b [NO]EXPORT @endtp
#     <td>Whether to export this target. (default: @c TRUE)</td>
#   </tr>
#   <tr>
#     @tp @b NO_BASIS_UTILITIES @endtp
#     <td>Specify that the BASIS utilities are not used by this script. If the
#         programming language of the script is known and BASIS utilities are
#         available for this language, BASIS will in most cases automatically
#         detect whether these utilities are used by a script or not. Use this
#         option to skip this check because the script does not make use of the
#         BASIS utilities.</td>
#   </tr>
#   <tr>
#     @tp @b USE_BASIS_UTILITIES @endtp
#     <td>Specify that the BASIS utilities are used and thus required by this script.
#         If the programming language of the script is known and BASIS utilities are
#         available for this language, BASIS will in most cases automatically
#         detect whether these utilities are used by a script or not. Use this option
#         to skip this check because it is already known that the script makes use of
#         the BASIS utilities. Note that an error is raised if this option is given,
#         but no BASIS utilities are available for the programming language of this
#         script or if the programming language is unknown, respectively, not detected
#         correctly. In this case, consider the use of the @p LANGUAGE argument.</td>
#   </tr>
# </table>
#
# @returns Adds a custom CMake target with the documented properties. The actual custom
#          command to build the script is added by basis_build_script().
#
# @ingroup CMakeAPI
function (basis_add_script TARGET_NAME)
  # parse arguments
  CMAKE_PARSE_ARGUMENTS (
    ARGN
      "MODULE;EXECUTABLE;LIBEXEC;NO_BASIS_UTILITIES;USE_BASIS_UTILITIES;EXPORT;NOEXPORT"
      "COMPONENT;DESTINATION;LANGUAGE"
      ""
    ${ARGN}
  )
  if (NOT ARGN_MODULE AND NOT ARGN_EXECUTABLE AND NOT ARGN_LIBEXEC)
    set (ARGN_MODULE TRUE)
  endif ()
  if (ARGN_MODULE)
    set (TYPE MODULE)
  else ()
    set (TYPE EXECUTABLE)
  endif ()
  string (TOLOWER "${TYPE}" type)
  # derive target name from file name if existing source file given as first argument
  get_filename_component (S "${TARGET_NAME}" ABSOLUTE)
  if (EXISTS "${S}" AND NOT IS_DIRECTORY "${S}" OR (NOT S MATCHES "\\.in$" AND EXISTS "${S}.in" AND NOT IS_DIRECTORY "${S}.in"))
    set (SOURCES "${S}")
    if (ARGN_MODULE)
      basis_get_source_target_name (TARGET_NAME "${TARGET_NAME}" NAME)
    else ()
      basis_get_source_target_name (TARGET_NAME "${TARGET_NAME}" NAME_WE)
    endif ()
    set (SET_OUTPUT_NAME_TO_TARGET_NAMEE FALSE)
  else ()
    set (SOURCES)
    set (SET_OUTPUT_NAME_TO_TARGET_NAME TRUE)
  endif ()
  # check target name
  basis_check_target_name ("${TARGET_NAME}")
  basis_make_target_uid (TARGET_UID "${TARGET_NAME}")
  message (STATUS "Adding ${type} script ${TARGET_UID}...")
  if (ARGN_MODULE AND TYPE MATCHES "EXECUTABLE")
    message (FATAL_ERROR "Target ${TARGET_UID}: MODULE and EXECUTABLE or LIBEXEC options are mutually exclusive!")
  endif ()
  # check/set parsed arguments
  basis_set_flag (ARGN EXPORT ${BASIS_EXPORT_DEFAULT})
  if (ARGN_USE_BASIS_UTILITIES AND ARGN_NO_BASIS_UTILITIES)
    message (FATAL_ERROR "Options USE_BASIS_UTILITIES and NO_BASIS_UTILITIES are mutually exclusive!")
  endif ()
  list (LENGTH ARGN_UNPARSED_ARGUMENTS N)
  if (SOURCES)
    math (EXPR N "${N} + 1")
  endif ()
  if (N GREATER 1)
    if (NOT SOURCES)
      list (REMOVE_AT ARGN_UNPARSED_ARGUMENTS 0)
    endif ()
    message (FATAL_ERROR "Target ${TARGET_UID}: Too many or unrecognized arguments: ${ARGN_UNPARSED_ARGUMENTS}!\n"
                         " Only one script can be built by each script target.")
  elseif (NOT SOURCES)
    set (SOURCES "${ARGN_UNPARSED_ARGUMENTS}")
    get_filename_component (SOURCES "${SOURCES}" ABSOLUTE)
  endif ()
  if (NOT EXISTS "${SOURCES}" AND NOT SOURCES MATCHES "\\.in$" AND EXISTS "${SOURCES}.in")
    set (SOURCES "${SOURCES}.in")
  endif ()
  if (NOT EXISTS "${SOURCES}")
    string (REGEX REPLACE "\\.in$" "" SOURCES "${SOURCES}")
    message (FATAL_ERROR "Target ${TARGET_UID}: Source file ${SOURCES}[.in] does not exist!")
  endif ()
  # add custom target
  add_custom_target (${TARGET_UID} ALL SOURCES ${SOURCES})
  # dump CMake variables for configuration of script
  set (BUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/${TARGET_UID}")
  basis_dump_variables ("${BUILD_DIR}.dir/cache.cmake.tmp")
  # auto-detect programming language (may be as well UNKNOWN)
  if (ARGN_LANGUAGE)
    string (TOUPPER "${ARGN_LANGUAGE}" ARGN_LANGUAGE)
  else ()
    basis_get_source_language (ARGN_LANGUAGE ${SOURCES})
  endif ()
  # IS_TEST flag
  basis_sanitize_for_regex (RE "${PROJECT_TESTING_DIR}")
  if (CMAKE_CURRENT_SOURCE_DIR MATCHES "^${RE}")
    set (IS_TEST TRUE)
  else ()
    set (IS_TEST FALSE)
  endif ()
  # default directory infix used below
  if (ARGN_MODULE)
    set (TYPE_INFIX "LIBRARY")
  elseif (ARGN_LIBEXEC)
    set (TYPE_INFIX "LIBEXEC")
  else ()
    set (TYPE_INFIX "RUNTIME")
  endif ()
  # output name
  string (REGEX REPLACE "\\.in$" "" SOURCE_NAME "${SOURCES}")
  if (SET_OUTPUT_NAME_TO_TARGET_NAME)
    basis_get_target_name (OUTPUT_NAME ${TARGET_UID})
  else ()
    get_filename_component (OUTPUT_NAME "${SOURCE_NAME}" NAME_WE)
  endif ()
  if (ARGN_MODULE)
    get_filename_component (SUFFIX "${SOURCE_NAME}" EXT)
  else ()
    if (WIN32)
      if (ARGN_LANGUAGE MATCHES "[JP]YTHON|PERL")
        set (SUFFIX ".cmd")
      else ()
        get_filename_component (SUFFIX "${SOURCE_NAME}" EXT)
      endif ()
    else ()
      set (SUFFIX)
    endif ()
  endif ()
  # output directory
  if (IS_TEST)
    set (OUTPUT_DIRECTORY "${TESTING_${TYPE_INFIX}_DIR}")
  else ()
    set (OUTPUT_DIRECTORY "${BINARY_${TYPE_INFIX}_DIR}")
  endif ()
  # installation component
  if (NOT ARGN_COMPONENT)
    if (ARGN_MODULE)
      set (ARGN_COMPONENT "${BASIS_LIBRARY_COMPONENT}")
    else ()
      set (ARGN_COMPONENT "${BASIS_RUNTIME_COMPONENT}")
    endif ()
  endif ()
  if (NOT ARGN_COMPONENT)
    set (ARGN_COMPONENT "Unspecified")
  endif ()
  # installation directory
  if (ARGN_DESTINATION)
    if (ARGN_DESTINATION MATCHES "^[nN][oO][nN][eE]$")
      set (ARGN_DESTINATION)
    elseif (IS_ABSOLUTE "${ARGN_DESTINATION}")
      file (RELATIVE_PATH ARGN_DESTINATION "${CMAKE_INSTALL_PREFIX}" "${ARGN_DESTINATION}")
    endif ()
  elseif (IS_TEST)
    set (ARGN_DESTINATION) # do not install
  else ()
    set (ARGN_DESTINATION "${INSTALL_${TYPE_INFIX}_DIR}")
  endif ()
  # script configuration ("compile definitions")
  if (EXISTS "${BINARY_CONFIG_DIR}/ScriptConfig.cmake")
    set (CONFIG_FILE "${BINARY_CONFIG_DIR}/ScriptConfig.cmake")
  else ()
    set (CONFIG_FILE)
  endif ()
  # auto-detect use of BASIS utilities
  set (LINK_DEPENDS)
  if (ARGN_LANGUAGE MATCHES "[JP]YTHON")
    set (UTILITIES_LANGUAGE "PYTHON")
  else ()
    set (UTILITIES_LANGUAGE "${ARGN_LANGUAGE}")
  endif ()
  if (ARGN_USE_BASIS_UTILITIES)
    if (NOT BASIS_UTILITIES_ENABLED MATCHES "${UTILITIES_LANGUAGE}")
      message (FATAL_ERROR "Target ${TARGET_UID} requires the BASIS utilities for ${UTILITIES_LANGUAGE}"
                           " but BASIS was either build without the build of these utilities enabled"
                           " or no utilities for this programming language are implemented. Remove the"
                           " USE_BASIS_UTILITIES option if no BASIS utilities are used by the script"
                           " ${SOURCES} or specify the correct programming language if it was not"
                           " detected correctly.")
    endif ()
    set (USES_BASIS_UTILITIES TRUE)
  elseif (NOT ARGN_NO_BASIS_UTILITIES AND NOT UTILITIES_LANGUAGE MATCHES "UNKNOWN")
    basis_utilities_check (USES_BASIS_UTILITIES ${SOURCES} ${UTILITIES_LANGUAGE})
  else ()
    set (USES_BASIS_UTILITIES FALSE)
  endif ()
  if (USES_BASIS_UTILITIES)
    basis_set_project_property (PROPERTY PROJECT_USES_${UTILITIES_LANGUAGE}_UTILITIES TRUE)
    if (BASIS_DEBUG)
      message ("** Target ${TARGET_UID} uses the BASIS utilities for ${UTILITIES_LANGUAGE}.")
    endif ()
  endif ()
  # set properties of custom build target
  set_target_properties (
    ${TARGET_UID}
    PROPERTIES
      LANGUAGE                 ${ARGN_LANGUAGE}
      BASIS_TYPE               SCRIPT_${TYPE}
      BASIS_UTILITIES          ${USES_BASIS_UTILITIES}
      BUILD_DIRECTORY          "${BUILD_DIR}"
      SOURCE_DIRECTORY         "${CMAKE_CURRENT_SOURCE_DIR}"
      BINARY_DIRECTORY         "${CMAKE_CURRENT_BINARY_DIR}"
      OUTPUT_DIRECTORY         "${OUTPUT_DIRECTORY}"
      INSTALL_DIRECTORY        "${ARGN_DESTINATION}"
      COMPONENT                "${ARGN_COMPONENT}"
      OUTPUT_NAME              "${OUTPUT_NAME}"
      PREFIX                   ""
      SUFFIX                   "${SUFFIX}"
      SCRIPT_DEFINITIONS       ""
      SCRIPT_DEFINITIONS_FILE  "${CONFIG_FILE}"
      LINK_DEPENDS             "${LINK_DEPENDS}"
      EXPORT                   ${EXPORT}
      COMPILE                  ${BASIS_COMPILE_SCRIPTS}
      TEST                     ${IS_TEST}
      LIBEXEC                  ${ARGN_LIBEXEC}
  )
  # add target to list of targets
  basis_set_project_property (APPEND PROPERTY TARGETS "${TARGET_UID}")
  message (STATUS "Adding ${type} script ${TARGET_UID}... - done")
endfunction ()

# ============================================================================
# internal helpers
# ============================================================================

# ----------------------------------------------------------------------------
## @brief Add executable target.
#
# This BASIS function overwrites CMake's
# <a href="http://www.cmake.org/cmake/help/cmake-2-8-docs.html#command:add_executable">
# add_executable()</a> command in order to store information of imported targets
# which is in particular used to generate the source code of the ExecutableTargetInfo
# modules which are part of the BASIS utilities.
#
# @note Use basis_add_executable() instead where possible!
#
# @param [in] TARGET_UID Name of the target.
# @param [in] ARGN       Further arguments of CMake's add_executable().
#
# @sa http://www.cmake.org/cmake/help/cmake-2-8-docs.html#command:add_executable
function (add_executable TARGET_UID)
  _add_executable (${TARGET_UID} ${ARGN})
  list (FIND ARGN IMPORTED IDX)
  if (IDX EQUAL 0)
    if (COMMAND basis_add_imported_target)
      basis_add_imported_target ("${TARGET_UID}" EXECUTABLE)
    endif ()
  else ()
    basis_set_project_property (APPEND PROPERTY TARGETS "${TARGET_UID}")
  endif ()
endfunction ()

# ----------------------------------------------------------------------------
## @brief Add library target.
#
# This BASIS function overwrites CMake's
# <a href="http://www.cmake.org/cmake/help/cmake-2-8-docs.html#command:add_library">
# add_library()</a> command in order to store information of imported targets.
#
# @note Use basis_add_library() instead where possible!
#
# @param [in] TARGET_UID Name of the target.
# @param [in] ARGN       Further arguments of CMake's add_library().
#
# @sa http://www.cmake.org/cmake/help/cmake-2-8-docs.html#command:add_library
function (add_library TARGET_UID)
  _add_library (${TARGET_UID} ${ARGN})
  list (FIND ARGN IMPORTED IDX)
  if (IDX EQUAL 1)
    if (COMMAND basis_add_imported_target)
      basis_add_imported_target ("${TARGET_UID}" "${ARGV1}")
    endif ()
  else ()
    basis_set_project_property (APPEND PROPERTY TARGETS "${TARGET_UID}")
  endif ()
endfunction ()

# ----------------------------------------------------------------------------
## @brief Add executable built from C++ source code.
#
# @note This function should not be used directly. Instead, it is called
#       by basis_add_executable() if the (detected) programming language
#       of the given source code files is @c CXX (i.e., C/C++).
#
# This function adds an executable target for the build of an executable from
# C++ source code files. Refer to the documentation of basis_add_executable()
# for a description of general options for adding an executable target.
#
# By default, the BASIS C++ utilities library is added as link dependency.
# If none of the BASIS C++ utilities are used by this target, the option
# NO_BASIS_UTILITIES can be given. To enable this option by default, set the
# variable @c BASIS_UTILITIES to @c FALSE, best in the <tt>Settings.cmake</tt>
# file located in the @c PROJECT_CONFIG_DIR (add such file if missing).
# If the use of the BASIS C++ utilities is disabled by default, the
# @c USE_BASIS_UTILITIES option can be used to enable them for this target
# only. Note that the utilities library is a static library and thus the linker
# would simply not include any of the BASIS utility functions in the final
# binary file if not used. The only advantage of setting @c BASIS_UTILITIES to
# @c FALSE or to always specify @c NO_BASIS_UTILITIES if no target uses the
# utilities is that the BASIS utilities library will not be build in this case.
#
# @param [in] TARGET_NAME Name of build target.
# @param [in] ARGN        This argument list is parsed and the following
#                         arguments are extracted, all other arguments are
#                         considered to be source code files and simply passed
#                         on to CMake's add_executable() command.
# @par
# <table border=0>
#   <tr>
#     @tp @b COMPONENT name @endtp
#     <td>Name of component as part of which this executable will be installed
#         if the specified @c DESTINATION is not "none".
#         (default: @c BASIS_RUNTIME_COMPONENT)</td>
#   </tr>
#   <tr>
#     @tp @b DESTINATION dir @endtp
#     <td>Installation directory relative to @c CMAKE_INSTALL_PREFIX.
#         If "none" (case-insensitive) is given as argument, no default
#         installation rules are added for this executable target.
#         (default: @c INSTALL_RUNTIME_DIR or @c INSTALL_LIBEXEC_DIR
#         if @p LIBEXEC is given)</td>
#   </tr>
#   <tr>
#     @tp @b LIBEXEC @endtp
#     <td>Specifies that the built executable is an auxiliary executable which
#         is only called by other executables. (default: @c FALSE)</td>
#   </tr>
#   <tr>
#     @tp @b [NO]EXPORT @endtp
#     <td>Whether to export this target. (default: @c TRUE)</td>
#   </tr>
#   <tr>
#     @tp @b NO_BASIS_UTILITIES @endtp
#     <td>Specify that the BASIS utilities are not used by this executable and hence
#         no link dependency on the BASIS utilities library shall be added.
#         (default: @c NOT @c BASIS_UTILITIES)</td>
#   </tr>
#   <tr>
#     @tp @b USE_BASIS_UTILITIES @endtp
#     <td>Specify that the BASIS utilities are used and required by this executable
#         and hence a link dependency on the BASIS utilities library has to be added.
#         (default: @c BASIS_UTILITIES)</td>
#   </tr>
# </table>
#
# @returns Adds executable target using CMake's add_executable() command.
#
# @sa basis_add_executable()
function (basis_add_executable_target TARGET_NAME)
  # check target name
  basis_check_target_name (${TARGET_NAME})
  basis_make_target_uid (TARGET_UID "${TARGET_NAME}")
  message (STATUS "Adding executable ${TARGET_UID}...")
  # parse arguments
  CMAKE_PARSE_ARGUMENTS (
    ARGN
      "USE_BASIS_UTILITIES;NO_BASIS_UTILITIES;EXPORT;NOEXPORT;LIBEXEC"
      "COMPONENT;DESTINATION"
      ""
    ${ARGN}
  )
  set (SOURCES ${ARGN_UNPARSED_ARGUMENTS})
  basis_set_flag (ARGN EXPORT  ${BASIS_EXPORT_DEFAULT})
  if (ARGN_USE_BASIS_UTILITIES AND ARGN_NO_BASIS_UTILITIES)
    message (FATAL_ERROR "Target ${TARGET_UID}: Options USE_BASIS_UTILITIES and NO_BASIS_UTILITIES are mutually exclusive!")
  endif ()
  if (ARGN_USE_BASIS_UTILITIES)
    set (USES_BASIS_UTILITIES TRUE)
  elseif (ARGN_NO_BASIS_UTILITIES)
    set (USES_BASIS_UTILITIES FALSE)
  else ()
    set (USES_BASIS_UTILITIES ${BASIS_UTILITIES})
  endif ()
  # IS_TEST flag
  basis_sanitize_for_regex (RE "${PROJECT_TESTING_DIR}")
  if (CMAKE_CURRENT_SOURCE_DIR MATCHES "^${RE}")
    set (IS_TEST TRUE)
  else ()
    set (IS_TEST FALSE)
  endif ()
  # installation component
  if (NOT ARGN_COMPONENT)
    set (ARGN_COMPONENT "${BASIS_RUNTIME_COMPONENT}")
  endif ()
  if (NOT ARGN_COMPONENT)
    set (ARGN_COMPONENT "Unspecified")
  endif ()
  # installation directory
  if (ARGN_DESTINATION)
    if (ARGN_DESTINATION MATCHES "^[nN][oO][nN][eE]$")
      set (ARGN_DESTINATION)
    elseif (IS_ABSOLUTE "${ARGN_DESTINATION}")
      file (RELATIVE_PATH ARGN_DESTINATION "${CMAKE_INSTALL_PREFIX}" "${ARGN_DESTINATION}")
    endif ()
  elseif (ARGN_LIBEXEC)
    set (ARGN_DESTINATION "${INSTALL_LIBEXEC_DIR}")
  else ()
    set (ARGN_DESTINATION "${INSTALL_RUNTIME_DIR}")
  endif ()
  # configure (.in) source files
  basis_configure_sources (SOURCES ${SOURCES})
  # add executable target
  add_executable (${TARGET_UID} ${SOURCES})
  basis_make_target_uid (HEADERS_TARGET headers)
  if (TARGET "${HEADERS_TARGET}")
    add_dependencies (${TARGET_UID} ${HEADERS_TARGET})
  endif ()
  basis_get_target_name (OUTPUT_NAME ${TARGET_UID})
  set_target_properties (${TARGET_UID} PROPERTIES BASIS_TYPE "EXECUTABLE" LANGUAGE "CXX" OUTPUT_NAME "${OUTPUT_NAME}")
  if (ARGN_LIBEXEC)
    set_target_properties (${TARGET_UID} PROPERTIES LIBEXEC 1 COMPILE_DEFINITIONS LIBEXEC SCRIPT_DEFINITIONS LIBEXEC)
  else ()
    set_target_properties (${TARGET_UID} PROPERTIES LIBEXEC 0)
  endif ()
  set_target_properties (${TARGET_UID} PROPERTIES TEST ${IS_TEST})
  # output directory
  if (IS_TEST)
    if (ARGN_LIBEXEC)
      set_target_properties (${TARGET_UID} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${TESTING_LIBEXEC_DIR}")
    else ()
      set_target_properties (${TARGET_UID} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${TESTING_RUNTIME_DIR}")
    endif ()
  elseif (ARGN_LIBEXEC)
    set_target_properties (${TARGET_UID} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${BINARY_LIBEXEC_DIR}")
  else ()
    set_target_properties (${TARGET_UID} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${BINARY_RUNTIME_DIR}")
  endif ()
  # installation directory
  set_target_properties (${TARGET_UID} PROPERTIES RUNTIME_INSTALL_DIRECTORY "${ARGN_DESTINATION}")
  # link to BASIS utilities
  if (USES_BASIS_UTILITIES)
    basis_target_link_libraries (.${TARGET_UID} basis)
  else ()
    set_target_properties (${TARGET_UID} PROPERTIES BASIS_UTILITIES FALSE)
  endif ()
  # export
  set (EXPORT_OPT)
  if (EXPORT)
    basis_add_export_target (EXPORT_OPT ${TARGET_UID} "${IS_TEST}" ${ARGN_DESTINATION})
  endif ()
  # installation
  if (ARGN_DESTINATION)
    if (IS_TEST)
      # TODO install (selected?) tests
    else ()
      install (
        TARGETS ${TARGET_UID} ${EXPORT_OPT}
        DESTINATION "${ARGN_DESTINATION}"
        COMPONENT   "${ARGN_COMPONENT}"
      )
    endif ()
  endif ()
  # done
  message (STATUS "Adding executable ${TARGET_UID}... - done")
endfunction ()

# ----------------------------------------------------------------------------
## @brief Add library built from C++ source code.
#
# @note This function should not be used directly. Instead, it is called
#       by basis_add_library() if the (detected) programming language
#       of the given source code files is @c CXX (i.e., C/C++) and the
#       option @c MEX is not given.
#
# This function adds a library target which builds a library from C++ source
# code files. Refer to the documentation of basis_add_library() for a
# description of the general options for adding a library target.
#
# By default, the BASIS C++ utilities library is added as link dependency.
# If none of the BASIS C++ utilities are used by this target, the option
# NO_BASIS_UTILITIES can be given. To enable this option by default, set the
# variable @c BASIS_UTILITIES to @c FALSE, best in the <tt>Settings.cmake</tt>
# file located in the @c PROJECT_CONFIG_DIR (add such file if missing).
# If the use of the BASIS C++ utilities is disabled by default, the
# @c USE_BASIS_UTILITIES option can be used to enable them for this target
# only. Note that the utilities library is a static library and thus the linker
# would simply not include any of the BASIS utility functions in the final
# binary file if not used. The only advantage of setting @c BASIS_UTILITIES to
# @c FALSE or to always specify @c NO_BASIS_UTILITIES if no target uses the
# utilities is that the BASIS utilities library will not be build in this case.
#
# @param [in] TARGET_NAME Name of build target.
# @param [in] ARGN        This argument list is parsed and the following
#                         arguments are extracted. All other arguments are
#                         considered to be source code files and simply
#                         passed on to CMake's add_library() command.
# @par
# <table border=0>
#   <tr>
#     @tp <b>STATIC</b>|<b>SHARED</b>|<b>MODULE</b> @endtp
#     <td>Type of the library. (default: @c SHARED if @c BUILD_SHARED_LIBS
#         evaluates to true or @c STATIC otherwise)</td>
#   </tr>
#   <tr>
#     @tp @b COMPONENT name @endtp
#     <td>Name of component as part of which this library will be installed
#         if either the @c RUNTIME_INSTALL_DIRECTORY or
#         @c LIBRARY_INSTALL_DIRECTORY property is not "none". Used only if
#         either @p RUNTIME_COMPONENT or @p LIBRARY_COMPONENT not specified.
#         (default: see @p RUNTIME_COMPONENT and @p LIBRARY_COMPONENT)</td>
#   </tr>
#   <tr>
#     @tp @b DESTINATION dir @endtp
#     <td>Installation directory for runtime and library component relative
#         to @c CMAKE_INSTALL_PREFIX. Used only if either @p RUNTIME_DESTINATION
#         or @p LIBRARY_DESTINATION not specified. If "none" (case-insensitive)
#         is given as argument, no default installation rules are added.
#         (default: see @p RUNTIME_DESTINATION and @p LIBRARY_DESTINATION)</td>
#   </tr>
#   <tr>
#     @tp @b LIBRARY_COMPONENT name @endtp
#     <td>Name of component as part of which import/static library will be intalled
#         if @c LIBRARY_INSTALL_DIRECTORY property is not "none".
#         (default: @c COMPONENT if specified or @c BASIS_LIBRARY_COMPONENT otherwise)</td>
#   </tr>
#   <tr>
#     @tp @b LIBRARY_DESTINATION dir @endtp
#     <td>Installation directory of the library component relative to
#         @c CMAKE_INSTALL_PREFIX. If "none" (case-insensitive) is given as argument,
#         no installation rule for the library component is added.
#         (default: @c INSTALL_ARCHIVE_DIR)</td>
#   </tr>
#   <tr>
#     @tp @b RUNTIME_COMPONENT name @endtp
#     <td>Name of component as part of which runtime library will be installed
#         if @c RUNTIME_INSTALL_DIRECTORY property is not "none".
#         (default: @c COMPONENT if specified or @c BASIS_RUNTIME_COMPONENT otherwise)</td>
#   </tr>
#   <tr>
#     @tp @b RUNTIME_DESTINATION dir @endtp
#     <td>Installation directory of the runtime component relative to
#         @c CMAKE_INSTALL_PREFIX. If "none" (case-insensitive) is given as argument,
#         no installation rule for the runtime library is added.
#         (default: @c INSTALL_LIBRARY_DIR on Unix or @c INSTALL_RUNTIME_DIR Windows)</td>
#   </tr>
#   <tr>
#     @tp @b [NO]EXPORT @endtp
#     <td>Whether to export this target. (default: @c TRUE)</td>
#   </tr>
#   <tr>
#     @tp @b NO_BASIS_UTILITIES @endtp
#     <td>Specify that the BASIS utilities are not used by this executable and hence
#         no link dependency on the BASIS utilities library shall be added.
#         (default: @c NOT BASIS_UTILITIES)</td>
#   </tr>
#   <tr>
#     @tp @b USE_BASIS_UTILITIES @endtp
#     <td>Specify that the BASIS utilities are used and required by this executable
#         and hence a link dependency on the BASIS utilities library shall be added.
#         (default: @c BASIS_UTILITIES)</td>
#   </tr>
# </table>
#
# @returns Adds library target using CMake's add_library() command.
#
# @sa basis_add_library()
function (basis_add_library_target TARGET_NAME)
  # On UNIX-based systems setting the VERSION property only creates
  # annoying files with the version string as suffix.
  # Moreover, MEX-files may NEVER have a suffix after the MEX extension!
  # Otherwise, the MATLAB Compiler when using the symbolic link
  # without this suffix will create code that fails on runtime
  # with an .auth file missing error.
  #
  # Thus, do NOT set VERSION and SOVERSION properties on library targets!

  # check target name
  basis_check_target_name (${TARGET_NAME})
  basis_make_target_uid (TARGET_UID "${TARGET_NAME}")
  # parse arguments
  CMAKE_PARSE_ARGUMENTS (
    ARGN
      "STATIC;SHARED;MODULE;USE_BASIS_UTILITIES;NO_BASIS_UTILITIES;EXPORT;NOEXPORT"
      "COMPONENT;RUNTIME_COMPONENT;LIBRARY_COMPONENT;DESTINATION;RUNTIME_DESTINATION;LIBRARY_DESTINATION"
      ""
    ${ARGN}
  )
  set (SOURCES ${ARGN_UNPARSED_ARGUMENTS})
  basis_set_flag (ARGN EXPORT ${BASIS_EXPORT_DEFAULT})
  if (ARGN_USE_BASIS_UTILITIES AND ARGN_NO_BASIS_UTILITIES)
    message (FATAL_ERROR "Target ${TARGET_UID}: Options USE_BASIS_UTILITIES and NO_BASIS_UTILITIES are mutually exclusive!")
  endif ()
  if (ARGN_USE_BASIS_UTILITIES)
    set (USES_BASIS_UTILITIES TRUE)
  elseif (ARGN_NO_BASIS_UTILITIES)
    set (USES_BASIS_UTILITIES FALSE)
  else ()
    set (USES_BASIS_UTILITIES ${BASIS_UTILITIES})
  endif ()
  # IS_TEST flag
  basis_sanitize_for_regex (RE "${PROJECT_TESTING_DIR}")
  if (CMAKE_CURRENT_SOURCE_DIR MATCHES "^${RE}")
    set (IS_TEST TRUE)
  else ()
    set (IS_TEST FALSE)
  endif ()
  # library type
  if (NOT ARGN_SHARED AND NOT ARGN_STATIC AND NOT ARGN_MODULE)
    if (BUILD_SHARED_LIBS)
      set (ARGN_SHARED TRUE)
    else ()
      set (ARGN_STATIC TRUE)
    endif ()
  endif ()
  set (TYPE)
  if (ARGN_STATIC)
    if (TYPE)
      message (FATAL_ERROR "More than one library type specified for target ${TARGET_UID}!")
    endif ()
    set (TYPE "STATIC")
  endif ()
  if (ARGN_SHARED)
    if (TYPE)
      message (FATAL_ERROR "More than one library type specified for target ${TARGET_UID}!")
    endif ()
    set (TYPE "SHARED")
  endif ()
  if (ARGN_MODULE)
    if (TYPE)
      message (FATAL_ERROR "More than one library type specified for target ${TARGET_UID}!")
    endif ()
    set (TYPE "MODULE")
  endif ()
  string (TOLOWER "${TYPE}" type)
  # status message
  message (STATUS "Adding ${type} library ${TARGET_UID}...")
  # installation component
  if (ARGN_COMPONENT)
    if (NOT ARGN_RUNTIME_COMPONENT)
      set (ARGN_RUNTIME_COMPONENT "${ARGN_COMPONENT}")
    endif ()
    if (NOT ARGN_LIBRARY_COMPONENT)
      set (ARGN_LIBRARY_COMPONENT "${ARGN_COMPONENT}")
    endif ()
  endif ()
  if (NOT ARGN_RUNTIME_COMPONENT)
    set (ARGN_RUNTIME_COMPONENT "${BASIS_RUNTIME_COMPONENT}")
  endif ()
  if (NOT ARGN_RUNTIME_COMPONENT)
    set (ARGN_RUNTIME_COMPONENT "Unspecified")
  endif ()
  if (NOT ARGN_LIBRARY_COMPONENT)
    set (ARGN_LIBRARY_COMPONENT "${BASIS_LIBRARY_COMPONENT}")
  endif ()
  if (NOT ARGN_LIBRARY_COMPONENT)
    set (ARGN_LIBRARY_COMPONENT "Unspecified")
  endif ()
  # installation directories
  if (ARGN_DESTINATION)
    if (NOT ARGN_STATIC AND NOT ARGN_RUNTIME_DESTINATION)
      set (ARGN_RUNTIME_DESTINATION "${ARGN_DESTINATION}")
    endif ()
    if (NOT ARGN_LIBRARY_DESTINATION)
      set (ARGN_LIBRARY_DESTINATION "${ARGN_DESTINATION}")
    endif ()
  endif ()
  if (NOT ARGN_RUNTIME_DESTINATION)
    set (ARGN_RUNTIME_DESTINATION "${INSTALL_RUNTIME_DIR}")
  endif ()
  if (NOT ARGN_LIBRARY_DESTINATION)
    set (ARGN_LIBRARY_DESTINATION "${INSTALL_LIBRARY_DIR}")
  endif ()
  if (ARGN_STATIC OR ARGN_RUNTIME_DESTINATION MATCHES "^[nN][oO][nN][eE]$")
    set (ARGN_RUNTIME_DESTINATION)
  endif ()
  if (ARGN_LIBRARY_DESTINATION MATCHES "^[nN][oO][nN][eE]$")
    set (ARGN_LIBRARY_DESTINATION)
  endif ()
  # configure (.in) source files
  basis_configure_sources (SOURCES ${SOURCES})
  # add library target
  add_library (${TARGET_UID} ${TYPE} ${SOURCES})
  basis_make_target_uid (HEADERS_TARGET headers)
  if (TARGET ${HEADERS_TARGET})
    add_dependencies (${TARGET_UID} ${HEADERS_TARGET})
  endif ()
  basis_get_target_name (OUTPUT_NAME ${TARGET_UID})
  set_target_properties (${TARGET_UID} PROPERTIES BASIS_TYPE "${TYPE}_LIBRARY" LANGUAGE "CXX" OUTPUT_NAME "${OUTPUT_NAME}")
  # output directory
  if (IS_TEST)
    set_target_properties (
      ${TARGET_UID}
      PROPERTIES
        RUNTIME_OUTPUT_DIRECTORY "${TESTING_RUNTIME_DIR}"
        LIBRARY_OUTPUT_DIRECTORY "${TESTING_LIBRARY_DIR}"
        ARCHIVE_OUTPUT_DIRECTORY "${TESTING_ARCHIVE_DIR}"
    )
  else ()
    set_target_properties (
      ${TARGET_UID}
      PROPERTIES
        RUNTIME_OUTPUT_DIRECTORY "${BINARY_RUNTIME_DIR}"
        LIBRARY_OUTPUT_DIRECTORY "${BINARY_LIBRARY_DIR}"
        ARCHIVE_OUTPUT_DIRECTORY "${BINARY_ARCHIVE_DIR}"
    )
  endif ()
  # installation directory
  # these properties are used by basis_get_target_location() in particular
  set_target_properties (
    ${TARGET_UID}
    PROPERTIES
      RUNTIME_INSTALL_DIRECTORY "${ARGN_RUNTIME_DESTINATION}"
      LIBRARY_INSTALL_DIRECTORY "${ARGN_LIBRARY_DESTINATION}"
      ARCHIVE_INSTALL_DIRECTORY "${ARGN_LIBRARY_DESTINATION}"
  )
  # link to BASIS utilities
  if (USES_BASIS_UTILITIES)
    basis_target_link_libraries (.${TARGET_UID} basis)
  else ()
    set_target_properties (${TARGET_UID} PROPERTIES BASIS_UTILITIES FALSE)
  endif ()
  # export
  set (EXPORT_OPT)
  if (EXPORT)
    basis_add_export_target (EXPORT_OPT ${TARGET_UID} "${IS_TEST}" ${ARGN_RUNTIME_DESTINATION} ${ARGN_LIBRARY_DESTINATION})
  endif ()
  # installation
  set (DESTINATION_OPTS)
  if (IS_TEST)
    # TODO At the moment, no tests are installed. Once there is a way to
    #      install selected tests, the shared libraries they depend on
    #      need to be installed as well.
  else ()
    if (ARGN_RUNTIME_DESTINATION)
      list (APPEND DESTINATION_OPTS
        RUNTIME
          DESTINATION "${ARGN_RUNTIME_DESTINATION}"
          COMPONENT   "${ARGN_RUNTIME_COMPONENT}"
      )
    endif ()
    if (ARGN_LIBRARY_DESTINATION)
      list (APPEND DESTINATION_OPTS
        LIBRARY
          DESTINATION "${ARGN_LIBRARY_DESTINATION}"
          COMPONENT   "${ARGN_LIBRARY_COMPONENT}"
        ARCHIVE
          DESTINATION "${ARGN_LIBRARY_DESTINATION}"
          COMPONENT   "${ARGN_LIBRARY_COMPONENT}"
      )
    endif ()
  endif ()
  if (DESTINATION_OPTS)
    install (TARGETS ${TARGET_UID} ${EXPORT_OPT} ${DESTINATION_OPTS})
  endif ()
  # done
  message (STATUS "Adding ${type} library ${TARGET_UID}... - done")
endfunction ()

# ----------------------------------------------------------------------------
## @brief Add script library target.
#
# @note This function should not be used directly. Instead, it is called
#       by basis_add_library() if the (detected) programming language
#       of the given source code files is neither @c CXX (i.e., C/C++) nor
#       @c MATLAB.
#
# This function adds a build target for libraries which are a collection of
# one or more modules written in a scripting language. The relative paths
# of the modules relative to the library's @p SOURCE_DIRECTORY property are
# preserved. This is important for the most widely used scripting languages
# such as Python, Perl, or MATLAB, where the file path relative to the
# package root directory defines the package namespace.
#
# A custom CMake build target with the following properties is added by this
# function to the build system. These properties are used by
# basis_build_script_library() to generate a build script written in CMake
# code which is executed by a custom CMake command. Before the invokation of
# basis_build_script_library(), the target properties can be modified using
# basis_set_target_properties().
#
# @note Custom BASIS build targets are finalized by BASIS using basis_project_end(),
#       i.e., the end of the root CMake configuration file of the (sub-)project.
#
# @par Properties on script library targets
# <table border=0>
#   <tr>
#     @tp @b BASIS_TYPE @endtp
#     <td>Read-only property with value "SCRIPT_LIBRARY" for script library targets.</td>
#   </tr>
#   <tr>
#     @tp @b BASIS_UTILITIES @endtp
#     <td>Whether the BASIS utilities are used by any module of this library.
#         For the supported scripting languages for which BASIS utilities are
#         implemented, BASIS will in most cases automatically detect whether
#         these utilities are used by a module or not. Otherwise, set this
#         property manually or use either the @p USE_BASIS_UTILITIES or the
#         @p NO_BASIS_UTILITIES option when adding the library target.
#         (default: auto-detected or @c UNKNOWN)</td>
#   </tr>
#   <tr>
#     @tp @b BINARY_DIRECTORY @endtp
#     <td>Build tree directory of this target. (default: @c CMAKE_CURRENT_BINARY_DIR)</td>
#   </tr>
#   <tr>
#     @tp @b COMPILE @endtp
#     <td>Whether to compile the library, respectively, it's modules if the
#         programming language allows such pre-compilation as in case of Python,
#         for example. If @c TRUE, only the compiled files are installed.
#         (default: @c BASIS_COMPILE_SCRIPTS)</td>
#   </tr>
#   <tr>
#     @tp @b SCRIPT_DEFINITIONS @endtp
#     <td>CMake code which is evaluated after the inclusion of the default script
#         configuration files. This code can be used to set the replacement text of the
#         CMake variables ("@VAR@" patterns) used in the source files.
#         See <a href="http://opensource.andreasschuh.com/cmake-basis/standard/scripttargets.html#script-configuration">
#         Build System Standard</a> for details. (default: "")</td>
#   </tr>
#   <tr>
#     @tp @b SCRIPT_DEFINITIONS_FILE @endtp
#     <td>CMake script file with compile definitions, also referred to as script
#         configuration file. The named files are included after the default BASIS
#         script configuration and before the @c SCRIPT_DEFINITIONS code is being
#         evaluated. (default: @c BINARY_CONFIG_DIR/ScriptConfig.cmake)</td>
#   </tr>
#   <tr>
#     @tp @b EXPORT @endtp
#     <td>Whether to export this build target in which case an import library
#         target is added to the custom exports file with the path to the
#         built/installed modules set as @c IMPORT_LOCATION. (default: @c TRUE)</td>
#   </tr>
#   <tr>
#     @tp @b LANGUAGE @endtp
#     <td>Read-only property of programming language of modules in uppercase letters.
#         (default: see @p LANGUAGE argument)</td>
#   </tr>
#   <tr>
#     @tp @b LIBRARY_COMPONENT @endtp
#     <td>Name of component as part of which this library is installed if
#         @c LIBRARY_INSTALL_DIRECTORY is not set to "none".
#         (default: see @p COMPONENT argument)</td>
#   </tr>
#   <tr>
#     @tp @b LIBRARY_INSTALL_DIRECTORY @endtp
#     <td>Installation directory of library configured for use in installation tree
#         relative to @c CMAKE_INSTALL_PREFIX. Set to "none" (case-insensitive) to skip the
#         addition of an installation rule.
#         (default: <tt>INSTALL_&lt;LANGUAGE&gt;_LIBRARY_DIR</tt> if defined or
#         @c INSTALL_LIBRARY_DIR otherwise)</td>
#   </tr>
#   <tr>
#     @tp @b LIBRARY_OUTPUT_DIRECTORY @endtp
#     <td>Output directory of library configured for use within the build tree.
#         (default: <tt>BINARY_&lt;LANGUAGE&gt;_LIBRARY_DIR</tt> if defined or
#         @c BINARY_LIBRARY_DIR otherwise)</td>
#   </tr>
#   <tr>
#     @tp @b LINK_DEPENDS @endtp
#     <td>Paths or target names of script modules and libraries used by this script.
#         For each listed build target, a dependency is added between this
#         library target and the named build targets. Use basis_target_link_libraries()
#         to add additional link dependencies. Further note that if this library is
#         a link dependency of an executable script added by basis_add_executable()
#         (i.e., basis_add_script() actually), the link dependencies of this library
#         are inherited by the executable script.
#         (default: BASIS utilities module if used or empty list otherwise)</td>
#   </tr>
#   <tr>
#     @tp @b PREFIX @endtp
#     <td>Common module prefix. The given directory path is appended to both
#         @c LIBRAR_OUTPUT_DIRECTORY and @c LIBRARY_INSTALL_DIRECTORY and can,
#         for example, be used to install modules of a Python package as part of
#         another Python package, where @c LIBRARY_OUTPUT_DIRECTORY or
#         @c LIBRARY_INSTALL_DIRECTORY, respectively, is the directory of the
#         main package which is added to the @c PYTHONPATH. Possibly missing
#         __init__.py files in case of Python are generated by the _initpy target
#         which is automatically added by BASIS in that case and further added to
#         the dependencies of this library target.
#         (default: @c PROJECT_NAMESPACE_PYTHON if @c LANGUAGE is @c PYTHON with
#         periods (.) replaced by slashes (/), @c PROJECT_NAMESPACE_PERL if
#         @c LANGUAGE is @c PERL with <tt>::</tt> replaced by slashes (/),
#         and "" otherwise)</td>
#   </tr>
#   <tr>
#     @tp @b SOURCE_DIRECTORY @endtp
#     <td>Source directory of this target. This directory is in particular
#         used to convert the paths of the given source files to relative paths.
#         The built modules within the build and installation tree will have the
#         same relative path (relative to the @c LIBRARY_OUTPUT_DIRECTORY or
#         @c LIBRARY_INSTALL_DIRECTORY, respectively).
#         (default: @c CMAKE_CURRENT_SOURCE_DIR)</td>
#   </tr>
#   <tr>
#     @tp @b SOURCES @endtp
#     <td>Read-only property which lists the source files of this library.
#         Note that the first element in this list actually names a directory
#         in the build, the one where the build script for this target is located
#         instead of a source file and thus should be ignored.</td>
#   </tr>
# </table>
#
# @attention Properties documented as read-only must not be modified.
#
# @param [in] TARGET_NAME Name of build target.
# @param [in] ARGN        The remaining arguments are parsed and the following
#                         arguments extracted. All unparsed arguments are treated
#                         as the module files of the script library.
# @par
# <table border=0>
#   <tr>
#     @tp @b COMPONENT name @endtp
#     <td>Name of installation component as part of which this library is being
#         installed if the @c LIBRARY_INSTALL_DIRECTORY property is not "none".
#         (default: @c BASIS_LIBRARY_COMPONENT)</td>
#   </tr>
#   <tr>
#     @tp @b DESTINATION dir @endtp
#     <td>Installation directory for library relative to @c CMAKE_INSTALL_PREFIX.
#         If an absolute path is given as argument, it is made relative to the
#         configured installation prefix. (default: @c INSTALL_LIBRARY_DIR)</td>
#   </tr>
#   <tr>
#     @tp @b LANGUAGE lang @endtp
#     <td>Programming language in which modules are written (case-insensitive).
#         If not specified, the programming language is derived from the file name
#         extensions of the source files and the shebang directive on the first line
#         of each module if any. If the programming language could not be detected
#         automatically, the @c LANGUAGE property is set to @c UNKNOWN. Note that
#         for script library targets, the library may still be built correctly
#         even if the scripting language was not recognized. The automatic detection
#         whether the BASIS utilities are used and required will fail, however.
#         In this case, specify the programming language using this option.
#         (default: auto-detected or @c UNKNOWN)</td>
#   </tr>
#   <tr>
#     @tp @b [NO]EXPORT @endtp
#     <td>Whether to export this target. (default: @c TRUE)</td>
#   </tr>
#   <tr>
#     @tp @b NO_BASIS_UTILITIES @endtp
#     <td>Specify that the BASIS utilities are not used by this library. If the
#         programming language of the modules is known and BASIS utilities are
#         available for this language, BASIS will in most cases automatically
#         detect whether these utilities are used by any module of this library.
#         Use this option to skip this check in the case that no module makes
#         use of the BASIS utilities.</td>
#   </tr>
#   <tr>
#     @tp @b USE_BASIS_UTILITIES @endtp
#     <td>Specify that the BASIS utilities are used and thus required by this library.
#         If the programming language of the modules is known and BASIS utilities are
#         available for this language, BASIS will in most cases automatically
#         detect whether these utilities are used by any module of this library.
#         Use this option to skip this check when it is already known that no module
#         makes use of the BASIS utilities. Note that an error is raised if this option
#         is given, but no BASIS utilities are available for the programming language
#         of this script or if the programming language is unknown, respectively, not
#         detected correctly. In this case, consider the use of the @p LANGUAGE argument.</td>
#   </tr>
# </table>
#
# @returns Adds a custom CMake target with the documented properties. The actual custom
#          command to build the library is added by basis_build_script_library().
#
# @sa basis_add_library()
function (basis_add_script_library TARGET_NAME)
  # check target name
  basis_check_target_name ("${TARGET_NAME}")
  basis_make_target_uid (TARGET_UID "${TARGET_NAME}")
  message (STATUS "Adding script library ${TARGET_UID}...")
  # dump CMake variables for configuration of script
  set (BUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/${TARGET_UID}")
  basis_dump_variables ("${BUILD_DIR}.dir/cache.cmake.tmp")
  # parse arguments
  CMAKE_PARSE_ARGUMENTS (
    ARGN
      "NO_BASIS_UTILITIES;USE_BASIS_UTILITIES;EXPORT;NOEXPORT"
      "COMPONENT;DESTINATION;LANGUAGE"
      ""
    ${ARGN}
  )
  basis_set_flag (ARGN EXPORT ${BASIS_EXPORT_DEFAULT})
  set (SOURCES "${ARGN_UNPARSED_ARGUMENTS}")
  # IS_TEST flag
  basis_sanitize_for_regex (RE "${PROJECT_TESTING_DIR}")
  if (CMAKE_CURRENT_SOURCE_DIR MATCHES "^${RE}")
    set (IS_TEST TRUE)
  else ()
    set (IS_TEST FALSE)
  endif ()
  # check source files
  set (_SOURCES)
  foreach (S IN LISTS SOURCES)
    get_filename_component (S "${S}" ABSOLUTE)
    if (NOT EXISTS "${S}" AND NOT S MATCHES "\\.in$" AND EXISTS "${S}.in" AND NOT IS_DIRECTORY "${S}.in")
      set (S "${S}.in")
    elseif (IS_DIRECTORY "${S}")
      message (FATAL_ERROR "Target ${TARGET_UID}: Directory ${S} given where file name expected!")
    endif ()
    if (NOT EXISTS "${S}")
      string (REGEX REPLACE "\\.in$" "" S "${S}")
      message (FATAL_ERROR "Target ${TARGET_UID}: Source file ${S}[.in] does not exist!")
    endif ()
    list (APPEND _SOURCES "${S}")
  endforeach ()
  if (NOT _SOURCES)
    message (FATAL_ERROR "Target ${TARGET_UID}: No source files specified!")
  endif ()
  set (SOURCES "${_SOURCES}")
  unset (_SOURCES)
  # auto-detect programming language (may be as well UNKNOWN)
  string (TOUPPER "${ARGN_LANGUAGE}" ARGN_LANGUAGE)
  if (NOT ARGN_LANGUAGE)
    basis_get_source_language (ARGN_LANGUAGE ${SOURCES})
    if (ARGN_LANGUAGE MATCHES "AMBIGUOUS|UNKNOWN")
      message (FATAL_ERROR "Target ${TARGET_UID}: Failed to determine programming"
                           " language of modules! Make sure that all modules are"
                           " written in the same language and that the used programming"
                           " language is supported by BASIS, i.e., either Python (Jython),"
                           " Perl, Bash, or MATLAB. Otherwise, try to specify the language"
                           " explicitly using the LANGUAGE option.")
    endif ()
  endif ()
  # output directory
  if (IS_TEST)
    if (DEFINED TESTING_${ARGN_LANGUAGE}_LIBRARY_DIR)
      set (OUTPUT_DIRECTORY "${TESTING_${ARGN_LANGUAGE}_LIBRARY_DIR}")
    else ()
      set (OUTPUT_DIRECTORY "${TESTING_LIBRARY_DIR}")
    endif ()
  else ()
    if (DEFINED BINARY_${ARGN_LANGUAGE}_LIBRARY_DIR)
      set (OUTPUT_DIRECTORY "${BINARY_${ARGN_LANGUAGE}_LIBRARY_DIR}")
    else ()
      set (OUTPUT_DIRECTORY "${BINARY_LIBRARY_DIR}")
    endif ()
  endif ()
  # installation component
  if (NOT ARGN_COMPONENT)
    set (ARGN_COMPONENT "${BASIS_LIBRARY_COMPONENT}")
  endif ()
  if (NOT ARGN_COMPONENT)
    set (ARGN_COMPONENT "Unspecified")
  endif ()
  # installation directory
  if (IS_TEST)
    if (ARGN_DESTINATION)
      message (WARNING "Target ${TARGET_UID} is a library used for testing only."
                       " Installation to the specified directory will be skipped.")
      set (ARGN_DESTINATION)
    endif ()
  else ()
    if (ARGN_DESTINATION)
      if (IS_ABSOLUTE "${ARGN_DESTINATION}")
        file (RELATIVE_PATH ARGN_DESTINATION "${CMAKE_INSTALL_PREFIX}" "${ARGN_DESTINATION}")
      endif ()
    else ()
      if (DEFINED INSTALL_${ARGN_LANGUAGE}_LIBRARY_DIR)
        set (ARGN_DESTINATION "${INSTALL_${ARGN_LANGUAGE}_LIBRARY_DIR}")
      else ()
        set (ARGN_DESTINATION "${INSTALL_LIBRARY_DIR}")
      endif ()
    endif ()
  endif ()
  # common module prefix
  if (ARGN_LANGUAGE MATCHES "^([JP]YTHON|PERL|MATLAB|BASH)$")
    basis_library_prefix (PREFIX ${ARGN_LANGUAGE})
  else ()
    set (PREFIX)
  endif ()
  # script configuration ("compile definitions")
  if (EXISTS "${BINARY_CONFIG_DIR}/ScriptConfig.cmake")
    set (CONFIG_FILE "${BINARY_CONFIG_DIR}/ScriptConfig.cmake")
  else ()
    set (CONFIG_FILE)
  endif ()
  # auto-detect use of BASIS utilities
  if (ARGN_LANGUAGE MATCHES "[JP]YTHON")
    set (UTILITIES_LANGUAGE "PYTHON")
  else ()
    set (UTILITIES_LANGUAGE "${ARGN_LANGUAGE}")
  endif ()
  if (ARGN_USE_BASIS_UTILITIES)
    if (NOT BASIS_UTILITIES_ENABLED MATCHES "${UTILITIES_LANGUAGE}")
      message (FATAL_ERROR "Target ${TARGET_UID} requires the BASIS utilities for ${UTILITIES_LANGUAGE}"
                           " but BASIS was either build without the build of these utilities enabled"
                           " or no utilities for this programming language are implemented. Remove the"
                           " USE_BASIS_UTILITIES option if no BASIS utilities are used by the modules"
                           " of the library or specify the correct programming language if it was not"
                           " detected correctly.")
    endif ()
    set (USES_BASIS_UTILITIES TRUE)
  elseif (BASIS_UTILITIES AND NOT ARGN_NO_BASIS_UTILITIES AND NOT UTILITIES_LANGUAGE MATCHES "UNKNOWN")
    set (USES_BASIS_UTILITIES FALSE)
    foreach (M IN LISTS SOURCES)
      basis_utilities_check (USES_BASIS_UTILITIES "${M}" ${UTILITIES_LANGUAGE})
      if (USES_BASIS_UTILITIES)
        break ()
      endif ()
    endforeach ()
  else ()
    set (USES_BASIS_UTILITIES FALSE)
  endif ()
  # add custom target
  add_custom_target (${TARGET_UID} ALL SOURCES ${SOURCES})
  set_target_properties (
    ${TARGET_UID}
    PROPERTIES
      LANGUAGE                  "${ARGN_LANGUAGE}"
      BASIS_TYPE                "SCRIPT_LIBRARY"
      BASIS_UTILITIES           "${USES_BASIS_UTILITIES}"
      BUILD_DIRECTORY           "${BUILD_DIR}"
      SOURCE_DIRECTORY          "${CMAKE_CURRENT_SOURCE_DIR}"
      BINARY_DIRECTORY          "${CMAKE_CURRENT_BINARY_DIR}"
      LIBRARY_OUTPUT_DIRECTORY  "${OUTPUT_DIRECTORY}"
      LIBRARY_INSTALL_DIRECTORY "${ARGN_DESTINATION}"
      LIBRARY_COMPONENT         "${BASIS_LIBRARY_COMPONENT}"
      PREFIX                    "${PREFIX}"
      SCRIPT_DEFINITIONS        ""
      SCRIPT_DEFINITIONS_FILE   "${CONFIG_FILE}"
      LINK_DEPENDS              ""
      EXPORT                    "${EXPORT}"
      COMPILE                   "${BASIS_COMPILE_SCRIPTS}"
      TEST                      "${IS_TEST}"
  )
  # link to BASIS utilities
  if (USES_BASIS_UTILITIES)
    basis_target_link_libraries (.${TARGET_UID} basis)
    if (BASIS_DEBUG)
      message ("** Target ${TARGET_UID} uses the BASIS utilities for ${UTILITIES_LANGUAGE}.")
    endif ()
  endif ()
  # add target to list of targets
  basis_set_project_property (APPEND PROPERTY TARGETS "${TARGET_UID}")
  message (STATUS "Adding script library ${TARGET_UID}... - done")
endfunction ()

# ============================================================================
# custom build commands
# ============================================================================

# ----------------------------------------------------------------------------
## @brief Finalize custom targets by adding the missing build commands.
#
# This function is called by basis_project_end() in order to finalize the
# addition of the custom build targets such as, for example, build targets
# for the build of executable scripts, Python packages, MATLAB Compiler
# executables and shared libraries, and MEX-files.
#
# @returns Generates the CMake build scripts and adds custom build commands
#          and corresponding targets for the execution of these scripts.
#
# @sa basis_build_script()
# @sa basis_build_script_library()
# @sa basis_build_mcc_target()
# @sa basis_build_mex_file()
function (basis_finalize_targets)
  basis_get_project_property (TARGETS PROPERTY TARGETS)
  foreach (TARGET_UID ${TARGETS})
    if (NOT TARGET _${TARGET_UID})
      get_target_property (BASIS_TYPE ${TARGET_UID} BASIS_TYPE)
      if (BASIS_TYPE MATCHES "^EXECUTABLE$|^(SHARED|MODULE)_LIBRARY$")
        if (BASIS_INSTALL_RPATH AND NOT CMAKE_SKIP_RPATH)
          # Only if BASIS is allowed to take care of the INSTALL_RPATH property
          # and the use of this property was not disabled by the project
          basis_set_target_install_rpath (${TARGET_UID})
        endif ()
      elseif (BASIS_TYPE MATCHES "SCRIPT_LIBRARY")
        basis_build_script_library (${TARGET_UID})
      elseif (BASIS_TYPE MATCHES "SCRIPT")
        basis_build_script (${TARGET_UID})
      elseif (BASIS_TYPE MATCHES "MEX")
        basis_build_mex_file (${TARGET_UID})
      elseif (BASIS_TYPE MATCHES "MCC")
        basis_build_mcc_target (${TARGET_UID})
      endif ()
    endif ()
  endforeach ()
endfunction ()

# ----------------------------------------------------------------------------
## @brief Set INSTALL_RPATH property of executable or shared library target.
#
# This function sets the @c INSTALL_RPATH property of a specified executable or
# shared library target using the @c LINK_DEPENDS obtained using the
# basis_get_target_link_libraries() function. It determines the installation
# location of each dependency using the basis_get_target_location() function.
#
# @returns Sets the @c INSTALL_RPATH property of the specified target.
#
# @sa basis_get_target_link_libraries()
function (basis_set_target_install_rpath TARGET_NAME)
  basis_get_target_uid (TARGET_UID "${TARGET_NAME}")
  if (BASIS_VERBOSE)
    message (STATUS "Setting INSTALL_RPATH property of ${TARGET_UID}...")
  endif ()
  if (NOT TARGET "${TARGET_UID}")
    message (FATAL_ERROR "Unknown target: ${TARGET_UID}")
  endif ()
  if (BASIS_DEBUG)
    message ("** basis_set_target_install_rpath():")
    message ("**    TARGET_NAME:   ${TARGET_UID}")
  endif ()
  if (CMAKE_HOST_APPLE)
    set (ORIGIN "@loader_path")
  else ()
    set (ORIGIN "\$ORIGIN")
  endif ()
  # always prefer libraries located within the same directory
  set (INSTALL_RPATH "${ORIGIN}/.")
  # common default RPATH (rarely used)
  if (CMAKE_INSTALL_RPATH)
    set (INSTALL_RPATH "${INSTALL_RPATH};${CMAKE_INSTALL_RPATH}")
  endif ()
  # get location of target used to make paths relative to this $ORIGIN
  basis_get_target_location (TARGET_LOCATION ${TARGET_UID} POST_INSTALL_PATH)
  # directories of external projects belonging to same bundle which
  # were added using [basis_]link_directories() command
  basis_get_project_property (BUNDLE_LINK_DIRS PROPERTY BUNDLE_LINK_DIRS)
  foreach (LINK_DIR ${BUNDLE_LINK_DIRS})
    list (FIND CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES "${LINK_DIR}" IDX)
    if (IDX EQUAL -1)
      if (BASIS_DEBUG AND BASIS_VERBOSE)
        message ("**    BUNDLE_LINK_DIR: ${LINK_DIR}")
      endif ()
      basis_get_relative_path (RPATH "${TARGET_LOCATION}" "${LINK_DIR}")
      list (APPEND INSTALL_RPATH "${ORIGIN}/${RPATH}")
    endif ()
  endforeach ()
  # directories of link libraries
  #
  # only the libraries of this project and targets imported
  # from other projects which are part of the same bundle
  basis_get_target_link_libraries (LINK_DEPENDS ${TARGET_UID})
  if (BASIS_DEBUG AND BASIS_VERBOSE)
    message ("**    LINK_DEPENDS: [${LINK_DEPENDS}]")
  endif ()
  foreach (LINK_DEPEND ${LINK_DEPENDS})
    set (DEPEND_LOCATION)
    if (TARGET "${LINK_DEPEND}")
      basis_get_target_property (BUNDLED  ${LINK_DEPEND} BUNDLED)
      basis_get_target_property (IMPORTED ${LINK_DEPEND} IMPORTED)
      if (NOT IMPORTED OR BUNDLED)
        basis_get_target_location (DEPEND_LOCATION ${LINK_DEPEND} POST_INSTALL_PATH)
        if (BASIS_DEBUG AND BASIS_VERBOSE)
          message ("**    LOCATION(${LINK_DEPEND}): ${DEPEND_LOCATION}")
        endif ()
      endif ()
    elseif (IS_ABSOLUTE "${LINK_DEPEND}")
      if (IS_DIRECTORY "${LINK_DEPEND}")
        set (DEPEND_LOCATION "${LINK_DEPEND}")
      else ()
        get_filename_component (DEPEND_LOCATION "${LINK_DEPEND}" PATH)
      endif ()
      list (FIND BUNDLE_LINK_DIRS "${DEPEND_LOCATION}" IDX)
      if (IDX EQUAL -1)
        set (DEPEND_LOCATION)
      endif ()
    endif ()
    if (DEPEND_LOCATION)
      list (FIND CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES "${DEPEND_LOCATION}" IDX)
      if (IDX EQUAL -1)
        basis_get_relative_path (RPATH "${TARGET_LOCATION}" "${DEPEND_LOCATION}")
        list (APPEND INSTALL_RPATH "${ORIGIN}/${RPATH}")
      endif ()
    endif ()
  endforeach ()
  # remove duplicates
  if (INSTALL_RPATH)
    list (REMOVE_DUPLICATES INSTALL_RPATH)
  endif ()
  # set INSTALL_RPATH property
  set_target_properties (${TARGET_UID} PROPERTIES INSTALL_RPATH "${INSTALL_RPATH}")
  if (BASIS_DEBUG)
    message ("**    INSTALL_RPATH: [${INSTALL_RPATH}]")
  endif ()
  if (BASIS_VERBOSE)
    message (STATUS "Setting INSTALL_RPATH property of ${TARGET_UID}... - done")
  endif ()
endfunction ()

# ----------------------------------------------------------------------------
## @brief Add custom command for build of single script.
#
# This function is called by basis_finalize_targets() which in turn is called
# by basis_project_end(), i.e., the end of the root CMake configuration file
# of the (sub-)project.
#
# @param [in] TARGET_UID Name/UID of custom target added by basis_add_script().
#
# @sa basis_add_script()
function (basis_build_script TARGET_UID)
  # does this target exist ?
  basis_get_target_uid (TARGET_UID "${TARGET_UID}")
  if (NOT TARGET "${TARGET_UID}")
    message (FATAL_ERROR "Unknown build target: ${TARGET_UID}")
  endif ()
  if (BASIS_VERBOSE)
    message (STATUS "Adding build command for target ${TARGET_UID}...")
  endif ()
  # get target properties
  basis_get_target_link_libraries (LINK_DEPENDS ${TARGET_UID}) # paths of script modules/packages
                                                               # including BASIS utilities if used
  set (
    PROPERTIES
      LANGUAGE                 # programming language of script
      BASIS_TYPE               # must match "^SCRIPT_(EXECUTABLE|LIBEXEC|MODULE)$"
      BUILD_DIRECTORY          # CMakeFiles build directory
      SOURCE_DIRECTORY         # CMake source directory
      BINARY_DIRECTORY         # CMake binary directory
      OUTPUT_DIRECTORY         # output directory for built script
      INSTALL_DIRECTORY        # installation directory for built script
      COMPONENT                # installation component
      OUTPUT_NAME              # name of built script including extension (if any)
      PREFIX                   # name prefix
      SUFFIX                   # name suffix (e.g., extension for executable script)
      SCRIPT_DEFINITIONS       # CMake code to set variables used to configure script
      SCRIPT_DEFINITIONS_FILE  # script configuration file
      EXPORT                   # whether this target shall be exported
      COMPILE                  # whether to compile script if applicable
      SOURCES                  # path of script source file
  )
  get_target_property (IS_TEST ${TARGET_UID} TEST) # whether this script is used for testing only
  foreach (PROPERTY ${PROPERTIES})
    get_target_property (${PROPERTY} ${TARGET_UID} ${PROPERTY})
  endforeach ()
  set (EXECUTABLE FALSE)
  set (LIBEXEC    FALSE)
  set (MODULE     FALSE)
  if (BASIS_TYPE MATCHES "^SCRIPT_(EXECUTABLE|LIBEXEC|MODULE)$")
    set (${CMAKE_MATCH_1} TRUE)
    if (LIBEXEC)
      set (EXECUTABLE TRUE)
    endif ()
  else ()
    message (FATAL_ERROR "Target ${TARGET_UID}: Unexpected BASIS_TYPE: ${BASIS_TYPE}")
  endif ()
  if (NOT BINARY_DIRECTORY)
    message (FATAL_ERROR "Target ${TARGET_UID}: Missing BINARY_DIRECTORY property!")
  endif ()
  file (RELATIVE_PATH _relpath "${CMAKE_BINARY_DIR}" "${BINARY_DIRECTORY}")
  if (_relpath MATCHES "^\\.\\./")
    message (FATAL_ERROR "Target ${TARGET_UID}: BINARY_DIRECTORY must be inside of build tree!")
  endif ()
  unset (_relpath)
  if (INSTALL_DIRECTORY AND NOT COMPONENT)
    set (COMPONENT "Unspecified")
  endif ()
  list (GET SOURCES 0 BUILD_DIR) # CMake <3.1 stores path to internal build directory here
  if (BUILD_DIR MATCHES "CMakeFiles")
    list (REMOVE_AT SOURCES 0)
  endif ()
  list (LENGTH SOURCES L)
  if (NOT L EQUAL 1)
    message (FATAL_ERROR "Target ${TARGET_UID}: Expected one element in SOURCES list!"
                         " Have you modified this (read-only) property or is your"
                         " (newer) CMake version not compatible with BASIS?")
  endif ()
  set (SOURCE_FILE "${SOURCES}")
  set (BUILD_DIR   "${BUILD_DIRECTORY}.dir")
  # output directory and name
  if (NOT OUTPUT_NAME)
    basis_get_target_name (OUTPUT_NAME ${TARGET_UID})
  endif ()
  if (PREFIX)
    set (OUTPUT_NAME "${PREFIX}${OUTPUT_NAME}")
  endif ()
  if (SUFFIX)
    set (OUTPUT_NAME "${OUTPUT_NAME}${SUFFIX}")
  endif ()
  if (CMAKE_GENERATOR MATCHES "Visual Studio|Xcode")
    set (OUTPUT_FILE "${BUILD_DIR}/build/${OUTPUT_NAME}")
    set (OUTPUT_DIR  "${OUTPUT_DIRECTORY}/$<CONFIGURATION>")
  elseif (MODULE AND COMPILE)
    set (OUTPUT_FILE "${BUILD_DIR}/build/${OUTPUT_NAME}")
    set (OUTPUT_DIR  "${OUTPUT_DIRECTORY}")
  else ()
    set (OUTPUT_FILE "${OUTPUT_DIRECTORY}/${OUTPUT_NAME}")
    unset (OUTPUT_DIR)
  endif ()
  # arguments of build script
  if (INSTALL_DIRECTORY)
    if (MODULE)
      get_filename_component (SOURCE_NAME "${SOURCE_FILE}" NAME)
      set (INSTALL_FILE "${BUILD_DIR}/install/${SOURCE_NAME}")
      string (REGEX REPLACE "\\.in$" "" INSTALL_FILE "${INSTALL_FILE}")
    else ()
      set (INSTALL_FILE "${BUILD_DIR}/install/${OUTPUT_NAME}")
    endif ()
    set (DESTINATION "${INSTALL_DIRECTORY}")
    if (NOT IS_ABSOLUTE "${DESTINATION}")
      set (DESTINATION "${CMAKE_INSTALL_PREFIX}/${DESTINATION}")
    endif ()
  else ()
    set (INSTALL_FILE)
    set (DESTINATION)
  endif ()
  set (CONFIG_FILES)
  if (EXISTS "${BASIS_SCRIPT_CONFIG_FILE}")
    list (APPEND CONFIG_FILES "${BASIS_SCRIPT_CONFIG_FILE}")
  endif ()
  if (SCRIPT_DEFINITIONS_FILE)
    list (APPEND CONFIG_FILES "${SCRIPT_DEFINITIONS_FILE}")
  endif ()
  if (SCRIPT_DEFINITIONS)
    set (SCRIPT_CONFIG_FILE "${BUILD_DIR}/ScriptConfig.cmake")
    file (WRITE "${SCRIPT_CONFIG_FILE}.tmp" "# DO NOT edit. Automatically generated by BASIS.\n${SCRIPT_DEFINITIONS}\n")
    execute_process (COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${SCRIPT_CONFIG_FILE}.tmp" "${SCRIPT_CONFIG_FILE}")
    file (REMOVE "${SCRIPT_CONFIG_FILE}.tmp")
    list (APPEND CONFIG_FILES "${SCRIPT_CONFIG_FILE}")
  endif ()
  set (CACHE_FILE "${BUILD_DIR}/cache.cmake")
  execute_process (COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${CACHE_FILE}.tmp" "${CACHE_FILE}")
  file (REMOVE "${CACHE_FILE}.tmp")
  set (OPTIONS)
  foreach (FLAG IN ITEMS COMPILE EXECUTABLE)
    if (${FLAG})
      list (APPEND OPTIONS ${FLAG})
    endif ()
  endforeach ()
  # link dependencies - module search paths
  set (BUILD_LINK_DEPENDS)
  set (INSTALL_LINK_DEPENDS)
  foreach (LINK_DEPEND IN LISTS LINK_DEPENDS)
    basis_get_target_uid (UID "${LINK_DEPEND}")
    if (TARGET "${UID}")
      get_target_property (IMPORTED ${UID} IMPORTED)
      get_target_property (BUNDLED  ${UID} BUNDLED)
      if (IMPORTED AND NOT BUNDLED)
        basis_get_target_location (LOCATION "${UID}" ABSOLUTE)
        if (LOCATION)
          list (APPEND BUILD_LINK_DEPENDS   "${LOCATION}")
          list (APPEND INSTALL_LINK_DEPENDS "${LOCATION}")
        else ()
          message (WARNING "Could not determine build tree location of file corresponding to target ${UID}")
        endif ()
      else ()
        basis_get_target_location (LOCATION "${UID}" ABSOLUTE)
        if (LOCATION)
          list (APPEND BUILD_LINK_DEPENDS "${LOCATION}")
        else ()
          message (WARNING "Could not determine build tree location of file corresponding to target ${UID}")
        endif ()
        basis_get_target_property (LINK_DEPEND_IS_TEST "${UID}" TEST)
        if (NOT LINK_DEPEND_IS_TEST)
          basis_get_target_location (LOCATION "${UID}" POST_INSTALL)
          if (LOCATION)
            list (APPEND INSTALL_LINK_DEPENDS "relative ${LOCATION}")
          else ()
            message (WARNING "Could not determine installation location of file corresponding to target ${UID}")
          endif ()
        endif ()
      endif ()
    else ()
      list (APPEND BUILD_LINK_DEPENDS   "${LINK_DEPEND}")
      list (APPEND INSTALL_LINK_DEPENDS "${LINK_DEPEND}")
    endif ()
  endforeach ()
  # prepend own module search paths - if dependencies among own modules
  #                                   not specified or to ensure that
  #                                   these are preferred
  if (LANGUAGE MATCHES "JYTHON")
    if (BINARY_PYTHON_LIBRARY_DIR)
      list (INSERT BUILD_LINK_DEPENDS 0 "${BINARY_PYTHON_LIBRARY_DIR}")
    endif ()
    if (INSTALL_PYTHON_LIBRARY_DIR)
      list (INSERT INSTALL_LINK_DEPENDS 0 "relative ${CMAKE_INSTALL_PREFIX}/${INSTALL_PYTHON_LIBRARY_DIR}")
    endif ()
  endif ()
  if (BINARY_${LANGUAGE}_LIBRARY_DIR)
    list (INSERT BUILD_LINK_DEPENDS 0 "${BINARY_${LANGUAGE}_LIBRARY_DIR}")
  endif ()
  if (INSTALL_${LANGUAGE}_LIBRARY_DIR)
    list (INSERT INSTALL_LINK_DEPENDS 0 "relative ${CMAKE_INSTALL_PREFIX}/${INSTALL_${LANGUAGE}_LIBRARY_DIR}")
  endif ()
  list (INSERT BUILD_LINK_DEPENDS   0 "${BINARY_LIBRARY_DIR}")
  list (INSERT INSTALL_LINK_DEPENDS 0 "relative ${CMAKE_INSTALL_PREFIX}/${INSTALL_LIBRARY_DIR}")
  if (IS_TEST)
    list (INSERT BUILD_LINK_DEPENDS 0 "${TESTING_LIBRARY_DIR}")
  endif ()
  if (BUILD_LINK_DEPENDS)
    list (REMOVE_DUPLICATES BUILD_LINK_DEPENDS)
  endif ()
  if (INSTALL_LINK_DEPENDS)
    list (REMOVE_DUPLICATES INSTALL_LINK_DEPENDS)
  endif ()
  # remove default site-packages directories
  if (LANGUAGE MATCHES "[JP]YTHON|PERL")
    list (REMOVE_ITEM BUILD_LINK_DEPENDS   "${${LANGUAGE}_SITELIB}")
    list (REMOVE_ITEM INSTALL_LINK_DEPENDS "${${LANGUAGE}_SITELIB}")
  endif ()
  # configure build script
  set (BUILD_SCRIPT "${BUILD_DIR}/build.cmake")
  configure_file ("${BASIS_MODULE_PATH}/configure_script.cmake.in" "${BUILD_SCRIPT}" @ONLY)
  # list of all output files
  set (OUTPUT_FILES "${OUTPUT_FILE}")
  if (INSTALL_FILE)
    list (APPEND OUTPUT_FILES "${INSTALL_FILE}")
  endif ()
  if (MODULE AND COMPILE)
    basis_get_compiled_file (OUTPUT_CFILE  "${OUTPUT_FILE}"  ${LANGUAGE})
    basis_get_compiled_file (INSTALL_CFILE "${INSTALL_FILE}" ${LANGUAGE})
    if (OUTPUT_CFILE)
      list (APPEND OUTPUT_FILES "${OUTPUT_CFILE}")
    endif ()
    if (INSTALL_CFILE)
      list (APPEND OUTPUT_FILES "${INSTALL_CFILE}")
    endif ()
    if (LANGUAGE MATCHES "PYTHON")
      basis_compile_python_modules_for_jython (RV)
      if (RV)
        basis_get_compiled_jython_file_of_python_module (JYTHON_OUTPUT_CFILE "${OUTPUT_FILE}")
        basis_get_compiled_file (JYTHON_INSTALL_CFILE "${INSTALL_FILE}" JYTHON)
        if (JYTHON_OUTPUT_CFILE)
          list (APPEND OUTPUT_FILES "${JYTHON_OUTPUT_CFILE}")
        endif ()
        if (JYTHON_INSTALL_CFILE)
          list (APPEND OUTPUT_FILES "${JYTHON_INSTALL_CFILE}")
        endif ()
      endif ()
    endif ()
  endif ()
  # add build command for script
  if (OUTPUT_CFILE)
    file (RELATIVE_PATH REL "${CMAKE_BINARY_DIR}" "${OUTPUT_CFILE}")
  else ()
    file (RELATIVE_PATH REL "${CMAKE_BINARY_DIR}" "${OUTPUT_FILE}")
  endif ()
  if (LANGUAGE MATCHES "UNKNOWN")
    set (COMMENT "Building script ${REL}...")
  elseif (MODULE)
    set (COMMENT "Building ${LANGUAGE} module ${REL}...")
  else ()
    set (COMMENT "Building ${LANGUAGE} executable ${REL}...")
  endif ()
  add_custom_command (
    OUTPUT          ${OUTPUT_FILES}
    COMMAND         "${CMAKE_COMMAND}" -D "CONFIGURATION:STRING=$<CONFIGURATION>" -P "${BUILD_SCRIPT}"
    MAIN_DEPENDENCY "${SOURCE_FILE}"
    DEPENDS         "${BUILD_SCRIPT}" "${BASIS_MODULE_PATH}/CommonTools.cmake" # basis_configure_script() definition
    COMMENT         "${COMMENT}"
    VERBATIM
  )
  # add custom target
  add_custom_target (_${TARGET_UID} DEPENDS ${OUTPUT_FILES})
  foreach (T IN LISTS LINK_DEPENDS)
    if (TARGET ${T})
      add_dependencies (_${TARGET_UID} ${T})
    endif ()
  endforeach ()
  add_dependencies (${TARGET_UID} _${TARGET_UID})
  # cleanup on "make clean" - including compiled files regardless of COMPILE flag
  set_property (DIRECTORY APPEND PROPERTY ADDITIONAL_MAKE_CLEAN_FILES ${OUTPUT_FILES})
  foreach (OUTPUT_FILE IN LISTS OUTPUT_FILES)
    basis_get_compiled_file (CFILE "${OUTPUT_FILE}" ${LANGUAGE})
    if (CFILE)
      list (FIND OUTPUT_FILES "${CFILE}" IDX)
      if (IDX EQUAL -1)
        set_property (DIRECTORY APPEND PROPERTY ADDITIONAL_MAKE_CLEAN_FILES "${CFILE}")
      endif ()
    endif ()
  endforeach ()
  # copy configured (and compiled) script to (configuration specific) output directory
  if (OUTPUT_DIR)
    if (OUTPUT_CFILE)
      get_filename_component (OUTPUT_CNAME "${OUTPUT_CFILE}" NAME)
      add_custom_command (
        TARGET _${TARGET_UID} POST_BUILD
        COMMAND "${CMAKE_COMMAND}" -E copy "${OUTPUT_CFILE}" "${OUTPUT_DIR}/${OUTPUT_CNAME}"
      )
      set_property (DIRECTORY APPEND PROPERTY ADDITIONAL_MAKE_CLEAN_FILES "${OUTPUT_DIR}/${OUTPUT_CNAME}")
      if (JYTHON_OUTPUT_CFILE)
        get_filename_component (OUTPUT_CNAME "${JYTHON_OUTPUT_CFILE}" NAME)
        add_custom_command (
          TARGET _${TARGET_UID} POST_BUILD
          COMMAND "${CMAKE_COMMAND}" -E copy "${JYTHON_OUTPUT_CFILE}" "${OUTPUT_DIR}/${OUTPUT_CNAME}"
        )
        set_property (DIRECTORY APPEND PROPERTY ADDITIONAL_MAKE_CLEAN_FILES "${OUTPUT_DIR}/${OUTPUT_CNAME}")
      endif ()
    else ()
      add_custom_command (
        TARGET _${TARGET_UID} POST_BUILD
        COMMAND "${CMAKE_COMMAND}" -E copy "${OUTPUT_FILE}" "${OUTPUT_DIR}/${OUTPUT_NAME}"
      )
      set_property (DIRECTORY APPEND PROPERTY ADDITIONAL_MAKE_CLEAN_FILES "${OUTPUT_DIR}/${OUTPUT_NAME}")
    endif ()
  endif ()
  # export target
  if (EXPORT)
    basis_add_custom_export_target (${TARGET_UID} "${IS_TEST}")
  endif ()
  # install script
  if (INSTALL_DIRECTORY)
    if (INSTALL_CFILE)
      if (MODULE AND LANGUAGE MATCHES "PYTHON")
        basis_compile_python_modules_for_jython (RV)
        if (RV)
          basis_get_compiled_script (INSTALL_JYTHON_CFILE "${INSTALL_FILE}" JYTHON)
          basis_sanitize_for_regex (LIBRE  "${INSTALL_PYTHON_LIBRARY_DIR}")
          basis_sanitize_for_regex (SITERE "${INSTALL_PYTHON_SITE_DIR}")
          if (INSTALL_CFILE MATCHES "^${LIBRE}/" AND INSTALL_JYTHON_LIBRARY_DIR)
            string (REGEX REPLACE "^${LIBRE}/" "${INSTALL_JYTHON_LIBRARY_DIR}/" INSTALL_DIRECTORY_JYTHON "${INSTALL_DIRECTORY}")
          elseif (INSTALL_CFILE MATCHES "^${SITERE}/" AND INSTALL_JYTHON_SITE_DIR)
            string (REGEX REPLACE "^${SITERE}/" "${INSTALL_JYTHON_SITE_DIR}/" INSTALL_DIRECTORY_JYTHON "${INSTALL_DIRECTORY}")
          else ()
            set (INSTALL_DIRECTORY_JYTHON "${INSTALL_DIRECTORY}")
          endif ()
          install (
            FILES       "${INSTALL_JYTHON_CFILE}"
            DESTINATION "${INSTALL_DIRECTORY_JYTHON}"
            COMPONENT   "${COMPONENT}"
          )
        endif ()
      endif ()
      set (INSTALL_FILE "${INSTALL_CFILE}")
    elseif (NOT INSTALL_FILE)
      set (INSTALL_FILE "${OUTPUT_FILE}")
    endif ()
    if (MODULE)
      set (INSTALLTYPE FILES)
    else ()
      set (INSTALLTYPE PROGRAMS)
    endif ()
    install (
      ${INSTALLTYPE} "${INSTALL_FILE}"
      DESTINATION    "${INSTALL_DIRECTORY}"
      COMPONENT      "${COMPONENT}"
      RENAME         "${OUTPUT_NAME}"
    )
  endif ()
  # done
  if (BASIS_VERBOSE)
    message (STATUS "Adding build command for target ${TARGET_UID}... - done")
  endif ()
endfunction ()

# ----------------------------------------------------------------------------
## @brief Add custom command for build of script library.
#
# This function is called by basis_finalize_targets() which in turn is called
# by basis_project_end(), i.e., the end of the root CMake configuration file
# of the (sub-)project.
#
# @param [in] TARGET_UID Name/UID of custom target added by basis_add_script_library().
#
# @sa basis_add_script_library()
function (basis_build_script_library TARGET_UID)
  # does this target exist ?
  basis_get_target_uid (TARGET_UID "${TARGET_UID}")
  if (NOT TARGET "${TARGET_UID}")
    message (FATAL_ERROR "Unknown target: ${TARGET_UID}")
  endif ()
  if (BASIS_VERBOSE)
    message (STATUS "Adding build command for target ${TARGET_UID}...")
  endif ()
  # get target properties
  basis_get_target_link_libraries (LINK_DEPENDS ${TARGET_UID}) # paths of script modules/packages
                                                               # including BASIS utilities if used
  set (
    PROPERTIES
      LANGUAGE                   # programming language of modules
      BASIS_TYPE                 # must be "SCRIPT_LIBRARY"
      BASIS_UTILITIES            # whether this target requires the BASIS utilities
      BUILD_DIRECTORY            # CMakeFiles build directory
      SOURCE_DIRECTORY           # CMake source directory
      BINARY_DIRECTORY           # CMake binary directory
      LIBRARY_OUTPUT_DIRECTORY   # output directory for built modules
      LIBRARY_INSTALL_DIRECTORY  # installation directory for built modules
      LIBRARY_COMPONENT          # installation component
      PREFIX                     # common prefix for modules
      SCRIPT_DEFINITIONS         # CMake code to set variables used to configure modules
      SCRIPT_DEFINITIONS_FILE    # script configuration file
      LINK_DEPENDS               # paths of script modules/packages used by the modules of this library
      EXPORT                     # whether to export this target
      COMPILE                    # whether to compile the modules/library if applicable
      SOURCES                    # source files of module scripts
  )
  get_target_property (IS_TEST ${TARGET_UID} TEST) # whether this script is used for testing only
  foreach (PROPERTY ${PROPERTIES})
    get_target_property (${PROPERTY} ${TARGET_UID} ${PROPERTY})
  endforeach ()
  if (NOT BASIS_TYPE MATCHES "^SCRIPT_LIBRARY$")
    message (FATAL_ERROR "Target ${TARGET_UID}: Unexpected BASIS_TYPE: ${BASIS_TYPE}")
  endif ()
  if (NOT SOURCE_DIRECTORY)
    message (FATAL_ERROR "Missing SOURCE_DIRECTORY property!")
  endif ()
  if (NOT LIBRARY_OUTPUT_DIRECTORY)
    message (FATAL_ERROR "Missing LIBRARY_OUTPUT_DIRECTORY property!")
  endif ()
  if (NOT IS_ABSOLUTE "${LIBRARY_OUTPUT_DIRECTORY}")
    set (LIBRARY_OUTPUT_DIRECTORY "${TOPLEVEL_PROJECT_BINARY_DIR}/${LIBRARY_OUTPUT_DIRECTORY}")
  endif ()
  file (RELATIVE_PATH _relpath "${CMAKE_BINARY_DIR}" "${LIBRARY_OUTPUT_DIRECTORY}")
  if (_relpath MATCHES "^\\.\\./")
    message (FATAL_ERROR "Output directory LIBRARY_OUTPUT_DIRECTORY is outside the build tree!")
  endif ()
  unset(_relpath)
  if (NOT LIBRARY_COMPONENT)
    set (LIBRARY_COMPONENT "Unspecified")
  endif ()
  list (GET SOURCES 0 BUILD_DIR) # CMake <3.1 stores path to internal build directory here
  if (BUILD_DIR MATCHES "CMakeFiles")
    list (REMOVE_AT SOURCES 0)
  endif ()
  if (NOT SOURCES)
    message (FATAL_ERROR "Target ${TARGET_UID}: Expected at least one element in SOURCES list!"
                         " Have you incorrectly modified this (read-only) property or"
                         " is your (newer) CMake version not compatible with BASIS?")
  endif ()
  set (BUILD_DIR "${BUILD_DIRECTORY}.dir")
  # common arguments of build script
  set (CONFIG_FILES)
  if (EXISTS "${BASIS_SCRIPT_CONFIG_FILE}")
    list (APPEND CONFIG_FILES "${BASIS_SCRIPT_CONFIG_FILE}")
  endif ()
  if (SCRIPT_DEFINITIONS_FILE)
    list (APPEND CONFIG_FILES ${SCRIPT_DEFINITIONS_FILE})
  endif ()
  if (SCRIPT_DEFINITIONS)
    set (SCRIPT_CONFIG_FILE "${BUILD_DIR}/ScriptConfig.cmake")
    file (WRITE "${SCRIPT_CONFIG_FILE}.tmp" "# DO NOT edit. Automatically generated by BASIS.\n${SCRIPT_DEFINITIONS}\n")
    execute_process (COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${SCRIPT_CONFIG_FILE}.tmp" "${SCRIPT_CONFIG_FILE}")
    file (REMOVE "${SCRIPT_CONFIG_FILE}.tmp")
    list (APPEND CONFIG_FILES "${SCRIPT_CONFIG_FILE}")
  endif ()
  set (CACHE_FILE "${BUILD_DIR}/cache.cmake")
  execute_process (COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${CACHE_FILE}.tmp" "${CACHE_FILE}")
  file (REMOVE "${CACHE_FILE}.tmp")
  set (OPTIONS) # no additional options
  if (COMPILE)
    list (APPEND OPTIONS COMPILE)
  endif ()
  # add build command for each module
  set (OUTPUT_FILES)                                    # list of all output files
  set (FILES_TO_COPY)                                   # relative paths of build tree modules
  set (FILES_TO_INSTALL)                                # list of output files for installation
  set (BINARY_INSTALL_DIRECTORY "${BUILD_DIR}/install") # common base directory for files to install 
  if (COMPILE OR CMAKE_GENERATOR MATCHES "Visual Studio|Xcode") # post-build copy to <libdir>/<config>/
    set (BINARY_OUTPUT_DIRECTORY "${BUILD_DIR}/build")  # common base directory for build tree files
    set (POST_BUILD_COPY TRUE)
  else ()
    set (BINARY_OUTPUT_DIRECTORY "${LIBRARY_OUTPUT_DIRECTORY}")
    set (POST_BUILD_COPY FALSE)
  endif ()
  foreach (SOURCE_FILE IN LISTS SOURCES)
    file (RELATIVE_PATH S "${SOURCE_DIRECTORY}" "${SOURCE_FILE}")
    string (REGEX REPLACE "\\.in$" "" S "${S}")
    basis_get_source_target_name (BUILD_SCRIPT_NAME "build_${S}")
    # arguments of build script
    if (PREFIX)
      set (S "${PREFIX}${S}")
    endif ()
    set (OUTPUT_FILE "${BINARY_OUTPUT_DIRECTORY}/${S}")
    get_filename_component (OUTPUT_DIR "${LIBRARY_OUTPUT_DIRECTORY}/${S}" PATH)
    if (LIBRARY_INSTALL_DIRECTORY)
      set (INSTALL_FILE "${BINARY_INSTALL_DIRECTORY}/${S}")
      set (DESTINATION  "${LIBRARY_INSTALL_DIRECTORY}/${S}")
      if (NOT IS_ABSOLUTE "${DESTINATION}")
        set (DESTINATION "${CMAKE_INSTALL_PREFIX}/${DESTINATION}")
      endif ()
      get_filename_component (DESTINATION "${DESTINATION}" PATH)
    else ()
      set (INSTALL_FILE)
      set (DESTINATION)
    endif ()
    # configure build script
    set (BUILD_SCRIPT "${BUILD_DIR}/${BUILD_SCRIPT_NAME}.cmake")
    configure_file ("${BASIS_MODULE_PATH}/configure_script.cmake.in" "${BUILD_SCRIPT}" @ONLY)
    # output files of this command
    set (_OUTPUT_FILES "${OUTPUT_FILE}")
    if (INSTALL_FILE)
      list (APPEND _OUTPUT_FILES "${INSTALL_FILE}")
    endif ()
    if (COMPILE)
      basis_get_compiled_file (OUTPUT_CFILE  "${OUTPUT_FILE}"  ${LANGUAGE})
      basis_get_compiled_file (INSTALL_CFILE "${INSTALL_FILE}" ${LANGUAGE})
      if (OUTPUT_CFILE)
        list (APPEND _OUTPUT_FILES "${OUTPUT_CFILE}")
      endif ()
      if (INSTALL_CFILE)
        list (APPEND _OUTPUT_FILES "${INSTALL_CFILE}")
      endif ()
      if (LANGUAGE MATCHES "PYTHON")
        basis_compile_python_modules_for_jython (RV)
        if (RV)
          basis_get_compiled_jython_file_of_python_module (JYTHON_OUTPUT_CFILE "${OUTPUT_FILE}")
          basis_get_compiled_file (JYTHON_INSTALL_CFILE "${INSTALL_FILE}" JYTHON)
          if (JYTHON_OUTPUT_CFILE)
            list (APPEND _OUTPUT_FILES "${JYTHON_OUTPUT_CFILE}")
          endif ()
          if (JYTHON_INSTALL_CFILE)
            list (APPEND _OUTPUT_FILES "${JYTHON_INSTALL_CFILE}")
          endif ()
        endif ()
      endif ()
    endif ()
    if (POST_BUILD_COPY)
      if (OUTPUT_CFILE)
        file (RELATIVE_PATH _file "${BINARY_OUTPUT_DIRECTORY}" "${OUTPUT_CFILE}")
        list (APPEND FILES_TO_COPY "${_file}")
        if (JYTHON_OUTPUT_CFILE)
          file (RELATIVE_PATH _file "${BINARY_OUTPUT_DIRECTORY}" "${JYTHON_OUTPUT_CFILE}")
          list (APPEND FILES_TO_COPY "${_file}")
        endif ()
      else ()
        list (APPEND FILES_TO_COPY "${S}")
      endif ()
    endif ()
    if (INSTALL_CFILE)
      list (APPEND FILES_TO_INSTALL "${INSTALL_CFILE}")
    elseif (INSTALL_FILE)
      list (APPEND FILES_TO_INSTALL "${INSTALL_FILE}")
    endif ()
    # add build command
    if (OUTPUT_CFILE)
      file (RELATIVE_PATH REL "${CMAKE_BINARY_DIR}" "${OUTPUT_CFILE}")
    else ()
      file (RELATIVE_PATH REL "${CMAKE_BINARY_DIR}" "${OUTPUT_FILE}")
    endif ()
    set (COMMENT "Building ${LANGUAGE} module ${REL}...")
    add_custom_command (
      OUTPUT          ${_OUTPUT_FILES}
      COMMAND         "${CMAKE_COMMAND}" -D "CONFIGURATION=$<CONFIGURATION>" -P "${BUILD_SCRIPT}"
      MAIN_DEPENDENCY "${SOURCE_FILE}"
      DEPENDS         "${BUILD_SCRIPT}" "${BASIS_MODULE_PATH}/CommonTools.cmake" # basis_configure_script() definition
      COMMENT         "${COMMENT}"
      VERBATIM
    )
    # add output files of command to list of all output files
    list (APPEND OUTPUT_FILES ${_OUTPUT_FILES})
  endforeach ()
  # add custom target to build modules
  add_custom_target (_${TARGET_UID} DEPENDS ${OUTPUT_FILES})
  foreach (T IN LISTS LINK_DEPENDS)
    if (TARGET ${T})
      add_dependencies (_${TARGET_UID} ${T})
    endif ()
  endforeach ()
  add_dependencies (${TARGET_UID} _${TARGET_UID})
  # copy configured modules to output directory
  foreach (OUTPUT_FILE IN LISTS FILES_TO_COPY)
    add_custom_command (
      TARGET _${TARGET_UID} POST_BUILD
      COMMAND "${CMAKE_COMMAND}" -E copy "${BINARY_OUTPUT_DIRECTORY}/${OUTPUT_FILE}" "${LIBRARY_OUTPUT_DIRECTORY}/${OUTPUT_FILE}"
    )
  endforeach ()
  # cleanup on "make clean" - including compiled files regardless of COMPILE flag
  set_property (DIRECTORY APPEND PROPERTY ADDITIONAL_MAKE_CLEAN_FILES ${OUTPUT_FILES})
  foreach (OUTPUT_FILE IN LISTS OUTPUT_FILES)
    basis_get_compiled_file (CFILE "${OUTPUT_FILE}" ${LANGUAGE})
    if (CFILE)
      list (FIND OUTPUT_FILES "${CFILE}" IDX)
      if (IDX EQUAL -1)
        set_property (DIRECTORY APPEND PROPERTY ADDITIONAL_MAKE_CLEAN_FILES "${CFILE}")
      endif ()
    endif ()
  endforeach ()
  # export target
  if (EXPORT)
    basis_add_custom_export_target (${TARGET_UID} "${IS_TEST}")
  endif ()
  # add installation rule
  foreach (INSTALL_FILE IN LISTS FILES_TO_INSTALL)
    get_filename_component (D "${INSTALL_FILE}" PATH)
    file (RELATIVE_PATH D "${BINARY_INSTALL_DIRECTORY}" "${D}")
    install (
      FILES       "${INSTALL_FILE}"
      DESTINATION "${LIBRARY_INSTALL_DIRECTORY}/${D}"
      COMPONENT   "${LIBRARY_COMPONENT}"
    )
    if (LANGUAGE MATCHES "PYTHON" AND INSTALL_FILE MATCHES "\\.pyc$")
      basis_compile_python_modules_for_jython (RV)
      if (RV)
        basis_sanitize_for_regex (LIBRE  "${INSTALL_PYTHON_LIBRARY_DIR}")
        basis_sanitize_for_regex (SITERE "${INSTALL_PYTHON_SITE_DIR}")
        if (LIBRARY_INSTALL_DIRECTORY MATCHES "^${LIBRE}/*$")
          set (JYTHON_INSTALL_DIRECTORY "${INSTALL_JYTHON_LIBRARY_DIR}")
        elseif (LIBRARY_INSTALL_DIRECTORY MATCHES "^${SITERE}/*$")
          set (JYTHON_INSTALL_DIRECTORY "${INSTALL_JYTHON_SITE_DIR}")
        else ()
          set (JYTHON_INSTALL_DIRECTORY "${LIBRARY_INSTALL_DIRECTORY}")
        endif ()
        string (REGEX REPLACE "c$" "" SOURCE_FILE "${INSTALL_FILE}")
        basis_get_compiled_file (INSTALL_JYTHON_CFILE "${SOURCE_FILE}" JYTHON)
        install (
          FILES       "${INSTALL_JYTHON_CFILE}"
          DESTINATION "${JYTHON_INSTALL_DIRECTORY}/${D}"
          COMPONENT   "${LIBRARY_COMPONENT}"
        )
      endif ()
    endif ()
  endforeach ()
  # done
  if (BASIS_VERBOSE)
    message (STATUS "Adding build command for target ${TARGET_UID}... - done")
  endif ()
endfunction ()

# ----------------------------------------------------------------------------
# @brief Add target to build/install __init__.py files.
function (basis_add_init_py_target)
  if (NOT BASIS_PYTHON_TEMPLATES_DIR)
    message (WARNING "BASIS_PYTHON_TEMPLATES_DIR not set, skipping basis_add_init_py_target")
    return ()
  endif ()
  # constants
  set (BUILD_DIR "${PROJECT_BINARY_DIR}/CMakeFiles/_initpy.dir")
  basis_sanitize_for_regex (BINARY_PYTHON_LIBRARY_DIR_RE  "${BINARY_PYTHON_LIBRARY_DIR}")
  basis_sanitize_for_regex (TESTING_PYTHON_LIBRARY_DIR_RE "${TESTING_PYTHON_LIBRARY_DIR}")
  basis_sanitize_for_regex (INSTALL_PYTHON_LIBRARY_DIR_RE "${INSTALL_PYTHON_LIBRARY_DIR}")
  basis_sanitize_for_regex (BINARY_JYTHON_LIBRARY_DIR_RE  "${BINARY_JYTHON_LIBRARY_DIR}")
  basis_sanitize_for_regex (TESTING_JYTHON_LIBRARY_DIR_RE "${TESTING_JYTHON_LIBRARY_DIR}")
  basis_sanitize_for_regex (INSTALL_JYTHON_LIBRARY_DIR_RE "${INSTALL_JYTHON_LIBRARY_DIR}")
  # collect directories requiring a __init__.py file
  set (DEPENDENTS)          # targets which generate Python/Jython modules and depend on _initpy
  set (PYTHON_DIRS)         # Python library directories requiring a __init__.py file
  set (JYTHON_DIRS)         # Jython library directories requiring a __init__.py file
  set (EXCLUDE)             # exclude these directories
  set (INSTALL_EXCLUDE)     # exclude these directories upon installation
  set (COMPONENTS)          # installation components
  basis_get_project_property (TARGETS PROPERTY TARGETS)
  foreach (TARGET_UID IN LISTS TARGETS)
    get_target_property (BASIS_TYPE ${TARGET_UID} BASIS_TYPE)
    get_target_property (LANGUAGE   ${TARGET_UID} LANGUAGE)
    if (BASIS_TYPE MATCHES "MODULE|LIBRARY" AND LANGUAGE MATCHES "^[JP]YTHON$")
      # get path of built Python modules
      basis_get_target_location (LOCATION         ${TARGET_UID} ABSOLUTE)
      basis_get_target_location (INSTALL_LOCATION ${TARGET_UID} POST_INSTALL_RELATIVE)
      if (BASIS_TYPE MATCHES "^SCRIPT_LIBRARY$")
        get_target_property (PREFIX           ${TARGET_UID} PREFIX)
        get_target_property (SOURCES          ${TARGET_UID} SOURCES)
        get_target_property (SOURCE_DIRECTORY ${TARGET_UID} SOURCE_DIRECTORY)
        string (REGEX REPLACE "/+$" "" PREFIX "${PREFIX}")
        if (PREFIX)
          set (LOCATION         "${LOCATION}/${PREFIX}")
          set (INSTALL_LOCATION "${INSTALL_LOCATION}/${PREFIX}")
        endif ()
        list (GET SOURCES 0 BINARY_DIR) # CMake <3.1 stores path to internal build directory here
        if (BINARY_DIR MATCHES "CMakeFiles")
          list (REMOVE_AT SOURCES 0)
        endif ()
        foreach (SOURCE IN LISTS SOURCES)
          file (RELATIVE_PATH SOURCE "${SOURCE_DIRECTORY}" "${SOURCE}")
          list (APPEND _LOCATION         "${LOCATION}/${SOURCE}")
          list (APPEND _INSTALL_LOCATION "${INSTALL_LOCATION}/${SOURCE}")
        endforeach ()
        set (LOCATION         "${_LOCATION}")
        set (INSTALL_LOCATION "${_INSTALL_LOCATION}")
      endif ()
      # get component (used by installation rule)
      get_target_property (COMPONENT ${TARGET_UID} "LIBRARY_COMPONENT")
      list (FIND COMPONENTS "${COMPONENT}" IDX)
      if (IDX EQUAL -1)
        list (APPEND COMPONENTS "${COMPONENT}")
        set (INSTALL_${LANGUAGE}_DIRS_${COMPONENT}) # list of directories for which to install
                                                    # __init__.py for this component
      endif ()
      # directories for which to build a __init__.py file
      foreach (L IN LISTS LOCATION)
        basis_get_filename_component (DIR "${L}" PATH)
        if (L MATCHES "/__init__\\.py$")
          list (APPEND EXCLUDE "${DIR}")
        else ()
          list (APPEND DEPENDENTS ${TARGET_UID}) # depends on _initpy
          if (BINARY_${LANGUAGE}_LIBRARY_DIR_RE AND DIR MATCHES "^${BINARY_${LANGUAGE}_LIBRARY_DIR_RE}/.+")
            while (NOT "${DIR}" MATCHES "^${BINARY_${LANGUAGE}_LIBRARY_DIR_RE}$")
              list (APPEND ${LANGUAGE}_DIRS "${DIR}")
              get_filename_component (DIR "${DIR}" PATH)
            endwhile ()
          elseif (TESTING_${LANGUAGE}_LIBRARY_DIR_RE AND DIR MATCHES "^${TESTING_${LANGUAGE}_LIBRARY_DIR_RE}/.+")
            while (NOT "${DIR}" MATCHES "^${TESTING_${LANGUAGE}_LIBRARY_DIR_RE}$")
              list (APPEND ${LANGUAGE}_DIRS "${DIR}")
              get_filename_component (DIR "${DIR}" PATH)
            endwhile ()
          endif ()
        endif ()
      endforeach ()
      # directories for which to install a __init__.py file
      foreach (L IN LISTS INSTALL_LOCATION)
        basis_get_filename_component (DIR "${L}" PATH)
        if (L MATCHES "/__init__\\.py$")
          list (APPEND INSTALL_EXCLUDE "${DIR}")
        else ()
          list (APPEND DEPENDENTS ${TARGET_UID}) # depends on _initpy
          if (INSTALL_${LANGUAGE}_LIBRARY_DIR_RE AND DIR MATCHES "^${INSTALL_${LANGUAGE}_LIBRARY_DIR_RE}/.+")
            while (NOT "${DIR}" MATCHES "^${INSTALL_${LANGUAGE}_LIBRARY_DIR_RE}$")
              list (APPEND INSTALL_${LANGUAGE}_DIRS_${COMPONENT} "${DIR}")
              if (BASIS_COMPILE_SCRIPTS AND LANGUAGE MATCHES "PYTHON")
                basis_compile_python_modules_for_jython (RV)
                if (RV)
                  if (INSTALL_JYTHON_LIBRARY_DIR)
                    file (RELATIVE_PATH REL "${CMAKE_INSTALL_PREFIX}/${INSTALL_PYTHON_LIBRARY_DIR}" "${CMAKE_INSTALL_PREFIX}/${DIR}")
                  else ()
                    set (REL)
                  endif ()
                  if (NOT REL MATCHES "^$|^\\.\\./")
                    list (APPEND INSTALL_JYTHON_DIRS_${COMPONENT} "${INSTALL_JYTHON_LIBRARY_DIR}/${REL}")
                  endif ()
                endif ()
              endif ()
              get_filename_component (DIR "${DIR}" PATH)
            endwhile ()
          endif ()
        endif ()
      endforeach ()
    endif ()
  endforeach ()
  if (DEPENDENTS)
    list (REMOVE_DUPLICATES DEPENDENTS)
  endif ()
  # return if nothing to do
  if (NOT PYTHON_DIRS AND NOT JYTHON_DIRS)
    return ()
  endif ()
  if (PYTHON_DIRS)
    list (REMOVE_DUPLICATES PYTHON_DIRS)
  endif ()
  if (JYTHON_DIRS)
    list (REMOVE_DUPLICATES JYTHON_DIRS)
  endif ()
  if (EXCLUDE)
    list (REMOVE_DUPLICATES EXCLUDE)
  endif ()
  if (INSTALL_EXCLUDE)
    list (REMOVE_DUPLICATES INSTALL_EXCLUDE)
  endif ()
  # generate build script
  set (PYTHON_COMPILED_FILES "${CFILE}")
  set (JYTHON_COMPILED_FILES "${CFILE}")
  set (C "configure_file (\"${BASIS_PYTHON_TEMPLATES_DIR}/__init__.py.in\" \"${BUILD_DIR}/__init__.py\" @ONLY)\n")
  foreach (LANGUAGE IN ITEMS PYTHON JYTHON) # Python *must* come first. See JYTHON_COMPILED_FILES list.
    set (${LANGUAGE}_OUTPUT_FILES)
    set (${LANGUAGE}_INSTALL_FILE "${BUILD_DIR}/__init__.py")
    set (C "${C}\nset (${LANGUAGE}_EXECUTABLE \"${${LANGUAGE}_EXECUTABLE}\")\n\n")
    foreach (DIR IN LISTS ${LANGUAGE}_DIRS)
      list (FIND EXCLUDE "${DIR}" IDX)
      if (IDX EQUAL -1)
        set (C "${C}configure_file (\"${BASIS_PYTHON_TEMPLATES_DIR}/__init__.py.in\" \"${DIR}/__init__.py\" @ONLY)\n")
        list (APPEND ${LANGUAGE}_OUTPUT_FILES "${DIR}/__init__.py")
      endif ()
    endforeach ()
    if (BASIS_COMPILE_SCRIPTS AND ${LANGUAGE}_EXECUTABLE)
      set (C "${C}\n")
      string (TOLOWER "${LANGUAGE}" language)
      basis_get_compiled_file (CFILE "${BUILD_DIR}/${language}/__init__.py" ${LANGUAGE})
      set (C "${C}file (MAKE_DIRECTORY \"${BUILD_DIR}/${language}\")\n")
      set (C "${C}execute_process (COMMAND \"${${LANGUAGE}_EXECUTABLE}\" -c \"import py_compile; py_compile.compile('${BUILD_DIR}/__init__.py', '${CFILE}')\")\n")
      set (${LANGUAGE}_INSTALL_FILE "${CFILE}")
      set (C "${C}\n")
      foreach (SFILE IN LISTS ${LANGUAGE}_OUTPUT_FILES)
        basis_get_compiled_file (CFILE "${SFILE}" ${LANGUAGE})
        set (C "${C}execute_process (COMMAND \"${${LANGUAGE}_EXECUTABLE}\" -c \"import py_compile; py_compile.compile('${SFILE}', '${CFILE}')\")\n")
        list (APPEND ${LANGUAGE}_COMPILED_FILES "${CFILE}")
        if (LANGUAGE MATCHES "PYTHON")
          basis_compile_python_modules_for_jython (RV)
          if (RV)
            if (BINARY_PYTHON_LIBRARY_DIR AND BINARY_JYTHON_LIBRARY_DIR)
              file (RELATIVE_PATH REL "${BINARY_PYTHON_LIBRARY_DIR}" "${SFILE}")
            else ()
              set (REL)
            endif ()
            if (NOT REL MATCHES "^$|^\\.\\./")
              basis_get_compiled_file (CFILE "${BINARY_JYTHON_LIBRARY_DIR}/${REL}" JYTHON)
            else ()
              basis_get_compiled_file (CFILE "${SFILE}" JYTHON)
            endif ()
            get_filename_component (CDIR "${CFILE}" PATH)
            set (C "${C}file (MAKE_DIRECTORY \"${CDIR}\")\n")
            set (C "${C}execute_process (COMMAND \"${JYTHON_EXECUTABLE}\" -c \"import py_compile; py_compile.compile('${SFILE}', '${CFILE}')\")\n")
            list (APPEND JYTHON_COMPILED_FILES "${CFILE}")
          endif ()
        endif ()
      endforeach ()
      list (APPEND ${LANGUAGE}_OUTPUT_FILES ${${LANGUAGE}_COMPILED_FILES})
    endif ()
  endforeach ()
  # write/update build script
  set (BUILD_SCRIPT "${BUILD_DIR}/build.cmake")
  if (EXISTS "${BUILD_SCRIPT}")
    file (WRITE "${BUILD_SCRIPT}.tmp" "${C}")
    execute_process (COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${BUILD_SCRIPT}.tmp" "${BUILD_SCRIPT}")
    file (REMOVE "${BUILD_SCRIPT}.tmp")
  else ()
    file (WRITE "${BUILD_SCRIPT}" "${C}")
  endif ()
  # add custom build command
  add_custom_command (
    OUTPUT          "${BUILD_DIR}/__init__.py" ${PYTHON_OUTPUT_FILES} ${JYTHON_OUTPUT_FILES}
    COMMAND         "${CMAKE_COMMAND}" -P "${BUILD_SCRIPT}"
    MAIN_DEPENDENCY "${BASIS_PYTHON_TEMPLATES_DIR}/__init__.py.in"
    COMMENT         "Building PYTHON modules */__init__.py..."
  )
  # add custom target which triggers execution of build script
  add_custom_target (_initpy ALL DEPENDS "${BUILD_DIR}/__init__.py" ${PYTHON_OUTPUT_FILES} ${JYTHON_OUTPUT_FILES})
  if (BASIS_DEBUG)
    message ("** basis_add_init_py_target():")
  endif ()
  foreach (DEPENDENT IN LISTS DEPENDENTS)
    if (BASIS_DEBUG)
      message ("**    Adding dependency on _initpy target to ${DEPENDENT}")
    endif ()
    add_dependencies (${DEPENDENT} _initpy)
  endforeach ()
  # cleanup on "make clean" - including compiled modules regardless of COMPILE flag
  set_property (DIRECTORY APPEND PROPERTY ADDITIONAL_MAKE_CLEAN_FILES "${BUILD_DIR}/__init__.py" ${PYTHON_OUTPUT_FILES} ${JYTHON_OUTPUT_FILES})
  # add install rules
  foreach (LANGUAGE IN ITEMS PYTHON JYTHON)
    foreach (COMPONENT IN LISTS COMPONENTS)
      if (INSTALL_${LANGUAGE}_DIRS_${COMPONENT})
        list (REMOVE_DUPLICATES INSTALL_${LANGUAGE}_DIRS_${COMPONENT})
      endif ()
      foreach (DIR IN LISTS INSTALL_${LANGUAGE}_DIRS_${COMPONENT})
        list (FIND INSTALL_EXCLUDE "${DIR}" IDX)
        if (IDX EQUAL -1)
          if (BASIS_DEBUG AND BASIS_VERBOSE)
            message("**    Copy ${${LANGUAGE}_INSTALL_FILE} to ${DIR} upon installation of ${COMPONENT} component")
          endif ()
          install (
            FILES       "${${LANGUAGE}_INSTALL_FILE}"
            DESTINATION "${DIR}"
            COMPONENT   "${COMPONENT}"
          )
        endif ()
      endforeach ()
    endforeach ()
  endforeach ()
endfunction ()


## @}
# end of Doxygen group