Add json output for gclient.

This is in order to support 'features' like got_revision for build systems
unwilling to scrape the human-readable output of gclient in order to extract
basic information :)

R=agable@chromium.org, maruel@chromium.org
BUG=265618

Review URL: https://chromiumcodereview.appspot.com/23753008

git-svn-id: svn://svn.chromium.org/chrome/trunk/tools/depot_tools@225046 0039d316-1c4b-4281-b951-d872f2087c98
experimental/szager/collated-output
iannucci@chromium.org 12 years ago
parent c7a9efad97
commit 2702bcdec7

@ -73,6 +73,7 @@
__version__ = '0.7' __version__ = '0.7'
import copy import copy
import json
import logging import logging
import optparse import optparse
import os import os
@ -301,6 +302,9 @@ class Dependency(gclient_utils.WorkItem, DependencySettings):
# This is the scm used to checkout self.url. It may be used by dependencies # This is the scm used to checkout self.url. It may be used by dependencies
# to get the datetime of the revision we checked out. # to get the datetime of the revision we checked out.
self._used_scm = None self._used_scm = None
# The actual revision we ended up getting, or None if that information is
# unavailable
self._got_revision = None
if not self.name and self.parent: if not self.name and self.parent:
raise gclient_utils.Error('Dependency without name') raise gclient_utils.Error('Dependency without name')
@ -624,7 +628,8 @@ class Dependency(gclient_utils.WorkItem, DependencySettings):
command, options, parsed_url, self.parent.name, revision_overrides) command, options, parsed_url, self.parent.name, revision_overrides)
self._used_scm = gclient_scm.CreateSCM( self._used_scm = gclient_scm.CreateSCM(
parsed_url, self.root.root_dir, self.name) parsed_url, self.root.root_dir, self.name)
self._used_scm.RunCommand(command, options, args, file_list) self._got_revision = self._used_scm.RunCommand(command, options, args,
file_list)
if file_list: if file_list:
file_list = [os.path.join(self.name, f.strip()) for f in file_list] file_list = [os.path.join(self.name, f.strip()) for f in file_list]
@ -854,6 +859,11 @@ class Dependency(gclient_utils.WorkItem, DependencySettings):
"""SCMWrapper instance for this dependency or None if not processed yet.""" """SCMWrapper instance for this dependency or None if not processed yet."""
return self._used_scm return self._used_scm
@property
@gclient_utils.lockedmethod
def got_revision(self):
return self._got_revision
@property @property
def file_list_and_children(self): def file_list_and_children(self):
result = list(self.file_list) result = list(self.file_list)
@ -1523,6 +1533,21 @@ def CMDstatus(parser, args):
all modules (useful for recovering files deleted from local copy) all modules (useful for recovering files deleted from local copy)
gclient sync --revision src@31000 gclient sync --revision src@31000
update src directory to r31000 update src directory to r31000
JSON output format:
If the --output-json option is specified, the following document structure will
be emitted to the provided file. 'null' entries may occur for subprojects which
are present in the gclient solution, but were not processed (due to custom_deps,
os_deps, etc.)
{
"solutions" : {
"<name>": { # <name> is the posix-normalized path to the solution.
"revision": [<svn rev int>|<git id hex string>|null],
"scm": ["svn"|"git"|null],
}
}
}
""") """)
def CMDsync(parser, args): def CMDsync(parser, args):
"""Checkout/update all modules.""" """Checkout/update all modules."""
@ -1573,6 +1598,9 @@ def CMDsync(parser, args):
'actual HEAD revision from the repository') 'actual HEAD revision from the repository')
parser.add_option('--upstream', action='store_true', parser.add_option('--upstream', action='store_true',
help='Make repo state match upstream branch.') help='Make repo state match upstream branch.')
parser.add_option('--output-json',
help='Output a json document to this path containing '
'summary information about the sync.')
(options, args) = parser.parse_args(args) (options, args) = parser.parse_args(args)
client = GClient.LoadCurrentConfig(options) client = GClient.LoadCurrentConfig(options)
@ -1587,7 +1615,18 @@ def CMDsync(parser, args):
# Print out the .gclient file. This is longer than if we just printed the # Print out the .gclient file. This is longer than if we just printed the
# client dict, but more legible, and it might contain helpful comments. # client dict, but more legible, and it might contain helpful comments.
print(client.config_content) print(client.config_content)
return client.RunOnDeps('update', args) ret = client.RunOnDeps('update', args)
if options.output_json:
slns = {}
for d in client.subtree(True):
normed = d.name.replace('\\', '/').rstrip('/') + '/'
slns[normed] = {
'revision': d.got_revision,
'scm': d.used_scm.name if d.used_scm else None,
}
with open(options.output_json, 'wb') as f:
json.dump({'solutions': slns}, f)
return ret
CMDupdate = CMDsync CMDupdate = CMDsync

@ -190,6 +190,7 @@ class GitFilter(object):
class GitWrapper(SCMWrapper): class GitWrapper(SCMWrapper):
"""Wrapper for Git""" """Wrapper for Git"""
name = 'git'
cache_dir = None cache_dir = None
# If a given cache is used in a solution more than once, prevent multiple # If a given cache is used in a solution more than once, prevent multiple
@ -363,13 +364,13 @@ class GitWrapper(SCMWrapper):
# Make the output a little prettier. It's nice to have some whitespace # Make the output a little prettier. It's nice to have some whitespace
# between projects when cloning. # between projects when cloning.
print('') print('')
return return self._Capture(['rev-parse', '--verify', 'HEAD'])
if not managed: if not managed:
self._UpdateBranchHeads(options, fetch=False) self._UpdateBranchHeads(options, fetch=False)
self.UpdateSubmoduleConfig() self.UpdateSubmoduleConfig()
print ('________ unmanaged solution; skipping %s' % self.relpath) print ('________ unmanaged solution; skipping %s' % self.relpath)
return return self._Capture(['rev-parse', '--verify', 'HEAD'])
if not os.path.exists(os.path.join(self.checkout_path, '.git')): if not os.path.exists(os.path.join(self.checkout_path, '.git')):
raise gclient_utils.Error('\n____ %s%s\n' raise gclient_utils.Error('\n____ %s%s\n'
@ -406,7 +407,7 @@ class GitWrapper(SCMWrapper):
self._PossiblySwitchCache(url, options) self._PossiblySwitchCache(url, options)
if return_early: if return_early:
return return self._Capture(['rev-parse', '--verify', 'HEAD'])
cur_branch = self._GetCurrentBranch() cur_branch = self._GetCurrentBranch()
@ -595,6 +596,8 @@ class GitWrapper(SCMWrapper):
print('\n_____ removing unversioned directory %s' % path) print('\n_____ removing unversioned directory %s' % path)
gclient_utils.rmtree(full_path) gclient_utils.rmtree(full_path)
return self._Capture(['rev-parse', '--verify', 'HEAD'])
def revert(self, options, _args, file_list): def revert(self, options, _args, file_list):
"""Reverts local modifications. """Reverts local modifications.
@ -1088,6 +1091,7 @@ class GitWrapper(SCMWrapper):
class SVNWrapper(SCMWrapper): class SVNWrapper(SCMWrapper):
""" Wrapper for SVN """ """ Wrapper for SVN """
name = 'svn'
@staticmethod @staticmethod
def BinaryExists(): def BinaryExists():
@ -1202,11 +1206,11 @@ class SVNWrapper(SCMWrapper):
command = ['checkout', url, self.checkout_path] command = ['checkout', url, self.checkout_path]
command = self._AddAdditionalUpdateFlags(command, options, revision) command = self._AddAdditionalUpdateFlags(command, options, revision)
self._RunAndGetFileList(command, options, file_list, self._root_dir) self._RunAndGetFileList(command, options, file_list, self._root_dir)
return return self.Svnversion()
if not managed: if not managed:
print ('________ unmanaged solution; skipping %s' % self.relpath) print ('________ unmanaged solution; skipping %s' % self.relpath)
return return self.Svnversion()
if 'URL' not in from_info: if 'URL' not in from_info:
raise gclient_utils.Error( raise gclient_utils.Error(
@ -1294,7 +1298,7 @@ class SVNWrapper(SCMWrapper):
command = ['checkout', url, self.checkout_path] command = ['checkout', url, self.checkout_path]
command = self._AddAdditionalUpdateFlags(command, options, revision) command = self._AddAdditionalUpdateFlags(command, options, revision)
self._RunAndGetFileList(command, options, file_list, self._root_dir) self._RunAndGetFileList(command, options, file_list, self._root_dir)
return return self.Svnversion()
# If the provided url has a revision number that matches the revision # If the provided url has a revision number that matches the revision
# number of the existing directory, then we don't need to bother updating. # number of the existing directory, then we don't need to bother updating.
@ -1316,6 +1320,7 @@ class SVNWrapper(SCMWrapper):
and not os.path.islink(full_path)): and not os.path.islink(full_path)):
print('\n_____ removing unversioned directory %s' % status[1]) print('\n_____ removing unversioned directory %s' % status[1])
gclient_utils.rmtree(full_path) gclient_utils.rmtree(full_path)
return self.Svnversion()
def updatesingle(self, options, args, file_list): def updatesingle(self, options, args, file_list):
filename = args.pop() filename = args.pop()
@ -1442,6 +1447,11 @@ class SVNWrapper(SCMWrapper):
gclient_utils.CheckCallAndFilterAndHeader(['svn'] + args, gclient_utils.CheckCallAndFilterAndHeader(['svn'] + args,
always=options.verbose, **kwargs) always=options.verbose, **kwargs)
def Svnversion(self):
"""Runs the lowest checked out revision in the current project."""
info = scm.SVN.CaptureLocalInfo([], os.path.join(self.checkout_path, '.'))
return info['Revision']
def _RunAndGetFileList(self, args, options, file_list, cwd=None): def _RunAndGetFileList(self, args, options, file_list, cwd=None):
"""Runs a commands that goes to stdout and grabs the file listed.""" """Runs a commands that goes to stdout and grabs the file listed."""
cwd = cwd or self.checkout_path cwd = cwd or self.checkout_path

@ -104,9 +104,11 @@ class SVNWrapperTestCase(BaseTestCase):
'GetCheckoutRoot', 'GetCheckoutRoot',
'GetRevisionDate', 'GetRevisionDate',
'GetUsableRev', 'GetUsableRev',
'Svnversion',
'RunCommand', 'RunCommand',
'cleanup', 'cleanup',
'diff', 'diff',
'name',
'nag_max', 'nag_max',
'nag_timer', 'nag_timer',
'pack', 'pack',
@ -198,6 +200,9 @@ class SVNWrapperTestCase(BaseTestCase):
cwd=self.root_dir, cwd=self.root_dir,
file_list=files_list) file_list=files_list)
gclient_scm.scm.SVN._CaptureInfo([], self.base_path+'/.'
).AndReturn({'Revision': 100})
self.mox.ReplayAll() self.mox.ReplayAll()
scm = self._scm_wrapper(url=self.url, root_dir=self.root_dir, scm = self._scm_wrapper(url=self.url, root_dir=self.root_dir,
relpath=self.relpath) relpath=self.relpath)
@ -228,6 +233,8 @@ class SVNWrapperTestCase(BaseTestCase):
cwd=self.root_dir, cwd=self.root_dir,
file_list=files_list) file_list=files_list)
gclient_scm.gclient_utils.rmtree(self.base_path) gclient_scm.gclient_utils.rmtree(self.base_path)
gclient_scm.scm.SVN._CaptureInfo([], self.base_path+'/.'
).AndReturn({'Revision': 100})
self.mox.ReplayAll() self.mox.ReplayAll()
scm = self._scm_wrapper(url=self.url, root_dir=self.root_dir, scm = self._scm_wrapper(url=self.url, root_dir=self.root_dir,
relpath=self.relpath) relpath=self.relpath)
@ -352,6 +359,8 @@ class SVNWrapperTestCase(BaseTestCase):
['checkout', self.url, self.base_path, '--force', '--ignore-externals'], ['checkout', self.url, self.base_path, '--force', '--ignore-externals'],
cwd=self.root_dir, cwd=self.root_dir,
file_list=files_list) file_list=files_list)
gclient_scm.scm.SVN._CaptureInfo([], self.base_path+'/.'
).AndReturn({'Revision': 100})
self.mox.ReplayAll() self.mox.ReplayAll()
scm = self._scm_wrapper(url=self.url, root_dir=self.root_dir, scm = self._scm_wrapper(url=self.url, root_dir=self.root_dir,
relpath=self.relpath) relpath=self.relpath)
@ -386,6 +395,9 @@ class SVNWrapperTestCase(BaseTestCase):
gclient_scm.scm.SVN.Capture(['--version', '--quiet'], None gclient_scm.scm.SVN.Capture(['--version', '--quiet'], None
).AndReturn('1.5.1') ).AndReturn('1.5.1')
gclient_scm.scm.SVN._CaptureInfo([], self.base_path+'/.'
).AndReturn({'Revision': 100})
additional_args = [] additional_args = []
if options.manually_grab_svn_rev: if options.manually_grab_svn_rev:
additional_args = ['--revision', str(file_info['Revision'])] additional_args = ['--revision', str(file_info['Revision'])]
@ -425,6 +437,9 @@ class SVNWrapperTestCase(BaseTestCase):
gclient_scm.scm.SVN._CaptureInfo([file_info['URL']], None gclient_scm.scm.SVN._CaptureInfo([file_info['URL']], None
).AndReturn(file_info) ).AndReturn(file_info)
gclient_scm.scm.SVN._CaptureInfo([], self.base_path+'/.'
).AndReturn({'Revision': 100})
self.mox.ReplayAll() self.mox.ReplayAll()
files_list = [] files_list = []
scm = self._scm_wrapper(url=self.url, root_dir=self.root_dir, scm = self._scm_wrapper(url=self.url, root_dir=self.root_dir,
@ -466,6 +481,9 @@ class SVNWrapperTestCase(BaseTestCase):
gclient_scm.os.path.islink(join(self.base_path, 'dir')).AndReturn(False) gclient_scm.os.path.islink(join(self.base_path, 'dir')).AndReturn(False)
gclient_scm.gclient_utils.rmtree(join(self.base_path, 'dir')) gclient_scm.gclient_utils.rmtree(join(self.base_path, 'dir'))
gclient_scm.scm.SVN._CaptureInfo([], self.base_path+'/.'
).AndReturn({'Revision': 100})
self.mox.ReplayAll() self.mox.ReplayAll()
scm = self._scm_wrapper(url=self.url, root_dir=self.root_dir, scm = self._scm_wrapper(url=self.url, root_dir=self.root_dir,
relpath=self.relpath) relpath=self.relpath)
@ -515,6 +533,9 @@ class SVNWrapperTestCase(BaseTestCase):
gclient_scm.scm.SVN._CaptureInfo([file_info['URL']], None gclient_scm.scm.SVN._CaptureInfo([file_info['URL']], None
).AndReturn(file_info) ).AndReturn(file_info)
gclient_scm.scm.SVN._CaptureInfo([], self.base_path+'/.'
).AndReturn({'Revision': 100})
self.mox.ReplayAll() self.mox.ReplayAll()
scm = self._scm_wrapper(url=self.url, root_dir=self.root_dir, scm = self._scm_wrapper(url=self.url, root_dir=self.root_dir,
relpath=self.relpath) relpath=self.relpath)
@ -585,6 +606,9 @@ class SVNWrapperTestCase(BaseTestCase):
gclient_scm.scm.SVN._CaptureInfo([file_info['URL']], None gclient_scm.scm.SVN._CaptureInfo([file_info['URL']], None
).AndReturn(file_info) ).AndReturn(file_info)
gclient_scm.scm.SVN._CaptureInfo([], self.base_path+'/.'
).AndReturn({'Revision': 100})
self.mox.ReplayAll() self.mox.ReplayAll()
scm = self._scm_wrapper(url=self.url, root_dir=self.root_dir, scm = self._scm_wrapper(url=self.url, root_dir=self.root_dir,
relpath=self.relpath) relpath=self.relpath)
@ -618,6 +642,9 @@ class SVNWrapperTestCase(BaseTestCase):
gclient_scm.scm.SVN._CaptureInfo([file_info['URL']], None gclient_scm.scm.SVN._CaptureInfo([file_info['URL']], None
).AndReturn(file_info) ).AndReturn(file_info)
gclient_scm.scm.SVN._CaptureInfo([], self.base_path+'/.'
).AndReturn({'Revision': 100})
self.mox.ReplayAll() self.mox.ReplayAll()
scm = self._scm_wrapper(url=self.url, root_dir=self.root_dir, scm = self._scm_wrapper(url=self.url, root_dir=self.root_dir,
relpath=self.relpath) relpath=self.relpath)
@ -799,6 +826,7 @@ class ManagedGitWrapperTestCase(BaseGitWrapperTestCase):
'cache_locks', 'cache_locks',
'cleanup', 'cleanup',
'diff', 'diff',
'name',
'nag_max', 'nag_max',
'nag_timer', 'nag_timer',
'pack', 'pack',

Loading…
Cancel
Save