From 0e90645ac037ee278f2937454faca413c409d42b Mon Sep 17 00:00:00 2001 From: Raphael Dumusc Date: Wed, 31 May 2017 11:38:56 +0200 Subject: [PATCH] CommonGraph: improve graph output and options, fix recursion 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. --- CommonFindPackage.cmake | 22 ++++++- CommonGraph.cmake | 141 +++++++++++++++++++++++++--------------- SubProject.cmake | 70 +++++++++++--------- 3 files changed, 145 insertions(+), 88 deletions(-) diff --git a/CommonFindPackage.cmake b/CommonFindPackage.cmake index a558c9f..975626d 100644 --- a/CommonFindPackage.cmake +++ b/CommonFindPackage.cmake @@ -85,8 +85,19 @@ 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() @@ -94,11 +105,16 @@ macro(common_find_package Package_Name) 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) diff --git a/CommonGraph.cmake b/CommonGraph.cmake index 65c62ae..2e6c4bb 100644 --- a/CommonGraph.cmake +++ b/CommonGraph.cmake @@ -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. @@ -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() diff --git a/SubProject.cmake b/SubProject.cmake index fae1d66..93e91a9 100644 --- a/SubProject.cmake +++ b/SubProject.cmake @@ -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: @@ -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}) @@ -111,9 +111,7 @@ 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 @@ -121,36 +119,41 @@ function(add_subproject name) 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() @@ -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()