Skip to content

Commit 1cc1160

Browse files
committed
Serve releases.json from GitHub Pages to fix publishing race
Meson fetches releases.json directly from the repo, via a redirect from wrapdb.mesonbuild.com. When a PR lands, releases.json is updated immediately but the GitHub release (with the wrap file, source mirror, and patch zip) is updated after a delay, which can be lengthy if CI is heavily loaded or down. During this time, `meson wrap install` and `meson wrap update` fail on the newly-updated wraps. Maintain a second copy of releases.json that lags the one in Git, updating it only after all the releases it describes have been published. Publish it via GitHub Pages: have create_release.py generate a static site, then publish that in the release workflow. Because of how GitHub Pages works with custom domains, the new file will be available from https://mesonbuild.com/wrapdb/releases.json. Update the wrapdb.mesonbuild.com nginx config to redirect there instead. While we're here, minify the published releases.json.
1 parent d658f9b commit 1cc1160

File tree

4 files changed

+39
-8
lines changed

4 files changed

+39
-8
lines changed

.github/workflows/release.yml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,29 @@ jobs:
2525
- name: Upload release assets
2626
run: |
2727
./tools/create_release.py ${{ github.repository }} ${{ secrets.GITHUB_TOKEN }}
28+
- name: Upload releases.json
29+
uses: actions/upload-pages-artifact@v4
30+
31+
update_index:
32+
needs: create_release
33+
runs-on: ubuntu-latest
34+
permissions:
35+
pages: write
36+
id-token: write
37+
environment:
38+
name: github-pages
39+
url: ${{ steps.deploy.outputs.page_url }}releases.json
40+
steps:
41+
- name: Deploy releases.json
42+
id: deploy
43+
uses: actions/deploy-pages@v4
2844

2945
# Ideally we should trigger Meson's CI to update the website, but unfortunately
3046
# it requires a Personal Access Token. Instead clone meson and do it ourself.
3147
# This job is copied from Meson's workflows.
3248
update_website:
49+
# the site builder reads releases.json, so update that first
50+
needs: update_index
3351
env:
3452
HAS_SSH_KEY: ${{ secrets.WEBSITE_PRIV_KEY != '' }}
3553
runs-on: ubuntu-latest

nginx/default

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
server {
1212
location /v2 {
13-
rewrite ^.*/v2/releases.json https://raw.githubusercontent.com/mesonbuild/wrapdb/master/releases.json permanent;
13+
rewrite ^.*/v2/releases.json https://mesonbuild.com/wrapdb/releases.json permanent;
1414
rewrite ^.*/v2/([^/]+)/get_patch https://github.com/mesonbuild/wrapdb/releases/download/$1/$1_patch.zip permanent;
1515
rewrite ^.*/v2/([^/]+)/([^/]+).wrap https://github.com/mesonbuild/wrapdb/releases/download/$1/$2.wrap permanent;
1616
}

tools/create_release.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,13 @@ def finalize(self) -> None:
193193
response.raise_for_status()
194194
print('Published release:', self.upload_url)
195195

196+
def generate_site(releases: Releases) -> None:
197+
site = Path('_site') # default path for actions/upload-pages-artifact
198+
if site.exists():
199+
shutil.rmtree(site)
200+
site.mkdir()
201+
releases.save(dir=site, compact=True)
202+
196203
def run(repo: T.Optional[str], token: T.Optional[str]) -> None:
197204
releases = Releases.load()
198205
stdout = subprocess.check_output(['git', 'tag'])
@@ -202,6 +209,7 @@ def run(repo: T.Optional[str], token: T.Optional[str]) -> None:
202209
latest_tag = f'{name}_{versions[0]}'
203210
if latest_tag not in tags:
204211
CreateRelease(repo, token, latest_tag)
212+
generate_site(releases)
205213

206214
if __name__ == '__main__':
207215
# Support local testing when passing no arguments

tools/utils.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -118,13 +118,18 @@ def load(cls) -> T.Self:
118118
with open(cls.FILENAME, encoding='utf-8') as f:
119119
return cls(json.load(f))
120120

121-
def encode(self) -> str:
122-
return json.dumps(self, indent=2, sort_keys=True) + '\n'
123-
124-
def save(self) -> None:
125-
with open(f'{self.FILENAME}.new', 'w', encoding='utf-8') as f:
126-
f.write(self.encode())
127-
os.rename(f'{self.FILENAME}.new', self.FILENAME)
121+
def encode(self, *, compact: bool = False) -> str:
122+
if compact:
123+
kwargs: dict[str, T.Any] = dict(separators=(',', ':'))
124+
else:
125+
kwargs = dict(indent=2)
126+
return json.dumps(self, sort_keys=True, **kwargs) + '\n'
127+
128+
def save(self, *, dir: Path = Path('.'), compact: bool = False) -> None:
129+
temp = dir / f'{self.FILENAME}.new'
130+
with temp.open('w', encoding='utf-8') as f:
131+
f.write(self.encode(compact=compact))
132+
temp.rename(dir / self.FILENAME)
128133

129134
@classmethod
130135
def format(cls, *, check: bool = False) -> None:

0 commit comments

Comments
 (0)