| # Copyright 2017 The Chromium Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Recursively create hardlinks to targets at output.""" |
| |
| |
| import argparse |
| import os |
| import shutil |
| import sys |
| |
| |
| def CreateHardlinkHelper(target, output): |
| """ |
| Creates hardlink to `target` at `output`. |
| |
| If `target` is a directory, the directory structure will be copied and |
| each file will be hardlinked independently. If `target` is a symlink, |
| a new symlink will be created. |
| |
| The parent directory of `output` must exists or the function will fail. |
| """ |
| if os.path.islink(target): |
| os.symlink(os.readlink(target), output) |
| elif os.path.isfile(target): |
| try: |
| os.link(target, output) |
| except: |
| shutil.copy(target, output) |
| else: |
| os.mkdir(output) |
| for name in os.listdir(target): |
| CreateHardlinkHelper( |
| os.path.join(target, name), |
| os.path.join(output, name)) |
| |
| |
| def CreateHardlink(target, output): |
| """ |
| Creates hardlink to `target` at `output`. |
| |
| If `target` is a directory, the directory structure will be copied and |
| each file will be hardlinked independently. If `target` is a symlink, |
| a new symlink will be created. |
| |
| If `output` already exists, it is first deleted. The parent directory |
| of `output` is created if it does not exists. |
| """ |
| if os.path.exists(output): |
| if os.path.isdir(output): |
| shutil.rmtree(output) |
| else: |
| os.unlink(output) |
| dirname = os.path.dirname(output) |
| if not os.path.isdir(dirname): |
| os.makedirs(dirname) |
| CreateHardlinkHelper(target, output) |
| |
| |
| def CreateHardlinks(output_dir, relative_to, targets): |
| """ |
| Creates hardlinks to `targets` in `output_dir`. |
| |
| The `targets` should starts with `relative_to` and the hardlink will |
| be created at `{output_dir}/{os.path.relpath(sources, relative_to)}`. |
| |
| Fails with an error if any file in `targets` not located inside the |
| `relative_to` directory or if creating any of the hardlinks fails. |
| """ |
| for target in targets: |
| if not target.startswith(relative_to): |
| print(f'error: "{target}" not relative to "{relative_to}', |
| file=sys.stderr) |
| sys.exit(1) |
| |
| for target in targets: |
| output = os.path.join(output_dir, os.path.relpath(target, relative_to)) |
| CreateHardlink(target, output) |
| |
| |
| def main(args): |
| parser = argparse.ArgumentParser() |
| |
| parser.add_argument('--output-dir', |
| required=True, |
| help='directory where the hardlinks should be created') |
| |
| parser.add_argument('--relative-to', |
| required=True, |
| help='sources file will be rebased to this directory') |
| |
| parser.add_argument( |
| 'sources', |
| nargs='+', |
| help='files that should be hardlinked, must be below RELATIVE_TO') |
| |
| parsed = parser.parse_args(args) |
| CreateHardlinks(os.path.normpath(parsed.output_dir), |
| os.path.normpath(parsed.relative_to) + os.sep, |
| [os.path.normpath(source) for source in parsed.sources]) |
| |
| |
| if __name__ == '__main__': |
| main(sys.argv[1:]) |