Jelajahi Sumber

Python AppImages updater

Valentin Niess 2 tahun lalu
induk
melakukan
4fcdf2cba1
2 mengubah file dengan 216 tambahan dan 0 penghapusan
  1. 14 0
      python_appimage/utils/manylinux.py
  2. 202 0
      scripts/update-appimages.py

+ 14 - 0
python_appimage/utils/manylinux.py

@@ -0,0 +1,14 @@
+def format_appimage_name(abi, version, tag):
+    '''Format the Python AppImage name using the ABI, python version and OS tags
+    '''
+    return 'python{:}-{:}-{:}.AppImage'.format(
+        version, abi, format_tag(tag))
+
+
+def format_tag(tag):
+    '''Format Manylinux tag
+    '''
+    if tag.startswith('2_'):
+        return 'manylinux_' + tag
+    else:
+        return 'manylinux' + tag

+ 202 - 0
scripts/update-appimages.py

@@ -0,0 +1,202 @@
+#! /usr/bin/env python3
+import argparse
+from collections import defaultdict
+from dataclasses import dataclass
+import os
+import subprocess
+from typing import Optional
+
+from github import Auth, Github
+
+from python_appimage.commands.build.manylinux import execute as build_manylinux
+from python_appimage.commands.list import execute as list_pythons
+from python_appimage.utils.log import log
+from python_appimage.utils.manylinux import format_appimage_name
+
+
+# Build matrix
+ARCHS = ('x86_64', 'i686')
+MANYLINUSES = ('1', '2010', '2014', '2_24', '2_28')
+EXCLUDES = ('2_28_i686',)
+
+# Build directory for AppImages
+APPIMAGES_DIR = 'build-appimages'
+
+
+@dataclass
+class ReleaseMeta:
+    '''Metadata relative to a GitHub release
+    '''
+
+    tag: str
+
+    release: Optional["github.GitRelease"] = None
+
+    def title(self):
+        '''Returns release title'''
+        version = self.tag[6:]
+        return f'Python {version}'
+
+
+@dataclass
+class AssetMeta:
+    '''Metadata relative to a release Asset
+    '''
+
+    tag: str
+    abi: str
+    version: str
+
+    asset: Optional["github.GitReleaseAsset"] = None
+
+    @classmethod
+    def from_appimage(cls, name):
+        '''Returns an instance from a Python AppImage name
+        '''
+        tmp = name[6:-9]
+        tmp, tag = tmp.split('-manylinux', 1)
+        if tag.startswith('_'):
+            tag = tag[1:]
+        version, abi = tmp.split('-', 1)
+        return cls(
+            tag = tag,
+            abi = abi,
+            version = version
+        )
+
+    def appimage_name(self):
+        '''Returns Python AppImage name'''
+        return format_appimage_name(self.abi, self.version, self.tag)
+
+    def release_tag(self):
+        '''Returns release git tag'''
+        version = self.version.rsplit('.', 1)[0]
+        return f'python{version}'
+
+
+def update(args):
+    '''Update Python AppImage GitHub releases
+    '''
+
+    # Connect to GitHub
+    if args.token is None:
+        # Get token from gh app (e.g. for local runs)
+        p = subprocess.run(
+            'gh auth token',
+            shell = True,
+            capture_output = True,
+            check = True
+        )
+        token = p.stdout.decode().strip()
+
+    auth = Auth.Token(token)
+    session = Github(auth=auth)
+    repo = session.get_repo('niess/python-appimage')
+
+    # Fetch currently released AppImages
+    log('FETCH', 'Currently released AppImages')
+    releases = {}
+    assets = defaultdict(dict)
+    n_assets = 0
+    for release in repo.get_releases():
+        if release.tag_name.startswith('python'):
+            releases[release.tag_name] = ReleaseMeta(
+                tag = release.tag_name,
+                release = release
+            )
+            for asset in release.get_assets():
+                if asset.name.endswith('.AppImage'):
+                    n_assets += 1
+                    meta = AssetMeta.from_appimage(asset.name)
+                    assert(meta.release_tag() == release.tag_name)
+                    meta.asset = asset
+                    assets[meta.tag][meta.abi] = meta
+
+    n_releases = len(releases)
+    log('FETCH', f'Found {n_assets} AppImages in {n_releases} releases')
+
+    # Look for updates.
+    new_releases = set()
+    new_assets = []
+
+    for manylinux in MANYLINUSES:
+        for arch in ARCHS:
+            tag = f'{manylinux}_{arch}'
+            if tag in EXCLUDES:
+                continue
+
+            pythons = list_pythons(tag)
+            for (abi, version) in pythons:
+                try:
+                    meta = assets[tag][abi]
+                except KeyError:
+                    meta = None
+
+                if meta is None or meta.version != version:
+                    new_meta = AssetMeta(
+                        tag = tag,
+                        abi = abi,
+                        version = version
+                    )
+                    if meta is not None:
+                        new_meta.asset = meta.asset
+                    new_assets.append(new_meta)
+
+                    rtag = new_meta.release_tag()
+                    if rtag not in releases:
+                        new_releases.add(rtag)
+
+    if not new_assets:
+        return
+
+    # Build new AppImage(s)
+    cwd = os.getcwd()
+    os.makedirs(APPIMAGES_DIR, exist_ok=True)
+    try:
+        os.chdir(APPIMAGES_DIR)
+        for meta in new_assets:
+            build_manylinux(meta.tag, meta.abi)
+    finally:
+        os.chdir(cwd)
+
+    # Create any new release(s).
+    repo = session.get_repo('niess/test-releases') # XXX
+
+    for tag in new_releases:
+        meta = ReleaseMeta(tag)
+        title = meta.title()
+        meta.release = repo.create_git_release(
+            tag = meta.tag,
+            name = title,
+            message = f'Appimage distributions of {title} (see `Assets` below)',
+            prerelease = True
+        )
+        releases[tag] = meta
+
+    # Update assets.
+    for meta in new_assets:
+        release = releases[meta.release_tag()].release
+        appimage = meta.appimage_name()
+        new_asset = release.upload_asset(
+            path = f'{APPIMAGES_DIR}/{appimage}',
+            label = appimage
+        )
+        if meta.asset:
+            meta.asset.delete_asset()
+        meta.asset = new_asset
+        assets[meta.tag][meta.abi] = meta
+
+
+if __name__ == '__main__':
+    parser = argparse.ArgumentParser(
+        description = "Update GitHub releases of Python AppImages"
+    )
+    parser.add_argument("-t", "--token",
+        help = "GitHub authentication token"
+    )
+    parser.add_argument("-s", "--sha",
+        help = "Current commit SHA"
+    )
+
+    args = parser.parse_args()
+    update(args)