Skip to content

Commit

Permalink
CommonGraph: improve graph output and options, fix recursion
Browse files Browse the repository at this point in the history
New ${PROJECT_NAME}_IS_METAPROJECT option in SubProject.cmake to
generate graphs without a top-level item for meta-projects that
don't call common_find_package(), such as VizStable.
  • Loading branch information
Raphael Dumusc committed Jun 2, 2017
1 parent 8806f56 commit 0e90645
Show file tree
Hide file tree
Showing 3 changed files with 145 additions and 88 deletions.
22 changes: 19 additions & 3 deletions CommonFindPackage.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -85,20 +85,36 @@ macro(common_find_package Package_Name)
${__find_quiet})
endif()

# Check find operation result and add graph dependency

if(${Package_Name}_IS_SUBPROJECT)
set(_is_subproject SOURCE)
else()
set(_is_subproject)
endif()

if(__pkg_REQUIRED) # required find
if((NOT ${Package_Name}_FOUND) AND (NOT ${PACKAGE_NAME}_FOUND))
if(${Package_Name}_FOUND OR ${PACKAGE_NAME}_FOUND)
common_graph_dep(${PROJECT_NAME} ${Package_Name} ${_is_subproject} REQUIRED)
else()
common_graph_dep(${PROJECT_NAME} ${Package_Name} ${_is_subproject} REQUIRED NOTFOUND)
if(CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR)
message(FATAL_ERROR "Not configured ${PROJECT_NAME}: Required ${Package_Name} not found")
else()
message(STATUS "SKIP ${PROJECT_NAME}: Required ${Package_Name} not found")
endif()
return()
endif()
common_graph_dep(${PROJECT_NAME} ${Package_Name} TRUE FALSE)
else() # optional find
common_graph_dep(${PROJECT_NAME} ${Package_Name} FALSE FALSE)
if(${Package_Name}_FOUND OR ${PACKAGE_NAME}_FOUND)
common_graph_dep(${PROJECT_NAME} ${Package_Name} ${_is_subproject})
else()
common_graph_dep(${PROJECT_NAME} ${Package_Name} ${_is_subproject} NOTFOUND)
endif()
endif()

# Set common found variables, link and include directories

if(${PACKAGE_NAME}_FOUND)
set(${Package_Name}_name ${PACKAGE_NAME})
set(${Package_Name}_FOUND TRUE)
Expand Down
141 changes: 88 additions & 53 deletions CommonGraph.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
# CMake options:
# * COMMON_GRAPH_SHOW_EXTERNAL include external dependencies in graphs.
# * COMMON_GRAPH_SHOW_OPTIONAL include optional dependencies in graphs.
# * COMMON_GRAPH_SHOW_NOTFOUND include missing dependencies in graphs.
#
# Targets generated:
# * graphs: generate .png graphs for all (sub)projects.
Expand All @@ -24,82 +25,116 @@ endif()
set(COMMON_GRAPH_DONE ON)

option(COMMON_GRAPH_SHOW_EXTERNAL "Include external dependencies in graphs" ON)
option(COMMON_GRAPH_SHOW_OPTIONAL "Include optional dependencies in graphs" OFF)
option(COMMON_GRAPH_SHOW_OPTIONAL "Include optional dependencies in graphs" ON)
option(COMMON_GRAPH_SHOW_NOTFOUND "Include missing dependencies in graphs" ON)

find_program(DOT_EXECUTABLE dot)
find_program(TRED_EXECUTABLE tred)

function(common_graph_dep From To Required Source)
function(common_graph_dep From To)
# Parse function arguments
set(__options SOURCE REQUIRED NOTFOUND TOPLEVEL)
set(__oneValueArgs)
set(__multiValueArgs)
cmake_parse_arguments(__dep "${__options}" "${__oneValueArgs}" "${__multiValueArgs}" ${ARGN})

if(NOT __dep_SOURCE AND NOT COMMON_GRAPH_SHOW_EXTERNAL)
return()
endif()
if(NOT __dep_REQUIRED AND NOT COMMON_GRAPH_SHOW_OPTIONAL)
return()
endif()
if(__dep_NOTFOUND AND NOT COMMON_GRAPH_SHOW_NOTFOUND)
return()
endif()

string(REPLACE "-" "_" Title ${From})
string(REPLACE "-" "_" Dep ${To})
# prevent syntax error in tred, e.g. Magick++ -> MagickPP
string(REPLACE "+" "P" Title ${Title})
string(REPLACE "+" "P" Dep ${Dep})

if(Source)
set(style "style=bold")
elseif(NOT COMMON_GRAPH_SHOW_EXTERNAL)
return()
elseif(Required)
set(style "style=solid")
elseif(COMMON_GRAPH_SHOW_OPTIONAL)
set(style "style=dashed")
if(__dep_SOURCE)
set(_style "style=bold")
elseif(__dep_REQUIRED)
set(_style "style=solid")
elseif(__dep_NOTFOUND)
set(_style "style=dotted")
else()
return()
set(_style "style=dashed")
endif()

set_property(GLOBAL APPEND_STRING PROPERTY ${From}_COMMON_GRAPH
"${Title} [label=\"${From}\"]\n")
if(Required)
if(__dep_NOTFOUND)
set(_linestyle "style=dotted")
elseif(__dep_SOURCE AND __dep_REQUIRED)
set(_linestyle "style=bold")
elseif(__dep_REQUIRED)
set(_linestyle "style=solid")
else()
set(_linestyle "style=dashed")
endif()

if(__dep_TOPLEVEL)
set_property(GLOBAL APPEND_STRING PROPERTY ${From}_COMMON_GRAPH
"${Dep} [${style}, label=\"${To}\"]\n"
"\"${Dep}\" -> \"${Title}\" [${style}]\n" )
"${Dep} [${_style}, label=\"${To}\"]\n")
else()
set_property(GLOBAL APPEND_STRING PROPERTY ${From}_COMMON_GRAPH
"${Dep} [${style}, label=\"${To}\", fontsize=10]\n"
"\"${Dep}\" -> \"${Title}\" [${style}]\n" )
"${Title} [style=bold, label=\"${From}\"]\n"
"${Dep} [${_style}, label=\"${To}\"]\n"
"\"${Dep}\" -> \"${Title}\" [${_linestyle}]\n")
endif()
set_property(GLOBAL APPEND PROPERTY ${From}_COMMON_GRAPH_DEPENDS ${To})
endfunction()

function(common_graph Name)
# collect graph recursively
get_property(graph GLOBAL PROPERTY ${Name}_COMMON_GRAPH)
get_property(graph_depends GLOBAL PROPERTY ${Name}_COMMON_GRAPH_DEPENDS)
if(NOT DOT_EXECUTABLE OR NOT TRED_EXECUTABLE)
return()
endif()

list(LENGTH graph_depends nDepends)
# collect graph recursively for all dependencies of Name
get_property(_graph GLOBAL PROPERTY ${Name}_COMMON_GRAPH)
get_property(_dependencies GLOBAL PROPERTY ${Name}_COMMON_GRAPH_DEPENDS)
list(LENGTH _dependencies nDepends)

set(_loop_count 0)
while(nDepends)
list(GET graph_depends 0 dep)
list(REMOVE_AT graph_depends 0)
get_property(graph_dep GLOBAL PROPERTY ${dep}_COMMON_GRAPH)
get_property(graph_dep_depends GLOBAL PROPERTY ${dep}_COMMON_GRAPH_DEPENDS)

set(graph "${graph_dep} ${graph}")
list(APPEND graph_dep ${graph_dep_depends})
list(LENGTH graph_depends nDepends)
list(GET _dependencies 0 dep)
list(REMOVE_AT _dependencies 0)

get_property(_dep_graph GLOBAL PROPERTY ${dep}_COMMON_GRAPH)
set(_graph "${_dep_graph} ${_graph}")

get_property(_dep_depends GLOBAL PROPERTY ${dep}_COMMON_GRAPH_DEPENDS)
list(APPEND _dependencies ${_dep_depends})
list(LENGTH _dependencies nDepends)

# prevent infinite looping in the case of circular dependencies
MATH(EXPR _loop_count "${_loop_count}+1")
if(_loop_count GREATER 1000)
message(WARNING "CommonGraph detected a probable circular dependency")
return()
endif()
endwhile()

if(DOT_EXECUTABLE AND TRED_EXECUTABLE)
set(_dot_file ${CMAKE_CURRENT_BINARY_DIR}/${Name}.dot)
file(GENERATE OUTPUT ${_dot_file}
CONTENT "strict digraph G { rankdir=\"RL\"; ${graph} }")

set(_tred_dot_file ${CMAKE_CURRENT_BINARY_DIR}/${Name}_tred.dot)
add_custom_command(OUTPUT ${_tred_dot_file}
COMMAND ${TRED_EXECUTABLE} ${_dot_file} > ${_tred_dot_file}
DEPENDS ${_dot_file})

set(_image_folder ${PROJECT_BINARY_DIR}/doc/images)
set(_image_file ${_image_folder}/${Name}.png)
add_custom_command(OUTPUT ${_image_file}
COMMAND ${CMAKE_COMMAND} -E make_directory ${_image_folder}
COMMAND ${DOT_EXECUTABLE} -o ${_image_file} -Tpng ${_tred_dot_file}
DEPENDS ${_tred_dot_file})

add_custom_target(${Name}-graph DEPENDS ${_image_file})
set_target_properties(${Name}-graph PROPERTIES EXCLUDE_FROM_DEFAULT_BUILD ON
FOLDER ${Name}/doxygen)
common_target(graphs doxygen)
add_dependencies(graphs ${Name}-graph)
endif()
set(_dot_file ${CMAKE_CURRENT_BINARY_DIR}/${Name}.dot)
file(GENERATE OUTPUT ${_dot_file}
CONTENT "strict digraph G { rankdir=\"RL\"; ${_graph} }")

set(_tred_dot_file ${CMAKE_CURRENT_BINARY_DIR}/${Name}_tred.dot)
add_custom_command(OUTPUT ${_tred_dot_file}
COMMAND ${TRED_EXECUTABLE} ${_dot_file} > ${_tred_dot_file}
DEPENDS ${_dot_file})

set(_image_folder ${PROJECT_BINARY_DIR}/doc/images)
set(_image_file ${_image_folder}/${Name}.png)
add_custom_command(OUTPUT ${_image_file}
COMMAND ${CMAKE_COMMAND} -E make_directory ${_image_folder}
COMMAND ${DOT_EXECUTABLE} -o ${_image_file} -Tpng ${_tred_dot_file}
DEPENDS ${_tred_dot_file})

add_custom_target(${Name}-graph DEPENDS ${_image_file})
set_target_properties(${Name}-graph PROPERTIES EXCLUDE_FROM_DEFAULT_BUILD ON
FOLDER ${Name}/doxygen)
common_target(graphs doxygen)
add_dependencies(graphs ${Name}-graph)
endfunction()
70 changes: 38 additions & 32 deletions SubProject.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@
# - SUBPROJECT_${name}: If set to OFF, the subproject is not added.
# - COMMON_SOURCE_DIR: When set, the source code of subprojects will be
# downloaded in this path instead of CMAKE_SOURCE_DIR.
# - ${PROJECT_NAME}_IS_METAPROJECT: Enable CommonGraph compatibility for a
# top-level project which does not use common_find_package().
# A sample project can be found at https://github.com/Eyescale/Collage.git
#
# How to create a dependency graph:
Expand Down Expand Up @@ -86,8 +88,6 @@ function(add_subproject name)
"cmake -DCLONE_SUBPROJECTS=ON\n"
"to git-clone it automatically.")
endif()
# enter again to catch direct add_subproject() calls
common_graph_dep(${PROJECT_NAME} ${name} TRUE TRUE)

# allow exclusion of subproject via set(SUBPROJECT_${name} OFF)
if(DEFINED SUBPROJECT_${name} AND NOT SUBPROJECT_${name})
Expand All @@ -111,46 +111,49 @@ function(add_subproject name)

# add the source sub directory to our build and set the binary dir
# to the build tree

add_subdirectory("${path}"
"${CMAKE_BINARY_DIR}/${name}")
add_subdirectory("${path}" "${CMAKE_BINARY_DIR}/${name}")
set(${name}_IS_SUBPROJECT ON PARENT_SCOPE)

# Mark globally that we've already used name as a sub project
set_property(GLOBAL PROPERTY ${name}_IS_SUBPROJECT ON)
endfunction()

macro(git_subproject name url tag)
# enter early to catch all dependencies
common_graph_dep(${PROJECT_NAME} ${name} TRUE TRUE)
if(NOT DISABLE_SUBPROJECTS)
string(TOUPPER ${name} NAME)
if(NOT ${NAME}_FOUND AND NOT ${name}_FOUND)
get_property(__included GLOBAL PROPERTY ${name}_IS_SUBPROJECT)
if(NOT __included)
if(NOT EXISTS ${__common_source_dir}/${name})
# Always try first using Config mode, then Module mode.
find_package(${name} QUIET CONFIG)
if(NOT ${NAME}_FOUND AND NOT ${name}_FOUND)
find_package(${name} QUIET MODULE)
endif()
if(DISABLE_SUBPROJECTS)
return()
endif()

# add graph dependency for meta-projects with no common_find_package() calls
if(${PROJECT_NAME}_IS_METAPROJECT)
common_graph_dep(${PROJECT_NAME} ${name} SOURCE TOPLEVEL REQUIRED)
endif()

string(TOUPPER ${name} NAME)
if(NOT ${NAME}_FOUND AND NOT ${name}_FOUND)
get_property(__included GLOBAL PROPERTY ${name}_IS_SUBPROJECT)
if(NOT __included)
if(NOT EXISTS ${__common_source_dir}/${name})
# Always try first using Config mode, then Module mode.
find_package(${name} QUIET CONFIG)
if(NOT ${NAME}_FOUND AND NOT ${name}_FOUND)
find_package(${name} QUIET MODULE)
endif()
if((NOT ${NAME}_FOUND AND NOT ${name}_FOUND) OR
${NAME}_FOUND_SUBPROJECT)
# not found externally, add as sub project
if(CLONE_SUBPROJECTS)
git_external(${__common_source_dir}/${name} ${url} ${tag})
endif()
add_subproject(${name})
endif()
if((NOT ${NAME}_FOUND AND NOT ${name}_FOUND) OR
${NAME}_FOUND_SUBPROJECT)
# not found externally, add as sub project
if(CLONE_SUBPROJECTS)
git_external(${__common_source_dir}/${name} ${url} ${tag})
endif()
add_subproject(${name})
endif()
endif()
get_property(__included GLOBAL PROPERTY ${name}_IS_SUBPROJECT)
if(__included)
list(APPEND __subprojects "${name} ${url} ${tag}")
if(TARGET ${PROJECT_NAME}-install AND TARGET ${name}-install)
add_dependencies(${PROJECT_NAME}-install ${name}-install)
endif()
endif()
get_property(__included GLOBAL PROPERTY ${name}_IS_SUBPROJECT)
if(__included)
list(APPEND __subprojects "${name} ${url} ${tag}")
if(TARGET ${PROJECT_NAME}-install AND TARGET ${name}-install)
add_dependencies(${PROJECT_NAME}-install ${name}-install)
endif()
endif()
endmacro()
Expand Down Expand Up @@ -204,6 +207,9 @@ if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.gitsubprojects")
set_target_properties(update PROPERTIES EXCLUDE_FROM_DEFAULT_BUILD ON)
endif()
add_dependencies(update ${PROJECT_NAME}-update-gitsubprojects)
endif()

if(${PROJECT_NAME}_IS_METAPROJECT)
common_graph(${PROJECT_NAME})
endif()
endif()
endif()

0 comments on commit 0e90645

Please sign in to comment.