CedaCommon.cmake

# CedaCommon.cmake

if (MSVC)
    add_definitions(
        # Target Windows XP SP2
        # cxSocket is using GetAddrInfoW() which requires WinXP SP2 and later
        /D_WIN32_WINNT=0x0502                 

        # GraphicsMagick is using std::unary_function which is removed in C++17
		/D_HAS_AUTO_PTR_ETC=1

		/D_SILENCE_ALL_CXX17_DEPRECATION_WARNINGS
    )

	# Currently by default MSVC only supports C++14, need this to enable using things like std::optional, std::filesystem
	add_compile_options(/std:c++latest)

    # warning C5033: 'register' is no longer a supported storage class
    # This is caused by the Python headers
    add_compile_options(/wd5033)		    
endif()

if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
    message("Adding switches for clang")

    # See http://clang-developers.42468.n3.nabble.com/clang-cl-error-on-building-simple-cpp-with-only-windows-h-boost-mutex-included-td4049894.html
    add_definitions(/DBOOST_SP_USE_STD_ATOMIC)

    # C:\python27x64\include/unicodeobject.h(534,5): error: ISO C++17 does not allow 'register' storage class specifier [-Wregister]
    add_compile_options(-Wregister)
endif()

# https://github.com/boostorg/system/issues/32
add_definitions(/DHAVE_SNPRINTF)

if (NOT BUILD_SHARED_LIBS)
    if (MSVC)
        message("Using static versions of the C Run Time library")

        # /MD :  Link with the multithread, DLL version of the run-time library.
        # /MT :  Link with the multithread, static version of the run-time library.
        set(CompilerFlags
                CMAKE_CXX_FLAGS
                CMAKE_CXX_FLAGS_DEBUG
                CMAKE_CXX_FLAGS_RELEASE
                CMAKE_CXX_FLAGS_RELWITHDEBINFO
                CMAKE_C_FLAGS
                CMAKE_C_FLAGS_DEBUG
                CMAKE_C_FLAGS_RELEASE
                CMAKE_C_FLAGS_RELWITHDEBINFO
                )
        foreach(CompilerFlag ${CompilerFlags})
            string(REPLACE "/MD" "/MT" ${CompilerFlag} "${${CompilerFlag}}")
        endforeach()
    endif()
endif()

function(cedaSetDefaultsForTarget targetname)
    # Configure where to output generated binaries
    #   *.lib will go to ARCHIVE_OUTPUT_DIRECTORY
    #   *.so will go to LIBRARY_OUTPUT_DIRECTORY
    #   *.dll will go to RUNTIME_OUTPUT_DIRECTORY
    set_target_properties(${targetname} PROPERTIES
        ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib
        LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib
        RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin
    )

    target_compile_definitions(${targetname}
		PRIVATE
			CEDA_USE_BOOST_MULTIPRECISION
			# Performance repercussions!
			# CEDA_CHECK_ASSERTIONS
			# CEDA_ASSERTION_FAILURE_THROWS_EXCEPTION
    )

    if ("${CMAKE_BUILD_TYPE}" MATCHES "Debug")
        target_compile_definitions(${targetname} PRIVATE CEDA_CHECK_ASSERTIONS)
    endif()

	if (MSVC)
		target_compile_definitions(${targetname}
			PRIVATE
				# Eliminate deprecation warnings for the older, less secure CRT functions such as strcpy.
				# Many old CRT functions have newer, more secure versions. If a secure function exists, 
				# the older, less secure version is marked as deprecated and the new version has the 
				# _s ("secure") suffix.
				_CRT_SECURE_NO_WARNINGS
		)
	endif()
endfunction()

###################################################################################################
# linkTargetWithLibs

function(linkTargetWithLibs targetname)
    foreach(libname ${ARGN})
        find_library(FOUND_${libname} NAMES ${libname})
        message("Target ${targetname} linking with library ${libname} : ${FOUND_${libname}}")
        target_link_libraries(${targetname} PRIVATE ${FOUND_${libname}})
    endforeach()
endfunction()

###################################################################################################
# linkTargetWithwxWidgets

function(linkTargetWithwxWidgets targetname)
    target_compile_definitions(${targetname} PRIVATE CEDA_USING_WXWIDGETS _UNICODE UNICODE __WXMSW__ wxUSE_GUI=1)
    if (BUILD_SHARED_LIBS)
        target_compile_definitions(${targetname} PRIVATE -DWXUSINGDLL)
    endif()
    find_path(WXWIDGETS_INCLUDE_DIR wx/wx.h)
    message("WXWIDGETS_INCLUDE_DIR = ${WXWIDGETS_INCLUDE_DIR}")
    target_include_directories(${targetname} PRIVATE ${WXWIDGETS_INCLUDE_DIR})
    if ("${CMAKE_BUILD_TYPE}" MATCHES "Debug")
        target_compile_definitions(${targetname} PRIVATE __WXDEBUG__)
        linkTargetWithLibs(${targetname}
            wxbase31ud
            wxbase31ud_net
            wxbase31ud_xml
            wxmsw31ud_adv
            wxmsw31ud_aui
            wxmsw31ud_core
            wxmsw31ud_gl
            wxmsw31ud_html
            wxmsw31ud_media
            wxmsw31ud_propgrid
            wxmsw31ud_qa
            wxmsw31ud_ribbon
            wxmsw31ud_richtext
            wxmsw31ud_stc
            wxmsw31ud_webview
            wxmsw31ud_xrc
            libpng16d 
            zlibd)
    elseif( ("${CMAKE_BUILD_TYPE}" MATCHES "Release") OR ("${CMAKE_BUILD_TYPE}" MATCHES "RelWithDebInfo") )
        linkTargetWithLibs(${targetname}
            wxbase31u
            wxbase31u_net
            wxbase31u_xml
            wxmsw31u_adv
            wxmsw31u_aui
            wxmsw31u_core
            wxmsw31u_gl
            wxmsw31u_html
            wxmsw31u_media
            wxmsw31u_propgrid
            wxmsw31u_qa
            wxmsw31u_ribbon
            wxmsw31u_richtext
            wxmsw31u_stc
            wxmsw31u_webview
            wxmsw31u_xrc
            libpng16 
            zlib)
    endif()
    target_link_libraries(${targetname} PRIVATE winmm comctl32 oleacc rpcrt4 shlwapi version wsock32)
endfunction()

###################################################################################################
# linkTargetWithPython2

function(linkTargetWithPython2 targetname)
    target_include_directories(${targetname} PRIVATE ${Python2_INCLUDE_DIRS})
	target_link_libraries(${targetname} PRIVATE ${Python2_LIBRARIES})
endfunction()

###################################################################################################
# linkTargetWithFfi
# (libffi)

function(linkTargetWithFfi targetname)
    find_path(FFI_INCLUDE_DIR ffi.h)
    message("FFI_INCLUDE_DIR = ${FFI_INCLUDE_DIR}")
    target_include_directories(${targetname} PRIVATE ${FFI_INCLUDE_DIR})

    find_library(FFI_LIBRARY NAMES ffi libffi)
    message("FFI_LIBRARY = ${FFI_LIBRARY}")
	target_link_libraries(${targetname} PRIVATE ${FFI_LIBRARY})
endfunction()

###################################################################################################
# linkTargetWithPango

function(linkTargetWithPango targetname)
    find_path(PANGO_INCLUDE_DIR pango/pango.h)
    message("PANGO_INCLUDE_DIR = ${PANGO_INCLUDE_DIR}")
    target_include_directories(${targetname} PRIVATE ${PANGO_INCLUDE_DIR})
    linkTargetWithLibs(${targetname} pango-1.0 pangocairo-1.0 pangoft2-1.0 pangowin32-1.0 gobject-2.0)
endfunction()

###################################################################################################
# linkTargetWithCairo

function(linkTargetWithCairo targetname)
    find_path(CAIRO_INCLUDE_DIR cairo/cairo.h)
    message("CAIRO_INCLUDE_DIR = ${CAIRO_INCLUDE_DIR}")
    target_include_directories(${targetname} PRIVATE ${CAIRO_INCLUDE_DIR})
    if ("${CMAKE_BUILD_TYPE}" MATCHES "Debug")
        linkTargetWithLibs(${targetname} cairod cairo-gobjectd)
    elseif( ("${CMAKE_BUILD_TYPE}" MATCHES "Release") OR ("${CMAKE_BUILD_TYPE}" MATCHES "RelWithDebInfo") )
        linkTargetWithLibs(${targetname} cairo cairo-gobject)
    endif()
endfunction()

###################################################################################################
# linkTargetWithTurboJpeg

function(linkTargetWithTurboJpeg targetname)
    find_path(TURBOJPEG_INCLUDE_DIR turbojpeg.h)
    message("TURBOJPEG_INCLUDE_DIR = ${TURBOJPEG_INCLUDE_DIR}")
    target_include_directories(${targetname} PRIVATE ${TURBOJPEG_INCLUDE_DIR})
    linkTargetWithLibs(${targetname} turbojpeg jpeg)
endfunction()

###################################################################################################
# linkTargetWithWebP

function(linkTargetWithWebP targetname)
    find_path(WEBP_INCLUDE_DIRS webp/decode.h)
    message("WEBP_INCLUDE_DIRS = ${WEBP_INCLUDE_DIRS}")
    target_include_directories(${targetname} PRIVATE ${WEBP_INCLUDE_DIRS})
    if ("${CMAKE_BUILD_TYPE}" MATCHES "Debug")
        linkTargetWithLibs(${targetname} webpd)
    elseif( ("${CMAKE_BUILD_TYPE}" MATCHES "Release") OR ("${CMAKE_BUILD_TYPE}" MATCHES "RelWithDebInfo") )
        linkTargetWithLibs(${targetname} webp)
    endif()
endfunction()

###################################################################################################
# cedaFindBoost

function(cedaFindBoost)
    message("----------------------- boost --------------------------")

    if (CEDA_CUSTOM_BUILD_ANDROID)
        # This is a hack for allowing our own build of boost for android 
        # (static boost for android libraries were built on Windows with clang, ndk 19r using our own bash script 
        # 'buildboostandroid_linux' or 'buildboostandroid_windows' running under git bash)
        # It not clear how to make our own build of boost work with Kitware's undocumented FindBoost.cmake
        # We can probably make it work, Cameron Lowell Palmer claims it can be done on stackoverflow:
        # https://stackoverflow.com/questions/18715694/cmake-doesnt-find-boost-libraries-while-using-android-cmake-toolchain-file

        # It is assumed BOOST_INCLUDEDIR and BOOST_LIBRARYDIR are provided in the command line arguments to cmake
        # (see windows_build_ceda_sdk_android.bat)
        add_definitions(
            -DBOOST_UUID_RANDOM_PROVIDER_FORCE_POSIX
        )

        foreach(name system thread filesystem)
            message("Using custom build of boost for android for static library ${BOOST_LIBRARYDIR}/libboost_${name}.a")
            add_library(Boost::${name} STATIC IMPORTED)
            set_target_properties(Boost::${name} PROPERTIES
                    INTERFACE_INCLUDE_DIRECTORIES "${BOOST_INCLUDEDIR}"
                    IMPORTED_LOCATION "${BOOST_LIBRARYDIR}/libboost_${name}.a"
                    )
        endforeach()
    else()
        # Boost_ADDITIONAL_VERSIONS can be set to a list of boost versions not known to the cmake FindBoost.cmake module
        set(Boost_ADDITIONAL_VERSIONS "1.69.0" "1.69")

        #find_package(boost REQUIRED)
        set(Boost_USE_STATIC_LIBS ON) 
        set(Boost_USE_MULTITHREADED ON)  
        set(Boost_USE_STATIC_RUNTIME OFF) 
        find_package(Boost REQUIRED COMPONENTS atomic chrono system thread filesystem locale)

        message("Boost_FOUND = ${Boost_FOUND}")
        message("Boost_VERSION = ${Boost_VERSION}")
        message("Boost_LIB_VERSION = ${Boost_LIB_VERSION}")
        message("Boost_INCLUDE_DIRS = ${Boost_INCLUDE_DIRS}")
        message("Boost_LIBRARIES = ${Boost_LIBRARIES}")
    endif()
endfunction(cedaFindBoost)

# Avoid the need to add the current directory to the include path every time
# https://cmake.org/cmake/help/v3.11/variable/CMAKE_INCLUDE_CURRENT_DIR_IN_INTERFACE.html#variable:CMAKE_INCLUDE_CURRENT_DIR_IN_INTERFACE
# If this variable is enabled, CMake automatically adds for each shared library target, static library target, 
# module target and executable target, CMAKE_CURRENT_SOURCE_DIR and CMAKE_CURRENT_BINARY_DIR to the 
# INTERFACE_INCLUDE_DIRECTORIES target property. 
# By default CMAKE_INCLUDE_CURRENT_DIR_IN_INTERFACE is OFF.
set(CMAKE_INCLUDE_CURRENT_DIR_IN_INTERFACE ON)

####################################################################################################
# Allow a global list of ceda projects to be defined

set_property(GLOBAL PROPERTY GP_CEDA_PROJECTS "")
function(cedaAddProject arg)
    set_property(GLOBAL APPEND PROPERTY GP_CEDA_PROJECTS "${arg}")
endfunction()

####################################################################################################
# Allow a global list of projects that are dependent on ceda projects to be defined

set_property(GLOBAL PROPERTY GP_DEPENDENT_CEDA_PROJECTS "")
function(cedaAddDependentProject arg)
    set_property(GLOBAL APPEND PROPERTY GP_DEPENDENT_CEDA_PROJECTS "${arg}")
endfunction()

###################################################################################################
# Allow a global list of ceda source roots to be defined

set_property(GLOBAL PROPERTY GP_CEDA_SOURCE_ROOTS "")
function(cedaAddSourceRoot arg)
    set_property(GLOBAL APPEND PROPERTY GP_CEDA_SOURCE_ROOTS "${arg}")
endfunction()

###################################################################################################

macro(cedaSetSourceRoot arg)
	# Various values are saved in global properties:
	#
	#		GP_CEDA_XCPP
    #	    GP_CEDA_XCPPCONFIG
	#       GP_CEDA_CONFIG
	#       GP_CEDA_PLATFORM
	#
	# This is necessary because a subdirectory cmake scope may include CedaCommon.cmake and yet the 'last step' call-back
    # to cedaRunXcppOnWorkspace may occur in a parent scope	where the cmake variables set from CedaCommon.cmake may not be 
	# visible.

	set_property(GLOBAL PROPERTY GP_CEDA_XCPP ${Ceda_XCPP})
	set_property(GLOBAL PROPERTY GP_CEDA_XCPPCONFIG ${Ceda_XCPPCONFIG})

    # "Debug", "Release" or "RelWithDebInfo"
	set_property(GLOBAL PROPERTY GP_CEDA_CONFIG ${CMAKE_BUILD_TYPE})

    if("${CMAKE_SYSTEM_NAME}" STREQUAL "Windows")
        if("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "AMD64")
            if (${CMAKE_SIZEOF_VOID_P} EQUAL "8")
                set(cedaPlatform "Windows-x64")
            else()
                # If CMAKE_SIZEOF_VOID_P disagrees with CMAKE_SYSTEM_PROCESSOR then go by CMAKE_SIZEOF_VOID_P
                set(cedaPlatform "Windows-x86")
            endif()
        elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "x86")
            set(cedaPlatform "Windows-x86")
        else()
            set(cedaPlatform ${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR})
        endif()
    else()
        set(cedaPlatform ${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR})
    endif()

    set_property(GLOBAL PROPERTY GP_CEDA_PLATFORM ${cedaPlatform})

    set(LIBTYPE "")
    if (NOT BUILD_SHARED_LIBS)
        set(LIBTYPE "-static")
    endif()
	
    # For example CMAKE_BINARY_DIR = C:/cedanet/build-ceda-sdk-samples/Win64-Debug
    # So          CEDA_XCPP_ROOT   = C:/cedanet/build-ceda-sdk-samples/Win64-Debug/ceda-build/xcppsource/Win64/Release
	get_property(cedaConfig GLOBAL PROPERTY GP_CEDA_CONFIG)
    set(CEDA_XCPP_ROOT ${CMAKE_BINARY_DIR}/ceda-build/xcppsource/${cedaPlatform}/${cedaConfig})

    set(CEDA_SOURCE_ROOT "${arg}")
    cedaAddSourceRoot("${arg}")

    include_directories(${CEDA_XCPP_ROOT})
    include_directories(${CEDA_SOURCE_ROOT})
endmacro()

file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/ceda-build)


###################################################################################################
#
#                            +---------------------------+
#                            | editable sources cxObject |  ${editSourcePath}
#                            +---------------------------+
#                                         | 
#	 	 	 	 	                     \|/  add_custom_target(${name}.in SOURCES ${editSourceFiles})
#	 	 	 	 	                      |	
#                            +---------------------------+
#                            |       cxObject.in         |  ${name}.in
#                            +---------------------------+
#                                         | 
#	 	 	 	 	                     \|/  add_dependencies(CEDA_RUN_XCPP ${name}.in)
#	 	 	 	 	                      |	
#	add_executable(Xcpp ...)              |	
#      +---------+           +---------------------------+
#      |  Xcpp   |----->-----|       CEDA_RUN_XCPP       |  add_custom_target(CEDA_RUN_XCPP)
#      +---------+     |     +---------------------------+  add_custom_command(TARGET CEDA_RUN_XCPP COMMAND ... WORKING_DIRECTORY ... BYPRODUCTS ${sourceFiles})
#                      |                  |
#   add_dependencies(CEDA_RUN_XCPP Xcpp)  | 
#                                         |   set_source_files_properties(${sourceFiles} PROPERTIES GENERATED 1)
#	 	 	 	 	                     \|/  (BYPRODUCTS)
#	 	 	 	 	                      |	
#                            +---------------------------+
#                            |    generated C++ files    |  ${sourceFiles}
#                            +---------------------------+
#                                         | 
#	 	 	 	 	                     \|/   add_library(${name} SHARED ${sourceFiles})
#	 	 	 	 	                      |	
#                            +---------------------------+
#                            |       cxObject.dll        |  ${name}
#                            +---------------------------+

set_property(GLOBAL PROPERTY GP_CEDA_BYPRODUCTS "")
add_custom_target(CEDA_RUN_XCPP)

###################################################################################################
# cedaRunXcppOnWorkspace
#
# This is a private function which is called at the end of the configure step to set the command
# used to execute Xcpp to translate all the files in the ceda projects in the virtual tree

function(cedaRunXcppOnWorkspace)
    message("------------------------ last step -------------------------")

    get_property(cedaByProducts GLOBAL PROPERTY GP_CEDA_BYPRODUCTS)

    get_property(cedaProjects GLOBAL PROPERTY GP_CEDA_PROJECTS)
    get_property(cedaDependentProjects GLOBAL PROPERTY GP_DEPENDENT_CEDA_PROJECTS)
    get_property(cedaSourceRoots GLOBAL PROPERTY GP_CEDA_SOURCE_ROOTS)

    get_property(cedaHostOs GLOBAL PROPERTY GP_CEDA_HOST_OS)
    get_property(cedaHostOsVersion GLOBAL PROPERTY GP_CEDA_HOST_OS_VERSION)
    get_property(cedaTargetOs GLOBAL PROPERTY GP_CEDA_TARGET_OS)
    get_property(cedaTargetOsVersion GLOBAL PROPERTY GP_CEDA_TARGET_OS_VERSION)

	get_property(cedaPlatform GLOBAL PROPERTY GP_CEDA_PLATFORM)
	get_property(cedaConfig GLOBAL PROPERTY GP_CEDA_CONFIG)
	get_property(cedaXcpp GLOBAL PROPERTY GP_CEDA_XCPP)
	get_property(cedaXcppConfig GLOBAL PROPERTY GP_CEDA_XCPPCONFIG)
	
    message("cedaProjects = ${cedaProjects}")
    message("cedaDependentProjects = ${cedaDependentProjects}")
    message("cedaSourceRoots = ${cedaSourceRoots}")
    message("cedaPlatform = ${cedaPlatform}")
    message("cedaConfig = ${cedaConfig}")
	message("cedaXcpp = ${cedaXcpp}")
	message("cedaXcppConfig = ${cedaXcppConfig}")

    set(wslist "")
    foreach(s ${cedaProjects})
        if (NOT wslist STREQUAL "")
            set(wslist "${wslist} ")
        endif()
        set(wslist "${wslist}'${s}'")
    endforeach()

    set(vtlist "")
    foreach(s ${cedaSourceRoots})
        if (NOT vtlist STREQUAL "")
            set(vtlist "${vtlist} ")
        endif()
        set(vtlist "${vtlist}'${s}'")
    endforeach()

    # CMAKE_HOST_SYSTEM_NAME
    # Name of the OS CMake is running on.
    # On systems that have the uname command, this variable is set to the output of uname -s. 
    # Linux, Windows, and Darwin for Mac OS X are the values found on the big three operating systems.

    # CMAKE_SYSTEM_NAME
    # "Windows", "WindowsCE", "WindowsPhone", "WindowsStore", "Linux", "Darwin" or "Android"

    # CMAKE_HOST_SYSTEM_PROCESSOR
    # The name of the CPU CMake is running on.
    # On systems that support uname, this variable is set to the output of uname -p, 
    # on windows it is set to the value of the environment variable PROCESSOR_ARCHITECTURE (which can be AMD64, IA64 or x86).

	# CMAKE_CXX_COMPILER_ID
    # "MSVC", "GNU" or "Clang"

    # MSVC_VERSION
    # The version of Microsoft Visual C/C++ being used if any.
    # Known version numbers are:
    #   1200 = VS  6.0
    #   1300 = VS  7.0
    #   1310 = VS  7.1
    #   1400 = VS  8.0
    #   1500 = VS  9.0
    #   1600 = VS 10.0
    #   1700 = VS 11.0
    #   1800 = VS 12.0
    #   1900 = VS 14.0

    # platform='${cedaPlatform}'
    
    # Example 1:  Run windows_build_ceda_sdk_android.bat on a x64 machine:
    #    CMAKE_HOST_SYSTEM_NAME      = Windows
    #    CMAKE_HOST_SYSTEM_PROCESSOR = AMD64
    #    CMAKE_HOST_SYSTEM_VERSION   = 10.0.17134
    #    CMAKE_SYSTEM_NAME           = Android
    #    CMAKE_SYSTEM_PROCESSOR      = aarch64
    #    CMAKE_SYSTEM_VERSION        = 1
    #    CMAKE_CXX_COMPILER_ID       = Clang

    # Example 2:  Run windows_package_ceda_sdk.bat on a x64 machine:
    #    CMAKE_HOST_SYSTEM_NAME      = Windows
    #    CMAKE_HOST_SYSTEM_PROCESSOR = AMD64
    #    CMAKE_HOST_SYSTEM_VERSION   = 10.0.17134
    #    CMAKE_SYSTEM_NAME           = Windows
    #    CMAKE_SYSTEM_PROCESSOR      = AMD64
    #    CMAKE_SYSTEM_VERSION        = 10.0.17134
    #    CMAKE_CXX_COMPILER_ID       = MSVC
    #    MSVC_VERSION                = 1915
    set(defaultToDll false)
    if (BUILD_SHARED_LIBS)
        set(defaultToDll true)
    endif()
    
    add_custom_command(TARGET CEDA_RUN_XCPP 
				      COMMAND ${cedaXcpp}
                        HOST_SYSTEM_NAME='${CMAKE_HOST_SYSTEM_NAME}'
                        HOST_SYSTEM_PROCESSOR='${CMAKE_HOST_SYSTEM_PROCESSOR}'
                        HOST_SYSTEM_VERSION='${CMAKE_HOST_SYSTEM_VERSION}'
                        SYSTEM_NAME='${CMAKE_SYSTEM_NAME}'
                        SYSTEM_PROCESSOR='${CMAKE_SYSTEM_PROCESSOR}'
                        SYSTEM_VERSION='${CMAKE_SYSTEM_VERSION}'
                        CXX_COMPILER_ID='${CMAKE_CXX_COMPILER_ID}'
                        MSVC_VERSION='${MSVC_VERSION}'
                        PLATFORM='${cedaPlatform}'
                        Ceda_XCPPCONFIG='${cedaXcppConfig}'
                        ${cedaXcppConfig}/base.xcpp
                        mode=build
                        config='${cedaConfig}'
                        defaultToDll=${defaultToDll}
                        workspaces={${wslist}} virtualTree={${vtlist}}
                      WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/ceda-build
                      BYPRODUCTS ${cedaByProducts})

    if(TARGET Xcpp)
        # The executable Xcpp must be built before it can be used to translate files from xc++ to c++
        # Xcpp ---> CEDA_RUN_XCPP
        add_dependencies(CEDA_RUN_XCPP Xcpp)
    endif()

    foreach(s ${cedaDependentProjects})
		add_dependencies(${s} CEDA_RUN_XCPP)
    endforeach()

endfunction(cedaRunXcppOnWorkspace)

###################################################################################################
# This is a hack to ensure cedaRunXcppOnWorkspace() is called exactly at the end of the
# configure step.  It involves watching the variable CMAKE_BACKWARDS_COMPATIBILITY which happens
# to be accessed at this time.
# It is a little fragile, it is important to be sure nothing else accesses CMAKE_BACKWARDS_COMPATIBILITY
# See https://stackoverflow.com/questions/15760580/execute-command-or-macro-in-cmake-as-the-last-step-before-the-configure-step-f

set_property(GLOBAL PROPERTY GP_CEDA_HAS_CONFIGURED_XCPP FALSE)

function(EndOfConfigurationHook Variable Access)
    if(${Variable} STREQUAL CMAKE_BACKWARDS_COMPATIBILITY AND (${Access} STREQUAL UNKNOWN_READ_ACCESS OR ${Access} STREQUAL READ_ACCESS))
        get_property(v GLOBAL PROPERTY GP_CEDA_HAS_CONFIGURED_XCPP)
        if (NOT ${v})
            cedaRunXcppOnWorkspace()
            set_property(GLOBAL PROPERTY GP_CEDA_HAS_CONFIGURED_XCPP TRUE)
        endif()
    endif()
endfunction()

variable_watch(CMAKE_BACKWARDS_COMPATIBILITY EndOfConfigurationHook)

###################################################################################################
# cedaTarget

function(cedaTarget)
    cmake_parse_arguments(
        PARSED_ARGS					    # prefix of output variables
        "EXE;XCPP"	                    # list of names of the boolean arguments (only defined ones will be true)
        "NAMESPACE;PROJPATH"			# list of names of mono-valued arguments
        "LIBS;INCLUDES"	     		    # list of names of multi-valued arguments (output variables are lists)
        ${ARGN}						    # arguments of the function to parse, here we take the all original ones
    )
    # note: if it remains unparsed arguments, here, they can be found in variable PARSED_ARGS_UNPARSED_ARGUMENTS

    if(NOT PARSED_ARGS_PROJPATH)
        message(FATAL_ERROR "PROJPATH parameter required in call to cedaTarget")
    endif()

	set(projPath ${PARSED_ARGS_PROJPATH})
	set(libs ${PARSED_ARGS_LIBS})
	set(includes ${PARSED_ARGS_INCLUDES})

    cedaAddProject(${projPath})

    get_filename_component(name ${projPath} NAME)

	if (${PARSED_ARGS_EXE})
		set(targetType "executable")
	else()
		set(targetType "library")
	endif()

	message("------------------------------------------------- ${targetType} ${projPath} -------------------------------------------------")
	message("xcpp = ${PARSED_ARGS_XCPP}")
	message("libs = ${libs}")
	message("includes = ${includes}")

	if (${PARSED_ARGS_XCPP})
	    set(editSourcePath ${CEDA_SOURCE_ROOT}/${projPath})
		set(sourcePath ${CEDA_XCPP_ROOT}/${projPath})

		message("editSourcePath = ${editSourcePath}")
		message("sourcePath = ${sourcePath}")

		file(GLOB_RECURSE editSourceFiles ${editSourcePath}/*.h ${editSourcePath}/*.cpp)

		# Calculate the	list of paths of the read-only source files which are output by Xcpp
		set(sourceFiles "")		# create empty list
		foreach(esf ${editSourceFiles})
			string(REPLACE ${editSourcePath} ${sourcePath} out ${esf})
  		    list(APPEND sourceFiles ${out})
		endforeach(esf)

        add_custom_target(${name}.in SOURCES ${editSourceFiles})
		add_dependencies(CEDA_RUN_XCPP ${name}.in)

        # Make sure add_executable() / add_library() don't balk because they work with generated files
        set_source_files_properties(${sourceFiles} PROPERTIES GENERATED 1)

        # Allow BYPRODUCTS to be specified for the command to run Xcpp, to ensure Ninga generator accounts for which
        # generated files have changed when it builds targets that depend on those generated files.
        set_property(GLOBAL APPEND PROPERTY GP_CEDA_BYPRODUCTS ${sourceFiles})

	else()
		set(sourcePath ${CEDA_SOURCE_ROOT}/${projPath})
		file(GLOB_RECURSE sourceFiles ${sourcePath}/*.h ${sourcePath}/*.cpp)
	endif()

    list (LENGTH sourceFiles numSourceFiles)
    message("${numSourceFiles} source files")

	if (${PARSED_ARGS_EXE})
	    add_executable(${name} ${sourceFiles})
	else()
	    add_library(${name} ${sourceFiles})

        if(PARSED_ARGS_NAMESPACE)
            # allow qualified name of library to be used
            message("Allowing ${PARSED_ARGS_NAMESPACE}::${name} to be used as alias of ${name}")
            add_library(${PARSED_ARGS_NAMESPACE}::${name} ALIAS ${name})
        endif()

	endif()

	# The scoping is private because we require other dependent targets to used fully qualified include paths
	# (E.g. #include "Ceda/cxUtils/Archive.h")
	target_include_directories(${name} PRIVATE ${sourcePath} ${sourcePath}/src ${includes})

	target_link_libraries(${name} PUBLIC ${libs})

    cedaSetDefaultsForTarget(${name})

	if (NOT ${PARSED_ARGS_EXE})
        if (NOT BUILD_SHARED_LIBS)        
            # It is important that this macro be public so it is visible to all users of the library
            # (i.e. so that users of the library see it as a static library or else they might apply __declspec(dllimport)
            #  under the MSVC compiler and end up with unreslved symbol linker errors)
            target_compile_definitions(${name} PUBLIC ${name}_STATIC_LIBRARY)
        endif()

		target_compile_definitions(${name} PRIVATE ${name}_EXPORTS)

		# Creates a header file which helps switch between building shared and static libraries
		#include(GenerateExportHeader)
		#generate_export_header(${name})
        #generate_export_header(${name}
        #    EXPORT_MACRO_NAME ${name}_API
        #    EXPORT_FILE_NAME ${CMAKE_BINARY_DIR}/include/${name}/core/common.h
        #)

	endif()
endfunction(cedaTarget)

###################################################################################################
# showCommonVars

function(showCommonVars)
    message("--------------- common vars -------------------")

    message("PROJECT_NAME                = ${PROJECT_NAME}")
    message("PROJECT_SOURCE_DIR          = ${PROJECT_SOURCE_DIR}")

    message("CMAKE_PROJECT_VERSION       = ${CMAKE_PROJECT_VERSION}")
    message("CMAKE_PROJECT_VERSION_MAJOR = ${CMAKE_PROJECT_VERSION_MAJOR}")
    message("CMAKE_PROJECT_VERSION_MINOR = ${CMAKE_PROJECT_VERSION_MINOR}")
    message("CMAKE_PROJECT_VERSION_PATCH = ${CMAKE_PROJECT_VERSION_PATCH}")

    message("PROJECT_VERSION             = ${PROJECT_VERSION}")
    message("PROJECT_VERSION_MAJOR       = ${PROJECT_VERSION_MAJOR}")
    message("PROJECT_VERSION_MINOR       = ${PROJECT_VERSION_MINOR}")
    message("PROJECT_VERSION_PATCH       = ${PROJECT_VERSION_PATCH}")

    message("CMAKE_SOURCE_DIR            = ${CMAKE_SOURCE_DIR}")
    message("CMAKE_CURRENT_SOURCE_DIR    = ${CMAKE_CURRENT_SOURCE_DIR}")
    message("CMAKE_BINARY_DIR            = ${CMAKE_BINARY_DIR}")
    message("CMAKE_CURRENT_BINARY_DIR    = ${CMAKE_CURRENT_BINARY_DIR}")

    message("EXECUTABLE_OUTPUT_PATH      = ${EXECUTABLE_OUTPUT_PATH}")
    message("LIBRARY_OUTPUT_PATH         = ${LIBRARY_OUTPUT_PATH}")
    
    message("CMAKE_HOST_SYSTEM_NAME      = ${CMAKE_HOST_SYSTEM_NAME}")
    message("CMAKE_HOST_SYSTEM_PROCESSOR = ${CMAKE_HOST_SYSTEM_PROCESSOR}")
    message("CMAKE_HOST_SYSTEM_VERSION   = ${CMAKE_HOST_SYSTEM_VERSION}")

    message("CMAKE_SYSTEM_NAME           = ${CMAKE_SYSTEM_NAME}")
    message("CMAKE_SYSTEM_PROCESSOR      = ${CMAKE_SYSTEM_PROCESSOR}")
    message("CMAKE_SYSTEM_VERSION        = ${CMAKE_SYSTEM_VERSION}")
    message("CMAKE_SIZEOF_VOID_P         = ${CMAKE_SIZEOF_VOID_P}")

    # The version of Microsoft Visual C/C++ being used if any.
    # Known version numbers are:
    #   1200 = VS  6.0
    #   1300 = VS  7.0
    #   1310 = VS  7.1
    #   1400 = VS  8.0
    #   1500 = VS  9.0
    #   1600 = VS 10.0
    #   1700 = VS 11.0
    #   1800 = VS 12.0
    #   1900 = VS 14.0
    message("MSVC_VERSION                = ${MSVC_VERSION}")
    
    # Default value for the ANDROID_API target property.
    message("CMAKE_ANDROID_API           = ${CMAKE_ANDROID_API}")

    # Default value for the ANDROID_GUI target property of executables
    message("CMAKE_ANDROID_GUI           = ${CMAKE_ANDROID_GUI}")

    # Use the install path for the RPATH
    # Normally CMake uses the build tree for the RPATH when building executables etc on systems that 
    # use RPATH. When the software is installed the executables etc are relinked by CMake to have 
    # the install RPATH. If this variable is set to true then the software is always built with the 
    # install path for the RPATH and does not need to be relinked when installed.
    message("CMAKE_BUILD_WITH_INSTALL_RPATH = ${CMAKE_BUILD_WITH_INSTALL_RPATH}")

    # Output directory for MS debug symbol .pdb files generated by the compiler while building source files.
    # This variable is used to initialize the COMPILE_PDB_OUTPUT_DIRECTORY property on all the targets.    
    message("CMAKE_COMPILE_PDB_OUTPUT_DIRECTORY = ${CMAKE_COMPILE_PDB_OUTPUT_DIRECTORY}")

    message("UNIX                        = ${UNIX}")
    message("WIN32                       = ${WIN32}")
    message("APPLE                       = ${APPLE}")
    message("MSYS                        = ${MSYS}")
    message("CMAKE_CXX_COMPILER_ID       = ${CMAKE_CXX_COMPILER_ID}")
    message("CMAKE_CXX_COMPILER_VERSION  = ${CMAKE_CXX_COMPILER_VERSION}")
    message("CMAKE_SYSROOT               = ${CMAKE_SYSROOT}")
    message("CMAKE_FIND_ROOT_PATH        = ${CMAKE_FIND_ROOT_PATH}")
    message("CMAKE_TOOLCHAIN_FILE        = ${CMAKE_TOOLCHAIN_FILE}")
    message("CMAKE_PREFIX_PATH           = ${CMAKE_PREFIX_PATH}")
    message("CMAKE_SYSTEM_PREFIX_PATH    = ${CMAKE_SYSTEM_PREFIX_PATH}")
    message("CMAKE_INCLUDE_PATH          = ${CMAKE_INCLUDE_PATH}")
    message("CMAKE_SYSTEM_INCLUDE_PATH   = ${CMAKE_SYSTEM_INCLUDE_PATH}")
    message("CMAKE_FRAMEWORK_PATH        = ${CMAKE_FRAMEWORK_PATH}")
    message("CMAKE_SYSTEM_FRAMEWORK_PATH = ${CMAKE_SYSTEM_FRAMEWORK_PATH}")
    message("CMAKE_LIBRARY_ARCHITECTURE  = ${CMAKE_LIBRARY_ARCHITECTURE}")

    message("CMAKE_INSTALL_PREFIX        = ${CMAKE_INSTALL_PREFIX}")
    message("CMAKE_INSTALL_LIBDIR        = ${CMAKE_INSTALL_LIBDIR}")
    message("CMAKE_INSTALL_BINDIR        = ${CMAKE_INSTALL_BINDIR}")
    message("CMAKE_INSTALL_INCLUDEDIR    = ${CMAKE_INSTALL_INCLUDEDIR}")

    message("CMAKE_MODULE_PATH           = ${CMAKE_MODULE_PATH}")
    message("CMAKE_BUILD_TYPE            = ${CMAKE_BUILD_TYPE}")
    message("CMAKE_CONFIGURATION_TYPES   = ${CMAKE_CONFIGURATION_TYPES}")

    message("VCPKG_TARGET_TRIPLET        = ${VCPKG_TARGET_TRIPLET}")
    message("BUILD_SHARED_LIBS           = ${BUILD_SHARED_LIBS}")

    message("CMAKE_CXX_FLAGS                = ${CMAKE_CXX_FLAGS}")
    message("CMAKE_CXX_FLAGS_DEBUG          = ${CMAKE_CXX_FLAGS_DEBUG}")
    message("CMAKE_CXX_FLAGS_RELEASE        = ${CMAKE_CXX_FLAGS_RELEASE}")
    message("CMAKE_CXX_FLAGS_RELWITHDEBINFO = ${CMAKE_CXX_FLAGS_RELWITHDEBINFO}")

    message("CMAKE_C_FLAGS                  = ${CMAKE_C_FLAGS}")
    message("CMAKE_C_FLAGS_DEBUG            = ${CMAKE_C_FLAGS_DEBUG}")
    message("CMAKE_C_FLAGS_RELEASE          = ${CMAKE_C_FLAGS_RELEASE}")
    message("CMAKE_C_FLAGS_RELWITHDEBINFO   = ${CMAKE_C_FLAGS_RELWITHDEBINFO}")

    message("CMAKE_EXE_LINKER_FLAGS         = ${CMAKE_EXE_LINKER_FLAGS}")
    
endfunction(showCommonVars)

###################################################################################################
# showFindVars

function(showFindVars)
    message("CMAKE_PREFIX_PATH              = ${CMAKE_PREFIX_PATH}")
    message("CMAKE_FRAMEWORK_PATH           = ${CMAKE_FRAMEWORK_PATH}")
    message("CMAKE_APPBUNDLE_PATH           = ${CMAKE_APPBUNDLE_PATH}")
    message("CMAKE_SYSTEM_PREFIX_PATH       = ${CMAKE_SYSTEM_PREFIX_PATH}")
    message("CMAKE_SYSTEM_FRAMEWORK_PATH    = ${CMAKE_SYSTEM_FRAMEWORK_PATH}")
    message("CMAKE_SYSTEM_APPBUNDLE_PATH    = ${CMAKE_SYSTEM_APPBUNDLE_PATH}")
    message("CMAKE_FIND_ROOT_PATH           = ${CMAKE_FIND_ROOT_PATH}")
    message("CMAKE_SYSROOT                  = ${CMAKE_SYSROOT}")
    message("CMAKE_STAGING_PREFIX           = ${CMAKE_STAGING_PREFIX}")
endfunction(showFindVars)

###################################################################################################
# showCedaVars

function(showCedaVars)
    message("--------------- CEDA vars -------------------")

    message("Ceda_ROOT               = ${Ceda_ROOT}")
    message("Ceda_CMAKE              = ${Ceda_CMAKE}")

    message("Ceda_SDK                = ${Ceda_SDK}")
    message("Ceda_BIN                = ${Ceda_BIN}")
    message("Ceda_HOST_BIN           = ${Ceda_HOST_BIN}")
    message("Ceda_LIB                = ${Ceda_LIB}")
    message("Ceda_INCLUDE            = ${Ceda_INCLUDE}")
    message("Ceda_EXPORT             = ${Ceda_EXPORT}")
    message("Ceda_XCPP               = ${Ceda_XCPP}")
    message("Ceda_XCPPCONFIG         = ${Ceda_XCPPCONFIG}")
    message("Ceda_LIBRARIES          = ${Ceda_LIBRARIES}")

    # The <Package>_FOUND variable is set to true or false, depending on whether the package was found.
    message("Ceda_FOUND              = ${Ceda_FOUND}")

    # The <Package>_DIR cache variable is set to the location of the package configuration file.
    message("Ceda_DIR                = ${Ceda_DIR}")

    # Full provided version string
    message("Ceda_VERSION            = ${Ceda_VERSION}")

    # Major version if provided, else 0
    message("Ceda_VERSION_MAJOR      = ${Ceda_VERSION_MAJOR}")

    # Minor version if provided, else 0
    message("Ceda_VERSION_MINOR      = ${Ceda_VERSION_MINOR}")

    # Patch version if provided, else 0
    message("Ceda_VERSION_PATCH      = ${Ceda_VERSION_PATCH}")

    # Tweak version if provided, else 0
    message("Ceda_VERSION_TWEAK      = ${Ceda_VERSION_TWEAK}")

    # Number of version components, 0 to 4
    message("Ceda_VERSION_COUNT      = ${Ceda_VERSION_COUNT}")

    # CedaSdk_VERSION            = 0.0.0
    # CedaSdk_VERSION_MAJOR      = 0
    # CedaSdk_VERSION_MINOR      = 0
    # CedaSdk_VERSION_PATCH      = 0
    message("CedaSdk_VERSION         = ${CedaSdk_VERSION}")
    message("CedaSdk_VERSION_MAJOR   = ${CedaSdk_VERSION_MAJOR}")
    message("CedaSdk_VERSION_MINOR   = ${CedaSdk_VERSION_MINOR}")
    message("CedaSdk_VERSION_PATCH   = ${CedaSdk_VERSION_PATCH}")

endfunction(showCedaVars)

###################################################################################################
# cedaSetInstallDefaults

macro(cedaSetInstallDefaults)
    # Given the name Ceda, cmake looks for a file called CedaConfig.cmake or ceda-config.cmake. The full set of locations 
    # is specified in the find_package() command documentation. One place it looks is:
    #     <prefix>/lib/cmake/Ceda*/
    #     <prefix>/cmake/
    # where Ceda* is a case-insensitive globbing expression. In our example the globbing expression will match 
    # <prefix>/lib/cmake/ceda-1.2 and the package configuration file will be found.
    #set(INSTALL_CONFIGDIR "lib/cmake/Ceda")
    set(INSTALL_CONFIGDIR "cmake")

    if ("${CMAKE_BUILD_TYPE}" MATCHES "Debug")
        set(CMAKE_INSTALL_BINDIR "debug/bin")
        set(CMAKE_INSTALL_LIBDIR "debug/lib")
    else()
        set(CMAKE_INSTALL_BINDIR "bin")     # redundant - this is the default
        set(CMAKE_INSTALL_LIBDIR "lib")     # redundant - this is the default
    endif()
    set(CMAKE_INSTALL_INCLUDEDIR "include")     # redundant - this is the default
    set(CMAKE_INSTALL_EXPORTDIR "export")
endmacro()

###################################################################################################
# cedaWriteCmakeVarsToFile

function (cedaWriteCmakeVarsToFile pathToFile)
    file(WRITE ${pathToFile} "")
    get_cmake_property(Vars VARIABLES)
    foreach(Var ${Vars})
        if (NOT ("${Var}" MATCHES "^ARG") AND 
            NOT ("${Var}" MATCHES "^_") AND
            NOT ("${${Var}}" MATCHES "\"") AND
            NOT ("${${Var}}" MATCHES "\\\\") AND
            NOT ("${Var}" STREQUAL "CMAKE_CXX_COMPILER_PRODUCED_OUTPUT") AND
            NOT ("${${Var}}" STREQUAL ""))
            file(APPEND ${pathToFile} "set(${Var} \"${${Var}}\")\n")
        endif()
    endforeach()
endfunction()

###################################################################################################
# cedaAddPythonPackage

function(cedaAddPythonPackage)
    cmake_parse_arguments(
        PARSED_ARGS					    # prefix of output variables
        ""								# list of names of the boolean arguments (only defined ones will be true)
        "NAME;SOURCEDIR"				# list of names of mono-valued arguments
        "SOURCEFILES;LIBS;GLOBS"	    # list of names of multi-valued arguments (output variables are lists)
        ${ARGN}						    # arguments of the function to parse, here we take the all original ones
    )

	if(NOT PARSED_ARGS_NAME)
        message(FATAL_ERROR "NAME parameter required in call to cedaAddPythonPackage")
    endif()

	if(NOT PARSED_ARGS_SOURCEDIR)
        message(FATAL_ERROR "SOURCEDIR parameter required in call to cedaAddPythonPackage")
    endif()

	if(NOT PARSED_ARGS_SOURCEFILES)
        message(FATAL_ERROR "SOURCEFILES parameter required in call to cedaAddPythonPackage")
    endif()

	if(NOT PARSED_ARGS_LIBS)
        message(FATAL_ERROR "LIBS parameter required in call to cedaAddPythonPackage")
    endif()

	message(NAME = ${PARSED_ARGS_NAME})

	message(SOURCEDIR = ${PARSED_ARGS_SOURCEDIR})
	
	foreach(sourceFile ${PARSED_ARGS_SOURCEFILES})
		message(SOURCEFILE = ${sourceFile})
	endforeach()

	foreach(lib ${PARSED_ARGS_LIBS})
		message(LIB = ${lib})
	endforeach()

	set(packageName ${PARSED_ARGS_NAME})
	set(pathToSrcPackage ${PARSED_ARGS_SOURCEDIR})
	set(pathToDstPackage "${CMAKE_CURRENT_BINARY_DIR}/${packageName}_package")

    cedaWriteCmakeVarsToFile(${CMAKE_CURRENT_BINARY_DIR}/vars.cmake)
	
	file(MAKE_DIRECTORY ${pathToDstPackage}/${packageName}/lib)

	set(wheelDeps "")		# create empty list

	foreach(f ${PARSED_ARGS_SOURCEFILES})
        add_custom_command(
			OUTPUT ${pathToDstPackage}/${f}
			DEPENDS ${pathToSrcPackage}/${f}
			COMMENT "Copying ${pathToSrcPackage}/${f} to ${pathToDstPackage}/${f}"
			COMMAND ${CMAKE_COMMAND} -DINPUTFILE=${pathToSrcPackage}/${f} 
                                     -DOUTPUTFILE=${pathToDstPackage}/${f}
                                     -DVARSFILE=${CMAKE_CURRENT_BINARY_DIR}/vars.cmake
                                     -P ${Ceda_CMAKE}/ConfigureFile.cmake)
		list(APPEND wheelDeps ${pathToDstPackage}/${f})
	endforeach()

    set(counter "0")
    foreach(f ${PARSED_ARGS_GLOBS})
        # A witness is needed for copying files determined by a glob expression because
        # we don't know what files are copied at configure time.
        set(witness ${CMAKE_CURRENT_BINARY_DIR}/copied_glob_${counter}_witness)

        # Increment the counter
        math(EXPR counter "${counter}+1")

        add_custom_command(
			OUTPUT ${witness}
			COMMENT "Copying files matching ${f} to ${pathToDstPackage}/${packageName}/lib" 
			COMMAND ${CMAKE_COMMAND} -DFILES_TO_COPY=${f} 
                                     -DDEST_FOLDER=${pathToDstPackage}/${packageName}/lib
                                     -P ${Ceda_CMAKE}/GlobRecurseFileCopy.cmake
			COMMAND ${CMAKE_COMMAND} -E touch ${witness})
		list(APPEND wheelDeps ${witness})
    endforeach()

    foreach(tgt ${PARSED_ARGS_LIBS})
		# A witness file is used for the command to copy the shared library file
		# This is needed because the name of the shared library file involves the generator expression 
		# $<TARGET_FILE:${tgt}> and this cannot be evaluated at configure time
		set(witness ${CMAKE_CURRENT_BINARY_DIR}/copied_${tgt}_witness)
        add_custom_command(
			OUTPUT ${witness}
			DEPENDS ${tgt}
			COMMENT "Copying ${tgt} library to ${pathToDstPackage}/${packageName}/lib" 
            COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:${tgt}> ${pathToDstPackage}/${packageName}/lib
			COMMAND ${CMAKE_COMMAND} -E touch ${witness})
		list(APPEND wheelDeps ${witness})
    endforeach()

	# A witness file is used for the command to create and install the wheel file
	# This is needed because the name of the wheel file is not known at configure time
	add_custom_command(
		OUTPUT ${pathToDstPackage}/dist/installed_witness
		DEPENDS ${wheelDeps}
		COMMENT "Creating and installing ${packageName} wheel" 
		COMMAND ${CMAKE_COMMAND} -DPython2_EXECUTABLE=${Python2_EXECUTABLE} -DPACKAGE=${pathToDstPackage} -P ${Ceda_CMAKE}/CreateAndInstallWheel.cmake)

	add_custom_target(${packageName} ALL DEPENDS ${pathToDstPackage}/dist/installed_witness)
endfunction()

###################################################################################################
# cedaAddJni

# Example:
#       pythonPackageName = pypizza
#       cppJniLib = pizza_jni
#       javaJniJar = PizzaJni
#       generateJniPythonScript = ${CMAKE_CURRENT_SOURCE_DIR}/generate_pizza_jni_wrappers.py
#       cppLibs = "MyCompany::Time;MyCompany::Shapes;MyCompany::Pizza"

function(cedaAddJni pythonPackageName cppJniLib javaJniJar generateJniPythonScript cppLibs)
    find_package(Java REQUIRED)
    find_package(JNI REQUIRED)

    # Path to witness file which is touched after all the .java and .cpp JNI files have been 
    # created by the python script
    set(generate_jni_files_witness ${CMAKE_CURRENT_BINARY_DIR}/generated_jni_files_witness)

    # Paths for where the generated .cpp and .java files will be written
    set(cppFolderPath ${CMAKE_CURRENT_BINARY_DIR}/cpp)
    set(javaFolderPath ${CMAKE_CURRENT_BINARY_DIR}/java)

    # These directories must be created at configure time, because the python script doesn't create them
    file(MAKE_DIRECTORY ${cppFolderPath})
    file(MAKE_DIRECTORY ${javaFolderPath})
    
    # JniFiles.cmake is a generated file which is checked into the source tree in the repo, and only updated
    # when it changes.
    #
    # It sets the variable 'jniCppFiles' to a list of relative paths of the generated JNI cpp files.
    #
    # This is done so we have the list of paths to the generated files at configure time, making it possible 
    # for this CMakeLists.txt file to provide the list of paths to the generated files in the call to 
    # add_library (see below) for the jni library.
    #
    # This avoids the need for an <a href="https://cmake.org/cmake/help/latest/module/ExternalProject.html">ExternalProject</a>
    # as discussed here: <a href="https://stackoverflow.com/questions/4222326/cmake-compiling-generated-files">CMake Compiling Generated Files</a>
    if (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/JniFiles.cmake)
        include(${CMAKE_CURRENT_SOURCE_DIR}/JniFiles.cmake)
    else()
        message("ERROR : ${CMAKE_CURRENT_SOURCE_DIR}/JniFiles.cmake doesn't exist - run the build to create it then reconfigure")
        set(jniCppFiles dummy.cpp)
        set(jniJavaFiles dummy.java)
    endif()

    # Calculate jnifullPathCppFiles to be a list of full paths to the generated cpp files by prepending 
    # ${cppFolderPath} to each relative path in ${jniCppFiles}
    set(jnifullPathCppFiles "")
    foreach(f ${jniCppFiles})
	    list(APPEND jnifullPathCppFiles ${cppFolderPath}/${f})
    endforeach()

    set(jnifullPathJavaFiles "")
    foreach(f ${jniJavaFiles})
	    list(APPEND jnifullPathJavaFiles ${javaFolderPath}/${f})
    endforeach()

    # Without this add_library() may balk at configure time if the generated files don't exist yet
    # (since they are only created at build time).
    set_source_files_properties(${jnifullPathCppFiles} PROPERTIES GENERATED 1)
    set_source_files_properties(${jnifullPathJavaFiles} PROPERTIES GENERATED 1)

    # Add command to run the python script ${generateJniPythonScript} to generate the JNI files.
    add_custom_command(
	    OUTPUT ${generate_jni_files_witness}
        BYPRODUCTS ${jnifullPathCppFiles} ${jnifullPathJavaFiles}
	    DEPENDS ${generateJniPythonScript} ${pythonPackageName}
	    COMMENT "Running ${generateJniPythonScript} ${javaFolderPath} ${cppFolderPath}"
        COMMAND ${Python2_EXECUTABLE} ${generateJniPythonScript} ${javaFolderPath} ${cppFolderPath}
        COMMAND ${CMAKE_COMMAND} -DCPP_PATH=${cppFolderPath} -DJAVA_PATH=${javaFolderPath} -DOUPUT_PATH=${CMAKE_CURRENT_SOURCE_DIR}/JniFiles.cmake -P ${Ceda_CMAKE}/GlobJniFiles.cmake
        COMMAND ${CMAKE_COMMAND} -E touch ${generate_jni_files_witness})

    add_custom_target(GenScriptTarget_${javaJniJar} DEPENDS ${generate_jni_files_witness})

    add_library(${cppJniLib} SHARED ${jnifullPathCppFiles})

    cedaAddDependentProject(${cppJniLib})
    cedaSetDefaultsForTarget(${cppJniLib})

    target_include_directories(${cppJniLib} PRIVATE ${JNI_INCLUDE_DIRS})

    target_link_libraries(${cppJniLib} Ceda::cxUtils Ceda::cxObject Ceda::cxPersistStore Ceda::cxJava ${cppLibs})

    ###################################################################################################
    # Create ${javaJniJar}.jar

    include(UseJava)

    set(CMAKE_JAVA_COMPILE_FLAGS "-source" "1.8" "-target" "1.8")

    # This command creates the file ${javaJniJar}.jar.
    set(CMAKE_JNI_TARGET TRUE)
    add_jar(${javaJniJar} SOURCES ${jnifullPathJavaFiles})

    # Make sure the .java files are generated before they are compiled into a jar file
    add_dependencies(${javaJniJar} GenScriptTarget_${javaJniJar})

endfunction()