Larger software systems and products are commonly assembled from many software components. Each component is a collection of programs. Components frequently re-use other components.
Each component usually corresponds to one or more packages. A package is typically an archive that contains the programs and information about the component - its name, what it does, who wrote it, and possibly the license. A dependent component or “dependency” may be required by this component. In formal terms, a package is the set of software programs and files that are distributed or used for a given component.
A program may require one or more other programs to run (the "dependencies"). The dependencies can be recursive with potentially very deep relationships. Sometimes the dependency is only for testing or "building/compiling" the program - i.e. a development or "dev" dependency. Sometimes it is needed to run the program; other times it can be optional to provide extra features.
Some dependencies for a program may be rather generic and implied. At a high level, many programs have a dependency on the operating system, the application server, or the database server. This type of dependency is often not stated explicitly. For example, a Java program requires a Java Virtual Machine (JVM) to run and the fact that a program is written in Java implies this. This dependency is not explicitly stated by most Java packages because most Java programs will not depend on a specific JVM version.
Dependency management is the approach practiced by software programmers to specify, provision, install, update and generally manage the set of dependent programs that their product or application relies on.
Managing dependencies is evolving with different tools available for different languages and frameworks. There are several tools and public package repositories that help programmers deal with a large volume of dependencies. Many of these dependencies are for free, libre and open source-licensed (FLOSS) components and their packages.
Each operating system / language / framework tends to have its own (eco) system for FLOSS components:
These systems are common in Linux distros, Java/Maven, JavaScript, Ruby, and Python. There are millions of these FLOSS packages readily available for download and installation. Some of these repositories and tools include:
Dependencies are often weakly defined and variable: A dependency statement may include a range of possible versions for a dependent package and may state if a dependency is "required" or "optional." Package systems compute (aka "resolve") a list of concrete dependencies when you provision packages using these definitions.
The dependency relationship between two programs may be more or less "intimate":
Programs are often compiled: the text of the programs source code is transformed in a binary code that the machine can understand and execute, i.e. C, C++, Java, Go.
But not always: some programming languages are “interpreted” from the source code when you run them and do not require pre-compilation (or are compiled on the fly when you run them). For instance JavaScript, Python, Ruby or Perl programs do not require compilation, though such programs may be "compressed" or minified or pre-compiled.
With the compilation process, several programs can be combined into larger (binary) programs. Combining programs together this way is often called "static linking" when programs are "merged" into a larger binary program.
Many software licenses have different requirements for source code and for binaries (BSD, MIT). Copyleft-licensed programs (using licenses such as the GPL or the LGPL) may require source code redistribution for other programs that use them depending on how programs are combined and depend on each other. Limited Copyleft and Copyleft licenses impose some constraints on how other (proprietary or not) programs can be combined or interact with Copyleft-licensed programs. For instance, the LGPL explicitly discusses static vs. dynamic linking. These are concepts originally defined for compiled languages such as C and C++.
These concepts do not always translate well to other languages. Technical and legal interpretation is usually required to translate these C/C++ oriented concepts to other languages.
Permissive licenses typically impose few constraints on how you can combine programs.
For Copyleft licenses, the interactions and dependencies between proprietary- or non-Copyleft- licensed) programs and Copyleft-licensed programs determine when and how the source code redistribution conditions of the GPL or LGPL are triggered.
Remember that the relationship between programs and their dependencies can be more or less intimate: Arm's length, Handshake, or Merged. Copyleft licenses may impact proprietary or non-Copyleft-licensed programs based on the nature of the dependencies and interaction between programs:
Arm's length is similar to "independent process" or "command line." Handshake is similar to "dynamic linking.” Merged is similar to "static linking."
Relationships may flow through a dependency chain and the related license conditions may also follow that dependency chain. What if a proprietary program depends on a program that depends on a program that depends on a program that is GPL-licensed? It depends!
Eventually the GPL license may flow up or down to a proprietary program for a "Handshake" or "Merged" style of relationship, unless the relevant license has an exception to stop this flow (e.g. such as the Linux User Space exception to the GPL license) or the nature of the interaction changes through this chain (such as when a dependent program is used at "Arm's length").
The recommended approach to properly handle dependencies -- whether you write proprietary or FLOSS-licensed code:
Using these elements, you can define a policy for which dependencies may be acceptable or not in your context and document your corresponding obligations for the use of FLOSS programs.
Like this blog post? Share it with your friends! Subscribe to nexB’s newsletter for updates on our blog, product, events, and more!