1*c8dee2aaSAndroid Build Coastguard Worker#!/usr/bin/python3 2*c8dee2aaSAndroid Build Coastguard Worker# Copyright 2014 Google Inc. 3*c8dee2aaSAndroid Build Coastguard Worker# 4*c8dee2aaSAndroid Build Coastguard Worker# Use of this source code is governed by a BSD-style license that can be 5*c8dee2aaSAndroid Build Coastguard Worker# found in the LICENSE file. 6*c8dee2aaSAndroid Build Coastguard Worker 7*c8dee2aaSAndroid Build Coastguard Worker 8*c8dee2aaSAndroid Build Coastguard Worker"""Parse a DEPS file and git checkout all of the dependencies. 9*c8dee2aaSAndroid Build Coastguard Worker 10*c8dee2aaSAndroid Build Coastguard WorkerArgs: 11*c8dee2aaSAndroid Build Coastguard Worker An optional list of deps_os values. 12*c8dee2aaSAndroid Build Coastguard Worker 13*c8dee2aaSAndroid Build Coastguard WorkerEnvironment Variables: 14*c8dee2aaSAndroid Build Coastguard Worker GIT_EXECUTABLE: path to "git" binary; if unset, will look for git in 15*c8dee2aaSAndroid Build Coastguard Worker your default path. 16*c8dee2aaSAndroid Build Coastguard Worker 17*c8dee2aaSAndroid Build Coastguard Worker GIT_SYNC_DEPS_PATH: file to get the dependency list from; if unset, 18*c8dee2aaSAndroid Build Coastguard Worker will use the file ../DEPS relative to this script's directory. 19*c8dee2aaSAndroid Build Coastguard Worker 20*c8dee2aaSAndroid Build Coastguard Worker GIT_SYNC_DEPS_QUIET: if set to non-empty string, suppress messages. 21*c8dee2aaSAndroid Build Coastguard Worker 22*c8dee2aaSAndroid Build Coastguard WorkerGit Config: 23*c8dee2aaSAndroid Build Coastguard Worker To disable syncing of a single repository: 24*c8dee2aaSAndroid Build Coastguard Worker cd path/to/repository 25*c8dee2aaSAndroid Build Coastguard Worker git config sync-deps.disable true 26*c8dee2aaSAndroid Build Coastguard Worker 27*c8dee2aaSAndroid Build Coastguard Worker To re-enable sync: 28*c8dee2aaSAndroid Build Coastguard Worker cd path/to/repository 29*c8dee2aaSAndroid Build Coastguard Worker git config --unset sync-deps.disable 30*c8dee2aaSAndroid Build Coastguard Worker""" 31*c8dee2aaSAndroid Build Coastguard Worker 32*c8dee2aaSAndroid Build Coastguard Worker 33*c8dee2aaSAndroid Build Coastguard Workerimport os 34*c8dee2aaSAndroid Build Coastguard Workerimport subprocess 35*c8dee2aaSAndroid Build Coastguard Workerimport sys 36*c8dee2aaSAndroid Build Coastguard Workerimport threading 37*c8dee2aaSAndroid Build Coastguard Worker 38*c8dee2aaSAndroid Build Coastguard Worker 39*c8dee2aaSAndroid Build Coastguard Workerdef git_executable(): 40*c8dee2aaSAndroid Build Coastguard Worker """Find the git executable. 41*c8dee2aaSAndroid Build Coastguard Worker 42*c8dee2aaSAndroid Build Coastguard Worker Returns: 43*c8dee2aaSAndroid Build Coastguard Worker A string suitable for passing to subprocess functions, or None. 44*c8dee2aaSAndroid Build Coastguard Worker """ 45*c8dee2aaSAndroid Build Coastguard Worker envgit = os.environ.get('GIT_EXECUTABLE') 46*c8dee2aaSAndroid Build Coastguard Worker searchlist = ['git', 'git.bat'] 47*c8dee2aaSAndroid Build Coastguard Worker if envgit: 48*c8dee2aaSAndroid Build Coastguard Worker searchlist.insert(0, envgit) 49*c8dee2aaSAndroid Build Coastguard Worker with open(os.devnull, 'w') as devnull: 50*c8dee2aaSAndroid Build Coastguard Worker for git in searchlist: 51*c8dee2aaSAndroid Build Coastguard Worker try: 52*c8dee2aaSAndroid Build Coastguard Worker subprocess.call([git, '--version'], stdout=devnull) 53*c8dee2aaSAndroid Build Coastguard Worker except (OSError,): 54*c8dee2aaSAndroid Build Coastguard Worker continue 55*c8dee2aaSAndroid Build Coastguard Worker return git 56*c8dee2aaSAndroid Build Coastguard Worker return None 57*c8dee2aaSAndroid Build Coastguard Worker 58*c8dee2aaSAndroid Build Coastguard Worker 59*c8dee2aaSAndroid Build Coastguard WorkerDEFAULT_DEPS_PATH = os.path.normpath( 60*c8dee2aaSAndroid Build Coastguard Worker os.path.join(os.path.dirname(__file__), os.pardir, 'DEPS')) 61*c8dee2aaSAndroid Build Coastguard Worker 62*c8dee2aaSAndroid Build Coastguard Worker 63*c8dee2aaSAndroid Build Coastguard Workerdef usage(deps_file_path = None): 64*c8dee2aaSAndroid Build Coastguard Worker sys.stderr.write( 65*c8dee2aaSAndroid Build Coastguard Worker 'Usage: run to grab dependencies, with optional platform support:\n') 66*c8dee2aaSAndroid Build Coastguard Worker sys.stderr.write(' %s %s' % (sys.executable, __file__)) 67*c8dee2aaSAndroid Build Coastguard Worker if deps_file_path: 68*c8dee2aaSAndroid Build Coastguard Worker parsed_deps = parse_file_to_dict(deps_file_path) 69*c8dee2aaSAndroid Build Coastguard Worker if 'deps_os' in parsed_deps: 70*c8dee2aaSAndroid Build Coastguard Worker for deps_os in parsed_deps['deps_os']: 71*c8dee2aaSAndroid Build Coastguard Worker sys.stderr.write(' [%s]' % deps_os) 72*c8dee2aaSAndroid Build Coastguard Worker sys.stderr.write('\n\n') 73*c8dee2aaSAndroid Build Coastguard Worker sys.stderr.write(__doc__) 74*c8dee2aaSAndroid Build Coastguard Worker 75*c8dee2aaSAndroid Build Coastguard Worker 76*c8dee2aaSAndroid Build Coastguard Workerdef git_repository_sync_is_disabled(git, directory): 77*c8dee2aaSAndroid Build Coastguard Worker try: 78*c8dee2aaSAndroid Build Coastguard Worker disable = subprocess.check_output( 79*c8dee2aaSAndroid Build Coastguard Worker [git, 'config', 'sync-deps.disable'], cwd=directory) 80*c8dee2aaSAndroid Build Coastguard Worker return disable.lower().strip() in ['true', '1', 'yes', 'on'] 81*c8dee2aaSAndroid Build Coastguard Worker except subprocess.CalledProcessError: 82*c8dee2aaSAndroid Build Coastguard Worker return False 83*c8dee2aaSAndroid Build Coastguard Worker 84*c8dee2aaSAndroid Build Coastguard Worker 85*c8dee2aaSAndroid Build Coastguard Workerdef is_git_toplevel(git, directory): 86*c8dee2aaSAndroid Build Coastguard Worker """Return true iff the directory is the top level of a Git repository. 87*c8dee2aaSAndroid Build Coastguard Worker 88*c8dee2aaSAndroid Build Coastguard Worker Args: 89*c8dee2aaSAndroid Build Coastguard Worker git (string) the git executable 90*c8dee2aaSAndroid Build Coastguard Worker 91*c8dee2aaSAndroid Build Coastguard Worker directory (string) the path into which the repository 92*c8dee2aaSAndroid Build Coastguard Worker is expected to be checked out. 93*c8dee2aaSAndroid Build Coastguard Worker """ 94*c8dee2aaSAndroid Build Coastguard Worker try: 95*c8dee2aaSAndroid Build Coastguard Worker toplevel = subprocess.check_output( 96*c8dee2aaSAndroid Build Coastguard Worker [git, 'rev-parse', '--show-toplevel'], cwd=directory).strip() 97*c8dee2aaSAndroid Build Coastguard Worker return (os.path.normcase(os.path.realpath(directory)) == 98*c8dee2aaSAndroid Build Coastguard Worker os.path.normcase(os.path.realpath(toplevel.decode()))) 99*c8dee2aaSAndroid Build Coastguard Worker except subprocess.CalledProcessError: 100*c8dee2aaSAndroid Build Coastguard Worker return False 101*c8dee2aaSAndroid Build Coastguard Worker 102*c8dee2aaSAndroid Build Coastguard Worker 103*c8dee2aaSAndroid Build Coastguard Workerdef status(directory, commithash, change): 104*c8dee2aaSAndroid Build Coastguard Worker def truncate_beginning(s, length): 105*c8dee2aaSAndroid Build Coastguard Worker return s if len(s) <= length else '...' + s[-(length-3):] 106*c8dee2aaSAndroid Build Coastguard Worker def truncate_end(s, length): 107*c8dee2aaSAndroid Build Coastguard Worker return s if len(s) <= length else s[:(length - 3)] + '...' 108*c8dee2aaSAndroid Build Coastguard Worker 109*c8dee2aaSAndroid Build Coastguard Worker dlen = 36 110*c8dee2aaSAndroid Build Coastguard Worker directory = truncate_beginning(directory, dlen) 111*c8dee2aaSAndroid Build Coastguard Worker commithash = truncate_end(commithash, 40) 112*c8dee2aaSAndroid Build Coastguard Worker symbol = '>' if change else '@' 113*c8dee2aaSAndroid Build Coastguard Worker sys.stdout.write('%-*s %s %s\n' % (dlen, directory, symbol, commithash)) 114*c8dee2aaSAndroid Build Coastguard Worker 115*c8dee2aaSAndroid Build Coastguard Worker 116*c8dee2aaSAndroid Build Coastguard Workerdef git_checkout_to_directory(git, repo, commithash, directory, shallow, verbose): 117*c8dee2aaSAndroid Build Coastguard Worker """Checkout (and clone if needed) a Git repository. 118*c8dee2aaSAndroid Build Coastguard Worker 119*c8dee2aaSAndroid Build Coastguard Worker Args: 120*c8dee2aaSAndroid Build Coastguard Worker git (string) the git executable 121*c8dee2aaSAndroid Build Coastguard Worker 122*c8dee2aaSAndroid Build Coastguard Worker repo (string) the location of the repository, suitable 123*c8dee2aaSAndroid Build Coastguard Worker for passing to `git clone`. 124*c8dee2aaSAndroid Build Coastguard Worker 125*c8dee2aaSAndroid Build Coastguard Worker commithash (string) a commit, suitable for passing to `git checkout` 126*c8dee2aaSAndroid Build Coastguard Worker 127*c8dee2aaSAndroid Build Coastguard Worker directory (string) the path into which the repository 128*c8dee2aaSAndroid Build Coastguard Worker should be checked out. 129*c8dee2aaSAndroid Build Coastguard Worker 130*c8dee2aaSAndroid Build Coastguard Worker verbose (boolean) 131*c8dee2aaSAndroid Build Coastguard Worker 132*c8dee2aaSAndroid Build Coastguard Worker Raises an exception if any calls to git fail. 133*c8dee2aaSAndroid Build Coastguard Worker """ 134*c8dee2aaSAndroid Build Coastguard Worker if not os.path.isdir(directory): 135*c8dee2aaSAndroid Build Coastguard Worker subprocess.check_call( 136*c8dee2aaSAndroid Build Coastguard Worker [git, 'clone', '--quiet', *(['--depth=1'] if shallow else []), 137*c8dee2aaSAndroid Build Coastguard Worker '--no-checkout', repo, directory]) 138*c8dee2aaSAndroid Build Coastguard Worker 139*c8dee2aaSAndroid Build Coastguard Worker if not is_git_toplevel(git, directory): 140*c8dee2aaSAndroid Build Coastguard Worker # if the directory exists, but isn't a git repo, you will modify 141*c8dee2aaSAndroid Build Coastguard Worker # the parent repository, which isn't what you want. 142*c8dee2aaSAndroid Build Coastguard Worker sys.stdout.write('%s\n IS NOT TOP-LEVEL GIT DIRECTORY.\n' % directory) 143*c8dee2aaSAndroid Build Coastguard Worker return 144*c8dee2aaSAndroid Build Coastguard Worker 145*c8dee2aaSAndroid Build Coastguard Worker # Check to see if this repo is disabled. Quick return. 146*c8dee2aaSAndroid Build Coastguard Worker if git_repository_sync_is_disabled(git, directory): 147*c8dee2aaSAndroid Build Coastguard Worker sys.stdout.write('%s\n SYNC IS DISABLED.\n' % directory) 148*c8dee2aaSAndroid Build Coastguard Worker return 149*c8dee2aaSAndroid Build Coastguard Worker 150*c8dee2aaSAndroid Build Coastguard Worker with open(os.devnull, 'w') as devnull: 151*c8dee2aaSAndroid Build Coastguard Worker # If this fails, we will fetch before trying again. Don't spam user 152*c8dee2aaSAndroid Build Coastguard Worker # with error infomation. 153*c8dee2aaSAndroid Build Coastguard Worker if 0 == subprocess.call([git, 'checkout', '--quiet', commithash], 154*c8dee2aaSAndroid Build Coastguard Worker cwd=directory, stderr=devnull): 155*c8dee2aaSAndroid Build Coastguard Worker # if this succeeds, skip slow `git fetch`. 156*c8dee2aaSAndroid Build Coastguard Worker if verbose: 157*c8dee2aaSAndroid Build Coastguard Worker status(directory, commithash, False) # Success. 158*c8dee2aaSAndroid Build Coastguard Worker return 159*c8dee2aaSAndroid Build Coastguard Worker 160*c8dee2aaSAndroid Build Coastguard Worker # If the repo has changed, always force use of the correct repo. 161*c8dee2aaSAndroid Build Coastguard Worker # If origin already points to repo, this is a quick no-op. 162*c8dee2aaSAndroid Build Coastguard Worker subprocess.check_call( 163*c8dee2aaSAndroid Build Coastguard Worker [git, 'remote', 'set-url', 'origin', repo], cwd=directory) 164*c8dee2aaSAndroid Build Coastguard Worker 165*c8dee2aaSAndroid Build Coastguard Worker subprocess.check_call( 166*c8dee2aaSAndroid Build Coastguard Worker [git, 'fetch', '--quiet', 167*c8dee2aaSAndroid Build Coastguard Worker *(['--depth=1', repo, commithash] if shallow else [])], 168*c8dee2aaSAndroid Build Coastguard Worker cwd=directory) 169*c8dee2aaSAndroid Build Coastguard Worker 170*c8dee2aaSAndroid Build Coastguard Worker subprocess.check_call([git, 'checkout', '--quiet', commithash], cwd=directory) 171*c8dee2aaSAndroid Build Coastguard Worker 172*c8dee2aaSAndroid Build Coastguard Worker if verbose: 173*c8dee2aaSAndroid Build Coastguard Worker status(directory, commithash, True) # Success. 174*c8dee2aaSAndroid Build Coastguard Worker 175*c8dee2aaSAndroid Build Coastguard Worker 176*c8dee2aaSAndroid Build Coastguard Workerdef parse_file_to_dict(path): 177*c8dee2aaSAndroid Build Coastguard Worker dictionary = {} 178*c8dee2aaSAndroid Build Coastguard Worker with open(path) as f: 179*c8dee2aaSAndroid Build Coastguard Worker exec('def Var(x): return vars[x]\n' + f.read(), dictionary) 180*c8dee2aaSAndroid Build Coastguard Worker return dictionary 181*c8dee2aaSAndroid Build Coastguard Worker 182*c8dee2aaSAndroid Build Coastguard Worker 183*c8dee2aaSAndroid Build Coastguard Workerdef is_sha1_sum(s): 184*c8dee2aaSAndroid Build Coastguard Worker """SHA1 sums are 160 bits, encoded as lowercase hexadecimal.""" 185*c8dee2aaSAndroid Build Coastguard Worker return len(s) == 40 and all(c in '0123456789abcdef' for c in s) 186*c8dee2aaSAndroid Build Coastguard Worker 187*c8dee2aaSAndroid Build Coastguard Worker 188*c8dee2aaSAndroid Build Coastguard Workerdef git_sync_deps(deps_file_path, command_line_os_requests, shallow, verbose): 189*c8dee2aaSAndroid Build Coastguard Worker """Grab dependencies, with optional platform support. 190*c8dee2aaSAndroid Build Coastguard Worker 191*c8dee2aaSAndroid Build Coastguard Worker Args: 192*c8dee2aaSAndroid Build Coastguard Worker deps_file_path (string) Path to the DEPS file. 193*c8dee2aaSAndroid Build Coastguard Worker 194*c8dee2aaSAndroid Build Coastguard Worker command_line_os_requests (list of strings) Can be empty list. 195*c8dee2aaSAndroid Build Coastguard Worker List of strings that should each be a key in the deps_os 196*c8dee2aaSAndroid Build Coastguard Worker dictionary in the DEPS file. 197*c8dee2aaSAndroid Build Coastguard Worker 198*c8dee2aaSAndroid Build Coastguard Worker Raises git Exceptions. 199*c8dee2aaSAndroid Build Coastguard Worker """ 200*c8dee2aaSAndroid Build Coastguard Worker git = git_executable() 201*c8dee2aaSAndroid Build Coastguard Worker assert git 202*c8dee2aaSAndroid Build Coastguard Worker 203*c8dee2aaSAndroid Build Coastguard Worker deps_file_directory = os.path.dirname(deps_file_path) 204*c8dee2aaSAndroid Build Coastguard Worker deps_file = parse_file_to_dict(deps_file_path) 205*c8dee2aaSAndroid Build Coastguard Worker dependencies = deps_file['deps'].copy() 206*c8dee2aaSAndroid Build Coastguard Worker os_specific_dependencies = deps_file.get('deps_os', dict()) 207*c8dee2aaSAndroid Build Coastguard Worker if 'all' in command_line_os_requests: 208*c8dee2aaSAndroid Build Coastguard Worker for value in os_specific_dependencies.itervalues(): 209*c8dee2aaSAndroid Build Coastguard Worker dependencies.update(value) 210*c8dee2aaSAndroid Build Coastguard Worker else: 211*c8dee2aaSAndroid Build Coastguard Worker for os_name in command_line_os_requests: 212*c8dee2aaSAndroid Build Coastguard Worker # Add OS-specific dependencies 213*c8dee2aaSAndroid Build Coastguard Worker if os_name in os_specific_dependencies: 214*c8dee2aaSAndroid Build Coastguard Worker dependencies.update(os_specific_dependencies[os_name]) 215*c8dee2aaSAndroid Build Coastguard Worker for directory in dependencies: 216*c8dee2aaSAndroid Build Coastguard Worker for other_dir in dependencies: 217*c8dee2aaSAndroid Build Coastguard Worker if directory.startswith(other_dir + '/'): 218*c8dee2aaSAndroid Build Coastguard Worker raise Exception('%r is parent of %r' % (other_dir, directory)) 219*c8dee2aaSAndroid Build Coastguard Worker list_of_arg_lists = [] 220*c8dee2aaSAndroid Build Coastguard Worker for directory in sorted(dependencies): 221*c8dee2aaSAndroid Build Coastguard Worker if not isinstance(dependencies[directory], str): 222*c8dee2aaSAndroid Build Coastguard Worker if verbose: 223*c8dee2aaSAndroid Build Coastguard Worker sys.stdout.write( 'Skipping "%s".\n' % directory) 224*c8dee2aaSAndroid Build Coastguard Worker continue 225*c8dee2aaSAndroid Build Coastguard Worker if '@' in dependencies[directory]: 226*c8dee2aaSAndroid Build Coastguard Worker repo, commithash = dependencies[directory].split('@', 1) 227*c8dee2aaSAndroid Build Coastguard Worker else: 228*c8dee2aaSAndroid Build Coastguard Worker raise Exception("please specify commit") 229*c8dee2aaSAndroid Build Coastguard Worker if not is_sha1_sum(commithash): 230*c8dee2aaSAndroid Build Coastguard Worker raise Exception("poorly formed commit hash: %r" % commithash) 231*c8dee2aaSAndroid Build Coastguard Worker 232*c8dee2aaSAndroid Build Coastguard Worker relative_directory = os.path.join(deps_file_directory, directory) 233*c8dee2aaSAndroid Build Coastguard Worker 234*c8dee2aaSAndroid Build Coastguard Worker list_of_arg_lists.append( 235*c8dee2aaSAndroid Build Coastguard Worker (git, repo, commithash, relative_directory, shallow, verbose)) 236*c8dee2aaSAndroid Build Coastguard Worker 237*c8dee2aaSAndroid Build Coastguard Worker multithread(git_checkout_to_directory, list_of_arg_lists) 238*c8dee2aaSAndroid Build Coastguard Worker 239*c8dee2aaSAndroid Build Coastguard Worker 240*c8dee2aaSAndroid Build Coastguard Workerdef multithread(function, list_of_arg_lists): 241*c8dee2aaSAndroid Build Coastguard Worker anything_failed = False 242*c8dee2aaSAndroid Build Coastguard Worker threads = [] 243*c8dee2aaSAndroid Build Coastguard Worker def hook(args): 244*c8dee2aaSAndroid Build Coastguard Worker nonlocal anything_failed 245*c8dee2aaSAndroid Build Coastguard Worker anything_failed = True 246*c8dee2aaSAndroid Build Coastguard Worker threading.excepthook = hook 247*c8dee2aaSAndroid Build Coastguard Worker for args in list_of_arg_lists: 248*c8dee2aaSAndroid Build Coastguard Worker thread = threading.Thread(None, function, None, args) 249*c8dee2aaSAndroid Build Coastguard Worker thread.start() 250*c8dee2aaSAndroid Build Coastguard Worker threads.append(thread) 251*c8dee2aaSAndroid Build Coastguard Worker for thread in threads: 252*c8dee2aaSAndroid Build Coastguard Worker thread.join() 253*c8dee2aaSAndroid Build Coastguard Worker if anything_failed: 254*c8dee2aaSAndroid Build Coastguard Worker raise Exception("Thread failure detected") 255*c8dee2aaSAndroid Build Coastguard Worker 256*c8dee2aaSAndroid Build Coastguard Worker 257*c8dee2aaSAndroid Build Coastguard Workerdef main(argv): 258*c8dee2aaSAndroid Build Coastguard Worker deps_file_path = os.environ.get('GIT_SYNC_DEPS_PATH', DEFAULT_DEPS_PATH) 259*c8dee2aaSAndroid Build Coastguard Worker verbose = not bool(os.environ.get('GIT_SYNC_DEPS_QUIET', False)) 260*c8dee2aaSAndroid Build Coastguard Worker skip_emsdk = bool(os.environ.get('GIT_SYNC_DEPS_SKIP_EMSDK', False)) 261*c8dee2aaSAndroid Build Coastguard Worker shallow = not ('--deep' in argv) 262*c8dee2aaSAndroid Build Coastguard Worker 263*c8dee2aaSAndroid Build Coastguard Worker if '--help' in argv or '-h' in argv: 264*c8dee2aaSAndroid Build Coastguard Worker usage(deps_file_path) 265*c8dee2aaSAndroid Build Coastguard Worker return 1 266*c8dee2aaSAndroid Build Coastguard Worker 267*c8dee2aaSAndroid Build Coastguard Worker git_sync_deps(deps_file_path, argv, shallow, verbose) 268*c8dee2aaSAndroid Build Coastguard Worker subprocess.check_call( 269*c8dee2aaSAndroid Build Coastguard Worker [sys.executable, 270*c8dee2aaSAndroid Build Coastguard Worker os.path.join(os.path.dirname(deps_file_path), 'bin', 'fetch-gn')]) 271*c8dee2aaSAndroid Build Coastguard Worker if not skip_emsdk: 272*c8dee2aaSAndroid Build Coastguard Worker subprocess.check_call( 273*c8dee2aaSAndroid Build Coastguard Worker [sys.executable, 274*c8dee2aaSAndroid Build Coastguard Worker os.path.join(os.path.dirname(deps_file_path), 'bin', 'activate-emsdk')]) 275*c8dee2aaSAndroid Build Coastguard Worker return 0 276*c8dee2aaSAndroid Build Coastguard Worker 277*c8dee2aaSAndroid Build Coastguard Worker 278*c8dee2aaSAndroid Build Coastguard Workerif __name__ == '__main__': 279*c8dee2aaSAndroid Build Coastguard Worker exit(main(sys.argv[1:])) 280