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