mozbuild-files.rst 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. .. _mozbuild-files:
  2. ===============
  3. moz.build Files
  4. ===============
  5. ``moz.build`` files are the mechanism by which tree metadata (notably
  6. the build configuration) is defined.
  7. Directories in the tree contain ``moz.build`` files which declare
  8. functionality for their respective part of the tree. This includes
  9. things such as the list of C++ files to compile, where to find tests,
  10. etc.
  11. ``moz.build`` files are actually Python scripts. However, their
  12. execution is governed by special rules. This is explained below.
  13. moz.build Python Sandbox
  14. ========================
  15. As mentioned above, ``moz.build`` files are Python scripts. However,
  16. they are executed in a special Python *sandbox* that significantly
  17. changes and limits the execution environment. The environment is so
  18. different, it's doubtful most ``moz.build`` files would execute without
  19. error if executed by a vanilla Python interpreter (e.g. ``python
  20. moz.build``.
  21. The following properties make execution of ``moz.build`` files special:
  22. 1. The execution environment exposes a limited subset of Python.
  23. 2. There is a special set of global symbols and an enforced naming
  24. convention of symbols.
  25. 3. Some symbols are inherited from previously-executed ``moz.build``
  26. files.
  27. The limited subset of Python is actually an extremely limited subset.
  28. Only a few symbols from ``__builtins__`` are exposed. These include
  29. ``True``, ``False``, and ``None``. Global functions like ``import``,
  30. ``print``, and ``open`` aren't available. Without these, ``moz.build``
  31. files can do very little. *This is by design*.
  32. The execution sandbox treats all ``UPPERCASE`` variables specially. Any
  33. ``UPPERCASE`` variable must be known to the sandbox before the script
  34. executes. Any attempt to read or write to an unknown ``UPPERCASE``
  35. variable will result in an exception being raised. Furthermore, the
  36. types of all ``UPPERCASE`` variables is strictly enforced. Attempts to
  37. assign an incompatible type to an ``UPPERCASE`` variable will result in
  38. an exception being raised.
  39. The strictness of behavior with ``UPPERCASE`` variables is a very
  40. intentional design decision. By ensuring strict behavior, any operation
  41. involving an ``UPPERCASE`` variable is guaranteed to have well-defined
  42. side-effects. Previously, when the build configuration was defined in
  43. ``Makefiles``, assignments to variables that did nothing would go
  44. unnoticed. ``moz.build`` files fix this problem by eliminating the
  45. potential for false promises.
  46. After a ``moz.build`` file has completed execution, only the
  47. ``UPPERCASE`` variables are used to retrieve state.
  48. The set of variables and functions available to the Python sandbox is
  49. defined by the :py:mod:`mozbuild.frontend.context` module. The
  50. data structures in this module are consumed by the
  51. :py:class:`mozbuild.frontend.reader.MozbuildSandbox` class to construct
  52. the sandbox. There are tests to ensure that the set of symbols exposed
  53. to an empty sandbox are all defined in the ``context`` module.
  54. This module also contains documentation for each symbol, so nothing can
  55. sneak into the sandbox without being explicitly defined and documented.
  56. Reading and Traversing moz.build Files
  57. ======================================
  58. The process for reading ``moz.build`` files roughly consists of:
  59. 1. Start at the root ``moz.build`` (``<topsrcdir>/moz.build``).
  60. 2. Evaluate the ``moz.build`` file in a new sandbox.
  61. 3. Emit the main *context* and any *sub-contexts* from the executed
  62. sandbox.
  63. 4. Extract a set of ``moz.build`` files to execute next.
  64. 5. For each additional ``moz.build`` file, goto #2 and repeat until all
  65. referenced files have executed.
  66. From the perspective of the consumer, the output of reading is a stream
  67. of :py:class:`mozbuild.frontend.reader.context.Context` instances. Each
  68. ``Context`` defines a particular aspect of data. Consumers iterate over
  69. these objects and do something with the data inside. Each object is
  70. essentially a dictionary of all the ``UPPERCASE`` variables populated
  71. during its execution.
  72. .. note::
  73. Historically, there was only one ``context`` per ``moz.build`` file.
  74. As the number of things tracked by ``moz.build`` files grew and more
  75. and more complex processing was desired, it was necessary to split these
  76. contexts into multiple logical parts. It is now common to emit
  77. multiple contexts per ``moz.build`` file.
  78. Build System Reading Mode
  79. -------------------------
  80. The traditional mode of evaluation of ``moz.build`` files is what's
  81. called *build system traversal mode.* In this mode, the ``CONFIG``
  82. variable in each ``moz.build`` sandbox is populated from data coming
  83. from ``config.status``, which is produced by ``configure``.
  84. During evaluation, ``moz.build`` files often make decisions conditional
  85. on the state of the build configuration. e.g. *only compile foo.cpp if
  86. feature X is enabled*.
  87. In this mode, traversal of ``moz.build`` files is governed by variables
  88. like ``DIRS`` and ``TEST_DIRS``. For example, to execute a child
  89. directory, ``foo``, you would add ``DIRS += ['foo']`` to a ``moz.build``
  90. file and ``foo/moz.build`` would be evaluated.
  91. .. _mozbuild_fs_reading_mode:
  92. Filesystem Reading Mode
  93. -----------------------
  94. There is an alternative reading mode that doesn't involve the build
  95. system and doesn't use ``DIRS`` variables to control traversal into
  96. child directories. This mode is called *filesystem reading mode*.
  97. In this reading mode, the ``CONFIG`` variable is a dummy, mostly empty
  98. object. Accessing all but a few special variables will return an empty
  99. value. This means that nearly all ``if CONFIG['FOO']:`` branches will
  100. not be taken.
  101. Instead of using content from within the evaluated ``moz.build``
  102. file to drive traversal into subsequent ``moz.build`` files, the set
  103. of files to evaluate is controlled by the thing doing the reading.
  104. A single ``moz.build`` file is not guaranteed to be executable in
  105. isolation. Instead, we must evaluate all *parent* ``moz.build`` files
  106. first. For example, in order to evaluate ``/foo/moz.build``, one must
  107. execute ``/moz.build`` and have its state influence the execution of
  108. ``/foo/moz.build``.
  109. Filesystem reading mode is utilized to power the
  110. :ref:`mozbuild_files_metadata` feature.
  111. Technical Details
  112. -----------------
  113. The code for reading ``moz.build`` files lives in
  114. :py:mod:`mozbuild.frontend.reader`. The Python sandboxes evaluation results
  115. (:py:class:`mozbuild.frontend.context.Context`) are passed into
  116. :py:mod:`mozbuild.frontend.emitter`, which converts them to classes defined
  117. in :py:mod:`mozbuild.frontend.data`. Each class in this module defines a
  118. domain-specific component of tree metdata. e.g. there will be separate
  119. classes that represent a JavaScript file vs a compiled C++ file or test
  120. manifests. This means downstream consumers of this data can filter on class
  121. types to only consume what they are interested in.
  122. There is no well-defined mapping between ``moz.build`` file instances
  123. and the number of :py:mod:`mozbuild.frontend.data` classes derived from
  124. each. Depending on the content of the ``moz.build`` file, there may be 1
  125. object derived or 100.
  126. The purpose of the ``emitter`` layer between low-level sandbox execution
  127. and metadata representation is to facilitate a unified normalization and
  128. verification step. There are multiple downstream consumers of the
  129. ``moz.build``-derived data and many will perform the same actions. This
  130. logic can be complicated, so we have a component dedicated to it.
  131. :py:class:`mozbuild.frontend.reader.BuildReader`` and
  132. :py:class:`mozbuild.frontend.reader.TreeMetadataEmitter`` have a
  133. stream-based API courtesy of generators. When you hook them up properly,
  134. the :py:mod:`mozbuild.frontend.data` classes are emitted before all
  135. ``moz.build`` files have been read. This means that downstream errors
  136. are raised soon after sandbox execution.
  137. Lots of the code for evaluating Python sandboxes is applicable to
  138. non-Mozilla systems. In theory, it could be extracted into a standalone
  139. and generic package. However, until there is a need, there will
  140. likely be some tightly coupled bits.