skia2/tools/git-sync-deps
Ben Wagner 96aa535b78 Remove .bat and .exe from commands where possible.
We expect .BAT and .EXE to be on PATHEXT. Also, although Python
CreateProcess doesn't honor PATHEXT, it will try .EXE. This allows most
commands to be platform-independent, and also resolves the issue with
the cpython package not including python.bat.

No-Tree-Checks: true
Docs-Preview: https://skia.org/?cl=135626
Bug: chromium:852581
Change-Id: Iab4189407df44ff4ad4d37da07ff52414229d397
Reviewed-on: https://skia-review.googlesource.com/135626
Commit-Queue: Ben Wagner <benjaminwagner@google.com>
Auto-Submit: Ben Wagner <benjaminwagner@google.com>
Reviewed-by: Eric Boren <borenet@google.com>
Reviewed-by: Mike Klein <mtklein@google.com>
2018-06-19 17:29:15 +00:00

254 lines
7.4 KiB
Python
Executable File

#!/usr/bin/env python
# Copyright 2014 Google Inc.
#
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Parse a DEPS file and git checkout all of the dependencies.
Args:
An optional list of deps_os values.
Environment Variables:
GIT_EXECUTABLE: path to "git" binary; if unset, will look for git in
your default path.
GIT_SYNC_DEPS_PATH: file to get the dependency list from; if unset,
will use the file ../DEPS relative to this script's directory.
GIT_SYNC_DEPS_QUIET: if set to non-empty string, suppress messages.
Git Config:
To disable syncing of a single repository:
cd path/to/repository
git config sync-deps.disable true
To re-enable sync:
cd path/to/repository
git config --unset sync-deps.disable
"""
import os
import subprocess
import sys
import threading
def git_executable():
"""Find the git executable.
Returns:
A string suitable for passing to subprocess functions, or None.
"""
envgit = os.environ.get('GIT_EXECUTABLE')
searchlist = ['git']
if envgit:
searchlist.insert(0, envgit)
with open(os.devnull, 'w') as devnull:
for git in searchlist:
try:
subprocess.call([git, '--version'], stdout=devnull)
except (OSError,):
continue
return git
return None
DEFAULT_DEPS_PATH = os.path.normpath(
os.path.join(os.path.dirname(__file__), os.pardir, 'DEPS'))
def usage(deps_file_path = None):
sys.stderr.write(
'Usage: run to grab dependencies, with optional platform support:\n')
sys.stderr.write(' %s %s' % (sys.executable, __file__))
if deps_file_path:
parsed_deps = parse_file_to_dict(deps_file_path)
if 'deps_os' in parsed_deps:
for deps_os in parsed_deps['deps_os']:
sys.stderr.write(' [%s]' % deps_os)
sys.stderr.write('\n\n')
sys.stderr.write(__doc__)
def git_repository_sync_is_disabled(git, directory):
try:
disable = subprocess.check_output(
[git, 'config', 'sync-deps.disable'], cwd=directory)
return disable.lower().strip() in ['true', '1', 'yes', 'on']
except subprocess.CalledProcessError:
return False
def is_git_toplevel(git, directory):
"""Return true iff the directory is the top level of a Git repository.
Args:
git (string) the git executable
directory (string) the path into which the repository
is expected to be checked out.
"""
try:
toplevel = subprocess.check_output(
[git, 'rev-parse', '--show-toplevel'], cwd=directory).strip()
return os.path.realpath(directory) == os.path.realpath(toplevel)
except subprocess.CalledProcessError:
return False
def status(directory, checkoutable):
def truncate(s, length):
return s if len(s) <= length else s[:(length - 3)] + '...'
dlen = 36
directory = truncate(directory, dlen)
checkoutable = truncate(checkoutable, 40)
sys.stdout.write('%-*s @ %s\n' % (dlen, directory, checkoutable))
def git_checkout_to_directory(git, repo, checkoutable, directory, verbose):
"""Checkout (and clone if needed) a Git repository.
Args:
git (string) the git executable
repo (string) the location of the repository, suitable
for passing to `git clone`.
checkoutable (string) a tag, branch, or commit, suitable for
passing to `git checkout`
directory (string) the path into which the repository
should be checked out.
verbose (boolean)
Raises an exception if any calls to git fail.
"""
if not os.path.isdir(directory):
subprocess.check_call(
[git, 'clone', '--quiet', repo, directory])
if not is_git_toplevel(git, directory):
# if the directory exists, but isn't a git repo, you will modify
# the parent repostory, which isn't what you want.
sys.stdout.write('%s\n IS NOT TOP-LEVEL GIT DIRECTORY.\n' % directory)
return
# Check to see if this repo is disabled. Quick return.
if git_repository_sync_is_disabled(git, directory):
sys.stdout.write('%s\n SYNC IS DISABLED.\n' % directory)
return
with open(os.devnull, 'w') as devnull:
# If this fails, we will fetch before trying again. Don't spam user
# with error infomation.
if 0 == subprocess.call([git, 'checkout', '--quiet', checkoutable],
cwd=directory, stderr=devnull):
# if this succeeds, skip slow `git fetch`.
if verbose:
status(directory, checkoutable) # Success.
return
# If the repo has changed, always force use of the correct repo.
# If origin already points to repo, this is a quick no-op.
subprocess.check_call(
[git, 'remote', 'set-url', 'origin', repo], cwd=directory)
subprocess.check_call([git, 'fetch', '--quiet'], cwd=directory)
subprocess.check_call([git, 'checkout', '--quiet', checkoutable], cwd=directory)
if verbose:
status(directory, checkoutable) # Success.
def parse_file_to_dict(path):
dictionary = {}
execfile(path, dictionary)
return dictionary
def git_sync_deps(deps_file_path, command_line_os_requests, verbose):
"""Grab dependencies, with optional platform support.
Args:
deps_file_path (string) Path to the DEPS file.
command_line_os_requests (list of strings) Can be empty list.
List of strings that should each be a key in the deps_os
dictionary in the DEPS file.
Raises git Exceptions.
"""
git = git_executable()
assert git
deps_file_directory = os.path.dirname(deps_file_path)
deps_file = parse_file_to_dict(deps_file_path)
dependencies = deps_file['deps'].copy()
os_specific_dependencies = deps_file.get('deps_os', dict())
if 'all' in command_line_os_requests:
for value in os_specific_dependencies.itervalues():
dependencies.update(value)
else:
for os_name in command_line_os_requests:
# Add OS-specific dependencies
if os_name in os_specific_dependencies:
dependencies.update(os_specific_dependencies[os_name])
for directory in dependencies:
for other_dir in dependencies:
if directory.startswith(other_dir + '/'):
raise Exception('%r is parent of %r' % (other_dir, directory))
list_of_arg_lists = []
for directory in sorted(dependencies):
if '@' in dependencies[directory]:
repo, checkoutable = dependencies[directory].split('@', 1)
else:
raise Exception("please specify commit or tag")
relative_directory = os.path.join(deps_file_directory, directory)
list_of_arg_lists.append(
(git, repo, checkoutable, relative_directory, verbose))
multithread(git_checkout_to_directory, list_of_arg_lists)
for directory in deps_file.get('recursedeps', []):
recursive_path = os.path.join(deps_file_directory, directory, 'DEPS')
git_sync_deps(recursive_path, command_line_os_requests, verbose)
def multithread(function, list_of_arg_lists):
# for args in list_of_arg_lists:
# function(*args)
# return
threads = []
for args in list_of_arg_lists:
thread = threading.Thread(None, function, None, args)
thread.start()
threads.append(thread)
for thread in threads:
thread.join()
def main(argv):
deps_file_path = os.environ.get('GIT_SYNC_DEPS_PATH', DEFAULT_DEPS_PATH)
verbose = not bool(os.environ.get('GIT_SYNC_DEPS_QUIET', False))
if '--help' in argv or '-h' in argv:
usage(deps_file_path)
return 1
git_sync_deps(deps_file_path, argv, verbose)
subprocess.check_call(
[sys.executable,
os.path.join(os.path.dirname(deps_file_path), 'bin', 'fetch-gn')])
return 0
if __name__ == '__main__':
exit(main(sys.argv[1:]))