Computing History · Open Source History

Complying With Source Obligations When Shipping Binaries

What counts as conveying a copy, how corresponding source is defined for a compiled artifact including its build scripts, and how a build can archive the exact sources and licenses that a shipped binary implies.

Binary distribution is where copyleft stops being a reading exercise. Everything before this point concerns which license applies to which source file; the obligation that matters commercially attaches to the artifact that leaves the building. Its trigger is narrow and worth stating exactly: running a program, modifying it privately, and serving it over a network all sit outside the source obligations of the GPL versions 2 and 3, while handing a copy to somebody does not. A container image, a firmware blob, an appliance, a game disc and an installer all convey copies, and each conveyed copy of a GPL-covered work carries the source obligation for that work.

The service case has a deliberate exception. The Affero GPL adds a clause requiring that users interacting with a modified version over a network be offered the corresponding source, which is why a service operator who modifies an AGPL program owes source to its users while an operator of the same code under GPL version 3 does not. Projects adopt it to remove that gap, and projects building on it can find themselves owing source for a deployment that never shipped a file.

Corresponding source is defined by the build, not the repository

The obligation is not “publish the upstream tarball”. What must accompany the artifact is the source needed to produce the artifact. Version 2 phrases this as the complete corresponding machine-readable source code for the work, and its list of exclusions points at components normally distributed with the operating system, such as the compiler and the kernel, which the distributor is not expected to re-publish. Version 3 makes the definition explicit and broader: corresponding source includes all the source needed to generate, install and, for an executable, run the object code, the scripts used to control compilation and installation, the interface definition files associated with the work, and the source for shared libraries and dynamically linked subprograms that the object code is designed to require, with an exception for system libraries.

Two consequences follow. The first is that build scripts are in scope. A release process that assembles an artifact with a makefile in a separate repository, a patch queue, and a templating step has to publish those inputs, not only the upstream release they start from. The second is that the definition is version-dependent, so an archive designed against version 2 can be short of what version 3 requires; building the archive to the broader definition is the only way to keep one process honest across both.

Distribution itself has a menu of accepted forms. Version 2 permits shipping the machine-readable source alongside the binary, shipping a written offer valid for at least three years to supply the source at no more than the cost of the transfer, or passing through an offer that the distributor received, with the written-offer route restricted to noncommercial distribution. Version 3 keeps a comparable set and adds the option of offering the object code and the corresponding source from the same place on a network, which is how most distributors satisfy the obligation now. Version 3 also adds a requirement that does not exist in version 2: for software conveyed inside a consumer device, the distributor must supply the information needed to install a modified version on that device, such as signing keys or documented bypasses. That clause is why some device makers avoid version 3 code and why others publish installation instructions they would prefer not to.

Weaker-scope licenses shift the obligation rather than removing it. Distributing a binary linked against an LGPL library requires making the library’s source available and enabling the recipient to relink the larger work against a modified library, which dynamic linking against a replaceable shared object satisfies and static linking does not. Distributing anything under a permissive license still requires that the copyright notice, the license text and, for Apache-licensed components, the NOTICE file travel with the copies.

Making the archive a build output

The reliable pattern treats the compliance archive as an artifact of the same build that produced the binary, rather than as a document assembled later. The ref that was built, the local modifications, the resolved dependency versions, the base image digest, and the scripts that drove compilation all belong in a directory that is archived, hashed, and stored alongside the binary it corresponds to.

set -eu
out="release/app-$(date -u +%Y%m%d)"
mkdir -p "$out/sources"
git -C . archive --format=tar --prefix=app/ "$BUILD_REF" | tar -x -C "$out/sources"
cp -r patches cmake "$out/sources/"                   # scripts used to compile
sha256sum app > "$out/artifact.sha256"                 # bind archive to artifact
tar -czf "$out.tar.gz" "$out"

The SBOM belongs in the same place, and it has to be produced from the resolved graph rather than from the declared manifest. A manifest lists the direct dependencies a human wrote; the resolved graph is what the linker actually consumed, including transitive components pulled in by a lock file or by a base image. Generating the inventory from the resolved graph at build time, with licenses recorded per component as expressions rather than as prose, is what lets a policy check run against the exact artifact rather than against an intention. The representation used for those license fields is described in license expressions.

Binding the archive to what was shipped

The archive only does its job if a recipient can move from the artifact in hand to the exact sources behind it. That means the release record carries the artifact’s digest together with the location of the archive and the inventory, rather than leaving the correspondence to a naming convention. A build that publishes the source archive to the same place as the binary, and links the two from the same release entry, satisfies the obligation in the form a recipient can actually use; a source tarball on an internal file share that a customer cannot reach has the content and not the delivery.

Usefulness is the second half of correspondence, and it is a build decision rather than a legal one. An archive that requires a toolchain, a base image, or a generator that no longer exists anywhere is technically the source and practically not, since nobody can build the artifact from it. Retaining the container image digest used by the build, the compiler version, and any code generator that was not itself part of the tree is what makes the archive reproducible years later. Retention then follows the product rather than the release: the written offer in the license sets a duration for one delivery mechanism, while a distributor supporting a long-lived product needs the archive for as long as the product is supported, which is normally longer.

The operational conclusion is that source availability is a gate on the release rather than a task after it. If publishing the corresponding source, the notices and the inventory is on the same checklist as signing the artifact, and the same build produces all of them from the same build, then a release that ships without them is a release that did not pass its own checks. This is also what makes a compliance notice cheap to answer: the material already exists, and the answer is a link rather than an archaeology project.

Where the build process leaks

The recurring leaks are all versions of one mistake: the archive corresponds to something other than the binary. A patch applied to the working tree after the source snapshot was taken produces an archive that does not rebuild the shipped file. A build step that downloads a binary component, a codec or a toolchain blob, leaves the obligation unaddressed because the download is not in the tree. A release built twice from the same tag with different base images produces two artifacts with one archive. A build script kept in a different repository is omitted because it did not look like source.

The legal failure beneath those is a scope error, in the direction of imagining that only modified files matter. The obligation covers corresponding source for the whole object code, not just the parts a distributor edited. A container image sharpens the point: the artifact is the union of its layers, so whoever distributes the image distributes the base layer’s GPL components and inherits their source obligations, regardless of which team wrote which layer and regardless of how the base image was obtained.

Two boundary questions sit adjacent. Whether a component is linked into a work or merely aggregated with it decides whether the copyleft condition reaches the rest of the artifact, and the answer depends on copyright’s derivative-work doctrine rather than on the mechanics of the link, as discussed in how copyleft travels through derivative works. Whether an incompatible pair can be shipped together at all is a compatibility question, and when the answer is no, the resolution is to remove or replace the component or to change a license with the consent of its holders, the process covered in relicensing a mature project. What the license permits is only half of the answer; the other half is whether the recipient’s own permissions remain intact.

References

  1. GNU General Public License, version 2 — Free Software Foundation
  2. GNU General Public License, version 3 — Free Software Foundation
  3. GNU Affero General Public License, version 3 — Free Software Foundation
  4. SPDX Specification, version 2.3 — SPDX Project, Linux Foundation
  5. CycloneDX Bill of Materials Specification — OWASP Foundation