1# Copyright 2017 The Chromium Authors 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5"""Recursively create hardlinks to targets at output.""" 6 7 8import argparse 9import os 10import shutil 11import sys 12 13 14def CreateHardlinkHelper(target, output): 15 """ 16 Creates hardlink to `target` at `output`. 17 18 If `target` is a directory, the directory structure will be copied and 19 each file will be hardlinked independently. If `target` is a symlink, 20 a new symlink will be created. 21 22 The parent directory of `output` must exists or the function will fail. 23 """ 24 if os.path.islink(target): 25 os.symlink(os.readlink(target), output) 26 elif os.path.isfile(target): 27 try: 28 os.link(target, output) 29 except: 30 shutil.copy(target, output) 31 else: 32 os.mkdir(output) 33 for name in os.listdir(target): 34 CreateHardlinkHelper( 35 os.path.join(target, name), 36 os.path.join(output, name)) 37 38 39def CreateHardlink(target, output): 40 """ 41 Creates hardlink to `target` at `output`. 42 43 If `target` is a directory, the directory structure will be copied and 44 each file will be hardlinked independently. If `target` is a symlink, 45 a new symlink will be created. 46 47 If `output` already exists, it is first deleted. The parent directory 48 of `output` is created if it does not exists. 49 """ 50 if os.path.exists(output): 51 if os.path.isdir(output): 52 shutil.rmtree(output) 53 else: 54 os.unlink(output) 55 dirname = os.path.dirname(output) 56 if not os.path.isdir(dirname): 57 os.makedirs(dirname) 58 CreateHardlinkHelper(target, output) 59 60 61def CreateHardlinks(output_dir, relative_to, targets): 62 """ 63 Creates hardlinks to `targets` in `output_dir`. 64 65 The `targets` should starts with `relative_to` and the hardlink will 66 be created at `{output_dir}/{os.path.relpath(sources, relative_to)}`. 67 68 Fails with an error if any file in `targets` not located inside the 69 `relative_to` directory or if creating any of the hardlinks fails. 70 """ 71 for target in targets: 72 if not target.startswith(relative_to): 73 print(f'error: "{target}" not relative to "{relative_to}', 74 file=sys.stderr) 75 sys.exit(1) 76 77 for target in targets: 78 output = os.path.join(output_dir, os.path.relpath(target, relative_to)) 79 CreateHardlink(target, output) 80 81 82def main(args): 83 parser = argparse.ArgumentParser() 84 85 parser.add_argument('--output-dir', 86 required=True, 87 help='directory where the hardlinks should be created') 88 89 parser.add_argument('--relative-to', 90 required=True, 91 help='sources file will be rebased to this directory') 92 93 parser.add_argument( 94 'sources', 95 nargs='+', 96 help='files that should be hardlinked, must be below RELATIVE_TO') 97 98 parsed = parser.parse_args(args) 99 CreateHardlinks(os.path.normpath(parsed.output_dir), 100 os.path.normpath(parsed.relative_to) + os.sep, 101 [os.path.normpath(source) for source in parsed.sources]) 102 103 104if __name__ == '__main__': 105 main(sys.argv[1:]) 106