From 6a21e195154de326602763b88d3b6e2e2954d696 Mon Sep 17 00:00:00 2001 From: Jussi Pakkanen Date: Sat, 19 May 2018 21:55:05 +0300 Subject: Refactored installer to use a class to eradicate global variables. --- mesonbuild/minstall.py | 533 ++++++++++++++++++++++++------------------------- 1 file changed, 264 insertions(+), 269 deletions(-) (limited to 'mesonbuild/minstall.py') diff --git a/mesonbuild/minstall.py b/mesonbuild/minstall.py index c5d99b5..27b5a58 100644 --- a/mesonbuild/minstall.py +++ b/mesonbuild/minstall.py @@ -22,7 +22,6 @@ from .mesonlib import is_windows, Popen_safe from .mtest import rebuild_all from __main__ import __file__ as main_file -install_log_file = None selinux_updates = [] def buildparser(): @@ -34,7 +33,8 @@ def buildparser(): return parser class DirMaker: - def __init__(self): + def __init__(self, lf): + self.lf = lf self.dirs = [] def makedirs(self, path, exist_ok=False): @@ -60,12 +60,18 @@ class DirMaker: def __exit__(self, type, value, traceback): self.dirs.reverse() for d in self.dirs: - append_to_log(d) + append_to_log(self.lf, d) def is_executable(path): '''Checks whether any of the "x" bits are set in the source file mode.''' return bool(os.stat(path).st_mode & 0o111) +def append_to_log(lf, line): + lf.write(line) + if not line.endswith('\n'): + lf.write('\n') + lf.flush() + def sanitize_permissions(path, umask): if umask is None: return @@ -135,96 +141,6 @@ def restore_selinux_contexts(): 'Standard output:', out.decode(), 'Standard error:', err.decode(), sep='\n') -def append_to_log(line): - install_log_file.write(line) - if not line.endswith('\n'): - install_log_file.write('\n') - install_log_file.flush() - -def do_copyfile(from_file, to_file): - if not os.path.isfile(from_file): - raise RuntimeError('Tried to install something that isn\'t a file:' - '{!r}'.format(from_file)) - # copyfile fails if the target file already exists, so remove it to - # allow overwriting a previous install. If the target is not a file, we - # want to give a readable error. - if os.path.exists(to_file): - if not os.path.isfile(to_file): - raise RuntimeError('Destination {!r} already exists and is not ' - 'a file'.format(to_file)) - os.unlink(to_file) - shutil.copyfile(from_file, to_file) - shutil.copystat(from_file, to_file) - selinux_updates.append(to_file) - append_to_log(to_file) - -def do_copydir(data, src_dir, dst_dir, exclude, install_mode): - ''' - Copies the contents of directory @src_dir into @dst_dir. - - For directory - /foo/ - bar/ - excluded - foobar - file - do_copydir(..., '/foo', '/dst/dir', {'bar/excluded'}, None) creates - /dst/ - dir/ - bar/ - foobar - file - - Args: - src_dir: str, absolute path to the source directory - dst_dir: str, absolute path to the destination directory - exclude: (set(str), set(str)), tuple of (exclude_files, exclude_dirs), - each element of the set is a path relative to src_dir. - install_mode: FileMode object, or None to use defaults. - ''' - if not os.path.isabs(src_dir): - raise ValueError('src_dir must be absolute, got %s' % src_dir) - if not os.path.isabs(dst_dir): - raise ValueError('dst_dir must be absolute, got %s' % dst_dir) - if exclude is not None: - exclude_files, exclude_dirs = exclude - else: - exclude_files = exclude_dirs = set() - for root, dirs, files in os.walk(src_dir): - assert os.path.isabs(root) - for d in dirs[:]: - abs_src = os.path.join(root, d) - filepart = os.path.relpath(abs_src, start=src_dir) - abs_dst = os.path.join(dst_dir, filepart) - # Remove these so they aren't visited by os.walk at all. - if filepart in exclude_dirs: - dirs.remove(d) - continue - if os.path.isdir(abs_dst): - continue - if os.path.exists(abs_dst): - print('Tried to copy directory %s but a file of that name already exists.' % abs_dst) - sys.exit(1) - data.dirmaker.makedirs(abs_dst) - shutil.copystat(abs_src, abs_dst) - sanitize_permissions(abs_dst, data.install_umask) - for f in files: - abs_src = os.path.join(root, f) - filepart = os.path.relpath(abs_src, start=src_dir) - if filepart in exclude_files: - continue - abs_dst = os.path.join(dst_dir, filepart) - if os.path.isdir(abs_dst): - print('Tried to copy file %s but a directory of that name already exists.' % abs_dst) - if os.path.exists(abs_dst): - os.unlink(abs_dst) - parent_dir = os.path.dirname(abs_dst) - if not os.path.isdir(parent_dir): - os.mkdir(parent_dir) - shutil.copystat(os.path.dirname(abs_src), parent_dir) - shutil.copy2(abs_src, abs_dst, follow_symlinks=False) - set_mode(abs_dst, install_mode, data.install_umask) - append_to_log(abs_dst) def get_destdir_path(d, path): if os.path.isabs(path): @@ -233,115 +149,6 @@ def get_destdir_path(d, path): output = os.path.join(d.fullprefix, path) return output -def do_install(log_dir, datafilename): - global install_log_file - - with open(datafilename, 'rb') as ifile: - d = pickle.load(ifile) - d.destdir = os.environ.get('DESTDIR', '') - d.fullprefix = destdir_join(d.destdir, d.prefix) - - if d.install_umask is not None: - os.umask(d.install_umask) - - with open(os.path.join(log_dir, 'install-log.txt'), 'w') as lf: - install_log_file = lf - append_to_log('# List of files installed by Meson') - append_to_log('# Does not contain files installed by custom scripts.') - - try: - d.dirmaker = DirMaker() - with d.dirmaker: - install_subdirs(d) # Must be first, because it needs to delete the old subtree. - install_targets(d) - install_headers(d) - install_man(d) - install_data(d) - restore_selinux_contexts() - run_install_script(d) - except PermissionError: - if shutil.which('pkexec') is not None and 'PKEXEC_UID' not in os.environ: - print('Installation failed due to insufficient permissions.') - print('Attempting to use polkit to gain elevated privileges...') - os.execlp('pkexec', 'pkexec', sys.executable, main_file, *sys.argv[1:], - os.getcwd()) - else: - raise - - -def install_subdirs(d): - for (src_dir, dst_dir, mode, exclude) in d.install_subdirs: - full_dst_dir = get_destdir_path(d, dst_dir) - print('Installing subdir %s to %s' % (src_dir, full_dst_dir)) - d.dirmaker.makedirs(full_dst_dir, exist_ok=True) - do_copydir(d, src_dir, full_dst_dir, exclude, mode) - -def install_data(d): - for i in d.data: - fullfilename = i[0] - outfilename = get_destdir_path(d, i[1]) - mode = i[2] - outdir = os.path.dirname(outfilename) - d.dirmaker.makedirs(outdir, exist_ok=True) - print('Installing %s to %s' % (fullfilename, outdir)) - do_copyfile(fullfilename, outfilename) - set_mode(outfilename, mode, d.install_umask) - -def install_man(d): - for m in d.man: - full_source_filename = m[0] - outfilename = get_destdir_path(d, m[1]) - outdir = os.path.dirname(outfilename) - d.dirmaker.makedirs(outdir, exist_ok=True) - install_mode = m[2] - print('Installing %s to %s' % (full_source_filename, outdir)) - if outfilename.endswith('.gz') and not full_source_filename.endswith('.gz'): - with open(outfilename, 'wb') as of: - with open(full_source_filename, 'rb') as sf: - # Set mtime and filename for reproducibility. - with gzip.GzipFile(fileobj=of, mode='wb', filename='', mtime=0) as gz: - gz.write(sf.read()) - shutil.copystat(full_source_filename, outfilename) - append_to_log(outfilename) - else: - do_copyfile(full_source_filename, outfilename) - set_mode(outfilename, install_mode, d.install_umask) - -def install_headers(d): - for t in d.headers: - fullfilename = t[0] - fname = os.path.basename(fullfilename) - outdir = get_destdir_path(d, t[1]) - outfilename = os.path.join(outdir, fname) - install_mode = t[2] - print('Installing %s to %s' % (fname, outdir)) - d.dirmaker.makedirs(outdir, exist_ok=True) - do_copyfile(fullfilename, outfilename) - set_mode(outfilename, install_mode, d.install_umask) - -def run_install_script(d): - env = {'MESON_SOURCE_ROOT': d.source_dir, - 'MESON_BUILD_ROOT': d.build_dir, - 'MESON_INSTALL_PREFIX': d.prefix, - 'MESON_INSTALL_DESTDIR_PREFIX': d.fullprefix, - 'MESONINTROSPECT': ' '.join([shlex.quote(x) for x in d.mesonintrospect]), - } - - child_env = os.environ.copy() - child_env.update(env) - - for i in d.install_scripts: - script = i['exe'] - args = i['args'] - name = ' '.join(script + args) - print('Running custom install script {!r}'.format(name)) - try: - rc = subprocess.call(script + args, env=child_env) - if rc != 0: - sys.exit(rc) - except OSError: - print('Failed to run install script {!r}'.format(name)) - sys.exit(1) def check_for_stampfile(fname): '''Some languages e.g. Rust have output files @@ -368,74 +175,263 @@ def check_for_stampfile(fname): return files[0] return fname -def install_targets(d): - for t in d.targets: - fname = check_for_stampfile(t.fname) - outdir = get_destdir_path(d, t.outdir) - outname = os.path.join(outdir, os.path.basename(fname)) - final_path = os.path.join(d.prefix, t.outdir, os.path.basename(fname)) - aliases = t.aliases - should_strip = t.strip - install_name_mappings = t.install_name_mappings - install_rpath = t.install_rpath - install_mode = t.install_mode - print('Installing %s to %s' % (fname, outname)) - d.dirmaker.makedirs(outdir, exist_ok=True) - if not os.path.exists(fname): - raise RuntimeError('File {!r} could not be found'.format(fname)) - elif os.path.isfile(fname): - do_copyfile(fname, outname) - set_mode(outname, install_mode, d.install_umask) - if should_strip and d.strip_bin is not None: - if fname.endswith('.jar'): - print('Not stripping jar target:', os.path.basename(fname)) +class Installer: + + def __init__(self, lf): + self.lf = lf + + def do_copyfile(self, from_file, to_file): + if not os.path.isfile(from_file): + raise RuntimeError('Tried to install something that isn\'t a file:' + '{!r}'.format(from_file)) + # copyfile fails if the target file already exists, so remove it to + # allow overwriting a previous install. If the target is not a file, we + # want to give a readable error. + if os.path.exists(to_file): + if not os.path.isfile(to_file): + raise RuntimeError('Destination {!r} already exists and is not ' + 'a file'.format(to_file)) + os.unlink(to_file) + shutil.copyfile(from_file, to_file) + shutil.copystat(from_file, to_file) + selinux_updates.append(to_file) + append_to_log(self.lf, to_file) + + def do_copydir(self, data, src_dir, dst_dir, exclude, install_mode): + ''' + Copies the contents of directory @src_dir into @dst_dir. + + For directory + /foo/ + bar/ + excluded + foobar + file + do_copydir(..., '/foo', '/dst/dir', {'bar/excluded'}) creates + /dst/ + dir/ + bar/ + foobar + file + + Args: + src_dir: str, absolute path to the source directory + dst_dir: str, absolute path to the destination directory + exclude: (set(str), set(str)), tuple of (exclude_files, exclude_dirs), + each element of the set is a path relative to src_dir. + ''' + if not os.path.isabs(src_dir): + raise ValueError('src_dir must be absolute, got %s' % src_dir) + if not os.path.isabs(dst_dir): + raise ValueError('dst_dir must be absolute, got %s' % dst_dir) + if exclude is not None: + exclude_files, exclude_dirs = exclude + else: + exclude_files = exclude_dirs = set() + for root, dirs, files in os.walk(src_dir): + assert os.path.isabs(root) + for d in dirs[:]: + abs_src = os.path.join(root, d) + filepart = os.path.relpath(abs_src, start=src_dir) + abs_dst = os.path.join(dst_dir, filepart) + # Remove these so they aren't visited by os.walk at all. + if filepart in exclude_dirs: + dirs.remove(d) continue - print('Stripping target {!r}'.format(fname)) - ps, stdo, stde = Popen_safe(d.strip_bin + [outname]) - if ps.returncode != 0: - print('Could not strip file.\n') - print('Stdout:\n%s\n' % stdo) - print('Stderr:\n%s\n' % stde) + if os.path.isdir(abs_dst): + continue + if os.path.exists(abs_dst): + print('Tried to copy directory %s but a file of that name already exists.' % abs_dst) sys.exit(1) - pdb_filename = os.path.splitext(fname)[0] + '.pdb' - if not should_strip and os.path.exists(pdb_filename): - pdb_outname = os.path.splitext(outname)[0] + '.pdb' - print('Installing pdb file %s to %s' % (pdb_filename, pdb_outname)) - do_copyfile(pdb_filename, pdb_outname) - set_mode(pdb_outname, install_mode, d.install_umask) - elif os.path.isdir(fname): - fname = os.path.join(d.build_dir, fname.rstrip('/')) - outname = os.path.join(outdir, os.path.basename(fname)) - do_copydir(d, fname, outname, None, install_mode) - else: - raise RuntimeError('Unknown file type for {!r}'.format(fname)) - printed_symlink_error = False - for alias, to in aliases.items(): + data.dirmaker.makedirs(abs_dst) + shutil.copystat(abs_src, abs_dst) + sanitize_permissions(abs_dst, data.install_umask) + for f in files: + abs_src = os.path.join(root, f) + filepart = os.path.relpath(abs_src, start=src_dir) + if filepart in exclude_files: + continue + abs_dst = os.path.join(dst_dir, filepart) + if os.path.isdir(abs_dst): + print('Tried to copy file %s but a directory of that name already exists.' % abs_dst) + if os.path.exists(abs_dst): + os.unlink(abs_dst) + parent_dir = os.path.dirname(abs_dst) + if not os.path.isdir(parent_dir): + os.mkdir(parent_dir) + shutil.copystat(os.path.dirname(abs_src), parent_dir) + shutil.copy2(abs_src, abs_dst, follow_symlinks=False) + set_mode(abs_dst, install_mode, data.install_umask) + append_to_log(self.lf, abs_dst) + + def do_install(self, datafilename): + with open(datafilename, 'rb') as ifile: + d = pickle.load(ifile) + d.destdir = os.environ.get('DESTDIR', '') + d.fullprefix = destdir_join(d.destdir, d.prefix) + + if d.install_umask is not None: + os.umask(d.install_umask) + + try: + d.dirmaker = DirMaker(self.lf) + with d.dirmaker: + self.install_subdirs(d) # Must be first, because it needs to delete the old subtree. + self.install_targets(d) + self.install_headers(d) + self.install_man(d) + self.install_data(d) + restore_selinux_contexts() + self.run_install_script(d) + except PermissionError: + if shutil.which('pkexec') is not None and 'PKEXEC_UID' not in os.environ: + print('Installation failed due to insufficient permissions.') + print('Attempting to use polkit to gain elevated privileges...') + os.execlp('pkexec', 'pkexec', sys.executable, main_file, *sys.argv[1:], + os.getcwd()) + else: + raise + + def install_subdirs(self, d): + for (src_dir, dst_dir, mode, exclude) in d.install_subdirs: + full_dst_dir = get_destdir_path(d, dst_dir) + print('Installing subdir %s to %s' % (src_dir, full_dst_dir)) + d.dirmaker.makedirs(full_dst_dir, exist_ok=True) + self.do_copydir(d, src_dir, full_dst_dir, exclude, mode) + + def install_data(self, d): + for i in d.data: + fullfilename = i[0] + outfilename = get_destdir_path(d, i[1]) + mode = i[2] + outdir = os.path.dirname(outfilename) + d.dirmaker.makedirs(outdir, exist_ok=True) + print('Installing %s to %s' % (fullfilename, outdir)) + self.do_copyfile(fullfilename, outfilename) + set_mode(outfilename, mode, d.install_umask) + + def install_man(self, d): + for m in d.man: + full_source_filename = m[0] + outfilename = get_destdir_path(d, m[1]) + outdir = os.path.dirname(outfilename) + d.dirmaker.makedirs(outdir, exist_ok=True) + install_mode = m[2] + print('Installing %s to %s' % (full_source_filename, outdir)) + if outfilename.endswith('.gz') and not full_source_filename.endswith('.gz'): + with open(outfilename, 'wb') as of: + with open(full_source_filename, 'rb') as sf: + # Set mtime and filename for reproducibility. + with gzip.GzipFile(fileobj=of, mode='wb', filename='', mtime=0) as gz: + gz.write(sf.read()) + shutil.copystat(full_source_filename, outfilename) + append_to_log(self.lf, outfilename) + else: + self.do_copyfile(full_source_filename, outfilename) + set_mode(outfilename, install_mode, d.install_umask) + + def install_headers(self, d): + for t in d.headers: + fullfilename = t[0] + fname = os.path.basename(fullfilename) + outdir = get_destdir_path(d, t[1]) + outfilename = os.path.join(outdir, fname) + install_mode = t[2] + print('Installing %s to %s' % (fname, outdir)) + d.dirmaker.makedirs(outdir, exist_ok=True) + self.do_copyfile(fullfilename, outfilename) + set_mode(outfilename, install_mode, d.install_umask) + + def run_install_script(self, d): + env = {'MESON_SOURCE_ROOT': d.source_dir, + 'MESON_BUILD_ROOT': d.build_dir, + 'MESON_INSTALL_PREFIX': d.prefix, + 'MESON_INSTALL_DESTDIR_PREFIX': d.fullprefix, + 'MESONINTROSPECT': ' '.join([shlex.quote(x) for x in d.mesonintrospect]), + } + + child_env = os.environ.copy() + child_env.update(env) + + for i in d.install_scripts: + script = i['exe'] + args = i['args'] + name = ' '.join(script + args) + print('Running custom install script {!r}'.format(name)) try: - symlinkfilename = os.path.join(outdir, alias) + rc = subprocess.call(script + args, env=child_env) + if rc != 0: + sys.exit(rc) + except OSError: + print('Failed to run install script {!r}'.format(name)) + sys.exit(1) + + def install_targets(self, d): + for t in d.targets: + fname = check_for_stampfile(t[0]) + outdir = get_destdir_path(d, t[1]) + outname = os.path.join(outdir, os.path.basename(fname)) + final_path = os.path.join(d.prefix, outname) + aliases = t[2] + should_strip = t[3] + install_rpath = t[4] + install_mode = t[5] + print('Installing %s to %s' % (fname, outname)) + d.dirmaker.makedirs(outdir, exist_ok=True) + if not os.path.exists(fname): + raise RuntimeError('File {!r} could not be found'.format(fname)) + elif os.path.isfile(fname): + self.do_copyfile(fname, outname) + set_mode(outname, install_mode, d.install_umask) + if should_strip and d.strip_bin is not None: + if fname.endswith('.jar'): + print('Not stripping jar target:', os.path.basename(fname)) + continue + print('Stripping target {!r}'.format(fname)) + ps, stdo, stde = Popen_safe(d.strip_bin + [outname]) + if ps.returncode != 0: + print('Could not strip file.\n') + print('Stdout:\n%s\n' % stdo) + print('Stderr:\n%s\n' % stde) + sys.exit(1) + pdb_filename = os.path.splitext(fname)[0] + '.pdb' + if not should_strip and os.path.exists(pdb_filename): + pdb_outname = os.path.splitext(outname)[0] + '.pdb' + print('Installing pdb file %s to %s' % (pdb_filename, pdb_outname)) + self.do_copyfile(pdb_filename, pdb_outname) + set_mode(pdb_outname, install_mode, d.install_umask) + elif os.path.isdir(fname): + fname = os.path.join(d.build_dir, fname.rstrip('/')) + outname = os.path.join(outdir, os.path.basename(fname)) + self.do_copydir(d, fname, outname, None, install_mode) + else: + raise RuntimeError('Unknown file type for {!r}'.format(fname)) + printed_symlink_error = False + for alias, to in aliases.items(): try: - os.unlink(symlinkfilename) - except FileNotFoundError: - pass - os.symlink(to, symlinkfilename) - append_to_log(symlinkfilename) - except (NotImplementedError, OSError): - if not printed_symlink_error: - print("Symlink creation does not work on this platform. " - "Skipping all symlinking.") - printed_symlink_error = True - if os.path.isfile(outname): - try: - depfixer.fix_rpath(outname, install_rpath, final_path, - install_name_mappings, verbose=False) - except SystemExit as e: - if isinstance(e.code, int) and e.code == 0: - pass - else: - raise + symlinkfilename = os.path.join(outdir, alias) + try: + os.unlink(symlinkfilename) + except FileNotFoundError: + pass + os.symlink(to, symlinkfilename) + append_to_log(self.lf, symlinkfilename) + except (NotImplementedError, OSError): + if not printed_symlink_error: + print("Symlink creation does not work on this platform. " + "Skipping all symlinking.") + printed_symlink_error = True + if os.path.isfile(outname): + try: + depfixer.fix_rpath(outname, install_rpath, final_path, + verbose=False) + except SystemExit as e: + if isinstance(e.code, int) and e.code == 0: + pass + else: + raise def run(args): - global install_log_file parser = buildparser() opts = parser.parse_args(args) datafilename = 'meson-private/install.dat' @@ -449,11 +445,10 @@ def run(args): sys.exit(-1) os.chdir(opts.wd) with open(os.path.join(log_dir, 'install-log.txt'), 'w') as lf: - install_log_file = lf - append_to_log('# List of files installed by Meson') - append_to_log('# Does not contain files installed by custom scripts.') - do_install(log_dir, datafilename) - install_log_file = None + installer = Installer(lf) + append_to_log(lf, '# List of files installed by Meson') + append_to_log(lf, '# Does not contain files installed by custom scripts.') + installer.do_install(datafilename) return 0 if __name__ == '__main__': -- cgit v1.1