From d81d83c218be6f8047c1c9cea63e69bc1ad4d9c0 Mon Sep 17 00:00:00 2001 From: Alex Neundorf Date: Sun, 18 Mar 2012 21:26:37 +0100 Subject: add macro check_required_components() to configure_package_config_file() As discussed on cmake-developers, this patch adds a macro check_required_components() to the file configured via configure_package_config_file(), so for proper handling of components in Config.cmake files users can simply call check_required_components(PackageName) and this will do the right thing. Alex --- Modules/CMakePackageConfigHelpers.cmake | 38 +++++++++++++++++++++--- Tests/FindPackageTest/CMakeLists.txt | 10 +++++++ Tests/FindPackageTest/RelocatableConfig.cmake.in | 6 ++++ 3 files changed, 50 insertions(+), 4 deletions(-) diff --git a/Modules/CMakePackageConfigHelpers.cmake b/Modules/CMakePackageConfigHelpers.cmake index 98cd560..48039e5 100644 --- a/Modules/CMakePackageConfigHelpers.cmake +++ b/Modules/CMakePackageConfigHelpers.cmake @@ -2,7 +2,8 @@ # # CONFIGURE_PACKAGE_CONFIG_FILE( INSTALL_DESTINATION # [PATH_VARS ... ] -# [NO_SET_AND_CHECK_MACRO] ) +# [NO_SET_AND_CHECK_MACRO] +# [NO_CHECK_REQUIRED_COMPONENTS_MACRO]) # # CONFIGURE_PACKAGE_CONFIG_FILE() should be used instead of the plain # CONFIGURE_FILE() command when creating the Config.cmake or -config.cmake @@ -49,8 +50,10 @@ # For absolute locations it works only if the absolute location is a subdirectory # of CMAKE_INSTALL_PREFIX. # -# By default configure_package_config_file() also generates a macro set_and_check() -# into the FooConfig.cmake file. This should be used instead of the normal set() +# By default configure_package_config_file() also generates two helper macros, +# set_and_check() and check_required_components() into the FooConfig.cmake file. +# +# set_and_check() should be used instead of the normal set() # command for setting directories and file locations. Additionally to setting the # variable it also checks that the referenced file or directory actually exists # and fails with a FATAL_ERROR otherwise. This makes sure that the created @@ -58,6 +61,16 @@ # When using the NO_SET_AND_CHECK_MACRO, this macro is not generated into the # FooConfig.cmake file. # +# check_required_components() should be called at the end of the +# FooConfig.cmake file if the package supports components. +# This macro checks whether all requested, non-optional components have been found, +# and if this is not the case, sets the Foo_FOUND variable to FALSE, so that the package +# is considered to be not found. +# It does that by testing the Foo__FOUND variables for all requested +# required components. +# When using the NO_CHECK_REQUIRED_COMPONENTS option, this macro is not generated +# into the FooConfig.cmake file. +# # For an example see below the documentation for WRITE_BASIC_PACKAGE_VERSION_FILE(). # # @@ -114,6 +127,8 @@ # ... # set_and_check(FOO_INCLUDE_DIR "@PACKAGE_INCLUDE_INSTALL_DIR@") # set_and_check(FOO_SYSCONFIG_DIR "@PACKAGE_SYSCONFIG_INSTALL_DIR@") +# +# check_required_components(Foo) #============================================================================= @@ -139,7 +154,7 @@ endmacro() function(CONFIGURE_PACKAGE_CONFIG_FILE _inputFile _outputFile) - set(options NO_SET_AND_CHECK_MACRO) + set(options NO_SET_AND_CHECK_MACRO NO_CHECK_REQUIRED_COMPONENTS_MACRO) set(oneValueArgs INSTALL_DESTINATION ) set(multiValueArgs PATH_VARS ) @@ -189,6 +204,21 @@ endmacro() ") endif() + + if(NOT CCF_NO_CHECK_REQUIRED_COMPONENTS_MACRO) + set(PACKAGE_INIT "${PACKAGE_INIT} +macro(check_required_components _NAME) + foreach(comp \${\${_NAME}_FIND_COMPONENTS}) + if(NOT \${_NAME}_\${comp}_FOUND) + if(\${_NAME}_FIND_REQUIRED_\${comp}) + set(\${_NAME}_FOUND FALSE) + endif() + endif() + endforeach(comp) +endmacro() +") + endif() + set(PACKAGE_INIT "${PACKAGE_INIT} ####################################################################################") diff --git a/Tests/FindPackageTest/CMakeLists.txt b/Tests/FindPackageTest/CMakeLists.txt index eebebc0..5862094 100644 --- a/Tests/FindPackageTest/CMakeLists.txt +++ b/Tests/FindPackageTest/CMakeLists.txt @@ -336,6 +336,8 @@ configure_package_config_file(RelocatableConfig.cmake.in "${CMAKE_CURRENT_BINARY PATH_VARS INCLUDE_INSTALL_DIR SHARE_INSTALL_DIR CURRENT_BUILD_DIR ) +set(Relocatable_FIND_COMPONENTS AComp BComp CComp) +set(Relocatable_FIND_REQUIRED_BComp 1) include("${CMAKE_CURRENT_BINARY_DIR}/RelocatableConfig.cmake") if(NOT "${RELOC_INCLUDE_DIR}" STREQUAL "${CMAKE_CURRENT_BINARY_DIR}/include") @@ -350,6 +352,14 @@ if(NOT "${RELOC_BUILD_DIR}" STREQUAL "${CMAKE_CURRENT_BINARY_DIR}") message(SEND_ERROR "RELOC_BUILD_DIR set by configure_package_config_file() is set to \"${RELOC_BUILD_DIR}\" (expected \"${CMAKE_CURRENT_BINARY_DIR}\")") endif() +if(NOT DEFINED Relocatable_FOUND) + message(SEND_ERROR "Relocatable_FOUND not defined !") +endif() + +if(Relocatable_FOUND) + message(SEND_ERROR "Relocatable_FOUND set to TRUE !") +endif() + #----------------------------------------------------------------------------- # Test write_basic_config_version_file(). diff --git a/Tests/FindPackageTest/RelocatableConfig.cmake.in b/Tests/FindPackageTest/RelocatableConfig.cmake.in index 7a34b2f..4a4b4e9 100644 --- a/Tests/FindPackageTest/RelocatableConfig.cmake.in +++ b/Tests/FindPackageTest/RelocatableConfig.cmake.in @@ -3,3 +3,9 @@ set(RELOC_INCLUDE_DIR "@PACKAGE_INCLUDE_INSTALL_DIR@") set(RELOC_SHARE_DIR "@PACKAGE_SHARE_INSTALL_DIR@") set_and_check(RELOC_BUILD_DIR "@PACKAGE_CURRENT_BUILD_DIR@") + +set(Relocatable_AComp_FOUND TRUE) +set(Relocatable_BComp_FOUND FALSE) +set(Relocatable_CComp_FOUND FALSE) + +check_required_components(Relocatable) -- cgit v0.12 href='#n86'>86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277
# Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
# file Copyright.txt or https://cmake.org/licensing for details.

cmake_minimum_required(VERSION 3.5)

function(get_hash_for_ref ref out_var err_var)
  execute_process(
    COMMAND "@git_EXECUTABLE@" rev-parse "${ref}^0"
    WORKING_DIRECTORY "@work_dir@"
    RESULT_VARIABLE error_code
    OUTPUT_VARIABLE ref_hash
    ERROR_VARIABLE error_msg
    OUTPUT_STRIP_TRAILING_WHITESPACE
  )
  if(error_code)
    set(${out_var} "" PARENT_SCOPE)
  else()
    set(${out_var} "${ref_hash}" PARENT_SCOPE)
  endif()
  set(${err_var} "${error_msg}" PARENT_SCOPE)
endfunction()

get_hash_for_ref(HEAD head_sha error_msg)
if(head_sha STREQUAL "")
  message(FATAL_ERROR "Failed to get the hash for HEAD:\n${error_msg}")
endif()


execute_process(
  COMMAND "@git_EXECUTABLE@" show-ref "@git_tag@"
  WORKING_DIRECTORY "@work_dir@"
  OUTPUT_VARIABLE show_ref_output
)
if(show_ref_output MATCHES "^[a-z0-9]+[ \\t]+refs/remotes/")
  # Given a full remote/branch-name and we know about it already. Since
  # branches can move around, we always have to fetch.
  set(fetch_required YES)
  set(checkout_name "@git_tag@")

elseif(show_ref_output MATCHES "^[a-z0-9]+[ \\t]+refs/tags/")
  # Given a tag name that we already know about. We don't know if the tag we
  # have matches the remote though (tags can move), so we should fetch.
  set(fetch_required YES)
  set(checkout_name "@git_tag@")

  # Special case to preserve backward compatibility: if we are already at the
  # same commit as the tag we hold locally, don't do a fetch and assume the tag
  # hasn't moved on the remote.
  # FIXME: We should provide an option to always fetch for this case
  get_hash_for_ref("@git_tag@" tag_sha error_msg)
  if(tag_sha STREQUAL head_sha)
    message(VERBOSE "Already at requested tag: ${tag_sha}")
    return()
  endif()

elseif(show_ref_output MATCHES "^[a-z0-9]+[ \\t]+refs/heads/")
  # Given a branch name without any remote and we already have a branch by that
  # name. We might already have that branch checked out or it might be a
  # different branch. It isn't safe to use a bare branch name without the
  # remote, so do a fetch and replace the ref with one that includes the remote.
  set(fetch_required YES)
  set(checkout_name "@git_remote_name@/@git_tag@")

else()
  get_hash_for_ref("@git_tag@" tag_sha error_msg)
  if(tag_sha STREQUAL head_sha)
    # Have the right commit checked out already
    message(VERBOSE "Already at requested ref: ${tag_sha}")
    return()

  elseif(tag_sha STREQUAL "")
    # We don't know about this ref yet, so we have no choice but to fetch.
    # We deliberately swallow any error message at the default log level
    # because it can be confusing for users to see a failed git command.
    # That failure is being handled here, so it isn't an error.
    set(fetch_required YES)
    set(checkout_name "@git_tag@")
    if(NOT error_msg STREQUAL "")
      message(VERBOSE "${error_msg}")
    endif()

  else()
    # We have the commit, so we know we were asked to find a commit hash
    # (otherwise it would have been handled further above), but we don't
    # have that commit checked out yet
    set(fetch_required NO)
    set(checkout_name "@git_tag@")
    if(NOT error_msg STREQUAL "")
      message(WARNING "${error_msg}")
    endif()

  endif()
endif()

if(fetch_required)
  message(VERBOSE "Fetching latest from the remote @git_remote_name@")
  execute_process(
    COMMAND "@git_EXECUTABLE@" fetch --tags --force "@git_remote_name@"
    WORKING_DIRECTORY "@work_dir@"
    COMMAND_ERROR_IS_FATAL ANY
  )
endif()

set(git_update_strategy "@git_update_strategy@")
if(git_update_strategy STREQUAL "")
  # Backward compatibility requires REBASE as the default behavior
  set(git_update_strategy REBASE)
endif()

if(git_update_strategy MATCHES "^REBASE(_CHECKOUT)?$")
  # Asked to potentially try to rebase first, maybe with fallback to checkout.
  # We can't if we aren't already on a branch and we shouldn't if that local
  # branch isn't tracking the one we want to checkout.
  execute_process(
    COMMAND "@git_EXECUTABLE@" symbolic-ref -q HEAD
    WORKING_DIRECTORY "@work_dir@"
    OUTPUT_VARIABLE current_branch
    OUTPUT_STRIP_TRAILING_WHITESPACE
    # Don't test for an error. If this isn't a branch, we get a non-zero error
    # code but empty output.
  )

  if(current_branch STREQUAL "")
    # Not on a branch, checkout is the only sensible option since any rebase
    # would always fail (and backward compatibility requires us to checkout in
    # this situation)
    set(git_update_strategy CHECKOUT)

  else()
    execute_process(
      COMMAND "@git_EXECUTABLE@" for-each-ref "--format=%(upstream:short)" "${current_branch}"
      WORKING_DIRECTORY "@work_dir@"
      OUTPUT_VARIABLE upstream_branch
      OUTPUT_STRIP_TRAILING_WHITESPACE
      COMMAND_ERROR_IS_FATAL ANY  # There is no error if no upstream is set
    )
    if(NOT upstream_branch STREQUAL checkout_name)
      # Not safe to rebase when asked to checkout a different branch to the one
      # we are tracking. If we did rebase, we could end up with arbitrary
      # commits added to the ref we were asked to checkout if the current local
      # branch happens to be able to rebase onto the target branch. There would
      # be no error message and the user wouldn't know this was occurring.
      set(git_update_strategy CHECKOUT)
    endif()

  endif()
elseif(NOT git_update_strategy STREQUAL "CHECKOUT")
  message(FATAL_ERROR "Unsupported git update strategy: ${git_update_strategy}")
endif()


# Check if stash is needed
execute_process(
  COMMAND "@git_EXECUTABLE@" status --porcelain
  WORKING_DIRECTORY "@work_dir@"
  RESULT_VARIABLE error_code
  OUTPUT_VARIABLE repo_status
)
if(error_code)
  message(FATAL_ERROR "Failed to get the status")
endif()
string(LENGTH "${repo_status}" need_stash)

# If not in clean state, stash changes in order to be able to perform a
# rebase or checkout without losing those changes permanently
if(need_stash)
  execute_process(
    COMMAND "@git_EXECUTABLE@" stash save @git_stash_save_options@
    WORKING_DIRECTORY "@work_dir@"
    COMMAND_ERROR_IS_FATAL ANY
  )
endif()

if(git_update_strategy STREQUAL "CHECKOUT")
  execute_process(
    COMMAND "@git_EXECUTABLE@" checkout "${checkout_name}"
    WORKING_DIRECTORY "@work_dir@"
    COMMAND_ERROR_IS_FATAL ANY
  )
else()
  execute_process(
    COMMAND "@git_EXECUTABLE@" rebase "${checkout_name}"
    WORKING_DIRECTORY "@work_dir@"
    RESULT_VARIABLE error_code
    OUTPUT_VARIABLE rebase_output
    ERROR_VARIABLE  rebase_output
  )
  if(error_code)
    # Rebase failed, undo the rebase attempt before continuing
    execute_process(
      COMMAND "@git_EXECUTABLE@" rebase --abort
      WORKING_DIRECTORY "@work_dir@"
    )

    if(NOT git_update_strategy STREQUAL "REBASE_CHECKOUT")
      # Not allowed to do a checkout as a fallback, so cannot proceed
      if(need_stash)
        execute_process(
          COMMAND "@git_EXECUTABLE@" stash pop --index --quiet
          WORKING_DIRECTORY "@work_dir@"
          )
      endif()
      message(FATAL_ERROR "\nFailed to rebase in: '@work_dir@'."
                          "\nOutput from the attempted rebase follows:"
                          "\n${rebase_output}"
                          "\n\nYou will have to resolve the conflicts manually")
    endif()

    # Fall back to checkout. We create an annotated tag so that the user
    # can manually inspect the situation and revert if required.
    # We can't log the failed rebase output because MSVC sees it and
    # intervenes, causing the build to fail even though it completes.
    # Write it to a file instead.
    string(TIMESTAMP tag_timestamp "%Y%m%dT%H%M%S" UTC)
    set(tag_name _cmake_ExternalProject_moved_from_here_${tag_timestamp}Z)
    set(error_log_file ${CMAKE_CURRENT_LIST_DIR}/rebase_error_${tag_timestamp}Z.log)
    file(WRITE ${error_log_file} "${rebase_output}")
    message(WARNING "Rebase failed, output has been saved to ${error_log_file}"
                    "\nFalling back to checkout, previous commit tagged as ${tag_name}")
    execute_process(
      COMMAND "@git_EXECUTABLE@" tag -a
              -m "ExternalProject attempting to move from here to ${checkout_name}"
              ${tag_name}
      WORKING_DIRECTORY "@work_dir@"
      COMMAND_ERROR_IS_FATAL ANY
    )

    execute_process(
      COMMAND "@git_EXECUTABLE@" checkout "${checkout_name}"
      WORKING_DIRECTORY "@work_dir@"
      COMMAND_ERROR_IS_FATAL ANY
    )
  endif()
endif()

if(need_stash)
  # Put back the stashed changes
  execute_process(
    COMMAND "@git_EXECUTABLE@" stash pop --index --quiet
    WORKING_DIRECTORY "@work_dir@"
    RESULT_VARIABLE error_code
    )
  if(error_code)
    # Stash pop --index failed: Try again dropping the index
    execute_process(
      COMMAND "@git_EXECUTABLE@" reset --hard --quiet
      WORKING_DIRECTORY "@work_dir@"
    )
    execute_process(
      COMMAND "@git_EXECUTABLE@" stash pop --quiet
      WORKING_DIRECTORY "@work_dir@"
      RESULT_VARIABLE error_code
    )
    if(error_code)
      # Stash pop failed: Restore previous state.
      execute_process(
        COMMAND "@git_EXECUTABLE@" reset --hard --quiet ${head_sha}
        WORKING_DIRECTORY "@work_dir@"
      )
      execute_process(
        COMMAND "@git_EXECUTABLE@" stash pop --index --quiet
        WORKING_DIRECTORY "@work_dir@"
      )
      message(FATAL_ERROR "\nFailed to unstash changes in: '@work_dir@'."
                          "\nYou will have to resolve the conflicts manually")
    endif()
  endif()
endif()

set(init_submodules "@init_submodules@")
if(init_submodules)
  execute_process(
    COMMAND "@git_EXECUTABLE@" submodule update @git_submodules_recurse@ --init @git_submodules@
    WORKING_DIRECTORY "@work_dir@"
    COMMAND_ERROR_IS_FATAL ANY
  )
endif()