2 # Copyright (c) 2013 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
7 Make a symlink and optionally touch a file (to handle dependencies).
9 usage = "%prog [options] source[ source ...] linkname"
11 A sym link to source is created at linkname. If multiple sources are specfied,
12 then linkname is assumed to be a directory, and will contain all the links to
13 the sources (basenames identical to their source).
15 On Windows, this will use hard links (mklink /H) to avoid requiring elevation.
16 This means that if the original is deleted and replaced, the link will still
17 have the old contents. This is not expected to interfere with the Chromium
30 parser = optparse.OptionParser(usage=usage, description=description,
32 parser.add_option('-f', '--force', action='store_true')
33 parser.add_option('--touch')
35 options, args = parser.parse_args(argv[1:])
37 parser.error('at least two arguments required.')
42 t = os.path.join(target, os.path.basename(s))
43 if len(sources) == 1 and not os.path.isdir(target):
45 t = os.path.expanduser(t)
46 if os.path.realpath(t) == os.path.realpath(s):
49 # N.B. Python 2.x does not have os.symlink for Windows.
50 # Python 3 has os.symlink for Windows, but requires either the admin-
51 # granted privilege SeCreateSymbolicLinkPrivilege or, as of Windows 10
52 # 1703, that Developer Mode be enabled. Hard links and junctions do not
53 # require any extra privileges to create.
55 # mklink does not tolerate /-delimited path names.
56 t = t.replace('/', '\\')
57 s = s.replace('/', '\\')
58 # N.B. This tool only handles file hardlinks, not directory junctions.
59 subprocess.check_output(['cmd.exe', '/c', 'mklink', '/H', t, s],
60 stderr=subprocess.STDOUT)
64 if e.errno == errno.EEXIST and options.force:
66 shutil.rmtree(t, ignore_errors=True)
72 except subprocess.CalledProcessError as e:
73 # Since subprocess.check_output does not return an easily checked error
74 # number, in the 'force' case always assume it is 'file already exists'
78 shutil.rmtree(t, ignore_errors=True)
81 subprocess.check_output(e.cmd, stderr=subprocess.STDOUT)
87 with open(options.touch, 'w') as f:
91 if __name__ == '__main__':
92 sys.exit(Main(sys.argv))