From f9b46b5e7f2dfc3cba0607567eb9fba360d968ae Mon Sep 17 00:00:00 2001 From: Valentin Niess Date: Thu, 22 May 2025 23:43:51 +0200 Subject: [PATCH] Add aarch64 to the update --- python_appimage/commands/build/manylinux.py | 4 +- python_appimage/manylinux/__init__.py | 6 ++- python_appimage/manylinux/extract.py | 52 ++++++++++++++------- python_appimage/manylinux/patch.py | 4 +- python_appimage/utils/manylinux.py | 14 ++++++ scripts/test-appimage.py | 23 ++++++--- scripts/update-appimages.py | 15 ++++-- 7 files changed, 86 insertions(+), 32 deletions(-) create mode 100644 python_appimage/utils/manylinux.py diff --git a/python_appimage/commands/build/manylinux.py b/python_appimage/commands/build/manylinux.py index 0bbd817..fc62397 100644 --- a/python_appimage/commands/build/manylinux.py +++ b/python_appimage/commands/build/manylinux.py @@ -18,7 +18,7 @@ def _unpack_args(args): return args.tag, args.abi, args.bare, args.clean, args.no_packaging -def execute(tag, abi, bare, clean, no_packaging): +def execute(tag, abi, bare=False, clean=False, no_packaging=False): '''Build a Python AppImage using a Manylinux image ''' @@ -48,7 +48,7 @@ def execute(tag, abi, bare, clean, no_packaging): ) elif bare: log('COMPRESS', fullname) - destination = f'{fullname}.tgz' + destination = f'{fullname}.tar.gz' tar_path = Path(tmpdir) / destination with tarfile.open(tar_path, "w:gz") as tar: tar.add(appdir, arcname=fullname) diff --git a/python_appimage/manylinux/__init__.py b/python_appimage/manylinux/__init__.py index a849ec6..56223e7 100644 --- a/python_appimage/manylinux/__init__.py +++ b/python_appimage/manylinux/__init__.py @@ -18,7 +18,11 @@ def ensure_image(tag, *, clean=False, extract=True): except ValueError: image_tag = 'latest' - tag, arch = tag.split('_', 1) + if tag.startswith('2_'): + tag, arch = tag[2:].split('_', 1) + tag = f'2_{tag}' + else: + tag, arch = tag.split('_', 1) tag = LinuxTag.from_brief(tag) arch = Arch.from_str(arch) diff --git a/python_appimage/manylinux/extract.py b/python_appimage/manylinux/extract.py index 11f1167..bc68c76 100644 --- a/python_appimage/manylinux/extract.py +++ b/python_appimage/manylinux/extract.py @@ -77,9 +77,17 @@ class PythonExtractor: if self.arch in (Arch.AARCH64, Arch.X86_64): paths.append(self.prefix / 'lib64') paths.append(self.prefix / 'usr/lib64') + if self.arch == Arch.X86_64: + paths.append(self.prefix / 'lib/x86_64-linux-gnu') + paths.append(self.prefix / 'usr/lib/x86_64-linux-gnu') + else: + paths.append(self.prefix / 'lib/aarch64-linux-gnu') + paths.append(self.prefix / 'usr/lib/aarch64-linux-gnu') elif self.arch == Arch.I686: paths.append(self.prefix / 'lib') paths.append(self.prefix / 'usr/lib') + paths.append(self.prefix / 'lib/i386-linux-gnu') + paths.append(self.prefix / 'usr/lib/i386-linux-gnu') else: raise NotImplementedError() paths.append(self.prefix / 'usr/local/lib') @@ -100,12 +108,13 @@ class PythonExtractor: else: ensure_excludelist() excludelist = Path(EXCLUDELIST) - excluded = [] + excluded = set() with excludelist.open() as f: for line in f: line = line.strip() if line and not line.startswith('#'): - excluded.append(line) + excluded.add(line) + excluded.add('ld-linux-aarch64.so.1') # patch for aarch64. object.__setattr__(self, 'excluded', excluded) # Set patchelf, if not provided. @@ -248,24 +257,31 @@ class PythonExtractor: # Copy Tcl & Tk data. tx_version = [] - for location in ('usr/local/lib', 'usr/share'): - tcltk_src = self.prefix / location - for match in glob.glob(str(tcltk_src / 'tk*')): - path = Path(match) - if path.is_dir(): - tx_version.append(LooseVersion(path.name[2:])) - tx_version.sort() - tx_version = tx_version[-1] + for match in glob.glob(str(system_dest / 'lib/libtk*')): + path = system_dest / f'lib/{match}' + tx_version.append(LooseVersion(path.name[5:8])) - log('INSTALL', f'Tcl/Tk{tx_version}') - tcltk_dir = Path(system_dest / 'share/tcltk') - tcltk_dir.mkdir(exist_ok=True, parents=True) + if tx_version: + tx_version.sort() + tx_version = tx_version[-1] - for tx in ('tcl', 'tk'): - name = f'{tx}{tx_version}' - src = tcltk_src / name - dst = tcltk_dir / name - shutil.copytree(src, dst, symlinks=True, dirs_exist_ok=True) + for location in ('usr/local/lib', 'usr/share', 'usr/share/tcltk'): + tcltk_src = self.prefix / location + path = tcltk_src / f'tk{tx_version}' + if path.exists() and path.is_dir(): + break + else: + raise ValueError(f'could not locate Tcl/Tk{tx_version}') + + log('INSTALL', f'Tcl/Tk{tx_version}') + tcltk_dir = Path(system_dest / 'share/tcltk') + tcltk_dir.mkdir(exist_ok=True, parents=True) + + for tx in ('tcl', 'tk'): + name = f'{tx}{tx_version}' + src = tcltk_src / name + dst = tcltk_dir / name + shutil.copytree(src, dst, symlinks=True, dirs_exist_ok=True) if appify: appifier = Appifier( diff --git a/python_appimage/manylinux/patch.py b/python_appimage/manylinux/patch.py index 603179e..8289c1e 100644 --- a/python_appimage/manylinux/patch.py +++ b/python_appimage/manylinux/patch.py @@ -29,7 +29,7 @@ class Patcher: patch = f'tk-manylinux1_{self.arch}' log('PATCH', patch) tarfile = f'{patch}.tar.gz' - path = cache / patch + path = cache / tarfile if not path.exists(): url = f'https://github.com/niess/python-appimage/releases/download/manylinux1/{tarfile}' urlretrieve(url, path) @@ -40,7 +40,7 @@ class Patcher: cmd = ''.join(( f'trap \'chmod u+rw -R {destination}\' EXIT ; ', f'mkdir -p {destination} && ', - f'tar -xzf {tarfile} -C {destination}', + f'tar -xzf {path} -C {destination}', )) r = subprocess.run(f'/bin/bash -c "{cmd}"', shell=True, capture_output=True) diff --git a/python_appimage/utils/manylinux.py b/python_appimage/utils/manylinux.py new file mode 100644 index 0000000..9b45882 --- /dev/null +++ b/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 diff --git a/scripts/test-appimage.py b/scripts/test-appimage.py index 149f257..28db66b 100755 --- a/scripts/test-appimage.py +++ b/scripts/test-appimage.py @@ -81,7 +81,10 @@ import os appdir = os.environ['APPDIR'] env = {} for var in ('SSL_CERT_FILE', 'TCL_LIBRARY', 'TK_LIBRARY', 'TKPATH'): - env[var] = os.environ[var].replace(appdir, '$APPDIR') + try: + env[var] = os.environ[var].replace(appdir, '$APPDIR') + except KeyError: + pass print(env) ''').run(appimage)) @@ -149,7 +152,11 @@ print(env) content ) content = self.list_content(f'{prefix}/include') - assert_eq([f'python{self.version.flavoured()}'], content) + if (self.version.major == 3) and (self.version.minor <= 7): + expected = [f'python{self.version.short()}m'] + else: + expected = [f'python{self.version.flavoured()}'] + assert_eq(expected, content) content = self.list_content(f'{prefix}/lib') assert_eq([f'python{self.version.flavoured()}'], content) return Status.SUCCESS @@ -171,10 +178,13 @@ print(env) def test_tcltk_bundling(self): '''Check Tcl/Tk bundling''' - for var in ('TCL_LIBRARY', 'TK_LIBRARY', 'TKPATH'): - path = Path(self.env[var].replace('$APPDIR', str(self.appdir))) - assert path.exists() - return Status.SUCCESS + if 'TK_LIBRARY' not in self.env: + return Status.SKIPPED + else: + for var in ('TCL_LIBRARY', 'TK_LIBRARY', 'TKPATH'): + path = Path(self.env[var].replace('$APPDIR', str(self.appdir))) + assert path.exists() + return Status.SUCCESS def test_ssl_bundling(self): '''Check SSL certs bundling''' @@ -262,6 +272,7 @@ with urllib.request.urlopen('https://wikipedia.org') as r: try: os.environ['DISPLAY'] + self.env['TK_LIBRARY'] except KeyError: return Status.SKIPPED else: diff --git a/scripts/update-appimages.py b/scripts/update-appimages.py index 4dec158..f469b89 100755 --- a/scripts/update-appimages.py +++ b/scripts/update-appimages.py @@ -16,9 +16,9 @@ from python_appimage.utils.manylinux import format_appimage_name, format_tag # Build matrix -ARCHS = ('x86_64', 'i686') +ARCHS = ('x86_64', 'i686', 'aarch64') MANYLINUSES = ('1', '2010', '2014', '2_24', '2_28') -EXCLUDES = ('2_28_i686',) +EXCLUDES = ('2_28_i686', '1_aarch64', '2010_aarch64') # Build directory for AppImages APPIMAGES_DIR = 'build-appimages' @@ -202,7 +202,8 @@ def update(args): if new_assets: log('DRY', f'new update summary with {len(new_assets)} entries') - return + if not args.build: + return if new_assets: # Build new AppImage(s) @@ -215,6 +216,9 @@ def update(args): finally: os.chdir(cwd) + if args.dry: + return + # Create any new release(s). for tag in new_releases: meta = ReleaseMeta(tag) @@ -305,6 +309,11 @@ if __name__ == '__main__': action = 'store_true', default = False ) + parser.add_argument('-b', '--build', + help = 'build AppImages (in dry mode)', + action = 'store_true', + default = False + ) parser.add_argument('-d', '--dry', help = 'dry run (only log changes)', action = 'store_true',