def _find_executables(self, destdir_prefix): executables = [] # filter out files to strip for filename in fileutils.accumulate_dirtree_contents(destdir_prefix): dirname, basename = os.path.split(filename) fullfilename = os.path.join(destdir_prefix, dirname, basename) if not os.path.isfile(fullfilename) or os.path.islink( fullfilename): continue if not os.access(fullfilename, os.X_OK) and 'so' not in basename.split( os.path.extsep): continue if filename.endswith('.debug'): continue fileinfo = '' try: fileinfo = subprocess.check_output( ['file', '-b', fullfilename]).strip() except Exception: pass if not fileinfo.startswith('ELF '): continue executables.append(filename) return executables
def process_install(self, buildscript, revision): assert self.supports_install_destdir destdir = self.get_destdir(buildscript) self._clean_la_files(buildscript, destdir) prefix_without_drive = os.path.splitdrive(buildscript.config.prefix)[1] stripped_prefix = prefix_without_drive[1:] previous_entry = buildscript.moduleset.packagedb.get(self.name) if previous_entry: previous_contents = previous_entry.get_manifest() else: previous_contents = None new_contents = fileutils.accumulate_dirtree_contents(destdir) install_succeeded = False save_broken_tree = False broken_name = destdir + '-broken' destdir_prefix = os.path.join(destdir, stripped_prefix) errors = [] if os.path.isdir(destdir_prefix): destdir_install = True logging.info( _('Moving temporary DESTDIR %r into build prefix') % (destdir, )) num_copied = self._process_install_files(destdir, destdir_prefix, buildscript.config.prefix, errors) # Now the destdir should have a series of empty directories: # $JHBUILD_PREFIX/_jhbuild/root-foo/$JHBUILD_PREFIX # Remove them one by one to clean the tree to the state we expect, # so we can better spot leftovers or broken things. prefix_dirs = filter(lambda x: x != '', stripped_prefix.split(os.sep)) while len(prefix_dirs) > 0: dirname = prefix_dirs.pop() subprefix = os.path.join(*([destdir] + prefix_dirs)) target = os.path.join(subprefix, dirname) assert target.startswith(buildscript.config.prefix) try: os.rmdir(target) except OSError, e: pass remaining_files = os.listdir(destdir) if len(remaining_files) > 0: logging.warn( _("Files remaining in buildroot %(dest)r; module may have installed files outside of prefix." ) % { 'num': len(remaining_files), 'dest': broken_name }) save_broken_tree = True # Even if there are some files outside the DESTDIR, count that as success for now; we just warn install_succeeded = True
def process_install(self, buildscript, revision): assert self.supports_install_destdir destdir = self.get_destdir(buildscript) self._clean_la_files(buildscript, destdir) prefix_without_drive = os.path.splitdrive(buildscript.config.prefix)[1] stripped_prefix = prefix_without_drive[1:] previous_entry = buildscript.moduleset.packagedb.get(self.name) if previous_entry: previous_contents = previous_entry.get_manifest() else: previous_contents = None new_contents = fileutils.accumulate_dirtree_contents(destdir) install_succeeded = False save_broken_tree = False broken_name = destdir + "-broken" destdir_prefix = os.path.join(destdir, stripped_prefix) if os.path.isdir(destdir_prefix): destdir_install = True logging.info(_("Moving temporary DESTDIR %r into build prefix") % (destdir,)) num_copied = self._process_install_files(destdir, destdir_prefix, buildscript.config.prefix) logging.info(_("Install complete: %d files copied") % (num_copied,)) # Now the destdir should have a series of empty directories: # $JHBUILD_PREFIX/_jhbuild/root-foo/$JHBUILD_PREFIX # Remove them one by one to clean the tree to the state we expect, # so we can better spot leftovers or broken things. prefix_dirs = filter(lambda x: x != "", stripped_prefix.split(os.sep)) while len(prefix_dirs) > 0: dirname = prefix_dirs.pop() subprefix = os.path.join(*([destdir] + prefix_dirs)) target = os.path.join(subprefix, dirname) assert target.startswith(buildscript.config.prefix) try: os.rmdir(target) except OSError, e: pass remaining_files = os.listdir(destdir) if len(remaining_files) > 0: logging.warn( _("Files remaining in buildroot %(dest)r; module may have installed files outside of prefix.") % {"num": len(remaining_files), "dest": broken_name} ) save_broken_tree = True # Even if there are some files outside the DESTDIR, count that as success for now; we just warn install_succeeded = True
def _strip_debug_symbols(self, destdir_prefix, installroot): assert self.supports_stripping_debug_symbols for filename in fileutils.accumulate_dirtree_contents(destdir_prefix): dirname, basename = os.path.split(filename) fullfilename = os.path.join(destdir_prefix, dirname, basename) if not os.path.isfile(fullfilename) or os.path.islink(fullfilename): continue if not os.access(fullfilename, os.X_OK) and 'so' not in basename.split(os.path.extsep): continue fileinfo = '' try: fileinfo = subprocess.check_output(['file', '-b', fullfilename]).strip() except Exception: pass if not fileinfo.startswith('ELF ') or not fileinfo.endswith(', not stripped'): continue # make sure file is writable os.chmod(fullfilename, os.stat(fullfilename).st_mode | stat.S_IWUSR) # first create the /opt/debug/dirname fulldebugdirname = os.path.join(destdir_prefix, 'debug', dirname) if not os.path.exists(fulldebugdirname): os.makedirs(fulldebugdirname) fulldebugfilename = os.path.join(fulldebugdirname, basename + '.debug') # strip try: subprocess.check_call(['objcopy', '--only-keep-debug', fullfilename, fulldebugfilename]) subprocess.check_call(['objcopy', '--remove-section', '.gnu_debuglink', fullfilename]) subprocess.check_call(['objcopy', '--add-gnu-debuglink', fulldebugfilename, fullfilename]) subprocess.check_call(['objcopy', '--strip-all', '--discard-all', '--preserve-dates', fullfilename]) except Exception: continue # create a /opt/dirname/basename.debug link to /opt/debug/dirname/basename.debug fulldebuglinkname = os.path.join(destdir_prefix, dirname, basename + '.debug') if os.path.exists(fulldebuglinkname): os.remove(fulldebuglinkname) os.symlink(os.path.join(installroot, 'debug', dirname, basename + '.debug'), fulldebuglinkname)
def _find_executables(self, destdir_prefix): executables = [] # filter out files to strip for filename in fileutils.accumulate_dirtree_contents(destdir_prefix): dirname, basename = os.path.split(filename) fullfilename = os.path.join(destdir_prefix, dirname, basename) if not os.path.isfile(fullfilename) or os.path.islink(fullfilename): continue if not os.access(fullfilename, os.X_OK) and 'so' not in basename.split(os.path.extsep): continue if filename.endswith('.debug'): continue fileinfo = '' try: fileinfo = subprocess.check_output(['file', '-b', fullfilename]).strip() except Exception: pass if not fileinfo.startswith('ELF '): continue executables.append(filename) return executables
def process_install(self, buildscript, revision): assert self.supports_install_destdir destdir = self.get_destdir(buildscript) self._clean_la_files(buildscript, destdir) self._clean_texinfo_dir_files(buildscript, destdir) prefix_without_drive = os.path.splitdrive(buildscript.config.prefix)[1] stripped_prefix = prefix_without_drive[1:] install_succeeded = False save_broken_tree = False broken_name = destdir + '-broken' destdir_prefix = os.path.join(destdir, stripped_prefix) new_contents = fileutils.accumulate_dirtree_contents(destdir_prefix) errors = [] if os.path.isdir(destdir_prefix): logging.info(_('Moving temporary DESTDIR %r into build prefix') % (destdir, )) num_copied = self._process_install_files(destdir, destdir_prefix, buildscript.config.prefix, errors) # Now the destdir should have a series of empty directories: # $JHBUILD_PREFIX/_jhbuild/root-foo/$JHBUILD_PREFIX # Remove them one by one to clean the tree to the state we expect, # so we can better spot leftovers or broken things. prefix_dirs = filterlist(lambda x: x != '', stripped_prefix.split(os.sep)) while len(prefix_dirs) > 0: dirname = prefix_dirs.pop() subprefix = os.path.join(*([destdir] + prefix_dirs)) target = os.path.join(subprefix, dirname) assert target.startswith(buildscript.config.prefix) try: os.rmdir(target) except OSError: pass remaining_files = os.listdir(destdir) if len(remaining_files) > 0: logging.warn(_("Files remaining in buildroot %(dest)r; module may have installed files outside of prefix.") % {'num': len(remaining_files), 'dest': broken_name}) save_broken_tree = True # Even if there are some files outside the DESTDIR, count that as success for now; we just warn install_succeeded = True else: save_broken_tree = True if save_broken_tree: if os.path.exists(broken_name): assert broken_name.startswith(buildscript.config.top_builddir) shutil.rmtree(broken_name) fileutils.rename(destdir, broken_name) else: assert destdir.startswith(buildscript.config.prefix) os.rmdir(destdir) if not install_succeeded: raise CommandError(_("Module failed to install into DESTDIR %(dest)r") % {'dest': broken_name}) else: to_delete = set() previous_entry = buildscript.moduleset.packagedb.get(self.name) if previous_entry: previous_contents = previous_entry.manifest if previous_contents: to_delete.update(fileutils.filter_files_by_prefix(self.config, previous_contents)) for filename in new_contents: to_delete.discard (os.path.join(self.config.prefix, filename)) if to_delete: # paranoid double-check assert to_delete == set(fileutils.filter_files_by_prefix(self.config, to_delete)) logging.info(_('%d files remaining from previous build') % (len(to_delete),)) for (path, was_deleted, error_string) in fileutils.remove_files_and_dirs(to_delete, allow_nonempty_dirs=True): if was_deleted: logging.info(_('Deleted: %(file)r') % { 'file': path, }) elif error_string is None: # We don't warn on not-empty directories pass else: logging.warn(_("Failed to delete no longer installed file %(file)r: %(msg)s") % { 'file': path, 'msg': error_string}) buildscript.moduleset.packagedb.add(self.name, revision or '', new_contents, self.configure_cmd) if errors: raise CommandError(_('Install encountered errors: %(num)d ' 'errors raised, %(files)d files copied. ' 'The errors are:\n %(err)s') % {'num' : len(errors), 'files' : num_copied, 'err' : '\n '.join(errors)}) else: logging.info(_('Install complete: %d files copied') % (num_copied, ))
def process_install(self, buildscript, revision): assert self.supports_install_destdir destdir = self.get_destdir(buildscript) self._clean_la_files(buildscript, destdir) self._clean_texinfo_dir_files(buildscript, destdir) prefix_without_drive = os.path.splitdrive(buildscript.config.prefix)[1] stripped_prefix = prefix_without_drive[1:] install_succeeded = False save_broken_tree = False broken_name = destdir + '-broken' destdir_prefix = os.path.join(destdir, stripped_prefix) # simon: dump sysdeps logging.info(_('Finding system dependencies ...')) systemdependencies, notfounds, rdependencies = self._find_executable_system_dependencies(destdir_prefix, buildscript.config.prefix) if notfounds: broken = set() missing = set() for lib, notfound in notfounds.iteritems(): broken.add(lib) missing.update(notfound) # collect broken binaries brokenqueue = set(broken) while brokenqueue: rdepend = rdependencies[brokenqueue.pop()] brokenqueue.update(set(rdepend).difference(broken)) broken.update(rdepend) for filename in broken: # os.unlink(os.path.join(destdir_prefix, filename)) logging.warn(_('Broken binary: %s') % filename) logging.warn(_('Missing system dependencies: %s') % ', '.join(sorted(missing))) # simon: strip debug info before install if self.supports_stripping_debug_symbols: logging.info(_('Stripping debug symbols ...')) self._strip_debug_symbols(destdir_prefix, buildscript.config.prefix) new_contents = fileutils.accumulate_dirtree_contents(destdir_prefix) errors = [] if os.path.isdir(destdir_prefix): destdir_install = True logging.info(_('Moving temporary DESTDIR %r into build prefix') % (destdir, )) num_copied = self._process_install_files(destdir, destdir_prefix, buildscript.config.prefix, errors) # Now the destdir should have a series of empty directories: # $JHBUILD_PREFIX/_jhbuild/root-foo/$JHBUILD_PREFIX # Remove them one by one to clean the tree to the state we expect, # so we can better spot leftovers or broken things. prefix_dirs = filter(lambda x: x != '', stripped_prefix.split(os.sep)) while len(prefix_dirs) > 0: dirname = prefix_dirs.pop() subprefix = os.path.join(*([destdir] + prefix_dirs)) target = os.path.join(subprefix, dirname) # liuhuan: need to disable this assertion to allow custom top_builddir #assert target.startswith(buildscript.config.prefix) try: os.rmdir(target) except OSError as e: pass remaining_files = os.listdir(destdir) if len(remaining_files) > 0: logging.warn(_("Files remaining in buildroot %(dest)r; module may have installed files outside of prefix.") % {'num': len(remaining_files), 'dest': broken_name}) save_broken_tree = True # Even if there are some files outside the DESTDIR, count that as success for now; we just warn install_succeeded = True else: save_broken_tree = True if save_broken_tree: if os.path.exists(broken_name): assert broken_name.startswith(buildscript.config.top_builddir) shutil.rmtree(broken_name) fileutils.rename(destdir, broken_name) else: # liuhuan: need to disable this assertion to allow custom top_builddir #assert destdir.startswith(buildscript.config.prefix) os.rmdir(destdir) if not install_succeeded: raise CommandError(_("Module failed to install into DESTDIR %(dest)r") % {'dest': broken_name}) else: to_delete = set() previous_entry = buildscript.moduleset.packagedb.get(self.name) if previous_entry: previous_contents = previous_entry.get_manifest() if previous_contents: to_delete.update(fileutils.filter_files_by_prefix(self.config, previous_contents)) for filename in new_contents: to_delete.discard (os.path.join(self.config.prefix, filename)) if to_delete: # paranoid double-check assert to_delete == set(fileutils.filter_files_by_prefix(self.config, to_delete)) logging.info(_('%d files remaining from previous build') % (len(to_delete),)) for (path, was_deleted, error_string) in fileutils.remove_files_and_dirs(to_delete, self.config, allow_nonempty_dirs=True): if was_deleted: logging.info(_('Deleted: %(file)r') % { 'file': path, }) elif error_string is None: # We don't warn on not-empty directories pass else: logging.warn(_("Failed to delete no longer installed file %(file)r: %(msg)s") % { 'file': path, 'msg': error_string}) buildscript.moduleset.packagedb.add(self.name, revision or '', new_contents, self.configure_cmd, systemdependencies) if errors: raise CommandError(_('Install encountered errors: %(num)d ' 'errors raised, %(files)d files copied. ' 'The errors are:\n %(err)s') % {'num' : len(errors), 'files' : num_copied, 'err' : '\n '.join(errors)}) else: logging.info(_('Install complete: %d files copied') % (num_copied, ))
def process_install(self, buildscript, revision): assert self.supports_install_destdir destdir = self.get_destdir(buildscript) self._clean_la_files(buildscript, destdir) self._clean_texinfo_dir_files(buildscript, destdir) prefix_without_drive = os.path.splitdrive(buildscript.config.prefix)[1] stripped_prefix = prefix_without_drive[1:] install_succeeded = False save_broken_tree = False broken_name = destdir + '-broken' destdir_prefix = os.path.join(destdir, stripped_prefix) # simon: dump sysdeps logging.info(_('Finding system dependencies ...')) systemdependencies, notfounds, rdependencies = self._find_executable_system_dependencies( destdir_prefix, buildscript.config.prefix) if notfounds: broken = set() missing = set() for lib, notfound in notfounds.iteritems(): broken.add(lib) missing.update(notfound) # collect broken binaries brokenqueue = set(broken) while brokenqueue: rdepend = rdependencies[brokenqueue.pop()] brokenqueue.update(set(rdepend).difference(broken)) broken.update(rdepend) for filename in broken: # os.unlink(os.path.join(destdir_prefix, filename)) logging.warn(_('Broken binary: %s') % filename) logging.warn( _('Missing system dependencies: %s') % ', '.join(sorted(missing))) # simon: strip debug info before install if self.supports_stripping_debug_symbols: logging.info(_('Stripping debug symbols ...')) self._strip_debug_symbols(destdir_prefix, buildscript.config.prefix) new_contents = fileutils.accumulate_dirtree_contents(destdir_prefix) errors = [] if os.path.isdir(destdir_prefix): destdir_install = True logging.info( _('Moving temporary DESTDIR %r into build prefix') % (destdir, )) num_copied = self._process_install_files(destdir, destdir_prefix, buildscript.config.prefix, errors) # Now the destdir should have a series of empty directories: # $JHBUILD_PREFIX/_jhbuild/root-foo/$JHBUILD_PREFIX # Remove them one by one to clean the tree to the state we expect, # so we can better spot leftovers or broken things. prefix_dirs = filter(lambda x: x != '', stripped_prefix.split(os.sep)) while len(prefix_dirs) > 0: dirname = prefix_dirs.pop() subprefix = os.path.join(*([destdir] + prefix_dirs)) target = os.path.join(subprefix, dirname) # liuhuan: need to disable this assertion to allow custom top_builddir #assert target.startswith(buildscript.config.prefix) try: os.rmdir(target) except OSError as e: pass remaining_files = os.listdir(destdir) if len(remaining_files) > 0: logging.warn( _("Files remaining in buildroot %(dest)r; module may have installed files outside of prefix." ) % { 'num': len(remaining_files), 'dest': broken_name }) save_broken_tree = True # Even if there are some files outside the DESTDIR, count that as success for now; we just warn install_succeeded = True else: save_broken_tree = True if save_broken_tree: if os.path.exists(broken_name): assert broken_name.startswith(buildscript.config.top_builddir) shutil.rmtree(broken_name) fileutils.rename(destdir, broken_name) else: # liuhuan: need to disable this assertion to allow custom top_builddir #assert destdir.startswith(buildscript.config.prefix) os.rmdir(destdir) if not install_succeeded: raise CommandError( _("Module failed to install into DESTDIR %(dest)r") % {'dest': broken_name}) else: to_delete = set() previous_entry = buildscript.moduleset.packagedb.get(self.name) if previous_entry: previous_contents = previous_entry.get_manifest() if previous_contents: to_delete.update( fileutils.filter_files_by_prefix( self.config, previous_contents)) for filename in new_contents: to_delete.discard(os.path.join(self.config.prefix, filename)) if to_delete: # paranoid double-check assert to_delete == set( fileutils.filter_files_by_prefix(self.config, to_delete)) logging.info( _('%d files remaining from previous build') % (len(to_delete), )) for (path, was_deleted, error_string) in fileutils.remove_files_and_dirs( to_delete, self.config, allow_nonempty_dirs=True): if was_deleted: logging.info( _('Deleted: %(file)r') % { 'file': path, }) elif error_string is None: # We don't warn on not-empty directories pass else: logging.warn( _("Failed to delete no longer installed file %(file)r: %(msg)s" ) % { 'file': path, 'msg': error_string }) # determine branch branch = None if self.branch is not None and hasattr( self.branch, 'repomodule') and self.branch.repomodule is not None: branch = {self.branch.repomodule: revision} logging.info(_('Installed branch: %s') % json.dumps(branch)) buildscript.moduleset.packagedb.add(self.name, revision or '', new_contents, self.configure_cmd, systemdependencies, branch, self.module_hash) if errors: raise CommandError( _('Install encountered errors: %(num)d ' 'errors raised, %(files)d files copied. ' 'The errors are:\n %(err)s') % { 'num': len(errors), 'files': num_copied, 'err': '\n '.join(errors) }) else: logging.info( _('Install complete: %d files copied') % (num_copied, ))
def _strip_debug_symbols(self, destdir_prefix, installroot): assert self.supports_stripping_debug_symbols for filename in fileutils.accumulate_dirtree_contents(destdir_prefix): dirname, basename = os.path.split(filename) fullfilename = os.path.join(destdir_prefix, dirname, basename) if not os.path.isfile(fullfilename) or os.path.islink( fullfilename): continue if not os.access(fullfilename, os.X_OK) and 'so' not in basename.split( os.path.extsep): continue fileinfo = '' try: fileinfo = subprocess.check_output( ['file', '-b', fullfilename]).strip() except Exception: pass if not fileinfo.startswith('ELF ') or not fileinfo.endswith( ', not stripped'): continue # make sure file is writable os.chmod(fullfilename, os.stat(fullfilename).st_mode | stat.S_IWUSR) # first create the /opt/debug/dirname fulldebugdirname = os.path.join(destdir_prefix, 'debug', dirname) if not os.path.exists(fulldebugdirname): os.makedirs(fulldebugdirname) fulldebugfilename = os.path.join(fulldebugdirname, basename + '.debug') # strip try: subprocess.check_call([ 'objcopy', '--only-keep-debug', fullfilename, fulldebugfilename ]) subprocess.check_call([ 'objcopy', '--remove-section', '.gnu_debuglink', fullfilename ]) subprocess.check_call([ 'objcopy', '--add-gnu-debuglink', fulldebugfilename, fullfilename ]) subprocess.check_call([ 'objcopy', '--strip-all', '--discard-all', '--preserve-dates', fullfilename ]) except Exception: continue # create a /opt/dirname/basename.debug link to /opt/debug/dirname/basename.debug fulldebuglinkname = os.path.join(destdir_prefix, dirname, basename + '.debug') if os.path.exists(fulldebuglinkname): os.remove(fulldebuglinkname) os.symlink( os.path.join(installroot, 'debug', dirname, basename + '.debug'), fulldebuglinkname)
def process_install(self, buildscript, revision): assert self.supports_install_destdir destdir = self.get_destdir(buildscript) self._clean_la_files(buildscript, destdir) self._clean_texinfo_dir_files(buildscript, destdir) prefix_without_drive = os.path.splitdrive(buildscript.config.prefix)[1] stripped_prefix = prefix_without_drive[1:] install_succeeded = False save_broken_tree = False broken_name = destdir + "-broken" destdir_prefix = os.path.join(destdir, stripped_prefix) new_contents = fileutils.accumulate_dirtree_contents(destdir_prefix) errors = [] if os.path.isdir(destdir_prefix): destdir_install = True logging.info(_("Moving temporary DESTDIR %r into build prefix") % (destdir,)) num_copied = self._process_install_files(destdir, destdir_prefix, buildscript.config.prefix, errors) # Now the destdir should have a series of empty directories: # $JHBUILD_PREFIX/_jhbuild/root-foo/$JHBUILD_PREFIX # Remove them one by one to clean the tree to the state we expect, # so we can better spot leftovers or broken things. prefix_dirs = filter(lambda x: x != "", stripped_prefix.split(os.sep)) while len(prefix_dirs) > 0: dirname = prefix_dirs.pop() subprefix = os.path.join(*([destdir] + prefix_dirs)) target = os.path.join(subprefix, dirname) assert target.startswith(buildscript.config.prefix) try: os.rmdir(target) except OSError as e: pass remaining_files = os.listdir(destdir) if len(remaining_files) > 0: logging.warn( _("Files remaining in buildroot %(dest)r; module may have installed files outside of prefix.") % {"num": len(remaining_files), "dest": broken_name} ) save_broken_tree = True # Even if there are some files outside the DESTDIR, count that as success for now; we just warn install_succeeded = True else: save_broken_tree = True if save_broken_tree: if os.path.exists(broken_name): assert broken_name.startswith(buildscript.config.top_builddir) shutil.rmtree(broken_name) fileutils.rename(destdir, broken_name) else: assert destdir.startswith(buildscript.config.prefix) os.rmdir(destdir) if not install_succeeded: raise CommandError(_("Module failed to install into DESTDIR %(dest)r") % {"dest": broken_name}) else: to_delete = set() previous_entry = buildscript.moduleset.packagedb.get(self.name) if previous_entry: previous_contents = previous_entry.get_manifest() if previous_contents: to_delete.update(fileutils.filter_files_by_prefix(self.config, previous_contents)) for filename in new_contents: to_delete.discard(os.path.join(self.config.prefix, filename)) if to_delete: # paranoid double-check assert to_delete == set(fileutils.filter_files_by_prefix(self.config, to_delete)) logging.info(_("%d files remaining from previous build") % (len(to_delete),)) for (path, was_deleted, error_string) in fileutils.remove_files_and_dirs( to_delete, allow_nonempty_dirs=True ): if was_deleted: logging.info(_("Deleted: %(file)r") % {"file": path}) elif error_string is None: # We don't warn on not-empty directories pass else: logging.warn( _("Failed to delete no longer installed file %(file)r: %(msg)s") % {"file": path, "msg": error_string} ) buildscript.moduleset.packagedb.add(self.name, revision or "", new_contents, self.configure_cmd) if errors: raise CommandError( _( "Install encountered errors: %(num)d " "errors raised, %(files)d files copied. " "The errors are:\n %(err)s" ) % {"num": len(errors), "files": num_copied, "err": "\n ".join(errors)} ) else: logging.info(_("Install complete: %d files copied") % (num_copied,))