-
Simon McVittie authored
Signed-off-by:
Simon McVittie <smcv@collabora.com>
Simon McVittie authoredSigned-off-by:
Simon McVittie <smcv@collabora.com>
build-relocatable-install.py 14.77 KiB
#!/usr/bin/env python3
# Copyright © 2017-2019 Collabora Ltd.
#
# SPDX-License-Identifier: MIT
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
import argparse
import glob
import os
import re
import shutil
import subprocess
try:
import typing
except ImportError:
pass
else:
typing # silence pyflakes
try:
from shlex import quote
except ImportError:
from pipes import quote # noqa
class Architecture:
def __init__(
self,
name, # type: str
multiarch, # type: str
ld_so # type: str
):
# type: (...) -> None
self.name = name
self.multiarch = multiarch
self.ld_so = ld_so
# Debian architecture => Debian multiarch tuple
ARCHS = [
Architecture(
name='amd64',
multiarch='x86_64-linux-gnu',
ld_so='/lib64/ld-linux-x86-64.so.2',
),
Architecture(
name='i386',
multiarch='i386-linux-gnu',
ld_so='/lib/ld-linux.so.2',
),
]
# package to install from => source package for copyright information
DEPENDENCIES = {
'libcapsule-tools-relocatable': 'libcapsule',
'libelf1': 'elfutils',
'zlib1g': 'zlib',
}
# program to install => binary package
WRAPPED_PROGRAMS = {
'bwrap': 'bubblewrap',
}
PRIMARY_ARCH_DEPENDENCIES = {
'bubblewrap': 'bubblewrap',
'libblkid1': 'util-linux',
'libcap2': 'libcap2',
'libffi6': 'libffi',
'libglib2.0-0': 'glib2.0',
'libmount1': 'util-linux',
'libpcre3': 'pcre3',
'libxau6': 'libxau',
'libselinux1': 'libselinux',
}
SCRIPTS = [
'pressure-vessel-test-ui',
'pressure-vessel-unruntime',
'pressure-vessel-wrap.sh'
]
EXECUTABLES = [
'pressure-vessel-wrap'
]
TOOLS = [
'capsule-capture-libs',
'capsule-symbols',
]
def install(src, dst, mode=0o644):
# type: (str, str, int) -> None
shutil.copy(src, dst)
if os.path.isdir(dst):
dst = os.path.join(dst, os.path.basename(src))
os.chmod(dst, mode)
def install_exe(src, dst, mode=0o755):
# type: (str, str, int) -> None
install(src, dst, mode)
def v_check_call(command, **kwargs):
print('# {}'.format(command))
subprocess.check_call(command, **kwargs)
def v_check_output(command, **kwargs):
print('# {}'.format(command))
return subprocess.check_output(command, **kwargs)
def main():
# type: () -> None
parser = argparse.ArgumentParser()
parser.add_argument(
'--destdir', default=os.getenv('DESTDIR', ''))
parser.add_argument(
'--srcdir', default=os.getenv('MESON_SOURCE_ROOT', '.'))
parser.add_argument(
'--builddir', default=os.getenv('MESON_BUILD_ROOT', '_build'))
parser.add_argument(
'--prefix',
default=os.getenv(
'MESON_INSTALL_PREFIX',
os.path.abspath(os.path.join('_build', 'relocatable-install')),
),
)
parser.add_argument(
'--archive', default=os.getenv('MESON_BUILD_ROOT', '.'))
parser.add_argument('--relocatabledir', default='')
parser.add_argument('--set-version', dest='version', default='unknown')
args = parser.parse_args()
os.chdir(args.builddir)
destdir_prefix = os.getenv(
'MESON_INSTALL_DESTDIR_PREFIX',
args.destdir + args.prefix,
)
os.makedirs(os.path.join(destdir_prefix, 'bin'), exist_ok=True)
os.makedirs(os.path.join(destdir_prefix, 'sources'), exist_ok=True)
for arch in ARCHS:
os.makedirs(
os.path.join(destdir_prefix, 'lib', arch.multiarch),
exist_ok=True,
)
for script in SCRIPTS:
install_exe(
os.path.join(args.srcdir, script),
os.path.join(destdir_prefix, 'bin'),
)
for exe in EXECUTABLES:
install_exe(
os.path.join(args.builddir, exe),
os.path.join(destdir_prefix, 'bin'),
)
install(
os.path.join(args.srcdir, 'THIRD-PARTY.md'),
os.path.join(destdir_prefix, 'sources', 'README.txt'),
0o644,
)
if args.relocatabledir:
for arch in ARCHS:
for tool in TOOLS:
install_exe(
os.path.join(
args.relocatabledir,
'{}-{}'.format(arch.multiarch, tool),
),
os.path.join(destdir_prefix, 'bin'),
)
install(
'/usr/share/doc/libcapsule-tools-relocatable/copyright',
os.path.join(destdir_prefix, 'sources', 'libcapsule.txt'),
)
else:
for arch in ARCHS:
for tool in TOOLS:
install_exe(
os.path.join(
'build-relocatable',
arch.name,
'libcapsule',
tool,
),
os.path.join(
'build-relocatable',
'{}-{}'.format(arch.multiarch, tool),
),
)
v_check_call([
'chrpath', '-r',
'${ORIGIN}/../lib/' + arch.multiarch,
os.path.join(
'build-relocatable',
'{}-{}'.format(arch.multiarch, tool),
),
])
install_exe(
os.path.join(
'build-relocatable',
'{}-{}'.format(arch.multiarch, tool),
),
os.path.join(destdir_prefix, 'bin'),
)
install(
os.path.join(
args.builddir,
'libcapsule',
'debian',
'copyright',
),
os.path.join(destdir_prefix, 'sources', 'libcapsule.txt'),
)
for dsc in glob.glob(
os.path.join(args.srcdir, 'libcapsule*.dsc')
):
v_check_call([
'dcmd', 'install', '-m644', dsc,
os.path.join(destdir_prefix, 'sources'),
])
primary_architecture = subprocess.check_output([
'dpkg', '--print-architecture',
]).decode('utf-8').strip()
for arch in ARCHS:
os.makedirs(
os.path.join('build-relocatable', arch.name, 'lib'),
exist_ok=True,
)
v_check_call([
'{}/bin/{}-capsule-capture-libs'.format(
destdir_prefix,
arch.multiarch,
),
'--dest=build-relocatable/{}/lib'.format(arch.name),
'--no-glibc',
'soname:libelf.so.1',
'soname:libz.so.1',
])
if arch.name == primary_architecture:
v_check_call([
'{}/bin/{}-capsule-capture-libs'.format(
destdir_prefix,
arch.multiarch,
),
'--dest=build-relocatable/{}/lib'.format(arch.name),
'--no-glibc',
'soname:libXau.so.6',
'soname:libcap.so.2',
'soname:libgio-2.0.so.0',
'soname:libpcre.so.3',
'soname:libselinux.so.1',
])
for so in glob.glob(
os.path.join('build-relocatable', arch.name, 'lib', '*.so.*'),
):
install(so, os.path.join(destdir_prefix, 'lib', arch.multiarch))
# For bwrap (and possibly other programs in future) we don't have
# a relocatable version with a RPATH/RUNPATH, so we wrap a script
# around it instead. The script avoids setting LD_LIBRARY_PATH
# because that would leak through to the programs invoked by bwrap.
for exe, package in WRAPPED_PROGRAMS.items():
path = '/usr/bin/{}'.format(exe)
if not os.path.exists(path):
v_check_call([
'apt-get',
'download',
package,
])
v_check_call(
'dpkg-deb -x {}_*.deb build-relocatable'.format(
quote(package),
),
shell=True,
)
path = 'build-relocatable/usr/bin/{}'.format(exe)
for arch in ARCHS:
if arch.name != primary_architecture:
continue
install_exe(
path,
os.path.join(destdir_prefix, 'bin', exe + '.bin'),
)
with open(
os.path.join('build-relocatable', arch.name, exe),
'w',
) as writer:
writer.write('#!/bin/sh\n')
writer.write('set -eu\n')
writer.write('here="$(dirname "$0")"\n')
writer.write(
'exec ${{RELOCATABLE_INSTALL_WRAPPER-}} {} '
'--library-path "$here"/../lib/{} '
'"$here"/{}.bin "$@"\n'.format(
quote(arch.ld_so),
quote(arch.multiarch),
quote(exe),
)
)
install_exe(
os.path.join('build-relocatable', arch.name, exe),
os.path.join(destdir_prefix, 'bin', exe),
)
source_to_download = set() # type: typing.Set[str]
for package, source in (
list(DEPENDENCIES.items()) + list(PRIMARY_ARCH_DEPENDENCIES.items())
):
if not args.relocatabledir and source == 'libcapsule':
continue
if os.path.exists('/usr/share/doc/{}/copyright'.format(package)):
install(
'/usr/share/doc/{}/copyright'.format(package),
os.path.join(
destdir_prefix,
'sources',
'{}.txt'.format(source),
),
)
for expr in set(
v_check_output([
'dpkg-query',
'-W',
'-f', '${source:Package}=${source:Version}\n',
package,
], universal_newlines=True).splitlines()
):
source_to_download.add(re.sub(r'[+]srt[0-9a-z.]+$', '', expr))
else:
install(
'build-relocatable/usr/share/doc/{}/copyright'.format(package),
os.path.join(
destdir_prefix,
'sources',
'{}.txt'.format(source),
),
)
source_to_download.add(source)
v_check_call(
[
'apt-get',
'--download-only',
'--only-source',
'source',
] + list(source_to_download),
cwd=os.path.join(destdir_prefix, 'sources'),
)
os.makedirs(
os.path.join(destdir_prefix, 'sources', 'pressure-vessel'),
exist_ok=True,
)
for src_tar in (
os.path.join(
args.srcdir,
'pressure-vessel-{}.tar.gz'.format(args.version),
),
os.path.join(
args.srcdir,
'pressure-vessel-{}.tar.xz'.format(args.version),
),
):
if os.path.exists(src_tar):
subprocess.check_call([
'tar',
'-C', os.path.join(
destdir_prefix,
'sources',
'pressure-vessel',
),
'--strip-components=1',
'-xvf', src_tar,
])
break
else:
if os.path.exists('/usr/bin/git'):
git_archive = subprocess.Popen([
'git', 'archive', '--format=tar', 'HEAD',
], cwd=args.srcdir, stdout=subprocess.PIPE)
subprocess.check_call([
'tar',
'-C', os.path.join(
destdir_prefix,
'sources',
'pressure-vessel',
),
'-xvf-',
], stdin=git_archive.stdout)
if git_archive.wait() != 0:
raise subprocess.CalledProcessError(
returncode=git_archive.returncode,
cmd=git_archive.args,
)
else:
tar = subprocess.Popen([
'tar',
'-C', args.srcdir,
'--exclude=./.git',
'--exclude=./_build',
'--exclude=./relocatable-install',
'--exclude=./.mypy_cache',
'--exclude=.*.swp',
'-cf-',
'.',
], stdout=subprocess.PIPE)
subprocess.check_call([
'tar',
'-C', os.path.join(
destdir_prefix,
'sources',
'pressure-vessel',
),
'-xvf-',
], stdin=tar.stdout)
if tar.wait() != 0:
raise subprocess.CalledProcessError(
returncode=git_archive.returncode,
cmd=git_archive.args,
)
bin_tar = 'pressure-vessel-{}-bin.tar.gz'.format(args.version)
src_tar = 'pressure-vessel-{}-bin+src.tar.gz'.format(args.version)
subprocess.check_call([
'tar',
r'--transform=s,^\(\.\(/\|$\)\)\?,pressure-vessel-{}/,'.format(
args.version,
),
'-zcvf', src_tar + '.tmp',
'-C', destdir_prefix,
'.',
])
subprocess.check_call([
'tar',
r'--transform=s,^\(\.\(/\|$\)\)\?,pressure-vessel-{}/,'.format(
args.version,
),
'--exclude=sources',
'-zcvf', bin_tar + '.tmp',
'-C', destdir_prefix,
'.',
])
os.rename(bin_tar + '.tmp', bin_tar)
os.rename(src_tar + '.tmp', src_tar)
print('Generated {}'.format(os.path.abspath(bin_tar)))
print('Generated {}'.format(os.path.abspath(src_tar)))
if __name__ == '__main__':
main()
# vim:set sw=4 sts=4 et: