gitcmakecpack

CPack: only pack files tracked by git


I'm trying to understand how to use CPack. I want to produce a binary and a source package. The binary one is perfectly fine, including only what's necessary. In the source distribution, though, there are basically all files present in the project's root directory - including my CMake build directory from where I ran the CPack command.

Since one might have several build directories, I can't see how I can exclude them all from being put into the source package.

set(CPACK_SOURCE_IGNORE_FILES "*.git*") works fine but ideally I would like to restrict the set of included files only to those that are tracked by version control, e.g. git, in the first place. And, no, I don't consider making sure that my project directory is completely clean before a cpack a viable option.

(Packing an entire directory into an archive is something I don't need CPack for.)


Solution

  • This is possible with CPACK_PROJECT_CONFIG_FILE. This file is executed by cpack before the CPACK_* variables are queried, so you can use this file to dynamically add entries to the CPACK_SOURCE_IGNORE_FILES list.

    In the top-level CMakeLists.txt add the following before the call to include(CPack):

    find_package(Git)
    configure_file(
      "${CMAKE_SOURCE_DIR}/ProjectCPackConfig.cmake.in"
      "${CMAKE_BINARY_DIR}/ProjectCPackConfig.cmake"
      @ONLY
    )
    set(CPACK_PROJECT_CONFIG_FILE "${CMAKE_BINARY_DIR}/ProjectCPackConfig.cmake")
    

    And create ProjectCPackConfig.cmake.in:

    # If we are packaging source, then ignore files that are untracked by git.
    if(CPACK_TOPLEVEL_TAG STREQUAL CPACK_SOURCE_TOPLEVEL_TAG AND "@GIT_FOUND@")
      # Use `git ls-files` to list untracked files/directories
      execute_process(
        COMMAND "@GIT_EXECUTABLE@" ls-files --ignored --other --exclude-standard --directory
        WORKING_DIRECTORY "@CMAKE_SOURCE_DIR@"
        RESULT_VARIABLE status
        OUTPUT_VARIABLE GIT_IGNORED_FILES
        OUTPUT_STRIP_TRAILING_WHITESPACE
      )
      if(NOT status EQUAL 0)
        message(ERROR "Failed to query git for file ignore list.")
      endif()
    
      # split the output of git ls-files into a list
      string(REPLACE "\n" ";" GIT_IGNORED_FILES "${GIT_IGNORED_FILES}")
    
      # .git directory is implicitly untracked
      list(APPEND GIT_IGNORED_FILES ".git/")
    
      foreach(IGN_FILE ${GIT_IGNORED_FILES})
        # apply escaping to the filename so nothing is mistaken for a regex quantifier
        string(REGEX REPLACE "." "\\\\\\0" IGN_FILE_QUOTED "@CMAKE_SOURCE_DIR@/${IGN_FILE}")
    
        # add the quoted filename to the CPACK_IGNORE_FILES list
        list(APPEND CPACK_IGNORE_FILES "^${IGN_FILE_QUOTED}")
      endforeach()
    endif()
    

    It is worth noting that cpack does not directly use any CPACK_SOURCE_* variables. Instead, CPACK_SOURCE_* values are assigned to CPACK_* variables in CMAKE_BINARY_DIR/CPackSourceConfig.cmake, and cpack executes this file before executing CMAKE_BINARY_DIR/ProjectCPackConfig.cmake. This is why I assign to CPACK_IGNORE_FILES instead of CPACK_SOURCE_IGNORE_FILES, and it is also why I use CPACK_TOPLEVEL_TAG STREQUAL CPACK_SOURCE_TOPLEVEL_TAG to detect whether a source package is currently being generated.