Release packaging: archives, workflow, download-first docs, Windows notes
release / release (push) Waiting to run
release / release (push) Waiting to run
- tools/build_release.sh: builds the Linux tar.gz and Windows zip from a git ref (default HEAD) using an explicit `git archive` pathspec allowlist: decoder (__init__.py, pd.py, gsmtap_stream.py), setup/start scripts, tools/vs_reader.py, READMEs, LICENSE and the README-referenced resources/ images. v* tags must match pd.py VERSION. Both archives are listed and extracted and checked for VERSION, py_compile, bash -n, .bat CRLF / .sh LF and tar exec bits; SHA256SUMS uses sha256sum or shasum. Builds into a staging dir and only then replaces dist/. - .github/workflows/release.yml: on a v* tag push run the unit tests, verify tag == pd.py VERSION, build, then upload into an existing release or create a draft and publish it; release-<tag> concurrency. - README/RUS: Installation documents both ways (release download recommended; source checkout for development), SHA256SUMS verification, and the Windows prerequisites verified against sigrok.org (0.7.2 release installer as the tested version, nightly caveat, VC++ 2010 redist, bundled fx2lafw firmware and Zadig, per-port driver binding). - start.bat: accept --opt=value alongside the original --opt value form (the usage text advertised the = form while the parser required a space); usage corrected and notes the core subset. - .gitignore: dist/. - AGENTS: release packaging, build verification and release process (commit docs/workflow first, then tag and push, auto-generated notes). No decoder behavior change; VERSION stays 1.9.7.
This commit is contained in:
1 parent
6e6f465841
commit
1cbafe715c
7 files changed
+518
-39
No files matched your search
Executable
+205
@@ -0,0 +1,205 @@
|
||||
#!/bin/bash
|
||||
# Build the GitHub release archives from a git ref.
|
||||
# Usage: ./tools/build_release.sh [REF]
|
||||
#
|
||||
# REF defaults to HEAD. The archives carry an explicit path allowlist: the
|
||||
# decoder, the setup/start scripts, tools/vs_reader.py, the two READMEs, the
|
||||
# license and the images the READMEs reference. The allowlist is passed to
|
||||
# `git archive` as its pathspec, so tests/, AGENTS.md, the CI config and the
|
||||
# development tools never enter the release — and the repository's own
|
||||
# (auto-generated) source archives are not affected.
|
||||
#
|
||||
# A v* tag must match the VERSION line in that ref's pd.py.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
cd "$ROOT"
|
||||
|
||||
die() { echo "build_release: $*" >&2; exit 1; }
|
||||
|
||||
REF="${1:-HEAD}"
|
||||
git rev-parse --verify "$REF^{commit}" >/dev/null 2>&1 || die "unknown ref: $REF"
|
||||
|
||||
VERSION="$(git show "$REF:pd.py" | sed -n "s/^VERSION = '\([0-9][0-9.]*\)'.*/\1/p")"
|
||||
[ -n "$VERSION" ] || die "cannot read VERSION from $REF:pd.py"
|
||||
|
||||
case "$REF" in
|
||||
v*)
|
||||
[ "${REF#v}" = "$VERSION" ] || \
|
||||
die "tag $REF does not match pd.py VERSION '$VERSION'"
|
||||
;;
|
||||
esac
|
||||
|
||||
PREFIX="sigrok_iso7816_stream-${VERSION}"
|
||||
|
||||
STAGE="$(mktemp -d "${TMPDIR:-/tmp}/sigrok_release.XXXXXX")"
|
||||
trap 'rm -rf "$STAGE"' EXIT
|
||||
|
||||
# --- expected release contents (allowlist) -------------------------------
|
||||
# The base runtime files plus every image referenced from the READMEs.
|
||||
README_RES="$(git show "$REF:README.md" \
|
||||
| grep -oE '\]\(resources/[^)]+' | sed 's/](//' | LC_ALL=C sort -u || true)"
|
||||
[ -n "$README_RES" ] || die "no resources/ images referenced from $REF:README.md"
|
||||
README_RUS_RES="$(git show "$REF:README_RUS.md" \
|
||||
| grep -oE '\]\(resources/[^)]+' | sed 's/](//' | LC_ALL=C sort -u || true)"
|
||||
[ "$README_RES" = "$README_RUS_RES" ] || \
|
||||
die "README.md and README_RUS.md reference different resources/ images"
|
||||
|
||||
EXPECTED="$STAGE/expected.list"
|
||||
{
|
||||
printf '%s\n' \
|
||||
__init__.py pd.py gsmtap_stream.py \
|
||||
setup.sh start.sh setup.bat start.bat \
|
||||
tools/vs_reader.py README.md README_RUS.md LICENSE
|
||||
printf '%s\n' "$README_RES"
|
||||
} | LC_ALL=C sort -u > "$EXPECTED"
|
||||
|
||||
PATHS=()
|
||||
while IFS= read -r path; do
|
||||
PATHS+=("$path")
|
||||
done < "$EXPECTED"
|
||||
|
||||
# Build into the staging directory; dist/ is only touched after every check
|
||||
# passes, so a failed build never leaves unverified archives behind.
|
||||
git archive --format=tar.gz --prefix="${PREFIX}/" "$REF" \
|
||||
-o "$STAGE/${PREFIX}.tar.gz" -- "${PATHS[@]}"
|
||||
git archive --format=zip --prefix="${PREFIX}/" "$REF" \
|
||||
-o "$STAGE/${PREFIX}.zip" -- "${PATHS[@]}"
|
||||
|
||||
# --- archive listings ----------------------------------------------------
|
||||
list_archive() {
|
||||
case "$1" in
|
||||
*.zip)
|
||||
if command -v unzip >/dev/null 2>&1; then
|
||||
unzip -Z1 "$1"
|
||||
else
|
||||
python3 - "$1" <<'PY'
|
||||
import sys, zipfile
|
||||
with zipfile.ZipFile(sys.argv[1]) as z:
|
||||
for name in z.namelist():
|
||||
print(name)
|
||||
PY
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
tar tzf "$1"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
normalize_listing() {
|
||||
# Strip the top-level prefix, drop directory entries, sort.
|
||||
awk -v p="$PREFIX/" '
|
||||
index($0, p) == 1 {
|
||||
name = substr($0, length(p) + 1)
|
||||
if (name != "" && name !~ /\/$/) print name
|
||||
}' | LC_ALL=C sort
|
||||
}
|
||||
|
||||
DEV_PATHS=(
|
||||
tests/
|
||||
AGENTS.md
|
||||
.github/
|
||||
.gitignore
|
||||
.gitattributes
|
||||
examples/
|
||||
tools/build_release.sh
|
||||
tools/capture_baseline.sh
|
||||
tools/compare_baseline.sh
|
||||
tools/signal_diagnostic.py
|
||||
)
|
||||
|
||||
check_archive_listing() {
|
||||
local archive="$1" listing="$2" actual="$3" path
|
||||
# Defense in depth: the pathspec already excludes these, but a future
|
||||
# refactor must not silently ship them.
|
||||
for path in "${DEV_PATHS[@]}"; do
|
||||
if grep -qF "${PREFIX}/${path}" "$listing"; then
|
||||
die "$archive unexpectedly contains dev-only path ${path}"
|
||||
fi
|
||||
done
|
||||
normalize_listing < "$listing" > "$actual"
|
||||
if ! diff -u "$EXPECTED" "$actual" > "$STAGE/list.diff"; then
|
||||
cat "$STAGE/list.diff" >&2
|
||||
die "$archive contents do not match the release allowlist (expected vs actual above)"
|
||||
fi
|
||||
}
|
||||
|
||||
list_archive "$STAGE/${PREFIX}.tar.gz" > "$STAGE/tar.list"
|
||||
list_archive "$STAGE/${PREFIX}.zip" > "$STAGE/zip.list"
|
||||
check_archive_listing "$STAGE/${PREFIX}.tar.gz" "$STAGE/tar.list" "$STAGE/tar.files"
|
||||
check_archive_listing "$STAGE/${PREFIX}.zip" "$STAGE/zip.list" "$STAGE/zip.files"
|
||||
|
||||
# --- unpack and verify both archives -------------------------------------
|
||||
mkdir -p "$STAGE/tar" "$STAGE/zip"
|
||||
tar xzf "$STAGE/${PREFIX}.tar.gz" -C "$STAGE/tar"
|
||||
TAR_DIR="$STAGE/tar/$PREFIX"
|
||||
[ -f "$TAR_DIR/pd.py" ] || die "tar.gz did not unpack ${PREFIX}/pd.py"
|
||||
|
||||
if command -v unzip >/dev/null 2>&1; then
|
||||
unzip -q "$STAGE/${PREFIX}.zip" -d "$STAGE/zip"
|
||||
else
|
||||
python3 - "$STAGE/${PREFIX}.zip" "$STAGE/zip" <<'PY'
|
||||
import sys, zipfile
|
||||
with zipfile.ZipFile(sys.argv[1]) as z:
|
||||
z.extractall(sys.argv[2])
|
||||
PY
|
||||
fi
|
||||
ZIP_DIR="$STAGE/zip/$PREFIX"
|
||||
[ -f "$ZIP_DIR/pd.py" ] || die "zip did not unpack ${PREFIX}/pd.py"
|
||||
|
||||
check_tree() {
|
||||
local src="$1" label="$2" file found
|
||||
found="$(sed -n "s/^VERSION = '\([0-9][0-9.]*\)'.*/\1/p" "$src/pd.py")"
|
||||
[ "$found" = "$VERSION" ] || \
|
||||
die "$label: pd.py VERSION '$found', expected '$VERSION'"
|
||||
python3 -m py_compile "$src/pd.py" "$src/gsmtap_stream.py" "$src/__init__.py" || \
|
||||
die "$label: py_compile failed"
|
||||
for file in setup.sh start.sh; do
|
||||
bash -n "$src/$file" || die "$label: bash -n $file failed"
|
||||
done
|
||||
for file in setup.bat start.bat; do
|
||||
python3 - "$src/$file" <<'PY' || die "$label: $file is not CRLF-terminated"
|
||||
import sys
|
||||
data = open(sys.argv[1], 'rb').read()
|
||||
if b'\n' not in data or data.count(b'\n') != data.count(b'\r\n'):
|
||||
sys.exit(1)
|
||||
PY
|
||||
done
|
||||
for file in setup.sh start.sh; do
|
||||
if grep -q $'\r' "$src/$file"; then
|
||||
die "$label: $file contains CR (expected LF only)"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
check_tree "$TAR_DIR" "tar.gz"
|
||||
check_tree "$ZIP_DIR" "zip"
|
||||
|
||||
[ -x "$TAR_DIR/setup.sh" ] || die "setup.sh is not executable in the tar.gz"
|
||||
[ -x "$TAR_DIR/start.sh" ] || die "start.sh is not executable in the tar.gz"
|
||||
|
||||
# --- checksums and publish ----------------------------------------------
|
||||
if command -v sha256sum >/dev/null 2>&1; then
|
||||
SHA256=(sha256sum)
|
||||
elif command -v shasum >/dev/null 2>&1; then
|
||||
SHA256=(shasum -a 256)
|
||||
else
|
||||
die "neither sha256sum nor shasum is available"
|
||||
fi
|
||||
(cd "$STAGE" && "${SHA256[@]}" "${PREFIX}.tar.gz" "${PREFIX}.zip" > SHA256SUMS)
|
||||
|
||||
# Verified: replace any previous release files so a later `gh release upload
|
||||
# dist/*` can never attach a stale archive.
|
||||
mkdir -p dist
|
||||
rm -f dist/sigrok_iso7816_stream-*.tar.gz \
|
||||
dist/sigrok_iso7816_stream-*.zip \
|
||||
dist/SHA256SUMS
|
||||
mv "$STAGE/${PREFIX}.tar.gz" "$STAGE/${PREFIX}.zip" "$STAGE/SHA256SUMS" dist/
|
||||
|
||||
echo "Built release archives (VERSION $VERSION):"
|
||||
du -h "dist/${PREFIX}.tar.gz" "dist/${PREFIX}.zip"
|
||||
echo ""
|
||||
echo "dist/SHA256SUMS:"
|
||||
cat dist/SHA256SUMS
|
||||
Reference in new issue
Block a user