123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279 |
- Step 7: Custom Commands and Generated Files
- ===========================================
- Code generation is a ubiquitous mechanism for extending programming languages
- beyond the bounds of their language model. CMake has first-class support for
- Qt's Meta-Object Compiler, but very few other code generators are notable
- enough to warrant that kind of effort.
- Instead, code generators tend to be bespoke and usage specific. CMake provides
- facilities for describing the usage of a code generator, so projects can
- add support for their individual needs.
- In this step, we will use :command:`add_custom_command` to add support for a
- code generator within the tutorial project.
- Background
- ^^^^^^^^^^
- Any step in the build process can generally be described in terms of its inputs
- and outputs. CMake assumes that code generators and other custom processes
- operate on the same principle. In this way, the code generator acts identically
- to compilers, linkers, and other elements of the toolchain; when the inputs are
- newer than the outputs (or the outputs don't exist), a user-specified command
- will be run to update the outputs.
- .. note::
- This model assumes the outputs of a process are known before it is run. CMake
- lacks the ability to describe code generators where the name and location of
- the outputs depends on the *content* of the input. Various hacks exist to
- shim this functionality into CMake, but they are outside the scope of this
- tutorial.
- Describing a code generator (or any custom process) is usually performed in
- two parts. First, the inputs and outputs are described independently of the
- CMake target model, concerned only with the generation process itself. Second,
- the outputs are associated with a CMake target to insert them into the CMake
- target model.
- For sources, this is as simple as adding the generated files to the source list
- of a ``STATIC``, ``SHARED``, or ``OBJECT`` library. For header-only generators,
- it's often necessary to use an intermediary target created via
- :command:`add_custom_target` to add the header file generation to the
- build stage (because ``INTERFACE`` libraries have no build step).
- Exercise 1 - Using a Code Generator
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- The primary mechanism for describing a code generator is the
- :command:`add_custom_command` command. A "command", for the purpose of
- :command:`add_custom_command` is either an executable available in the build
- environment or a CMake executable target name.
- .. code-block:: cmake
- add_executable(Tool)
- # ...
- add_custom_command(
- OUTPUT Generated.cxx
- COMMAND Tool -i input.txt -o Generated.cxx
- DEPENDS Tool input.txt
- VERBATIM
- )
- # ...
- add_library(GeneratedObject OBJECT)
- target_sources(GeneratedObject
- PRIVATE
- Generated.cxx
- )
- Most of the keywords are self-explanatory, with the exception of ``VERBATIM``.
- This argument is effectively mandatory for legacy reasons that are uninteresting
- to explain in a modern context. The curious should consult the
- :command:`add_custom_command` documentation for additional details.
- The ``Tool`` executable target appears both in the ``COMMAND`` and ``DEPENDS``
- parameters. While ``COMMAND`` is sufficient for the code to build correctly,
- adding the ``Tool`` itself as a dependency of the custom command ensure that
- if ``Tool`` is updated, the custom command will be rerun.
- For header-only file generation, additional commands are necessary because the
- library itself has no build step. We can use :command:`add_custom_target` to
- create an "artificial" build step for the library. We then force the custom
- target to be run before any targets which link the library with the command
- :command:`add_dependencies`.
- .. code-block:: cmake
- add_custom_target(RunGenerator DEPENDS Generated.h)
- add_library(GeneratedLib INTERFACE)
- target_sources(GeneratedLib
- INTERFACE
- FILE_SET HEADERS
- BASE_DIRS
- ${CMAKE_CURRENT_BINARY_DIR}
- FILES
- ${CMAKE_CURRENT_BINARY_DIR}/Generated.h
- )
- add_dependencies(GeneratedLib RunGenerator)
- .. note::
- We add the :variable:`CMAKE_CURRENT_BINARY_DIR`, a variable which names the
- current location in the build tree where our artifacts are being placed, to
- the base directories because that's the working directory our code generator
- will be run inside of. Listing the ``FILES`` is unnecessary for the build and
- done so here only for clarity.
- Goal
- ----
- Add a generated table of pre-computed square roots to the ``MathFunctions``
- library.
- Helpful Resources
- -----------------
- * :command:`add_executable`
- * :command:`add_library`
- * :command:`target_sources`
- * :command:`add_custom_command`
- * :command:`add_custom_target`
- * :command:`add_dependencies`
- Files to Edit
- -------------
- * ``MathFunctions/CMakeLists.txt``
- * ``MathFunctions/MakeTable/CMakeLists.txt``
- * ``MathFunctions/MathFunctions.cxx``
- Getting Started
- ---------------
- The ``MathFunctions`` library has been edited to use a pre-computed table when
- given a number less than 10. However, the hardcoded table is not particularly
- accurate, containing only the nearest truncated integer value.
- The ``MakeTable.cxx`` source file describes a program which will generate a
- better table. It takes a single argument as input, the file name of the table
- to be generated.
- Complete ``TODO 1`` through ``TODO 10``.
- Build and Run
- -------------
- No special configuration is needed, configure and build as usual. Note that
- the ``MakeTable`` executable is sequenced before ``MathFunctions``.
- .. code-block:: console
- cmake --preset tutorial
- cmake --build build
- Verify the output of ``Tutorial`` now uses the pre-computed table for values
- less than 10.
- Solution
- --------
- First we add a new executable to generate the tables, adding the
- ``MakeTable.cxx`` file as a source.
- .. raw:: html
- <details><summary>TODO 1-2: Click to show/hide answer</summary>
- .. literalinclude:: Step8/MathFunctions/MakeTable/CMakeLists.txt
- :caption: TODO 1-2: MathFunctions/MakeTable/CMakeLists.txt
- :name: MathFunctions/MakeTable/CMakeLists.txt-add_executable
- :language: cmake
- :start-at: add_executable
- :end-at: MakeTable.cxx
- :append: )
- .. raw:: html
- </details>
- Then we add a custom command which produces the table, and custom target which
- depends on the table.
- .. raw:: html
- <details><summary>TODO 3-4: Click to show/hide answer</summary>
- .. literalinclude:: Step8/MathFunctions/MakeTable/CMakeLists.txt
- :caption: TODO 3-4: MathFunctions/MakeTable/CMakeLists.txt
- :name: MathFunctions/MakeTable/CMakeLists.txt-add_custom_command
- :language: cmake
- :start-at: add_custom_command
- :end-at: add_custom_target
- .. raw:: html
- </details>
- We need to add an interface library which describes the output which will
- appear in :variable:`CMAKE_CURRENT_BINARY_DIR`. The ``FILES`` parameter is
- optional.
- .. raw:: html
- <details><summary>TODO 5-6: Click to show/hide answer</summary>
- .. literalinclude:: Step8/MathFunctions/MakeTable/CMakeLists.txt
- :caption: TODO 5-6: MathFunctions/MakeTable/CMakeLists.txt
- :name: MathFunctions/MakeTable/CMakeLists.txt-add_library
- :language: cmake
- :start-at: add_library
- :end-at: SqrtTable.h
- :append: )
- .. raw:: html
- </details>
- Now that all the targets are described, we can force the custom target to run
- before any dependents of the interface library by associating them with
- :command:`add_dependencies`.
- .. raw:: html
- <details><summary>TODO 7: Click to show/hide answer</summary>
- .. literalinclude:: Step8/MathFunctions/MakeTable/CMakeLists.txt
- :caption: TODO 7: MathFunctions/MakeTable/CMakeLists.txt
- :name: MathFunctions/MakeTable/CMakeLists.txt-add_dependencies
- :language: cmake
- :start-at: add_dependencies
- :end-at: add_dependencies
- .. raw:: html
- </details>
- We are ready to add the interface library to the linked libraries of
- ``MathFunctions``, and add the entire ``MakeTable`` folder to the project.
- .. raw:: html
- <details><summary>TODO 8-9: Click to show/hide answer</summary>
- .. literalinclude:: Step8/MathFunctions/CMakeLists.txt
- :caption: TODO 8: MathFunctions/CMakeLists.txt
- :name: MathFunctions/CMakeLists.txt-link-sqrttable
- :language: cmake
- :start-at: target_link_libraries(MathFunctions
- :end-at: )
- .. literalinclude:: Step8/MathFunctions/CMakeLists.txt
- :caption: TODO 9: MathFunctions/CMakeLists.txt
- :name: MathFunctions/CMakeLists.txt-add-maketable
- :language: cmake
- :start-at: add_subdirectory(MakeTable
- :end-at: add_subdirectory(MakeTable
- .. raw:: html
- </details>
- Finally, we update the ``MathFunctions`` library itself to take advantage of
- the generated table.
- .. raw:: html
- <details><summary>TODO 10: Click to show/hide answer</summary>
- .. literalinclude:: Step8/MathFunctions/MathFunctions.cxx
- :caption: TODO 10: MathFunctions/MathFunctions.cxx
- :name: MathFunctions/MathFunctions.cxx-include-sqrttable
- :language: c++
- :start-at: #include <SqrtTable.h>
- :end-at: {
- .. raw:: html
- </details>
|