nandi/nimstaticpublic Fork 0
bc5f3d7349721bd1eede7f9f7e7ffbaca92e9e16
Commits
Clone
git clone https://git.rickub.com/nandi/nimstatic.git
git clone ssh://git@rickub.com/nandi/nimstatic.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

ci: check staticness by reading the ELF, not by ldd 799d4ce · on bc5f3d7349721bd1eede7f9f7e7ffbaca92e9e16 · nandi · 14h ago
release.yml · 167 lines · 7.3 KBYAML Blame HistoryRaw
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
# Cut a release binary when a v* tag is pushed.
#
# nimstatic builds itself: a throwaway host build of the tool produces the
# static one that ships, which means the release artifact is also the test that
# the tool works on a clean machine.
name: Release

on:
  push:
    tags: ["v*"]
  workflow_dispatch:
    inputs:
      tag:
        description: "Existing tag to rebuild; defaults to the newest v* tag"
        required: false

permissions:
  contents: write        # required — a job is read-only unless it asks

env:
  NIM_VERSION: "2.2.4"
  ZIG_VERSION: "0.15.1"

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0     # tags, so a dispatch can find the latest one

      # One place decides which tag is being released: the dispatch input, the
      # tag that triggered the run, or — for a bare dispatch, which carries no
      # inputs through the API — the newest v* tag. Anything else is a mistake
      # worth stopping for, since the alternative is a release named after a
      # branch.
      - name: Resolve the tag
        env:
          INPUT_TAG: ${{ inputs.tag }}
        run: |
          set -euo pipefail
          # Every variable here is read with a default: this runner does not
          # set GITHUB_REF_TYPE, and under `set -u` one missing name is a dead
          # job three steps before anything interesting happens.
          REF="${GITHUB_REF:-}"
          if [ -n "${INPUT_TAG:-}" ]; then
            TAG="$INPUT_TAG"
          else
            case "$REF" in
              refs/tags/*) TAG="${REF#refs/tags/}" ;;
              *)
                # A dispatch: no tag in the ref, so ask the API for the newest.
                TAG=$(curl -sSfL -H "Authorization: Bearer $GITHUB_TOKEN" \
                        "$GITHUB_API_URL/repos/$GITHUB_REPOSITORY/tags" \
                      | python3 -c "import json,sys; ts=[t['name'] for t in json.load(sys.stdin) if t['name'].startswith('v')]; print(ts[0] if ts else '')")
                ;;
            esac
          fi
          case "$TAG" in
            v*) ;;
            *) echo "refusing to release '$TAG': not a v* tag" >&2; exit 1 ;;
          esac
          # The runner hands a tag build the right tree already; a dispatch
          # gets whatever branch it ran on, so move only when we can.
          if [ -d .git ]; then
            git checkout --detach "$TAG" 2>/dev/null || echo "note: building the checked-out tree, not $TAG"
          fi
          echo "releasing $TAG"
          echo "TAG=$TAG" >> "$GITHUB_ENV"

      # Toolchains come straight from upstream tarballs rather than setup
      # actions: two curls, pinned versions, nothing else to trust.
      - name: Install Nim and zig
        run: |
          set -euo pipefail
          mkdir -p "$HOME/toolchains"
          curl -sSfL "https://nim-lang.org/download/nim-${NIM_VERSION}-linux_x64.tar.xz" \
            | tar -xJ -C "$HOME/toolchains"
          curl -sSfL "https://ziglang.org/download/${ZIG_VERSION}/zig-x86_64-linux-${ZIG_VERSION}.tar.xz" \
            | tar -xJ -C "$HOME/toolchains"
          echo "$HOME/toolchains/nim-${NIM_VERSION}/bin" >> "$GITHUB_PATH"
          echo "$HOME/toolchains/zig-x86_64-linux-${ZIG_VERSION}" >> "$GITHUB_PATH"

      - name: Versions
        run: |
          nim --version | head -1
          zig version

      # Alpine packages are cached so a re-run does not re-download them.
      - uses: actions/cache@v4
        with:
          path: ~/.cache/nimstatic
          key: nimstatic-alpine-${{ runner.os }}-v3.21

      - name: Test
        id: test
        run: nim c -d:ssl --hints:off -r tests/test_nimstatic.nim

      - name: Bootstrap nimstatic on the host
        run: nim c -d:release -d:ssl --hints:off -o:nimstatic-host src/nimstatic.nim

      - name: Build the static binary with itself
        run: |
          set -euo pipefail
          VERSION="${TAG#v}"
          NAME="nimstatic-${VERSION}-x86_64-linux"
          ./nimstatic-host src/nimstatic.nim -o "$NAME" -- -d:ssl --passL:-s
          # Neither file(1) nor ldd is on the runner image, and `ldd … | grep`
          # fails silently when ldd is missing — the pipe eats the error and
          # grep just finds nothing. Read the ELF program headers instead: a
          # PT_INTERP entry (type 3) is what makes a binary dynamic.
          python3 -c "import struct,sys;d=open(sys.argv[1],'rb').read();assert d[:4]==b'\x7fELF';off=struct.unpack_from('<Q',d,0x20)[0];esz=struct.unpack_from('<H',d,0x36)[0];n=struct.unpack_from('<H',d,0x38)[0];t=[struct.unpack_from('<I',d,off+i*esz)[0] for i in range(n)];sys.exit('dynamic: has PT_INTERP' if 3 in t else 0)" "$NAME"
          ./"$NAME" --help > /dev/null
          xz -9e -k "$NAME"
          sha256sum "$NAME" "$NAME.xz" > SHA256SUMS
          cat SHA256SUMS
          echo "NAME=$NAME" >> "$GITHUB_ENV"

      # The release exists already (the tag was pushed, or it is being rebuilt),
      # so this attaches assets to it rather than creating one. The upload host
      # is whatever the API itself advertises in upload_url, which is the one
      # value that cannot go stale.
      # The release exists already (the tag was pushed, or it is being rebuilt),
      # so this attaches assets to it rather than creating one. The upload host
      # is whatever the API advertises in upload_url — the one value that
      # cannot go stale.
      - name: Attach the binaries to the release
        run: |
          set -euo pipefail
          api() { curl -sSfL -H "Authorization: Bearer $GITHUB_TOKEN" \
                       -H "Accept: application/vnd.github+json" "$@"; }
          field() { python3 -c "import json,sys; print(json.load(sys.stdin).get(sys.argv[1],''))" "$1"; }

          if ! api "$GITHUB_API_URL/repos/$GITHUB_REPOSITORY/releases/tags/$TAG" > release.json; then
            api -X POST "$GITHUB_API_URL/repos/$GITHUB_REPOSITORY/releases" \
                -d "{\"tag_name\":\"$TAG\",\"name\":\"nimstatic $TAG\"}" > release.json
          fi

          # Prefer the upload_url the API advertises; fall back to the
          # id-based path when this forge does not send one.
          upload_url=$(field upload_url < release.json | cut -d'{' -f1)
          if [ -z "$upload_url" ]; then
            release_id=$(field id < release.json)
            [ -n "$release_id" ] || { echo "no release id in:"; cat release.json; exit 1; }
            upload_url="$GITHUB_API_URL/repos/$GITHUB_REPOSITORY/releases/$release_id/assets"
          fi
          echo "uploading to $upload_url"

          for asset in "$NAME" "$NAME.xz" SHA256SUMS; do
            # Replace an asset of the same name, so a re-run is idempotent.
            existing=$(python3 -c "import json,sys; print(next((a['url'] for a in (json.load(open('release.json')).get('assets') or []) if a['name']==sys.argv[1]),''))" "$asset")
            if [ -n "$existing" ]; then
              api -X DELETE "$existing" > /dev/null
            fi
            api -X POST "$upload_url?name=$asset" \
                -H "Content-Type: application/octet-stream" \
                --data-binary "@$asset" > /dev/null
            echo "uploaded $asset"
          done

      - uses: actions/upload-artifact@v4
        with:
          name: nimstatic-x86_64-linux
          path: |
            nimstatic-*-x86_64-linux
            nimstatic-*-x86_64-linux.xz
            SHA256SUMS