Skip to content

Commit 721ec5d

Browse files
committed
feat: add WASIX extension assets and release workflows
Generate packaged WASIX extension metadata, portable assets, and source-only target AOT crate templates for promoted SQL extensions. Unify runtime/root setup, protocol correctness paths, pg_dump/data-dir APIs, and extension smoke coverage around the Wasmer/WASIX backend. Operationalize maintainer workflows with path-aware CI, source-free committed asset verification, CI-generated AOT release artifacts, release-plz publishing, dependency invariants, and updated release docs.
1 parent 05d42e5 commit 721ec5d

123 files changed

Lines changed: 21350 additions & 112047 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
name: Setup Rust Tools
2+
description: Install the pinned Rust toolchain, cache Cargo output, and install optional Cargo tools.
3+
4+
inputs:
5+
toolchain:
6+
description: Rust toolchain version.
7+
required: false
8+
default: "1.92"
9+
components:
10+
description: Comma-separated Rust components.
11+
required: false
12+
default: ""
13+
cache:
14+
description: Whether to enable the Cargo cache.
15+
required: false
16+
default: "true"
17+
cache-workspaces:
18+
description: Workspace mapping for Swatinem/rust-cache.
19+
required: false
20+
default: ". -> target"
21+
cache-save-if:
22+
description: Expression string passed to Swatinem/rust-cache save-if.
23+
required: false
24+
default: "true"
25+
tools:
26+
description: Comma-separated tools for taiki-e/install-action.
27+
required: false
28+
default: ""
29+
30+
runs:
31+
using: composite
32+
steps:
33+
- name: Install Rust toolchain
34+
uses: dtolnay/rust-toolchain@3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9
35+
with:
36+
toolchain: ${{ inputs.toolchain }}
37+
components: ${{ inputs.components }}
38+
39+
- name: Cache Cargo output
40+
if: ${{ inputs.cache == 'true' }}
41+
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32
42+
with:
43+
workspaces: ${{ inputs.cache-workspaces }}
44+
save-if: ${{ inputs.cache-save-if }}
45+
46+
- name: Install Cargo tools
47+
if: ${{ inputs.tools != '' }}
48+
uses: taiki-e/install-action@1f2425cdb59f8fffb99ee16a5968edf6f57a2b93
49+
with:
50+
tool: ${{ inputs.tools }}

.github/pull_request_template.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,13 @@
44

55
- [ ] Package/API/runtime change: PR title uses `feat:`, `fix:`, `perf:`, `refactor:`, `revert:`, or a breaking `!`.
66
- [ ] Docs/CI/repository-only change: no release intended.
7+
- [ ] Asset/source-spine change: source pins/fingerprints are current and the Assets workflow will generate/test release artifacts.
78

89
## Verification
910

10-
- [ ] `scripts/validate.sh ci`
11-
- [ ] `scripts/validate.sh release`
11+
- [ ] `scripts/validate.sh repo`
12+
- [ ] `scripts/validate.sh artifacts`
13+
- [ ] `scripts/validate.sh lint`
14+
- [ ] `scripts/validate.sh test`
15+
- [ ] `scripts/validate.sh package` when published package contents changed
1216
- [ ] `cargo deny check`

.github/scripts/check-release-changelog.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ EOF
9999
exit 1
100100
fi
101101

102-
expected_unreleased="[Unreleased]: https://github.com/f0rr0/pglite-oxide/compare/pglite-oxide-v${package_version}...HEAD"
102+
expected_unreleased="[Unreleased]: https://github.com/f0rr0/pglite-oxide/compare/${package_version}...HEAD"
103103

104104
if ! grep -Fxq "${expected_unreleased}" CHANGELOG.md; then
105105
cat >&2 <<EOF

.github/scripts/check-release-intent.sh

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,14 @@ if [[ -z "${base_versions}" || -z "${head_versions}" ]]; then
6767
exit 1
6868
fi
6969

70-
if [[ "${base_versions}" != "${head_versions}" && "${is_release_pr}" != true ]]; then
70+
changed_existing_versions="$(
71+
join -t $'\t' \
72+
<(printf '%s\n' "${base_versions}" | sed 's/=/\t/' | sort -t $'\t' -k1,1) \
73+
<(printf '%s\n' "${head_versions}" | sed 's/=/\t/' | sort -t $'\t' -k1,1) |
74+
awk -F '\t' '$2 != $3 { print $1 "=" $2 " -> " $3 }'
75+
)"
76+
77+
if [[ -n "${changed_existing_versions}" && "${is_release_pr}" != true ]]; then
7178
cat >&2 <<EOF
7279
This PR changes one or more workspace package versions.
7380
@@ -86,6 +93,9 @@ ${base_versions}
8693
8794
Head package versions:
8895
${head_versions}
96+
97+
Changed existing package versions:
98+
${changed_existing_versions}
8999
EOF
90100
exit 1
91101
fi
@@ -94,7 +104,7 @@ while IFS= read -r file; do
94104
[[ -z "${file}" ]] && continue
95105

96106
case "${file}" in
97-
Cargo.toml | Cargo.lock | build.rs | src/* | assets/* | crates/* | xtask/* | examples/* | benches/*)
107+
Cargo.toml | build.rs | src/* | crates/*)
98108
affected_files+=("${file}")
99109
;;
100110
esac
@@ -125,8 +135,9 @@ Breaking changes may use any type with !, for example:
125135
release-plz PRs are exempt only when their branch starts with release-plz- and
126136
their title starts with chore(release):.
127137
128-
Docs, CI, issue-template, and repository-only changes can keep non-release types
129-
such as docs:, ci:, chore:, style:, or test: when they do not touch package code.
138+
Docs, CI, tests, examples, xtask-only maintenance, source-checkout scripts, and
139+
other repository-only changes can keep non-release types such as docs:, ci:,
140+
chore:, style:, or test: when they do not touch published package code.
130141
131142
Received:
132143
${subject}
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
: "${GITHUB_REPOSITORY:?GITHUB_REPOSITORY is required}"
5+
: "${GITHUB_SHA:?GITHUB_SHA is required}"
6+
: "${GITHUB_TOKEN:?GITHUB_TOKEN is required}"
7+
8+
python3 - <<'PY'
9+
import io
10+
import json
11+
import os
12+
import sys
13+
import urllib.request
14+
import zipfile
15+
from pathlib import Path
16+
17+
repo = os.environ["GITHUB_REPOSITORY"]
18+
sha = os.environ["GITHUB_SHA"]
19+
token = os.environ["GITHUB_TOKEN"]
20+
targets = [
21+
"aarch64-apple-darwin",
22+
"x86_64-apple-darwin",
23+
"x86_64-unknown-linux-gnu",
24+
"aarch64-unknown-linux-gnu",
25+
"x86_64-pc-windows-msvc",
26+
]
27+
28+
29+
def github_json(url: str) -> dict:
30+
request = urllib.request.Request(
31+
url,
32+
headers={
33+
"Accept": "application/vnd.github+json",
34+
"Authorization": f"Bearer {token}",
35+
"X-GitHub-Api-Version": "2022-11-28",
36+
},
37+
)
38+
with urllib.request.urlopen(request) as response:
39+
return json.load(response)
40+
41+
42+
def github_bytes(url: str) -> bytes:
43+
request = urllib.request.Request(
44+
url,
45+
headers={
46+
"Accept": "application/vnd.github+json",
47+
"Authorization": f"Bearer {token}",
48+
"X-GitHub-Api-Version": "2022-11-28",
49+
},
50+
)
51+
with urllib.request.urlopen(request) as response:
52+
return response.read()
53+
54+
55+
def extract_artifact(data: bytes) -> None:
56+
root = Path(".").resolve()
57+
with zipfile.ZipFile(io.BytesIO(data)) as archive:
58+
for member in archive.infolist():
59+
target = (root / member.filename).resolve()
60+
if not str(target).startswith(f"{root}/") and target != root:
61+
print(f"artifact contains unsafe path: {member.filename}", file=sys.stderr)
62+
sys.exit(1)
63+
archive.extractall(root)
64+
65+
66+
runs = github_json(
67+
f"https://api.github.com/repos/{repo}/actions/workflows/assets.yml/runs"
68+
f"?head_sha={sha}&per_page=20"
69+
).get("workflow_runs", [])
70+
run = next(
71+
(
72+
run
73+
for run in runs
74+
if run.get("head_sha") == sha
75+
and run.get("status") == "completed"
76+
and run.get("conclusion") == "success"
77+
),
78+
None,
79+
)
80+
if run is None:
81+
print(f"no successful Assets workflow found for {sha}", file=sys.stderr)
82+
for candidate in runs:
83+
print(
84+
f"- {candidate.get('status')} / {candidate.get('conclusion')} / {candidate.get('html_url')}",
85+
file=sys.stderr,
86+
)
87+
sys.exit(1)
88+
89+
artifacts = github_json(
90+
f"https://api.github.com/repos/{repo}/actions/runs/{run['id']}/artifacts?per_page=100"
91+
).get("artifacts", [])
92+
by_name = {artifact["name"]: artifact for artifact in artifacts}
93+
required = ["pglite-oxide-portable-wasix"] + [f"pglite-oxide-aot-{target}" for target in targets]
94+
missing = [name for name in required if name not in by_name]
95+
if missing:
96+
print(
97+
f"Assets workflow {run.get('html_url')} is missing release artifacts: {', '.join(missing)}",
98+
file=sys.stderr,
99+
)
100+
sys.exit(1)
101+
102+
portable = by_name["pglite-oxide-portable-wasix"]
103+
extract_artifact(github_bytes(portable["archive_download_url"]))
104+
portable_manifest = Path("target/pglite-oxide/assets/manifest.json")
105+
if not portable_manifest.is_file():
106+
print(f"pglite-oxide-portable-wasix did not contain {portable_manifest}", file=sys.stderr)
107+
sys.exit(1)
108+
print(f"downloaded pglite-oxide-portable-wasix from {run.get('html_url')}")
109+
110+
for target in targets:
111+
name = f"pglite-oxide-aot-{target}"
112+
artifact = by_name[name]
113+
data = github_bytes(artifact["archive_download_url"])
114+
extract_artifact(data)
115+
manifest = Path("target/pglite-oxide/aot") / target / "manifest.json"
116+
if not manifest.is_file():
117+
print(f"{name} did not contain {manifest}", file=sys.stderr)
118+
sys.exit(1)
119+
print(f"downloaded {name} from {run.get('html_url')}")
120+
PY

0 commit comments

Comments
 (0)