chromium의 배치 도구 depottools 및 gclient
gclient 파일은 bash 스크립트입니다.
#########glcient###########
/usr/bin/bash
base_dir=$(dirname "$0")
if [[ "#grep#fetch#cleanup#diff#" != *"#$1#"* ]]; then
"$base_dir"/update_depot_tools
fi
PYTHONDONTWRITEBYTECODE=1 exec python "$base_dir/gclient.py" "$@"
우선, 스크립트의 디렉터리를 가져와base 에 값을 부여합니다dir, 그리고 명령 매개 변수 1이grep|fetch|cleanup|diff인지 판단하고basedir의 updatdepot_tools 스크립트, 이 스크립트는git, svn 도구를 업데이트합니다.마지막으로 현재 스크립트 디렉터리 (depot tools) 의python 스크립트 gclient를 호출합니다.py, 이 스크립트에 매개 변수를 전달합니다.
###############glcient.py#######
#
def Main(argv):
.....
dispatcher = subcommand.CommandDispatcher(__name__)
try:
return dispatcher.execute(OptionParser(), argv)
......
if '__main__' == __name__:
sys.exit(Main(sys.argv[1:]))
gclient에서.py 스크립트가 시작되고 함수Main을 호출합니다. 파라미터는 bash 스크립트에 전달됩니다.Main은 주로 두 가지 과정을 수행한다. 하나는 디스패치 대상을 만들고 파라미터는 현재 모듈이다.그리고 디스패치의 excute 방법을 호출합니다. 매개 변수는 OptionParser 대상이고 매개 변수는 Main에 전달되는 매개 변수입니다.
다음은subcommand 모듈 아래의 클래스CommandDispatcher에 들어가execute 방법을 분석합니다.
################# subcommand.py ###########
class CommandDispatcher(object):
def __init__(self, module):
"""module is the name of the main python module where to look for commands.
The python builtin variable __name__ MUST be used for |module|. If the
script is executed in the form 'python script.py', __name__ == '__main__'
and sys.modules['script'] doesn't exist. On the other hand if it is unit
tested, __main__ will be the unit test's module so it has to reference to
itself with 'script'. __name__ always match the right value.
"""
self.module = sys.modules[module]
def enumerate_commands(self):
"""Returns a dict of command and their handling function.
The commands must be in the '__main__' modules. To import a command from a
submodule, use:
from mysubcommand import CMDfoo
Automatically adds 'help' if not already defined.
A command can be effectively disabled by defining a global variable to None,
e.g.:
CMDhelp = None
"""
cmds = dict(
(fn[3:], getattr(self.module, fn))
for fn in dir(self.module) if fn.startswith('CMD'))
cmds.setdefault('help', CMDhelp)
return cmds
def find_nearest_command(self, name):
"""Retrieves the function to handle a command.
It automatically tries to guess the intended command by handling typos or
incomplete names.
"""
# Implicitly replace foo-bar to foo_bar since foo-bar is not a valid python
# symbol but it's faster to type.
name = name.replace('-', '_')
commands = self.enumerate_commands()
if name in commands:
return commands[name]
# An exact match was not found. Try to be smart and look if there's
# something similar.
commands_with_prefix = [c for c in commands if c.startswith(name)]
if len(commands_with_prefix) == 1:
return commands[commands_with_prefix[0]]
# A #closeenough approximation of levenshtein distance.
def close_enough(a, b):
return difflib.SequenceMatcher(a=a, b=b).ratio()
hamming_commands = sorted(
((close_enough(c, name), c) for c in commands),
reverse=True)
if (hamming_commands[0][0] - hamming_commands[1][0]) < 0.3:
# Too ambiguous.
return
if hamming_commands[0][0] < 0.8:
# Not similar enough. Don't be a fool and run a random command.
return
return commands[hamming_commands[0][1]]
def _gen_commands_list(self):
"""Generates the short list of supported commands."""
commands = self.enumerate_commands()
docs = sorted(
(name, self._create_command_summary(name, handler))
for name, handler in commands.iteritems())
# Skip commands without a docstring.
docs = [i for i in docs if i[1]]
# Then calculate maximum length for alignment:
length = max(len(c) for c in commands)
# Look if color is supported.
colors = _get_color_module()
green = reset = ''
if colors:
green = colors.Fore.GREEN
reset = colors.Fore.RESET
return (
'Commands are:
' +
''.join(
' %s%-*s%s %s
' % (green, length, name, reset, doc)
for name, doc in docs))
def _add_command_usage(self, parser, command):
"""Modifies an OptionParser object with the function's documentation."""
name = command.__name__[3:]
if name == 'help':
name = ''
# Use the module's docstring as the description for the 'help' command if
# available.
parser.description = (self.module.__doc__ or '').rstrip()
if parser.description:
parser.description += '
'
parser.description += self._gen_commands_list()
# Do not touch epilog.
else:
# Use the command's docstring if available. For commands, unlike module
# docstring, realign.
lines = (command.__doc__ or '').rstrip().splitlines()
if lines[:1]:
rest = textwrap.dedent('
'.join(lines[1:]))
parser.description = '
'.join((lines[0], rest))
else:
parser.description = lines[0]
if parser.description:
parser.description += '
'
parser.epilog = getattr(command, 'epilog', None)
if parser.epilog:
parser.epilog = '
' + parser.epilog.strip() + '
'
more = getattr(command, 'usage_more', '')
parser.set_usage(
'usage: %%prog %s [options]%s' % (name, '' if not more else ' ' + more))
@staticmethod
def _create_command_summary(name, command):
"""Creates a oneline summary from the command's docstring."""
if name != command.__name__[3:]:
# Skip aliases.
return ''
doc = command.__doc__ or ''
line = doc.split('
', 1)[0].rstrip('.')
if not line:
return line
return (line[0].lower() + line[1:]).strip()
def execute(self, parser, args):
"""Dispatches execution to the right command.
Fallbacks to 'help' if not disabled.
"""
# Unconditionally disable format_description() and format_epilog().
# Technically, a formatter should be used but it's not worth (yet) the
# trouble.
parser.format_description = lambda _: parser.description or ''
parser.format_epilog = lambda _: parser.epilog or ''
if args:
if args[0] in ('-h', '--help') and len(args) > 1:
# Inverse the argument order so 'tool --help cmd' is rewritten to
# 'tool cmd --help'.
args = [args[1], args[0]] + args[2:]
command = self.find_nearest_command(args[0])
if command:
if command.__name__ == 'CMDhelp' and len(args) > 1:
# Inverse the arguments order so 'tool help cmd' is rewritten to
# 'tool cmd --help'. Do it here since we want 'tool hel cmd' to work
# too.
args = [args[1], '--help'] + args[2:]
command = self.find_nearest_command(args[0]) or command
# "fix" the usage and the description now that we know the subcommand.
self._add_command_usage(parser, command)
return command(parser, args[1:])
cmdhelp = self.enumerate_commands().get('help')
if cmdhelp:
# Not a known command. Default to help.
self._add_command_usage(parser, cmdhelp)
return cmdhelp(parser, args)
# Nothing can be done.
return 2
gcient.py에서 dispatcher = subcomamnd.CommandDispatcher(__name__),전송된 파라미터가 gclient임을 알 수 있습니다.py 이 모듈.CommandDispatcher의init__중,self.module는 gclent입니다.그걸 알면 뒤에 있는 엔umrate에 대해commands 함수는 유용합니다.그 이름에서 알 수 있듯이 지원하는 모든 명령을 일일이 열거하고, 되돌아오는 결과는 dict이며, 키는 명령 이름이고, 값은 대응하는 처리 함수이다. 예를 들어 {"sync": CMDsync}.기타 함수add_command_usage, _create_command_summary, _gen_commands_list는 보조 함수로 기능도 비교적 명확하다.비교적 복잡한 것은findnearest_command, 이것은enumerate 에서commands에서 생성한 dict 사전에서 명령을 찾습니다. 정확하게 일치하는 명령이 없으면 모호한 검색을 통해 명령에 대응하는 처리 함수를 되돌려줍니다.
############# gclient.py ##################
class OptionParser(optparse.OptionParser):
gclientfile_default = os.environ.get('GCLIENT_FILE', '.gclient')
def __init__(self, **kwargs):
optparse.OptionParser.__init__(
self, version='%prog ' + __version__, **kwargs)
# Some arm boards have issues with parallel sync.
if platform.machine().startswith('arm'):
jobs = 1
else:
jobs = max(8, gclient_utils.NumLocalCpus())
# cmp: 2013/06/19
# Temporary workaround to lower bot-load on SVN server.
# Bypassed if a bot_update flag is detected.
if (os.environ.get('CHROME_HEADLESS') == '1' and
not os.path.exists('update.flag')):
jobs = 1
self.add_option(
'-j', '--jobs', default=jobs, type='int',
help='Specify how many SCM commands can run in parallel; defaults to '
'%default on this machine')
self.add_option(
'-v', '--verbose', action='count', default=0,
help='Produces additional output for diagnostics. Can be used up to '
'three times for more logging info.')
self.add_option(
'--gclientfile', dest='config_filename',
help='Specify an alternate %s file' % self.gclientfile_default)
self.add_option(
'--spec',
help='create a gclient file containing the provided string. Due to '
'Cygwin/Python brokenness, it can\'t contain any newlines.')
self.add_option(
'--no-nag-max', default=False, action='store_true',
help='Ignored for backwards compatibility.')
OptionParser 매개변수는 gclient입니다.py의 한 종류로 서브 모듈의optparser를 계승합니다.OptionParser.주요 기능은 파라미터를 해석하고tuple(options,args)을 되돌려 주는 것이다. OptionParser라는 예에 대해 명확하게 설명한다.반환 값은 입력 매개 변수가 포맷 분석된 결과입니다.이 함수 처리는 두 가지 측면으로 나뉘는데, 하나는 조회를 돕는 것이고, 하나는 명령을 실행하는 것이다.도움말 질의에서 사용자가 입력한 명령은 gclient --help sync 등입니다.사실,execute에 전달될 때 매개 변수는 --help sync로 변한다. 그러면 먼저 매개 변수의 위치를 바꾸어서sync--help, 즉args[0]를 "sync",args[1]을 --help로 하고 self를 호출한다.fine_nearest_command 함수, 정확한 명령이나 가장 비슷한 모호한 일치하는 명령을 찾고, 결과 (명령 처리 함수는 CMD로 시작) 를 command 변수에 부여합니다.
execute()에 들어가기, 매개변수 해석,findnearest_commands () 명령을 받은 후 도움말 명령인지 판단하고, 그렇지 않으면 대응하는 처리 함수인returncommand (parser,args[1:]) 를 직접 호출합니다.위 예제에서는 CMDsync 함수를 호출하고 매개변수는 parser 및 sync 이후의 매개변수입니다.
CMDsync가 입력 매개변수를 어떻게 처리하는지 확인합니다.
#############gclient.py#############
def CMDsync(parser, args):
"""Checkout/update all modules."""
parser.add_option('-f', '--force', action='store_true',
help='force update even for unchanged modules')
parser.add_option('-n', '--nohooks', action='store_true',
help='don\'t run hooks after the update is complete')
parser.add_option('-p', '--noprehooks', action='store_true',
help='don\'t run pre-DEPS hooks', default=False)
parser.add_option('-r', '--revision', action='append',
dest='revisions', metavar='REV', default=[],
help='Enforces revision/hash for the solutions with the '
'format src@rev. The src@ part is optional and can be '
'skipped. -r can be used multiple times when .gclient '
'has multiple solutions configured and will work even '
'if the src@ part is skipped. Note that specifying '
'--revision means your safesync_url gets ignored.')
parser.add_option('--with_branch_heads', action='store_true',
help='Clone git "branch_heads" refspecs in addition to '
'the default refspecs. This adds about 1/2GB to a '
'full checkout. (git only)')
parser.add_option('-t', '--transitive', action='store_true',
help='When a revision is specified (in the DEPS file or '
'with the command-line flag), transitively update '
'the dependencies to the date of the given revision. '
'Only supported for SVN repositories.')
parser.add_option('-H', '--head', action='store_true',
help='skips any safesync_urls specified in '
'configured solutions and sync to head instead')
parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
help='Deletes from the working copy any dependencies that '
'have been removed since the last sync, as long as '
'there are no local modifications. When used with '
'--force, such dependencies are removed even if they '
'have local modifications. When used with --reset, '
'all untracked directories are removed from the '
'working copy, excluding those which are explicitly '
'ignored in the repository.')
parser.add_option('-R', '--reset', action='store_true',
help='resets any local changes before updating (git only)')
parser.add_option('-M', '--merge', action='store_true',
help='merge upstream changes instead of trying to '
'fast-forward or rebase')
parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
help='override deps for the specified (comma-separated) '
'platform(s); \'all\' will process all deps_os '
'references')
parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
help='Skip svn up whenever possible by requesting '
'actual HEAD revision from the repository')
parser.add_option('--upstream', action='store_true',
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)
client = GClient.LoadCurrentConfig(options)
if not client:
raise gclient_utils.Error('client not configured; see \'gclient config\'')
if options.revisions and options.head:
# TODO(maruel): Make it a parser.error if it doesn't break any builder.
print('Warning: you cannot use both --head and --revision')
if options.verbose:
# 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.
print(client.config_content)
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
options, args = parser.parse_args(args), CMDsync 명령 매개변수를 확인한 다음 Gclient를 호출합니다.LoadCurrentConfig(options) - 쉽게 볼 때 gclient sync를 입력하면 options와args가 비어 있습니다.
############glcient.py#############
def LoadCurrentConfig(options):
"""Searches for and loads a .gclient file relative to the current working
dir. Returns a GClient object."""
if options.spec:
client = GClient('.', options)
client.SetConfig(options.spec)
else:
path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
if not path:
return None
client = GClient(path, options)
client.SetConfig(gclient_utils.FileRead(
os.path.join(path, options.config_filename)))
if (options.revisions and
len(client.dependencies) > 1 and
any('@' not in r for r in options.revisions)):
print >> sys.stderr, (
'You must specify the full solution name like --revision %s@%s
'
'when you have multiple solutions setup in your .gclient file.
'
'Other solutions present are: %s.') % (
client.dependencies[0].name,
options.revisions[0],
', '.join(s.name for s in client.dependencies[1:]))
return client
이 함수는 현재 디렉터리에 조회됩니다.glcient 파일, 최종적으로client 대상을 되돌려줍니다.이 함수는 GClient(path, options)에 호출됩니다.
############## gclient.py ##################
class GClient(Dependency):
"""Object that represent a gclient checkout. A tree of Dependency(), one per
solution or DEPS entry."""
DEPS_OS_CHOICES = {
"win32": "win",
"win": "win",
"cygwin": "win",
"darwin": "mac",
"mac": "mac",
"unix": "unix",
"linux": "unix",
"linux2": "unix",
"linux3": "unix",
"android": "android",
}
DEFAULT_CLIENT_FILE_TEXT = ("""\
solutions = [
{ "name" : "%(solution_name)s",
"url" : "%(solution_url)s",
"deps_file" : "%(deps_file)s",
"managed" : %(managed)s,
"custom_deps" : {
},
"safesync_url": "%(safesync_url)s",
},
]
cache_dir = %(cache_dir)r
""")
DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
{ "name" : "%(solution_name)s",
"url" : "%(solution_url)s",
"deps_file" : "%(deps_file)s",
"managed" : %(managed)s,
"custom_deps" : {
%(solution_deps)s },
"safesync_url": "%(safesync_url)s",
},
""")
DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
# Snapshot generated with gclient revinfo --snapshot
solutions = [
%(solution_list)s]
""")
def __init__(self, root_dir, options):
# Do not change previous behavior. Only solution level and immediate DEPS
# are processed.
self._recursion_limit = 2
Dependency.__init__(self, None, None, None, None, True, None, None, None,
'unused', True)
self._options = options
if options.deps_os:
enforced_os = options.deps_os.split(',')
else:
enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
if 'all' in enforced_os:
enforced_os = self.DEPS_OS_CHOICES.itervalues()
self._enforced_os = tuple(set(enforced_os))
self._root_dir = root_dir
self.config_content = None
def SetConfig(self, content):
assert not self.dependencies
config_dict = {}
self.config_content = content
try:
exec(content, config_dict)
except SyntaxError, e:
gclient_utils.SyntaxErrorToError('.gclient', e)
# Append any target OS that is not already being enforced to the tuple.
target_os = config_dict.get('target_os', [])
if config_dict.get('target_os_only', False):
self._enforced_os = tuple(set(target_os))
else:
self._enforced_os = tuple(set(self._enforced_os).union(target_os))
gclient_scm.GitWrapper.cache_dir = config_dict.get('cache_dir')
if not target_os and config_dict.get('target_os_only', False):
raise gclient_utils.Error('Can\'t use target_os_only if target_os is '
'not specified')
deps_to_add = []
for s in config_dict.get('solutions', []):
try:
deps_to_add.append(Dependency(
self, s['name'], s['url'],
s.get('safesync_url', None),
s.get('managed', True),
s.get('custom_deps', {}),
s.get('custom_vars', {}),
s.get('custom_hooks', []),
s.get('deps_file', 'DEPS'),
True))
except KeyError:
raise gclient_utils.Error('Invalid .gclient file. Solution is '
'incomplete: %s' % s)
self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
logging.info('SetConfig() done')
def SaveConfig(self):
gclient_utils.FileWrite(os.path.join(self.root_dir,
self._options.config_filename),
self.config_content)
def RunOnDeps(self, command, args, ignore_requirements=False, progress=True):
"""Runs a command on each dependency in a client and its dependencies.
Args:
command: The command to use (e.g., 'status' or 'diff')
args: list of str - extra arguments to add to the command line.
"""
if not self.dependencies:
raise gclient_utils.Error('No solution specified')
revision_overrides = {}
# It's unnecessary to check for revision overrides for 'recurse'.
# Save a few seconds by not calling _EnforceRevisions() in that case.
if command not in ('diff', 'recurse', 'runhooks', 'status'):
revision_overrides = self._EnforceRevisions()
pm = None
# Disable progress for non-tty stdout.
if (sys.stdout.isatty() and not self._options.verbose and progress):
if command in ('update', 'revert'):
pm = Progress('Syncing projects', 1)
elif command == 'recurse':
pm = Progress(' '.join(args), 1)
work_queue = gclient_utils.ExecutionQueue(
self._options.jobs, pm, ignore_requirements=ignore_requirements)
for s in self.dependencies:
work_queue.enqueue(s)
work_queue.flush(revision_overrides, command, args, options=self._options)
# Once all the dependencies have been processed, it's now safe to run the
# hooks.
if not self._options.nohooks:
self.RunHooksRecursively(self._options)
if command == 'update':
# Notify the user if there is an orphaned entry in their working copy.
# Only delete the directory if there are no changes in it, and
# delete_unversioned_trees is set to true.
entries = [i.name for i in self.root.subtree(False) if i.url]
full_entries = [os.path.join(self.root_dir, e.replace('/', os.path.sep))
for e in entries]
for entry, prev_url in self._ReadEntries().iteritems():
if not prev_url:
# entry must have been overridden via .gclient custom_deps
continue
# Fix path separator on Windows.
entry_fixed = entry.replace('/', os.path.sep)
e_dir = os.path.join(self.root_dir, entry_fixed)
def _IsParentOfAny(parent, path_list):
parent_plus_slash = parent + '/'
return any(
path[:len(parent_plus_slash)] == parent_plus_slash
for path in path_list)
# Use entry and not entry_fixed there.
if (entry not in entries and
(not any(path.startswith(entry + '/') for path in entries)) and
os.path.exists(e_dir)):
scm = gclient_scm.CreateSCM(prev_url, self.root_dir, entry_fixed)
# Check to see if this directory is now part of a higher-up checkout.
if scm.GetCheckoutRoot() in full_entries:
logging.info('%s is part of a higher level checkout, not '
'removing.', scm.GetCheckoutRoot())
continue
file_list = []
scm.status(self._options, [], file_list)
modified_files = file_list != []
if (not self._options.delete_unversioned_trees or
(modified_files and not self._options.force)):
# There are modified files in this entry. Keep warning until
# removed.
print(('
WARNING: \'%s\' is no longer part of this client. '
'It is recommended that you manually remove it.
') %
entry_fixed)
else:
# Delete the entry
print('
________ deleting \'%s\' in \'%s\'' % (
entry_fixed, self.root_dir))
gclient_utils.rmtree(e_dir)
# record the current list of entries for next time
self._SaveEntries()
return 0
client.SetConfig () 설정 파일의 Solutions를 읽고 dependencies의list 변수 depsto_dd, 그리고 호출.add_dependencies_and_close, 매개 변수는 deps 를 포함합니다to_add.adddependencies_and_close 함수에서 deps를 검증합니다. 유효하면 의존 트리에 추가합니다.
CMDsync 함수에서 client를 실행합니다.RunOnDeps().이것은 dependengcies의 모든 작업을 gclient 에 추가합니다tuils.ExecutionQueue 대기열을 실행하고모든 작업을 완료한 후 runhook을 실행합니다.
######## src/DEPS #############
hooks = [
{
# A change to a .gyp, .gypi, or to GYP itself should run the generator.
"pattern": ".",
"action": ["python", "src/build/gyp_chromium"],
},
]
위쪽은 src 아래의 DEPS에서 hooks에 관한 부분입니다. 즉,sync 명령을 실행한 후 파일 업데이트는 src/build/gypchromium 파일.
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
다양한 언어의 JSONJSON은 Javascript 표기법을 사용하여 데이터 구조를 레이아웃하는 데이터 형식입니다. 그러나 Javascript가 코드에서 이러한 구조를 나타낼 수 있는 유일한 언어는 아닙니다. 저는 일반적으로 '객체'{}...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.