| #!/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()) |