Finding Dependencies.rst 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529
  1. Step 10: Finding Dependencies
  2. =============================
  3. In C/C++ software development, managing build dependencies is consistently
  4. one of the highest ranked challenges facing modern developers. CMake provides
  5. an extensive toolset for discovering and validating dependencies of different
  6. kinds.
  7. However, for correctly packaged projects there is no need to use these advanced
  8. tools. Many popular library and utility projects today produce correct install
  9. trees, like the one we set up in ``Step 9``, which are easy is to integrate
  10. into CMake.
  11. In this best-case scenario, we only need the :command:`find_package` to
  12. import dependencies into our project.
  13. Background
  14. ^^^^^^^^^^
  15. There are five principle commands used for discovering dependencies with
  16. CMake, the first four are:
  17. :command:`find_file`
  18. Finds and reports the full path to a named file, this tends to be the
  19. most flexible of the ``find`` commands.
  20. :command:`find_library`
  21. Finds and reports the full path to a static archive or shared object
  22. suitable for use with :command:`target_link_libraries`.
  23. :command:`find_path`
  24. Finds and reports the full path to a directory *containing* a file. This
  25. is most commonly used for headers in combination with
  26. :command:`target_include_directories`.
  27. :command:`find_program`
  28. Finds and reports and invocable name or path for a program. Often used in
  29. combination with :command:`execute_process` or :command:`add_custom_command`.
  30. These commands should be considered "backup", used when the primary find command
  31. is unsuitable. The primary find command is :command:`find_package`. It uses
  32. comprehensive built-in heuristics and upstream-provided packaging files to
  33. provide the best interface to the requested dependency.
  34. Exercise 1 - Using ``find_package()``
  35. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  36. The search paths and behaviors used by :command:`find_package` are fully
  37. described in its documentation, but much too verbose to replicate here. Suffice
  38. to say it searches well known, lesser known, obscure, and user-provided
  39. locations attempting to find a package which meets the requirements given to it.
  40. .. code-block:: cmake
  41. find_package(ForeignLibrary)
  42. The best way to use :command:`find_package` is to ensure all dependencies have
  43. been installed to a single install tree prior to the build, and then make the
  44. location of that install tree known to :command:`find_package` via the
  45. :variable:`CMAKE_PREFIX_PATH` variable.
  46. .. note::
  47. Building and installing dependencies can itself be an immense amount of labor.
  48. While this tutorial will do so for illustration purposes, it is **extremely**
  49. recommended that a package manager be used for project-local dependency
  50. management.
  51. :command:`find_package` accepts several parameters besides the package to be
  52. found. The most notable are:
  53. * A positional ``<version>`` argument, for describing a version to be checked
  54. against the package's config version file. This should be used sparingly,
  55. it is better to control the version of the dependency being installed via
  56. a package manager than possibly break the build on otherwise innocuous
  57. version updates.
  58. If the package is known to rely on an older version of a dependency, it
  59. may be appropriate to use a version requirement.
  60. * ``REQUIRED`` for non-optional dependencies which should abort the build
  61. if not found.
  62. * ``QUIET`` for optional dependencies which should not report anything to
  63. users when not found.
  64. :command:`find_package` reports its results via ``<PackageName>_FOUND``
  65. variables, which will be set to a true or false value for found and not found
  66. packages respectively.
  67. Goal
  68. ----
  69. Integrate an externally installed test framework into the Tutorial project.
  70. Helpful Resources
  71. -----------------
  72. * :command:`find_package`
  73. * :command:`target_link_libraries`
  74. Files to Edit
  75. -------------
  76. * ``TutorialProject/CMakePresets.json``
  77. * ``TutorialProject/Tests/CMakeLists.txt``
  78. * ``TutorialProject/Tests/TestMathFunctions.cxx``
  79. Getting Started
  80. ---------------
  81. The ``Step10`` folder is organized differently than previous steps. The tutorial
  82. project we need to edit is under ``Step10/TutorialProject``. Another project
  83. is now present, ``SimpleTest``, as well as a partially populated install tree
  84. which we will use in later exercises. You do not need to edit anything in these
  85. other directories for this exercise, all ``TODOs`` and solution steps are for
  86. ``TutorialProject``.
  87. The ``SimpleTest`` package provides two useful constructs, the
  88. ``SimpleTest::SimpleTest`` target to be linked into a test binary, and the
  89. ``simpletest_discover_tests`` function for automatically adding tests to
  90. CTest.
  91. Similar to other test frameworks, ``simpletest_discover_tests`` only needs
  92. to be passed the name of the executable target containing the tests.
  93. .. code-block:: cmake
  94. simpletest_discover_tests(MyTestExe)
  95. The ``TestMathFunctions.cxx`` file has been updated to use the ``SimpleTest``
  96. framework in the vein of GoogleTest or Catch2. Perform ``TODO 1`` through
  97. ``TODO 5`` in order to use the new test framework.
  98. .. note::
  99. It may go without saying, but ``SimpleTest`` is a very poor test framework
  100. which only facially resembles a functional testing library. While much of
  101. the CMake code in this tutorial could be used unaltered in other projects,
  102. you should not use ``SimpleTest`` outside this tutorial, or try to learn from
  103. the CMake code it provides.
  104. Build and Run
  105. -------------
  106. First we must install the ``SimpleTest`` framework. Navigate to the
  107. ``Help/guide/Step10/SimpleTest`` directory and run the following commands
  108. .. code-block:: console
  109. cmake --preset tutorial
  110. cmake --install build
  111. .. note::
  112. The ``SimpleTest`` preset sets up everything needed to install ``SimpleTest``
  113. for the tutorial. For reasons that are beyond the scope of this tutorial,
  114. there is no need to build or provide any other configuration for
  115. ``SimpleTest``.
  116. We can observe that the ``Step10/install`` directory has now been populated by
  117. the ``SimpleTest`` header and package files.
  118. Now we can configure and build the Tutorial project as per usual, navigating to
  119. the ``Help/guide/Step10/TutorialProject`` and running:
  120. .. code-block:: console
  121. cmake --preset tutorial
  122. cmake --build build
  123. Verify that the ``SimpleTest`` framework has been consumed correctly by running
  124. the tests with CTest.
  125. Solution
  126. --------
  127. First we call :command:`find_package` to discover the ``SimpleTest`` package.
  128. We do this with ``REQUIRED`` because the tests cannot build without
  129. ``SimpleTest``.
  130. .. raw:: html
  131. <details><summary>TODO 1 Click to show/hide answer</summary>
  132. .. literalinclude:: Step11/TutorialProject/Tests/CMakeLists.txt
  133. :caption: TODO 1: TutorialProject/Tests/CMakeLists.txt
  134. :name: TutorialProject/Tests/CMakeLists.txt-find_package
  135. :language: cmake
  136. :start-at: find_package
  137. :end-at: find_package
  138. .. raw:: html
  139. </details>
  140. Next we add the ``SimpleTest::SimpleTest`` target to ``TestMathFunctions``
  141. .. raw:: html
  142. <details><summary>TODO 2 Click to show/hide answer</summary>
  143. .. literalinclude:: Step11/TutorialProject/Tests/CMakeLists.txt
  144. :caption: TODO 2: TutorialProject/Tests/CMakeLists.txt
  145. :name: TutorialProject/Tests/CMakeLists.txt-link-simple-test
  146. :language: cmake
  147. :start-at: target_link_libraries(TestMathFunctions
  148. :end-at: )
  149. .. raw:: html
  150. </details>
  151. Now we can replace our test description code with a call to
  152. ``simpletest_discover_tests``.
  153. .. raw:: html
  154. <details><summary>TODO 3 Click to show/hide answer</summary>
  155. .. literalinclude:: Step11/TutorialProject/Tests/CMakeLists.txt
  156. :caption: TODO 3: TutorialProject/Tests/CMakeLists.txt
  157. :name: TutorialProject/Tests/CMakeLists.txt-simpletest_discover_tests
  158. :language: cmake
  159. :start-at: simpletest_discover_tests
  160. :end-at: simpletest_discover_tests
  161. .. raw:: html
  162. </details>
  163. We ensure :command:`find_package` can discover ``SimpleTest`` by
  164. adding the install tree to :variable:`CMAKE_PREFIX_PATH`.
  165. .. raw:: html
  166. <details><summary>TODO 4 Click to show/hide answer</summary>
  167. .. literalinclude:: Step11/TutorialProject/CMakePresets.json
  168. :caption: TODO 4: TutorialProject/CMakePresets.json
  169. :name: TutorialProject/CMakePresets.json-CMAKE_PREFIX_PATH
  170. :language: json
  171. :start-at: cacheVariables
  172. :end-at: TUTORIAL_ENABLE_IPO
  173. :dedent: 6
  174. :append: }
  175. .. raw:: html
  176. </details>
  177. Finally, we update the tests to use the macros provided by ``SimpleTest`` by
  178. removing the placeholders and including the appropriate header.
  179. .. raw:: html
  180. <details><summary>TODO 5 Click to show/hide answer</summary>
  181. .. literalinclude:: Step11/TutorialProject/Tests/TestMathFunctions.cxx
  182. :caption: TODO 5: TutorialProject/Tests/TestMathFunctions.cxx
  183. :name: TutorialProject/Tests/TestMathFunctions.cxx-simpletest
  184. :language: c++
  185. :start-at: #include <MathFunctions.h>
  186. :end-at: {
  187. .. raw:: html
  188. </details>
  189. Exercise 2 - Transitive Dependencies
  190. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  191. Libraries often build on one another. A multimedia application may depend on a
  192. library which provides support for various container formats, which may in turn
  193. rely on one or more other libraries for compression algorithms.
  194. We need to express these transitive requirements inside the package config
  195. files we place in the install tree. We do so with the
  196. :module:`CMakeFindDependencyMacro` module, which provides a safe mechanism for
  197. installed packages to recursively discover one another.
  198. .. code-block:: cmake
  199. include(CMakeFindDependencyMacro)
  200. find_dependency(zlib)
  201. :module:`find_dependency() <CMakeFindDependencyMacro>` also forwards arguments
  202. from the top-level :command:`find_package` call. If :command:`find_package` is
  203. called with ``QUIET`` or ``REQUIRED``,
  204. :module:`find_dependency() <CMakeFindDependencyMacro>` will also use ``QUIET``
  205. and/or ``REQUIRED``.
  206. Goal
  207. ----
  208. Add a dependency to ``SimpleTest`` and ensure that packages which rely on
  209. ``SimpleTest`` also discover this transitive dependency.
  210. Helpful Resources
  211. -----------------
  212. * :module:`CMakeFindDependencyMacro`
  213. * :command:`find_package`
  214. * :command:`target_link_libraries`
  215. Files to Edit
  216. -------------
  217. * ``SimpleTest/CMakeLists.txt``
  218. * ``SimpleTest/cmake/SimpleTestConfig.cmake``
  219. Getting Started
  220. ---------------
  221. For this step we will only be editing the ``SimpleTest`` project. The transitive
  222. dependency, ``TransitiveDep``, is a dummy dependency which provides no behavior.
  223. However CMake doesn't know this and the ``TutorialProject`` tests will fail to
  224. configure and build if CMake cannot find all required dependencies.
  225. The ``TransitiveDep`` package has already been installed to the
  226. ``Step10/install`` tree. We do not need to install it as we did with
  227. ``SimpleTest``.
  228. Complete ``TODO 6`` through ``TODO 8``.
  229. Build and Run
  230. -------------
  231. We need to reinstall the SimpleTest framework. Navigate to the
  232. ``Help/guide/Step10/SimpleTest`` directory and run the same commands as before.
  233. .. code-block:: console
  234. cmake --preset tutorial
  235. cmake --install build
  236. Now we can reconfigure and rebuild the ``TutorialProject``, navigate to
  237. ``Help/guide/Step10/TutorialProject`` and perform the usual steps to do so.
  238. .. code-block:: console
  239. cmake --preset tutorial
  240. cmake --build build
  241. If the build passed we have likely successfully propagated the transitive
  242. dependency. Verify this by searching the ``CMakeCache.txt`` of
  243. ``TutorialProject`` for an entry named ``TransitiveDep_DIR``. This demonstrates
  244. the ``TutorialProject`` searched for an found ``TransitiveDep`` even though it
  245. has no direct requirement for it.
  246. Solution
  247. --------
  248. First we call :command:`find_package` to discover the ``TransitiveDep`` package.
  249. We use ``REQUIRED`` to verify we have found ``TransitiveDep``.
  250. .. raw:: html
  251. <details><summary>TODO 6 Click to show/hide answer</summary>
  252. .. literalinclude:: Step11/SimpleTest/CMakeLists.txt
  253. :caption: TODO 6: SimpleTest/CMakeLists.txt
  254. :name: SimpleTest/CMakeLists.txt-find_package
  255. :language: cmake
  256. :start-at: find_package
  257. :end-at: find_package
  258. .. raw:: html
  259. </details>
  260. Next we add the ``TransitiveDep::TransitiveDep`` target to ``SimpleTest``.
  261. .. raw:: html
  262. <details><summary>TODO 7 Click to show/hide answer</summary>
  263. .. literalinclude:: Step11/SimpleTest/CMakeLists.txt
  264. :caption: TODO 7: SimpleTest/CMakeLists.txt
  265. :name: SimpleTest/CMakeLists.txt-link-transitive-dep
  266. :language: cmake
  267. :start-at: target_link_libraries(SimpleTest
  268. :end-at: )
  269. .. raw:: html
  270. </details>
  271. .. note::
  272. If we built ``TutorialProject`` at this point, we would expect the
  273. configuration to fail due to the ``TransitiveDep::TransitiveDep`` target
  274. being unavailable inside that project.
  275. Finally, we include the :module:`CMakeFindDependencyMacro` and call
  276. :module:`find_dependency() <CMakeFindDependencyMacro>` inside the ``SimpleTest``
  277. package config file to propagate the transitive dependency.
  278. .. raw:: html
  279. <details><summary>TODO 8 Click to show/hide answer</summary>
  280. .. literalinclude:: Step11/SimpleTest/cmake/SimpleTestConfig.cmake
  281. :caption: TODO 8: SimpleTest/cmake/SimpleTestConfig.cmake
  282. :name: SimpleTest/cmake/SimpleTestConfig.cmake-find_dependency
  283. :language: cmake
  284. :start-at: include
  285. :end-at: find_dependency
  286. .. raw:: html
  287. </details>
  288. </details>
  289. Exercise 3 - Finding Other Kinds of Files
  290. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  291. In a perfect world every dependency we care about would be packaged correctly,
  292. or at least some other developer would have written a module that discovers it
  293. for us. We do no live in a perfect world, and sometimes we will have to get
  294. our hands dirty and discover build requirements manually.
  295. For this we have the other find commands enumerated earlier in the step, such
  296. as :command:`find_path`.
  297. .. code-block:: cmake
  298. find_path(PackageIncludeFolder Package.h REQUIRED
  299. PATH_SUFFIXES
  300. Package
  301. )
  302. target_include_directories(MyApp
  303. PRIVATE
  304. ${PackageIncludeFolder}
  305. )
  306. Goal
  307. ----
  308. Add an unpackaged header to the ``Tutorial`` executable of the
  309. ``TutorialProject``.
  310. Helpful Resources
  311. -----------------
  312. * :command:`find_path`
  313. * :command:`target_include_directories`
  314. Files to Edit
  315. -------------
  316. * ``TutorialProject/Tutorial/CMakeLists.txt``
  317. * ``TutorialProject/Tutorial/Tutorial.cxx``
  318. Getting Started
  319. ---------------
  320. For this step we will only be editing the ``TutorialProject`` project. The
  321. unpackaged header, ``Unpackaged/Unpackaged.h`` has already been installed to the
  322. ``Step10/install`` tree.
  323. Complete ``TODO 9`` through ``TODO 11``.
  324. Build and Run
  325. -------------
  326. There are no special build steps for this exercise, navigate to
  327. ``Help/guide/Step10/TutorialProject`` and perform the usual build.
  328. .. code-block:: console
  329. cmake --build build
  330. If the build passed we have successfully added the ``Unpackaged`` include
  331. directory to the project.
  332. Solution
  333. --------
  334. First we call :command:`find_path` to discover the ``Unpackaged`` include
  335. directory. We use ``REQUIRED`` because building ``Tutorial`` will fail if
  336. we cannot locate the ``Unpackaged.h`` header.
  337. .. raw:: html
  338. <details><summary>TODO 9 Click to show/hide answer</summary>
  339. .. literalinclude:: Step11/TutorialProject/Tutorial/CMakeLists.txt
  340. :caption: TODO 9: TutorialProject/Tutorial/CMakeLists.txt
  341. :name: TutorialProject/Tutorial/CMakeLists.txt-find_path
  342. :language: cmake
  343. :start-at: find_path
  344. :end-at: )
  345. .. raw:: html
  346. </details>
  347. Next we add the discovered path to ``Tutorial`` using
  348. :command:`target_include_directories`.
  349. .. raw:: html
  350. <details><summary>TODO 10 Click to show/hide answer</summary>
  351. .. literalinclude:: Step11/TutorialProject/Tutorial/CMakeLists.txt
  352. :caption: TODO 10: TutorialProject/Tutorial/CMakeLists.txt
  353. :name: TutorialProject/Tutorial/CMakeLists.txt-target_include_directories
  354. :language: cmake
  355. :start-at: target_include_directories
  356. :end-at: )
  357. .. raw:: html
  358. </details>
  359. Finally, we edit ``Tutorial.cxx`` to include the discovered header.
  360. .. raw:: html
  361. <details><summary>TODO 11 Click to show/hide answer</summary>
  362. .. literalinclude:: Step11/TutorialProject/Tutorial/Tutorial.cxx
  363. :caption: TODO 11: TutorialProject/Tutorial/Tutorial.cxx
  364. :name: TutorialProject/Tutorial/Tutorial.cxx-include-unpackaged
  365. :language: c++
  366. :start-at: #include <MathFunctions.h>
  367. :end-at: #include <Unpackaged.h>
  368. .. raw:: html
  369. </details>