There are a number of facts that we accept as generally true of software that, considered together, support the conclusion that applications must be given the ability to select a specific release series for their dependencies.

First, we understand that software is typically versioned, and that a sequence of releases has a beginning and and end.

Diagram of an application release

Furthermore, we understand that applications use shared libraries and other types of dependencies, which share the same basic characteristics. Shared libraries are also versioned, with a finite maintenance window.

Diagram of an application release's dependencies

In addition to those characteristics, an application’s dependencies share one more characteristic, which is that the dependencies were released before the application.

Diagram of an application release's dependencies' age

Dependencies aren’t meaningfully different from applications. They may also have dependencies, and when they do, those dependencies will typically have been released earlier, in turn.

Diagram of an application release's dependencies' dependencies

And, like applications, dependencies typically follow stable release practices. So, for example, at any given time, the developers of a hypothetical “libEtc” may be supporting multiple releases.

Diagram of a dependency's release cycle

Which brings us to build system requirements. At a given point in time, there may be applications that rely specifically on libEtc’s 1.2 release series. Even if the 1.3 series is intended to be compatible, there might be changes in undefined behavior which are not compatible with that application.

At the same time, there may be applications that require libEtc’s 2.0 release series.

Diagram of a an application and dependencies at release branch time

In some cases, incompatible versions of a single component can be installed in parallel, using distinct path prefixes or distinct file names in the same prefixes. But that’s not always the case. Some languages don’t make parallel installation simple or idiomatic. Even fewer do so for parallel installation of components that are intended to be backward compatible upgrades, like libEtc 1.2 and 1.3.

For those reasons, many applications will bundle the specific versions of components against which they have been tested, and with which they are known to be compatible. Bundling is not merely easiest, but necessary in many cases.

Applications might bundle their dependencies in a shared prefix (i.e. “/opt/…” in FHS), or they may bundle them by shipping as a container image (including a Flatpak image). But in order to support the build process for those applications, a build system must supply the versions they need, which means that a build system must not flatten component release streams at build time.