mirror of
https://github.com/niess/python-appimage.git
synced 2026-03-14 04:10:15 +01:00
Clean the package layout
This commit is contained in:
0
python_appimage/utils/__init__.py
Normal file
0
python_appimage/utils/__init__.py
Normal file
99
python_appimage/utils/deps.py
Normal file
99
python_appimage/utils/deps.py
Normal file
@@ -0,0 +1,99 @@
|
||||
import os
|
||||
import platform
|
||||
import stat
|
||||
|
||||
from .fs import copy_file, copy_tree, make_tree
|
||||
from .log import log
|
||||
from .system import system
|
||||
from .tmp import TemporaryDirectory
|
||||
from .url import urlretrieve
|
||||
|
||||
|
||||
__all__ = ['APPIMAGETOOL', 'EXCLUDELIST', 'PATCHELF', 'PREFIX',
|
||||
'ensure_appimagetool', 'ensure_excludelist', 'ensure_patchelf',
|
||||
'fetch_all']
|
||||
|
||||
|
||||
_ARCH = platform.machine()
|
||||
|
||||
|
||||
PREFIX = os.path.abspath(os.path.dirname(__file__) + '/..')
|
||||
'''Package installation prefix'''
|
||||
|
||||
APPIMAGETOOL = PREFIX + '/bin/appimagetool.' + _ARCH
|
||||
'''Location of the appimagetool binary'''
|
||||
|
||||
EXCLUDELIST = PREFIX + '/data/excludelist'
|
||||
'''AppImage exclusion list'''
|
||||
|
||||
PATCHELF = PREFIX + '/bin/patchelf.' + _ARCH
|
||||
'''Location of the PatchELF binary'''
|
||||
|
||||
|
||||
def ensure_appimagetool():
|
||||
'''Fetch appimagetool from the web if not available locally
|
||||
'''
|
||||
if os.path.exists(APPIMAGETOOL):
|
||||
return
|
||||
|
||||
appimage = 'appimagetool-{0:}.AppImage'.format(_ARCH)
|
||||
baseurl = 'https://github.com/AppImage/AppImageKit/releases/' \
|
||||
'download/continuous'
|
||||
log('INSTALL', 'appimagetool from %s', baseurl)
|
||||
|
||||
appdir_name = 'appimagetool-{:}.appdir'.format(_ARCH)
|
||||
appdir = os.path.join(os.path.dirname(APPIMAGETOOL), appdir_name)
|
||||
if not os.path.exists(appdir):
|
||||
make_tree(os.path.dirname(appdir))
|
||||
with TemporaryDirectory() as tmpdir:
|
||||
urlretrieve(os.path.join(baseurl, appimage), appimage)
|
||||
os.chmod(appimage, stat.S_IRWXU)
|
||||
system('./' + appimage, '--appimage-extract')
|
||||
copy_tree('squashfs-root', appdir)
|
||||
|
||||
if not os.path.exists(APPIMAGETOOL):
|
||||
os.symlink(appdir_name + '/AppRun', APPIMAGETOOL)
|
||||
|
||||
|
||||
# Installers for dependencies
|
||||
def ensure_excludelist():
|
||||
'''Fetch the AppImage excludelist from the web if not available locally
|
||||
'''
|
||||
if os.path.exists(EXCLUDELIST):
|
||||
return
|
||||
|
||||
baseurl = 'https://raw.githubusercontent.com/probonopd/AppImages/master'
|
||||
log('INSTALL', 'excludelist from %s', baseurl)
|
||||
urlretrieve(baseurl + '/excludelist', EXCLUDELIST)
|
||||
mode = os.stat(EXCLUDELIST)[stat.ST_MODE]
|
||||
os.chmod(EXCLUDELIST, mode | stat.S_IWGRP | stat.S_IWOTH)
|
||||
|
||||
|
||||
def ensure_patchelf():
|
||||
'''Fetch PatchELF from the web if not available locally
|
||||
'''
|
||||
if os.path.exists(PATCHELF):
|
||||
return
|
||||
|
||||
iarch = 'i386' if _ARCH == 'i686' else _ARCH
|
||||
appimage = 'patchelf-{0:}.AppImage'.format(iarch)
|
||||
baseurl = 'https://github.com/niess/patchelf.appimage/releases/download'
|
||||
log('INSTALL', 'patchelf from %s', baseurl)
|
||||
|
||||
dirname = os.path.dirname(PATCHELF)
|
||||
patchelf = dirname + '/patchelf.' + _ARCH
|
||||
make_tree(dirname)
|
||||
with TemporaryDirectory() as tmpdir:
|
||||
urlretrieve(os.path.join(baseurl, 'rolling', appimage), appimage)
|
||||
os.chmod(appimage, stat.S_IRWXU)
|
||||
system('./' + appimage, '--appimage-extract')
|
||||
copy_file('squashfs-root/usr/bin/patchelf', patchelf)
|
||||
os.chmod(patchelf, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
|
||||
|
||||
|
||||
def fetch_all():
|
||||
'''Fetch all dependencies from the web
|
||||
'''
|
||||
ensure_appimagetool()
|
||||
ensure_excludelist()
|
||||
ensure_patchelf()
|
||||
49
python_appimage/utils/docker.py
Normal file
49
python_appimage/utils/docker.py
Normal file
@@ -0,0 +1,49 @@
|
||||
import os
|
||||
import platform
|
||||
import stat
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from .log import log
|
||||
from .system import system
|
||||
|
||||
|
||||
def docker_run(image, extra_cmds):
|
||||
'''Execute commands within a docker container
|
||||
'''
|
||||
|
||||
ARCH = platform.machine()
|
||||
if image.endswith(ARCH):
|
||||
bash_arg = '/pwd/run.sh'
|
||||
elif image.endswith('i686') and ARCH == 'x86_64':
|
||||
bash_arg = '-c "linux32 /pwd/run.sh"'
|
||||
elif image.endswith('x86_64') and ARCH == 'i686':
|
||||
bash_arg = '-c "linux64 /pwd/run.sh"'
|
||||
else:
|
||||
raise ValueError('Unsupported Docker image: ' + image)
|
||||
|
||||
log('PULL', image)
|
||||
system('docker', 'pull', image)
|
||||
|
||||
script = [
|
||||
'set -e',
|
||||
'trap "chown -R {:}:{:} *" EXIT'.format(os.getuid(),
|
||||
os.getgid()),
|
||||
'cd /pwd'
|
||||
]
|
||||
|
||||
script += extra_cmds
|
||||
|
||||
with open('run.sh', 'w') as f:
|
||||
f.write(os.linesep.join(script))
|
||||
os.chmod('run.sh', stat.S_IRWXU)
|
||||
|
||||
cmd = ' '.join(('docker', 'run', '--mount',
|
||||
'type=bind,source={:},target=/pwd'.format(os.getcwd()),
|
||||
image, '/bin/bash', bash_arg))
|
||||
|
||||
log('RUN', image)
|
||||
p = subprocess.Popen(cmd, shell=True)
|
||||
p.communicate()
|
||||
if p.returncode != 0:
|
||||
sys.exit(p.returncode)
|
||||
75
python_appimage/utils/fs.py
Normal file
75
python_appimage/utils/fs.py
Normal file
@@ -0,0 +1,75 @@
|
||||
from distutils.dir_util import mkpath as _mkpath, remove_tree as _remove_tree
|
||||
from distutils.file_util import copy_file as _copy_file
|
||||
import errno
|
||||
import os
|
||||
|
||||
from .log import debug
|
||||
|
||||
|
||||
__all__ = ['copy_file', 'copy_tree', 'make_tree', 'remove_file', 'remove_tree']
|
||||
|
||||
|
||||
# Wrap some file system related functions
|
||||
def make_tree(path):
|
||||
'''Create directories recursively if they don't exist
|
||||
'''
|
||||
debug('MKDIR', path)
|
||||
return _mkpath(path)
|
||||
|
||||
|
||||
def copy_file(source, destination, update=False, verbose=True):
|
||||
'''
|
||||
'''
|
||||
name = os.path.basename(source)
|
||||
if verbose:
|
||||
debug('COPY', '%s from %s', name, os.path.dirname(source))
|
||||
_copy_file(source, destination, update=update)
|
||||
|
||||
|
||||
def copy_tree(source, destination):
|
||||
'''Copy (or update) a directory preserving symlinks
|
||||
'''
|
||||
if not os.path.exists(source):
|
||||
raise OSError(errno.ENOENT, 'No such file or directory: ' + source)
|
||||
|
||||
name = os.path.basename(source)
|
||||
debug('COPY', '%s from %s', name, os.path.dirname(source))
|
||||
|
||||
for root, _, files in os.walk(source):
|
||||
relpath = os.path.relpath(root, source)
|
||||
dirname = os.path.join(destination, relpath)
|
||||
_mkpath(dirname)
|
||||
for file_ in files:
|
||||
src = os.path.join(root, file_)
|
||||
dst = os.path.join(dirname, file_)
|
||||
if os.path.islink(src):
|
||||
try:
|
||||
os.remove(dst)
|
||||
except OSError:
|
||||
pass
|
||||
linkto = os.readlink(src)
|
||||
os.symlink(linkto, dst)
|
||||
else:
|
||||
copy_file(src, dst, update=True, verbose=False)
|
||||
|
||||
|
||||
def remove_file(path):
|
||||
'''remove a file if it exists
|
||||
'''
|
||||
name = os.path.basename(path)
|
||||
debug('REMOVE', '%s from %s', name, os.path.dirname(path))
|
||||
try:
|
||||
os.remove(path)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
def remove_tree(path):
|
||||
'''remove a directory if it exists
|
||||
'''
|
||||
name = os.path.basename(path)
|
||||
debug('REMOVE', '%s from %s', name, os.path.dirname(path))
|
||||
try:
|
||||
_remove_tree(path)
|
||||
except OSError:
|
||||
pass
|
||||
23
python_appimage/utils/log.py
Normal file
23
python_appimage/utils/log.py
Normal file
@@ -0,0 +1,23 @@
|
||||
import logging
|
||||
|
||||
|
||||
__all__ = ['debug', 'log']
|
||||
|
||||
|
||||
# Configure the logger
|
||||
logging.basicConfig(
|
||||
format='[%(asctime)s] %(message)s',
|
||||
level=logging.INFO
|
||||
)
|
||||
|
||||
|
||||
def log(task, fmt, *args):
|
||||
'''Log a standard message
|
||||
'''
|
||||
logging.info('%-8s ' + fmt, task, *args)
|
||||
|
||||
|
||||
def debug(task, fmt, *args):
|
||||
'''Report some debug information
|
||||
'''
|
||||
logging.debug('%-8s ' + fmt, task, *args)
|
||||
46
python_appimage/utils/system.py
Normal file
46
python_appimage/utils/system.py
Normal file
@@ -0,0 +1,46 @@
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
|
||||
from .log import debug
|
||||
|
||||
|
||||
__all__ = ['ldd', 'system']
|
||||
|
||||
|
||||
def _decode(s):
|
||||
'''Decode Python 3 bytes as str
|
||||
'''
|
||||
try:
|
||||
return s.decode()
|
||||
except AttributeError:
|
||||
return s
|
||||
|
||||
|
||||
def system(*args):
|
||||
'''System call with capturing output
|
||||
'''
|
||||
cmd = ' '.join(args)
|
||||
debug('SYSTEM', cmd)
|
||||
|
||||
p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
out, err = p.communicate()
|
||||
if err:
|
||||
err = _decode(err)
|
||||
stripped = [line for line in err.split(os.linesep)
|
||||
if line and not line.startswith('fuse: warning:')]
|
||||
if stripped:
|
||||
raise RuntimeError(err)
|
||||
|
||||
return str(_decode(out).strip())
|
||||
|
||||
|
||||
_ldd_pattern = re.compile('=> (.+) [(]0x')
|
||||
|
||||
|
||||
def ldd(path):
|
||||
'''Get dependencies list of dynamic libraries
|
||||
'''
|
||||
out = system('ldd', path)
|
||||
return _ldd_pattern.findall(out)
|
||||
24
python_appimage/utils/tmp.py
Normal file
24
python_appimage/utils/tmp.py
Normal file
@@ -0,0 +1,24 @@
|
||||
from contextlib import contextmanager as contextmanager
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
from .fs import remove_tree
|
||||
from .log import debug
|
||||
|
||||
|
||||
__all__ = ['TemporaryDirectory']
|
||||
|
||||
|
||||
@contextmanager
|
||||
def TemporaryDirectory():
|
||||
'''Create a temporary directory (Python 2 wrapper)
|
||||
'''
|
||||
tmpdir = tempfile.mkdtemp(prefix='python-appimage-')
|
||||
debug('MKDIR', tmpdir)
|
||||
pwd = os.getcwd()
|
||||
os.chdir(tmpdir)
|
||||
try:
|
||||
yield tmpdir
|
||||
finally:
|
||||
os.chdir(pwd)
|
||||
remove_tree(tmpdir)
|
||||
28
python_appimage/utils/url.py
Normal file
28
python_appimage/utils/url.py
Normal file
@@ -0,0 +1,28 @@
|
||||
import os
|
||||
try:
|
||||
from urllib.request import urlretrieve as _urlretrieve
|
||||
except ImportError:
|
||||
import urllib2
|
||||
_urlretrieve = None
|
||||
|
||||
from .log import debug
|
||||
|
||||
|
||||
__all__ = ['urlretrieve']
|
||||
|
||||
|
||||
def urlretrieve(url, filename=None):
|
||||
'''Download a file to disk
|
||||
'''
|
||||
if filename is None:
|
||||
filename = os.path.basename(url)
|
||||
debug('DOWNLOAD', '%s from %s', name, os.path.dirname(url))
|
||||
else:
|
||||
debug('DOWNLOAD', '%s as %s', url, filename)
|
||||
|
||||
if _urlretrieve is None:
|
||||
data = urllib2.urlopen(url).read()
|
||||
with open(filename, 'w') as f:
|
||||
f.write(data)
|
||||
else:
|
||||
_urlretrieve(url, filename)
|
||||
Reference in New Issue
Block a user