aboutsummaryrefslogtreecommitdiff
path: root/cmake/scripts/common/ModuleHelpers.cmake
blob: e9657cea1e503612876a37dd09b960808c200f8a (plain)
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
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
# This script provides helper functions for FindModules

# Parse and set variables from VERSION dependency file
# On return:
#   MODULENAME_ARCHIVE will be set to parent scope
#   MODULENAME_VER will be set to parent scope (eg FFMPEG_VER, DAV1D_VER)
#   MODULENAME_BASE_URL will be set to parent scope if exists in VERSION file (eg FFMPEG_BASE_URL)
#   MODULENAME_HASH will be set if either SHA256 or SHA512 exists in VERSION file
#   MODULENAME_BYPRODUCT will be set to parent scope
function(get_versionfile_data)

  # Dependency path
  set(MODULE_PATH "${PROJECTSOURCE}/tools/depends/${LIB_TYPE}/${MODULE_LC}")

  if(NOT EXISTS "${MODULE_PATH}/${MODULE}-VERSION")
    MESSAGE(FATAL_ERROR "${MODULE}-VERSION does not exist at ${MODULE_PATH}.")
  else()
    set(${MODULE}_FILE "${MODULE_PATH}/${MODULE}-VERSION")
  endif()

  file(STRINGS ${${MODULE}_FILE} ${MODULE}_LNAME REGEX "^[ \t]*LIBNAME=")
  file(STRINGS ${${MODULE}_FILE} ${MODULE}_VER REGEX "^[ \t]*VERSION=")
  file(STRINGS ${${MODULE}_FILE} ${MODULE}_ARCHIVE REGEX "^[ \t]*ARCHIVE=")
  file(STRINGS ${${MODULE}_FILE} ${MODULE}_BASE_URL REGEX "^[ \t]*BASE_URL=")
  if(WIN32 OR WINDOWS_STORE)
    file(STRINGS ${${MODULE}_FILE} ${MODULE}_BYPRODUCT REGEX "^[ \t]*BYPRODUCT_WIN=")
  else()
    file(STRINGS ${${MODULE}_FILE} ${MODULE}_BYPRODUCT REGEX "^[ \t]*BYPRODUCT=")
  endif()

  # Tarball Hash
  file(STRINGS ${${MODULE}_FILE} ${MODULE}_HASH_SHA256 REGEX "^[ \t]*SHA256=")
  file(STRINGS ${${MODULE}_FILE} ${MODULE}_HASH_SHA512 REGEX "^[ \t]*SHA512=")

  string(REGEX REPLACE ".*LIBNAME=([^ \t]*).*" "\\1" ${MODULE}_LNAME "${${MODULE}_LNAME}")
  string(REGEX REPLACE ".*VERSION=([^ \t]*).*" "\\1" ${MODULE}_VER "${${MODULE}_VER}")
  string(REGEX REPLACE ".*ARCHIVE=([^ \t]*).*" "\\1" ${MODULE}_ARCHIVE "${${MODULE}_ARCHIVE}")
  string(REGEX REPLACE ".*BASE_URL=([^ \t]*).*" "\\1" ${MODULE}_BASE_URL "${${MODULE}_BASE_URL}")
  if(WIN32 OR WINDOWS_STORE)
    string(REGEX REPLACE ".*BYPRODUCT_WIN=([^ \t]*).*" "\\1" ${MODULE}_BYPRODUCT "${${MODULE}_BYPRODUCT}")
  else()
    string(REGEX REPLACE ".*BYPRODUCT=([^ \t]*).*" "\\1" ${MODULE}_BYPRODUCT "${${MODULE}_BYPRODUCT}")
  endif()

  string(REGEX REPLACE "\\$\\(LIBNAME\\)" "${${MODULE}_LNAME}" ${MODULE}_ARCHIVE "${${MODULE}_ARCHIVE}")
  string(REGEX REPLACE "\\$\\(VERSION\\)" "${${MODULE}_VER}" ${MODULE}_ARCHIVE "${${MODULE}_ARCHIVE}")

  set(${MODULE}_ARCHIVE ${${MODULE}_ARCHIVE} PARENT_SCOPE)

  set(${MODULE}_VER ${${MODULE}_VER} PARENT_SCOPE)

  if (${MODULE}_BASE_URL)
    set(${MODULE}_BASE_URL ${${MODULE}_BASE_URL} PARENT_SCOPE)
  else()
    set(${MODULE}_BASE_URL "http://mirrors.kodi.tv/build-deps/sources" PARENT_SCOPE)
  endif()
  set(${MODULE}_BYPRODUCT ${${MODULE}_BYPRODUCT} PARENT_SCOPE)

  # allow user to override the download URL hash with a local tarball hash
  # needed for offline build envs
  if (NOT DEFINED ${MODULE}_HASH)
    if (${MODULE}_HASH_SHA256)
      set(${MODULE}_HASH ${${MODULE}_HASH_SHA256} PARENT_SCOPE)
    elseif(${MODULE}_HASH_SHA512)
      set(${MODULE}_HASH ${${MODULE}_HASH_SHA512} PARENT_SCOPE)
    endif()
  endif()
endfunction()

# Parse and set Version from VERSION dependency file
# Used for retrieving version numbers for dependency libs to allow setting
# a required version for find_package call
# On return:
#   LIB_MODULENAME_VER will be set to parent scope (eg LIB_FMT_VER)
function(get_libversion_data module libtype)

  # Dependency path
  set(LIB_MODULE_PATH "${CMAKE_SOURCE_DIR}/tools/depends/${libtype}/${module}")
  string(TOUPPER ${module} MOD_UPPER)

  if(NOT EXISTS "${LIB_MODULE_PATH}/${MOD_UPPER}-VERSION")
    MESSAGE(FATAL_ERROR "${MOD_UPPER}-VERSION does not exist at ${LIB_MODULE_PATH}.")
  else()
    set(${MOD_UPPER}_FILE "${LIB_MODULE_PATH}/${MOD_UPPER}-VERSION")
  endif()

  file(STRINGS ${${MOD_UPPER}_FILE} ${MOD_UPPER}_VER REGEX "^[ \t]*VERSION=")

  string(REGEX REPLACE ".*VERSION=([^ \t]*).*" "\\1" ${MOD_UPPER}_VER "${${MOD_UPPER}_VER}")

  set(LIB_${MOD_UPPER}_VER ${${MOD_UPPER}_VER} PARENT_SCOPE)
endfunction()

# Function to loop through list of patch files (full path)
# Sets to a PATCH_COMMAND variable and set to parent scope (caller)
# Used to test windows line endings and set appropriate patch commands
function(generate_patchcommand _patchlist)
  # find the path to the patch executable
  find_package(Patch MODULE REQUIRED)

  # Loop through patches and add to PATCH_COMMAND
  # for windows, check CRLF/LF state

  set(_count 0)
  foreach(patch ${_patchlist})
    if(WIN32 OR WINDOWS_STORE)
      PATCH_LF_CHECK(${patch})
    endif()
    if(${_count} EQUAL "0")
      set(_patch_command ${PATCH_EXECUTABLE} -p1 -i ${patch})
    else()
      list(APPEND _patch_command COMMAND ${PATCH_EXECUTABLE} -p1 -i ${patch})
    endif()

    math(EXPR _count "${_count}+1")
  endforeach()
  set(PATCH_COMMAND ${_patch_command} PARENT_SCOPE)
  unset(_count)
  unset(_patch_command)
endfunction()

# Macro to factor out the repetitive URL setup
macro(SETUP_BUILD_VARS)
  string(TOUPPER ${MODULE_LC} MODULE)

  # Fall through to target build module dir if not explicitly set
  if(NOT DEFINED LIB_TYPE)
    set(LIB_TYPE "target")
  endif()

  # Location for build type, native or target
  if(LIB_TYPE STREQUAL "target")
    set(DEP_LOCATION "${DEPENDS_PATH}")
  else()
    set(DEP_LOCATION "${NATIVEPREFIX}")
  endif()

  # PROJECTSOURCE used in native toolchain to provide core project sourcedir
  # to externalproject_add targets that have a different CMAKE_SOURCE_DIR (eg jsonschema/texturepacker in-tree)
  if(NOT PROJECTSOURCE)
    set(PROJECTSOURCE ${CMAKE_SOURCE_DIR})
  endif()

  # populate variables of data from VERSION file for MODULE
  get_versionfile_data()

  # allow user to override the download URL with a local tarball
  # needed for offline build envs
  if(${MODULE}_URL)
    get_filename_component(${MODULE}_URL "${${MODULE}_URL}" ABSOLUTE)
  else()
    set(${MODULE}_URL ${${MODULE}_BASE_URL}/${${MODULE}_ARCHIVE})
  endif()
  if(VERBOSE)
    message(STATUS "MODULE: ${MODULE}")
    message(STATUS "LIB_TYPE: ${LIB_TYPE}")
    message(STATUS "DEP_LOCATION: ${DEP_LOCATION}")
    message(STATUS "PROJECTSOURCE: ${PROJECTSOURCE}")
    message(STATUS "${MODULE}_URL: ${${MODULE}_URL}")
  endif()
endmacro()

macro(CLEAR_BUILD_VARS)
  # unset all generic variables to insure clean state between macro calls
  # Potentially an issue with scope when a macro is used inside a dep that uses a macro
  unset(PROJECTSOURCE)
  unset(LIB_TYPE)
  unset(BUILD_NAME)
  unset(INSTALL_DIR)
  unset(CMAKE_ARGS)
  unset(PATCH_COMMAND)
  unset(CONFIGURE_COMMAND)
  unset(BUILD_COMMAND)
  unset(INSTALL_COMMAND)
  unset(BUILD_IN_SOURCE)
  unset(BUILD_BYPRODUCTS)

  # unset all module specific variables to insure clean state between macro calls
  # potentially an issue when a native and a target of the same module exists
  unset(${MODULE}_LIST_SEPARATOR)
  unset(${MODULE}_GENERATOR)
  unset(${MODULE}_GENERATOR_PLATFORM)
  unset(${MODULE}_INSTALL_PREFIX)
  unset(${MODULE}_TOOLCHAIN_FILE)
endmacro()

# Macro to create externalproject_add target
# 
# Common usage
#
# CMAKE_ARGS: cmake(required)
# PATCH_COMMAND: ALL(optional)
# CONFIGURE_COMMAND: autoconf(required), meson(required)
# BUILD_COMMAND: autoconf(required), meson(required), cmake(optional)
# INSTALL_COMMAND: autoconf(required), meson(required), cmake(optional)
# BUILD_IN_SOURCE: ALL(optional)
# BUILD_BYPRODUCTS: ALL(optional)
#
macro(BUILD_DEP_TARGET)
  include(ExternalProject)

  # Remove cmake warning when Xcode generator used with "New" build system
  if(CMAKE_GENERATOR STREQUAL Xcode)
    # Policy CMP0114 is not set to NEW.  In order to support the Xcode "new build
    # system", this project must be updated to set policy CMP0114 to NEW.
    if(CMAKE_XCODE_BUILD_SYSTEM STREQUAL 12)
      cmake_policy(SET CMP0114 NEW)
    else()
      cmake_policy(SET CMP0114 OLD)
    endif()
  endif()

  if(CMAKE_ARGS)
    set(CMAKE_ARGS CMAKE_ARGS ${CMAKE_ARGS}
                             -DCMAKE_INSTALL_LIBDIR=lib
                             -DPROJECTSOURCE=${PROJECTSOURCE}
                             "-DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH}")

    if(${MODULE}_INSTALL_PREFIX)
      list(APPEND CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${${MODULE}_INSTALL_PREFIX})
    else()
      list(APPEND CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${DEP_LOCATION})
    endif()

    if(DEFINED ${MODULE}_TOOLCHAIN_FILE)
      list(APPEND CMAKE_ARGS -DCMAKE_TOOLCHAIN_FILE=${${MODULE}_TOOLCHAIN_FILE})
    elseif(CMAKE_TOOLCHAIN_FILE)
      list(APPEND CMAKE_ARGS -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE})
    endif()

    # Set build type for dep build.
    # if MODULE has set a manual build type, use it, otherwise use project build type
    if(${MODULE}_BUILD_TYPE)
      list(APPEND CMAKE_ARGS "-DCMAKE_BUILD_TYPE=${${MODULE}_BUILD_TYPE}")
      # Build_type is forced, so unset the opposite <MODULE>_LIBRARY_<TYPE> to only give
      # select_library_configurations one library name to choose from
      if(${MODULE}_BUILD_TYPE STREQUAL "Release")
        unset(${MODULE}_LIBRARY_DEBUG)
      else()
        unset(${MODULE}_LIBRARY_RELEASE)
      endif()
    else()
      if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.20")
        list(APPEND CMAKE_ARGS "-DCMAKE_BUILD_TYPE=$<IF:$<CONFIG:Debug,RelWithDebInfo>,Debug,Release>")
      else()
        # single config generator (ie Make, Ninja)
        if(CMAKE_BUILD_TYPE)
          list(APPEND CMAKE_ARGS "-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}")
        else()
          # Multi-config generators (eg VS, Xcode, Ninja Multi-Config) will not have CMAKE_BUILD_TYPE
          # Use config genex to generate the types
          # Potential issue if Build type isnt supported by lib project
          # eg lib supports Debug/Release, however users selects RelWithDebInfo in project
          list(APPEND CMAKE_ARGS "-DCMAKE_BUILD_TYPE=$<CONFIG>")
        endif()
      endif()
    endif()

    # Xcode - Default sub projects to makefile builds. More consistent
    # Windows - Default to same generator version used in parent
    if(CMAKE_GENERATOR STREQUAL Xcode)
      if(NOT ${MODULE}_GENERATOR)
        set(${MODULE}_GENERATOR CMAKE_GENERATOR "Unix Makefiles")
      endif()
    elseif(MSVC)
      if(NOT ${MODULE}_GENERATOR)
        set(${MODULE}_GENERATOR CMAKE_GENERATOR "${CMAKE_GENERATOR}")
      endif()
      if(NOT ${MODULE}_GENERATOR_PLATFORM)
        set(${MODULE}_GENERATOR_PLATFORM CMAKE_GENERATOR_PLATFORM ${CMAKE_GENERATOR_PLATFORM})
      endif()
    endif()
  endif()

  if(PATCH_COMMAND)
    set(PATCH_COMMAND PATCH_COMMAND ${PATCH_COMMAND})
  endif()

  if(CONFIGURE_COMMAND)
    if(NOT CMAKE_ARGS AND DEP_BUILDENV)
      # DEP_BUILDENV only used for non cmake externalproject_add builds
      # iterate through CONFIGURE_COMMAND looking for multiple COMMAND, we need to
      # add DEP_BUILDENV for each distinct COMMAND
      set(tmp_config_command ${DEP_BUILDENV})
      foreach(item ${CONFIGURE_COMMAND})
        list(APPEND tmp_config_command ${item})
        if(item STREQUAL "COMMAND")
          list(APPEND tmp_config_command ${DEP_BUILDENV})
        endif()
      endforeach()
      set(CONFIGURE_COMMAND CONFIGURE_COMMAND ${tmp_config_command})
      unset(tmp_config_command)
    else()
      set(CONFIGURE_COMMAND CONFIGURE_COMMAND ${CONFIGURE_COMMAND})
    endif()
  endif()

  if(BUILD_COMMAND)
    set(BUILD_COMMAND BUILD_COMMAND ${BUILD_COMMAND})
  endif()

  if(INSTALL_COMMAND)
    set(INSTALL_COMMAND INSTALL_COMMAND ${INSTALL_COMMAND})
  endif()

  if(BUILD_IN_SOURCE)
    set(BUILD_IN_SOURCE BUILD_IN_SOURCE ${BUILD_IN_SOURCE})
  endif()

  # Set Library names.
  if(DEFINED ${MODULE}_DEBUG_POSTFIX)
    set(_POSTFIX ${${MODULE}_DEBUG_POSTFIX})
    string(REGEX REPLACE "\\.[^.]*$" "" _LIBNAME ${${MODULE}_BYPRODUCT})
    string(REGEX REPLACE "^.*\\." "" _LIBEXT ${${MODULE}_BYPRODUCT})
    set(${MODULE}_LIBRARY_DEBUG ${DEP_LOCATION}/lib/${_LIBNAME}${${MODULE}_DEBUG_POSTFIX}.${_LIBEXT})
  endif()
  # set <MODULE>_LIBRARY_RELEASE for use of select_library_configurations
  # any modules that dont use select_library_configurations, we set <MODULE>_LIBRARY
  # No harm in having either set for both potential paths
  set(${MODULE}_LIBRARY_RELEASE ${DEP_LOCATION}/lib/${${MODULE}_BYPRODUCT})
  set(${MODULE}_LIBRARY ${${MODULE}_LIBRARY_RELEASE})

  if(NOT ${MODULE}_INCLUDE_DIR)
    set(${MODULE}_INCLUDE_DIR ${DEP_LOCATION}/include)
  endif()

  if(BUILD_BYPRODUCTS)
    set(BUILD_BYPRODUCTS BUILD_BYPRODUCTS ${BUILD_BYPRODUCTS})
  else()
    if(DEFINED ${MODULE}_LIBRARY_DEBUG)
      if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.20")
        set(BUILD_BYPRODUCTS BUILD_BYPRODUCTS "$<IF:$<CONFIG:Debug,RelWithDebInfo>,${${MODULE}_LIBRARY_DEBUG},${${MODULE}_LIBRARY_RELEASE}>")
      else()
        if(DEFINED CMAKE_BUILD_TYPE)
          if(NOT CMAKE_BUILD_TYPE STREQUAL "Release" AND DEFINED ${MODULE}_LIBRARY_DEBUG)
            set(BUILD_BYPRODUCTS BUILD_BYPRODUCTS "${${MODULE}_LIBRARY_DEBUG}")
          else()
            set(BUILD_BYPRODUCTS BUILD_BYPRODUCTS "${${MODULE}_LIBRARY}")
          endif()
        else()
          message(FATAL_ERROR "MultiConfig Generator usage requires CMake >= 3.20.0 - Generator Expressions in BYPRODUCT option")
        endif()
      endif()
    else()
      set(BUILD_BYPRODUCTS BUILD_BYPRODUCTS "${${MODULE}_LIBRARY}")
    endif()
  endif()

  if(NOT BUILD_NAME)
    set(BUILD_NAME ${MODULE_LC})
  endif()

  if(NOT INSTALL_DIR)
    set(INSTALL_DIR ${DEP_LOCATION})
  endif()

  # Allow a target to supply in-tree source location. eg TexturePacker, JsonSchemaBuilder
  if(${MODULE}_SOURCE_DIR)
    set(BUILD_DOWNLOAD_STEPS SOURCE_DIR "${${MODULE}_SOURCE_DIR}")
  else()
    set(BUILD_DOWNLOAD_STEPS URL ${${MODULE}_URL}
                             URL_HASH ${${MODULE}_HASH}
                             DOWNLOAD_DIR ${TARBALL_DIR}
                             DOWNLOAD_NAME ${${MODULE}_ARCHIVE})
  endif()

  externalproject_add(${BUILD_NAME}
                      ${BUILD_DOWNLOAD_STEPS}
                      PREFIX ${CORE_BUILD_DIR}/${BUILD_NAME}
                      INSTALL_DIR ${INSTALL_DIR}
                      ${${MODULE}_LIST_SEPARATOR}
                      ${CMAKE_ARGS}
                      ${${MODULE}_GENERATOR}
                      ${${MODULE}_GENERATOR_PLATFORM}
                      ${PATCH_COMMAND}
                      ${CONFIGURE_COMMAND}
                      ${BUILD_COMMAND}
                      ${INSTALL_COMMAND}
                      ${BUILD_BYPRODUCTS}
                      ${BUILD_IN_SOURCE})

  set_target_properties(${BUILD_NAME} PROPERTIES FOLDER "External Projects")

  CLEAR_BUILD_VARS()
endmacro()

# Macro to test format of line endings of a patch
# Windows Specific
macro(PATCH_LF_CHECK patch)
  if(CMAKE_HOST_WIN32)
    # On Windows "patch.exe" can only handle CR-LF line-endings.
    # Our patches have LF-only line endings - except when they
    # have been checked out as part of a dependency hosted on Git
    # and core.autocrlf=true.
    file(READ ${ARGV0} patch_content_hex HEX)
    # Force handle LF-only line endings
    if(NOT patch_content_hex MATCHES "0d0a")
      if (NOT "--binary" IN_LIST PATCH_EXECUTABLE)
        list(APPEND PATCH_EXECUTABLE --binary)
      endif()
    else()
      if ("--binary" IN_LIST PATCH_EXECUTABLE)
        list(REMOVE_ITEM PATCH_EXECUTABLE --binary)
      endif()
    endif()
  endif()
  unset(patch_content_hex)
endmacro()