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 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 67def 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.realpath(directory) == os.path.realpath(toplevel) 80 except subprocess.CalledProcessError: 81 return False 82 83 84def 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 107 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 113 # 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:])) 206