Notcurses-Native.git | scripts/ci/ | emit-third-party-kit.sh


#!/usr/bin/env bash # Write the licensing kit into a pack: THIRD-PARTY.md at the pack # root plus a LICENSES/ directory holding the full text of every # licence that pack's contents are under. # # usage: emit-third-party-kit.sh [bundle-dir] # # Both are generated from resources/third-party.json so the document # and the gate can never disagree — scripts/ci/audit-third-party.sh # reads the same file, and it runs immediately after this script does, # over the pack this script has just added files to. # # Why this ships INSIDE the archive rather than only living in the # repository: the archive is what a user actually receives. The # permissive licences in it (BSD-2, BSD-3, MIT, X11, Zlib) all require # their copyright notice and licence text to accompany a binary # redistribution, and the copyleft ones (FFmpeg's LGPL-2.1, # libunistring's LGPL-3.0, and libiconv's LGPL-2.1 in the Windows # packs) require the recipient be told of their rights and where the # source is. A file in a git repository the user # has never visited discharges none of that; a file next to the .so # they just downloaded does. # # PORTABILITY: runs under GitHub's macOS images, where `shell: bash` # is /bin/bash 3.2. No `mapfile`, no `declare -A`, and array # expansions are guarded for `set -u`. Needs jq (see the note in # audit-third-party.sh). set -euo pipefail PLATFORM="${1:-}" BUNDLE_DIR="${2:-bundle}" case "$PLATFORM" in macos|linux|windows) ;; *) echo "usage: $0 [bundle-dir]" >&2 echo " (got '${PLATFORM:-}')" >&2 exit 2 ;; esac SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" MANIFEST="${THIRD_PARTY_MANIFEST:-$REPO_ROOT/resources/third-party.json}" LICENSE_DIR="${THIRD_PARTY_LICENSE_DIR:-$REPO_ROOT/resources/licenses}" if ! command -v jq >/dev/null 2>&1; then echo "❌ jq is not on PATH — cannot read the manifest." >&2 exit 1 fi for path in "$MANIFEST" "$LICENSE_DIR"; do if [[ ! -e "$path" ]]; then echo "❌ '$path' not found." >&2 exit 1 fi done if [[ ! -d "$BUNDLE_DIR" ]]; then echo "❌ Bundle directory '$BUNDLE_DIR' does not exist." >&2 exit 1 fi # Components that can appear in THIS pack: a non-empty pattern list # for this platform. Everything else is someone else's platform and # would only be noise in a document meant to describe what the reader # is holding. present_ids=() while IFS= read -r id; do [[ -n "$id" ]] && present_ids+=( "$id" ) done < <( jq -r --arg p "$PLATFORM" ' .components[] | select(((.binaries[$p].patterns // []) | length) > 0) | .id ' "$MANIFEST" ) if (( ${#present_ids[@]} == 0 )); then echo "❌ No component in the manifest ships on '$PLATFORM' — that cannot be right." >&2 exit 1 fi # ---------------------------------------------------------------- # # LICENSES/ # ---------------------------------------------------------------- # # Every licence text referenced by a component that can appear here. # Note this is "can appear", not "did appear": the two Windows # compiler runtimes are mutually exclusive per subsystem and several # components are optional, but the set of texts a pack carries should # be a property of the platform, not of which DLLs the linker happened # to pull in on the day. A licence text nobody needed is inert; a # missing one is a notice we failed to give. license_out="$BUNDLE_DIR/LICENSES" rm -rf "$license_out" mkdir -p "$license_out" copied=0 while IFS= read -r lic; do [[ -n "$lic" ]] || continue if [[ ! -f "$LICENSE_DIR/$lic" ]]; then echo "❌ Manifest references licence text '$lic', which is not in $LICENSE_DIR." >&2 exit 1 fi cp "$LICENSE_DIR/$lic" "$license_out/$lic" copied=$(( copied + 1 )) done < <( jq -r --arg p "$PLATFORM" ' [ .components[] | select(((.binaries[$p].patterns // []) | length) > 0) | ."license-files"[] ] | unique | .[] ' "$MANIFEST" ) echo "Wrote $copied licence text(s) to $license_out/" # ---------------------------------------------------------------- # # THIRD-PARTY.md # ---------------------------------------------------------------- # md="$BUNDLE_DIR/THIRD-PARTY.md" { printf '# Third-party components in this package\n\n' printf 'Platform: %s\n\n' "\`$PLATFORM\`" cat <<'PREAMBLE' This archive is a binary redistribution. Everything it contains is listed below with the licence it is conveyed under and the exact source it was built from. Full licence texts are in `LICENSES/` alongside this file. Where the source is given as a tarball URL with a SHA-256, that exact tarball is attached to the GitHub release this archive came from, so the corresponding source for these binaries stays available for as long as the binaries do. That is a requirement of the LGPL for FFmpeg, GNU libunistring and — in the Windows archives, the only ones that carry it — GNU libiconv; the other tarballs are attached for consistency, so that "which source built this?" has one answer for every component rather than two kinds of answer. FFmpeg here is a decoder-only build with neither `--enable-gpl` nor `--enable-nonfree`, so it is conveyed under the LGPL v2.1 or later and none of FFmpeg's GPL-only components are compiled in. PREAMBLE printf '## Summary\n\n' printf '| Component | Version | Licence (SPDX) | Source |\n' printf '|---|---|---|---|\n' jq -r --arg p "$PLATFORM" ' .components[] | select(((.binaries[$p].patterns // []) | length) > 0) | . as $c | ( if $c.source.kind == "tarball" then "[tarball](\($c.source.url))" elif $c.source.kind == "git" then "[git](\($c.source.url)) @ `\($c.source.ref)`" elif $c.source.kind == "in-tree" then "[this project](\($c.source.url))" else "platform package manager" end ) as $src | "| \($c.name) | \($c.version) | `\($c["spdx-license"])` | \($src) |" ' "$MANIFEST" printf '\n## Details\n' jq -r --arg p "$PLATFORM" ' .components[] | select(((.binaries[$p].patterns // []) | length) > 0) | . as $c | [ "", "### \($c.name) — \($c.version)", "", "* Licence: `\($c["spdx-license"])`" + (if $c["conveyed-under"] then " — conveyed under `\($c["conveyed-under"])`" else "" end), "* Licence text: " + ([ $c["license-files"][] | "`LICENSES/\(.)`" ] | join(", ")), "* \($c.copyright)", "* Upstream: <\($c["project-url"])>" ] + ( if $c.source.kind == "tarball" then [ "* Source: <\($c.source.url)>", "* Source SHA-256: `\($c.source.sha256)`", "* Attached to this release as `\($c.source.filename)`" ] elif $c.source.kind == "git" then [ "* Source: <\($c.source.url)> at commit `\($c.source.ref)`" ] elif $c.source.kind == "in-tree" then [ "* Source: <\($c.source.url)>" ] else [ "* Source: supplied by the package manager of the build platform" ] end ) + [ "* Files in this archive: " + ([ $c.binaries[$p].patterns[] | "`\(.)`" ] | join(", ")) ] + ( if $c.notes then [ "", $c.notes ] else [] end ) | .[] ' "$MANIFEST" cat <<'FOOTER' --- Generated from `resources/third-party.json` by `scripts/ci/emit-third-party-kit.sh`. Do not edit this file by hand — edit the manifest, which `scripts/ci/audit-third-party.sh` enforces against the actual contents of this archive on every release. FOOTER } > "$md" echo "Wrote $md ($(wc -l < "$md" | tr -d ' ') lines) for ${#present_ids[@]} component(s)"