|  | #!/bin/python3 | 
|  |  | 
|  | # This file is licensed under the Apache License v2.0 with LLVM Exceptions. | 
|  | # See https://llvm.org/LICENSE.txt for license information. | 
|  | # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | 
|  | """Overlays two directories into a target directory using symlinks. | 
|  |  | 
|  | Tries to minimize the number of symlinks created (that is, does not symlink | 
|  | every single file). Symlinks every file in the overlay directory. Only symlinks | 
|  | individual files in the source directory if their parent directory is also | 
|  | contained in the overlay directory tree. | 
|  | """ | 
|  |  | 
|  | import argparse | 
|  | import errno | 
|  | import os | 
|  | import sys | 
|  |  | 
|  |  | 
|  | def _check_python_version(): | 
|  | if sys.version_info[0] < 3: | 
|  | raise RuntimeError( | 
|  | "Must be invoked with a python 3 interpreter but was %s" % sys.executable | 
|  | ) | 
|  |  | 
|  |  | 
|  | def _check_dir_exists(path): | 
|  | if not os.path.isdir(path): | 
|  | raise OSError(errno.ENOENT, os.strerror(errno.ENOENT), path) | 
|  |  | 
|  |  | 
|  | def parse_arguments(): | 
|  | parser = argparse.ArgumentParser( | 
|  | description=""" | 
|  | Overlays two directories into a target directory using symlinks. | 
|  |  | 
|  | Tries to minimize the number of symlinks created (that is, does not symlink | 
|  | every single file). Symlinks every file in the overlay directory. Only | 
|  | symlinks individual files in the source directory if their parent directory | 
|  | is also contained in the overlay directory tree. | 
|  | """ | 
|  | ) | 
|  | parser.add_argument( | 
|  | "--src", | 
|  | required=True, | 
|  | help="Directory that contains most of the content to symlink.", | 
|  | ) | 
|  | parser.add_argument( | 
|  | "--overlay", | 
|  | required=True, | 
|  | help="Directory to overlay on top of the source directory.", | 
|  | ) | 
|  | parser.add_argument( | 
|  | "--target", | 
|  | required=True, | 
|  | help="Directory in which to place the fused symlink directories.", | 
|  | ) | 
|  |  | 
|  | args = parser.parse_args() | 
|  |  | 
|  | _check_dir_exists(args.target) | 
|  | _check_dir_exists(args.overlay) | 
|  | _check_dir_exists(args.src) | 
|  |  | 
|  | return args | 
|  |  | 
|  |  | 
|  | def _symlink_abs(from_path, to_path): | 
|  | os.symlink(os.path.abspath(from_path), os.path.abspath(to_path)) | 
|  |  | 
|  |  | 
|  | def main(args): | 
|  | for root, dirs, files in os.walk(args.overlay): | 
|  | # We could do something more intelligent here and only symlink individual | 
|  | # files if the directory is present in both overlay and src. This could also | 
|  | # be generalized to an arbitrary number of directories without any | 
|  | # "src/overlay" distinction. In the current use case we only have two and | 
|  | # the overlay directory is always small, so putting that off for now. | 
|  | rel_root = os.path.relpath(root, start=args.overlay) | 
|  | if rel_root != ".": | 
|  | os.mkdir(os.path.join(args.target, rel_root)) | 
|  |  | 
|  | for file in files: | 
|  | relpath = os.path.join(rel_root, file) | 
|  | _symlink_abs( | 
|  | os.path.join(args.overlay, relpath), os.path.join(args.target, relpath) | 
|  | ) | 
|  |  | 
|  | for src_entry in os.listdir(os.path.join(args.src, rel_root)): | 
|  | if src_entry not in dirs: | 
|  | relpath = os.path.join(rel_root, src_entry) | 
|  | _symlink_abs( | 
|  | os.path.join(args.src, relpath), os.path.join(args.target, relpath) | 
|  | ) | 
|  |  | 
|  |  | 
|  | if __name__ == "__main__": | 
|  | _check_python_version() | 
|  | main(parse_arguments()) |