Skip to content

Add package lock #121

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
May 27, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ Dependencies using CPM will automatically use the updated script of the outermos

- **Small and reusable projects** CPM takes care of all project dependencies, allowing developers to focus on creating small, well-tested libraries.
- **Cross-Platform** CPM adds projects directly at the configure stage and is compatible with all CMake toolchains and generators.
- **Reproducable builds** By versioning dependencies via git commits or tags it is ensured that a project will always be buildable.
- **Reproducible builds** By versioning dependencies via git commits or tags it is ensured that a project will always be buildable.
- **Recursive dependencies** Ensures that no dependency is added twice and all are added in the minimum required version.
- **Plug-and-play** No need to install anything. Just add the script to your project and you're good to go.
- **No packaging required** Simply add all external sources as a dependency.
Expand All @@ -105,7 +105,7 @@ Dependencies using CPM will automatically use the updated script of the outermos

- **No pre-built binaries** For every new build directory, all dependencies are initially downloaded and built from scratch. To avoid extra downloads it is recommend to set the [`CPM_SOURCE_CACHE`](#CPM_SOURCE_CACHE) environmental variable. Using a caching compiler such as [ccache](https://github.com/TheLartians/Ccache.cmake) can drastically reduce build time.
- **Dependent on good CMakeLists** Many libraries do not have CMakeLists that work well for subprojects. Luckily this is slowly changing, however, until then, some manual configuration may be required (see the snippets [below](#snippets) for examples). For best practices on preparing projects for CPM, see the [wiki](https://github.com/TheLartians/CPM.cmake/wiki/Preparing-projects-for-CPM.cmake).
- **First version used** In diamond-shaped dependency graphs (e.g. `A` depends on `C`@1.1 and `B`, which itself depends on `C`@1.2 the first added dependency will be used (in this case `C`@1.1). In this case, B requires a newer version of `C` than `A`, so CPM will emit a warning. This can be resolved by adding a new version of the dependency in the outermost project.
- **First version used** In diamond-shaped dependency graphs (e.g. `A` depends on `C`@1.1 and `B`, which itself depends on `C`@1.2 the first added dependency will be used (in this case `C`@1.1). In this case, B requires a newer version of `C` than `A`, so CPM will emit a warning. This can be easily resolved by adding a new version of the dependency in the outermost project, or by introducing a [package lock file](https://github.com/TheLartians/CPM.cmake/wiki/Package-lock).

For projects with more complex needs and where an extra setup step doesn't matter, it may be worth to check out an external C++ package manager such as [vcpkg](https://github.com/microsoft/vcpkg), [conan](https://conan.io) or [hunter](https://github.com/ruslo/hunter).
Dependencies added with `CPMFindPackage` should work with external package managers.
Expand Down
134 changes: 100 additions & 34 deletions cmake/CPM.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@

cmake_minimum_required(VERSION 3.14 FATAL_ERROR)

set(CURRENT_CPM_VERSION 0.24)
set(CURRENT_CPM_VERSION 0.25)

if(CPM_DIRECTORY)
if(NOT CPM_DIRECTORY STREQUAL CMAKE_CURRENT_LIST_DIR)
Expand All @@ -54,6 +54,7 @@ option(CPM_USE_LOCAL_PACKAGES "Always try to use `find_package` to get dependenc
option(CPM_LOCAL_PACKAGES_ONLY "Only use `find_package` to get dependencies" $ENV{CPM_LOCAL_PACKAGES_ONLY})
option(CPM_DOWNLOAD_ALL "Always download dependencies from source" $ENV{CPM_DOWNLOAD_ALL})
option(CPM_DONT_UPDATE_MODULE_PATH "Don't update the module path to allow using find_package" $ENV{CPM_DONT_UPDATE_MODULE_PATH})
option(CPM_DONT_CREATE_PACKAGE_LOCK "Don't create a package lock file in the binary path" $ENV{CPM_DONT_CREATE_PACKAGE_LOCK})

set(CPM_VERSION ${CURRENT_CPM_VERSION} CACHE INTERNAL "")
set(CPM_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} CACHE INTERNAL "")
Expand All @@ -78,6 +79,11 @@ if (NOT CPM_DONT_UPDATE_MODULE_PATH)
set(CMAKE_MODULE_PATH "${CPM_MODULE_PATH};${CMAKE_MODULE_PATH}")
endif()

if (NOT CPM_DONT_CREATE_PACKAGE_LOCK)
set(CPM_PACKAGE_LOCK_FILE "${CMAKE_BINARY_DIR}/cpm-package-lock.cmake" CACHE INTERNAL "")
file(WRITE ${CPM_PACKAGE_LOCK_FILE} "# CPM Package Lock\n# This file should be committed to version control\n\n")
endif()

include(FetchContent)
include(CMakeParseArguments)

Expand Down Expand Up @@ -128,21 +134,21 @@ function(CPMFindPackage)

if (CPM_DOWNLOAD_ALL)
CPMAddPackage(${ARGN})
cpm_export_variables()
cpm_export_variables(${CPM_ARGS_NAME})
return()
endif()

CPMCheckIfPackageAlreadyAdded(${CPM_ARGS_NAME} "${CPM_ARGS_VERSION}" "${CPM_ARGS_OPTIONS}")
if (CPM_PACKAGE_ALREADY_ADDED)
cpm_export_variables()
cpm_export_variables(${CPM_ARGS_NAME})
return()
endif()

cpm_find_package(${CPM_ARGS_NAME} "${CPM_ARGS_VERSION}" ${CPM_ARGS_FIND_PACKAGE_ARGUMENTS})

if(NOT CPM_PACKAGE_FOUND)
CPMAddPackage(${ARGN})
cpm_export_variables()
cpm_export_variables(${CPM_ARGS_NAME})
endif()

endfunction()
Expand All @@ -165,7 +171,7 @@ function(CPMCheckIfPackageAlreadyAdded CPM_ARGS_NAME CPM_ARGS_VERSION CPM_ARGS_O
cpm_get_fetch_properties(${CPM_ARGS_NAME})
SET(${CPM_ARGS_NAME}_ADDED NO)
SET(CPM_PACKAGE_ALREADY_ADDED YES PARENT_SCOPE)
cpm_export_variables()
cpm_export_variables(${CPM_ARGS_NAME})
else()
SET(CPM_PACKAGE_ALREADY_ADDED NO PARENT_SCOPE)
endif()
Expand All @@ -181,6 +187,7 @@ function(CPMAddPackage)
DOWNLOAD_ONLY
GITHUB_REPOSITORY
GITLAB_REPOSITORY
GIT_REPOSITORY
SOURCE_DIR
DOWNLOAD_COMMAND
FIND_PACKAGE_ARGUMENTS
Expand All @@ -192,6 +199,8 @@ function(CPMAddPackage)

cmake_parse_arguments(CPM_ARGS "" "${oneValueArgs}" "${multiValueArgs}" "${ARGN}")

# Set default values for arguments

if (NOT DEFINED CPM_ARGS_VERSION)
if (DEFINED CPM_ARGS_GIT_TAG)
cpm_get_version_from_git_tag("${CPM_ARGS_GIT_TAG}" CPM_ARGS_VERSION)
Expand All @@ -201,36 +210,55 @@ function(CPMAddPackage)
endif()
endif()

if (NOT DEFINED CPM_ARGS_GIT_TAG)
set(CPM_ARGS_GIT_TAG v${CPM_ARGS_VERSION})
endif()

list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS GIT_TAG ${CPM_ARGS_GIT_TAG})

if(CPM_ARGS_DOWNLOAD_ONLY)
set(DOWNLOAD_ONLY ${CPM_ARGS_DOWNLOAD_ONLY})
else()
set(DOWNLOAD_ONLY NO)
endif()

if (CPM_ARGS_GITHUB_REPOSITORY)
list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS GIT_REPOSITORY "https://github.com/${CPM_ARGS_GITHUB_REPOSITORY}.git")
set(CPM_ARGS_GIT_REPOSITORY "https://github.com/${CPM_ARGS_GITHUB_REPOSITORY}.git")
endif()

if (CPM_ARGS_GITLAB_REPOSITORY)
list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS GIT_REPOSITORY "https://gitlab.com/${CPM_ARGS_GITLAB_REPOSITORY}.git")
list(CPM_ARGS_GIT_REPOSITORY "https://gitlab.com/${CPM_ARGS_GITLAB_REPOSITORY}.git")
endif()

if (DEFINED CPM_ARGS_GIT_REPOSITORY)
list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS GIT_REPOSITORY ${CPM_ARGS_GIT_REPOSITORY})
if (NOT DEFINED CPM_ARGS_GIT_TAG)
set(CPM_ARGS_GIT_TAG v${CPM_ARGS_VERSION})
endif()
endif()

if (CPM_ARGS_GIT_TAG)
list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS GIT_TAG ${CPM_ARGS_GIT_TAG})
endif()

# Check if package has been added before
CPMCheckIfPackageAlreadyAdded(${CPM_ARGS_NAME} "${CPM_ARGS_VERSION}" "${CPM_ARGS_OPTIONS}")
if (CPM_PACKAGE_ALREADY_ADDED)
cpm_export_variables()
cpm_export_variables(${CPM_ARGS_NAME})
return()
endif()

# Check for available declaration
if (DEFINED "CPM_DECLARATION_${CPM_ARGS_NAME}" AND NOT "${CPM_DECLARATION_${CPM_ARGS_NAME}}" STREQUAL "")
set(declaration ${CPM_DECLARATION_${CPM_ARGS_NAME}})
set(CPM_DECLARATION_${CPM_ARGS_NAME} "")
CPMAddPackage(${declaration})
set(CPM_DECLARATION_${CPM_ARGS_NAME} "${declaration}")
cpm_export_variables(${CPM_ARGS_NAME})
# checking again to ensure version and option compatibility
CPMCheckIfPackageAlreadyAdded(${CPM_ARGS_NAME} "${CPM_ARGS_VERSION}" "${CPM_ARGS_OPTIONS}")
return()
endif()

if(CPM_USE_LOCAL_PACKAGES OR CPM_LOCAL_PACKAGES_ONLY)
cpm_find_package(${CPM_ARGS_NAME} "${CPM_ARGS_VERSION}" ${CPM_ARGS_FIND_PACKAGE_ARGUMENTS})

if(CPM_PACKAGE_FOUND)
cpm_export_variables(${CPM_ARGS_NAME})
return()
endif()

Expand All @@ -248,28 +276,26 @@ function(CPMAddPackage)
endforeach()
endif()

set(FETCH_CONTENT_DECLARE_EXTRA_OPTS "")

if (DEFINED CPM_ARGS_GIT_TAG)
set(PACKAGE_INFO "${CPM_ARGS_GIT_TAG}")
else()
set(PACKAGE_INFO "${CPM_ARGS_VERSION}")
endif()

if (DEFINED CPM_ARGS_DOWNLOAD_COMMAND)
set(FETCH_CONTENT_DECLARE_EXTRA_OPTS DOWNLOAD_COMMAND ${CPM_ARGS_DOWNLOAD_COMMAND})
list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS DOWNLOAD_COMMAND ${CPM_ARGS_DOWNLOAD_COMMAND})
elseif(DEFINED CPM_ARGS_SOURCE_DIR)
set(FETCH_CONTENT_DECLARE_EXTRA_OPTS SOURCE_DIR ${CPM_ARGS_SOURCE_DIR})
list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS SOURCE_DIR ${CPM_ARGS_SOURCE_DIR})
elseif (CPM_SOURCE_CACHE)
string(TOLOWER ${CPM_ARGS_NAME} lower_case_name)
set(origin_parameters ${CPM_ARGS_UNPARSED_ARGUMENTS})
list(SORT origin_parameters)
string(SHA1 origin_hash "${origin_parameters}")
set(download_directory ${CPM_SOURCE_CACHE}/${lower_case_name}/${origin_hash})
list(APPEND FETCH_CONTENT_DECLARE_EXTRA_OPTS SOURCE_DIR ${download_directory})
list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS SOURCE_DIR ${download_directory})
if (EXISTS ${download_directory})
# disable the download command to allow offline builds
list(APPEND FETCH_CONTENT_DECLARE_EXTRA_OPTS DOWNLOAD_COMMAND "${CMAKE_COMMAND}")
list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS DOWNLOAD_COMMAND "${CMAKE_COMMAND}")
set(PACKAGE_INFO "${download_directory}")
else()
# remove timestamps so CMake will re-download the dependency
Expand All @@ -278,23 +304,65 @@ function(CPMAddPackage)
endif()
endif()

cpm_declare_fetch(${CPM_ARGS_NAME} ${CPM_ARGS_VERSION} ${PACKAGE_INFO} "${CPM_ARGS_UNPARSED_ARGUMENTS}" ${FETCH_CONTENT_DECLARE_EXTRA_OPTS})
cpm_declare_fetch(${CPM_ARGS_NAME} ${CPM_ARGS_VERSION} ${PACKAGE_INFO} ${CPM_ARGS_UNPARSED_ARGUMENTS})
cpm_fetch_package(${CPM_ARGS_NAME} ${DOWNLOAD_ONLY})
cpm_get_fetch_properties(${CPM_ARGS_NAME})
CPMCreateModuleFile(${CPM_ARGS_NAME} "CPMAddPackage(${ARGN})")
if (TARGET cpm-update-package-lock)
cpm_add_to_package_lock(${CPM_ARGS_NAME} "${ARGN}")
endif()
SET(${CPM_ARGS_NAME}_ADDED YES)
cpm_export_variables()
cpm_export_variables(${CPM_ARGS_NAME})
endfunction()

# Fetch a previously declared package
macro(CPMGetPackage Name)
if (DEFINED "CPM_DECLARATION_${Name}")
CPMAddPackage(
NAME ${Name}
)
else()
message(SEND_ERROR "Cannot retrieve package ${Name}: no declaration available")
endif()
endmacro()

# export variables available to the caller to the parent scope
# expects ${CPM_ARGS_NAME} to be set
macro(cpm_export_variables)
SET(${CPM_ARGS_NAME}_SOURCE_DIR "${${CPM_ARGS_NAME}_SOURCE_DIR}" PARENT_SCOPE)
SET(${CPM_ARGS_NAME}_BINARY_DIR "${${CPM_ARGS_NAME}_BINARY_DIR}" PARENT_SCOPE)
SET(${CPM_ARGS_NAME}_ADDED "${${CPM_ARGS_NAME}_ADDED}" PARENT_SCOPE)
macro(cpm_export_variables name)
SET(${name}_SOURCE_DIR "${${name}_SOURCE_DIR}" PARENT_SCOPE)
SET(${name}_BINARY_DIR "${${name}_BINARY_DIR}" PARENT_SCOPE)
SET(${name}_ADDED "${${name}_ADDED}" PARENT_SCOPE)
endmacro()

# declares a package, so that any call to CPMAddPackage for the
# package name will use these arguments instead
macro(CPMDeclarePackage Name)
if (NOT DEFINED "CPM_DECLARATION_${Name}")
set("CPM_DECLARATION_${Name}" "${ARGN}")
endif()
endmacro()

function(cpm_add_to_package_lock Name)
if (NOT CPM_DONT_CREATE_PACKAGE_LOCK)
file(APPEND ${CPM_PACKAGE_LOCK_FILE} "# ${Name}\nCPMDeclarePackage(${Name} \"${ARGN}\")\n")
endif()
endfunction()

# includes the package lock file if it exists and creates a target
# `cpm-write-package-lock` to update it
macro(CPMUsePackageLock file)
if (NOT CPM_DONT_CREATE_PACKAGE_LOCK)
get_filename_component(CPM_ABSOLUTE_PACKAGE_LOCK_PATH ${file} ABSOLUTE)
if(EXISTS ${CPM_ABSOLUTE_PACKAGE_LOCK_PATH})
include(${CPM_ABSOLUTE_PACKAGE_LOCK_PATH})
endif()
if (NOT TARGET cpm-update-package-lock)
add_custom_target(cpm-update-package-lock COMMAND ${CMAKE_COMMAND} -E copy ${CPM_PACKAGE_LOCK_FILE} ${CPM_ABSOLUTE_PACKAGE_LOCK_PATH})
endif()
endif()
endmacro()

# declares that a package has been added to CPM
# registers a package that has been added to CPM
function(CPMRegisterPackage PACKAGE VERSION)
list(APPEND CPM_PACKAGES ${PACKAGE})
set(CPM_PACKAGES ${CPM_PACKAGES} CACHE INTERNAL "")
Expand All @@ -315,8 +383,7 @@ function (cpm_declare_fetch PACKAGE VERSION INFO)
return()
endif()

FetchContent_Declare(
${PACKAGE}
FetchContent_Declare(${PACKAGE}
${ARGN}
)
endfunction()
Expand All @@ -334,23 +401,22 @@ endfunction()

# downloads a previously declared package via FetchContent
function (cpm_fetch_package PACKAGE DOWNLOAD_ONLY)

if (${CPM_DRY_RUN})
message(STATUS "${CPM_INDENT} package ${PACKAGE} not fetched (dry run)")
return()
endif()

set(CPM_OLD_INDENT "${CPM_INDENT}")
set(CPM_INDENT "${CPM_INDENT} ${PACKAGE}:")
if(${DOWNLOAD_ONLY})
if(DOWNLOAD_ONLY)
FetchContent_GetProperties(${PACKAGE})
if(NOT ${PACKAGE}_POPULATED)
FetchContent_Populate(${PACKAGE})
endif()
else()
set(CPM_OLD_INDENT "${CPM_INDENT}")
set(CPM_INDENT "${CPM_INDENT} ${PACKAGE}:")
FetchContent_MakeAvailable(${PACKAGE})
set(CPM_INDENT "${CPM_OLD_INDENT}")
endif()
set(CPM_INDENT "${CPM_OLD_INDENT}")
endfunction()

# splits a package option
Expand Down
2 changes: 1 addition & 1 deletion test/unit/modules.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ set(TEST_BUILD_DIR ${CMAKE_CURRENT_BINARY_DIR}/modules)

function(initProjectWithDependency TEST_DEPENDENCY_NAME)
configure_package_config_file(
"${CMAKE_CURRENT_LIST_DIR}/test_project/CMakeLists.txt.in"
"${CMAKE_CURRENT_LIST_DIR}/test_project/ModuleCMakeLists.txt.in"
"${CMAKE_CURRENT_LIST_DIR}/test_project/CMakeLists.txt"
INSTALL_DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/junk
)
Expand Down
48 changes: 48 additions & 0 deletions test/unit/package-lock.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@

include(CMakePackageConfigHelpers)
include(${CPM_PATH}/testing.cmake)

set(TEST_BUILD_DIR ${CMAKE_CURRENT_BINARY_DIR}/package-lock)

function(configureWithDeclare DECLARE_DEPENDENCY)
execute_process(COMMAND ${CMAKE_COMMAND} -E rm -rf ${TEST_BUILD_DIR})

if (DECLARE_DEPENDENCY)
set(PREPARE_CODE "CPMDeclarePackage(Dependency
NAME Dependency
SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/test_project/dependency
)")
else()
set(PREPARE_CODE "")
endif()

configure_package_config_file(
"${CMAKE_CURRENT_LIST_DIR}/test_project/PackageLockCMakeLists.txt.in"
"${CMAKE_CURRENT_LIST_DIR}/test_project/CMakeLists.txt"
INSTALL_DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/junk
)

execute_process(
COMMAND ${CMAKE_COMMAND} -H${CMAKE_CURRENT_LIST_DIR}/test_project -B${TEST_BUILD_DIR}
RESULT_VARIABLE ret
)

ASSERT_EQUAL(${ret} "0")
endfunction()

function(updatePackageLock)
execute_process(
COMMAND ${CMAKE_COMMAND} --build ${TEST_BUILD_DIR} --target cpm-update-package-lock
RESULT_VARIABLE ret
)

ASSERT_EQUAL(${ret} "0")
endfunction()

execute_process(COMMAND ${CMAKE_COMMAND} -E rm -f ${CMAKE_CURRENT_LIST_DIR}/test_project/package-lock.cmake)
configureWithDeclare(YES)
ASSERT_NOT_EXISTS(${CMAKE_CURRENT_LIST_DIR}/test_project/package-lock.cmake)
updatePackageLock()
ASSERT_EXISTS(${CMAKE_CURRENT_LIST_DIR}/test_project/package-lock.cmake)
configureWithDeclare(NO)

2 changes: 1 addition & 1 deletion test/unit/source_dir.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ set(TEST_BUILD_DIR ${CMAKE_CURRENT_BINARY_DIR}/source_dir)
set(TEST_DEPENDENCY_NAME Dependency)

configure_package_config_file(
"${CMAKE_CURRENT_LIST_DIR}/test_project/CMakeLists.txt.in"
"${CMAKE_CURRENT_LIST_DIR}/test_project/ModuleCMakeLists.txt.in"
"${CMAKE_CURRENT_LIST_DIR}/test_project/CMakeLists.txt"
INSTALL_DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/junk
)
Expand Down
3 changes: 2 additions & 1 deletion test/unit/test_project/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
/CMakeLists.txt
/CMakeLists.txt
/package-lock.cmake
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.14 FATAL_ERROR)

project(CPMExampleCatch2)
project(CPMTest)

# ---- Options ----

Expand Down
Loading