diff --git a/.gitattributes b/.gitattributes index a1ec394..29276dc 100644 --- a/.gitattributes +++ b/.gitattributes @@ -9,4 +9,3 @@ *.png binary .github export-ignore -Tests export-ignore diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 26c12e9..d9f6037 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -67,6 +67,11 @@ jobs: - name: Test signing identity resolution run: Scripts/ci/test-signing-identity-resolution.sh + - name: Test source release archive + env: + RESIZELINT_VERIFY_FORMULA_CHECKSUM: ${{ matrix.runner == 'macos-26' && '1' || '0' }} + run: Scripts/release/test-source-archive.sh + - name: Validate machine contracts run: Scripts/ci/validate-machine-contracts.py diff --git a/Formula/resizelint.rb b/Formula/resizelint.rb index 1d576e4..e968afb 100644 --- a/Formula/resizelint.rb +++ b/Formula/resizelint.rb @@ -2,7 +2,7 @@ class Resizelint < Formula desc "Static analysis for Swift apps that need to work in every window size" homepage "https://github.com/mikonyaa/ResizeLint" url "https://github.com/mikonyaa/ResizeLint/releases/download/1.0.0/ResizeLint-1.0.0-source.tar.gz" - sha256 "fea82e67b1c23fbfea201d7d326b553d98847bdfed8cf2164053f2e1be33fac1" + sha256 "4cea87f9dcc2e418f97cd3b4f5c2ac8d66f817b3a74962d92e8cef4f7328f173" license "MIT" depends_on "swift" => :build diff --git a/Scripts/release/build-source-archive.sh b/Scripts/release/build-source-archive.sh index 62d1edf..2bf3ea7 100755 --- a/Scripts/release/build-source-archive.sh +++ b/Scripts/release/build-source-archive.sh @@ -22,41 +22,24 @@ temporary_root=$(mktemp -d "${TMPDIR:-/tmp}/resizelint-source.XXXXXX") trap 'rm -rf "$temporary_root"' EXIT prefix="ResizeLint-$version" -staging="$temporary_root/staging" -mkdir -p "$staging/$prefix" - +uncompressed="$temporary_root/source.tar" ( cd "$repository_root" - git checkout-index -a --prefix="$staging/$prefix/" -) - -rm -rf \ - "$staging/$prefix/.build" \ - "$staging/$prefix/.github" \ - "$staging/$prefix/Artifacts" \ - "$staging/$prefix/Formula" - -find "$staging/$prefix" -exec touch -h -t 200001010000 {} + -listing="$temporary_root/files.txt" -( - cd "$staging" - LC_ALL=C find "$prefix" -print | LC_ALL=C sort > "$listing" + tree=$(git write-tree) + git archive \ + --format=tar \ + --mtime="2000-01-01T00:00:00Z" \ + --prefix="$prefix/" \ + --output="$uncompressed" \ + "$tree" \ + -- \ + . \ + ':(exclude).build' \ + ':(exclude).github' \ + ':(exclude)Artifacts' \ + ':(exclude)Formula' ) -uncompressed="$temporary_root/source.tar" -tar -cf "$uncompressed" \ - --format=ustar \ - --uid 0 \ - --gid 0 \ - --uname root \ - --gname root \ - --no-acls \ - --no-fflags \ - --no-mac-metadata \ - --no-xattrs \ - -C "$staging" \ - -T "$listing" - archive="$destination/ResizeLint-$version-source.tar.gz" gzip -9 -n -c "$uncompressed" > "$archive" printf '%s\n' "$archive" diff --git a/Scripts/release/test-source-archive.sh b/Scripts/release/test-source-archive.sh index e5c98f2..a6ba7fa 100755 --- a/Scripts/release/test-source-archive.sh +++ b/Scripts/release/test-source-archive.sh @@ -4,6 +4,7 @@ set -euo pipefail repository_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd -P) builder="$repository_root/Scripts/release/build-source-archive.sh" +formula="$repository_root/Formula/resizelint.rb" if [[ ! -x "$builder" ]]; then echo "Source archive builder is missing or not executable: $builder" >&2 @@ -13,8 +14,20 @@ fi temporary_root=$(mktemp -d "${TMPDIR:-/tmp}/resizelint-source-test.XXXXXX") trap 'rm -rf "$temporary_root"' EXIT -first=$($builder "$temporary_root/first" 1.0.0) -second=$($builder "$temporary_root/second" 1.0.0) +fake_bin="$temporary_root/bin" +mkdir -p "$fake_bin" +real_tar=$(command -v tar) +cat > "$fake_bin/tar" <<'EOF' +#!/usr/bin/env bash + +set -euo pipefail +echo "Source archive builder must not invoke system tar" >&2 +exit 66 +EOF +chmod 0755 "$fake_bin/tar" + +first=$(PATH="$fake_bin:$PATH" "$builder" "$temporary_root/first" 1.0.0) +second=$(PATH="$fake_bin:$PATH" "$builder" "$temporary_root/second" 1.0.0) first_hash=$(shasum -a 256 "$first" | awk '{ print $1 }') second_hash=$(shasum -a 256 "$second" | awk '{ print $1 }') @@ -23,8 +36,20 @@ if [[ "$first_hash" != "$second_hash" ]]; then exit 1 fi +if [[ "${RESIZELINT_VERIFY_FORMULA_CHECKSUM:-0}" == "1" ]]; then + formula_hash=$(awk '/^[[:space:]]*sha256 / { gsub(/"/, "", $2); print $2; exit }' "$formula") + if [[ -z "$formula_hash" ]]; then + echo "Homebrew formula does not declare a source archive checksum" >&2 + exit 1 + fi + if [[ "$formula_hash" != "$first_hash" ]]; then + echo "Homebrew formula checksum is $formula_hash; source archive checksum is $first_hash" >&2 + exit 1 + fi +fi + listing="$temporary_root/listing.txt" -tar -tzf "$first" > "$listing" +"$real_tar" -tzf "$first" > "$listing" grep -q '^ResizeLint-1.0.0/Package.swift$' "$listing" grep -q '^ResizeLint-1.0.0/Tests/ResizeLintCoreTests/VersionTests.swift$' "$listing" @@ -33,4 +58,7 @@ if grep -Eq '(^|/)Formula/|(^|/)\.build/|(^|/)\.git/' "$listing"; then exit 1 fi -echo "Source archive self-test passed: $first_hash" +echo "Source archive reproducibility test passed: $first_hash" +if [[ "${RESIZELINT_VERIFY_FORMULA_CHECKSUM:-0}" == "1" ]]; then + echo "Homebrew formula checksum test passed." +fi