110 cedaTarget

The cedaTarget function is used to add a target which uses CEDA.

For example the library cxUtils is added like this:


    cedaTarget(NAMESPACE Ceda PROJPATH Ceda/cxUtils)
    target_link_libraries(cxUtils PRIVATE Boost::system Boost::thread Boost::filesystem)

For an executable target like Xcpp you should pass the EXE flag


    cedaTarget(EXE PROJPATH Ceda/Xcpp)
    target_link_libraries(Xcpp PUBLIC cxUtils cxMacroExpander cxBuild)

For a target written in the Xc++ language you pass the XCPP flag:


    cedaTarget(XCPP NAMESPACE Ceda PROJPATH Ceda/cxObject)
    linkTargetWithFfi(cxObject)
    target_link_libraries(cxObject PUBLIC cxUtils cxThread PRIVATE Boost::system)

Path to the root of all the source code

When cedaTarget is called it is assumed CEDA_SOURCE_ROOT has been set to the path to the root of all the source code. For example:

    CEDA_SOURCE_ROOT = C:/cedanet/repos/ceda-core

List of paths to the editable source files

cedaTarget uses a recursive file glob command to set editSourceFiles to a list of paths to all the editable source files for the library or executable which is being added by the call to cedaTarget:


    set(editSourcePath ${CEDA_SOURCE_ROOT}/${projPath})

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

This results in a list of absolute paths to the editable source files, for example:

    editSourcePath = C:/cedanet/repos/ceda-core/Ceda/cxObject

    editSourceFiles =
        C:/cedanet/repos/ceda-core/Ceda/../Ceda/cxObject/Array.h;
        C:/cedanet/repos/ceda-core/Ceda/../Ceda/cxObject/AutoUnlocker.h;
        C:/cedanet/repos/ceda-core/Ceda/../Ceda/cxObject/CSpace.h;
        ...
        C:/cedanet/repos/ceda-core/Ceda/../Ceda/cxObject/src/TypeOps.cpp;
        C:/cedanet/repos/ceda-core/Ceda/../Ceda/cxObject/src/TypeOpsRegistry.cpp;
        C:/cedanet/repos/ceda-core/Ceda/../Ceda/cxObject/src/WCSpace.cpp;
        C:/cedanet/repos/ceda-core/Ceda/../Ceda/cxObject/src/cxObject.cpp;
        C:/cedanet/repos/ceda-core/Ceda/../Ceda/cxObject/unique_interface_ptr.h

Path to the root of all the generated source code

When cedaTarget is called it is assumed CEDA_XCPP_ROOT has been set to the path to the root of all the generated source code. For example:

    CEDA_XCPP_ROOT = C:/cedanet/build-ceda-core/windows-x64-debug/ceda-build/xcppsource/windows-x64/Debug

todo: This path is a bit over the top. It is pointless having the folders windows-x64 and Debug under xcppsource, since we are already under windows-x64-debug. Also why do we have ceda-build?

List of paths to the generated source files

Since we know that Xcpp generates an output file under a folder named xcppsource for each input file, we can calculate the set of paths to the generated files:


    set(sourcePath ${CEDA_XCPP_ROOT}/${projPath})

    # 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)

This results in a list of absolute paths to the generated source files, for example:

    sourcePath = C:/cedanet/build-ceda-core/windows-x64-debug/ceda-build/xcppsource/windows-x64/Debug/Ceda/cxObject

    sourceFiles =
        C:/cedanet/build-ceda-core/windows-x64-debug/ceda-build/xcppsource/windows-x64/Debug/Ceda/cxObject/Array.h;
        C:/cedanet/build-ceda-core/windows-x64-debug/ceda-build/xcppsource/windows-x64/Debug/Ceda/cxObject/AutoUnlocker.h;
        C:/cedanet/build-ceda-core/windows-x64-debug/ceda-build/xcppsource/windows-x64/Debug/Ceda/cxObject/CSpace.h;
        ...
        C:/cedanet/build-ceda-core/windows-x64-debug/ceda-build/xcppsource/windows-x64/Debug/Ceda/cxObject/src/TypeOps.cpp;
        C:/cedanet/build-ceda-core/windows-x64-debug/ceda-build/xcppsource/windows-x64/Debug/Ceda/cxObject/src/TypeOpsRegistry.cpp;
        C:/cedanet/build-ceda-core/windows-x64-debug/ceda-build/xcppsource/windows-x64/Debug/Ceda/cxObject/src/WCSpace.cpp;
        C:/cedanet/build-ceda-core/windows-x64-debug/ceda-build/xcppsource/windows-x64/Debug/Ceda/cxObject/src/cxObject.cpp;
        C:/cedanet/build-ceda-core/windows-x64-debug/ceda-build/xcppsource/windows-x64/Debug/Ceda/cxObject/unique_interface_ptr.h

Telling CMake that the generated source files are generated

Consider the case where the library cxObject is to be built from the generated C++ files:

CMake balks when add_library is called if it finds that the generated C++ files don't exist at configure time. This is solved by telling CMake that these files are generated:


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

Values of variables when cedaTarget is called

When cedaTarget is called to add the cxObject library we get:

    PARSED_ARGS_NAMESPACE = Ceda
    name = cxObject
    targetType = library
    projPath = Ceda/cxObject
    xcpp = TRUE
    libs = [empty]
    includes = [empty]

Picture showing the dependencies

In the following diagram the blue boxes are targets and the green boxes are lists of files.

It is recommended to study the following:

Proposal

We shall use a witness file for running Xcpp


    add_custom_command(
        OUTPUT run_xcpp_witness.txt
        BYPRODUCTS ...
        COMMAND ...
        COMMAND ${CMAKE_COMMAND} -E touch run_xcpp_witness.txt
        DEPENDS ...
    )

    add_custom_target(CEDA_RUN_XCPP DEPENDS run_xcpp_witness.txt)

Note the following

  • We still have a custom target named CEDA_RUN_XCPP but it doesn't have an associated command
  • To express the dependency of running Xcpp on building Xcpp we add Xcpp into the DEPENDS list
  • To express the dependency of running Xcpp on all the editable source files we add cxObject.in etc to the DEPENDS list
  • All the generated C++ files must be listed in the BYPRODUCTS list
  • Note that Xcpp won't even run if the output witness file is not younger than any of the dependencies
  • For the cedaDependentProjects, a dependency on the witness file is declared.

cedaTarget


function(cedaTarget)
    cmake_parse_arguments(
        PARSED_ARGS					    # prefix of output variables
        "UTIL;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")
    elseif(${PARSED_ARGS_UTIL})
		set(targetType "utility")
	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})

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

		# 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})
        set_property(GLOBAL APPEND PROPERTY GP_RUN_XCPP_DEPS "${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 ${sourcePath}/*.py ${sourcePath}/*.java)
	endif()

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

	if (NOT (${PARSED_ARGS_UTIL}))
	    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()
    endif()
endfunction(cedaTarget)