You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

248 lines
8.7KB

  1. .. highlight:: yaml
  2. Library and Package Dependencies
  3. ################################
  4. ``dds`` considers that all libraries belong to a single *package*, but a single
  5. package may contain one or more *libraries*. For this reason, and to better
  6. interoperate with other build and packaging tools, we consider the issues of
  7. package dependencies and library dependencies separately.
  8. .. _deps.pkg-deps:
  9. Package Dependencies
  10. ********************
  11. Consider that we are creating a package ``acme-gadgets@4.3.6``. We declare the
  12. name and version in the ``package.json5`` in the package root:
  13. .. code-block:: js
  14. {
  15. name: 'acme-widgets',
  16. version: '4.3.6',
  17. namespace: 'acme',
  18. }
  19. .. note::
  20. The ``namespace`` field is required, but will be addressed in the
  21. :ref:`deps.lib-deps` section.
  22. Suppose that our package's libraries build upon the libraries in the
  23. ``acme-widgets`` package, and that we require version ``1.4.3`` or newer, but
  24. not as new as ``2.0.0``. Such a dependency can be declared with the ``Depends``
  25. key:
  26. .. code-block:: js
  27. :emphasize-lines: 5-7
  28. {
  29. name: 'acme-gadgets',
  30. version: '4.3.6',
  31. namespace: 'acme',
  32. depends: {
  33. 'acme-widgets': '^1.4.3',
  34. },
  35. }
  36. .. seealso:: :ref:`deps.ranges`.
  37. If we wish to declare additional dependencies, we simply declare them with
  38. additional ``Depends`` keys
  39. .. code-block::
  40. :emphasize-lines: 7-8
  41. {
  42. name: 'acme-gadgets',
  43. version: '4.3.6',
  44. namespace: 'acme',
  45. depends: {
  46. 'acme-widgets': '^1.4.3',
  47. 'acme-gizmos': '~5.6.5',
  48. 'acme-utils': '^3.3.0',
  49. },
  50. }
  51. When ``dds`` attempts to build a project, it will first build the dependency
  52. solution by iteratively scanning the dependencies of the containing project and
  53. all transitive dependencies.
  54. .. _deps.ranges:
  55. Compatible Range Specifiers
  56. ===========================
  57. When specifying a dependency on a package, one will want to specify which
  58. versions of the dependency are supported.
  59. .. note::
  60. Unlike other packaging tools, ``dds`` will find a solution with the
  61. *lowest* possible version that satisfies the given requirements for each
  62. package. This decision is not incidental: It's entirely intentional.
  63. Refer to: :ref:`deps.ranges.why-lowest`.
  64. ``dds`` compatible-version ranges are similar to the shorthand range specifiers
  65. supported by ``npm`` and ``npm``-like tools. There are five (and a half)
  66. version range formats available, listed in order of most-to-least restrictive:
  67. Exact: ``1.2.3`` and ``=1.2.3``
  68. Specifies an *exact* requirement. The dependency must match the named
  69. version *exactly* or it is considered incompatible.
  70. Minor: ``~1.2.3``
  71. Specifies a *minor* requirement. The version of the dependency should be
  72. *at least* the given version, but not as new or newer than the next minor
  73. revision. In this example, it represents the half-open version range
  74. ``[1.2.3, 1.3.0)``.
  75. Major: ``^1.2.3``
  76. Specifies a *major* requirement. The version must be *at least* the same
  77. given version, but not any newer than the the next major version. In the
  78. example, this is the half-open range ``[1.2.3, 2.0.0)``.
  79. .. note::
  80. This is the recommended default option to reach for, as it matches the
  81. intended behavior of `Semantic Versioning <https://semver.org>`_.
  82. At-least: ``+1.2.3``
  83. Specifies an *at least* requirement. The version must be *at least* the
  84. given version, but any newer version is acceptable.
  85. Anything: ``*``
  86. An asterisk ``*`` represents than *any* version is acceptable. This is not
  87. recommended for most dependencies.
  88. .. _deps.ranges.why-lowest:
  89. Why Pull the *Lowest* Matching Version?
  90. ---------------------------------------
  91. When resolving dependencies, ``dds`` will pull the version of the dependency
  92. that is the lowest version that satisfies the given range. In most cases,
  93. this will be the same version that is the base of the version range.
  94. Imagine a scenario where we *did* select the "latest-matching-version":
  95. Suppose we are developing a library ``Gadgets``, and we wish to make use of
  96. ``Widgets``. The latest version is ``1.5.2``, and they promise Semantic
  97. Versioning compatibility, so we select a version range of ``^1.5.2``.
  98. Suppose a month passes, and ``Widgets@1.6.0`` is published. A few things
  99. happen:
  100. #. Our CI builds now switch from ``1.5.2`` to ``1.6.0`` *without any code
  101. changes*. Should be okay, right? I mean... it's still compatible, yeah?
  102. #. Bugs in ``Widgets@1.6.0`` will now appear in all CI builds, and won't be
  103. reproducible locally unless we re-pull our dependencies and obtain the
  104. new version of ``Widgets``. This requires that we be conscientious enough to
  105. realize what is actually going on.
  106. #. Even if ``Widgets@1.6.0`` introduces no new bugs, a developer re-pulling
  107. their dependencies will suddenly be developing against ``1.6.0``, and may
  108. not even realize it. In fact, this may continue for weeks or months until
  109. *everyone* is developing against ``1.6.0`` without realizing that they
  110. actually only require ``1.5.2`` in their dependency declarations.
  111. #. Code in our project is written that presupposes features or bugfixes added
  112. in ``1.6.0``, and thus makes the dependency declaration on ``Widgets ^1.5.2``
  113. a *lie*.
  114. Pulling the lowest-matching-version has two *huge* benefits:
  115. #. No automatic CI upgrades. The code built today will produce the same result
  116. when built a year from now.
  117. #. Using a feature/fix beyond our minimum requirement becomes a compile error,
  118. and we catch these up-front rather than waiting for a downstream user
  119. discovering them for us.
  120. *Isn't this what lockfiles are for?*
  121. """"""""""""""""""""""""""""""""""""
  122. Somewhat. Lockfiles will prevent automatic upgrades, but they will do nothing
  123. to stop accidental reliance on new versions. There are other useful features
  124. of lockfiles, but preventing automatic upgrades can be a non-issue by simply
  125. using lowest-matching-version.
  126. *So, if this is the case, why use ranges at all?*
  127. """""""""""""""""""""""""""""""""""""""""""""""""
  128. In short: *Your* compatibility ranges are not for *you*. They are for *your
  129. users*.
  130. Suppose package ``A`` requires ``B ^1.0.0``, and ``B`` requires ``C ^1.2.0``.
  131. Now let us suppose that ``A`` wishes to use a newer feature of ``C``, and thus
  132. declares a dependency on ``C ^1.3.0``. ``B`` and ``A`` have different
  133. compatibility ranges on ``C``, but this will work perfectly fine **as long as
  134. the compatible version ranges of A and B have some overlap**.
  135. That final qualification is the reason we use compatibility ranges: To support
  136. our downstream users to form dependency graphs that would otherwise form
  137. conflicts if we required *exact* versions for everything. In the above example,
  138. ``C@1.3.0`` will be selected for the build of ``A``.
  139. Now, if another downstream user wants to use ``A``, they will get ``C@1.3.0``.
  140. But they discover that they actually need a bugfix in ``C``, so they place
  141. their own requirement on ``C ^1.3.1``. Thus, they get ``C@1.3.1``, which still
  142. satisfies the compatibility ranges of ``A`` and ``B``. Everyone gets along
  143. just fine!
  144. .. _deps.lib-deps:
  145. Library Dependencies
  146. ********************
  147. In ``dds``, library interdependencies are tracked separately from the packages
  148. that contain them. A library must declare its intent to use another library
  149. in the ``library.json5`` at its library root. The minimal content of a
  150. ``library.json5`` is the ``name`` key:
  151. .. code-block:: js
  152. {
  153. name: 'gadgets'
  154. }
  155. To announce that a library wishes to *use* another library, use the aptly-named
  156. ``uses`` key:
  157. .. code-block:: js
  158. :emphasize-lines: 3-7
  159. {
  160. name: 'gadgets',
  161. uses: [
  162. 'acme/widgets',
  163. 'acme/gizmos',
  164. 'acme/utils',
  165. ],
  166. }
  167. Here is where the package's ``namespace`` key comes into play: A library's
  168. qualified name is specified by joining the ``namespace`` of the containing
  169. package with the ``name`` of the library within that package with a ``/``
  170. between them.
  171. It is the responsibility of package authors to document the ``namespace`` and
  172. ``name`` of the packages and libraries that they distribute.
  173. .. note::
  174. The ``namespace`` of a package is completely arbitrary, and need not relate
  175. to a C++ ``namespace``.
  176. .. note::
  177. The ``namespace`` need not be unique to a single package. For example, a
  178. single organization (Like Acme Inc.) can share a single ``namespace`` for
  179. many of their packages and libraries.
  180. However, it is essential that the ``<namespace>/<name>`` pair be
  181. universally unique, so choose wisely!
  182. Once the ``uses`` key appears in the ``library.dds`` file of a library, ``dds``
  183. will make available the headers for the library being used, and will
  184. transitively propagate that usage requirement to users of the library.