#!/usr/bin/env python3 """Uninstall a previously installed CMake installation. Read the ``install_manifest.txt`` file from a CMake build directory which has been installed then delete the files, and optionally the empty subdirectories of the install prefix, specified within. """ from argparse import ArgumentParser, RawDescriptionHelpFormatter from os import listdir, path, remove, rmdir from sys import stderr def uninstall(build_dir, recursive=False): """Uninstall a previoulsy installed CMake installation. Arguments: :build_dir: Path to the build directory containing the ``install_manifest.txt`` of files to uninstall. :recursive: Boolean flag to enable recursively uninstall empty directories up to the install prefix specified in ``CMakeCache.txt``. """ # Get the list of installed files from the install_manifest.txt install_manifest_path = path.join(build_dir, 'install_manifest.txt') with open(install_manifest_path, 'r') as install_manifest_file: install_manifest = sorted(install_manifest_file.read().splitlines()) # Delete files from the filesystem removed = [] directories = [] for entry in install_manifest: directories.append(path.dirname(entry)) if path.isfile(entry): remove(entry) removed.append(entry) if recursive: # Get the install prefix from CMakeCache.txt cmakecache_path = path.join(build_dir, 'CMakeCache.txt') with open(cmakecache_path, 'r') as cmakecache_file: for line in cmakecache_file.read().splitlines(): if line.startswith('CMAKE_INSTALL_PREFIX'): prefix = path.normpath(line.split('=')[1]) break while True: # Remove duplicates from list directories = list(dict.fromkeys(directories)) # Find directories in list which are not empty not_empty = [] for index, entry in enumerate(directories): if listdir(entry): not_empty.append(index) # Remove directories which are not empty from list for index in reversed(not_empty): del directories[index] # Delete directories from the filesystem to_del = [] to_append = [] for index, entry in enumerate(directories): rmdir(entry) removed.append(entry) to_del.append(index) parent = path.dirname(entry) # Add parent directory to the list when not the install prefix if path.normpath(parent) != prefix: to_append.append(parent) # Remove deleted directories from list for index in reversed(to_del): del directories[index] directories += to_append # Exit loop when no more directories in list if not directories: break return removed def main(): """Command line entry point.""" cli = ArgumentParser(description=__doc__, formatter_class=RawDescriptionHelpFormatter) cli.add_argument('--version', action='version', version='%(prog)s 0.1.0') cli.add_argument('-q', '--quiet', action='store_true', help="don't print removed entries to stdout") cli.add_argument('-r', '--recursive', action='store_true', help='recursively remove empty directories') cli.add_argument('build_dir', help='path to the installed CMake build directory') args = cli.parse_args() try: removed = uninstall(args.build_dir, recursive=args.recursive) if not args.quiet: for entry in removed: print(f'-- Uninstalling: {entry}') except FileNotFoundError as error: print(f'error: file not found: {error.filename}', file=stderr) exit(1) if __name__ == '__main__': try: main() except KeyboardInterrupt: exit(130)