blob: 8edc0c36c71ea9dfb85a90f11d1ad374235e2ec7 [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:
14 GIT_EXECUTABLE: path to "git" binary; if unset, will look for one of
15 ['git', 'git.exe', 'git.bat'] in your default path.
16
17 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
38from git_utils import git_executable
39
40
41DEFAULT_DEPS_PATH = os.path.normpath(
42 os.path.join(os.path.dirname(__file__), os.pardir, 'DEPS'))
43
44
45def usage(deps_file_path = None):
46 sys.stderr.write(
47 'Usage: run to grab dependencies, with optional platform support:\n')
48 sys.stderr.write(' python %s' % __file__)
49 if deps_file_path:
50 for deps_os in parse_file_to_dict(deps_file_path)['deps_os']:
51 sys.stderr.write(' [%s]' % deps_os)
52 else:
53 sys.stderr.write(' [DEPS_OS...]')
54 sys.stderr.write('\n\n')
55 sys.stderr.write(__doc__)
56
57
58def git_repository_sync_is_disabled(git, directory):
59 try:
60 disable = subprocess.check_output(
61 [git, 'config', 'sync-deps.disable'], cwd=directory)
62 return disable.lower().strip() in ['true', '1', 'yes', 'on']
63 except subprocess.CalledProcessError:
64 return False
65
66
commit-bot@chromium.orgaae6e6f2014-04-21 19:03:05 +000067def is_git_toplevel(git, directory):
68 """Return true iff the directory is the top level of a Git repository.
69
70 Args:
71 git (string) the git executable
72
73 directory (string) the path into which the repository
74 is expected to be checked out.
75 """
76 try:
77 toplevel = subprocess.check_output(
78 [git, 'rev-parse', '--show-toplevel'], cwd=directory).strip()
79 return os.path.abspath(directory) == os.path.abspath(toplevel)
80 except subprocess.CalledProcessError:
81 return False
82
83
commit-bot@chromium.org71e800a2014-04-16 19:21:00 +000084def git_checkout_to_directory(git, repo, checkoutable, directory, verbose):
85 """Checkout (and clone if needed) a Git repository.
86
87 Args:
88 git (string) the git executable
89
90 repo (string) the location of the repository, suitable
91 for passing to `git clone`.
92
93 checkoutable (string) a tag, branch, or commit, suitable for
94 passing to `git checkout`
95
96 directory (string) the path into which the repository
97 should be checked out.
98
99 verbose (boolean)
100
101 Raises an exception if any calls to git fail.
102 """
103 if not os.path.isdir(directory):
104 subprocess.check_call(
105 [git, 'clone', '--quiet', repo, directory])
106
commit-bot@chromium.orgaae6e6f2014-04-21 19:03:05 +0000107 if not is_git_toplevel(git, directory):
108 # if the directory exists, but isn't a git repo, you will modify
109 # the parent repostory, which isn't what you want.
110 sys.stdout.write('%s\n IS NOT TOP-LEVEL GIT DIRECTORY.\n' % directory)
111 return
112
commit-bot@chromium.org71e800a2014-04-16 19:21:00 +0000113 # Check to see if this repo is disabled. Quick return.
114 if git_repository_sync_is_disabled(git, directory):
115 sys.stdout.write('%s\n SYNC IS DISABLED.\n' % directory)
116 return
117
118 subprocess.check_call([git, 'fetch', '--quiet'], cwd=directory)
119
120 subprocess.check_call(
121 [git, 'checkout', '--quiet', checkoutable], cwd=directory)
122
123 if verbose:
124 sys.stdout.write('%s\n @ %s\n' % (directory, checkoutable)) # Success.
125
126
127def parse_file_to_dict(path):
128 dictionary = {}
129 execfile(path, dictionary)
130 return dictionary
131
132
133class DepsError(Exception):
134 """Raised if deps_os is a bad key.
135 """
136 pass
137
138
139def git_sync_deps(deps_file_path, deps_os_list, verbose):
140 """Grab dependencies, with optional platform support.
141
142 Args:
143 deps_file_path (string) Path to the DEPS file.
144
145 deps_os_list (list of strings) Can be empty list. List of
146 strings that should each be a key in the deps_os
147 dictionary in the DEPS file.
148
149 Raises DepsError exception and git Exceptions.
150 """
151 git = git_executable()
152 assert git
153
154 deps_file_directory = os.path.dirname(deps_file_path)
155 deps = parse_file_to_dict(deps_file_path)
156 dependencies = deps['deps'].copy()
157 for deps_os in deps_os_list:
158 # Add OS-specific dependencies
159 if deps_os not in deps['deps_os']:
160 raise DepsError(
161 'Argument "%s" not found within deps_os keys %r' %
162 (deps_os, deps['deps_os'].keys()))
163 for dep in deps['deps_os'][deps_os]:
164 dependencies[dep] = deps['deps_os'][deps_os][dep]
165 list_of_arg_lists = []
166 for directory in dependencies:
167 if '@' in dependencies[directory]:
168 repo, checkoutable = dependencies[directory].split('@', 1)
169 else:
170 repo, checkoutable = dependencies[directory], 'origin/master'
171
172 relative_directory = os.path.join(deps_file_directory, directory)
173
174 list_of_arg_lists.append(
175 (git, repo, checkoutable, relative_directory, verbose))
176
177 multithread(git_checkout_to_directory, list_of_arg_lists)
178
179
180def multithread(function, list_of_arg_lists):
181 # for args in list_of_arg_lists:
182 # function(*args)
183 # return
184 threads = []
185 for args in list_of_arg_lists:
186 thread = threading.Thread(None, function, None, args)
187 thread.start()
188 threads.append(thread)
189 for thread in threads:
190 thread.join()
191
192
193def main(argv):
194 deps_file_path = os.environ.get('GIT_SYNC_DEPS_PATH', DEFAULT_DEPS_PATH)
195 verbose = not bool(os.environ.get('GIT_SYNC_DEPS_QUIET', False))
196 try:
197 git_sync_deps(deps_file_path, argv, verbose)
198 return 0
199 except DepsError:
200 usage(deps_file_path)
201 return 1
202
203
204if __name__ == '__main__':
205 exit(main(sys.argv[1:]))