ReviewOS

also looking at this

pantry-pm/pantry

fix(ci): bot commits land through a checked pull request, not a bypass

#274
Merged glennmichael123 wants to merge fix/bot-pushes-via-pr into main
10 files +621 -23
.github/actions/merge-via-pr/action.ymladded+154-0
Changes to .github/actions/merge-via-pr/action.yml
@@ -0,0 +1,154 @@
1name: Merge via PR
2description: >-
3 Land an already-committed change on the default branch through a pull request
4 that actually runs the required checks.
5
6inputs:
7 token:
8 description: "GitHub token with contents, pull-requests and actions write."
9 required: true
10 branch-prefix:
11 description: "Branch namespace for the pull request's head."
12 required: false
13 default: 'chore/automated'
14 title:
15 description: "Pull request title. Defaults to the commit subject."
16 required: false
17 default: ''
18 body:
19 description: "Pull request body."
20 required: false
21 default: ''
22 checks:
23 description: "Comma-separated required check names to wait for."
24 required: false
25 default: 'lint,typecheck,test,zig-tests'
26 workflow:
27 description: "Workflow dispatched to produce those checks."
28 required: false
29 default: 'ci.yml'
30 timeout-minutes:
31 description: "How long to wait for the checks before giving up."
32 required: false
33 default: '40'
34
35outputs:
36 merged:
37 description: "true when the pull request was merged."
38 value: ${{ steps.land.outputs.merged }}
39 pr-number:
40 description: "Number of the pull request that was opened."
41 value: ${{ steps.land.outputs.pr-number }}
42 pr-url:
43 description: "URL of the pull request that was opened."
44 value: ${{ steps.land.outputs.pr-url }}
45
46runs:
47 using: composite
48 steps:
49 # A bot cannot push to the default branch: the branch ruleset requires the
50 # CI contexts, and `github-actions[bot]` holds no repository role, so its
51 # push is refused with GH013. Going through a pull request is the honest
52 # answer — the generated catalog gets checked like anything else — but it
53 # needs one piece of care. GitHub suppresses events caused by GITHUB_TOKEN,
54 # which covers a pull request opened with it just as it covers a push, so
55 # the `pull_request` trigger never fires and nothing would ever report
56 # against the required contexts. Dispatching CI at the head branch does
57 # report: a check run is attached to a commit, and that commit is the one
58 # the pull request is gated on.
59 - name: Land the commit through a pull request
60 id: land
61 shell: bash
62 env:
63 GH_TOKEN: ${{ inputs.token }}
64 REPO: ${{ github.repository }}
65 BASE: ${{ github.event.repository.default_branch || 'main' }}
66 PREFIX: ${{ inputs.branch-prefix }}
67 TITLE_IN: ${{ inputs.title }}
68 BODY_IN: ${{ inputs.body }}
69 CHECKS: ${{ inputs.checks }}
70 WORKFLOW: ${{ inputs.workflow }}
71 TIMEOUT_MIN: ${{ inputs.timeout-minutes }}
72 RUN_ID: ${{ github.run_id }}
73 RUN_ATTEMPT: ${{ github.run_attempt }}
74 run: |
75 set -uo pipefail
76
77 BRANCH="${PREFIX}/${RUN_ID}-${RUN_ATTEMPT}"
78 SHA="$(git rev-parse HEAD)"
79 TITLE="${TITLE_IN:-$(git log -1 --pretty=%s)}"
80 echo "merged=false" >> "$GITHUB_OUTPUT"
81
82 # Each run's commit regenerates the whole of what the last one wrote, so
83 # an older open pull request from this same prefix is superseded rather
84 # than merely stale. Closing it keeps a red or slow run from piling up a
85 # queue of pull requests that all say the same thing.
86 gh pr list --repo "$REPO" --state open --json number,headRefName \
87 --jq ".[] | select(.headRefName | startswith(\"${PREFIX}/\")) | .number" 2>/dev/null \
88 | while read -r stale; do
89 [ -n "$stale" ] || continue
90 echo "Superseding #${stale}"
91 gh pr close "$stale" --repo "$REPO" --delete-branch \
92 --comment "Superseded by a newer automated run." || true
93 done
94
95 if ! git push origin "HEAD:refs/heads/${BRANCH}"; then
96 echo "::error::could not push ${BRANCH}"
97 exit 1
98 fi
99
100 PR_URL="$(gh pr create --repo "$REPO" --base "$BASE" --head "$BRANCH" \
101 --title "$TITLE" --body "${BODY_IN:-Opened automatically so the required checks run on generated output.}")"
102 if [ -z "$PR_URL" ]; then
103 echo "::error::could not open a pull request for ${BRANCH}"
104 git push origin --delete "$BRANCH" || true
105 exit 1
106 fi
107 PR_NUMBER="${PR_URL##*/}"
108 echo "pr-number=${PR_NUMBER}" >> "$GITHUB_OUTPUT"
109 echo "pr-url=${PR_URL}" >> "$GITHUB_OUTPUT"
110 echo "Opened ${PR_URL}"
111
112 if ! gh workflow run "$WORKFLOW" --repo "$REPO" --ref "$BRANCH"; then
113 echo "::error::could not dispatch ${WORKFLOW} at ${BRANCH}; nothing would report on ${SHA}"
114 exit 1
115 fi
116
117 IFS=',' read -ra WANTED <<< "$CHECKS"
118 DEADLINE=$(( $(date +%s) + TIMEOUT_MIN * 60 ))
119 while :; do
120 if [ "$(date +%s)" -ge "$DEADLINE" ]; then
121 echo "::error::${CHECKS} did not all report on ${SHA} within ${TIMEOUT_MIN}m; ${PR_URL} is left open"
122 exit 1
123 fi
124
125 RUNS="$(gh api "repos/${REPO}/commits/${SHA}/check-runs?per_page=100" \
126 --jq '.check_runs[] | "\(.name)\t\(.status)\t\(.conclusion // "")"' 2>/dev/null)"
127
128 PASSED=0
129 FAILED=''
130 for want in "${WANTED[@]}"; do
131 line="$(printf '%s\n' "$RUNS" | awk -F'\t' -v n="$want" '$1 == n { print; exit }')"
132 [ -n "$line" ] || continue
133 status="$(printf '%s' "$line" | cut -f2)"
134 [ "$status" = "completed" ] || continue
135 case "$(printf '%s' "$line" | cut -f3)" in
136 success|skipped|neutral) PASSED=$((PASSED + 1)) ;;
137 *) FAILED="${FAILED:+$FAILED, }${want}" ;;
138 esac
139 done
140
141 if [ -n "$FAILED" ]; then
142 echo "::error::required checks failed on generated output: ${FAILED}. ${PR_URL} is left open to show why."
143 exit 1
144 fi
145 [ "$PASSED" -eq "${#WANTED[@]}" ] && break
146 sleep 20
147 done
148
149 if ! gh pr merge "$PR_NUMBER" --repo "$REPO" --squash --delete-branch; then
150 echo "::error:__stx_checks="eyJzdGF0ZSI6Im5ldXRyYWwiLCJjb3VudHMiOnsiZmFpbHVyZSI6MCwicGVuZGluZyI6MCwic3VjY2VzcyI6MCwibmV1dHJhbCI6MH0sImVudHJpZXMiOltdLCJyZXF1aXJlZCI6eyJzYXRpc2ZpZWQiOnRydWUsIm1pc3NpbmciOltdLCJmYWlsaW5nIjpbXSwicGVuZGluZyI6W119LCJhbm5vdGF0aW9ucyI6W119" passed but ${PR_URL} would not merge; it is left open"
151 exit 1
152 fi
153 echo "merged=true" >> "$GITHUB_OUTPUT"
154 echo "Merged ${PR_URL}"
.github/workflows/check-desktop-updates.ymlmodified+15-14
Changes to .github/workflows/check-desktop-updates.yml
@@ -142,6 +142,10 @@ jobs:
142142 # the commit — the gate cannot let a stale manifest through.
143143 record:
144144 needs: [publish-ubuntu, publish-macos]
145 permissions:
146 contents: write
147 actions: write
148 pull-requests: write
145149 if: >-
146150 always() &&
147151 needs.publish-ubuntu.result == 'success' &&
@@ -160,17 +164,14 @@ jobs:
160164 - name: Record verified registry state
161165 working-directory: packages/ts-pantry
162166 run: bun scripts/check-desktop-updates.ts --require-current --commit
163 - name: Push verified manifest commit
164 working-directory: packages/ts-pantry
165 run: |
166 for attempt in 1 2 3 4 5; do
167 if git push; then
168 echo "push ok (attempt $attempt)"
169 exit 0
170 fi
171 echo "push rejected (attempt $attempt); rebasing onto latest main"
172 git fetch origin main
173 git rebase origin/main
174 done
175 echo "ERROR: could not push manifest after 5 attempts" >&2
176 exit 1
167 # The rebase-and-retry loop this replaces answered a race with another
168 # push. It cannot answer the ruleset: main requires the CI contexts, a
169 # bot push holds no repository role to be excused by, and retrying a
170 # GH013 five times only rebases five times before failing anyway.
171 - name: Land the verified manifest through a pull request
172 uses: ./.github/actions/merge-via-pr
173 with:
174 token: ${{ secrets.PAT_TOKEN || secrets.GITHUB_TOKEN }}
175 branch-prefix: chore/desktop-manifest
176 body: |
177 Verified desktop manifest state from `check-desktop-updates.yml`.
.github/workflows/update-packages.ymlmodified+27-5
Changes to .github/workflows/update-packages.yml
@@ -53,10 +53,12 @@ jobs:
5353 update-packages:
5454 name: Update packages and index
5555 runs-on: ubuntu-latest
56 timeout-minutes: 30
56 # Covers the sweep plus the wait for CI to report on the pull request.
57 timeout-minutes: 80
5758 permissions:
5859 contents: write
5960 actions: write
61 pull-requests: write
6062
6163 steps:
6264 - name: Checkout repository
@@ -221,7 +223,7 @@ jobs:
221223 fi
222224 echo "safe_to_commit=true" >> $GITHUB_OUTPUT
223225
224 - name: Commit and push changes
226 - name: Commit changes
225227 id: commit
226228 if: steps.git-check.outputs.changes == 'true' && steps.deletion-check.outputs.safe_to_commit == 'true'
227229 run: |
@@ -234,12 +236,32 @@ jobs:
234236 test "$(git rev-parse HEAD)" = "$(git rev-parse origin/main)"
235237 git add packages/ts-pantry/src/packages/ packages/ts-pantry/src/generated-package-names.ts packages/zig/src/packages/generated.zig packages/action/dist/index.js packages/action/dist/post.js
236238 git commit -F /tmp/pantry-update-commit-message.txt
237 git push origin HEAD:refs/heads/main
238239
239 echo "committed=true" >> $GITHUB_OUTPUT
240 # This commit used to go straight to main. It cannot any more: main
241 # requires the CI contexts, and a bot push carries no repository role to
242 # be excused by, so the push comes back GH013. The alternative to a pull
243 # request was excusing the Actions app from the checks, which would have
244 # exempted every workflow that ever pushes rather than this one, and
245 # would have kept the generated catalog the only thing on main nobody
246 # checks. So it is checked now, like everything else.
247 - name: Land the update through a pull request
248 id: land
249 if: steps.commit.conclusion == 'success'
250 uses: ./.github/actions/merge-via-pr
251 with:
252 token: ${{ secrets.PAT_TOKEN || secrets.GITHUB_TOKEN }}
253 branch-prefix: chore/package-updates
254 body: |
255 Automated catalog refresh from `update-packages.yml`.
256
257 Opened rather than pushed because `main` requires the CI contexts
258 and a bot push holds no role to be excused by. CI is dispatched at
259 this branch explicitly: a pull request opened with `GITHUB_TOKEN`
260 does not fire `pull_request`, so without the dispatch nothing would
261 ever report here.
240262
241263 - name: Publish updated packages to the registry
242 if: steps.commit.outputs.committed == 'true'
264 if: steps.land.outputs.merged == 'true'
243265 env:
244266 GH_TOKEN: ${{ secrets.PAT_TOKEN || secrets.GITHUB_TOKEN }}
245267 run: |
packages/ts-pantry/src/packages/craft-native.org.tsmodified+5-1
Changes to packages/ts-pantry/src/packages/craft-native.org.ts
@@ -54,7 +54,11 @@ export const craftPackage = {
5454 'craft',
5555 ] as const,
5656 companions: [] as const,
57 dependencies: [] as const,
57 dependencies: [
58 'linux:gtk.org/gtk3^3.24',
59 'linux:gnome.org/glib^2',
60 'linux:webkitgtk.org^2.54',
61 ] as const,
5862 /**
5963 * Build dependencies for this package.
6064 * These are only required when building the package from source.
packages/ts-pantry/src/packages/index.tsmodified+3-0
Changes to packages/ts-pantry/src/packages/index.ts
@@ -1688,6 +1688,7 @@ import { waterlanhomexs4allnldos2unixPackage } from './waterlan.home.xs4all.nl/d
16881688import { wavpackcomPackage } from './wavpackcom'
16891689import { waylandfreedesktoporgprotocolsPackage } from './wayland.freedesktop.org/protocols'
16901690import { waylandfreedesktoporgPackage } from './waylandfreedesktoporg'
1691import { webkitgtkorgPackage } from './webkitgtkorg'
16911692import { webmprojectorglibvpxPackage } from './webmproject.org/libvpx'
16921693import { werfioPackage } from './werfio'
16931694import { wezfurlongorgweztermPackage } from './wezfurlong.org/wezterm'
@@ -4564,6 +4565,7 @@ export interface Pantry {
45644565 protocols: typeof waylandfreedesktoporgprotocolsPackage
45654566 waylandfreedesktoporgprotocols: typeof waylandfreedesktoporgprotocolsPackage
45664567 waylandfreedesktoporg: typeof waylandfreedesktoporgPackage
4568 webkitgtkorg: typeof webkitgtkorgPackage
45674569
45684570 webmprojectorglibvpx: typeof webmprojectorglibvpxPackage
45694571 werfio: typeof werfioPackage
@@ -9272,6 +9274,7 @@ export const pantry: Pantry = {
92729274 protocols: waylandfreedesktoporgprotocolsPackage,
92739275 waylandfreedesktoporgprotocols: waylandfreedesktoporgprotocolsPackage,
92749276 waylandfreedesktoporg: waylandfreedesktoporgPackage,
9277 webkitgtkorg: webkitgtkorgPackage,
92759278
92769279 webmprojectorglibvpx: webmprojectorglibvpxPackage,
92779280 werfio: werfioPackage,
packages/ts-pantry/src/packages/webkitgtkorg.tsadded+110-0
Changes to packages/ts-pantry/src/packages/webkitgtkorg.ts
@@ -0,0 +1,110 @@
1/**
2 * **webkitgtk** - WebKit rendering engine for GTK, providing the webkit2gtk-4.1 API
3 *
4 * @domain `webkitgtk.org`
5 * @version `2.54.2` (3 versions available)
6 * @versions From newest version to oldest.
7 *
8 * @install `pantry install webkitgtk.org`
9 * @homepage https://webkitgtk.org
10 * @dependencies `gtk.org/gtk3^3.24`, `gnome.org/glib^2`, `libsoup.org^3`, ... (+26 more)
11 * @buildDependencies `cmake.org^3`, `ninja-build.org`, `freedesktop.org/pkg-config`, ... (+4 more) - required only when building from source
12 *
13 * @example
14 * ```typescript
15 * import { pantry } from 'ts-pantry'
16 *
17 * const pkg = pantry.webkitgtkorg
18 * console.log(pkg.name) // "webkitgtk"
19 * console.log(pkg.description) // "WebKit rendering engine for GTK, providing the ..."
20 * console.log(pkg.versions[0]) // "2.54.2" (latest)
21 * ```
22 *
23 * @see https://ts-pantry.netlify.app/packages/webkitgtk-org.md
24 * @see https://ts-pantry.netlify.app/usage
25 */
26export const webkitgtkorgPackage = {
27 /**
28 * The display name of this package.
29 */
30 name: 'webkitgtk' as const,
31 /**
32 * The canonical domain name for this package.
33 */
34 domain: 'webkitgtk.org' as const,
35 /**
36 * Brief description of what this package does.
37 */
38 description: 'WebKit rendering engine for GTK, providing the webkit2gtk-4.1 API' as const,
39 packageYmlUrl: '' as const,
40 homepageUrl: 'https://webkitgtk.org' as const,
41 githubUrl: 'https://github.com/WebKit/WebKit' as const,
42 /**
43 * Command to install this package using pantry.
44 * @example pantry install package-name
45 */
46 installCommand: 'pantry install webkitgtk.org' as const,
47 pantryInstallCommand: 'pantry install webkitgtk.org' as const,
48 programs: [] as const,
49 companions: [] as const,
50 /**
51 * Runtime dependencies for this package.
52 * These are required when running the package.
53 */
54 dependencies: [
55 'gtk.org/gtk3^3.24',
56 'gnome.org/glib^2',
57 'libsoup.org^3',
58 'unicode.org',
59 'cairographics.org',
60 'gnome.org/pango',
61 'harfbuzz.org',
62 'freetype.org',
63 'freedesktop.org/fontconfig',
64 'gnome.org/libxml2',
65 'gnome.org/libxslt',
66 'sqlite.org',
67 'libjpeg-turbo.org',
68 'libpng.org',
69 'google.com/webp',
70 'google.com/woff2',
71 'gnu.org/libtasn1',
72 'gnupg.org/libgcrypt',
73 'littlecms.com',
74 'gnome.org/atk',
75 'gnome.org/gdk-pixbuf',
76 'github.com/anholt/libepoxy',
77 'gnome.org/libsecret',
78 'openjpeg.org',
79 'zlib.net',
80 'x.org/x11',
81 'x.org/exts',
82 'wayland.freedesktop.org',
83 'mesa3d.org',
84 ] as const,
85 /**
86 * Available versions from newest to oldest.
87 * @see https://ts-pantry.netlify.app/usage for installation instructions
88 */
89 versions: [
90 '2.54.2',
91 '2.54.1',
92 '2.54.0',
93 ] as const,
94 /**
95 * Build dependencies for this package.
96 * These are only required when building the package from source.
97 */
98 buildDependencies: [
99 'cmake.org^3',
100 'ninja-build.org',
101 'freedesktop.org/pkg-config',
102 'gnu.org/gettext',
103 'gnu.org/gperf',
104 'ruby-lang.org',
105 'python.org^3',
106 ] as const,
107 aliases: [] as const,
108}
109
110export type WebkitgtkorgPackage = typeof webkitgtkorgPackage
packages/ts-pantry/src/recipes/craft-native.org.tsmodified+14-0
Changes to packages/ts-pantry/src/recipes/craft-native.org.ts
@@ -7,6 +7,20 @@ export const recipe: Recipe = {
77 homepage: 'https://craft-native.org',
88 github: 'https://github.com/craft-native/craft',
99 programs: ['craft'],
10
11 // Craft's Linux binary links GTK3 (libgtk-3, libgdk-3), GLib (libglib-2.0,
12 // libgobject-2.0, libgio-2.0) and the GTK3 WebKit API (libwebkit2gtk-4.1,
13 // libjavascriptcoregtk-4.1). It downloaded none of them, so ELF
14 // normalization correctly refused to publish the Linux artifact with seven
15 // unresolved libraries. Keyed under `linux` because the Darwin binaries use
16 // the system WebKit framework and must not acquire these.
17 dependencies: {
18 linux: {
19 'gtk.org/gtk3': '^3.24',
20 'gnome.org/glib': '^2',
21 'webkitgtk.org': '^2.54',
22 },
23 },
1024 revisions: { '0.0.350': 1, '0.0.423': 1 },
1125 // Many craft GitHub releases (v0.0.16 and everything older) ship NO binary
1226 // assets at all — they predate upstream's binary publishing. The scheduled
packages/ts-pantry/src/recipes/webkitgtk.org.tsadded+174-0
Changes to packages/ts-pantry/src/recipes/webkitgtk.org.ts
@@ -0,0 +1,174 @@
1import type { Recipe } from '../../scripts/recipe-types'
2
3export const recipe: Recipe = {
4 domain: 'webkitgtk.org',
5 name: 'webkitgtk',
6 description: 'WebKit rendering engine for GTK, providing the webkit2gtk-4.1 API',
7 homepage: 'https://webkitgtk.org',
8 github: 'https://github.com/WebKit/WebKit',
9 programs: [],
10
11 // WebKitGTK 2.x builds exactly one API per configure, chosen by USE_GTK4:
12 // ON (upstream's default) gives GTK4 + webkitgtk-6.0, OFF gives GTK3 +
13 // webkit2gtk-4.1. Source/cmake/OptionsGTK.cmake sets WEBKITGTK_API_INFIX to
14 // "2" for 4.1, which is why the sonames are libwebkit2gtk-4.1.so.0 and
15 // libjavascriptcoregtk-4.1.so.0 — the two craft-native.org's Linux binary
16 // fails to resolve. 4.1 is GTK3 + libsoup3; upstream deprecated libsoup2
17 // (API 4.0), and 2.54's OptionsGTK.cmake now calls find_package(Soup3 3.0.0
18 // REQUIRED) unconditionally, so there is no soup2 variant left to pick.
19 // WPE and the GTK4 6.0 API are DIFFERENT APIs, not substitutes: neither
20 // provides these sonames.
21 platforms: ['linux/x86-64'],
22
23 versionSource: {
24 type: 'github-tags',
25 repo: 'WebKit/WebKit',
26 // The WebKit monorepo tags every port; only webkitgtk-* releases are ours.
27 // Anchored, with a bounded numeric capture, so a hypothetical
28 // webkitgtk-2.54.0-rc1 cannot match (github-tags carries no prerelease
29 // flag) and the pattern cannot backtrack super-linearly.
30 tagPattern: /^webkitgtk-(\d+(?:\.\d+){0,3})$/,
31 },
32
33 distributable: {
34 // Upstream's own release tarballs, not the monorepo archive: the GitHub
35 // tag tree is the whole of WebKit (every port) and does not carry the
36 // generated files the release tarball ships.
37 url: 'https://webkitgtk.org/releases/webkitgtk-.tar.xz',
38 stripComponents: 1,
39 },
40
41 // libwebkit2gtk does not do its own rendering in-process: it spawns
42 // WebKitWebProcess and WebKitNetworkProcess from PKGLIBEXECDIR and loads the
43 // injected bundle from the lib tree (Source/WebKit/PlatformGTK.cmake installs
44 // it under lib/webkit2gtk-4.1/injected-bundle). A consumer that bundles only
45 // the .so gets a library that loads and then fails the moment a page is
46 // created, so both directories travel with it.
47 runtimeResources: [
48 'libexec/webkit2gtk-4.1',
49 'lib/webkit2gtk-4.1/injected-bundle',
50 ],
51
52 // Every domain below is published for linux-x86-64 today; this closure was
53 // checked against the registry before the recipe was written, because a
54 // dependency declaration naming a provider that does not exist fails at
55 // install time rather than at publish time.
56 dependencies: {
57 'gtk.org/gtk3': '^3.24',
58 'gnome.org/glib': '^2',
59 'libsoup.org': '^3',
60 'unicode.org': '*',
61 'cairographics.org': '*',
62 'gnome.org/pango': '*',
63 'harfbuzz.org': '*',
64 'freetype.org': '*',
65 'freedesktop.org/fontconfig': '*',
66 'gnome.org/libxml2': '*',
67 'gnome.org/libxslt': '*',
68 'sqlite.org': '*',
69 'libjpeg-turbo.org': '*',
70 'libpng.org': '*',
71 'google.com/webp': '*',
72 'google.com/woff2': '*',
73 'gnu.org/libtasn1': '*',
74 'gnupg.org/libgcrypt': '*',
75 'littlecms.com': '*',
76 'gnome.org/atk': '*',
77 'gnome.org/gdk-pixbuf': '*',
78 'github.com/anholt/libepoxy': '*',
79 'gnome.org/libsecret': '*',
80 'openjpeg.org': '*',
81 'zlib.net': '*',
82 'x.org/x11': '*',
83 'x.org/exts': '*',
84 'wayland.freedesktop.org': '*',
85 'mesa3d.org': '*',
86 },
87
88 buildDependencies: {
89 'cmake.org': '^3',
90 'ninja-build.org': '*',
91 'freedesktop.org/pkg-config': '*',
92 'gnu.org/gettext': '*',
93 'gnu.org/gperf': '*',
94 'ruby-lang.org': '*',
95 'python.org': '^3',
96 },
97
98 build: {
99 script: [
100 'cmake -S . -B build -G Ninja $CMAKE_ARGS',
101 'cmake --build build',
102 'cmake --install build',
103 ],
104 env: {
105 CMAKE_ARGS: [
106 '-DPORT=GTK',
107 '-DCMAKE_BUILD_TYPE=Release',
108 '-DCMAKE_INSTALL_PREFIX=',
109 '-DCMAKE_INSTALL_LIBDIR=lib',
110 '-DCMAKE_INSTALL_LIBEXECDIR=libexec',
111 '-DCMAKE_INSTALL_RPATH=/lib',
112 '-DCMAKE_FIND_FRAMEWORK=LAST',
113 '-Wno-dev',
114
115 // The API selector. Everything else here exists to keep that build's
116 // dependency closure inside what Pantry actually publishes.
117 '-DUSE_GTK4=OFF',
118
119 // GStreamer is NOT optional behind ENABLE_VIDEO. GStreamerChecks.cmake
120 // guards on USE_GSTREAMER and then calls find_package(GStreamer 1.18.4
121 // REQUIRED COMPONENTS app pbutils) with app/pbutils listed
122 // unconditionally — turning off video and web audio alone still fails
123 // configure. Pantry's catalog has gstreamer.freedesktop.org/orc and no
124 // GStreamer core or gst-plugins-base, and neither does pkgx, so the
125 // whole subsystem is off until those recipes exist. Consequence, stated
126 // plainly: <video>, <audio> and WebAudio do not work in this build.
127 '-DUSE_GSTREAMER=OFF',
128 '-DENABLE_VIDEO=OFF',
129 '-DENABLE_WEB_AUDIO=OFF',
130 '-DENABLE_MEDIA_SOURCE=OFF',
131 '-DENABLE_MEDIA_STREAM=OFF',
132 '-DENABLE_MEDIA_RECORDER=OFF',
133 '-DENABLE_MEDIA_SESSION=OFF',
134 '-DUSE_GSTREAMER_WEBRTC=OFF',
135 // ENABLE_WEB_RTC already defaults OFF for this port; pinning it keeps
136 // the ENABLE_MEDIA_STREAM AND ENABLE_WEB_RTC branch in
137 // GStreamerChecks.cmake — which sits OUTSIDE the USE_GSTREAMER guard
138 // and would otherwise turn on the bundled libwebrtc — unreachable.
139 '-DENABLE_WEB_RTC=OFF',
140
141 // Each of these has a find_package ... FATAL_ERROR in OptionsGTK.cmake
142 // and no provider in the catalog: Enchant (spellcheck), libhyphen
143 // (automatic hyphenation), libmanette (gamepad), libdrm/GBM — and
144 // ENABLE_GPU_PROCESS depends on USE_GBM, so it goes with them.
145 '-DENABLE_SPELLCHECK=OFF',
146 '-DUSE_LIBHYPHEN=OFF',
147 '-DENABLE_GAMEPAD=OFF',
148 '-DUSE_LIBDRM=OFF',
149 '-DUSE_GBM=OFF',
150 '-DENABLE_GPU_PROCESS=OFF',
151 '-DENABLE_SPEECH_SYNTHESIS=OFF',
152 '-DENABLE_JOURNALD_LOG=OFF',
153 '-DUSE_LIBBACKTRACE=OFF',
154 '-DUSE_JPEGXL=OFF',
155 '-DUSE_AVIF=OFF',
156
157 // The sandbox needs bubblewrap and xdg-dbus-proxy at RUNTIME, found by
158 // absolute path; neither is a Pantry package, and a sandbox that cannot
159 // find its helper refuses to start the web process.
160 '-DENABLE_BUBBLEWRAP_SANDBOX=OFF',
161
162 // Introspection pulls gi-docgen for documentation; craft links the C
163 // ABI directly and needs no GIR, so both stay off to keep the build
164 // closure to libraries we publish.
165 '-DENABLE_INTROSPECTION=OFF',
166 '-DENABLE_DOCUMENTATION=OFF',
167
168 // MiniBrowser is a developer tool; this package ships libraries only
169 // (programs: []), so building it would be dead weight in the artifact.
170 '-DENABLE_MINIBROWSER=OFF',
171 ],
172 },
173 },
174}
packages/ts-pantry/test/webkitgtk-provider.test.tsadded+105-0
Changes to packages/ts-pantry/test/webkitgtk-provider.test.ts
@@ -0,0 +1,105 @@
1// craft-native.org's Linux artifact could not be published: its binary links
2// seven libraries the recipe never provided, and ELF normalization refused it.
3// Three of those sonames belong to a WebKit API that had no owner in the
4// catalog at all, so a dependency declaration alone could not have fixed it.
5// These tests pin the provider's identity and the closure that must exist.
6
7import { describe, expect, it } from 'bun:test'
8import { recipeDepsToSpecs } from '../src/dependency-spec'
9import { pantry } from '../src/packages'
10import { recipe as craft } from '../src/recipes/craft-native.org'
11import { recipe as webkitgtk } from '../src/recipes/webkitgtk.org'
12
13const cmakeArgs = (webkitgtk.build?.env?.CMAKE_ARGS ?? []) as string[]
14const byDomain = new Map(Object.values(pantry).map(entry => [(entry as any).domain, entry as any]))
15
16describe('the WebKitGTK provider owns the API craft actually links', () => {
17 it('selects the GTK3 4.1 API rather than the GTK4 6.0 default', () => {
18 // OptionsGTK.cmake defaults USE_GTK4 ON, which produces webkitgtk-6.0.
19 // Only USE_GTK4=OFF yields WEBKITGTK_API_INFIX "2" and so the
20 // libwebkit2gtk-4.1 / libjavascriptcoregtk-4.1 sonames craft needs.
21 expect(cmakeArgs).toContain('-DUSE_GTK4=OFF')
22 expect(cmakeArgs).toContain('-DPORT=GTK')
23 })
24
25 it('is not a WPE or GTK4 substitute', () => {
26 expect(webkitgtk.domain).toBe('webkitgtk.org')
27 expect(webkitgtk.domain).not.toContain('wpewebkit')
28 expect(JSON.stringify(webkitgtk.dependencies)).not.toContain('wpewebkit')
29 expect(JSON.stringify(webkitgtk.dependencies)).not.toContain('gtk4')
30 })
31
32 it('ships the helper processes and injected bundle its libraries spawn', () => {
33 // libwebkit2gtk does not render in-process: it execs WebKitWebProcess and
34 // WebKitNetworkProcess from PKGLIBEXECDIR and dlopens the injected bundle.
35 // A consumer that bundles only the .so gets a library that fails on the
36 // first page it creates.
37 expect(webkitgtk.runtimeResources).toEqual([
38 'libexec/webkit2gtk-4.1',
39 'lib/webkit2gtk-4.1/injected-bundle',
40 ])
41 })
42
43 it('builds only where the GTK3 stack is published', () => {
44 expect(webkitgtk.platforms).toEqual(['linux/x86-64'])
45 })
46})
47
48describe('the provider only claims a closure the catalog can satisfy', () => {
49 it('disables GStreamer itself, not merely video and web audio', () => {
50 // GStreamerChecks.cmake guards on USE_GSTREAMER and then calls
51 // find_package(GStreamer REQUIRED COMPONENTS app pbutils) with app and
52 // pbutils listed unconditionally — ENABLE_VIDEO=OFF alone still fails
53 // configure. Pantry publishes gstreamer.freedesktop.org/orc and no
54 // GStreamer core, so the subsystem is off until those recipes exist.
55 expect(cmakeArgs).toContain('-DUSE_GSTREAMER=OFF')
56 expect(cmakeArgs).toContain('-DENABLE_VIDEO=OFF')
57 expect(cmakeArgs).toContain('-DENABLE_WEB_AUDIO=OFF')
58 expect(JSON.stringify(webkitgtk.dependencies)).not.toContain('gstreamer')
59 })
60
61 it('turns off every feature whose provider the catalog lacks', () => {
62 // Each has a find_package ... FATAL_ERROR in OptionsGTK.cmake and no
63 // provider here: Enchant, libhyphen, libmanette, libdrm/GBM.
64 for (const flag of ['-DENABLE_SPELLCHECK=OFF', '-DUSE_LIBHYPHEN=OFF', '-DENABLE_GAMEPAD=OFF', '-DUSE_LIBDRM=OFF', '-DUSE_GBM=OFF'])
65 expect(cmakeArgs).toContain(flag)
66 })
67
68 it('requires libsoup3, which is what the 4.1 API is', () => {
69 // 2.54 calls find_package(Soup3 3.0.0 REQUIRED) unconditionally; the
70 // libsoup2 API 4.0 variant is gone.
71 expect((webkitgtk.dependencies as any)['libsoup.org']).toBe('^3')
72 })
73
74 it('names only domains that exist in the catalog', () => {
75 const declared = Object.keys(webkitgtk.dependencies ?? {})
76 expect(declared.length).toBeGreaterThan(0)
77 expect(declared.filter(domain => !byDomain.has(domain))).toEqual([])
78 })
79})
80
81describe("craft declares its Linux runtime closure and nothing on Darwin", () => {
82 const specs = recipeDepsToSpecs(craft.dependencies as any)
83
84 it('covers each unresolved library through its owning provider', () => {
85 // libgtk-3 / libgdk-3 → gtk3; libglib-2.0 / libgobject-2.0 / libgio-2.0 →
86 // glib; libwebkit2gtk-4.1 / libjavascriptcoregtk-4.1 → webkitgtk.
87 expect(specs).toEqual([
88 'linux:gtk.org/gtk3^3.24',
89 'linux:gnome.org/glib^2',
90 'linux:webkitgtk.org^2.54',
91 ])
92 })
93
94 it('adds nothing on Darwin, which uses the system WebKit framework', () => {
95 expect(specs.every(spec => spec.startsWith('linux:'))).toBe(true)
96 expect(specs.some(spec => spec.startsWith('darwin'))).toBe(false)
97 })
98
99 it('points every declared provider at a real catalog entry', () => {
100 for (const spec of specs) {
101 const domain = spec.slice('linux:'.length).replace(/[@^~<>=].*$/, '')
102 expect(byDomain.get(domain)).toBeDefined()
103 }
104 })
105})
packages/zig/src/packages/generated.zigmodified+14-3
Changes to packages/zig/src/packages/generated.zig
@@ -1,6 +1,6 @@
11//! Package definitions generated from ts-pantry
22//! DO NOT EDIT MANUALLY - This file is auto-generated
3//! Total packages: 1767
3//! Total packages: 1768
44
55const std = ;
66
@@ -28,7 +28,7 @@ pub const PackageInfo = struct {
2828 versions: []const []const u8,
2929};
3030
31/// All known packages (1767 total)
31/// All known packages (1768 total)
3232pub const packages = [_]PackageInfo{ .{
3333 .name = "1Password",
3434 .domain = "1password.com",
@@ -1829,7 +1829,7 @@ pub const packages = [_]PackageInfo{ .{
18291829 .description = "Build desktop apps with web languages, powered by Zig",
18301830 .homepage_url = "https://craft-native.org",
18311831 .programs = &[_][]const u8{"craft"},
1832 .dependencies = &[_][]const u8{},
1832 .dependencies = &[_][]const u8{ "linux:gtk.org/gtk3^3.24", "linux:gnome.org/glib^2", "linux:webkitgtk.org^2.54" },
18331833 .build_dependencies = &[_][]const u8{"ziglang.org"},
18341834 .aliases = &[_][]const u8{"craft"},
18351835 .versions = &[_][]const u8{ "0.0.424", "0.0.423.1", "0.0.423", "0.0.420", "0.0.418", "0.0.417", "0.0.416", "0.0.415", "0.0.414", "0.0.413", "0.0.411", "0.0.410", "0.0.409", "0.0.408", "0.0.396", "0.0.395", "0.0.394", "0.0.393", "0.0.392", "0.0.390", "0.0.387", "0.0.385", "0.0.384", "0.0.382", "0.0.380", "0.0.377", "0.0.375", "0.0.374", "0.0.369", "0.0.368", "0.0.366", "0.0.365", "0.0.361", "0.0.360", "0.0.359", "0.0.358", "0.0.355", "0.0.354", "0.0.353", "0.0.352", "0.0.351", "0.0.350.1", "0.0.350", "0.0.348", "0.0.346", "0.0.345", "0.0.344", "0.0.341", "0.0.340", "0.0.338", "0.0.330", "0.0.329", "0.0.328", "0.0.319", "0.0.304", "0.0.301", "0.0.299", "0.0.296", "0.0.293", "0.0.290", "0.0.278", "0.0.276", "0.0.272", "0.0.262", "0.0.254", "0.0.251", "0.0.250", "0.0.249", "0.0.248", "0.0.247", "0.0.246", "0.0.245", "0.0.244", "0.0.243", "0.0.242", "0.0.241", "0.0.238", "0.0.237", "0.0.236", "0.0.235", "0.0.234", "0.0.233", "0.0.232", "0.0.231", "0.0.228", "0.0.227", "0.0.226", "0.0.225", "0.0.224", "0.0.223", "0.0.222", "0.0.221", "0.0.220", "0.0.219", "0.0.218", "0.0.217", "0.0.216", "0.0.215", "0.0.214", "0.0.213", "0.0.212", "0.0.211", "0.0.210", "0.0.209", "0.0.207", "0.0.206", "0.0.205", "0.0.204", "0.0.203", "0.0.202", "0.0.201", "0.0.200", "0.0.199", "0.0.198", "0.0.197", "0.0.196", "0.0.195", "0.0.194", "0.0.193", "0.0.192", "0.0.191", "0.0.189", "0.0.187", "0.0.186", "0.0.185", "0.0.184", "0.0.183", "0.0.182", "0.0.181", "0.0.180", "0.0.179", "0.0.178", "0.0.177", "0.0.176", "0.0.175", "0.0.174", "0.0.173", "0.0.172", "0.0.171", "0.0.170", "0.0.169", "0.0.168", "0.0.167", "0.0.166", "0.0.164", "0.0.163", "0.0.162", "0.0.161", "0.0.160", "0.0.158", "0.0.157", "0.0.156", "0.0.155", "0.0.154", "0.0.153", "0.0.152", "0.0.151", "0.0.150", "0.0.149", "0.0.148", "0.0.147", "0.0.146", "0.0.145", "0.0.144", "0.0.143", "0.0.142", "0.0.141", "0.0.140", "0.0.139", "0.0.138", "0.0.136", "0.0.135", "0.0.134", "0.0.133", "0.0.132", "0.0.128", "0.0.127", "0.0.126", "0.0.125", "0.0.124", "0.0.123", "0.0.122", "0.0.121", "0.0.120", "0.0.119", "0.0.118", "0.0.115", "0.0.113", "0.0.112", "0.0.111", "0.0.110", "0.0.109", "0.0.107", "0.0.105", "0.0.104", "0.0.103", "0.0.101", "0.0.100", "0.0.99", "0.0.98", "0.0.97", "0.0.96", "0.0.92", "0.0.91", "0.0.90", "0.0.88", "0.0.87", "0.0.86", "0.0.85", "0.0.84", "0.0.83", "0.0.81", "0.0.80", "0.0.79", "0.0.37", "0.0.23", "0.0.20", "0.0.19" },
@@ -18565,6 +18565,17 @@ pub const packages = [_]PackageInfo{ .{
1856518565 .build_dependencies = &[_][]const u8{ "mesonbuild.com", "python.org@~3.11", "wayland.freedesktop.org", "curl.se" },
1856618566 .aliases = &[_][]const u8{},
1856718567 .versions = &[_][]const u8{ "1.47.0", "1.46.0", "1.45.0", "1.44.0", "1.43.0", "1.42.0", "1.41.0", "1.40.0", "1.39.0", "1.38.0", "1.37.0", "1.36.0", "1.35.0", "1.34.0", "1.33.0", "1.32.0" },
18568}, .{
18569 .name = "webkitgtk",
18570 .domain = "webkitgtk.org",
18571 .kind = .library,
18572 .description = "WebKit rendering engine for GTK, providing the webkit2gtk-4.1 API",
18573 .homepage_url = "https://webkitgtk.org",
18574 .programs = &[_][]const u8{},
18575 .dependencies = &[_][]const u8{ "gtk.org/gtk3^3.24", "gnome.org/glib^2", "libsoup.org^3", "unicode.org", "cairographics.org", "gnome.org/pango", "harfbuzz.org", "freetype.org", "freedesktop.org/fontconfig", "gnome.org/libxml2", "gnome.org/libxslt", "sqlite.org", "libjpeg-turbo.org", "libpng.org", "google.com/webp", "google.com/woff2", "gnu.org/libtasn1", "gnupg.org/libgcrypt", "littlecms.com", "gnome.org/atk", "gnome.org/gdk-pixbuf", "github.com/anholt/libepoxy", "gnome.org/libsecret", "openjpeg.org", "zlib.net", "x.org/x11", "x.org/exts", "wayland.freedesktop.org", "mesa3d.org" },
18576 .build_dependencies = &[_][]const u8{ "cmake.org^3", "ninja-build.org", "freedesktop.org/pkg-config", "gnu.org/gettext", "gnu.org/gperf", "ruby-lang.org", "python.org^3" },
18577 .aliases = &[_][]const u8{},
18578 .versions = &[_][]const u8{ "2.54.2", "2.54.1", "2.54.0" },
1856818579}, .{
1856918580 .name = "libvpx",
1857018581 .domain = "webmproject.org/libvpx",