blob: 1efb98d055ac50d425b547b65f004f113fa627b9 [file] [log] [blame]
John Stiles9944fb42022-03-16 10:42:53 -04001#!/usr/bin/python3
commit-bot@chromium.org71e800a2014-04-16 19:21:00 +00002# Copyright 2014 Google Inc.
3#
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7
8"""Parse a DEPS file and git checkout all of the dependencies.
9
10Args:
11 An optional list of deps_os values.
12
13Environment Variables:
Ben Wagner96aa5352018-06-19 10:34:32 -040014 GIT_EXECUTABLE: path to "git" binary; if unset, will look for git in
15 your default path.
halcanary20fb7c62014-06-25 13:28:29 -070016
commit-bot@chromium.org71e800a2014-04-16 19:21:00 +000017 GIT_SYNC_DEPS_PATH: file to get the dependency list from; if unset,
18 will use the file ../DEPS relative to this script's directory.
19
20 GIT_SYNC_DEPS_QUIET: if set to non-empty string, suppress messages.
21
22Git Config:
23 To disable syncing of a single repository:
24 cd path/to/repository
25 git config sync-deps.disable true
26
27 To re-enable sync:
28 cd path/to/repository
29 git config --unset sync-deps.disable
30"""
31
32
33import os
34import subprocess
35import sys
36import threading
37
halcanary20fb7c62014-06-25 13:28:29 -070038
39def git_executable():
40 """Find the git executable.
41
42 Returns:
43 A string suitable for passing to subprocess functions, or None.
44 """
45 envgit = os.environ.get('GIT_EXECUTABLE')
Dominik Röttsches01339782022-01-20 13:16:26 +020046 searchlist = ['git', 'git.bat']
halcanary20fb7c62014-06-25 13:28:29 -070047 if envgit:
48 searchlist.insert(0, envgit)
49 with open(os.devnull, 'w') as devnull:
50 for git in searchlist:
51 try:
52 subprocess.call([git, '--version'], stdout=devnull)
53 except (OSError,):
54 continue
55 return git
56 return None
commit-bot@chromium.org71e800a2014-04-16 19:21:00 +000057
58
59DEFAULT_DEPS_PATH = os.path.normpath(
60 os.path.join(os.path.dirname(__file__), os.pardir, 'DEPS'))
61
62
63def usage(deps_file_path = None):
64 sys.stderr.write(
65 'Usage: run to grab dependencies, with optional platform support:\n')
Hal Canarya6f6f772017-01-27 11:57:13 -050066 sys.stderr.write(' %s %s' % (sys.executable, __file__))
commit-bot@chromium.org71e800a2014-04-16 19:21:00 +000067 if deps_file_path:
Hal Canarya6f6f772017-01-27 11:57:13 -050068 parsed_deps = parse_file_to_dict(deps_file_path)
69 if 'deps_os' in parsed_deps:
70 for deps_os in parsed_deps['deps_os']:
71 sys.stderr.write(' [%s]' % deps_os)
commit-bot@chromium.org71e800a2014-04-16 19:21:00 +000072 sys.stderr.write('\n\n')
73 sys.stderr.write(__doc__)
74
75
76def git_repository_sync_is_disabled(git, directory):
77 try:
78 disable = subprocess.check_output(
79 [git, 'config', 'sync-deps.disable'], cwd=directory)
80 return disable.lower().strip() in ['true', '1', 'yes', 'on']
81 except subprocess.CalledProcessError:
82 return False
83
84
commit-bot@chromium.orgaae6e6f2014-04-21 19:03:05 +000085def is_git_toplevel(git, directory):
86 """Return true iff the directory is the top level of a Git repository.
87
88 Args:
89 git (string) the git executable
90
91 directory (string) the path into which the repository
92 is expected to be checked out.
93 """
94 try:
95 toplevel = subprocess.check_output(
96 [git, 'rev-parse', '--show-toplevel'], cwd=directory).strip()
Ben Wagner32871e692022-07-06 13:52:07 -040097 return (os.path.normcase(os.path.realpath(directory)) ==
98 os.path.normcase(os.path.realpath(toplevel.decode())))
commit-bot@chromium.orgaae6e6f2014-04-21 19:03:05 +000099 except subprocess.CalledProcessError:
100 return False
101
102
Hal Canary5e5f9dc2019-11-01 10:34:32 -0400103def status(directory, commithash, change):
Kevin Lubick0566f5d2021-11-15 07:46:32 -0500104 def truncate_beginning(s, length):
105 return s if len(s) <= length else '...' + s[-(length-3):]
106 def truncate_end(s, length):
Hal Canarya6f6f772017-01-27 11:57:13 -0500107 return s if len(s) <= length else s[:(length - 3)] + '...'
Kevin Lubick0566f5d2021-11-15 07:46:32 -0500108
Hal Canarya6f6f772017-01-27 11:57:13 -0500109 dlen = 36
Kevin Lubick0566f5d2021-11-15 07:46:32 -0500110 directory = truncate_beginning(directory, dlen)
111 commithash = truncate_end(commithash, 40)
Hal Canary5e5f9dc2019-11-01 10:34:32 -0400112 symbol = '>' if change else '@'
113 sys.stdout.write('%-*s %s %s\n' % (dlen, directory, symbol, commithash))
Hal Canarya6f6f772017-01-27 11:57:13 -0500114
115
Ben Wagnerb1ffef62023-11-16 13:44:03 -0500116def git_checkout_to_directory(git, repo, commithash, directory, shallow, verbose):
commit-bot@chromium.org71e800a2014-04-16 19:21:00 +0000117 """Checkout (and clone if needed) a Git repository.
118
119 Args:
120 git (string) the git executable
121
122 repo (string) the location of the repository, suitable
123 for passing to `git clone`.
124
Hal Canary5e5f9dc2019-11-01 10:34:32 -0400125 commithash (string) a commit, suitable for passing to `git checkout`
commit-bot@chromium.org71e800a2014-04-16 19:21:00 +0000126
127 directory (string) the path into which the repository
128 should be checked out.
129
130 verbose (boolean)
131
132 Raises an exception if any calls to git fail.
133 """
134 if not os.path.isdir(directory):
135 subprocess.check_call(
Ben Wagnerb1ffef62023-11-16 13:44:03 -0500136 [git, 'clone', '--quiet', *(['--depth=1'] if shallow else []),
137 '--no-checkout', repo, directory])
commit-bot@chromium.org71e800a2014-04-16 19:21:00 +0000138
commit-bot@chromium.orgaae6e6f2014-04-21 19:03:05 +0000139 if not is_git_toplevel(git, directory):
140 # if the directory exists, but isn't a git repo, you will modify
Kevin Lubickc13cd642022-02-14 15:08:12 -0500141 # the parent repository, which isn't what you want.
commit-bot@chromium.orgaae6e6f2014-04-21 19:03:05 +0000142 sys.stdout.write('%s\n IS NOT TOP-LEVEL GIT DIRECTORY.\n' % directory)
143 return
144
commit-bot@chromium.org71e800a2014-04-16 19:21:00 +0000145 # Check to see if this repo is disabled. Quick return.
146 if git_repository_sync_is_disabled(git, directory):
147 sys.stdout.write('%s\n SYNC IS DISABLED.\n' % directory)
148 return
149
Hal Canary73eae982017-02-13 13:40:56 -0500150 with open(os.devnull, 'w') as devnull:
151 # If this fails, we will fetch before trying again. Don't spam user
152 # with error infomation.
Hal Canary5e5f9dc2019-11-01 10:34:32 -0400153 if 0 == subprocess.call([git, 'checkout', '--quiet', commithash],
Hal Canary73eae982017-02-13 13:40:56 -0500154 cwd=directory, stderr=devnull):
155 # if this succeeds, skip slow `git fetch`.
156 if verbose:
Hal Canary5e5f9dc2019-11-01 10:34:32 -0400157 status(directory, commithash, False) # Success.
Hal Canary73eae982017-02-13 13:40:56 -0500158 return
halcanary6aff54c2015-11-03 09:50:03 -0800159
Hal Canarya6f6f772017-01-27 11:57:13 -0500160 # If the repo has changed, always force use of the correct repo.
161 # If origin already points to repo, this is a quick no-op.
162 subprocess.check_call(
163 [git, 'remote', 'set-url', 'origin', repo], cwd=directory)
164
Ben Wagnerb1ffef62023-11-16 13:44:03 -0500165 subprocess.check_call(
166 [git, 'fetch', '--quiet',
167 *(['--depth=1', repo, commithash] if shallow else [])],
168 cwd=directory)
commit-bot@chromium.org71e800a2014-04-16 19:21:00 +0000169
Hal Canary5e5f9dc2019-11-01 10:34:32 -0400170 subprocess.check_call([git, 'checkout', '--quiet', commithash], cwd=directory)
commit-bot@chromium.org71e800a2014-04-16 19:21:00 +0000171
172 if verbose:
Hal Canary5e5f9dc2019-11-01 10:34:32 -0400173 status(directory, commithash, True) # Success.
commit-bot@chromium.org71e800a2014-04-16 19:21:00 +0000174
175
176def parse_file_to_dict(path):
177 dictionary = {}
Hal Canary70a4fd22020-01-09 12:35:22 -0500178 with open(path) as f:
Mike Kleina12796b2021-04-09 09:57:07 -0500179 exec('def Var(x): return vars[x]\n' + f.read(), dictionary)
commit-bot@chromium.org71e800a2014-04-16 19:21:00 +0000180 return dictionary
181
182
Hal Canary5e5f9dc2019-11-01 10:34:32 -0400183def is_sha1_sum(s):
184 """SHA1 sums are 160 bits, encoded as lowercase hexadecimal."""
185 return len(s) == 40 and all(c in '0123456789abcdef' for c in s)
186
187
Ben Wagnerb1ffef62023-11-16 13:44:03 -0500188def git_sync_deps(deps_file_path, command_line_os_requests, shallow, verbose):
commit-bot@chromium.org71e800a2014-04-16 19:21:00 +0000189 """Grab dependencies, with optional platform support.
190
191 Args:
192 deps_file_path (string) Path to the DEPS file.
193
halcanaryc6c06242014-08-26 12:06:47 -0700194 command_line_os_requests (list of strings) Can be empty list.
195 List of strings that should each be a key in the deps_os
196 dictionary in the DEPS file.
commit-bot@chromium.org71e800a2014-04-16 19:21:00 +0000197
halcanaryc6c06242014-08-26 12:06:47 -0700198 Raises git Exceptions.
commit-bot@chromium.org71e800a2014-04-16 19:21:00 +0000199 """
halcanary20fb7c62014-06-25 13:28:29 -0700200 git = git_executable()
201 assert git
202
commit-bot@chromium.org71e800a2014-04-16 19:21:00 +0000203 deps_file_directory = os.path.dirname(deps_file_path)
halcanaryc6c06242014-08-26 12:06:47 -0700204 deps_file = parse_file_to_dict(deps_file_path)
205 dependencies = deps_file['deps'].copy()
Hal Canarya6f6f772017-01-27 11:57:13 -0500206 os_specific_dependencies = deps_file.get('deps_os', dict())
207 if 'all' in command_line_os_requests:
208 for value in os_specific_dependencies.itervalues():
209 dependencies.update(value)
210 else:
211 for os_name in command_line_os_requests:
212 # Add OS-specific dependencies
213 if os_name in os_specific_dependencies:
214 dependencies.update(os_specific_dependencies[os_name])
commit-bot@chromium.org71e800a2014-04-16 19:21:00 +0000215 for directory in dependencies:
Hal Canarya6f6f772017-01-27 11:57:13 -0500216 for other_dir in dependencies:
217 if directory.startswith(other_dir + '/'):
218 raise Exception('%r is parent of %r' % (other_dir, directory))
219 list_of_arg_lists = []
220 for directory in sorted(dependencies):
Hal Canary70a4fd22020-01-09 12:35:22 -0500221 if not isinstance(dependencies[directory], str):
Hal Canary2c74c322018-06-20 10:28:37 -0400222 if verbose:
Hal Canary70a4fd22020-01-09 12:35:22 -0500223 sys.stdout.write( 'Skipping "%s".\n' % directory)
Hal Canary2c74c322018-06-20 10:28:37 -0400224 continue
commit-bot@chromium.org71e800a2014-04-16 19:21:00 +0000225 if '@' in dependencies[directory]:
Hal Canary5e5f9dc2019-11-01 10:34:32 -0400226 repo, commithash = dependencies[directory].split('@', 1)
commit-bot@chromium.org71e800a2014-04-16 19:21:00 +0000227 else:
Hal Canary5e5f9dc2019-11-01 10:34:32 -0400228 raise Exception("please specify commit")
229 if not is_sha1_sum(commithash):
230 raise Exception("poorly formed commit hash: %r" % commithash)
commit-bot@chromium.org71e800a2014-04-16 19:21:00 +0000231
232 relative_directory = os.path.join(deps_file_directory, directory)
233
234 list_of_arg_lists.append(
Ben Wagnerb1ffef62023-11-16 13:44:03 -0500235 (git, repo, commithash, relative_directory, shallow, verbose))
commit-bot@chromium.org71e800a2014-04-16 19:21:00 +0000236
237 multithread(git_checkout_to_directory, list_of_arg_lists)
238
commit-bot@chromium.org71e800a2014-04-16 19:21:00 +0000239
240def multithread(function, list_of_arg_lists):
Kevin Lubick8f8c2cf2022-10-11 08:06:15 -0400241 anything_failed = False
commit-bot@chromium.org71e800a2014-04-16 19:21:00 +0000242 threads = []
Kevin Lubick8f8c2cf2022-10-11 08:06:15 -0400243 def hook(args):
244 nonlocal anything_failed
245 anything_failed = True
246 threading.excepthook = hook
commit-bot@chromium.org71e800a2014-04-16 19:21:00 +0000247 for args in list_of_arg_lists:
248 thread = threading.Thread(None, function, None, args)
249 thread.start()
250 threads.append(thread)
251 for thread in threads:
252 thread.join()
Kevin Lubick8f8c2cf2022-10-11 08:06:15 -0400253 if anything_failed:
254 raise Exception("Thread failure detected")
commit-bot@chromium.org71e800a2014-04-16 19:21:00 +0000255
256
257def main(argv):
258 deps_file_path = os.environ.get('GIT_SYNC_DEPS_PATH', DEFAULT_DEPS_PATH)
259 verbose = not bool(os.environ.get('GIT_SYNC_DEPS_QUIET', False))
Kevin Lubick297049b2023-11-16 11:15:33 -0500260 skip_emsdk = bool(os.environ.get('GIT_SYNC_DEPS_SKIP_EMSDK', False))
Brian Osmanea021a32024-01-22 16:01:31 -0500261 shallow = not ('--deep' in argv)
halcanaryc6c06242014-08-26 12:06:47 -0700262
263 if '--help' in argv or '-h' in argv:
commit-bot@chromium.org71e800a2014-04-16 19:21:00 +0000264 usage(deps_file_path)
265 return 1
266
Ben Wagnerb1ffef62023-11-16 13:44:03 -0500267 git_sync_deps(deps_file_path, argv, shallow, verbose)
Hal Canarya6f6f772017-01-27 11:57:13 -0500268 subprocess.check_call(
269 [sys.executable,
270 os.path.join(os.path.dirname(deps_file_path), 'bin', 'fetch-gn')])
Kevin Lubick297049b2023-11-16 11:15:33 -0500271 if not skip_emsdk:
272 subprocess.check_call(
273 [sys.executable,
274 os.path.join(os.path.dirname(deps_file_path), 'bin', 'activate-emsdk')])
halcanaryc6c06242014-08-26 12:06:47 -0700275 return 0
276
commit-bot@chromium.org71e800a2014-04-16 19:21:00 +0000277
278if __name__ == '__main__':
279 exit(main(sys.argv[1:]))