From 1944706948e734feebe61c1c1e1ed1b11f361ee0 Mon Sep 17 00:00:00 2001 From: Benjamin Gilbert Date: Mon, 29 Sep 2025 21:42:15 -0700 Subject: [PATCH] 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. --- .github/workflows/release.yml | 18 ++++++++++++++++++ nginx/default | 2 +- tools/create_release.py | 8 ++++++++ tools/utils.py | 19 ++++++++++++------- 4 files changed, 39 insertions(+), 8 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3b0053f17..02e9b9cdb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -25,11 +25,29 @@ jobs: - name: Upload release assets run: | ./tools/create_release.py ${{ github.repository }} ${{ secrets.GITHUB_TOKEN }} + - name: Upload releases.json + uses: actions/upload-pages-artifact@v4 + + update_index: + needs: create_release + runs-on: ubuntu-latest + permissions: + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deploy.outputs.page_url }}releases.json + steps: + - name: Deploy releases.json + id: deploy + uses: actions/deploy-pages@v4 # Ideally we should trigger Meson's CI to update the website, but unfortunately # it requires a Personal Access Token. Instead clone meson and do it ourself. # This job is copied from Meson's workflows. update_website: + # the site builder reads releases.json, so update that first + needs: update_index env: HAS_SSH_KEY: ${{ secrets.WEBSITE_PRIV_KEY != '' }} runs-on: ubuntu-latest diff --git a/nginx/default b/nginx/default index 57f5647d7..957eba1aa 100644 --- a/nginx/default +++ b/nginx/default @@ -10,7 +10,7 @@ server { location /v2 { - rewrite ^.*/v2/releases.json$ https://raw.githubusercontent.com/mesonbuild/wrapdb/master/releases.json permanent; + rewrite ^.*/v2/releases.json$ https://mesonbuild.com/wrapdb/releases.json permanent; rewrite ^.*/v2/([^/]+)/get_source/([^/]+)$ https://github.com/mesonbuild/wrapdb/releases/download/$1/$2 permanent; rewrite ^.*/v2/([^/]+)/get_patch$ https://github.com/mesonbuild/wrapdb/releases/download/$1/$1_patch.zip permanent; rewrite ^.*/v2/([^/]+)/([^/]+).wrap$ https://github.com/mesonbuild/wrapdb/releases/download/$1/$2.wrap permanent; diff --git a/tools/create_release.py b/tools/create_release.py index 6a83c8761..342d09e13 100755 --- a/tools/create_release.py +++ b/tools/create_release.py @@ -193,6 +193,13 @@ def finalize(self) -> None: response.raise_for_status() print('Published release:', self.upload_url) +def generate_site(releases: Releases) -> None: + site = Path('_site') # default path for actions/upload-pages-artifact + if site.exists(): + shutil.rmtree(site) + site.mkdir() + releases.save(dir=site, compact=True) + def run(repo: T.Optional[str], token: T.Optional[str]) -> None: releases = Releases.load() stdout = subprocess.check_output(['git', 'tag']) @@ -202,6 +209,7 @@ def run(repo: T.Optional[str], token: T.Optional[str]) -> None: latest_tag = f'{name}_{versions[0]}' if latest_tag not in tags: CreateRelease(repo, token, latest_tag) + generate_site(releases) if __name__ == '__main__': # Support local testing when passing no arguments diff --git a/tools/utils.py b/tools/utils.py index ea9b08e7a..83fb060cf 100644 --- a/tools/utils.py +++ b/tools/utils.py @@ -118,13 +118,18 @@ def load(cls) -> T.Self: with open(cls.FILENAME, encoding='utf-8') as f: return cls(json.load(f)) - def encode(self) -> str: - return json.dumps(self, indent=2, sort_keys=True) + '\n' - - def save(self) -> None: - with open(f'{self.FILENAME}.new', 'w', encoding='utf-8') as f: - f.write(self.encode()) - os.rename(f'{self.FILENAME}.new', self.FILENAME) + def encode(self, *, compact: bool = False) -> str: + if compact: + kwargs: dict[str, T.Any] = dict(separators=(',', ':')) + else: + kwargs = dict(indent=2) + return json.dumps(self, sort_keys=True, **kwargs) + '\n' + + def save(self, *, dir: Path = Path('.'), compact: bool = False) -> None: + temp = dir / f'{self.FILENAME}.new' + with temp.open('w', encoding='utf-8') as f: + f.write(self.encode(compact=compact)) + temp.rename(dir / self.FILENAME) @classmethod def format(cls, *, check: bool = False) -> None: