-
Notifications
You must be signed in to change notification settings - Fork 1
/
RepoMan.cmake
executable file
·360 lines (294 loc) · 18.6 KB
/
RepoMan.cmake
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
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
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
#[=======================================================================[.rst:
RepoMan
-------
Overview
^^^^^^^^
This module handles project dependencies.
Usage
^^^^^
Including the module is sufficient. It will automatically look for a dependencies file in ``PROJECT_SOURCE_DIR`` and resolve the dependencies recursively.
.. code-block:: cmake
# Optional: use workspace instead of default FetchContent directories
set(REPOMAN_DEPENDENCIES_USE_WORKSPACE ON CACHE BOOL "")
# Optional: set a custom name and/or location for the workspace
set(REPOMAN_DEPENDENCIES_WORKSPACE "../" CACHE PATH "") # use automatically generated directory name next to current project
set(REPOMAN_DEPENDENCIES_WORKSPACE "../my_ws" CACHE PATH "") # use custom directory name next to current project
set(REPOMAN_DEPENDENCIES_WORKSPACE "my_ws" CACHE PATH "") # use custom name inside current project build directory
set(REPOMAN_DEPENDENCIES_WORKSPACE "/home/dev/sources/my_ws" CACHE PATH "") # use absolute path
# Optional: use a custom file name for dependency files
set(REPOMAN_DEPENDENCIES_FILE_NAME "my_deps.txt" CACHE STRING "")
include(RepoMan)
Alternatively, you can also include it via add_subdirectory() or provide it via FetchContent():
.. code-block:: cmake
include(FetchContent)
FetchContent_Declare(
cmake_utilities
GIT_REPOSITORY https://github.com/daixtrose/cmake_utilities
GIT_TAG main
)
# Optional: use workspace instead of default FetchContent directories
set(REPOMAN_DEPENDENCIES_USE_WORKSPACE ON CACHE BOOL "")
# Optional: set a custom name and/or location for the workspace
set(REPOMAN_DEPENDENCIES_WORKSPACE "../" CACHE PATH "") # use automatically generated directory name next to current project
set(REPOMAN_DEPENDENCIES_WORKSPACE "../my_ws" CACHE PATH "") # use custom directory name next to current project
set(REPOMAN_DEPENDENCIES_WORKSPACE "my_ws" CACHE PATH "") # use custom name inside current project build directory
set(REPOMAN_DEPENDENCIES_WORKSPACE "/home/dev/sources/my_ws" CACHE PATH "") # use absolute path
# Optional: use a custom file name for dependency files
set(REPOMAN_DEPENDENCIES_FILE_NAME "my_deps.txt" CACHE STRING "")
FetchContent_MakeAvailable(cmake_utilities)
You can also run the RepoManResolve.cmake script as a command directly from the command line. This also works for non-CMake projects. This s called ``script mode``, in contrast to ``project mode``, which is the normal usage of the module though a CMake project file.
In oder to actually do anything, the project root directory must contain a ``dependencies. txt`` file or a file with a different name, if ``REPOMAN_DEPENDENCIES_FILE_NAME`` is set accordingly.
This file must contain one line for each dependency, in the format given to ``FetchContent()``. All arguments of ``FetchContent()`` are supported. Empty and commented lines are also allowed and will be ignored.
Each dependency defined this way will be provided and included via ``add_subdirectory()``. Any sub-dependencies in a dependency's ``dependencies.txt`` will also be added. If a dependency has already been defined in a parent project, that definition takes precedence, so higher-level projects can override their child dependency's requirements.
Variables
^^^^^^^^^
The following variables modify the behaviour of the module. They are set to reasonable default values.
.. note::
If you want to modify the variables in your project code, you should do so before including the module.
.. variable:: REPOMAN_DEPENDENCIES_USE_WORKSPACE
Use the workspace defined by ``REPOMAN_DEPENDENCIES_WORKSPACE`` for dependency sources instead of the default FetchContent directories. This allows easier editing of dependency sources. Defaults to ``ON`` in script mode and ``OFF`` in project mode.
.. variable:: REPOMAN_DEPENDENCIES_WORKSPACE
Where to put the dependency sources. This can be either empty, a name, a relative path or an absolute path.
An empty string will use the defaults: a directory name generated from the current project directory, in the current project's parent directory.
A name will use that given name, in the current project directory.
A relative or absolute path will use that path as a base. A new directory will be generated only if the given path is a starting substring of the current project path,
This is ignored unless ``REPOMAN_DEPENDENCIES_USE_WORKSPACE`` is ``ON``.
.. variable:: REPOMAN_DEPENDENCIES_FILE_NAME
The dependencies file name, ``dependencies.txt`` by default.
#]=======================================================================]
# Update this if the dependencies file format changes
set(DEPENDENCIES_FILE_REQUIRED_VERSION "v1.0.0")
cmake_path(GET CMAKE_SOURCE_DIR FILENAME PROJECT_DIRECTORY_NAME)
if(CMAKE_SCRIPT_MODE_FILE)
set(SCRIPT_MODE TRUE)
# If RepoMan is run as a script (outside of a CMake project), the variables below do not have any meaning by default.
set(PROJECT_SOURCE_DIR $ENV{PWD})
set(FETCHCONTENT_BASE_DIR "${CMAKE_SOURCE_DIR}/../RepoMan-${PROJECT_DIRECTORY_NAME}-temp" CACHE PATH "The FetchContent base directory, modified by RepoMan." FORCE)
else()
set(SCRIPT_MODE FALSE)
endif()
# Module Configuration
set(REPOMAN_DEPENDENCIES_USE_WORKSPACE ${SCRIPT_MODE} CACHE BOOL "Allow editing of dependencies. This puts the sources next to the main project to allow easier editing.")
set(REPOMAN_DEPENDENCIES_FILE_NAME "dependencies.txt" CACHE STRING "The dependencies file name.")
mark_as_advanced(REPOMAN_DEPENDENCIES_FILE_NAME)
if(REPOMAN_DEPENDENCIES_WORKSPACE)
file(REAL_PATH "${REPOMAN_DEPENDENCIES_WORKSPACE}" REPOMAN_WORKSPACE BASE_DIRECTORY "${CMAKE_SOURCE_DIR}" EXPAND_TILDE)
cmake_path(IS_PREFIX REPOMAN_WORKSPACE "${CMAKE_SOURCE_DIR}" NORMALIZE IS_PREFIX)
cmake_path(COMPARE "${REPOMAN_WORKSPACE}" EQUAL "${CMAKE_SOURCE_DIR}" IS_PROJECT_DIR)
if(IS_PREFIX OR IS_PROJECT_DIR)
file(REAL_PATH "${REPOMAN_WORKSPACE}/${PROJECT_DIRECTORY_NAME}-dependencies" REPOMAN_WORKSPACE)
endif()
else()
file(REAL_PATH "${CMAKE_SOURCE_DIR}/../${PROJECT_DIRECTORY_NAME}-dependencies" REPOMAN_WORKSPACE_INIT EXPAND_TILDE)
set(REPOMAN_DEPENDENCIES_WORKSPACE "${REPOMAN_WORKSPACE_INIT}" CACHE STRING "The base workspace for projects.")
set(REPOMAN_WORKSPACE "${REPOMAN_DEPENDENCIES_WORKSPACE}")
endif()
include(FetchContent)
#[[
repoman__internal__handle_dependencies()
Resolve and provide dependencies recursively.
Arguments
^^^^^^^^^
DIRECTORY The directory to search for REPOMAN_DEPENDENCIES_FILE_NAME
#]]
function(repoman__internal__handle_dependencies DIRECTORY)
set(REPOMAN_DEPENDENCY_FILE "${DIRECTORY}/${REPOMAN_DEPENDENCIES_FILE_NAME}")
if(EXISTS "${REPOMAN_DEPENDENCY_FILE}")
message(STATUS "Resolving dependencies of project ${DIRECTORY}")
file(MAKE_DIRECTORY "${REPOMAN_WORKSPACE}")
if(NOT SCRIPT_MODE)
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${REPOMAN_DEPENDENCY_FILE}")
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${REPOMAN_WORKSPACE}")
endif()
unset(REPOMAN_DEPENDENCIES)
file(STRINGS "${REPOMAN_DEPENDENCY_FILE}" REPOMAN_DEPENDENCY_SPECS ENCODING UTF-8)
list(GET REPOMAN_DEPENDENCY_SPECS 0 VERSION_INFO)
if(VERSION_INFO MATCHES "Version: *(v[0-9\.]+)")
set(DEPENDENCIES_FILE_VERSION "${CMAKE_MATCH_1}")
list(POP_FRONT REPOMAN_DEPENDENCY_SPECS)
else()
set(DEPENDENCIES_FILE_VERSION "<undefined>")
endif()
if(NOT DEPENDENCIES_FILE_REQUIRED_VERSION STREQUAL DEPENDENCIES_FILE_VERSION)
message(FATAL_ERROR "Dependencies file '${REPOMAN_DEPENDENCY_FILE}' has version '${DEPENDENCIES_FILE_VERSION}', but required version is '${DEPENDENCIES_FILE_REQUIRED_VERSION}'")
endif()
foreach(DEPENDENCY IN LISTS REPOMAN_DEPENDENCY_SPECS)
# Filter out empty or commented lines
if(DEPENDENCY MATCHES "^ *#.*" OR DEPENDENCY STREQUAL "")
continue()
endif()
# Change line to list
string(REPLACE " " ";" DEPENDENCY_INFO ${DEPENDENCY})
# Parse dependency definition to be used in checks and printed information
list(GET DEPENDENCY_INFO 0 REPOMAN_DEPENDENCY_NAME)
unset(REPOMAN_DEPENDENCY_GIT_REPOSITORY)
unset(REPOMAN_DEPENDENCY_URL)
unset(REPOMAN_DEPENDENCY_SVN_REPOSITORY)
unset(REPOMAN_DEPENDENCY_HG_REPOSITORY)
unset(REPOMAN_DEPENDENCY_CVS_REPOSITORY)
cmake_parse_arguments(REPOMAN_DEPENDENCY
""
"GIT_REPOSITORY;GIT_TAG;URL_HASH;URL_MD5;SVN_REPOSITORY;SVN_REVISION;HG_REPOSITORY;HG_TAG;CVS_REPOSITORY;CVS_MODULE;CVS_TAG"
"URL"
"${DEPENDENCY_INFO}")
if(REPOMAN_DEPENDENCY_GIT_REPOSITORY)
set(REPOMAN_DEPENDENCY_URI ${REPOMAN_DEPENDENCY_GIT_REPOSITORY})
set(REPOMAN_DEPENDENCY_REVISION ${REPOMAN_DEPENDENCY_GIT_TAG})
elseif(REPOMAN_DEPENDENCY_URL)
set(REPOMAN_DEPENDENCY_URI ${REPOMAN_DEPENDENCY_URL})
if(REPOMAN_DEPENDENCY_URL_HASH)
set(REPOMAN_DEPENDENCY_REVISION ${REPOMAN_DEPENDENCY_URL_HASH})
elseif(REPOMAN_DEPENDENCY_URL_MD5)
set(REPOMAN_DEPENDENCY_REVISION ${REPOMAN_DEPENDENCY_URL_MD5})
endif()
elseif(REPOMAN_DEPENDENCY_SVN_REPOSITORY)
set(REPOMAN_DEPENDENCY_URI ${REPOMAN_DEPENDENCY_SVN_REPOSITORY})
set(REPOMAN_DEPENDENCY_REVISION ${REPOMAN_DEPENDENCY_SVN_TAG})
elseif(REPOMAN_DEPENDENCY_HG_REPOSITORY)
set(REPOMAN_DEPENDENCY_URI ${REPOMAN_DEPENDENCY_HG_REPOSITORY})
set(REPOMAN_DEPENDENCY_REVISION ${REPOMAN_DEPENDENCY_HG_TAG})
elseif(REPOMAN_DEPENDENCY_CVS_REPOSITORY)
set(REPOMAN_DEPENDENCY_URI ${REPOMAN_DEPENDENCY_CVS_REPOSITORY})
set(REPOMAN_DEPENDENCY_REVISION ${REPOMAN_DEPENDENCY_CVS_TAG})
endif()
message(STATUS "Checking dependency '${REPOMAN_DEPENDENCY_NAME}': ${REPOMAN_DEPENDENCY_URI} @ ${REPOMAN_DEPENDENCY_REVISION}")
# Set first-encountered revision
get_property(OVERWRITE_REVISION GLOBAL PROPERTY ${REPOMAN_DEPENDENCY_NAME}_REVISION)
if(NOT OVERWRITE_REVISION)
set_property(GLOBAL PROPERTY ${REPOMAN_DEPENDENCY_NAME}_REVISION ${REPOMAN_DEPENDENCY_REVISION})
set_property(GLOBAL APPEND PROPERTY GLOBAL_REPOMAN_DEPENDENCIES ${REPOMAN_DEPENDENCY_NAME})
endif()
set_property(GLOBAL APPEND PROPERTY ${REPOMAN_DEPENDENCY_NAME}_REQUESTED_REVISIONS ${REPOMAN_DEPENDENCY_REVISION})
list(APPEND REPOMAN_DEPENDENCIES ${REPOMAN_DEPENDENCY_NAME})
# Set depedencvy directories
if(REPOMAN_DEPENDENCIES_USE_WORKSPACE)
set(DEPENDENCY_SOURCE_DIR ${REPOMAN_WORKSPACE}/${REPOMAN_DEPENDENCY_NAME})
else()
set(DEPENDENCY_SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/${REPOMAN_DEPENDENCY_NAME}-src)
endif()
set(DEPENDENCY_BINARY_DIR ${FETCHCONTENT_BASE_DIR}/${REPOMAN_DEPENDENCY_NAME}-build)
set(DEPENDENCY_SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/${REPOMAN_DEPENDENCY_NAME}-subbuild)
# FetchContent_Declare() defines properties and i thus not usable in script mode
if(NOT SCRIPT_MODE)
FetchContent_Declare(${DEPENDENCY_INFO}
SOURCE_DIR "${DEPENDENCY_SOURCE_DIR}"
BINARY_DIR "${DEPENDENCY_BINARY_DIR}"
SUBBUILD_DIR "${DEPENDENCY_SUBBUILD_DIR}")
endif()
# Handle dependency
if(NOT EXISTS "${DEPENDENCY_SOURCE_DIR}" OR NOT REPOMAN_DEPENDENCIES_USE_WORKSPACE)
# Define and fetch dependencies
FetchContent_GetProperties(${REPOMAN_DEPENDENCY_NAME} POPULATED IS_POPULATED)
if(NOT IS_POPULATED)
message(STATUS "Initializing in '${DEPENDENCY_SOURCE_DIR}'")
if(SCRIPT_MODE)
# Script mode without FetchContent_Declare(): define and provide
FetchContent_Populate(${DEPENDENCY_INFO}
SOURCE_DIR "${DEPENDENCY_SOURCE_DIR}"
BINARY_DIR "${DEPENDENCY_BINARY_DIR}"
SUBBUILD_DIR "${DEPENDENCY_SUBBUILD_DIR}")
else()
# Inside a CMake project: dependency has been declared with FetchContent_Declare() above.
FetchContent_Populate(${REPOMAN_DEPENDENCY_NAME})
endif()
if(NOT REPOMAN_DEPENDENCY_URI)
string(TOLOWER ${REPOMAN_DEPENDENCY_NAME} LOWER_NAME)
string(TOUPPER ${REPOMAN_DEPENDENCY_NAME} UPPER_NAME)
set(FETCHCONTENT_SOURCE_DIR_${UPPER_NAME} "${${LOWER_NAME}_SOURCE_DIR}")
endif()
endif()
else()
# Dependency already exists, show status information
string(TOLOWER ${REPOMAN_DEPENDENCY_NAME} LOWER_NAME)
set(FETCH_CONTENT_PREFIX "_FetchContent_${LOWER_NAME}")
set_property(GLOBAL PROPERTY "${FETCH_CONTENT_PREFIX}_sourceDir" "${DEPENDENCY_SOURCE_DIR}")
set_property(GLOBAL PROPERTY "${FETCH_CONTENT_PREFIX}_binaryDir" "${DEPENDENCY_BINARY_DIR}")
set_property(GLOBAL PROPERTY "${FETCH_CONTENT_PREFIX}_populated" TRUE)
file(MAKE_DIRECTORY "${DEPENDENCY_BINARY_DIR}")
if(OVERWRITE_REVISION AND NOT REPOMAN_DEPENDENCY_REVISION STREQUAL OVERWRITE_REVISION)
# Print message in case a dependency is overridden by a parent.
message(STATUS "Dependency '${REPOMAN_DEPENDENCY_NAME} @ ${REPOMAN_DEPENDENCY_REVISION}' is overridden with '${REPOMAN_DEPENDENCY_NAME} @ ${OVERWRITE_REVISION}'")
set(REPOMAN_DEPENDENCY_REVISION ${OVERWRITE_REVISION})
endif()
if (EXISTS ${DEPENDENCY_SOURCE_DIR}/.git AND NOT OVERWRITE_REVISION)
# Show status of local repository
# Only supports git for now
set(NAME ${REPOMAN_DEPENDENCY_NAME})
set(REPO ${DEPENDENCY_SOURCE_DIR})
set(EXPECTED_REVISION ${REPOMAN_DEPENDENCY_REVISION})
set(EXPECTED_REMOTE ${REPOMAN_DEPENDENCY_URI})
include(${CMAKE_CURRENT_FUNCTION_LIST_DIR}/RepoManStatus.cmake)
endif()
endif()
set_property(GLOBAL PROPERTY ${REPOMAN_DEPENDENCY_NAME}_EXPECTED_REVISION ${REPOMAN_DEPENDENCY_REVISION})
set_property(GLOBAL PROPERTY ${REPOMAN_DEPENDENCY_NAME}_EXPECTED_REMOTE ${REPOMAN_DEPENDENCY_URI})
endforeach()
# Include dependencies as sub-projects and resolve their dependencies
foreach(DEPENDENCY IN LISTS REPOMAN_DEPENDENCIES)
get_property(ADDED GLOBAL PROPERTY ${DEPENDENCY}_ADDED)
string(TOLOWER ${DEPENDENCY} NAME)
FetchContent_GetProperties(${NAME})
# Add not-yet included dependencies
if(NOT ADDED AND ${NAME}_POPULATED)
repoman__internal__handle_dependencies(${${NAME}_SOURCE_DIR})
if(NOT SCRIPT_MODE)
# add_subdirectory() does not work in script mode
add_subdirectory(${${NAME}_SOURCE_DIR} ${${NAME}_BINARY_DIR})
endif()
set_property(GLOBAL PROPERTY ${DEPENDENCY}_ADDED TRUE)
endif()
endforeach()
endif()
endfunction()
# repoman__internal__print_summary()
# Prints a summary of requested and provided dependencies.
function(repoman__internal__print_summary)
get_property(REPOMAN_DEPENDENCIES GLOBAL PROPERTY GLOBAL_REPOMAN_DEPENDENCIES)
if(REPOMAN_DEPENDENCIES)
message(STATUS "Dependencies:")
foreach(DEPENDENCY IN LISTS REPOMAN_DEPENDENCIES)
get_property(REVISION GLOBAL PROPERTY ${DEPENDENCY}_REVISION)
get_property(ALL_REVISIONS GLOBAL PROPERTY ${DEPENDENCY}_REQUESTED_REVISIONS)
list(LENGTH ALL_REVISIONS REVISION_COUNT)
if(REVISION_COUNT GREATER 1)
string(REPLACE ";" ", " ALL_REVISIONS "${ALL_REVISIONS}")
set(CHOICES ", chosen from [${ALL_REVISIONS}]")
else()
set(CHOICES "")
endif()
message(STATUS " ${DEPENDENCY} @ ${REVISION}${CHOICES}")
endforeach()
endif()
endfunction()
# Run dependency resolution
repoman__internal__handle_dependencies(${PROJECT_SOURCE_DIR})
# Print summary after resolving all dependencies.
if(SCRIPT_MODE)
repoman__internal__print_summary()
file(REMOVE_RECURSE "${FETCHCONTENT_BASE_DIR}")
else()
if(NOT TARGET repoman-status)
get_property(REPOMAN_DEPENDENCIES GLOBAL PROPERTY GLOBAL_REPOMAN_DEPENDENCIES)
if(REPOMAN_DEPENDENCIES)
foreach(DEPENDENCY IN LISTS REPOMAN_DEPENDENCIES)
get_property(DEPENDENCY_SOURCE_DIR GLOBAL PROPERTY ${DEPENDENCY}_SOURCE_DIR)
get_property(DEPENDENCY_EXPECTED_REVISION GLOBAL PROPERTY ${DEPENDENCY}_EXPECTED_REVISION)
get_property(DEPENDENCY_EXPECTED_REMOTE GLOBAL PROPERTY ${DEPENDENCY}_EXPECTED_REMOTE)
if (EXISTS ${${DEPENDENCY}_SOURCE_DIR}/.git)
list(APPEND REPOMAN_STATUS_COMMANDS "COMMAND" "${CMAKE_COMMAND}" "-DNAME=${DEPENDENCY}" "-DREPO=${${DEPENDENCY}_SOURCE_DIR}" "-DEXPECTED_REVISION=${DEPENDENCY_EXPECTED_REVISION}" "-DEXPECTED_REMOTE=${DEPENDENCY_EXPECTED_REMOTE}" "-P" "${CMAKE_CURRENT_LIST_DIR}/RepoManStatus.cmake")
endif()
endforeach()
add_custom_target(repoman-status
${REPOMAN_STATUS_COMMANDS})
endif()
endif()
get_property(DEFER_INSTALLED GLOBAL PROPERTY REPOMAN_DEFER_INSTALLED)
if(NOT DEFER_INSTALLED)
cmake_language(DEFER DIRECTORY "${CMAKE_SOURCE_DIR}" ID repo_man_summary CALL repoman__internal__print_summary)
set_property(GLOBAL PROPERTY REPOMAN_DEFER_INSTALLED TRUE)
endif()
endif()
# Prevent setup function to be called from outside after inclusion
function(repoman__internal__handle_dependencies)
message(FATAL_ERROR "Please do not call any RepoMan functions. Including the module is sufficient.")
endfunction()