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