ci: check staticness by reading the ELF, not by ldd
The runner image has no ldd, and `ldd X | grep -q` fails silently when it is missing — the pipe eats the error and grep simply finds nothing, so a good build died with no message. Reading the program headers for PT_INTERP needs nothing but python3, and is what 'static' actually means. The upload step also falls back to the id-based asset path when the API sends no upload_url. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
799d4ce parent: a977b7e modified
.rickub/workflows/release.yml +12 -3 | @@ -105,9 +105,11 @@ jobs: | ||
| 105 | 105 | VERSION="${TAG#v}" |
| 106 | 106 | NAME="nimstatic-${VERSION}-x86_64-linux" |
| 107 | 107 | ./nimstatic-host src/nimstatic.nim -o "$NAME" -- -d:ssl --passL:-s |
| 108 | - # `file` is not on the runner image; ldd answers the only question | |
| 109 | - # that matters, and running it proves the thing actually starts. | |
| 110 | - ldd "$NAME" 2>&1 | grep -q "not a dynamic executable" | |
| 108 | + # Neither file(1) nor ldd is on the runner image, and `ldd … | grep` | |
| 109 | + # fails silently when ldd is missing — the pipe eats the error and | |
| 110 | + # grep just finds nothing. Read the ELF program headers instead: a | |
| 111 | + # PT_INTERP entry (type 3) is what makes a binary dynamic. | |
| 112 | + 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" | |
| 111 | 113 | ./"$NAME" --help > /dev/null |
| 112 | 114 | xz -9e -k "$NAME" |
| 113 | 115 | sha256sum "$NAME" "$NAME.xz" > SHA256SUMS |
| @@ -134,7 +136,14 @@ jobs: | ||
| 134 | 136 | -d "{\"tag_name\":\"$TAG\",\"name\":\"nimstatic $TAG\"}" > release.json |
| 135 | 137 | fi |
| 136 | 138 | |
| 139 | + # Prefer the upload_url the API advertises; fall back to the | |
| 140 | + # id-based path when this forge does not send one. | |
| 137 | 141 | upload_url=$(field upload_url < release.json | cut -d'{' -f1) |
| 142 | + if [ -z "$upload_url" ]; then | |
| 143 | + release_id=$(field id < release.json) | |
| 144 | + [ -n "$release_id" ] || { echo "no release id in:"; cat release.json; exit 1; } | |
| 145 | + upload_url="$GITHUB_API_URL/repos/$GITHUB_REPOSITORY/releases/$release_id/assets" | |
| 146 | + fi | |
| 138 | 147 | echo "uploading to $upload_url" |
| 139 | 148 | |
| 140 | 149 | for asset in "$NAME" "$NAME.xz" SHA256SUMS; do |
| @@ -105,9 +105,11 @@ jobs: | |||
| 105 | VERSION="${TAG#v}" | 105 | VERSION="${TAG#v}" |
| 106 | NAME="nimstatic-${VERSION}-x86_64-linux" | 106 | NAME="nimstatic-${VERSION}-x86_64-linux" |
| 107 | ./nimstatic-host src/nimstatic.nim -o "$NAME" -- -d:ssl --passL:-s | 107 | ./nimstatic-host src/nimstatic.nim -o "$NAME" -- -d:ssl --passL:-s |
| 108 | - # `file` is not on the runner image; ldd answers the only question | 108 | + # Neither file(1) nor ldd is on the runner image, and `ldd … | grep` |
| 109 | - # that matters, and running it proves the thing actually starts. | 109 | + # fails silently when ldd is missing — the pipe eats the error and |
| 110 | - ldd "$NAME" 2>&1 | grep -q "not a dynamic executable" | 110 | + # grep just finds nothing. Read the ELF program headers instead: a |
| 111 | + # PT_INTERP entry (type 3) is what makes a binary dynamic. | ||
| 112 | + 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" | ||
| 111 | ./"$NAME" --help > /dev/null | 113 | ./"$NAME" --help > /dev/null |
| 112 | xz -9e -k "$NAME" | 114 | xz -9e -k "$NAME" |
| 113 | sha256sum "$NAME" "$NAME.xz" > SHA256SUMS | 115 | sha256sum "$NAME" "$NAME.xz" > SHA256SUMS |
| @@ -134,7 +136,14 @@ jobs: | |||
| 134 | -d "{\"tag_name\":\"$TAG\",\"name\":\"nimstatic $TAG\"}" > release.json | 136 | -d "{\"tag_name\":\"$TAG\",\"name\":\"nimstatic $TAG\"}" > release.json |
| 135 | fi | 137 | fi |
| 136 | 138 | ||
| 139 | + # Prefer the upload_url the API advertises; fall back to the | ||
| 140 | + # id-based path when this forge does not send one. | ||
| 137 | upload_url=$(field upload_url < release.json | cut -d'{' -f1) | 141 | upload_url=$(field upload_url < release.json | cut -d'{' -f1) |
| 142 | + if [ -z "$upload_url" ]; then | ||
| 143 | + release_id=$(field id < release.json) | ||
| 144 | + [ -n "$release_id" ] || { echo "no release id in:"; cat release.json; exit 1; } | ||
| 145 | + upload_url="$GITHUB_API_URL/repos/$GITHUB_REPOSITORY/releases/$release_id/assets" | ||
| 146 | + fi | ||
| 138 | echo "uploading to $upload_url" | 147 | echo "uploading to $upload_url" |
| 139 | 148 | ||
| 140 | for asset in "$NAME" "$NAME.xz" SHA256SUMS; do | 149 | for asset in "$NAME" "$NAME.xz" SHA256SUMS; do |
modified
nimstatic.nimble +1 -1 | @@ -1,4 +1,4 @@ | ||
| 1 | -version = "0.1.4" | |
| 1 | +version = "0.1.5" | |
| 2 | 2 | author = "nandi" |
| 3 | 3 | description = "Fully static Nim binaries, dependencies and all — detects what you link and fetches musl archives from Alpine" |
| 4 | 4 | license = "MIT" |
| @@ -1,4 +1,4 @@ | |||
| 1 | -version = "0.1.4" | 1 | +version = "0.1.5" |
| 2 | author = "nandi" | 2 | author = "nandi" |
| 3 | description = "Fully static Nim binaries, dependencies and all — detects what you link and fetches musl archives from Alpine" | 3 | description = "Fully static Nim binaries, dependencies and all — detects what you link and fetches musl archives from Alpine" |
| 4 | license = "MIT" | 4 | license = "MIT" |