Home | History | Annotate | Download | only in tools
      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 
     10 Args:
     11   An optional list of deps_os values.
     12 
     13 Environment 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 
     22 Git 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 
     33 import os
     34 import subprocess
     35 import sys
     36 import threading
     37 
     38 from git_utils import git_executable
     39 
     40 
     41 DEFAULT_DEPS_PATH = os.path.normpath(
     42   os.path.join(os.path.dirname(__file__), os.pardir, 'DEPS'))
     43 
     44 
     45 def 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 
     58 def 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 
     67 def 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 
     84 def 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 
    127 def parse_file_to_dict(path):
    128   dictionary = {}
    129   execfile(path, dictionary)
    130   return dictionary
    131 
    132 
    133 class DepsError(Exception):
    134   """Raised if deps_os is a bad key.
    135   """
    136   pass
    137 
    138 
    139 def 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 
    180 def 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 
    193 def 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 
    204 if __name__ == '__main__':
    205   exit(main(sys.argv[1:]))
    206