Skip to content

Commit 2724324

Browse files
Build system and component architecture documentation (#207)
* Initial README changes. * First section of build system documentation and other minor changes. * Added components section. * Added final section about the CMake mechanisms. * Documentation for extending Babylon Native. * Update Documentation/Extending.md Co-Authored-By: Gary Hsu <[email protected]> * Fixed a few typos, phrasing problems, and grammatical mistakes. * Make Native API link work * Fixed typo Co-authored-by: Gary Hsu <[email protected]>
1 parent 1c17bc5 commit 2724324

File tree

5 files changed

+505
-12
lines changed

5 files changed

+505
-12
lines changed

Documentation/BuildSystem.md

Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
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+
![VerticalDependencyManagement](Images/VerticalDependencyManagement.jpg)
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+
![LateralDependencyManagement](Images/LateralDependencyManagement.jpg)
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

Comments
 (0)