Project: MongoDB (App Definition and Development / Database)
File: build_system_reference.md

Libdeps is a subsystem within the build, which is centered around the LIBrary DEPendency graph. It tracks and maintains the dependency graph as well as lints, analyzes and provides useful metrics about the graph. The libdeps subsystem is divided into several stages, described in order of use as follows. During the build, the SConscripts are read and all the library relationships are setup via the `LIBDEPS` variables. Some issues can be identified early during processing of the SConscripts via the build-time linter. Most of these will be style and usage issues which can be realized without needing the full graph. This component lives within the build and is executed through the SCons emitters added via the libdeps subsystem. For more advanced analysis and linting, a full graph is necessary. The build target `generate-libdeps-graph` builds all libdeps and things which use libdeps, and generates the graph to a file in graphml format. The libdeps analyzer module is a python library which provides and Application Programming Interface (API) to analyze and lint the graph. The library internally leverages the networkx python module for the generic graph interfaces. The libdeps analyzer module is used in the libdeps Graph Analysis Command Line Interface (gacli) tool and the libdeps Graph Visualizer web service. Both tools read in the graph file generated from the build and provide the Human Machine Interface (HMI) for analysis and linting. The variables include several types of lists to be added to libraries per a SCons builder instance: | Variable | Use | | | -- | | `LIBDEPS` | transitive dependencies | | `LIBDEPS_PRIVATE` | local dependencies | | `LIBDEPS_INTERFACE` | transitive dependencies excluding self | | `LIBDEPS_DEPENDENTS` | reverse dependencies | | `PROGDEPS_DEPENDENTS` | reverse dependencies for Programs | `LIBDEPS` is the 'public' type, such that libraries that are added to this list become a dependency of the current library, and also become dependencies of libraries which may depend on the current library. This propagation also includes not just the libraries in the `LIBDEPS` list, but all `LIBDEPS` of those `LIBDEPS` recursively, meaning that all dependencies of the `LIBDEPS` libraries, also become dependencies of the current library and libraries which depend on it. `LIBDEPSPRIVATE`_ should be a list of libraries which creates dependencies only between the current library and the libraries in the list. However, in static linking builds, this will behave the same as `LIBDEPS` due to the nature of static linking. `LIBDEPSINTERFACE` is very similar to `LIBDEPS`, however it does not create propagating dependencies for the libraries themselves in the `LIBDEPSINTERFACE` list. Only the dependencies of those `LIBDEPS_INTERFACE` libraries are propagated forward. `LIBDEPSDEPENDENTS` are added to libraries which will force themselves as dependencies of the libraries in the supplied list. This is conceptually a reverse dependency, where the library which is a dependency is the one declaring itself as the dependency of some other library. By default this creates a `LIBDEPSPRIVATE` like relationship, but a tuple can be used to force it to a `LIBDEPS` like or other relationship. `PROGDEPSDEPENDENTS` are the same as `LIBDEPSDEPENDENTS`, but intended for use only with Program builders. The `LIBDEPSTAGS` variable is used to mark certain libdeps for various

Some `LIBDEPSTAGS` are used to mark certain libraries for `LIBDEPSTAGEXPANSIONS` variable which is used to create a function which can expand to a string on the command line. Below is a table of available `LIBDEPS` tags: | Tag | Description | | - | -- | -- | - | | `illegalcyclicorunresolveddependencies_allowlisted` | SCons subst expansion tag to handle dependency cycles | | `init-no-global-side-effects` | SCons subst expansion tag for causing linkers to avoid pulling in all symbols | | `lint-public-dep-allowed` | Linting exemption tag exempting the `lint-no-public-deps` tag | | `lint-no-public-deps` | Linting inclusion tag ensuring a libdep has no `LIBDEPS` declared | | `lint-allow-non-alphabetic` | Linting exemption tag allowing `LIBDEPS` variable lists to be non-alphabetic | | `lint-leaf-node-allowed-dep` | Linting exemption tag exempting the `lint-leaf-node-no-deps` tag | | `lint-leaf-node-no-deps` | Linting inclusion tag ensuring a libdep has no libdeps and is a leaf node | | `lint-allow-nonlist-libdeps` | Linting exemption tag allowing a `LIBDEPS` variable to not be a list | `lint-allow-bidirectional-edges` | Linting exemption tag allowing reverse dependencies to also be a forward dependencies | | `lint-allow-nonprivate-on-deps-dependents` | Linting exemption tag allowing reverse dependencies to be transitive | | `lint-allow-dup-libdeps` | Linting exemption tag allowing `LIBDEPS` variables to contain duplicate libdeps on a given library | | `lint-allow-program-links-private` | Linting exemption tag allowing `Program`s to have `PRIVATE_LIBDEPS` | This tag should not be used anymore because the library dependency graph has been successfully converted to a Directed Acyclic Graph (DAG). Prior to this accomplishment, it was necessary to handle cycles specifically with platform specific options on the command line. Adding this flag to a library turns on platform specific compiler flags which will cause the linker to pull in just the symbols it needs. Note that by default, the build is configured to pull in all symbols from libraries because of the use of static initializers, however if a library is known to not have any of these initializers, then this flag can be added for some performance improvement. The libdeps linter features automatically detect certain classes of LIBDEPS usage errors. The libdeps linters are implemented as build-time linting and post-build linting procedures to maintain order in usage of the libdeps tool and the builds library dependency graph. You will need to comply with the rules enforced by the libdeps linter, and fix issues that it raises when modifying the build scripts. There are exemption tags to prevent the linter from blocking things, however these exemption tags should only be used in extraordinary cases, and with good reason. A goal of the libdeps linter is to drive and maintain the number of exemption tags in use to zero. There are a number of existing issues that need to be addressed, but they will be addressed in future tickets. In the meantime, the use of specific strings in the LIBDEPSTAGS variable can allow the libdeps linter to skip certain issues on given libraries. For example, to have the linter skip enforcement of the lint rule against bidirectional edges for "somelibrary": ``` env.Library( target=some_library ... LIBDEPS_TAGS=[lint-allow-bidirectional-edges] ) ``` If there is a build-time issue, the build will fail until it is

This linting feature will be on by default and takes about half a second to complete in a full enterprise build (at the time of writing this), but can be turned off by using the --libdeps-linting=off option on your SCons invocation. The current rules and there exemptions are listed below: A 'Program' can not link a non-public dependency, it can only have LIBDEPS links. ``` env.Program( target=some_program, ... LIBDEPS=[lib1], # OK LIBDEPS_PRIVATE=[lib2], # This is a Program, BAD ) ``` A Program can not be linked into anything else, and there for the transitiveness does not apply. A default value of LIBDEPS was selected for consistency since most Program's were already doing this at the time the rule was created. 'lint-allow-program-links-private' on the target node A 'Node' can only directly link a given library once. ``` env.Library( target=some_library, ... LIBDEPS=[lib1], # Linked once, OK LIBDEPS_PRIVATE=[lib1], # Also linked in LIBDEPS, BAD LIBDEPS_INTERFACE=[lib2, 'lib2'], # Linked twice, BAD ) ``` Libdeps will ignore duplicate links, so this rule is mostly for consistency and neatness in the build scripts. 'lint-allow-dup-libdeps' on the target node A 'Node' which uses LIBDEPS_DEPENDENTS or PROGDEPS_DEPENDENTS can only have LIBDEPS_PRIVATE links. ``` env.Library( target=some_library, ... LIBDEPS_DEPENDENTS=['lib3'], LIBDEPS=[lib1], # LIBDEPS_DEPENDENTS is in use, BAD LIBDEPS_PRIVATE=[lib2], # OK ) ``` The node that the library is using LIBDEPSDEPENDENTS or PROGDEPSDEPENDENT to inject its dependency onto should be conditional, therefore there should not be transitiveness for that dependency since it cannot be the source of any resolved symbols. 'lint-allow-nonprivate-on-deps-dependents' on the target node A 'Node' can not link directly to a library that uses LIBDEPS_DEPENDENTS or PROGDEPS_DEPENDENTS. ``` env.Library( target='other_library', ... LIBDEPS=['lib1'], # BAD, 'lib1' has LIBDEPS_DEPENDENTS env.Library( target=lib1, ... LIBDEPS_DEPENDENTS=['lib3'], ) ``` A library that is using LIBDEPSDEPENDENTS or PROGDEPSDEPENDENT should only be used for reverse dependency edges. If a node does need to link directly to a library that does have reverse dependency edges, that indicates the library should be split into two separate libraries, containing its direct dependency content and its conditional reverse dependency content. 'lint-allow-bidirectional-edges' on the target node All libdeps environment vars must be assigned as lists. ``` env.Library( target='some_library', ... LIBDEPS='lib1', # not a list, BAD LIBDEPS_PRIVATE=['lib2'], # OK ) ``` Libdeps will handle non-list environment variables, so this is more for consistency and neatness in the build scripts. 'lint-allow-nonlist-libdeps' on the target node Libdeps with the tag 'lint-leaf-node-no-deps' shall not link any libdeps. ``` env.Library( target='lib2', ... LIBDEPS_TAGS=[ 'lint-leaf-node-allowed-dep' ] ) env.Library( target='some_library', ... LIBDEPS=['lib1'], # BAD, should have no LIBDEPS LIBDEPS_PRIVATE=['lib2'], # OK, has exemption tag LIBDEPS_TAGS=[ 'lint-leaf-node-no-deps' ] ) ``` The special tag allows certain nodes to be marked and programmatically checked that they remain lead nodes. An example use-case is when we want to make sure certain nodes never link mongodb code. 'lint-leaf-node-allowed-dep' on the exempted libdep 'lint-leaf-node-no-deps' on the target node Libdeps with the tag 'lint-no-public-deps' shall not link any libdeps. ``` env.Library( target='lib2', ... LIBDEPS_TAGS=[ 'lint-public-dep-allowed' ] ) env.Library( target='some_library', ... LIBDEPS=[ 'lib1' # BAD 'lib2' # OK, has exemption tag ], LIBDEPS_TAGS=[ 'lint-no-public-deps' ] ) ``` The special tag allows certain nodes to be marked and programmatically checked that they do not link publicly. Some nodes such as mongod_main have special requirements that this programmatically checks. 'lint-public-dep-allowed' on the exempted libdep 'lint-no-public-deps' on the target node Libdeps shall be sorted alphabetically in LIBDEPS lists in the SCons files. ```

target='lib2', ... LIBDEPS=[ '$BUILD/mongo/db/d', # OK, $ comes before c 'c', # OK, c comes before s 'src/a', # BAD, s should be after b 'b', # BAD, b should be before c ] ) ``` Keeping the SCons files neat and ordered allows for easier Code Review diffs and generally better maintainability. 'lint-allow-non-alphabetic' on the exempted libdep The libdeps linter also has the `--libdeps-linting=print` option which will perform linting, and instead of failing the build on an issue, just print and continue on. It will also ignore exemption tags, and still print the issue because it will not fail the build. This is a good way to see the entirety of existing issues that are exempted by tags, as well as printing other metrics such as time spent linting. The dependency graph can be analyzed post-build by leveraging the completeness of the graph to perform more extensive analysis. You will need to install the libdeps requirements file to python when attempting to use the post-build analysis tools: ``` python3 -m poetry install --no-root --sync -E libdeps ``` The command line interface tool (gacli) has a comprehensive help text which will describe the available analysis options and interface. The visualizer tool includes a GUI which displays the available analysis options graphically. These tools will be briefly covered in the following sections. To generate the full graph, build the target `generate-libdeps-graph`. This will build all things involving libdeps and construct a graphml file representing the library dependency graph. The graph can be used in the command line interface tool or the visualizer web service tool. The minimal set of required SCons arguments to build the graph file is shown below: ``` python3 buildscripts/scons.py --link-model=dynamic --build-tools=next generate-libdeps-graph --linker=gold --modules= ``` The graph file by default will be generate to `build/opt/libdeps/libdeps.graphml` (where `build/opt` is the `$BUILD_DIR`). Below is a basic example of usage of the libdeps analyzer API: ``` import libdeps libdepsgraph = libdeps.graph.loadlibdeps_graph('path/to/libdeps.graphml') listofanalysistorun = [ libdeps.analyzer.NodeCounter(libdeps_graph), libdeps.analyzer.DirectDependencies(libdeps_graph, node='path/to/library'), ] analysisresults = libdeps.graph.LibdepsGraphAnalysis(listofanalysisto_run) libdeps.analyzer.GaPrettyPrinter(analysisafterrun).print() ``` Walking through this example, first the graph is loaded from file. Then a list of desired Analyzer instances is created. Some example analyzer classes are instantiated in the example above, but there are many others to choose from. Specific Analyzers have different interfaces and should be supplied an argument list corresponding to that analyzer. Note: The graph file will contain the build dir that the graph data was created with and it expects all node arguments to be relative to the build dir. If you are using the libdeps module generically in some app, you can extract the build dir from the libdeps graph and append it to any generic library path. Once the list of analyzers is created, they can be used to create a LibdepsGraphAnalysis instance, which will upon instantiation, run the analysis list provided. Once the instance is created, it contains the results, and optionally can be fed into different printer classes. In this case, a human readable format printer called GaPrettyPrinter is used to print to the console. The command line interface tool can be used from the command line to run analysis on a given graph. The only required argument is the graph

The default with no args will run all the counters and linters on the graph. Here is an example output: ``` (venv) Apr.20 02:46 ubuntu[mongo]: python buildscripts/libdeps/gacli.py --graph-file build/cached/libdeps/libdeps.graphml Loading graph data...Loaded! Graph built from git hash: 1358cdc6ff0e53e4f4c01ea0e6fcf544fa7e1672 Graph Schema version: 2 Build invocation: "/home/ubuntu/venv/bin/python" "buildscripts/scons.py" "--variables-files=etc/scons/mongodbtoolchainstablegcc.vars" "--cache=all" "--cache-dir=/home/ubuntu/scons-cache" "--link-model=dynamic" "--build-tools=next" "ICECC=icecc" "CCACHE=ccache" "-j200" "--cache-signature-mode=validate" "--cache-debug=-" "generate-libdeps-graph" Nodes in Graph: 867 Edges in Graph: 90706 Direct Edges in Graph: 5948 Transitive Edges in Graph: 84758 Direct Public Edges in Graph: 3483 Public Edges in Graph: 88241 Private Edges in Graph: 2440 Interface Edges in Graph: 25 Shim Nodes in Graph: 20 Program Nodes in Graph: 136 Library Nodes in Graph: 731 LibdepsLinter: PUBLIC libdeps that could be PRIVATE: 0 ``` Use the `--help` option to see detailing information about all the available options. The graph visualizer tools starts up a web service to provide a frontend GUI for navigating and examining the graph files. The Visualizer uses a Python Flask backend and React/Redux Javascript frontend. For installing the dependencies for the frontend, you will need node >= 12.0.0 and npm installed and in the PATH. To install the dependencies navigate to directory where package.json lives, and run: ``` cd buildscripts/libdeps/graphvisualizerweb_stack && npm install ``` Alternatively if you are on linux, you can use the setupnodeenv.sh script to automatically download node 12 and npm, setup the local environment and install the dependencies. Run the command: ``` source buildscripts/libdeps/graphvisualizerwebstack/setupnode_env.sh install ``` Assuming you are on a remote workstation and using defaults, you will need to make ssh tunnels to the web service to access the service in your local browser. The frontend and backend both use a port (this case 3000 is the frontend and 5000 is the backend), and the default host is localhost, so you will need to open two tunnels so the frontend running in your local web browser can communicate with the backend. If you are using the default host and port the tunnel command will look like this: ``` ssh -L 3000:localhost:3000 -L 5000:localhost:5000 ubuntu@workstation.hostname ``` Next we need to start the web service. It will require you to pass a directory where it will search for `.graphml` files which contain the graph data for various commits: ``` python3 buildscripts/libdeps/graph_visualizer.py --graphml-dir build/opt/libdeps ``` The script will launch the backend and then build the optimized production frontend and launch it. You can supply the `--debug` argument to work in development load which starts up much faster and allows real time updates as files are modified, with a small cost to performance on the frontend. Other options allow more configuration and can be viewed in the `--help` text. After the server has started up, it should notify you via the terminal that you can access it at http://localhost:3000 locally in your browser. The `MONGOGITHASH` SCons variable controls the value of the git hash which will be interpolated into the build to identify the commit currently being built. If not overridden, this defaults to the git hash of the current commit. The `MONGO_VERSION` SCons variable controls the value which will be interpolated into the build to identify the version of the software currently being built. If not overridden, this defaults to the result of `git describe`, which will use the local tags to derive a version.