#!/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: