1#!/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 38 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 57 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 85def 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() 97 return os.path.realpath(directory) == os.path.realpath(toplevel) 98 except subprocess.CalledProcessError: 99 return False 100 101 102def 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 125 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 131 # 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 136 subprocess.check_call([git, 'fetch', '--quiet'], cwd=directory) 137 138 subprocess.check_call( 139 [git, 'checkout', '--quiet', checkoutable], cwd=directory) 140 141 if verbose: 142 sys.stdout.write('%s\n @ %s\n' % (directory, checkoutable)) # Success. 143 144 145def parse_file_to_dict(path): 146 dictionary = {} 147 execfile(path, dictionary) 148 return dictionary 149 150 151def git_sync_deps(deps_file_path, command_line_os_requests, verbose): 152 """Grab dependencies, with optional platform support. 153 154 Args: 155 deps_file_path (string) Path to the DEPS file. 156 157 command_line_os_requests (list of strings) Can be empty list. 158 List of strings that should each be a key in the deps_os 159 dictionary in the DEPS file. 160 161 Raises git Exceptions. 162 """ 163 git = git_executable() 164 assert git 165 166 deps_file_directory = os.path.dirname(deps_file_path) 167 deps_file = parse_file_to_dict(deps_file_path) 168 dependencies = deps_file['deps'].copy() 169 os_specific_dependencies = deps_file.get('deps_os', []) 170 for os_name in command_line_os_requests: 171 # Add OS-specific dependencies 172 if os_name in os_specific_dependencies: 173 dependencies.update(os_specific_dependencies[os_name]) 174 list_of_arg_lists = [] 175 for directory in dependencies: 176 if '@' in dependencies[directory]: 177 repo, checkoutable = dependencies[directory].split('@', 1) 178 else: 179 repo, checkoutable = dependencies[directory], 'origin/master' 180 181 relative_directory = os.path.join(deps_file_directory, directory) 182 183 list_of_arg_lists.append( 184 (git, repo, checkoutable, relative_directory, verbose)) 185 186 multithread(git_checkout_to_directory, list_of_arg_lists) 187 188 for directory in deps_file.get('recursedeps', []): 189 recursive_path = os.path.join(deps_file_directory, directory, 'DEPS') 190 git_sync_deps(recursive_path, command_line_os_requests, verbose) 191 192 193def multithread(function, list_of_arg_lists): 194 # for args in list_of_arg_lists: 195 # function(*args) 196 # return 197 threads = [] 198 for args in list_of_arg_lists: 199 thread = threading.Thread(None, function, None, args) 200 thread.start() 201 threads.append(thread) 202 for thread in threads: 203 thread.join() 204 205 206def main(argv): 207 deps_file_path = os.environ.get('GIT_SYNC_DEPS_PATH', DEFAULT_DEPS_PATH) 208 verbose = not bool(os.environ.get('GIT_SYNC_DEPS_QUIET', False)) 209 210 if '--help' in argv or '-h' in argv: 211 usage(deps_file_path) 212 return 1 213 214 git_sync_deps(deps_file_path, argv, verbose) 215 return 0 216 217 218if __name__ == '__main__': 219 exit(main(sys.argv[1:])) 220