blob: 2b460cb83afb7b3aae04a8ba78335891cd145870 [file] [log] [blame]
Hal Canarya6f6f772017-01-27 11:57:13 -05001#!/usr/bin/env python
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:
halcanary20fb7c62014-06-25 13:28:29 -070014 GIT_EXECUTABLE: path to "git" binary; if unset, will look for one of
15 ['git', 'git.exe', 'git.bat'] in your default path.
16
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')
46 searchlist = ['git', 'git.exe', 'git.bat']
47 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()
commit-bot@chromium.org2349a1e2014-04-28 13:39:47 +000097 return os.path.realpath(directory) == os.path.realpath(toplevel)
commit-bot@chromium.orgaae6e6f2014-04-21 19:03:05 +000098 except subprocess.CalledProcessError:
99 return False
100
101
Hal Canarya6f6f772017-01-27 11:57:13 -0500102def status(directory, checkoutable):
103 def truncate(s, length):
104 return s if len(s) <= length else s[:(length - 3)] + '...'
105 dlen = 36
106 directory = truncate(directory, dlen)
107 checkoutable = truncate(checkoutable, 40)
108 sys.stdout.write('%-*s @ %s\n' % (dlen, directory, checkoutable))
109
110
commit-bot@chromium.org71e800a2014-04-16 19:21:00 +0000111def git_checkout_to_directory(git, repo, checkoutable, directory, verbose):
112 """Checkout (and clone if needed) a Git repository.
113
114 Args:
115 git (string) the git executable
116
117 repo (string) the location of the repository, suitable
118 for passing to `git clone`.
119
120 checkoutable (string) a tag, branch, or commit, suitable for
121 passing to `git checkout`
122
123 directory (string) the path into which the repository
124 should be checked out.
125
126 verbose (boolean)
127
128 Raises an exception if any calls to git fail.
129 """
130 if not os.path.isdir(directory):
131 subprocess.check_call(
132 [git, 'clone', '--quiet', repo, directory])
133
commit-bot@chromium.orgaae6e6f2014-04-21 19:03:05 +0000134 if not is_git_toplevel(git, directory):
135 # if the directory exists, but isn't a git repo, you will modify
136 # the parent repostory, which isn't what you want.
137 sys.stdout.write('%s\n IS NOT TOP-LEVEL GIT DIRECTORY.\n' % directory)
138 return
139
commit-bot@chromium.org71e800a2014-04-16 19:21:00 +0000140 # Check to see if this repo is disabled. Quick return.
141 if git_repository_sync_is_disabled(git, directory):
142 sys.stdout.write('%s\n SYNC IS DISABLED.\n' % directory)
143 return
144
halcanary6aff54c2015-11-03 09:50:03 -0800145 if 0 == subprocess.call(
146 [git, 'checkout', '--quiet', checkoutable], cwd=directory):
147 # if this succeeds, skip slow `git fetch`.
148 if verbose:
Hal Canarya6f6f772017-01-27 11:57:13 -0500149 status(directory, checkoutable) # Success.
halcanary6aff54c2015-11-03 09:50:03 -0800150 return
151
Hal Canarya6f6f772017-01-27 11:57:13 -0500152 # If the repo has changed, always force use of the correct repo.
153 # If origin already points to repo, this is a quick no-op.
154 subprocess.check_call(
155 [git, 'remote', 'set-url', 'origin', repo], cwd=directory)
156
commit-bot@chromium.org71e800a2014-04-16 19:21:00 +0000157 subprocess.check_call([git, 'fetch', '--quiet'], cwd=directory)
158
Hal Canary49deb7e2017-02-07 12:05:47 -0500159 subprocess.check_call([git, 'checkout', '--quiet', checkoutable], cwd=directory)
commit-bot@chromium.org71e800a2014-04-16 19:21:00 +0000160
161 if verbose:
Hal Canarya6f6f772017-01-27 11:57:13 -0500162 status(directory, checkoutable) # Success.
commit-bot@chromium.org71e800a2014-04-16 19:21:00 +0000163
164
165def parse_file_to_dict(path):
166 dictionary = {}
167 execfile(path, dictionary)
168 return dictionary
169
170
halcanaryc6c06242014-08-26 12:06:47 -0700171def git_sync_deps(deps_file_path, command_line_os_requests, verbose):
commit-bot@chromium.org71e800a2014-04-16 19:21:00 +0000172 """Grab dependencies, with optional platform support.
173
174 Args:
175 deps_file_path (string) Path to the DEPS file.
176
halcanaryc6c06242014-08-26 12:06:47 -0700177 command_line_os_requests (list of strings) Can be empty list.
178 List of strings that should each be a key in the deps_os
179 dictionary in the DEPS file.
commit-bot@chromium.org71e800a2014-04-16 19:21:00 +0000180
halcanaryc6c06242014-08-26 12:06:47 -0700181 Raises git Exceptions.
commit-bot@chromium.org71e800a2014-04-16 19:21:00 +0000182 """
halcanary20fb7c62014-06-25 13:28:29 -0700183 git = git_executable()
184 assert git
185
commit-bot@chromium.org71e800a2014-04-16 19:21:00 +0000186 deps_file_directory = os.path.dirname(deps_file_path)
halcanaryc6c06242014-08-26 12:06:47 -0700187 deps_file = parse_file_to_dict(deps_file_path)
188 dependencies = deps_file['deps'].copy()
Hal Canarya6f6f772017-01-27 11:57:13 -0500189 os_specific_dependencies = deps_file.get('deps_os', dict())
190 if 'all' in command_line_os_requests:
191 for value in os_specific_dependencies.itervalues():
192 dependencies.update(value)
193 else:
194 for os_name in command_line_os_requests:
195 # Add OS-specific dependencies
196 if os_name in os_specific_dependencies:
197 dependencies.update(os_specific_dependencies[os_name])
commit-bot@chromium.org71e800a2014-04-16 19:21:00 +0000198 for directory in dependencies:
Hal Canarya6f6f772017-01-27 11:57:13 -0500199 for other_dir in dependencies:
200 if directory.startswith(other_dir + '/'):
201 raise Exception('%r is parent of %r' % (other_dir, directory))
202 list_of_arg_lists = []
203 for directory in sorted(dependencies):
commit-bot@chromium.org71e800a2014-04-16 19:21:00 +0000204 if '@' in dependencies[directory]:
205 repo, checkoutable = dependencies[directory].split('@', 1)
206 else:
halcanary6aff54c2015-11-03 09:50:03 -0800207 raise Exception("please specify commit or tag")
commit-bot@chromium.org71e800a2014-04-16 19:21:00 +0000208
209 relative_directory = os.path.join(deps_file_directory, directory)
210
211 list_of_arg_lists.append(
halcanary20fb7c62014-06-25 13:28:29 -0700212 (git, repo, checkoutable, relative_directory, verbose))
commit-bot@chromium.org71e800a2014-04-16 19:21:00 +0000213
214 multithread(git_checkout_to_directory, list_of_arg_lists)
215
halcanaryc6c06242014-08-26 12:06:47 -0700216 for directory in deps_file.get('recursedeps', []):
halcanary5164a972014-08-21 13:17:43 -0700217 recursive_path = os.path.join(deps_file_directory, directory, 'DEPS')
halcanaryc6c06242014-08-26 12:06:47 -0700218 git_sync_deps(recursive_path, command_line_os_requests, verbose)
halcanary5164a972014-08-21 13:17:43 -0700219
commit-bot@chromium.org71e800a2014-04-16 19:21:00 +0000220
221def multithread(function, list_of_arg_lists):
222 # for args in list_of_arg_lists:
223 # function(*args)
224 # return
225 threads = []
226 for args in list_of_arg_lists:
227 thread = threading.Thread(None, function, None, args)
228 thread.start()
229 threads.append(thread)
230 for thread in threads:
231 thread.join()
232
233
234def main(argv):
235 deps_file_path = os.environ.get('GIT_SYNC_DEPS_PATH', DEFAULT_DEPS_PATH)
236 verbose = not bool(os.environ.get('GIT_SYNC_DEPS_QUIET', False))
halcanaryc6c06242014-08-26 12:06:47 -0700237
238 if '--help' in argv or '-h' in argv:
commit-bot@chromium.org71e800a2014-04-16 19:21:00 +0000239 usage(deps_file_path)
240 return 1
241
halcanaryc6c06242014-08-26 12:06:47 -0700242 git_sync_deps(deps_file_path, argv, verbose)
Hal Canarya6f6f772017-01-27 11:57:13 -0500243 subprocess.check_call(
244 [sys.executable,
245 os.path.join(os.path.dirname(deps_file_path), 'bin', 'fetch-gn')])
halcanaryc6c06242014-08-26 12:06:47 -0700246 return 0
247
commit-bot@chromium.org71e800a2014-04-16 19:21:00 +0000248
249if __name__ == '__main__':
250 exit(main(sys.argv[1:]))