|
| 1 | +# The Babylon Native Build System |
| 2 | + |
| 3 | +The Babylon Native build system is founded on a lateral dependency management strategy |
| 4 | +implemented using Git Submodules and CMake build targets. This system is designed to be |
| 5 | +as extensible and flexible as possible: it should be easy to add new features/extensions, |
| 6 | +and it should be easy to incorporate Babylon Native into other existing projects. This |
| 7 | +document provides an overview of the concepts underpinning the Babylon Native build system |
| 8 | +and outlines some of the reasoning and intent behind them. |
| 9 | + |
| 10 | +## Lateral Dependency Management |
| 11 | + |
| 12 | +Scalable dependency management can be a challenge when making heavy use of Git Submodules. |
| 13 | +The Babylon Native build system addresses this challenge using a strategy we've informally |
| 14 | +described as *lateral dependency management*. To explain what this is and the motivation |
| 15 | +behind it, we'll begin by exploring some of the problems this strategy attempts to solve. |
| 16 | + |
| 17 | +One of the biggest challenges of dependency management with Git Submodules is that, when |
| 18 | +creating submodules in isolation, efforts to make each repository self-contained often |
| 19 | +lead to submodules bringing along their own dependencies, often as other submodules, |
| 20 | +resulting in a submodule "tree" that can rapidly become unwieldy. For the purposes of |
| 21 | +this discussion, we'll call this approach *vertical dependency management*. |
| 22 | + |
| 23 | + |
| 24 | + |
| 25 | +As illustrated in the diagram above, vertical dependency management can result in rather |
| 26 | +clunky and confusing repository structures that do not scale very well as the number of |
| 27 | +dependencies grows. More problematic, however, is the difficulty that arises if two or more |
| 28 | +"first tier" dependencies actually share a dependency of their own, or if they depend on |
| 29 | +each other. For example, consider what would happen if `Dependency A.1` and `Dependency B.1` |
| 30 | +were actually the same library. Because `Dependency A` and `Dependency B` were developed |
| 31 | +as independent standalone repositories, they each "bring along" their own copy of the |
| 32 | +shared dependency (and might even bring along different versions), causing that dependency |
| 33 | +to essentially be incorporated twice into `Root Project`, which will cause problems. |
| 34 | + |
| 35 | +The Babylon Native build system's strategy of *lateral dependency management* was |
| 36 | +principally created to avoid this "shared dependency" problem. Instead of a nested, vertical |
| 37 | +approach where every submodule is a standalone repository that "brings along" everything it |
| 38 | +needs, submodules are instead intended to *not* bring their own dependencies with them. |
| 39 | +Instead, all submodules are added to the `Root Project` directly, making it the top-level |
| 40 | +repository's responsibility to ensure that all dependencies (and their dependencies, and so |
| 41 | +on) are provided for. |
| 42 | + |
| 43 | + |
| 44 | + |
| 45 | +The "greyed out" dependency arrows in the above diagram allude to the indirect manner by |
| 46 | +which submodules depend upon each other: dependency relationships are controlled by CMake |
| 47 | +target name, not by repository location. For example, `Dependency A.2` might expose a |
| 48 | +CMake library target called `dependency-a-2`, which the `Dependency A` submodule would |
| 49 | +then attempt to link to by name. A more detailed description of this is provided in a |
| 50 | +[later section](#connecting-it-all-with-cmake). |
| 51 | + |
| 52 | +This approach has several advantages when contrasted with vertical dependency management. |
| 53 | + |
| 54 | +- The structure of the project is dramatically simplified. |
| 55 | +- It is now possible for multiple submodules to share a dependency without including |
| 56 | +redundant copies of the dependency. |
| 57 | +- The root-level repository now has substantially more control. Because the `Root Project` |
| 58 | +is now responsible for satisfying *all* dependencies, including dependencies of |
| 59 | +dependencies, it is now in a position to make arbitration decisions about what gets included |
| 60 | +and at what version. For example, in the diagram above, both `Dependency A` and |
| 61 | +`Dependency B` depend on `Dependency A.1/B.1`; but because they now only attempt to link to |
| 62 | +their dependencies by name (in this case, perhaps `Dependency A.1/B.1` exposes a target |
| 63 | +called `dependency-x-1`), the submodules are no longer tightly coupled to a particular |
| 64 | +version of their dependency. `Dependency A` and `Dependency B` might have been developed |
| 65 | +against slightly different but API-compatible versions of `Dependency A.1/B.1`. While this |
| 66 | +would have been a problem with vertical dependency management, lateral dependency |
| 67 | +management allows the `Root Project` to arbitrarily decide that the more recent of these |
| 68 | +versions (or even a different API-compatible version) is the right one for `Root Project`'s |
| 69 | +use case; and because the provided version still exposes the `dependency-x-1` target, both |
| 70 | +`Dependency A` and `Dependency B` will automatically link to it by name without requiring |
| 71 | +any modification to the submodules themselves. `Root Project` could even choose to |
| 72 | +"polyfill" the depency with an entirely different API-compatible implementation, and as |
| 73 | +long as that implementation was exposed under the expected target name, `Dependency A` and |
| 74 | +`Dependency B` would correctly link to it and the build would "just work." |
| 75 | + |
| 76 | +These advantages come at the cost of a small number of corresponding disadvantages and |
| 77 | +"quirks." |
| 78 | + |
| 79 | +- Because they no longer "bring along" everything they need, submodules designed to be used |
| 80 | +in a lateral dependency management scenario cannot be thought of as stand-alone libraries. |
| 81 | +This problem can be mitigated by pairing each repository of this kind with a default |
| 82 | +"harness" repository which consumes it as a submodule, laterally supplies its dependencies, |
| 83 | +and allows the building of a stand-alone library. |
| 84 | +- Many existing repositories follow patterns more reminiscent of vertical dependency |
| 85 | +management and so do not fit perfectly into a lateral dependency management strategy. This |
| 86 | +is usually not a problem; the vertically-dependent submodule can simply be treated as a |
| 87 | +"leaf" dependency with no dependencies of its own, thus allowing the submodule's internal |
| 88 | +vertical dependency management to exist "inside" the encompassing lateral dependency |
| 89 | +management mechanism. Note that this will not work if more than one submodule employs this |
| 90 | +stragety to import the same shared dependency, as described above. However, if this |
| 91 | +happens, then the two or more dependencies in question are fundamentally incompatible as |
| 92 | +submodules; the problem cannot be solved by either lateral or vertical dependency |
| 93 | +management, and it is likely that another approach, such as the modification of one or more |
| 94 | +of the repositories, will be necessary in order to allow these submodules to work together. |
| 95 | +- Making the `Root Project` responsible for the satisfaction of *all* dependencies |
| 96 | +makes each dependency submodule less of a "black box" because the `Root Project` must now |
| 97 | +know about the submodule's dependencies so that it can supply them. In practice, this tends |
| 98 | +to be a small cost, as adding additional submodules is not difficult and makes the |
| 99 | +repository no more heavyweight than it would be otherwise. Still, with regards to the |
| 100 | +encapsulation of information and the hiding of implementation details, it is a disadvantage |
| 101 | +or "quirk" worth noting. |
| 102 | + |
| 103 | +In summary, lateral dependency management allows the dependency structure of a repository |
| 104 | +to scale *out* instead of *up*. It does this by treating submodule dependencies not as |
| 105 | +independent, self-contained libraries, but as modular components intended to function as |
| 106 | +part of an assemblage, the implementation of which is the responsibility of the root-level |
| 107 | +repository. The following sections describe how this idea is implemented within the |
| 108 | +Babylon Native build system. |
| 109 | + |
| 110 | +## Babylon Native Components |
| 111 | + |
| 112 | +As a concept, lateral dependency management lends itself particularly well to highly |
| 113 | +moduler, componentized architectures. The Babylon Native build system is designed to |
| 114 | +leverage this strength, providing virtually all of its capabilities as modular components |
| 115 | +that can be assembled, recombined, excluded, and/or replaced with relative ease. These |
| 116 | +components fall into four principle categories, which are reflected in the top-level |
| 117 | +structure of the repository. |
| 118 | + |
| 119 | +- [**`Dependencies`**](https://github.com/BabylonJS/BabylonNative/tree/master/Dependencies): |
| 120 | +As mentioned in a prior section, it is not always possible to get every dependency to |
| 121 | +conform to the laterally-focused patterns used in Babylon Native, particularly when the |
| 122 | +dependency is on external and/or pre-existing code. Dependencies of this description are |
| 123 | +contained in the `Dependencies` folder, along with "adapter" mechanisms to allow the |
| 124 | +dependency to be consumed by other components that do follow the lateral dependency |
| 125 | +management pattern. An example of this can be seen in the |
| 126 | +[adapter code for `base-n`](https://github.com/BabylonJS/BabylonNative/blob/345d5bbffc8245f41d4fbd23efc13c0161b15244/Dependencies/CMakeLists.txt#L14-L17); |
| 127 | +because the `base-n` dependency does not provide a CMake target for other components to |
| 128 | +link to by name, we simply add one for it from outside the submodule so that this name |
| 129 | +can be consumed by other components like the |
| 130 | +[`Window` polyfill](https://github.com/BabylonJS/BabylonNative/blob/74878d6ce9f3568b334029094fe100aa8834eca0/Polyfills/Window/CMakeLists.txt#L13). |
| 131 | +- [**`Core`**](https://github.com/BabylonJS/BabylonNative/tree/master/Core): Components in |
| 132 | +the `Core` folder are the most foundational of all Babylon Native components. The most |
| 133 | +prominent among these is |
| 134 | +[`JsRuntime`](https://github.com/BabylonJS/BabylonNative/tree/master/Core/JsRuntime), |
| 135 | +which is unique in its centrality (nearly every other Babylon Native component is expected |
| 136 | +to depend on it), but other miscellaneous fundamental components also reside within this |
| 137 | +folder. `Core` components have two distinguishing characteristics: (1) they must be |
| 138 | +extremely fundamental Babylon Native components with no dependencies outside the `Core` and |
| 139 | +`Dependencies` folders; and (2) **their primary focus should be to expose functionality to |
| 140 | +the native (C++) layer**. `JsRuntime` is the archetypical `Core` component: it is |
| 141 | +foundational, it has few dependencies, and it provides the primary mechanism with which |
| 142 | +other C++ components can interact with the JavaScript layer. |
| 143 | +- [**`Plugins`**](https://github.com/BabylonJS/BabylonNative/tree/master/Plugins): Components |
| 144 | +in the `Plugins` folder differ from those in `Core` on both of the latter group's |
| 145 | +distinguishing axes. Firstly, `Plugins` are not expected to be foundational and can have |
| 146 | +many dependencies on other components from the `Plugins`, `Core`, and `Dependencies` |
| 147 | +folders. More importantly, however, `Plugins` differ from `Core` components in that |
| 148 | +**their primary focus should be to expose functionality to the JavaScript layer**. |
| 149 | +Often, but not always, `Plugins` expose functionality by exposing *new types* to |
| 150 | +JavaScript; it is common (though not required) for these new types to be implemented as C++ |
| 151 | +types exposed to JavaScript through `N-API`. |
| 152 | +[`NativeEngine`](https://github.com/BabylonJS/BabylonNative/tree/master/Plugins/NativeEngine) |
| 153 | +is by far the most important and sophisticated `Plugins` component provided by the Babylon |
| 154 | +Native repository. |
| 155 | +- [**`Polyfills`**](https://github.com/BabylonJS/BabylonNative/tree/master/Polyfills): |
| 156 | +As the name implies, components in the `Polyfills` folder exist to "polyfill" common |
| 157 | +capabilities needed by JavaScript libraries. |
| 158 | +[`Console`](https://github.com/BabylonJS/BabylonNative/tree/master/Polyfills/Console), for |
| 159 | +example, polyfills the `console.log()` capability frequently used in JavaScript. Because |
| 160 | +they have few dependency constraints and are intended to expose functionality to |
| 161 | +JavaScript, `Polyfills` are very similar to `Plugins`, but with two important differences. |
| 162 | +Firstly, whereas `Plugins` often expose functionality by exposing *new* JavaScript types, |
| 163 | +**`Polyfills` *must* expose their functionality by providing implementations for |
| 164 | +well-known, pre-existing JavaScript capabilities** such as browser features. Secondly, |
| 165 | +whereas `Plugins` can be a dependency for another component and can even depend on other |
| 166 | +`Plugins`, **no Babylon Native component can ever depend on a `Polyfill`**. Note that this |
| 167 | +second restriction does not extend into JavaScript; the fact that the capability is |
| 168 | +polyfilled is an implementation detail of the native layer and should not affect |
| 169 | +implementations in JavaScript. Only native, C++ components are explicitly disallowed from |
| 170 | +depending on `Polyfills`. |
| 171 | + |
| 172 | +Babylon Native components from all four of these categories are all intended to be combined |
| 173 | +and reassembled in different ways for different projects. They are also all designed to |
| 174 | +follow the same lateral dependency management pattern |
| 175 | +[described in more detail below](#connecting-it-all-with-cmake). (The exception, of |
| 176 | +course, is the `Dependencies` folder, where there are a number of external dependencies |
| 177 | +that do not conform to Babylon Native's conventions.) For this reason, every Babylon Native |
| 178 | +component can be thought of "philosophically" as a Git submodule. Note that not every |
| 179 | +component actually *is* a submodule; so far, we have not found it worthwhile to carry the |
| 180 | +concept to that extreme since most of the provided components are lightweight and/or |
| 181 | +co-occur in almost every consumption scenario. However, every Babylon Native component |
| 182 | +*could* be separated out into its own repository; its place in the Babylon Native repo |
| 183 | +would subsequently be filled by the new repository as a submodule. This idea -- that |
| 184 | +components are submodules, in concept even when not in fact -- is the cornerstone principle |
| 185 | +of Babylon Native's lateral dependency management architecture. The benefits of this |
| 186 | +architecture are explored further in the documentation on |
| 187 | +[extending Babylon Native](Extending.md). |
| 188 | + |
| 189 | +## Connecting It All With CMake |
| 190 | + |
| 191 | +The implementation of the Babylon Native build system is based on the use of CMake targets |
| 192 | +to allow the lateral dependency management strategy described above to function. The |
| 193 | +high-level workflow of the build is to process all the component folders -- `Dependencies`, |
| 194 | +`Core`, `Plugins`, and `Polyfills`, respectively -- in order to "discover" all the CMake |
| 195 | +targets, which become addressable by name as they are "discovered." Finally, the `Apps` |
| 196 | +folder is processed, whereupon the provided demo applications (notably the |
| 197 | +[Playground](https://github.com/BabylonJS/BabylonNative/tree/master/Apps/Playground)) |
| 198 | +assemble their dependencies with a small amount of |
| 199 | +[glue code](https://github.com/BabylonJS/BabylonNative/blob/74878d6ce9f3568b334029094fe100aa8834eca0/Apps/Playground/Win32/App.cpp#L96-L120) |
| 200 | +into the final executable program. |
| 201 | + |
| 202 | +This build process, in which different side-by-side folders are processed in order to |
| 203 | +"discover" the CMake targets they expose, can be thought as a sort of metaphorical |
| 204 | +craftsman's table on which we will try to make our app. When we begin, there is nothing |
| 205 | +on our craftsman's table; we have no tools yet, so we can only process things that have |
| 206 | +no dependencies. In the example from the diagram near the beginning of this document, |
| 207 | +the components that have no dependencies are `Dependency A.1/B.1`, `Dependency A.2`, |
| 208 | +`Dependency C.1`, and `Dependency C.2`. Because these do not depend on each other, the |
| 209 | +order in which they are processed does not matter, so we arbitrarily choose to process |
| 210 | +them in the order listed. As each of these folders is processed, it "leaves behind" the |
| 211 | +named CMake target(s) it exposes: `dependency-x-1`, `dependency-a-2`, and so on. Each of |
| 212 | +these named targets can be thought of as a new tool which has been put on our metaphorical |
| 213 | +craftsman's table; and now that we have these tools, we can use them to make *more* tools: |
| 214 | +`Dependency A`, `Dependency B`, `Dependency C`, and finally the `Root Project` itself. |
| 215 | + |
| 216 | +As this metaphor illustrates, the relative locations of the different components used in a |
| 217 | +build do not matter because components never interact with each other directly. This system |
| 218 | +is also indifferent to version numbers and even API-compatible implementation |
| 219 | +substitutions. The only requirement in order for a build such as this to succeed is that, |
| 220 | +at the time a component is "put on the table" to be processed, all the tools (CMake target |
| 221 | +names) on which it depends must already be available. Consequently, the implementation of |
| 222 | +the Babylon Native build system must simply enforce (1) that all the required dependencies |
| 223 | +are available and (2) that they are processed in the correct order. |
| 224 | + |
| 225 | +Because order matters, the order in which the component folders are processed also enforces |
| 226 | +constraints on the dependencies of the various components. No component can depend on |
| 227 | +another component which is processed after it. Consequently, `Dependencies` can only |
| 228 | +depend on other `Dependencies`; `Core` components can depend on other `Core` components as |
| 229 | +well as `Dependencies`; `Plugins` can depend on components in `Plugins`, `Core`, or |
| 230 | +`Dependencies`; and `Polyfills` can depend on components in any of they other three |
| 231 | +folders. (Theoretically, `Polyfills` could also take a dependency on other `Polyfills`, |
| 232 | +but as was described above, natively depending on a polyfill is not allowed.) Note |
| 233 | +that this build system is incapable of describing a circular dependency. |
| 234 | + |
| 235 | +In summary, the Babylon Native build system functions by traversing a laterally organized |
| 236 | +list of components grouped into component folders distinguished by category: |
| 237 | +`Dependencies`, `Core`, `Plugins`, and `Polyfills`. The build invokes each of these folders |
| 238 | +in turn. Each component folder contains its own `CMakeLists.txt` which determines the order |
| 239 | +in which components are processed. (In the case of `Dependencies`, this `CMakeLists.txt` |
| 240 | +also provides adaptation mechanisms for some external libraries.) This causes the build |
| 241 | +to invoke each of the components in turn. Each component contains a `CMakeLists.txt` that |
| 242 | +provides for its own build instructions, links to its dependencies by name, and exposes |
| 243 | +its own named CMake target to be linked to by other components or applications later in |
| 244 | +the build. In this way, the list of available CMake targets grows over time as the build |
| 245 | +traverses the list of components from beginning to end. Finally, when all components have |
| 246 | +been processed, the build system invokes the `Apps` folder, which creates the final targets |
| 247 | +that consume and assemble the various Babylon Native components to build the final |
| 248 | +executable program. |
| 249 | + |
| 250 | +This concludes the description of how the Babylon Native build system works within itself. |
| 251 | +For further discussion of how this can be modified, extended, or externally consumed, |
| 252 | +please refer to the documentation page about |
| 253 | +[Extending Babylon Native](Extending.md). |
0 commit comments