Skip to content

Embed license notices in the binary and trim the release archive - #14

Merged
dpleshakov merged 8 commits into
mainfrom
feat/third-party-licenses
Oct 6, 2026
Merged

dpleshakov merged 8 commits into
mainfrom
feat/third-party-licenses

Conversation

@dpleshakov

Copy link
Copy Markdown
Owner

The binary statically links the Go standard library and eight third-party modules whose licenses (MIT, BSD-3-Clause, Apache-2.0) require their text to accompany every binary copy; the archives carried only our own LICENSE. The texts are now embedded in the binary, as GitHub CLI does.

Changes

  • tfscli licenses prints the components built into the binary and the license text of each: tfscli, the Go standard library, and, in release builds, the third-party modules.
  • The third-party set is generated per platform at release time by a goreleaser build hook running go-licenses save (v2.0.1, a build tool, not in go.mod); each binary embeds its own platform's set.
  • Release archives unpack into tfscli-<version>-<os>-<arch>/ and hold only the binary, LICENSE, and the agent skill. config.example.json is removed.
  • make check runs go-licenses check for all six release platforms; the release workflow builds a snapshot before tagging and verifies every binary carries its licenses.

dpleshakov and others added 8 commits October 6, 2026 09:21
The binary statically contains third-party modules and the Go standard
library, whose licenses require their texts to accompany every binary
copy, and the archives carry only the project's own LICENSE. The plan
embeds the texts in the binary, as GitHub CLI does, so that they travel
with the binary itself, and trims the archive to what a user needs.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015w3YJNX9AzYUZq25WyQFDp
The binary statically contains the Go standard library and third-party
modules whose licenses require their texts to accompany every binary copy.
The texts are now embedded in the binary itself, as GitHub CLI does, so
that they travel with it even when it is taken out of the archive, and
tfscli licenses prints them.

The Go license is a committed copy, embedded in every build and kept equal
to the toolchain's by a test. The third-party set differs by platform, so
each release platform embeds its own directory through a file named for
it; the release build fills that directory, and a committed placeholder
beside the generated third-party/ keeps go:embed compiling everywhere
else. Such a build says that the third-party texts are only in the release
builds. The list of components comes from the directory names, since the
set is generated without a shell to capture a report.

The command reads neither the credential nor the config file. The two
tasks are committed together because the package alone would be unused.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015w3YJNX9AzYUZq25WyQFDp
goreleaser now installs go-licenses v2.0.1 before the builds, and each
build saves the licenses of its target's dependencies into the directory
internal/licenses embeds for that target. GOOS and GOARCH are set on the
hook, since the dependencies differ by platform: mousetrap is linked only
on Windows. Each target writes to its own directory because the builds run
in parallel. The tool is installed in a before hook rather than run with
go run in the build hook, which would build it for the target's platform
and fail to execute it.

The tasks file now counts eight third-party modules: the earlier nine
included tfscli itself.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015w3YJNX9AzYUZq25WyQFDp
The archive scattered six entries into whatever directory it was unpacked
in. It now unpacks into a directory named like the archive and holds only
what a user needs beside the binary: the project's LICENSE and the agent
skill, which the README tells the user to copy from it. The README and the
changelog are read on GitHub and on the release page, and the third-party
licenses travel inside the binary.

config.example.json is deleted rather than only dropped from the archive:
it held the same three lines the README shows, so nothing else needs it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015w3YJNX9AzYUZq25WyQFDp
make check now fails when a dependency linked into the binary is under a
license other than MIT, BSD-3-Clause, or Apache-2.0, so that a new
license is noticed on the pull request that brings it rather than at the
release. go-licenses runs through go run with a pinned version, which
needs nothing installed and leaves go.mod alone, so the tools make check
requires stay the same.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015w3YJNX9AzYUZq25WyQFDp
The README describes tfscli licenses and what a binary built from source
prints for it, and the archive section names the new contents and the
directory it unpacks into. The changelog records the command and the
change to the archives for users. The architecture records go-licenses as
a build tool, with the reasons the texts are embedded per platform rather
than shipped beside the binary or committed, and adds the licenses
package. CONTRIBUTING covers the new target and what the release and a
snapshot now do; the agent skill and CLAUDE.md name the command.

Closes docs/archive/2026-10-06-tasks-third-party-licenses.md

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015w3YJNX9AzYUZq25WyQFDp
The project owes no notice to itself, but whoever passes the binary on
owes one for tfscli, and now finds it in the binary like every other
notice. The license is a committed copy, which a test keeps equal to
LICENSE, rather than part of the generated set: every build carries it,
and it is not listed among the third-party modules.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015w3YJNX9AzYUZq25WyQFDp
make licenses checked only the platform it ran on, so a dependency linked
on Windows or macOS alone could bring a license outside the allowed list
unnoticed. tools/check-licenses.go now runs go-licenses check for each of
the six release platforms. It installs go-licenses for the platform it
runs on first: go run with a target's GOOS would build the tool for that
target, and setting GOOS in a make recipe would need a Unix shell.

Nothing verified that a release binary actually carries its licenses: a
build hook that generated nothing would have shipped binaries saying the
texts exist only in release builds. The release workflow now builds a
snapshot before it pushes, checks that every platform's set holds
spf13/cobra, which every platform links, and that the linux/amd64 binary
prints it, so such a failure stops the release before the tag. The real
release builds the same commit with the same tools and modules.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015w3YJNX9AzYUZq25WyQFDp
@dpleshakov
dpleshakov merged commit 8e33687 into main Oct 6, 2026
1 check passed
@dpleshakov
dpleshakov deleted the feat/third-party-licenses branch October 6, 2026 06:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant