1#
2# Arm SCP/MCP Software
3# Copyright (c) 2021-2022, Arm Limited and Contributors. All rights reserved.
4#
5# SPDX-License-Identifier: BSD-3-Clause
6#
7
8# cmake-lint: disable=C0301
9
10cmake_minimum_required(VERSION 3.18.3)
11
12list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
13
14#
15# Individual firmware targets are configured based on the 'Firmware.cmake'
16# living in the firmware source directory that the user provides to us, which
17# determines the default set of configuration options we are given, including
18# the toolchain file.
19#
20
21if(SCP_FIRMWARE_SOURCE_DIR)
22    get_filename_component(SCP_FIRMWARE_SOURCE_DIR "${SCP_FIRMWARE_SOURCE_DIR}"
23                           ABSOLUTE BASE_DIR "${CMAKE_SOURCE_DIR}/product")
24
25    include("${SCP_FIRMWARE_SOURCE_DIR}/Firmware.cmake")
26
27    if((NOT SCP_FIRMWARE) OR (NOT SCP_FIRMWARE_TARGET))
28        # cmake-format: off
29        message(FATAL_ERROR
30            "Insufficient firmware metadata provided.\n"
31
32            "Please ensure your 'Firmware.cmake' has set both `SCP_FIRMWARE` "
33            "and `SCP_FIRMWARE_TARGET` to the name of your firmware and the "
34            "firmware's CMake target respectively.")
35        # cmake-format: on
36    endif()
37
38    if(NOT SCP_FIRMWARE_BINARY_DIR)
39        #
40        # Derive the binary directory from the location of the source directory
41        # if it's in-tree.
42        #
43
44        file(RELATIVE_PATH SCP_FIRMWARE_BINARY_DIR "${CMAKE_SOURCE_DIR}"
45             "${SCP_FIRMWARE_SOURCE_DIR}")
46    endif()
47
48    if(SCP_FIRMWARE_BINARY_DIR MATCHES "\\.\\.")
49        # cmake-format: off
50        message(FATAL_ERROR
51            "Invalid firmware binary directory.\n"
52
53            "Please ensure your firmware binary directory is a relative path. "
54            "This path is used as the location within the project binary "
55            "directory that will be used for firmware artifacts.")
56        # cmake-format: on
57    endif()
58endif()
59
60#
61# Handle automatic selection of the toolchain. This only occurs if the user has
62# not explicitly provided the path to a toolchain file and the firmware has
63# given us a default preference.
64#
65
66if(SCP_TOOLCHAIN_INIT AND (NOT CMAKE_TOOLCHAIN_FILE))
67    #
68    # Let the firmware decide what its default toolchain should be, but allow
69    # the user to override it.
70    #
71
72    if(NOT SCP_TOOLCHAIN)
73        set(SCP_TOOLCHAIN "${SCP_TOOLCHAIN_INIT}")
74    endif()
75
76    set(CMAKE_TOOLCHAIN_FILE
77        "${SCP_FIRMWARE_SOURCE_DIR}/Toolchain-${SCP_TOOLCHAIN}.cmake")
78endif()
79
80if(SCP_TOOLCHAIN STREQUAL "Clang" AND NOT SCP_LLVM_SYSROOT_CC)
81    message(
82        FATAL_ERROR
83            "When Clang is set as toolchain SCP_LLVM_SYSROOT_CC must be defined"
84    )
85endif()
86
87project(
88    SCP
89    VERSION 2.11.0
90    DESCRIPTION "Arm SCP/MCP Software"
91    HOMEPAGE_URL
92        "https://developer.arm.com/tools-and-software/open-source-software/firmware/scp-firmware"
93    LANGUAGES C ASM)
94
95#
96# Configure the default build type to be "Release". We choose to default to a
97# release build as non-developer consumers will generally want a release build,
98# whereas developers who need a debug build are generally familiar with how to
99# enable it in the build system.
100#
101
102if((NOT CMAKE_BUILD_TYPE) AND (NOT CMAKE_CONFIGURATION_TYPES))
103    set(CMAKE_BUILD_TYPE
104        "Release"
105        CACHE STRING "Build type." FORCE)
106
107    set_property(
108        CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel"
109                                                "RelWithDebInfo")
110endif()
111
112#
113# Set the default C standard to C11 plus any compiler extensions.
114#
115
116set(CMAKE_C_STANDARD 11)
117set(CMAKE_C_STANDARD_REQUIRED TRUE)
118set(CMAKE_C_EXTENSIONS TRUE)
119
120#
121# We use `__declspec` when available, but Clang needs to explicitly enable
122# support for it before we can use them.
123#
124
125if(CMAKE_C_COMPILER_ID STREQUAL "Clang")
126    string(APPEND CMAKE_C_FLAGS " -fms-extensions")
127endif()
128
129#
130# Enable inter-procedural optimization for all targets if we can.
131# `CheckIPOSupported` doesn't play particularly nicely with toolchains that
132# cannot build executables right off the bat, so we skip this check if we're
133# cross compiling.
134#
135
136include(CheckIPOSupported)
137include(CMakeDependentOption)
138
139check_ipo_supported(RESULT SCP_IPO_SUPPORTED)
140
141cmake_dependent_option(
142    SCP_ENABLE_IPO
143    "Enable the interprocedural optimization (IPO) if supported?"
144    "${SCP_ENABLE_IPO_INIT}" "DEFINED SCP_ENABLE_IPO_INIT" "${SCP_ENABLE_IPO}")
145
146if(CMAKE_CROSSCOMPILING
147   AND (CMAKE_C_COMPILER_ID STREQUAL "GNU")
148   AND SCP_ENABLE_IPO)
149    set(SCP_IPO_SUPPORTED TRUE)
150endif()
151
152cmake_dependent_option(
153    CMAKE_INTERPROCEDURAL_OPTIMIZATION "Enable interprocedural optimization?"
154    TRUE "SCP_IPO_SUPPORTED;SCP_ENABLE_IPO" FALSE)
155
156#
157# Export a `compile_commands.json` file for generators that support it.
158#
159
160set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE)
161
162#
163# Set up Cppcheck if it's available and enabled.
164#
165if(NOT DISABLE_CPPCHECK)
166    find_program(CMAKE_C_CPPCHECK NAMES "cppcheck")
167
168    if(NOT CMAKE_C_CPPCHECK)
169        unset(CMAKE_C_CPPCHECK CACHE)
170    endif()
171
172    if(CMAKE_C_CPPCHECK)
173        # Prepend cppcheck_wrapper.py before binary to perform pre and post
174        # cppcheck operations.
175        set(CPPCHECK_WRAPPER ${CMAKE_SOURCE_DIR}/tools/cppcheck_wrapper.py)
176        # cmake-format: off
177
178        list(PREPEND CMAKE_C_CPPCHECK  ${CPPCHECK_WRAPPER})
179        list(APPEND CMAKE_C_CPPCHECK "--quiet")
180        list(APPEND CMAKE_C_CPPCHECK "--suppressions-list=${CMAKE_CURRENT_SOURCE_DIR}/tools/cppcheck_suppress_list.txt")
181        list(APPEND CMAKE_C_CPPCHECK "--enable=all")
182        list(APPEND CMAKE_C_CPPCHECK "--error-exitcode=1")
183
184        if(SCP_ARCHITECTURE STREQUAL "arm-m")
185            if(CMAKE_SYSTEM_PROCESSOR MATCHES "cortex-m(33|55)")
186                list(APPEND CMAKE_C_CPPCHECK "--library=${CMAKE_CURRENT_SOURCE_DIR}/.armv8m.cppcheck.cfg")
187            elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "cortex-m(3|7)")
188                list(APPEND CMAKE_C_CPPCHECK "--library=${CMAKE_CURRENT_SOURCE_DIR}/.armv7m.cppcheck.cfg")
189            endif()
190        endif()
191
192        if(CMAKE_C_COMPILER_ID STREQUAL "ARMClang")
193            list(APPEND CMAKE_C_CPPCHECK "--library=${CMAKE_CURRENT_SOURCE_DIR}/.armclang.cppcheck.cfg")
194        else()
195            list(APPEND CMAKE_C_CPPCHECK "--library=${CMAKE_CURRENT_SOURCE_DIR}/.gnu.cppcheck.cfg")
196        endif()
197
198        if(COMMAND_OUTPUT_VERBOSE)
199             list(APPEND CMAKE_C_CPPCHECK "verbose")
200        endif()
201
202        # cmake-format: on
203    endif()
204endif()
205
206#
207# Set up Clang-Tidy if it's available and enabled.
208#
209if(ENABLE_CLANG_TIDY)
210
211    find_program(
212        CMAKE_C_CLANG_TIDY
213        NAMES "clang-tidy"
214              "clang-tidy-3.6"
215              "clang-tidy-3.7"
216              "clang-tidy-3.8"
217              "clang-tidy-3.9"
218              "clang-tidy-4.0"
219              "clang-tidy-5.0"
220              "clang-tidy-6.0"
221              "clang-tidy-7"
222              "clang-tidy-8"
223              "clang-tidy-9"
224              "clang-tidy-10"
225              "clang-tidy-11")
226
227    if(NOT CMAKE_C_CLANG_TIDY)
228        unset(CMAKE_C_CLANG_TIDY CACHE)
229    endif()
230
231    if(CMAKE_C_CLANG_TIDY)
232        # cmake-format: off
233
234        if(CMAKE_C_COMPILER_TARGET)
235            list(APPEND CMAKE_C_CLANG_TIDY "--extra-arg=--target=${CMAKE_C_COMPILER_TARGET}")
236        endif()
237
238        foreach(dir IN LISTS CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES)
239            list(APPEND CMAKE_C_CLANG_TIDY "--extra-arg=-isystem")
240            list(APPEND CMAKE_C_CLANG_TIDY "--extra-arg=${dir}")
241            list(APPEND CMAKE_C_CLANG_TIDY "--extra-arg=-fms-extensions")
242        endforeach()
243
244        if(CMAKE_C_COMPILER_ID STREQUAL "ARMClang")
245            list(APPEND CMAKE_C_CLANG_TIDY "--extra-arg=-D__ARM_PROMISE=__builtin_assume")
246            list(APPEND CMAKE_C_CLANG_TIDY "--extra-arg=-D__ARMCC_VERSION=600000")
247            list(APPEND CMAKE_C_CLANG_TIDY "--extra-arg=-D__ESCAPE__(x)=(x)")
248        endif()
249
250        list(APPEND CMAKE_C_CLANG_TIDY "--quiet")
251
252        # cmake-format: on
253    endif()
254
255endif()
256
257#
258# Set up Include What You Use ("IWYU") if it's available and enabled.
259#
260if(ENABLE_IWYU)
261
262    find_program(CMAKE_C_INCLUDE_WHAT_YOU_USE NAMES iwyu include-what-you-use)
263
264    if(NOT CMAKE_C_INCLUDE_WHAT_YOU_USE)
265        unset(CMAKE_C_INCLUDE_WHAT_YOU_USE CACHE)
266    endif()
267
268    if(CMAKE_C_INCLUDE_WHAT_YOU_USE)
269        # cmake-format: off
270
271        if(CMAKE_C_COMPILER_TARGET)
272            list(APPEND CMAKE_C_INCLUDE_WHAT_YOU_USE "--target=${CMAKE_C_COMPILER_TARGET}")
273        endif()
274
275        foreach(dir IN LISTS CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES)
276            list(APPEND CMAKE_C_INCLUDE_WHAT_YOU_USE "-isystem")
277            list(APPEND CMAKE_C_INCLUDE_WHAT_YOU_USE "${dir}")
278        endforeach()
279
280        if(CMAKE_C_COMPILER_ID STREQUAL "ARMClang")
281            list(APPEND CMAKE_C_INCLUDE_WHAT_YOU_USE "-D__ARM_PROMISE=__builtin_assume")
282            list(APPEND CMAKE_C_INCLUDE_WHAT_YOU_USE "-D__ARMCC_VERSION=600000")
283            list(APPEND CMAKE_C_INCLUDE_WHAT_YOU_USE "-D__ESCAPE__(x)=(x)")
284        endif()
285
286        # cmake-format: on
287    endif()
288
289endif()
290
291#
292# Try to locate Arm Compiler's 'fromelf' tool.
293#
294
295if(CMAKE_C_COMPILER_ID MATCHES "ARMClang")
296    get_filename_component(base ${CMAKE_C_COMPILER} DIRECTORY)
297
298    find_program(SCP_FROMELF fromelf ${base})
299
300    if(SCP_FROMELF)
301        mark_as_advanced(SCP_FROMELF)
302    endif()
303endif()
304
305#
306# Try to identify any standard libraries that require special treatment.
307#
308
309include(CheckSymbolExists)
310
311check_symbol_exists("__NEWLIB__" "sys/features.h" SCP_HAVE_NEWLIB)
312
313#
314# If the user asks for it, we can save them some time by creating a flat binary
315# from the firmware's compiled ELF image.
316#
317
318include(CMakeDependentOption)
319
320cmake_dependent_option(
321    SCP_GENERATE_FLAT_BINARY "Generate a flat binary (.bin) image?"
322    "${SCP_GENERATE_FLAT_BINARY_INIT}" "DEFINED SCP_GENERATE_FLAT_BINARY_INIT"
323    "${SCP_GENERATE_FLAT_BINARY}")
324
325#
326# If the firmware developer has given us initial values for these configuration
327# options, we can expose them to the user.
328#
329
330include(CMakeDependentOption)
331
332# Common build options
333
334if(SCP_ARCHITECTURE STREQUAL "optee")
335    set(SCP_ENABLE_SUB_SYSTEM_MODE_INIT TRUE)
336endif()
337
338set(SCP_ENABLE_OVERRIDE_FIRMWARE_NAME
339    ${SCP_ENABLE_OVERRIDE_FIRMWARE_NAME_INIT}
340    CACHE STRING "Override firmware binary name")
341
342cmake_dependent_option(
343    SCP_ENABLE_SUB_SYSTEM_MODE "Enable the execution as a sub-system?"
344    "${SCP_ENABLE_SUB_SYSTEM_MODE_INIT}"
345    "DEFINED SCP_ENABLE_SUB_SYSTEM_MODE_INIT"
346    "${SCP_ENABLE_SUB_SYSTEM_MODE}")
347
348cmake_dependent_option(
349    SCP_ENABLE_NOTIFICATIONS "Enable the notification subsystem?"
350    "${SCP_ENABLE_NOTIFICATIONS_INIT}" "DEFINED SCP_ENABLE_NOTIFICATIONS_INIT"
351    "${SCP_ENABLE_NOTIFICATIONS}")
352
353cmake_dependent_option(
354    SCP_ENABLE_RESOURCE_PERMISSIONS
355    "Enable the resource permission support?"
356    "${SCP_ENABLE_RESOURCE_PERMISSIONS_INIT}"
357    "DEFINED SCP_ENABLE_RESOURCE_PERMISSIONS_INIT"
358    "${SCP_ENABLE_RESOURCE_PERMISSIONS}")
359
360cmake_dependent_option(
361    SCP_ENABLE_SCMI_NOTIFICATIONS
362    "Enable the SCMI notifications?"
363    "${SCP_ENABLE_SCMI_NOTIFICATIONS_INIT}"
364    "DEFINED SCP_ENABLE_SCMI_NOTIFICATIONS_INIT"
365    "${SCP_ENABLE_SCMI_NOTIFICATIONS}")
366
367cmake_dependent_option(
368    SCP_ENABLE_SCMI_SENSOR_EVENTS
369    "Enable the SCMI sensor events?"
370    "${SCP_ENABLE_SCMI_SENSOR_EVENTS_INIT}"
371    "DEFINED SCP_ENABLE_SCMI_SENSOR_EVENTS_INIT"
372    "${SCP_ENABLE_SCMI_SENSOR_EVENTS}")
373
374cmake_dependent_option(
375    SCP_ENABLE_FAST_CHANNELS
376    "Enable the transport Fast Channels?"
377    "${SCP_ENABLE_FAST_CHANNELS_INIT}"
378    "DEFINED SCP_ENABLE_FAST_CHANNELS_INIT"
379    "${SCP_ENABLE_FAST_CHANNELS}")
380
381# Include firmware specific build options
382include("${SCP_FIRMWARE_SOURCE_DIR}/Buildoptions.cmake" OPTIONAL)
383
384#
385# Wrap `add_executable` in a way that allows us to do some extra processing on
386# the firmware target.(e.g. flat binary generation) This is necessary almost
387# exclusively because  `add_custom_command` cannot be used with a target created
388# in a different directory.
389#
390
391# cmake-lint: disable=C0103,C0111
392
393macro(add_executable target)
394    _add_executable(${target} ${ARGN})
395
396    if("${target}" STREQUAL "${SCP_FIRMWARE_TARGET}")
397        if(SCP_GENERATE_FLAT_BINARY)
398            #
399            # Invoke 'objcopy' or 'fromelf', which we use to generate a flat
400            # binary for the generated firmware image. We make this optional
401            # because some binaries consist of disjoint sections far apart from
402            # one another, which can generate huge binaries.
403            #
404
405            if(SCP_FROMELF)
406                # cmake-format: off
407                add_custom_command(
408                    TARGET ${target} POST_BUILD
409                    COMMAND ${SCP_FROMELF}
410                    ARGS
411                        --bin "$<TARGET_FILE:${target}>"
412                        --output "$<TARGET_FILE_DIR:${target}>/$<TARGET_PROPERTY:${target},OUTPUT_NAME>.bin"
413                    COMMENT "Generating flat binary ${target}.bin")
414                # cmake-format: on
415            elseif(CMAKE_OBJCOPY)
416                # cmake-format: off
417                add_custom_command(
418                    TARGET ${target} POST_BUILD
419                    COMMAND ${CMAKE_OBJCOPY}
420                    ARGS -O binary
421                        "$<TARGET_FILE:${target}>"
422                        "$<TARGET_FILE_DIR:${target}>/$<TARGET_FILE_BASE_NAME:${target}>.bin"
423                    COMMENT "Generating flat binary ${target}.bin")
424                # cmake-format: on
425            endif()
426        endif()
427    endif()
428endmacro()
429
430if(SCP_FIRMWARE)
431    #
432    # Pull in the CMSIS package.
433    #
434
435    add_subdirectory("contrib/cmsis" EXCLUDE_FROM_ALL)
436
437    #
438    # Load in the firmware. We do this now so that the firmware list files can
439    # make adjustments to any modules it wishes to load in, configure the
440    # architecture library, etc.
441    #
442
443    add_subdirectory("${SCP_FIRMWARE_SOURCE_DIR}" "${SCP_FIRMWARE_BINARY_DIR}"
444                     EXCLUDE_FROM_ALL)
445
446    #
447    # Generate a map file for the firmware.
448    #
449
450    if(CMAKE_C_COMPILER_ID STREQUAL "ARMClang")
451        target_link_options(
452            ${SCP_FIRMWARE_TARGET}
453            PRIVATE "LINKER:--map"
454                    "LINKER:--list=$<TARGET_FILE_DIR:${SCP_FIRMWARE_TARGET}>/$<TARGET_FILE_BASE_NAME:${SCP_FIRMWARE_TARGET}>.map")
455    else()
456        target_link_options(
457            ${SCP_FIRMWARE_TARGET}
458            PRIVATE "LINKER:--cref"
459                    "LINKER:-Map=$<TARGET_FILE_DIR:${SCP_FIRMWARE_TARGET}>/$<TARGET_FILE_BASE_NAME:${SCP_FIRMWARE_TARGET}>.map")
460    endif()
461
462    #
463    # Make the firmware target a part of the 'all' pseudo-target and give it a
464    # fixed output binary name so that different toolchains don't generate
465    # different names (which is desirable especially for higher-level build
466    # systems).
467    #
468
469    if("${SCP_ENABLE_OVERRIDE_FIRMWARE_NAME}" STREQUAL "")
470        set(SCP_FIRMWARE_NAME ${SCP_FIRMWARE_TARGET})
471    else()
472        set(SCP_FIRMWARE_NAME ${SCP_ENABLE_OVERRIDE_FIRMWARE_NAME})
473    endif()
474
475    set_target_properties(
476        ${SCP_FIRMWARE_TARGET}
477        PROPERTIES EXCLUDE_FROM_ALL FALSE
478                   OUTPUT_NAME "${SCP_FIRMWARE_NAME}"
479                   RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
480
481    set_target_properties(
482        ${SCP_FIRMWARE_TARGET}
483        PROPERTIES EXCLUDE_FROM_ALL FALSE
484        SUFFIX ".elf")
485    #
486    # Load in the CLI debugger if it was requested.
487    #
488
489    cmake_dependent_option(
490        SCP_ENABLE_DEBUGGER "Enable the debugger-cli subsystem?"
491        "${SCP_ENABLE_DEBUGGER_INIT}" "DEFINED SCP_ENABLE_DEBUGGER_INIT"
492        "${SCP_ENABLE_DEBUGGER}")
493
494    if(SCP_ENABLE_DEBUGGER)
495        add_subdirectory("debugger" EXCLUDE_FROM_ALL)
496    endif()
497
498    #
499    # Load in the architecture support library.
500    #
501
502    add_subdirectory("arch" EXCLUDE_FROM_ALL)
503
504    #
505    # Load in the modules specified, plus any that need to be included to
506    # support other modules.
507    #
508
509    add_subdirectory("module" EXCLUDE_FROM_ALL)
510
511    #
512    # Load in the framework. This should be the last of the targets we need.
513    #
514
515    add_subdirectory("framework" EXCLUDE_FROM_ALL)
516
517    #
518    # Collect up all the source files used to build the various targets created
519    # above.
520    #
521
522    # cmake-format: off
523
524    string(APPEND scp_sources "$<TARGET_PROPERTY:framework,INTERFACE_SOURCES>$<SEMICOLON>")
525    string(APPEND scp_sources "$<TARGET_PROPERTY:framework,SOURCES>$<SEMICOLON>")
526    string(APPEND scp_sources "$<TARGET_PROPERTY:${SCP_ARCHITECTURE_TARGET},INTERFACE_SOURCES>$<SEMICOLON>")
527    string(APPEND scp_sources "$<TARGET_PROPERTY:${SCP_ARCHITECTURE_TARGET},SOURCES>$<SEMICOLON>")
528    string(APPEND scp_sources "$<TARGET_PROPERTY:${SCP_FIRMWARE_TARGET},INTERFACE_SOURCES>$<SEMICOLON>")
529    string(APPEND scp_sources "$<TARGET_PROPERTY:${SCP_FIRMWARE_TARGET},SOURCES>$<SEMICOLON>")
530
531    string(APPEND scp_includes "$<TARGET_PROPERTY:framework,INTERFACE_INCLUDE_DIRECTORIES>$<SEMICOLON>")
532    string(APPEND scp_includes "$<TARGET_PROPERTY:framework,INCLUDE_DIRECTORIES>$<SEMICOLON>")
533    string(APPEND scp_includes "$<TARGET_PROPERTY:${SCP_ARCHITECTURE_TARGET},INTERFACE_INCLUDE_DIRECTORIES>$<SEMICOLON>")
534    string(APPEND scp_includes "$<TARGET_PROPERTY:${SCP_ARCHITECTURE_TARGET},INCLUDE_DIRECTORIES>$<SEMICOLON>")
535    string(APPEND scp_includes "$<TARGET_PROPERTY:${SCP_FIRMWARE_TARGET},INTERFACE_INCLUDE_DIRECTORIES>$<SEMICOLON>")
536    string(APPEND scp_includes "$<TARGET_PROPERTY:${SCP_FIRMWARE_TARGET},INCLUDE_DIRECTORIES>$<SEMICOLON>")
537
538    string(APPEND scp_defines "$<TARGET_PROPERTY:framework,INTERFACE_COMPILE_DEFINITIONS>$<SEMICOLON>")
539    string(APPEND scp_defines "$<TARGET_PROPERTY:framework,COMPILE_DEFINITIONS>$<SEMICOLON>")
540    string(APPEND scp_defines "$<TARGET_PROPERTY:${SCP_ARCHITECTURE_TARGET},INTERFACE_COMPILE_DEFINITIONS>$<SEMICOLON>")
541    string(APPEND scp_defines "$<TARGET_PROPERTY:${SCP_ARCHITECTURE_TARGET},COMPILE_DEFINITIONS>$<SEMICOLON>" )
542    string(APPEND scp_defines "$<TARGET_PROPERTY:${SCP_FIRMWARE_TARGET},INTERFACE_COMPILE_DEFINITIONS>$<SEMICOLON>")
543    string(APPEND scp_defines "$<TARGET_PROPERTY:${SCP_FIRMWARE_TARGET},COMPILE_DEFINITIONS>$<SEMICOLON>")
544
545    list(APPEND scp_libraries "$<TARGET_FILE:framework>")
546    list(APPEND scp_libraries "$<TARGET_FILE:${SCP_ARCHITECTURE_TARGET}>")
547    list(APPEND scp_libraries "$<TARGET_FILE:${SCP_FIRMWARE_TARGET}>")
548
549    foreach(module IN LISTS SCP_MODULE_TARGETS)
550        string(APPEND scp_sources "$<TARGET_PROPERTY:${module},INTERFACE_SOURCES>$<SEMICOLON>")
551        string(APPEND scp_sources "$<TARGET_PROPERTY:${module},SOURCES>$<SEMICOLON>")
552
553        string(APPEND scp_includes "$<TARGET_PROPERTY:${module},INTERFACE_INCLUDE_DIRECTORIES>$<SEMICOLON>")
554        string(APPEND scp_includes "$<TARGET_PROPERTY:${module},INCLUDE_DIRECTORIES>$<SEMICOLON>")
555
556        string(APPEND scp_defines "$<TARGET_PROPERTY:${module},INTERFACE_COMPILE_DEFINITIONS>$<SEMICOLON>")
557        string(APPEND scp_defines "$<TARGET_PROPERTY:${module},COMPILE_DEFINITIONS>$<SEMICOLON>")
558
559        list(APPEND scp_libraries "$<TARGET_FILE:${module}>")
560    endforeach()
561
562    set(scp_sources $<REMOVE_DUPLICATES:${scp_sources}>)
563    set(scp_includes $<REMOVE_DUPLICATES:${scp_includes}>)
564    set(scp_defines $<REMOVE_DUPLICATES:${scp_defines}>)
565
566    # cmake-format: on
567
568    #
569    # Load in the documentation generation.
570    #
571    add_subdirectory("doc" EXCLUDE_FROM_ALL)
572endif()
573
574
575set(SCP_FIRMWARE_LIB "${SCP_FIRMWARE_TARGET}-all")
576
577# merged libraries is only built when `make ${SCP_FIRMWARE_LIB}` is issued
578add_custom_target(${SCP_FIRMWARE_LIB}
579    # COMMAND/COMMENT must be upper case
580        COMMAND ${CMAKE_AR} rcT $<TARGET_FILE_DIR:${SCP_FIRMWARE_TARGET}>/lib${SCP_FIRMWARE_LIB}.a ${scp_libraries}
581        COMMENT " Merging all libraries in a single lib${SCP_FIRMWARE_LIB}.a"
582)
583
584add_dependencies(${SCP_FIRMWARE_LIB} ${SCP_FIRMWARE_TARGET})
585
586#
587# Set up global inclusions and exclusions for source file quality assurance
588# tools. This is intended to filter in external directories (e.g. out-of-tree
589# modules) and filter out third-party directories.
590#
591
592list(APPEND glob_includes "${SCP_SOURCE_DIR}")
593
594list(APPEND glob_excludes "^${SCP_SOURCE_DIR}/contrib/cmsis/git")
595list(APPEND glob_excludes "^${SCP_SOURCE_DIR}/contrib/run-clang-format/git")
596
597list(APPEND cmake_globs "CMakeLists.txt")
598list(APPEND cmake_globs "*.cmake")
599
600list(APPEND markdown_globs "*.md")
601
602list(APPEND yaml_globs "*.yml")
603list(APPEND yaml_globs "*.yaml")
604
605list(APPEND c_globs "*.[ch]")
606list(APPEND c_globs "*.[ch]pp")
607list(APPEND c_globs "*.[ch]xx")
608
609#
610# Glob sources and place them in their respective variables. Globs for source
611# types can be with specified with a `${type}_globs` variable, which should be a
612# list of globs to be fed into `file(GLOB_RECURSE ...)`.
613#
614
615# cmake-lint: disable=C0103
616
617foreach(type cmake markdown yaml c)
618    unset(sources)
619
620    foreach(include IN LISTS glob_includes)
621        foreach(glob IN LISTS ${type}_globs)
622            file(GLOB_RECURSE _sources "${include}/${glob}")
623            list(APPEND sources ${_sources})
624        endforeach()
625    endforeach()
626
627    foreach(exclude IN LISTS glob_excludes)
628        list(FILTER sources EXCLUDE REGEX "${exclude}")
629    endforeach()
630
631    set(${type}_sources ${sources})
632endforeach()
633
634#
635# Configure cmake-format.
636#
637
638find_package(CMakeFormat OPTIONAL_COMPONENTS Format Lint)
639
640if(TARGET CMakeFormat::Format)
641    add_custom_target(
642        format-cmake
643        COMMAND CMakeFormat::Format -i "${cmake_sources}"
644        WORKING_DIRECTORY "${SCP_SOURCE_DIR}"
645        COMMENT "Formatting CMake sources..."
646        COMMAND_EXPAND_LISTS)
647
648    list(APPEND format_targets "format-cmake")
649
650    add_custom_target(
651        check-cmake
652        COMMAND CMakeFormat::Format --check "${cmake_sources}"
653        WORKING_DIRECTORY "${SCP_SOURCE_DIR}"
654        COMMENT "Checking CMake sources..."
655        COMMAND_EXPAND_LISTS)
656
657    list(APPEND check_targets "check-cmake")
658endif()
659
660if(TARGET CMakeFormat::Lint)
661    add_custom_target(
662        lint-cmake
663        COMMAND CMakeFormat::Lint --suppress-decorations "${cmake_sources}"
664        WORKING_DIRECTORY "${SCP_SOURCE_DIR}"
665        COMMENT "Linting CMake sources..."
666        COMMAND_EXPAND_LISTS)
667
668    list(APPEND lint_targets "lint-cmake")
669endif()
670
671#
672# Configure markdownlint.
673#
674
675find_package(Markdownlint)
676
677if(Markdownlint_FOUND)
678    add_custom_target(
679        lint-markdown
680        COMMAND Markdownlint "${markdown_sources}"
681        WORKING_DIRECTORY "${SCP_SOURCE_DIR}"
682        COMMENT "Running Markdown sources..."
683        COMMAND_EXPAND_LISTS)
684
685    list(APPEND lint_targets "lint-markdown")
686endif()
687
688#
689# Configure yaml-format.
690#
691
692find_package(Python3 COMPONENTS Interpreter)
693
694if(Python3_Interpreter_FOUND)
695    add_custom_target(
696        format-yaml
697        COMMAND Python3::Interpreter "${SCP_SOURCE_DIR}/tools/yaml-format.py"
698                format -i "${yaml_sources}"
699        WORKING_DIRECTORY "${SCP_SOURCE_DIR}"
700        COMMENT "Formatting YAML sources..."
701        COMMAND_EXPAND_LISTS)
702
703    list(APPEND format_targets "format-yaml")
704
705    add_custom_target(
706        check-yaml
707        COMMAND Python3::Interpreter "${SCP_SOURCE_DIR}/tools/yaml-format.py"
708                diff --check "${yaml_sources}"
709        WORKING_DIRECTORY "${SCP_SOURCE_DIR}"
710        COMMENT "Checking YAML sources..."
711        COMMAND_EXPAND_LISTS)
712
713    list(APPEND check_targets "check-yaml")
714endif()
715
716#
717# Configure yamllint.
718#
719
720find_package(Yamllint)
721
722if(Yamllint_FOUND)
723    add_custom_target(
724        lint-yaml
725        COMMAND Yamllint -s "${yaml_sources}"
726        WORKING_DIRECTORY "${SCP_SOURCE_DIR}"
727        COMMENT "Linting YAML sources..."
728        COMMAND_EXPAND_LISTS)
729
730    list(APPEND lint_targets "lint-yaml")
731endif()
732
733#
734# Configure Clang-Format.
735#
736
737find_package(Clang OPTIONAL_COMPONENTS Format FormatGit)
738
739if(Clang_FormatGit_FOUND)
740    add_custom_target(
741        format-diff-c
742        COMMAND Clang::FormatGit HEAD
743        WORKING_DIRECTORY "${SCP_SOURCE_DIR}"
744        COMMENT "Formatting modified C/C++ sources..."
745        COMMAND_EXPAND_LISTS)
746
747    list(APPEND format_diff_targets "format-diff-c")
748endif()
749
750if(Clang_Format_FOUND)
751    add_custom_target(
752        format-c
753        COMMAND Clang::Format -i "${c_sources}"
754        WORKING_DIRECTORY "${SCP_SOURCE_DIR}"
755        COMMENT "Formatting C/C++ sources..."
756        COMMAND_EXPAND_LISTS)
757
758    list(APPEND format_targets "format-c")
759endif()
760
761#
762# Configure run-clang-format.
763#
764
765find_package(Python3 COMPONENTS Interpreter)
766
767if(Python3_Interpreter_FOUND)
768    add_custom_target(
769        check-c
770        COMMAND
771            Python3::Interpreter
772            "${SCP_SOURCE_DIR}/contrib/run-clang-format/git/run-clang-format.py"
773            --clang-format-executable "${Clang_Format_EXECUTABLE}"
774            "${c_sources}"
775        WORKING_DIRECTORY "${SCP_SOURCE_DIR}"
776        COMMENT "Checking C sources..."
777        COMMAND_EXPAND_LISTS)
778
779    list(APPEND check_targets "check-c")
780endif()
781
782#
783# Create the final check targets. These targets consist of miscellaneous quality
784# assurance tasks like linting and formatting, and act as dummy targets that
785# invoke the various tools we set up above.
786#
787
788add_custom_target(
789    format-diff
790    DEPENDS ${format_diff_targets}
791    COMMENT "Formatting modified sources...")
792
793add_custom_target(
794    format
795    DEPENDS ${format_targets}
796    COMMENT "Formatting all sources...")
797
798add_custom_target(
799    lint
800    DEPENDS ${lint_targets}
801    COMMENT "Linting all sources...")
802
803add_custom_target(
804    check
805    DEPENDS ${check_targets} lint
806    COMMENT "Checking lint...")
807