Skip to content

Commit 63cc5f7

Browse files
Merge pull request #548 from roc-lang/feat/linker-inputs
Publish independently built linker inputs
2 parents f2fb7f3 + 7c59c6f commit 63cc5f7

7 files changed

Lines changed: 561 additions & 0 deletions

File tree

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
name: Linker inputs
2+
on:
3+
workflow_dispatch:
4+
pull_request:
5+
paths:
6+
- '.github/workflows/linker-inputs.yml'
7+
- 'native/linker-inputs/**'
8+
- 'native/windows/**'
9+
- 'scripts/linker_inputs.py'
10+
- 'scripts/windows_runtime.py'
11+
- 'scripts/test_linker_inputs.py'
12+
permissions: {}
13+
jobs:
14+
build:
15+
strategy:
16+
fail-fast: false
17+
matrix:
18+
include:
19+
- {target: x64musl, os: ubuntu-24.04}
20+
- {target: arm64musl, os: ubuntu-24.04-arm}
21+
- {target: x64win, os: windows-2025}
22+
runs-on: ${{ matrix.os }}
23+
permissions:
24+
contents: read
25+
steps:
26+
- uses: actions/checkout@v7
27+
- uses: actions/setup-python@v6
28+
with:
29+
python-version: '3.12'
30+
- uses: mlugg/setup-zig@8d6198c65fb0feaa111df26e6b467fea8345e46f
31+
with:
32+
version: 0.17.0
33+
- name: Test artifact verification and caching
34+
run: python -m unittest scripts/test_linker_inputs.py
35+
- name: Build runtime inputs
36+
run: python scripts/linker_inputs.py build --target ${{ matrix.target }} --directory dist/linker-inputs
37+
- uses: actions/upload-artifact@v7
38+
with:
39+
name: linker-inputs-${{ matrix.target }}
40+
path: dist/linker-inputs/*
41+
if-no-files-found: error
42+
publish:
43+
needs: build
44+
if: github.event_name == 'workflow_dispatch' && github.ref_name == github.event.repository.default_branch
45+
runs-on: ubuntu-24.04
46+
permissions:
47+
contents: write
48+
steps:
49+
- uses: actions/checkout@v7
50+
- uses: actions/download-artifact@v8
51+
with:
52+
pattern: linker-inputs-*
53+
merge-multiple: true
54+
path: dist/linker-inputs
55+
- name: Verify and publish immutable runtime release
56+
env:
57+
GH_TOKEN: ${{ github.token }}
58+
run: |
59+
tag=$(python3 scripts/linker_inputs.py manifest --directory dist/linker-inputs --repository "$GITHUB_REPOSITORY")
60+
if gh release view "$tag" >/dev/null 2>&1; then
61+
mkdir -p target/published-inputs
62+
gh release download "$tag" --dir target/published-inputs
63+
diff -r dist/linker-inputs target/published-inputs
64+
else
65+
gh release create "$tag" dist/linker-inputs/* --target "$GITHUB_SHA" --prerelease --latest=false --title "Linker inputs ${tag#linker-inputs-}" --notes "Independently built native runtime inputs. Consumers pin the attached manifest and archive SHA-256 hashes."
66+
fi

‎native/linker-inputs/README.md‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Linker inputs
2+
3+
The Linker inputs workflow builds runtime archives independently of the Rust
4+
host. Run it manually on the default branch to publish a content-addressed
5+
prerelease. The attached `linker-inputs.lock.json` pins each target archive by
6+
URL, SHA-256, and size. Platform builds will consume that lock after migration.
7+
8+
The producer pins musl 1.2.6's source archive and Zig 0.17.0's libunwind source
9+
tree. Linux libraries are PIC and use baseline CPUs. The recorded musl change
10+
allows an absent dynamic section in `rcrt1.o`, supporting fixed-address static
11+
executables as well as static PIE. Native producer checks exercise both forms,
12+
including stored function pointers, thread-local data, and threads.
13+
14+
Windows uses the existing source-verified startup and import-library producer.
15+
Its native check exercises initializer tables, TLS, security cookies, and
16+
MSVC type-info support. Source hashes and original licenses accompany each
17+
archive. Rust host libraries are built by the platform consumer workflows.
18+
19+
For local production, use Python 3.12, Zig 0.17.0, and GNU make. Linux native
20+
checks also use the platform GNU linker and readelf. For example,
21+
22+
```sh
23+
python3 scripts/linker_inputs.py build --target x64musl --directory dist/linker-inputs
24+
```
25+
26+
Publication requires each target to have passed validation on its native OS and
27+
architecture. The workflow publishes only after all three producers succeed.

‎native/linker-inputs/recipe.json‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"format_version": 1,
3+
"zig_version": "0.17.0",
4+
"musl": {
5+
"version": "1.2.6",
6+
"url": "https://musl.libc.org/releases/musl-1.2.6.tar.gz",
7+
"sha256": "d585fd3b613c66151fc3249e8ed44f77020cb5e6c1e635a616d3f9f82460512a"
8+
},
9+
"unwind": {
10+
"source": "Zig 0.17.0 bundled LLVM libunwind",
11+
"tree_sha256": "672d3a4e64506cbe64b50742748afba1a84ac8090ba90419202214012fe8e8b6"
12+
},
13+
"targets": {
14+
"x64musl": "x86_64-linux-musl",
15+
"arm64musl": "aarch64-linux-musl",
16+
"x64win": "x86_64-windows-msvc"
17+
}
18+
}

‎native/linker-inputs/startup.c‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#include <pthread.h>
2+
#include <stdio.h>
3+
static const char *text(void) { return "startup passed"; }
4+
static const char *(*volatile callback)(void) = text;
5+
static _Thread_local int value = 42;
6+
static void *worker(void *unused) {
7+
(void)unused;
8+
if (value != 42) return (void *)1;
9+
value = 99;
10+
return 0;
11+
}
12+
int main(void) {
13+
pthread_t thread;
14+
void *result;
15+
if (pthread_create(&thread, 0, worker, 0) || pthread_join(thread, &result)) return 1;
16+
if (result || value != 42) return 2;
17+
puts(callback());
18+
return 0;
19+
}

‎native/linker-inputs/unwind.cpp‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Exercise exception handling and destructor execution in the producer toolchain.
2+
#include <stdio.h>
3+
static int destroyed;
4+
struct Cleanup { ~Cleanup() { destroyed = 1; } };
5+
int main() {
6+
try {
7+
Cleanup cleanup;
8+
throw 42;
9+
} catch (int value) {
10+
if (value == 42 && destroyed) {
11+
puts("unwind passed");
12+
return 0;
13+
}
14+
}
15+
return 1;
16+
}

0 commit comments

Comments
 (0)