a46e7eb961
check-branding.sh could say what disagreed but not fix it, and its canonical block lived in its own header — so renaming meant hand-editing the checker, which is exactly the fragile step it exists to catch. branding.conf now holds the identity once. Both scripts read it and neither contains a branding literal anywhere, so a rename never has to rewrite its own tooling. brand-plugin.sh derives the package name and Rust types from the display name, validates them against what Cargo actually accepts, rewrites every tracked file longest-value-first (a short name can be a substring of a longer one), appends the retired spellings to STALE, and finishes by running the checker. A run that ends green is a rename that landed. Dry run unless --apply. It refuses three things worth refusing: - Package names Cargo rejects — digit-leading (`206 Prototype` derives to `206_prototype`), Rust keywords, built-in crate collisions. Verified against cargo init rather than assumed. - Moving CLAP_ID or VST3_CLASS_ID without --allow-identity-change. Hosts key saved projects off those; changing either orphans every existing session silently. The 16-byte class ID is *proposed*, never derived — it is a fixed-width frozen identity, so the choice stays human. - Development history in CLAP_DESCRIPTION. Hosts show it to users who never knew the codename, so any retired name or the repo codename appearing there is rejected. The checker gains the other half of its job: asserting canonical values are present where they belong, not only that retired ones are gone. Absence alone would pass a rename to the *wrong* new spelling. New positive assertions cover Cargo authors, Cargo.lock, the CI bundle command and NOTICE — every one of which the recent Bari 206 rename had to touch. Structural checks read their file locations from branding.conf so a plugin whose params outgrow src/lib.rs can point them elsewhere. Also fixes NOTICE naming the repo rather than the product, found by the new assertion, and excludes branding.conf from the stale sweep — it lists every retired spelling by definition, the same reason check-branding.sh was already excluded. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WHt4DykxXaQnyaJfyS8KCn
213 lines
10 KiB
Bash
Executable File
213 lines
10 KiB
Bash
Executable File
#!/bin/sh
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
# check-branding.sh - verify the plugin's branding/identity strings agree everywhere.
|
|
#
|
|
# Plugin branding is duplicated across the build manifest, the bundler manifest,
|
|
# the source, the docs and (once you add one) the deploy script. Nothing makes
|
|
# them agree, so a rename drifts silently: the bundle builds under one name and
|
|
# the deploy script looks for another. This asserts they match, and that the
|
|
# identity constants are legal.
|
|
#
|
|
# In a template this matters more than usual: renaming is the first thing you do
|
|
# with this repo, and it touches every file below. Run this immediately after,
|
|
# and the STALE machinery will list whatever you missed.
|
|
#
|
|
# Usage: sh check-branding.sh (exit 0 = consistent, 1 = drift found)
|
|
#
|
|
# The canonical identity lives in branding.conf, shared with brand-plugin.sh.
|
|
# Edit it there, then run this to find every copy that still holds the old value.
|
|
|
|
set -u
|
|
cd "$(dirname "$0")" || exit 1
|
|
|
|
if [ ! -f branding.conf ]; then
|
|
printf 'branding.conf not found next to this script\n' >&2
|
|
exit 1
|
|
fi
|
|
# shellcheck disable=SC1091
|
|
. ./branding.conf
|
|
|
|
# Every canonical spelling concatenated, for the substring test below.
|
|
CANONICAL_BLOB="$PKG
|
|
$DESCRIPTION
|
|
$CLAP_DESCRIPTION
|
|
$STRUCT
|
|
$PARAMS_STRUCT
|
|
$DISPLAY
|
|
$PLUGIN_NAME
|
|
$VENDOR
|
|
$EMAIL
|
|
$HOMEPAGE
|
|
$CLAP_ID
|
|
$VST3_CLASS_ID"
|
|
|
|
pass=0; fail=0; skip=0
|
|
ok() { pass=$((pass+1)); printf ' ok %s\n' "$1"; }
|
|
bad() { fail=$((fail+1)); printf ' FAIL %s\n' "$1"; [ $# -gt 1 ] && printf ' %s\n' "$2"; return 0; }
|
|
note() { skip=$((skip+1)); printf ' skip %s\n' "$1"; }
|
|
# Reported for awareness only - never counted, never fails the run.
|
|
info() { printf ' -- %s\n' "$1"; }
|
|
|
|
# has FILE PATTERN -> fixed-string search
|
|
has() { grep -qF "$2" "$1" 2>/dev/null; }
|
|
|
|
check() { # check LABEL FILE LITERAL
|
|
if [ ! -f "$2" ]; then bad "$1" "missing file: $2"; return; fi
|
|
if has "$2" "$3"; then ok "$1"; else bad "$1" "$2 does not contain: $3"; fi
|
|
}
|
|
|
|
# Same as check, but a missing file is "not applicable yet" rather than drift.
|
|
# The template ships without a deploy script; this starts checking once you add one.
|
|
opt_check() { # opt_check LABEL FILE LITERAL
|
|
if [ ! -f "$2" ]; then note "$1 ($2 not present)"; return; fi
|
|
if has "$2" "$3"; then ok "$1"; else bad "$1" "$2 does not contain: $3"; fi
|
|
}
|
|
|
|
printf 'branding check (canonical: %s / "%s")\n\n' "$PKG" "$DISPLAY"
|
|
|
|
printf 'build manifests\n'
|
|
check "Cargo.toml package name" Cargo.toml "name = \"$PKG\""
|
|
check "Cargo.toml homepage" Cargo.toml "homepage = \"$HOMEPAGE\""
|
|
check "Cargo.toml description" Cargo.toml "description = \"$DESCRIPTION\""
|
|
check "Cargo.toml authors" Cargo.toml "authors = [\"$VENDOR <$EMAIL>\"]"
|
|
# Cargo regenerates the lockfile on the next build, but only if someone builds
|
|
# before pushing. A stale root entry here means CI resolves a package that no
|
|
# longer exists under that name.
|
|
opt_check "Cargo.lock package" Cargo.lock "name = \"$PKG\""
|
|
check "bundler.toml table key" bundler.toml "[$PKG]"
|
|
check "bundler.toml display name" bundler.toml "name = \"$DISPLAY\""
|
|
|
|
printf '\nsource identity (src/lib.rs)\n'
|
|
check "Plugin::NAME" "$LIB_FILE" "const NAME: &'static str = \"$PLUGIN_NAME\""
|
|
check "Plugin::VENDOR" "$LIB_FILE" "const VENDOR: &'static str = \"$VENDOR\""
|
|
check "Plugin::EMAIL" "$LIB_FILE" "const EMAIL: &'static str = \"$EMAIL\""
|
|
check "CLAP_ID" "$LIB_FILE" "const CLAP_ID: &'static str = \"$CLAP_ID\""
|
|
check "CLAP_DESCRIPTION" "$LIB_FILE" "const CLAP_DESCRIPTION: Option<&'static str> = Some(\"$CLAP_DESCRIPTION\")"
|
|
check "VST3_CLASS_ID" "$LIB_FILE" "const VST3_CLASS_ID: [u8; 16] = *b\"$VST3_CLASS_ID\""
|
|
|
|
# Plugin::URL is env!("CARGO_PKG_HOMEPAGE"), so the manifest is the only place
|
|
# it is spelled. Assert the indirection is still in place rather than hardcoded.
|
|
check "Plugin::URL from manifest" "$LIB_FILE" "const URL: &'static str = env!(\"CARGO_PKG_HOMEPAGE\")"
|
|
|
|
# VST3 class IDs are a fixed 16-byte array. A shorter or longer literal is a
|
|
# compile error, but it is far clearer to say so here than to read rustc on it.
|
|
n=$(printf '%s' "$VST3_CLASS_ID" | wc -c | tr -d ' ')
|
|
if [ "$n" -eq 16 ]; then ok "VST3_CLASS_ID is 16 bytes"
|
|
else bad "VST3_CLASS_ID is 16 bytes" "got $n bytes: \"$VST3_CLASS_ID\""; fi
|
|
|
|
# CLAP IDs are reverse-DNS by convention and are compared verbatim by hosts, so
|
|
# a stray space or capital is a silent identity change between machines.
|
|
case "$CLAP_ID" in
|
|
*\ *|*[A-Z]*) bad "CLAP_ID is reverse-DNS safe" "no spaces or capitals allowed: \"$CLAP_ID\"" ;;
|
|
*.*) ok "CLAP_ID is reverse-DNS safe" ;;
|
|
*) bad "CLAP_ID is reverse-DNS safe" "expected dotted reverse-DNS, got: \"$CLAP_ID\"" ;;
|
|
esac
|
|
|
|
printf '\nstructural names (src/lib.rs)\n'
|
|
# Every site the type names appear. A rename that misses one is a compile error
|
|
# rather than silent drift, but naming the site is faster than reading rustc,
|
|
# and the Vst3Plugin impl sits behind a cfg so a default build never reports it.
|
|
check "struct $STRUCT" "$LIB_FILE" "struct $STRUCT {"
|
|
check "impl Plugin" "$LIB_FILE" "impl Plugin for $STRUCT {"
|
|
check "impl ClapPlugin" "$LIB_FILE" "impl ClapPlugin for $STRUCT {"
|
|
check "impl Vst3Plugin" "$LIB_FILE" "impl Vst3Plugin for $STRUCT {"
|
|
check "nih_export_clap!" "$LIB_FILE" "nih_export_clap!($STRUCT);"
|
|
check "nih_export_vst3!" "$LIB_FILE" "nih_export_vst3!($STRUCT);"
|
|
check "struct $PARAMS_STRUCT" "$PARAMS_FILE" "struct $PARAMS_STRUCT {"
|
|
check "impl Default for params" "$PARAMS_FILE" "impl Default for $PARAMS_STRUCT {"
|
|
check "params field type" "$LIB_FILE" "params: Arc<$PARAMS_STRUCT>,"
|
|
|
|
printf '\ndocs\n'
|
|
opt_check "README bundle command" README.md "cargo xtask bundle $PKG"
|
|
|
|
# CLAP_DESCRIPTION is shown to users in a host's plugin browser. It should say
|
|
# what the plugin does now, with no development history in it. "Free of history"
|
|
# is a judgement a script cannot make, but the failure it produces is mechanical:
|
|
# a description still carrying a retired name or the repo's development codename.
|
|
printf '\ndescription hygiene\n'
|
|
hygiene_hits=""
|
|
while IFS= read -r s_entry; do
|
|
[ -n "$s_entry" ] || continue
|
|
case "$CANONICAL_BLOB" in *"$s_entry"*) continue ;; esac
|
|
case "$CLAP_DESCRIPTION" in *"$s_entry"*) hygiene_hits="$hygiene_hits \"$s_entry\"" ;; esac
|
|
done <<HYGIENE_LIST
|
|
$STALE
|
|
$CODENAME
|
|
HYGIENE_LIST
|
|
if [ -z "$hygiene_hits" ]; then ok "CLAP_DESCRIPTION carries no retired name or codename"
|
|
else bad "CLAP_DESCRIPTION carries no retired name or codename" "found:$hygiene_hits"; fi
|
|
|
|
printf '\nci\n'
|
|
# CI bundles by package name, so a rename that misses this file breaks the build
|
|
# rather than drifting quietly - but it breaks after a full compile, which is a
|
|
# slow way to learn about a typo. Checking it here costs nothing.
|
|
opt_check "CI bundle command" .github/workflows/ci.yaml "cargo xtask bundle $PKG"
|
|
|
|
printf '\nattribution\n'
|
|
# NOTICE names the product in its first line; a rename that misses it ships an
|
|
# artifact crediting a name that no longer exists.
|
|
opt_check "NOTICE product name" NOTICE "$DISPLAY"
|
|
|
|
printf '\ndeploy script\n'
|
|
opt_check "deploy.ps1 default package" deploy.ps1 "\$Package = \"$PKG\""
|
|
opt_check "deploy.ps1 VST3 bundle" deploy.ps1 "\$vst3Name = \"$DISPLAY.vst3\""
|
|
opt_check "deploy.ps1 CLAP bundle" deploy.ps1 "\$clapName = \"$DISPLAY.clap\""
|
|
|
|
printf '\nstale spellings\n'
|
|
# File list for the scan below. Prefer git, so build output and any vendored
|
|
# tree stay out of it. Fall back to find when this is not a checkout - an
|
|
# exported tarball, or a CI step that fetched without git metadata - because
|
|
# `git ls-files` there returns nothing, and an empty list would report every
|
|
# stale spelling as absent. A check that passes hardest when it can see least
|
|
# is worse than no check.
|
|
if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
file_list() { git ls-files; }
|
|
scan_source="git"
|
|
else
|
|
file_list() { find . -path ./.git -prune -o -path ./target -prune -o -type f -print | sed 's|^\./||'; }
|
|
scan_source="find (not a git checkout)"
|
|
fi
|
|
info "scanning tracked files via $scan_source"
|
|
# Fed by here-doc rather than a pipe on purpose: a piped `while` runs in a
|
|
# subshell, and the pass/fail counters it increments would be discarded.
|
|
while IFS= read -r s; do
|
|
[ -n "$s" ] || continue
|
|
|
|
# Still part of a canonical value? Then it is current branding, not a leftover.
|
|
# This is what lets the list ship pre-seeded: entries arm themselves as you
|
|
# rename. Substring, not equality, because placeholders nest - "your-domain"
|
|
# lives inside the canonical CLAP_ID, and "YOUR-USERNAME" inside the homepage.
|
|
# Over-skipping is the safe direction: a false FAIL on a name you still use
|
|
# would train you to ignore this section.
|
|
case "$CANONICAL_BLOB" in
|
|
*"$s"*) note "\"$s\" (still canonical)"; continue ;;
|
|
esac
|
|
|
|
# These two name every retired spelling by definition - the STALE list lives in
|
|
# branding.conf and this script reads it - so neither can ever be a real hit.
|
|
hits=$(file_list 2>/dev/null | while read -r f; do
|
|
case "$f" in check-branding.sh|branding.conf) continue ;; esac
|
|
[ -f "$f" ] && grep -lF "$s" "$f" 2>/dev/null
|
|
done)
|
|
if [ -z "$hits" ]; then ok "no occurrences of \"$s\""
|
|
else bad "no occurrences of \"$s\"" "still present in: $(echo "$hits" | tr '\n' ' ')"; fi
|
|
done <<STALE_LIST
|
|
$STALE
|
|
STALE_LIST
|
|
|
|
# Template scaffolding, not drift: TODOs are legitimate while you are still
|
|
# building. Reported so a finished plugin does not quietly ship with them.
|
|
# `grep -c` prints 0 AND exits 1 when there are no matches, so `|| echo 0`
|
|
# would append a second zero and make the count unparseable. Swallow the
|
|
# status instead, and default an empty result (missing file) to zero.
|
|
todos=$(grep -c 'TODO' src/lib.rs 2>/dev/null || :)
|
|
[ -n "$todos" ] || todos=0
|
|
printf '\nscaffolding\n'
|
|
if [ "$todos" -eq 0 ]; then ok "no TODO markers left in src/lib.rs"
|
|
else info "$todos TODO marker(s) still in src/lib.rs (not a failure)"; fi
|
|
|
|
printf '\n%d passed, %d failed' "$pass" "$fail"
|
|
[ "$skip" -gt 0 ] && printf ', %d skipped' "$skip"
|
|
printf '\n'
|
|
[ "$fail" -eq 0 ] || exit 1
|