cmake_framework.rst 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. TF-A CMake buildsystem
  2. ======================
  3. :Author: Balint Dobszay
  4. :Organization: Arm Limited
  5. :Contact: Balint Dobszay <balint.dobszay@arm.com>
  6. :Status: Accepted
  7. .. contents:: Table of Contents
  8. Abstract
  9. --------
  10. This document presents a proposal for a new buildsystem for TF-A using CMake,
  11. and as part of this a reusable CMake framework for embedded projects.
  12. Introduction
  13. ------------
  14. The current Makefile based buildsystem of TF-A has become complicated and hard
  15. to maintain, there is a need for a new, more flexible solution. The proposal is
  16. to use CMake language for the new buildsystem. The main reasons of this decision
  17. are the following:
  18. * It is a well-established, mature tool, widely accepted by open-source
  19. projects.
  20. * TF-M is already using CMake, reducing fragmentation for tf.org projects can be
  21. beneficial.
  22. * CMake has various advantages over Make, e.g.:
  23. * Host and target system agnostic project.
  24. * CMake project is scalable, supports project modularization.
  25. * Supports software integration.
  26. * Out-of-the-box support for integration with several tools (e.g. project
  27. generation for various IDEs, integration with cppcheck, etc).
  28. Of course there are drawbacks too:
  29. * Language is problematic (e.g. variable scope).
  30. * Not embedded approach.
  31. To overcome these and other problems, we need to create workarounds for some
  32. tasks, wrap CMake functions, etc. Since this functionality can be useful in
  33. other embedded projects too, it is beneficial to collect the new code into a
  34. reusable framework and store this in a separate repository. The following
  35. diagram provides an overview of the framework structure:
  36. |Framework structure|
  37. Main features
  38. -------------
  39. Structured configuration description
  40. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  41. In the current Makefile system the build configuration description, validation,
  42. processing, and the target creation, source file description are mixed and
  43. spread across several files. One of the goals of the framework is to organize
  44. this.
  45. The framework provides a solution to describe the input build parameters, flags,
  46. macros, etc. in a structured way. It contains two utilities for this purpose:
  47. * Map: simple key-value pair implementation.
  48. * Group: collection of related maps.
  49. The related parameters shall be packed into a group (or "setting group"). The
  50. setting groups shall be defined and filled with content in config files.
  51. Currently the config files are created and edited manually, but later a
  52. configuration management tool (e.g. Kconfig) shall be used to generate these
  53. files. Therefore, the framework does not contain parameter validation and
  54. conflict checking, these shall be handled by the configuration tool.
  55. Target description
  56. ^^^^^^^^^^^^^^^^^^
  57. The framework provides an API called STGT ('simple target') to describe the
  58. targets, i.e. what is the build output, what source files are used, what
  59. libraries are linked, etc. The API wraps the CMake target functions, and also
  60. extends the built-in functionality, it can use the setting groups described in
  61. the previous section. A group can be applied onto a target, i.e. a collection of
  62. macros, flags, etc. can be applied onto the given output executable/library.
  63. This provides a more granular way than the current Makefile system where most of
  64. these are global and applied onto each target.
  65. Compiler abstraction
  66. ^^^^^^^^^^^^^^^^^^^^
  67. Apart from the built-in CMake usage of the compiler, there are some common tasks
  68. that CMake does not solve (e.g. preprocessing a file). For these tasks the
  69. framework uses wrapper functions instead of direct calls to the compiler. This
  70. way it is not tied to one specific compiler.
  71. External tools
  72. ^^^^^^^^^^^^^^
  73. In the TF-A buildsystem some external tools are used, e.g. fiptool for image
  74. generation or dtc for device tree compilation. These tools have to be found
  75. and/or built by the framework. For this, the CMake find_package functionality is
  76. used, any other necessary tools can be added later.
  77. Workflow
  78. --------
  79. The following diagram demonstrates the development workflow using the framework:
  80. |Framework workflow|
  81. The process can be split into two main phases:
  82. In the provisioning phase, first we have to obtain the necessary resources, i.e.
  83. clone the code repository and other dependencies. Next we have to do the
  84. configuration, preferably using a config tool like KConfig.
  85. In the development phase first we run CMake, which will generate the buildsystem
  86. using the selected generator backend (currently only the Makefile generator is
  87. supported). After this we run the selected build tool which in turn calls the
  88. compiler, linker, packaging tool, etc. Finally we can run and debug the output
  89. executables.
  90. Usually during development only the steps in this second phase have to be
  91. repeated, while the provisioning phase needs to be done only once (or rarely).
  92. Example
  93. -------
  94. This is a short example for the basic framework usage.
  95. First, we create a setting group called *mem_conf* and fill it with several
  96. parameters. It is worth noting the difference between *CONFIG* and *DEFINE*
  97. types: the former is only a CMake domain option, the latter is only a C language
  98. macro.
  99. Next, we create a target called *fw1* and add the *mem_conf* setting group to
  100. it. This means that all source and header files used by the target will have all
  101. the parameters declared in the setting group. Then we set the target type to
  102. executable, and add some source files. Since the target has the parameters from
  103. the settings group, we can use it for conditionally adding source files. E.g.
  104. *dram_controller.c* will only be added if MEM_TYPE equals dram.
  105. .. code-block:: cmake
  106. group_new(NAME mem_conf)
  107. group_add(NAME mem_conf TYPE DEFINE KEY MEM_SIZE VAL 1024)
  108. group_add(NAME mem_conf TYPE CONFIG DEFINE KEY MEM_TYPE VAL dram)
  109. group_add(NAME mem_conf TYPE CFLAG KEY -Os)
  110. stgt_create(NAME fw1)
  111. stgt_add_setting(NAME fw1 GROUPS mem_conf)
  112. stgt_set_target(NAME fw1 TYPE exe)
  113. stgt_add_src(NAME fw1 SRC
  114. ${CMAKE_SOURCE_DIR}/main.c
  115. )
  116. stgt_add_src_cond(NAME fw1 KEY MEM_TYPE VAL dram SRC
  117. ${CMAKE_SOURCE_DIR}/dram_controller.c
  118. )
  119. .. |Framework structure| image::
  120. ../resources/diagrams/cmake_framework_structure.png
  121. :width: 75 %
  122. .. |Framework workflow| image::
  123. ../resources/diagrams/cmake_framework_workflow.png
  124. --------------
  125. *Copyright (c) 2019-2024, Arm Limited and Contributors. All rights reserved.*