blob: c99ae9120727abdd17a3163743d0506f0c57e10c [file] [log] [blame]
commit-bot@chromium.org71e800a2014-04-16 19:21:00 +00001#!/usr/bin/python
2# 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')
66 sys.stderr.write(' python %s' % __file__)
67 if deps_file_path:
68 for deps_os in parse_file_to_dict(deps_file_path)['deps_os']:
69 sys.stderr.write(' [%s]' % deps_os)
70 else:
71 sys.stderr.write(' [DEPS_OS...]')
72 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
commit-bot@chromium.org71e800a2014-04-16 19:21:00 +0000102def git_checkout_to_directory(git, repo, checkoutable, directory, verbose):
103 """Checkout (and clone if needed) a Git repository.
104
105 Args:
106 git (string) the git executable
107
108 repo (string) the location of the repository, suitable
109 for passing to `git clone`.
110
111 checkoutable (string) a tag, branch, or commit, suitable for
112 passing to `git checkout`
113
114 directory (string) the path into which the repository
115 should be checked out.
116
117 verbose (boolean)
118
119 Raises an exception if any calls to git fail.
120 """
121 if not os.path.isdir(directory):
122 subprocess.check_call(
123 [git, 'clone', '--quiet', repo, directory])
124
commit-bot@chromium.orgaae6e6f2014-04-21 19:03:05 +0000125 if not is_git_toplevel(git, directory):
126 # if the directory exists, but isn't a git repo, you will modify
127 # the parent repostory, which isn't what you want.
128 sys.stdout.write('%s\n IS NOT TOP-LEVEL GIT DIRECTORY.\n' % directory)
129 return
130
commit-bot@chromium.org71e800a2014-04-16 19:21:00 +0000131 # Check to see if this repo is disabled. Quick return.
132 if git_repository_sync_is_disabled(git, directory):
133 sys.stdout.write('%s\n SYNC IS DISABLED.\n' % directory)
134 return
135
halcanary6aff54c2015-11-03 09:50:03 -0800136 if 0 == subprocess.call(
137 [git, 'checkout', '--quiet', checkoutable], cwd=directory):
138 # if this succeeds, skip slow `git fetch`.
139 if verbose:
140 sys.stdout.write('%s\n @ %s\n' % (directory, checkoutable))
141 return
142
commit-bot@chromium.org71e800a2014-04-16 19:21:00 +0000143 subprocess.check_call([git, 'fetch', '--quiet'], cwd=directory)
144
halcanary8b6ab362015-11-24 13:10:52 -0800145 if 0 != subprocess.call(
146 [git, 'checkout', '--quiet', checkoutable], cwd=directory):
147 subprocess.check_call([git, 'remote', 'set-url', repo], cwd=directory)
148 subprocess.check_call([git, 'fetch', '--quiet'], cwd=directory)
149 subprocess.check_call([git, 'checkout', '--quiet'], cwd=directory)
commit-bot@chromium.org71e800a2014-04-16 19:21:00 +0000150
151 if verbose:
152 sys.stdout.write('%s\n @ %s\n' % (directory, checkoutable)) # Success.
153
154
155def parse_file_to_dict(path):
156 dictionary = {}
157 execfile(path, dictionary)
158 return dictionary
159
160
halcanaryc6c06242014-08-26 12:06:47 -0700161def git_sync_deps(deps_file_path, command_line_os_requests, verbose):
commit-bot@chromium.org71e800a2014-04-16 19:21:00 +0000162 """Grab dependencies, with optional platform support.
163
164 Args:
165 deps_file_path (string) Path to the DEPS file.
166
halcanaryc6c06242014-08-26 12:06:47 -0700167 command_line_os_requests (list of strings) Can be empty list.
168 List of strings that should each be a key in the deps_os
169 dictionary in the DEPS file.
commit-bot@chromium.org71e800a2014-04-16 19:21:00 +0000170
halcanaryc6c06242014-08-26 12:06:47 -0700171 Raises git Exceptions.
commit-bot@chromium.org71e800a2014-04-16 19:21:00 +0000172 """
halcanary20fb7c62014-06-25 13:28:29 -0700173 git = git_executable()
174 assert git
175
commit-bot@chromium.org71e800a2014-04-16 19:21:00 +0000176 deps_file_directory = os.path.dirname(deps_file_path)
halcanaryc6c06242014-08-26 12:06:47 -0700177 deps_file = parse_file_to_dict(deps_file_path)
178 dependencies = deps_file['deps'].copy()
179 os_specific_dependencies = deps_file.get('deps_os', [])
180 for os_name in command_line_os_requests:
commit-bot@chromium.org71e800a2014-04-16 19:21:00 +0000181 # Add OS-specific dependencies
halcanaryc6c06242014-08-26 12:06:47 -0700182 if os_name in os_specific_dependencies:
183 dependencies.update(os_specific_dependencies[os_name])
commit-bot@chromium.org71e800a2014-04-16 19:21:00 +0000184 list_of_arg_lists = []
185 for directory in dependencies:
186 if '@' in dependencies[directory]:
187 repo, checkoutable = dependencies[directory].split('@', 1)
188 else:
halcanary6aff54c2015-11-03 09:50:03 -0800189 raise Exception("please specify commit or tag")
commit-bot@chromium.org71e800a2014-04-16 19:21:00 +0000190
191 relative_directory = os.path.join(deps_file_directory, directory)
192
193 list_of_arg_lists.append(
halcanary20fb7c62014-06-25 13:28:29 -0700194 (git, repo, checkoutable, relative_directory, verbose))
commit-bot@chromium.org71e800a2014-04-16 19:21:00 +0000195
196 multithread(git_checkout_to_directory, list_of_arg_lists)
197
halcanaryc6c06242014-08-26 12:06:47 -0700198 for directory in deps_file.get('recursedeps', []):
halcanary5164a972014-08-21 13:17:43 -0700199 recursive_path = os.path.join(deps_file_directory, directory, 'DEPS')
halcanaryc6c06242014-08-26 12:06:47 -0700200 git_sync_deps(recursive_path, command_line_os_requests, verbose)
halcanary5164a972014-08-21 13:17:43 -0700201
commit-bot@chromium.org71e800a2014-04-16 19:21:00 +0000202
203def multithread(function, list_of_arg_lists):
204 # for args in list_of_arg_lists:
205 # function(*args)
206 # return
207 threads = []
208 for args in list_of_arg_lists:
209 thread = threading.Thread(None, function, None, args)
210 thread.start()
211 threads.append(thread)
212 for thread in threads:
213 thread.join()
214
215
216def main(argv):
217 deps_file_path = os.environ.get('GIT_SYNC_DEPS_PATH', DEFAULT_DEPS_PATH)
218 verbose = not bool(os.environ.get('GIT_SYNC_DEPS_QUIET', False))
halcanaryc6c06242014-08-26 12:06:47 -0700219
220 if '--help' in argv or '-h' in argv:
commit-bot@chromium.org71e800a2014-04-16 19:21:00 +0000221 usage(deps_file_path)
222 return 1
223
halcanaryc6c06242014-08-26 12:06:47 -0700224 git_sync_deps(deps_file_path, argv, verbose)
225 return 0
226
commit-bot@chromium.org71e800a2014-04-16 19:21:00 +0000227
228if __name__ == '__main__':
229 exit(main(sys.argv[1:]))