From 1cbafe715c859e4a8cf02539c9751a7525b6a962 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BD=D1=82=D0=BE=D0=BD=20=D0=A2=D1=80=D0=BE=D1=88?= =?UTF-8?q?=D0=B8=D0=BD?= Date: Tue, 20 Oct 2026 23:32:05 +0300 Subject: [PATCH] Release packaging: archives, workflow, download-first docs, Windows notes - 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- 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. --- .github/workflows/release.yml | 63 +++++++++++ .gitignore | 3 + AGENTS.md | 38 +++++++ README.md | 105 ++++++++++++++--- README_RUS.md | 107 +++++++++++++++--- start.bat | 36 ++++-- tools/build_release.sh | 205 ++++++++++++++++++++++++++++++++++ 7 files changed, 518 insertions(+), 39 deletions(-) create mode 100644 .github/workflows/release.yml create mode 100755 tools/build_release.sh diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..ff0eb22 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,63 @@ +name: release + +on: + push: + tags: + - 'v*' + +permissions: + contents: write + +concurrency: + group: release-${{ github.ref_name }} + cancel-in-progress: false + +jobs: + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - uses: actions/setup-python@v5 + with: + python-version: '3.x' + + - name: Unit tests + run: python3 tests/test_gsmtap.py + + - name: Verify tag matches pd.py VERSION + shell: bash + run: | + set -euo pipefail + version=$(sed -n "s/^VERSION = '\([0-9][0-9.]*\)'.*/\1/p" pd.py) + if [ -z "$version" ]; then + echo "::error::cannot read VERSION from pd.py" + exit 1 + fi + if [ "$GITHUB_REF_NAME" != "v$version" ]; then + echo "::error::tag $GITHUB_REF_NAME does not match pd.py VERSION '$version'" + exit 1 + fi + echo "tag $GITHUB_REF_NAME matches pd.py VERSION $version" + + - name: Build release archives + run: ./tools/build_release.sh "$GITHUB_REF_NAME" + + - name: Create or update the GitHub release + shell: bash + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + version=$(sed -n "s/^VERSION = '\([0-9][0-9.]*\)'.*/\1/p" pd.py) + # Upload into an existing release first. If the release does not + # exist yet, create it as a draft so the published release never + # appears with a partial asset set. + if ! gh release upload "$GITHUB_REF_NAME" dist/* --clobber; then + gh release create "$GITHUB_REF_NAME" dist/* --draft \ + --title "sigrok_iso7816_stream $version" --generate-notes + fi + # Publish a new draft and re-publish an existing one. + gh release edit "$GITHUB_REF_NAME" --draft=false diff --git a/.gitignore b/.gitignore index 23a8d51..a1ad0cd 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,6 @@ __pycache__/ # Trace fixtures are local-only (large, machine-specific); never tracked. examples/ + +# Release archives built by tools/build_release.sh. +dist/ diff --git a/AGENTS.md b/AGENTS.md index f841850..058613d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1019,6 +1019,44 @@ append the measured CLK frequency when known The decoder version is defined in `pd.py` as `VERSION = '1.9.7'`. The version is printed to the log on decoder startup. +### Release packaging (v1.9.7) + +The GitHub release archives let users run the decoder without cloning the +repo. `./tools/build_release.sh [REF]` (REF defaults to `HEAD`) builds +`dist/sigrok_iso7816_stream-.tar.gz` (Linux) and +`dist/sigrok_iso7816_stream-.zip` (Windows) plus +`dist/SHA256SUMS`. `VERSION` comes from the ref's `pd.py`; a `v*` REF must +equal it. + +The release file set is an explicit allowlist passed to `git archive` as its +pathspec, and also compared exactly against both archive listings (missing +*and* unexpected entries fail): the decoder (`__init__.py`, `pd.py`, +`gsmtap_stream.py`), the scripts (`setup.sh`/`start.sh`, +`setup.bat`/`start.bat`), `tools/vs_reader.py`, `README.md`, `README_RUS.md`, +`LICENSE`, and every `resources/` image referenced from `README.md`. Tests, +`AGENTS.md`, the CI config, the development tools and the unreferenced +`resources/` images never enter the release. No `export-ignore` rules are +used, so GitHub's own source archives still contain the full tree. `dist/` +is gitignored. + +Both archives are unpacked and verified: `pd.py` VERSION, `py_compile` of the +three modules, `bash -n` of the shell scripts, the tar exec bits, CRLF on the +`.bat` files and no CR in the `.sh` files. The archives are built in a temp +staging dir; only after every check passes are stale `dist/` archives removed +and the new tar.gz/zip/`SHA256SUMS` moved in (a failed build leaves `dist/` +untouched, and `gh release upload dist/*` can never attach a stale version). + +`.github/workflows/release.yml` runs on a `v*` tag push: unit tests +(`tests/test_gsmtap.py`), tag == `pd.py` VERSION, the build script, then +`gh release upload --clobber` (or `gh release create --draft` when there is +no release yet) followed by `gh release edit --draft=false`, with +`release-` concurrency. **Release process:** commit the docs, workflow +and scripts first, then tag `v1.9.7` and push the tag (with user approval) - +the workflow must exist in the tagged commit, and the archive ships the +committed READMEs. Release notes are GitHub's auto-generated notes from the +commits; `AGENTS.md` and `tests/` are not in the archive. Packaging/docs +only - no decoder behavior change, and `VERSION` is unchanged. + ### Testing after decoder changes After any change to `pd.py`: diff --git a/README.md b/README.md index 212526c..d4bc9fd 100644 --- a/README.md +++ b/README.md @@ -117,13 +117,17 @@ data flags and are not part of the libosmocore spec. ## Usage -Quick start after cloning (see Installation below): +Quick start (after installing — release download or source checkout; see +Installation below): ```bash ./setup.sh # check sigrok-cli, register decoder symlink ./start.sh # capture + GSMTAP stream to 127.0.0.1:4729 ``` +On Windows the same two steps are `setup.bat` then `start.bat` (see +Installation below). + `start.sh` defaults: pins D5=CLK, D0=I/O, D4=RST, D2=VCC, 16 MHz samplerate. Override via args: @@ -318,27 +322,61 @@ same restriction. ## Installation -### Linux +Both ways install the same decoder — pick one: + +- **Download (recommended)** — a release archive. Nothing to clone and no git + history: extract it, run `setup`, run `start`. Best if you only want to + capture. +- **Source checkout** — a `git clone`. The same runtime plus the development + material: `tests/` and the trace/baseline tools (`tools/capture_baseline.sh`, + `tools/compare_baseline.sh`, `tools/vs_reader.py`). The example traces + (`examples/`) are gitignored and stay local in either case. The release + archives intentionally do not contain `tests/`, the dev tools or + `examples/`. + +### Download (recommended) + +Grab the latest archive from +[Releases](https://github.com/anttro/sigrok_iso7816_stream/releases/latest): + +- `sigrok_iso7816_stream-.tar.gz` — Linux +- `sigrok_iso7816_stream-.zip` — Windows + +Each release also publishes `SHA256SUMS`, which lists **both** archives; keep +it next to the file you downloaded and verify: ```bash -git clone git@github.com:anttro/sigrok_iso7816_stream.git -cd sigrok_iso7816_stream +sha256sum -c --ignore-missing SHA256SUMS # GNU coreutils (Linux) +``` + +On Windows run `certutil -hashfile sigrok_iso7816_stream-.zip SHA256` +and compare the output with the `.zip` line in `SHA256SUMS`. + +#### Linux (download) + +```bash +tar xzf sigrok_iso7816_stream-.tar.gz +cd sigrok_iso7816_stream- ./setup.sh +./start.sh ``` `setup.sh` verifies sigrok-cli and the FX2 firmware package -(`sigrok-firmware-fx2lafw`) are installed, symlinks the repo into +(`sigrok-firmware-fx2lafw`) are installed, symlinks the project directory into `~/.local/share/libsigrokdecode/decoders/iso7816` (user-local decoders shadow system-wide ones), and probes the connected device for its maximum supported samplerate. -### Windows +#### Windows (download) Prerequisites: 1. **sigrok-cli** Windows installer (0.7.2 x86_64) from - [sigrok.org/wiki/Downloads](https://sigrok.org/wiki/Downloads). - If it fails to start with error `0xc0150002`, install the + [sigrok.org/wiki/Downloads](https://sigrok.org/wiki/Downloads) — the tested + release. Nightly Windows installers are also published on that page; they + may change the sigrok-cli syntax or the decoder API, so the 0.7.2 + release installer remains the supported version. + If sigrok-cli fails to start with error `0xc0150002`, install the [Visual C++ 2010 Redistributable](https://www.microsoft.com/en-us/download/details.aspx?id=26999) (`msvcr100.dll` is required by the bundled Python runtime). 2. fx2lafw firmware ships bundled with the installer — no separate download. @@ -354,7 +392,40 @@ Driver setup (FX2 dongles need WinUSB, the vendor driver won't work): - Windows binds drivers per USB port when no serial number is present: moving the dongle to another port may require repeating Zadig. -Then: +Then extract the release `.zip` (right-click → Extract All in Explorer) and +run, inside the extracted folder: + +```bat +setup.bat +start.bat +``` + +`setup.bat` verifies sigrok-cli and bundled firmware, registers this project's +decoder via a junction into `%LOCALAPPDATA%\libsigrokdecode\decoders\iso7816` +(confirmed by `sigrok-cli -L` listing `iso7816`), and probes the device's +accepted samplerates. The release zip ships the batch files with CRLF line +endings, so no conversion is needed. `start.bat --help` lists its options +(the core subset of `start.sh`: channels, samplerate, clock, host/port, pcap, +protocol, debug). + +### Linux (source checkout) + +```bash +git clone git@github.com:anttro/sigrok_iso7816_stream.git +cd sigrok_iso7816_stream +./setup.sh +``` + +A fresh clone needs the same `./setup.sh` checks and symlink as above: it +verifies sigrok-cli and the FX2 firmware package, symlinks the project +directory into `~/.local/share/libsigrokdecode/decoders/iso7816` and probes +the device's maximum samplerate. + +### Windows (source checkout) + +The Windows prerequisites (sigrok-cli + the Visual C++ 2010 Redistributable, +the bundled fx2lafw firmware, and the Zadig/WinUSB driver setup) are the same +as in [Windows (download)](#windows-download) above. ```bat git clone git@github.com:anttro/sigrok_iso7816_stream.git @@ -362,10 +433,15 @@ cd sigrok_iso7816_stream setup.bat ``` -`setup.bat` verifies sigrok-cli and bundled firmware, registers this repo's -decoder via a junction into `%LOCALAPPDATA%\libsigrokdecode\decoders\iso7816` -(confirmed by `sigrok-cli -L` listing `iso7816`), and probes the device's -accepted samplerates. +`setup.bat` performs the same checks and junction registration as above. +`start.bat` supports the core option subset (channels, samplerate, clock, +host/port, pcap, protocol, debug; `start.sh` has the full set) — the space +form is the primary one, and `--clk=CH` also works: + +```bat +start.bat --clk D1 --data D6 +start.bat --help +``` Capture works the same as on Linux: @@ -374,7 +450,8 @@ start.bat rem capture + GSMTAP stream ``` Note: batch files need CRLF line endings; the repo enforces this via -`.gitattributes`, so a fresh clone on Windows is already correct. +`.gitattributes`, so a fresh clone on Windows is already correct (the release +zip ships the same CRLF files). ## RST/VCC detection notes and limitations diff --git a/README_RUS.md b/README_RUS.md index 1177274..f4cf8ee 100644 --- a/README_RUS.md +++ b/README_RUS.md @@ -121,13 +121,17 @@ flags) USB-протокола simtrace2 и не является частью с ## Использование -Быстрый старт после клонирования (см. «Установка» ниже): +Быстрый старт (после установки — скачанный архив или клон репозитория; +см. «Установка» ниже): ```bash ./setup.sh # проверить sigrok-cli, зарегистрировать симлинк декодера ./start.sh # запись + поток GSMTAP на 127.0.0.1:4729 ``` +В Windows те же два шага — `setup.bat`, затем `start.bat` (см. «Установка» +ниже). + Умолчания `start.sh`: выводы D5=CLK, D0=I/O, D4=RST, D2=VCC, частота дискретизации 16 МГц. Переопределяются аргументами: @@ -331,27 +335,60 @@ libsigrok ограничивает 12 МГц. Поэтому `start.sh`/`start. ## Установка -### Linux +Оба способа ставят один и тот же декодер — выберите один: + +- **Скачать (рекомендуется)** — архив релиза. Не нужно ничего клонировать и + нет истории git: распакуйте, запустите `setup`, затем `start`. Лучший + вариант, если нужен только съём. +- **Из исходников** — `git clone`. Тот же рантайм плюс материал для + разработки: `tests/` и инструменты трасс и базовой линии + (`tools/capture_baseline.sh`, `tools/compare_baseline.sh`, + `tools/vs_reader.py`). Трассы примеров (`examples/`) исключены из git и в + любом случае остаются локальными. В архивах релиза намеренно нет `tests/`, + dev-инструментов и `examples/`. + +### Скачать (рекомендуется) + +Скачайте последний архив со страницы +[Releases](https://github.com/anttro/sigrok_iso7816_stream/releases/latest): + +- `sigrok_iso7816_stream-.tar.gz` — Linux +- `sigrok_iso7816_stream-.zip` — Windows + +К каждому релизу прилагается `SHA256SUMS` — в нём **оба** архива. +Положите файл рядом со скачанным архивом и проверьте: ```bash -git clone git@github.com:anttro/sigrok_iso7816_stream.git -cd sigrok_iso7816_stream +sha256sum -c --ignore-missing SHA256SUMS # GNU coreutils (Linux) +``` + +В Windows выполните `certutil -hashfile sigrok_iso7816_stream-.zip SHA256` +и сравните вывод со строкой `.zip` в `SHA256SUMS`. + +#### Linux (скачать) + +```bash +tar xzf sigrok_iso7816_stream-.tar.gz +cd sigrok_iso7816_stream- ./setup.sh +./start.sh ``` `setup.sh` проверяет, что установлены sigrok-cli и пакет прошивки FX2 -(`sigrok-firmware-fx2lafw`), создаёт симлинк репозитория в +(`sigrok-firmware-fx2lafw`), создаёт симлинк каталога проекта в `~/.local/share/libsigrokdecode/decoders/iso7816` (пользовательские декодеры перекрывают системные) и опрашивает подключённое устройство на предмет максимальной поддерживаемой частоты дискретизации. -### Windows +#### Windows (скачать) Предварительные требования: 1. **sigrok-cli** — установщик для Windows (0.7.2 x86_64) с - [sigrok.org/wiki/Downloads](https://sigrok.org/wiki/Downloads). - Если он не запускается с ошибкой `0xc0150002`, установите + [sigrok.org/wiki/Downloads](https://sigrok.org/wiki/Downloads) — + протестированная версия. На этой же странице публикуются ночные сборки + для Windows; они могут менять синтаксис sigrok-cli или API декодеров, + поэтому поддерживаемой остаётся версия из релизного установщика 0.7.2. Если sigrok-cli не запускается с ошибкой `0xc0150002`, установите [Visual C++ 2010 Redistributable](https://www.microsoft.com/en-us/download/details.aspx?id=26999) (`msvcr100.dll` требуется встроенному Python-рантайму). 2. Прошивка fx2lafw поставляется вместе с установщиком — отдельно скачивать не нужно. @@ -367,7 +404,42 @@ cd sigrok_iso7816_stream - Windows привязывает драйверы к USB-порту, когда нет серийного номера: перенос донгла в другой порт может потребовать повторить Zadig. -Затем: +Затем распакуйте архив `.zip` (в Проводнике: правой кнопкой → «Извлечь все») +и запустите в распакованном каталоге: + +```bat +setup.bat +start.bat +``` + +`setup.bat` проверяет sigrok-cli и встроенную прошивку, регистрирует декодер +этого проекта через junction в +`%LOCALAPPDATA%\libsigrokdecode\decoders\iso7816` (подтверждается тем, что +`sigrok-cli -L` показывает `iso7816`), и опрашивает устройство на предмет +поддерживаемых частот дискретизации. В zip-архиве релиза bat-файлы уже с +переводами строк CRLF, поэтому преобразование не требуется. +`start.bat --help` перечисляет его опции (основное подмножество `start.sh`: +каналы, частота дискретизации, clock, host/port, pcap, protocol, debug). + +### Linux (из исходников) + +```bash +git clone git@github.com:anttro/sigrok_iso7816_stream.git +cd sigrok_iso7816_stream +./setup.sh +``` + +Свежему клону нужны те же проверки и симлинк `./setup.sh`, что и выше: он +проверяет sigrok-cli и пакет прошивки FX2, создаёт симлинк каталога проекта +в `~/.local/share/libsigrokdecode/decoders/iso7816` и опрашивает устройство +на предмет максимальной частоты дискретизации. + +### Windows (из исходников) + +Предварительные требования Windows (sigrok-cli и Visual C++ 2010 +Redistributable, встроенная прошивка fx2lafw и настройка драйвера +Zadig/WinUSB) — те же, что в разделе +[Windows (скачать)](#windows-скачать) выше. ```bat git clone git@github.com:anttro/sigrok_iso7816_stream.git @@ -375,11 +447,15 @@ cd sigrok_iso7816_stream setup.bat ``` -`setup.bat` проверяет sigrok-cli и встроенную прошивку, регистрирует декодер -этого репозитория через junction в -`%LOCALAPPDATA%\libsigrokdecode\decoders\iso7816` (подтверждается тем, что -`sigrok-cli -L` показывает `iso7816`), и опрашивает устройство на предмет -поддерживаемых частот дискретизации. +`setup.bat` выполняет те же проверки и регистрацию junction, что и выше. +`start.bat` поддерживает основное подмножество опций (каналы, частота +дискретизации, clock, host/port, pcap, protocol, debug; полный набор — в +`start.sh`) — основной формат с пробелом, а `--clk=CH` тоже работает: + +```bat +start.bat --clk D1 --data D6 +start.bat --help +``` Запись работает так же, как в Linux: @@ -388,7 +464,8 @@ start.bat rem запись + поток GSMTAP ``` Примечание: bat-файлы требуют переводов строк CRLF; репозиторий следит за -этим через `.gitattributes`, поэтому свежий клон в Windows уже корректен. +этим через `.gitattributes`, поэтому свежий клон в Windows уже корректен +(zip-архив релиза содержит те же файлы с CRLF). ## Заметки и ограничения по детектированию RST/VCC diff --git a/start.bat b/start.bat index 034d9ef..ea8f99a 100644 --- a/start.bat +++ b/start.bat @@ -36,27 +36,43 @@ if /i "%~1"=="--no-rst" ( set "RST=" & shift & goto :argloop ) if /i "%~1"=="--no-vcc" ( set "VCC=" & shift & goto :argloop ) if /i "%~1"=="-h" ( goto :usage ) if /i "%~1"=="--help" ( goto :usage ) +rem Value options also accept the --opt=value form (usage shows the space form). +for /f "tokens=1,* delims==" %%A in ("%~1") do ( + if /i "%%A"=="--clk" ( set "CLK=%%B" & shift & goto :argloop ) + if /i "%%A"=="--data" ( set "DATA=%%B" & shift & goto :argloop ) + if /i "%%A"=="--rst" ( set "RST=%%B" & shift & goto :argloop ) + if /i "%%A"=="--vcc" ( set "VCC=%%B" & shift & goto :argloop ) + if /i "%%A"=="--samplerate" ( set "SAMPLERATE=%%B" & shift & goto :argloop ) + if /i "%%A"=="--clock" ( set "CLOCK=%%B" & shift & goto :argloop ) + if /i "%%A"=="--host" ( set "HOST=%%B" & shift & goto :argloop ) + if /i "%%A"=="--port" ( set "PORT=%%B" & shift & goto :argloop ) + if /i "%%A"=="--pcap" ( set "PCAP=%%B" & shift & goto :argloop ) + if /i "%%A"=="--protocol" ( set "PROTOCOL=%%B" & shift & goto :argloop ) +) echo Unknown option: %~1 exit /b 1 :usage echo Usage: %~n0 [OPTIONS] -echo --clk=CH CLK channel default: %CLK% -echo --data=CH I/O channel default: %DATA% -echo --rst=CH RST channel default: %RST% -echo --vcc=CH VCC channel default: %VCC% -echo --samplerate=SR Sample rate default: %SAMPLERATE% -echo --clock=MODE native/detect/sample_as_clock -echo --host=IP GSMTAP destination default: 127.0.0.1 -echo --port=N GSMTAP port default: 4729 -echo --pcap=FILE Also write decoded events to FILE (no spaces in path) +echo --clk CH CLK channel default: %CLK% +echo --data CH I/O channel default: %DATA% +echo --rst CH RST channel default: %RST% +echo --vcc CH VCC channel default: %VCC% +echo --samplerate SR Sample rate default: %SAMPLERATE% +echo --clock MODE native/detect/sample_as_clock +echo --host IP GSMTAP destination default: 127.0.0.1 +echo --port N GSMTAP port default: 4729 +echo --pcap FILE Also write decoded events to FILE (no spaces in path) echo --debug Full decoder + sigrok output (all annotation rows + -l 4). echo echo By default only decoded APDUs and decoder warnings/errors are shown. -echo --protocol=MODE T=0 (default), T=1, or auto (mid-session decode) +echo --protocol MODE T=0 (default), T=1, or auto (mid-session decode) echo --no-rst Disable RST tracking echo --no-vcc Disable VCC tracking echo. +echo The --clk=CH form also works. start.bat supports this core subset; +echo start.sh has the full option set. +echo. echo To log output on Windows use redirection: %~n0 ... ^> capture.log 2^>^&1 exit /b 0 diff --git a/tools/build_release.sh b/tools/build_release.sh new file mode 100755 index 0000000..d59abd6 --- /dev/null +++ b/tools/build_release.sh @@ -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