2 Answers
2

Software dependencies: A not-too-technical introduction

Summary

Programs depend on other programs, often with many deep levels of indirection.

Million of packaged FLOSS programs are available through public repositories for easy dependencies download and installation.

How dependent programs relate to and interact with each other can be more or less intimate.

Each program's license FLOSS requirements and obligations vary based on the relationship and interaction between dependent.

You need to know:

The whole chain of program or package dependencies

The purpose and use of each program or package in that chain (test, tool, runtime)

Which dependent are shipped and redistributed with your product, application or library vs. which may be installed by your user

The license of each dependency in this chain

How software is built

Larger software systems and products are commonly assembled from software components. Each component is some collection of programs. And components frequently re-use other components.

Each component is usually delivered as a package. A package is typically an archive that contains the programs. A package also commonly contains information about the component name & version, what it is about, who wrote it, sometimes its license, etc. And also what other components may be required by this component: this is called a dependent component or a "dependency". In formal terms, I would define a package as the set of exact software programs and files that are distributed or used for a given component.

Dependencies

A program may require one or more other programs to run (the "dependency-ies"). The dependencies can be recursive potentially creating 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 or sometimes it can be optional to provide extra features.

Some dependencies for a program may be rather generic and sometimes implied. At a top level many programs can have a dependency on the operating system, an application server, a database server, etc. These type of dependencies are often not stated explicitly. For example a Java program requires a Java Virtual Machine (JVM) to run and the fact a program is written in Java implies this and is not explicitly stated by most Java packages as many/most Java programs will not depend on a specific version of that JVM.

Dependency management is the approach practiced by software programmers to specify, provision/download, install, update and generally manage the set of dependent code that their product or application relies on.

Managing dependencies is evolving to be rather sophisticated and several tools and public package repositories are helping programmers dealing with eventually large volume of dependencies. A large number of these dependencies are for free, libre and open source-licensed (FLOSS) components and their packages.

Package dependencies

Each operating system / language / framework tends to have its own (eco) system for FLOSS components:

Documenting dependencies in one or more configuration or "meta" files,

Downloading (aka. provisioning) the dependent components from a public FLOSS or private repository of pre-built or pre-packaged programs.

This provisioning may occur at build-time or run-time; for instance when JavaScript code is fetched from a remote repository or CDN when you load a web page.

These systems are common in Linux distros, Java/Maven, JavaScript, Ruby, Python. And there are millions of these FLOSS packages readily available for download and installation.

Some of these repositories and tools include:

JavaScript - NPMs for NodeJS,

Ruby - GEMs from RubyGems,

Python - Wheels from Pypi,

Java - JARs from Maven,

Linux packages such as RPM and Debian,

and many more.

Dependencies are often weakly defined and variable: A dependency statement may include a range of possible versions for a dependent package and if a dependecy 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":

At arm's length, when the programs are entirely separated and may only communicate by writing or reading files, or by network interaction

At a handshake level - where the programs call each other's subroutines (aka "functions") directly and exchange data directly

Or extremely intimate when merged or combined into a single larger (executable) program

Program dependencies: programs can be compiled and combined

Often programs are compiled: the text of the programs source code is transformed in a binary code that the machine can understand and execute. For instance programming languages such as C, C++, Java, Go require this "compilation".

But not always: some programming languages are executed directly 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 code does not require ahead-of-time compilation, though it is sometimes "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 things are "merged" into a larger binary program.

Free, libre and open source licensing and dependencies

Many software licenses have different requirements for source code and binaries (BSD, MIT), have specific terms about the implication of how programs are combined and depend on each other (L/GPL). Limited Copyleft and Copyleft licenses impose some constraints on how other (proprietary or not) programs can be combined or interact with copyleft code. For instance, the LGPL explicitly discuss static vs. dynamic linking. These are concepts originally defined in compiled languages such as C and C++.

They do not always translate well to other "dynamic" languages and this require some adjustments to apply these terms in these other language contexts.

Permissive licenses typically impose few constraints on how you can combine programs.

For Copyleft licenses, how (proprietary- or non-Copyleft- licensed) programs and Copyleft-licensed programs are used together, how they depend and interact with each other is the essence of what triggers the Copyleft clauses of the GPL and LGPL.

Remember that the relationship between programs and their dependencies can be more or less intimate: Arm's length, Handshake, Merged. Copyleft licenses may impact proprietary or non-copyleft code based on the nature of the dependencies and interaction between programs:

Any Handshake or Merged relationship with a GPL-licensed program implies that GPL becomes the primary license for that combined program

Any Merged relationship with LGPL-licensed programs implies that LGPL becomes the primary license for that combined program

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 the Dependency chains and the related licensing requirement may also follow through.
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!

What is the type of relationships at each step?

What is the license of the intermediate programs?

Eventually the GPL license may flow up or down to the proprietary program for an "Handshake" or "Merged" style of relation, unless some licenses have exception to stop this flow (e.g. such as the Linux in user space exception to the GPL license) or the nature of the interactions changes through this chain. (such as when a dependent program is used at "Arm's length").