diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b602895c..663818ce 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -275,9 +275,28 @@ jobs: if-no-files-found: error retention-days: 1 + # El cask pide macOS 15 (Sequoia) y la app 15.4 (ADR-0040): no se prueba en el runner de compilacion. + verify-cask: + name: Cask de macOS + needs: macos + runs-on: macos-15 + timeout-minutes: 15 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ inputs.ref }} + + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: bundle-macos + path: dmg + + - name: Instala y desinstala el cask con ese .dmg + run: packaging/macos/verify-cask.sh dmg/*.dmg + collect-packages: name: Reune y verifica paquetes - needs: [native-bridge, test-native, flatpak, bundle, windows, macos] + needs: [native-bridge, test-native, flatpak, bundle, windows, macos, verify-cask] if: | always() && !cancelled() && @@ -286,6 +305,7 @@ jobs: needs.flatpak.result == 'success' && needs.windows.result == 'success' && needs.macos.result == 'success' && + needs.verify-cask.result == 'success' && (needs.bundle.result == 'success' || needs.bundle.result == 'skipped') runs-on: ubuntu-latest timeout-minutes: 10 diff --git a/justfile b/justfile index 7283d2c0..d8f07607 100644 --- a/justfile +++ b/justfile @@ -126,6 +126,7 @@ check-repo: check-version fmt-check {{ root }}/packaging/repo/publish-tree.test.sh {{ root }}/packaging/windows/sign-updater.test.sh {{ root }}/packaging/verify-packages.test.sh + {{ root }}/packaging/macos/verify-cask.test.sh {{ root }}/packaging/check_launchers.py python3 -m unittest discover -s {{ root }}/packaging -p 'test_check_launchers.py' python3 -m unittest discover -s {{ root }}/scripts/tests -p 'test_*.py' diff --git a/packaging/macos/verify-cask.sh b/packaging/macos/verify-cask.sh new file mode 100755 index 00000000..b70dd9c8 --- /dev/null +++ b/packaging/macos/verify-cask.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +# Instala el cask del tap con el .dmg recién construido, arranca la app y la desinstala, sin escribir en el tap (TD-139). +set -euo pipefail + +dmg="${1:?uso: verify-cask.sh }" +dmg="$(cd "$(dirname "$dmg")" && pwd)/$(basename "$dmg")" +source="${CASK_SOURCE:-https://raw.githubusercontent.com/sgomez/homebrew-rfirma/main/Casks/rfirma.rb}" +applications="${APPLICATIONS_DIR:-/Applications}" +tap="rfirma-ci/local" + +tmp="$(mktemp -d)" +cleanup() { + brew untap "$tap" > /dev/null 2>&1 || true + rm -rf "$tmp" +} +trap cleanup EXIT + +case "$source" in + http*) curl -fsSL "$source" -o "$tmp/rfirma.rb" ;; + *) cp "$source" "$tmp/rfirma.rb" ;; +esac + +sha="$(shasum -a 256 "$dmg" | cut -d' ' -f1)" +sed -E \ + -e "s|^( *)version .*|\\1version \"0.0.0\"|" \ + -e "s|^( *)sha256 .*|\\1sha256 \"$sha\"|" \ + -e "s|^( *)url \"[^\"]*\".*|\\1url \"file://$dmg\"|" \ + "$tmp/rfirma.rb" > "$tmp/cask.rb" + +brew tap-new --no-git "$tap" > /dev/null +mkdir -p "$(brew --repository "$tap")/Casks" +cp "$tmp/cask.rb" "$(brew --repository "$tap")/Casks/rfirma.rb" + +brew install --cask "$tap/rfirma" +app="$applications/rfirma.app" +[ -d "$app" ] || { echo "$app no existe tras instalar" >&2; exit 1; } +xattr -c "$app" +"$app/Contents/MacOS/rfirma" --version + +brew uninstall --cask "$tap/rfirma" +[ ! -e "$app" ] || { echo "$app sigue ahí tras desinstalar" >&2; exit 1; } +echo "cask: instala, arranca y desinstala" diff --git a/packaging/macos/verify-cask.test.sh b/packaging/macos/verify-cask.test.sh new file mode 100755 index 00000000..57e91207 --- /dev/null +++ b/packaging/macos/verify-cask.test.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +# Las pruebas de `verify-cask.sh` con un `brew` de mentira, sin red. +set -euo pipefail + +root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +verify="$root/packaging/macos/verify-cask.sh" +tmp="$(mktemp -d)" +trap 'rm -rf "$tmp"' EXIT + +mkdir -p "$tmp/bin" "$tmp/apps" "$tmp/repo" +printf 'dmg de prueba' > "$tmp/rfirma_1.0.0_aarch64.dmg" +expected_sha="$(shasum -a 256 "$tmp/rfirma_1.0.0_aarch64.dmg" | cut -d' ' -f1)" + +cat > "$tmp/cask.rb" <<'CASK' +cask "rfirma" do + version "0.0.0" + sha256 "0000000000000000000000000000000000000000000000000000000000000000" + + url "https://github.com/sgomez/rfirma/releases/download/v#{version}/rfirma_#{version}_aarch64.dmg" + app "rfirma.app" +end +CASK + +cat > "$tmp/bin/brew" <<'BREW' +#!/usr/bin/env bash +echo "$*" >> "$FAKE_BREW_LOG" +case "$1" in + tap-new) mkdir -p "$FAKE_REPO/Casks" ;; + --repository) echo "$FAKE_REPO" ;; + install) + cp "$FAKE_REPO/Casks/rfirma.rb" "$FAKE_INSTALLED_CASK" + mkdir -p "$APPLICATIONS_DIR/rfirma.app/Contents/MacOS" + printf '#!/bin/sh\necho "$*" > "$FAKE_LAUNCH_LOG"\n' > "$APPLICATIONS_DIR/rfirma.app/Contents/MacOS/rfirma" + chmod +x "$APPLICATIONS_DIR/rfirma.app/Contents/MacOS/rfirma" + ;; + uninstall) [ -n "${FAKE_KEEP_APP:-}" ] || rm -rf "$APPLICATIONS_DIR/rfirma.app" ;; +esac +BREW +chmod +x "$tmp/bin/brew" +printf '#!/bin/sh\n' > "$tmp/bin/xattr" +chmod +x "$tmp/bin/xattr" + +export PATH="$tmp/bin:$PATH" FAKE_REPO="$tmp/repo" FAKE_BREW_LOG="$tmp/brew.log" FAKE_LAUNCH_LOG="$tmp/launch.log" +export FAKE_INSTALLED_CASK="$tmp/installed.rb" APPLICATIONS_DIR="$tmp/apps" CASK_SOURCE="$tmp/cask.rb" + +fail() { echo "FALLO $*" >&2; exit 1; } + +"$verify" "$tmp/rfirma_1.0.0_aarch64.dmg" > "$tmp/out" 2>&1 || fail "debía pasar: $(cat "$tmp/out")" +grep -q '^tap-new --no-git' "$tmp/brew.log" || fail "el tap de usar y tirar debe crearse sin git" +grep -qF "url \"file://$tmp/rfirma_1.0.0_aarch64.dmg\"" "$tmp/installed.rb" || fail "el cask debe apuntar al dmg local" +grep -q "sha256 \"$expected_sha\"" "$tmp/installed.rb" || fail "el cask debe llevar el sha256 del dmg local" +grep -q 'app "rfirma.app"' "$tmp/installed.rb" || fail "el resto del cask debe quedar igual" +[ "$(cat "$tmp/launch.log")" = "--version" ] || fail "debe arrancar la app con --version" +grep -q '^uninstall --cask' "$tmp/brew.log" || fail "debe desinstalar" +[ ! -e "$tmp/apps/rfirma.app" ] || fail "la app debe haberse retirado" + +if FAKE_KEEP_APP=1 "$verify" "$tmp/rfirma_1.0.0_aarch64.dmg" > "$tmp/out" 2>&1; then + fail "si la desinstalación deja la app, tenía que fallar" +fi +grep -q 'sigue' "$tmp/out" || fail "esperaba el aviso de que la app sigue: $(cat "$tmp/out")" + +echo "OK verify-cask" diff --git a/packaging/repo/build-tree.sh b/packaging/repo/build-tree.sh index 02e3bb2a..b6c264e6 100755 --- a/packaging/repo/build-tree.sh +++ b/packaging/repo/build-tree.sh @@ -42,7 +42,7 @@ # /apt/ con dists/stable/main/binary-amd64/ # /rpm/ con repodata/ # /windows/ el `-setup.exe` y el `.sig` de cada version, y `latest.json` -# /macos/ el `.app.tar.gz` y el `.sig` de cada version que lo trae, y +# /macos/ el `.dmg`, el `.app.tar.gz` y el `.sig` de cada version que los trae, y # `latest.json` # # APT CON SUITE `stable` Y NO REPOSITORIO PLANO (ID-175): el plano es mas @@ -465,13 +465,16 @@ jq -n \ > "$arbol/windows/latest.json" # ----------------------------------------------------------------- 5. macos -- -# Como `windows/`, con las versiones estables que traen la actualizacion de macOS. +# Como `windows/`, con las versiones estables que traen la actualizacion de macOS y su `.dmg`. echo " macos: colocando las actualizaciones y latest.json" ultima_app="" for i in "${!versiones[@]}"; do [[ "${versiones[$i]}" == *-* ]] && continue [ -n "${app_de[$i]}" ] || continue cp "$serie/${versiones[$i]}/${app_de[$i]}" "$serie/${versiones[$i]}/${app_de[$i]}.sig" "$arbol/macos/" + while IFS= read -r nombre; do + cp "$serie/${versiones[$i]}/$nombre" "$arbol/macos/" + done < <(paquetes_del_formato "$serie/${versiones[$i]}" dmg) ultima_app="$i" done if [ -z "$ultima_app" ]; then diff --git a/packaging/repo/build-tree.test.sh b/packaging/repo/build-tree.test.sh index 1433398e..417da587 100755 --- a/packaging/repo/build-tree.test.sh +++ b/packaging/repo/build-tree.test.sh @@ -81,12 +81,13 @@ actualizacion_de_macos_de_pega() { local dir="$1" version="$2" echo "app $version" > "$dir/rFirma_${version}_aarch64.app.tar.gz" printf 'firma mac de %s\n' "$version" > "$dir/rFirma_${version}_aarch64.app.tar.gz.sig" + echo "dmg $version" > "$dir/rFirma_${version}_aarch64.dmg" "$raiz/scripts/packages-manifest.sh" write "$dir" } sin_actualizacion_de_macos() { local dir="$1" - rm -f "$dir"/*.app.tar.gz "$dir"/*.app.tar.gz.sig + rm -f "$dir"/*.app.tar.gz "$dir"/*.app.tar.gz.sig "$dir"/*.dmg jq 'map(select(.platform != "macos"))' "$dir/paquetes.json" > "$dir/paquetes.json.nuevo" mv "$dir/paquetes.json.nuevo" "$dir/paquetes.json" } @@ -289,6 +290,7 @@ echo "de mas" > "$serie/v0.4.10/rFirma_9.9.9_x64-setup.exe.sig" echo "de mas" > "$serie/v0.4.10/subido-a-mano.exe" echo "de mas" > "$serie/v0.4.10/rFirma_9.9.9_aarch64.app.tar.gz" echo "de mas" > "$serie/v0.4.10/rFirma_9.9.9_aarch64.app.tar.gz.sig" +echo "de mas" > "$serie/v0.4.10/rFirma_9.9.9_aarch64.dmg" # --------------------------------------------------------------------------- # El arbol, dos veces y en dos directorios distintos @@ -506,16 +508,17 @@ fi # --------------------------------------------------------------------------- # /macos/: la actualizacion y latest.json # --------------------------------------------------------------------------- -# macos_has_the_update_and_signature_of_every_version_that_brings_it +# macos_has_the_dmg_update_and_signature_of_every_version_that_brings_them faltan_de_macos=0 for version in 0.4.2 0.4.10; do - for fichero in "rFirma_${version}_aarch64.app.tar.gz" "rFirma_${version}_aarch64.app.tar.gz.sig"; do + for fichero in "rFirma_${version}_aarch64.dmg" "rFirma_${version}_aarch64.app.tar.gz" "rFirma_${version}_aarch64.app.tar.gz.sig"; do [ -f "$tmp/arbol/macos/$fichero" ] || { faltan_de_macos=1; echo " falta $fichero" >&2; } done done if [ "$faltan_de_macos" -eq 0 ] && [ -z "$(find "$tmp/arbol/macos" -name '*0.4.0*' -print -quit)" ] \ - && [ ! -e "$tmp/arbol/macos/rFirma_9.9.9_aarch64.app.tar.gz" ]; then - ok "macos/ trae la actualizacion y su .sig de cada version que la trae, y nada fuera del manifiesto" + && [ ! -e "$tmp/arbol/macos/rFirma_9.9.9_aarch64.app.tar.gz" ] \ + && [ ! -e "$tmp/arbol/macos/rFirma_9.9.9_aarch64.dmg" ]; then + ok "macos/ trae el .dmg, la actualizacion y su .sig de cada version que los trae, y nada fuera del manifiesto" else fail "macos/ no trae lo que dicen los manifiestos de la serie" fi