blob: 9cb14a3c919b7bf31440d11629486090f99557fa [file] [log] [blame]
Kuang-che Wu6e4beca2018-06-27 17:45:02 +08001# -*- coding: utf-8 -*-
Kuang-che Wu3eb6b502018-06-06 16:15:18 +08002# Copyright 2018 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5"""Gclient utility."""
6
7from __future__ import print_function
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +08008import collections
9import itertools
Kuang-che Wu3eb6b502018-06-06 16:15:18 +080010import logging
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +080011import operator
Kuang-che Wu3eb6b502018-06-06 16:15:18 +080012import os
Kuang-che Wu6948ecc2018-09-11 17:43:49 +080013import pprint
Zheng-Jie Chang8b8a4822020-06-24 13:24:02 +080014import queue
Kuang-che Wu067ff292019-02-14 18:16:23 +080015import shutil
Kuang-che Wub17b3b92018-09-04 18:12:11 +080016import sys
Kuang-che Wu1e49f512018-12-06 15:27:42 +080017import tempfile
Kuang-che Wu999893c2020-04-13 22:06:22 +080018import urllib.parse
Kuang-che Wua7ddf9b2019-11-25 18:59:57 +080019
Kuang-che Wud2646f42019-11-27 18:31:08 +080020import six
Kuang-che Wu3eb6b502018-06-06 16:15:18 +080021
Zheng-Jie Chang8b8a4822020-06-24 13:24:02 +080022# from third_party
23from depot_tools import gclient_eval
24
Kuang-che Wu3eb6b502018-06-06 16:15:18 +080025from bisect_kit import codechange
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +080026from bisect_kit import git_util
Kuang-che Wu1e49f512018-12-06 15:27:42 +080027from bisect_kit import locking
Kuang-che Wu3eb6b502018-06-06 16:15:18 +080028from bisect_kit import util
29
30logger = logging.getLogger(__name__)
Kuang-che Wuced2dbf2019-01-30 23:13:24 +080031emitted_warnings = set()
Kuang-che Wu3eb6b502018-06-06 16:15:18 +080032
Kuang-che Wu88e96312020-10-20 16:21:11 +080033# If the dependency is not pinned in DEPS file, the default branch.
34# ref: gclient_scm.py GitWrapper.update default_rev
35# TODO(kcwu): follow gclient to change the default branch name
36DEFAULT_BRANCH_NAME = 'master'
37
Kuang-che Wu3eb6b502018-06-06 16:15:18 +080038
Kuang-che Wu41e8b592018-09-25 17:01:30 +080039def config(gclient_dir,
40 url=None,
41 cache_dir=None,
42 deps_file=None,
43 custom_var=None,
44 spec=None):
Kuang-che Wu3eb6b502018-06-06 16:15:18 +080045 """Simply wrapper of `gclient config`.
46
47 Args:
48 gclient_dir: root directory of gclient project
49 url: URL of gclient configuration files
50 cache_dir: gclient's git cache folder
51 deps_file: override the default DEPS file name
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +080052 custom_var: custom variables
Kuang-che Wu41e8b592018-09-25 17:01:30 +080053 spec: content of gclient file
Kuang-che Wu3eb6b502018-06-06 16:15:18 +080054 """
55 cmd = ['gclient', 'config']
56 if deps_file:
57 cmd += ['--deps-file', deps_file]
58 if cache_dir:
59 cmd += ['--cache-dir', cache_dir]
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +080060 if custom_var:
61 cmd += ['--custom-var', custom_var]
Kuang-che Wu41e8b592018-09-25 17:01:30 +080062 if spec:
63 cmd += ['--spec', spec]
64 if url:
65 cmd.append(url)
Kuang-che Wu3eb6b502018-06-06 16:15:18 +080066
67 util.check_call(*cmd, cwd=gclient_dir)
68
69
Kuang-che Wudc714412018-10-17 16:06:39 +080070def sync(gclient_dir,
71 with_branch_heads=False,
72 with_tags=False,
73 ignore_locks=False,
74 jobs=8):
Kuang-che Wu3eb6b502018-06-06 16:15:18 +080075 """Simply wrapper of `gclient sync`.
76
77 Args:
78 gclient_dir: root directory of gclient project
79 with_branch_heads: whether to clone git `branch_heads` refspecs
80 with_tags: whether to clone git tags
Kuang-che Wudc714412018-10-17 16:06:39 +080081 ignore_locks: bypass gclient's lock
Kuang-che Wu3eb6b502018-06-06 16:15:18 +080082 jobs: how many workers running in parallel
83 """
Kuang-che Wua9639eb2019-03-19 17:15:08 +080084 # Work around gclient issue crbug/943430
85 # gclient rejected to sync if there are untracked symlink even with --force
86 for path in [
87 'src/chromeos/assistant/libassistant/src/deps',
88 'src/chromeos/assistant/libassistant/src/libassistant',
89 ]:
90 if os.path.islink(os.path.join(gclient_dir, path)):
91 os.unlink(os.path.join(gclient_dir, path))
92
93 cmd = [
94 'gclient',
95 'sync',
96 '--jobs=%d' % jobs,
97 '--delete_unversioned_trees',
98 # --force is necessary because runhook may generate some untracked files.
99 '--force',
100 ]
Kuang-che Wu3eb6b502018-06-06 16:15:18 +0800101 if with_branch_heads:
102 cmd.append('--with_branch_heads')
103 if with_tags:
104 cmd.append('--with_tags')
Kuang-che Wudc714412018-10-17 16:06:39 +0800105
106 # If 'gclient sync' is interrupted by ctrl-c or terminated with whatever
107 # reasons, it will leave annoying lock files on disk and thus unfriendly to
108 # bot tasks. In bisect-kit, we will use our own lock mechanism (in caller of
109 # this function) and bypass gclient's.
110 if ignore_locks:
111 cmd.append('--ignore_locks')
112
Kuang-che Wu067ff292019-02-14 18:16:23 +0800113 try:
114 old_projects = load_gclient_entries(gclient_dir)
115 except IOError:
116 old_projects = {}
117
Kuang-che Wu3eb6b502018-06-06 16:15:18 +0800118 util.check_call(*cmd, cwd=gclient_dir)
119
Kuang-che Wu067ff292019-02-14 18:16:23 +0800120 # Remove dead .git folder after sync.
121 # Ideally, this should be handled by gclient but sometimes gclient didn't
122 # (crbug/930047).
123 new_projects = load_gclient_entries(gclient_dir)
124 for path in old_projects:
125 if path in new_projects:
126 continue
127 old_git_dir = os.path.join(gclient_dir, path)
Kuang-che Wucbe12432019-03-18 19:35:03 +0800128 if not os.path.exists(old_git_dir):
129 continue
130
131 if git_util.is_git_root(old_git_dir):
Kuang-che Wu067ff292019-02-14 18:16:23 +0800132 logger.warning(
133 '%s was removed from .gclient_entries but %s still exists; remove it',
134 path, old_git_dir)
135 shutil.rmtree(old_git_dir)
Kuang-che Wucbe12432019-03-18 19:35:03 +0800136 else:
137 logger.warning(
138 '%s was removed from .gclient_entries but %s still exists;'
139 ' keep it because it is not git root', path, old_git_dir)
140
141
142def runhook(gclient_dir, jobs=8):
143 """Simply wrapper of `gclient runhook`.
144
145 Args:
146 gclient_dir: root directory of gclient project
147 jobs: how many workers running in parallel
148 """
149 util.check_call('gclient', 'runhook', '--jobs', str(jobs), cwd=gclient_dir)
Kuang-che Wu067ff292019-02-14 18:16:23 +0800150
151
152def load_gclient_entries(gclient_dir):
153 """Loads .gclient_entries."""
154 repo_project_list = os.path.join(gclient_dir, '.gclient_entries')
155 scope = {}
Kuang-che Wud2646f42019-11-27 18:31:08 +0800156 with open(repo_project_list) as f:
157 code = compile(f.read(), repo_project_list, 'exec')
158 six.exec_(code, scope)
Kuang-che Wu058ac042019-03-18 14:14:53 +0800159 entries = scope.get('entries', {})
160
161 # normalize path: remove trailing slash
162 entries = dict((os.path.normpath(path), url) for path, url in entries.items())
163
164 return entries
Kuang-che Wu067ff292019-02-14 18:16:23 +0800165
166
167def write_gclient_entries(gclient_dir, projects):
168 """Writes .gclient_entries."""
169 repo_project_list = os.path.join(gclient_dir, '.gclient_entries')
170 content = 'entries = {\n'
Kuang-che Wuc89f2a22019-11-26 15:30:50 +0800171 for path, repo_url in sorted(projects.items()):
172 content += ' %s: %s,\n' % (pprint.pformat(path), pprint.pformat(repo_url))
Kuang-che Wu067ff292019-02-14 18:16:23 +0800173 content += '}\n'
174 with open(repo_project_list, 'w') as f:
175 f.write(content)
176
Kuang-che Wu3eb6b502018-06-06 16:15:18 +0800177
Kuang-che Wu1e49f512018-12-06 15:27:42 +0800178def mirror(code_storage, repo_url):
179 """Mirror git repo.
180
181 This function mimics the caching behavior of 'gclient sync' with 'cache_dir'.
182
183 Args:
184 code_storage: CodeStorage object
185 repo_url: remote repo url
186 """
187 logger.info('mirror %s', repo_url)
188 tmp_dir = tempfile.mkdtemp(dir=code_storage.cache_dir)
189 git_root = code_storage.cached_git_root(repo_url)
190 assert not os.path.exists(git_root)
191
192 util.check_call('git', 'init', '--bare', cwd=tmp_dir)
193
194 # These config parameters are copied from gclient.
195 git_util.config(tmp_dir, 'gc.autodetach', '0')
196 git_util.config(tmp_dir, 'gc.autopacklimit', '0')
197 git_util.config(tmp_dir, 'core.deltaBaseCacheLimit', '2g')
198 git_util.config(tmp_dir, 'remote.origin.url', repo_url)
199 git_util.config(tmp_dir, '--replace-all', 'remote.origin.fetch',
200 '+refs/heads/*:refs/heads/*', r'\+refs/heads/\*:.*')
201 git_util.config(tmp_dir, '--replace-all', 'remote.origin.fetch',
202 '+refs/tags/*:refs/tags/*', r'\+refs/tags/\*:.*')
203 git_util.config(tmp_dir, '--replace-all', 'remote.origin.fetch',
204 '+refs/branch-heads/*:refs/branch-heads/*',
205 r'\+refs/branch-heads/\*:.*')
206
207 git_util.fetch(tmp_dir, 'origin', '+refs/heads/*:refs/heads/*')
208 git_util.fetch(tmp_dir, 'origin', '+refs/tags/*:refs/tags/*')
209 git_util.fetch(tmp_dir, 'origin', '+refs/branch-heads/*:refs/branch-heads/*')
210
211 # Rename to correct name atomically.
212 os.rename(tmp_dir, git_root)
213
214
Kuang-che Wub17b3b92018-09-04 18:12:11 +0800215# Copied from depot_tools' gclient.py
216_PLATFORM_MAPPING = {
217 'cygwin': 'win',
218 'darwin': 'mac',
219 'linux2': 'linux',
Kuang-che Wud2646f42019-11-27 18:31:08 +0800220 'linux': 'linux',
Kuang-che Wub17b3b92018-09-04 18:12:11 +0800221 'win32': 'win',
222 'aix6': 'aix',
223}
224
225
226def _detect_host_os():
227 return _PLATFORM_MAPPING[sys.platform]
228
229
Kuang-che Wu23192ad2020-03-11 18:12:46 +0800230class Dep:
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800231 """Represent one entry of DEPS's deps.
Kuang-che Wu3eb6b502018-06-06 16:15:18 +0800232
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800233 One Dep object means one subproject inside DEPS file. It recorded what to
234 checkout (like git or cipd) content of each subproject.
Kuang-che Wu3eb6b502018-06-06 16:15:18 +0800235
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800236 Attributes:
237 path: subproject path, relative to project root
238 variables: the variables of the containing DEPS file; these variables will
239 be applied to fields of this object (like 'url' and 'condition') and
240 children projects.
241 condition: whether to checkout this subproject
242 dep_type: 'git' or 'cipd'
243 url: if dep_type='git', the url of remote repo and associated branch/commit
244 packages: if dep_type='cipd', cipd package version and location
Kuang-che Wu3eb6b502018-06-06 16:15:18 +0800245 """
246
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800247 def __init__(self, path, variables, entry):
248 self.path = path
249 self.variables = variables
Kuang-che Wu3eb6b502018-06-06 16:15:18 +0800250
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800251 self.url = None # only valid for dep_type='git'
252 self.packages = None # only valid for dep_type='cipd'
Kuang-che Wu3eb6b502018-06-06 16:15:18 +0800253
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800254 if isinstance(entry, str):
255 self.dep_type = 'git'
256 self.url = entry
257 self.condition = None
258 else:
259 self.dep_type = entry.get('dep_type', 'git')
260 self.condition = entry.get('condition')
261 if self.dep_type == 'git':
262 self.url = entry['url']
Kuang-che Wu3eb6b502018-06-06 16:15:18 +0800263 else:
Kuang-che Wufe1e88a2019-09-10 21:52:25 +0800264 assert self.dep_type == 'cipd', 'unknown dep_type:' + self.dep_type
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800265 self.packages = entry['packages']
Kuang-che Wu3eb6b502018-06-06 16:15:18 +0800266
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800267 if self.dep_type == 'git':
268 self.url = self.url.format(**self.variables)
Kuang-che Wu3eb6b502018-06-06 16:15:18 +0800269
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800270 def __eq__(self, rhs):
271 return vars(self) == vars(rhs)
Kuang-che Wu3eb6b502018-06-06 16:15:18 +0800272
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800273 def __ne__(self, rhs):
274 return not self.__eq__(rhs)
275
Zheng-Jie Chang8b8a4822020-06-24 13:24:02 +0800276 def set_url(self, repo_url, at):
277 assert self.dep_type == 'git'
278 self.url = '%s@%s' % (repo_url, at)
279
280 def set_revision(self, at):
281 assert self.dep_type == 'git'
282 repo_url, _ = self.parse_url()
283 self.set_url(repo_url, at)
284
285 def parse_url(self):
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800286 assert self.dep_type == 'git'
287
288 if '@' in self.url:
289 repo_url, at = self.url.split('@')
290 else:
Kuang-che Wu88e96312020-10-20 16:21:11 +0800291 # If the dependency is not pinned, the default branch.
292 repo_url, at = self.url, DEFAULT_BRANCH_NAME
Zheng-Jie Chang8b8a4822020-06-24 13:24:02 +0800293 return repo_url, at
294
295 def as_path_spec(self):
296 repo_url, at = self.parse_url()
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800297 return codechange.PathSpec(self.path, repo_url, at)
298
299 def eval_condition(self):
300 """Evaluate condition for DEPS parsing.
301
302 Returns:
303 eval result
304 """
305 if not self.condition:
306 return True
307
Kuang-che Wu3e182ec2019-02-21 15:04:30 +0800308 # Currently, we only support chromeos as target_os.
309 # TODO(kcwu): make it configurable if we need to bisect for other os.
Kuang-che Wu0d7409c2019-03-18 12:29:03 +0800310 # We don't specify `target_os_only`, so `unix` will be considered by
311 # gclient as well.
312 target_os = ['chromeos', 'unix']
Kuang-che Wu3e182ec2019-02-21 15:04:30 +0800313
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800314 vars_dict = {
Kuang-che Wu3e182ec2019-02-21 15:04:30 +0800315 'checkout_android': 'android' in target_os,
316 'checkout_chromeos': 'chromeos' in target_os,
317 'checkout_fuchsia': 'fuchsia' in target_os,
318 'checkout_ios': 'ios' in target_os,
319 'checkout_linux': 'unix' in target_os,
320 'checkout_mac': 'mac' in target_os,
321 'checkout_win': 'win' in target_os,
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800322 # default cpu: x64
323 'checkout_arm64': False,
324 'checkout_arm': False,
325 'checkout_mips': False,
326 'checkout_ppc': False,
327 'checkout_s390': False,
328 'checkout_x64': True,
329 'checkout_x86': False,
330 'host_os': _detect_host_os(),
331 'False': False,
332 'None': None,
333 'True': True,
334 }
335 vars_dict.update(self.variables)
336 # pylint: disable=eval-used
337 return eval(self.condition, vars_dict)
338
Zheng-Jie Chang7ab92082020-05-29 19:39:59 +0800339 def to_lines(self):
340 s = []
341 condition_part = ([' "condition": %r,' %
342 self.condition] if self.condition else [])
343 if self.dep_type == 'cipd':
344 s.extend([
345 ' "%s": {' % (self.path.split(':')[0],),
346 ' "packages": [',
347 ])
348 for p in sorted(self.packages, key=lambda x: x['package']):
349 s.extend([
350 ' {',
351 ' "package": "%s",' % p['package'],
352 ' "version": "%s",' % p['version'],
353 ' },',
354 ])
355 s.extend([
356 ' ],',
357 ' "dep_type": "cipd",',
358 ] + condition_part + [
359 ' },',
360 '',
361 ])
362 else:
363 s.extend([
364 ' "%s": {' % (self.path,),
365 ' "url": "%s",' % (self.url,),
366 ] + condition_part + [
367 ' },',
368 '',
369 ])
370 return s
371
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800372
Kuang-che Wu23192ad2020-03-11 18:12:46 +0800373class Deps:
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800374 """DEPS parsed result.
375
376 Attributes:
377 variables: 'vars' dict in DEPS file; these variables will be applied
378 recursively to children.
379 entries: dict of Dep objects
380 recursedeps: list of recursive projects
381 """
382
383 def __init__(self):
384 self.variables = {}
385 self.entries = {}
Zheng-Jie Chang7ab92082020-05-29 19:39:59 +0800386 self.ignored_entries = {}
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800387 self.recursedeps = []
Zheng-Jie Chang7ab92082020-05-29 19:39:59 +0800388 self.allowed_hosts = set()
Zheng-Jie Chang8b8a4822020-06-24 13:24:02 +0800389 self.gn_args_from = None
Zheng-Jie Chang7ab92082020-05-29 19:39:59 +0800390 self.gn_args_file = None
391 self.gn_args = []
392 self.hooks = []
393 self.pre_deps_hooks = []
Zheng-Jie Chang8b8a4822020-06-24 13:24:02 +0800394 self.modified = set()
Zheng-Jie Chang7ab92082020-05-29 19:39:59 +0800395
396 def _gn_settings_to_lines(self):
397 s = []
398 if self.gn_args_file:
399 s.extend([
400 'gclient_gn_args_file = "%s"' % self.gn_args_file,
401 'gclient_gn_args = %r' % self.gn_args,
402 ])
403 return s
404
405 def _allowed_hosts_to_lines(self):
406 """Converts |allowed_hosts| set to list of lines for output."""
407 if not self.allowed_hosts:
408 return []
409 s = ['allowed_hosts = [']
410 for h in sorted(self.allowed_hosts):
411 s.append(' "%s",' % h)
412 s.extend([']', ''])
413 return s
414
415 def _entries_to_lines(self):
416 """Converts |entries| dict to list of lines for output."""
417 entries = self.ignored_entries
418 entries.update(self.entries)
419 if not entries:
420 return []
421 s = ['deps = {']
422 for _, dep in sorted(entries.items()):
423 s.extend(dep.to_lines())
424 s.extend(['}', ''])
425 return s
426
427 def _vars_to_lines(self):
428 """Converts |variables| dict to list of lines for output."""
429 if not self.variables:
430 return []
431 s = ['vars = {']
432 for key, value in sorted(self.variables.items()):
433 s.extend([
434 ' "%s": %r,' % (key, value),
435 '',
436 ])
437 s.extend(['}', ''])
438 return s
439
440 def _hooks_to_lines(self, name, hooks):
441 """Converts |hooks| list to list of lines for output."""
442 if not hooks:
443 return []
Zheng-Jie Chang8b8a4822020-06-24 13:24:02 +0800444 hooks.sort(key=lambda x: x.get('name', ''))
Zheng-Jie Chang7ab92082020-05-29 19:39:59 +0800445 s = ['%s = [' % name]
446 for hook in hooks:
447 s.extend([
448 ' {',
449 ])
450 if hook.get('name') is not None:
451 s.append(' "name": "%s",' % hook.get('name'))
452 if hook.get('pattern') is not None:
453 s.append(' "pattern": "%s",' % hook.get('pattern'))
454 if hook.get('condition') is not None:
455 s.append(' "condition": %r,' % hook.get('condition'))
456 # Flattened hooks need to be written relative to the root gclient dir
Zheng-Jie Chang8b8a4822020-06-24 13:24:02 +0800457 cwd = os.path.relpath(os.path.normpath(hook.get('cwd', '.')))
Zheng-Jie Chang7ab92082020-05-29 19:39:59 +0800458 s.extend([' "cwd": "%s",' % cwd] + [' "action": ['] +
459 [' "%s",' % arg for arg in hook.get('action', [])] +
460 [' ]', ' },', ''])
461 s.extend([']', ''])
462 return s
463
464 def to_string(self):
465 """Return flatten DEPS string."""
466 return '\n'.join(
467 self._gn_settings_to_lines() + self._allowed_hosts_to_lines() +
468 self._entries_to_lines() + self._hooks_to_lines('hooks', self.hooks) +
469 self._hooks_to_lines('pre_deps_hooks', self.pre_deps_hooks) +
470 self._vars_to_lines() + ['']) # Ensure newline at end of file.
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800471
Zheng-Jie Changc0d3cd72020-06-03 02:46:43 +0800472 def remove_src(self):
473 """Return src_revision for buildbucket use."""
474 assert 'src' in self.entries
475 _, src_rev = self.entries['src'].parse_url()
476 del self.entries['src']
477 return src_rev
478
Zheng-Jie Chang8b8a4822020-06-24 13:24:02 +0800479 def apply_commit(self, path, revision, overwrite=True):
480 """Set revision to a project by path.
481
482 Args:
483 path: A project's path.
484 revision: A git commit id.
485 overwrite: Overwrite flag, the project won't change if overwrite=False
486 and it was modified before.
487 """
488 if path in self.modified and not overwrite:
489 return
490 self.modified.add(path)
491
492 if path not in self.entries:
493 logger.warning('path: %s not found in DEPS', path)
494 return
495 self.entries[path].set_revision(revision)
496
497 def apply_action_groups(self, action_groups):
498 """Apply multiple action groups to DEPS.
499
500 If there are multiple actions in one repo, only last one is applied.
501
502 Args:
503 action_groups: A list of action groups.
504 """
505 # Apply in reversed order with overwrite=False,
506 # so each repo is on the state of last action.
507 for action_group in reversed(action_groups):
508 for action in reversed(action_group.actions):
509 if isinstance(action, codechange.GitCheckoutCommit):
510 self.apply_commit(action.path, action.rev, overwrite=False)
511 if isinstance(action, codechange.GitAddRepo):
512 self.apply_commit(action.path, action.rev, overwrite=False)
513 if isinstance(action, codechange.GitRemoveRepo):
514 assert action.path not in self.entries
515 self.modified.add(action.path)
516
517 def apply_deps(self, deps):
518 for path, dep in deps.entries.items():
519 if path in self.entries:
520 _, rev = dep.parse_url()
521 self.apply_commit(path, rev, overwrite=False)
522
523 # hooks, vars, ignored_entries are ignored and should be set by float_spec
524
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800525
Kuang-che Wu23192ad2020-03-11 18:12:46 +0800526class TimeSeriesTree:
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800527 """Data structure for generating snapshots of historical dependency tree.
528
529 This is a tree structure with time information. Each tree node represents not
530 only typical tree data and tree children information, but also historical
531 value of those tree data and tree children.
532
533 To be more specific in terms of DEPS parsing, one TimeSeriesTree object
534 represent a DEPS file. The caller will add_snapshot() to add parsed result of
535 historical DEPS instances. After that, the tree root of this class can
536 reconstruct the every historical moment of the project dependency state.
537
538 This class is slight abstraction of git_util.get_history_recursively() to
539 support more than single git repo and be version control system independent.
540 """
541
542 # TODO(kcwu): refactor git_util.get_history_recursively() to reuse this class.
543
Kuang-che Wu3a7352d2018-10-20 17:22:03 +0800544 def __init__(self, parent_deps, entry, start_time, end_time):
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800545 """TimeSeriesTree constructor.
546
547 Args:
548 parent_deps: parent DEPS of the given period. None if this is tree root.
Kuang-che Wu3a7352d2018-10-20 17:22:03 +0800549 entry: project entry
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800550 start_time: start time
551 end_time: end time
552 """
553 self.parent_deps = parent_deps
Kuang-che Wu3a7352d2018-10-20 17:22:03 +0800554 self.entry = entry
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800555 self.snapshots = {}
556 self.start_time = start_time
557 self.end_time = end_time
558
559 # Intermediate dict to keep track alive children for the time being.
560 # Maintained by add_snapshot() and no_more_snapshot().
561 self.alive_children = {}
562
563 # All historical children (TimeSeriesTree object) between start_time and
Kuang-che Wu3a7352d2018-10-20 17:22:03 +0800564 # end_time. It's possible that children with the same entry appear more than
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800565 # once in this list because they are removed and added back to the DEPS
566 # file.
567 self.subtrees = []
568
Kuang-che Wu3a7352d2018-10-20 17:22:03 +0800569 def subtree_eq(self, deps_a, deps_b, child_entry):
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800570 """Compares subtree of two Deps.
571
572 Args:
573 deps_a: Deps object
574 deps_b: Deps object
Kuang-che Wu3a7352d2018-10-20 17:22:03 +0800575 child_entry: the subtree to compare
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800576
577 Returns:
578 True if the said subtree of these two Deps equal
579 """
580 # Need to compare variables because they may influence subtree parsing
581 # behavior
Kuang-che Wu3a7352d2018-10-20 17:22:03 +0800582 path = child_entry[0]
583 return (deps_a.entries[path] == deps_b.entries[path] and
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800584 deps_a.variables == deps_b.variables)
585
Kuang-che Wu3a7352d2018-10-20 17:22:03 +0800586 def add_snapshot(self, timestamp, deps, children_entries):
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800587 """Adds parsed DEPS result and children.
588
589 For example, if a given DEPS file has N revisions between start_time and
590 end_time, the caller should call this method N times to feed all parsed
591 results in order (timestamp increasing).
592
593 Args:
594 timestamp: timestamp of `deps`
595 deps: Deps object
Kuang-che Wu3a7352d2018-10-20 17:22:03 +0800596 children_entries: list of names of deps' children
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800597 """
598 assert timestamp not in self.snapshots
599 self.snapshots[timestamp] = deps
600
Kuang-che Wua7ddf9b2019-11-25 18:59:57 +0800601 for child_entry in set(list(self.alive_children.keys()) + children_entries):
Kuang-che Wu3a7352d2018-10-20 17:22:03 +0800602 # `child_entry` is added at `timestamp`
603 if child_entry not in self.alive_children:
604 self.alive_children[child_entry] = timestamp, deps
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800605
Kuang-che Wu3a7352d2018-10-20 17:22:03 +0800606 # `child_entry` is removed at `timestamp`
607 elif child_entry not in children_entries:
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800608 self.subtrees.append(
Kuang-che Wu3a7352d2018-10-20 17:22:03 +0800609 TimeSeriesTree(self.alive_children[child_entry][1], child_entry,
610 self.alive_children[child_entry][0], timestamp))
611 del self.alive_children[child_entry]
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800612
Kuang-che Wu3a7352d2018-10-20 17:22:03 +0800613 # `child_entry` is alive before and after `timestamp`
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800614 else:
Kuang-che Wu3a7352d2018-10-20 17:22:03 +0800615 last_deps = self.alive_children[child_entry][1]
616 if not self.subtree_eq(last_deps, deps, child_entry):
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800617 self.subtrees.append(
Kuang-che Wu3a7352d2018-10-20 17:22:03 +0800618 TimeSeriesTree(last_deps, child_entry,
619 self.alive_children[child_entry][0], timestamp))
620 self.alive_children[child_entry] = timestamp, deps
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800621
Kuang-che Wua7ddf9b2019-11-25 18:59:57 +0800622 def no_more_snapshot(self):
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800623 """Indicates all snapshots are added.
624
625 add_snapshot() should not be invoked after no_more_snapshot().
626 """
Kuang-che Wu3a7352d2018-10-20 17:22:03 +0800627 for child_entry, (timestamp, deps) in self.alive_children.items():
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800628 if timestamp == self.end_time:
629 continue
630 self.subtrees.append(
Kuang-che Wu3a7352d2018-10-20 17:22:03 +0800631 TimeSeriesTree(deps, child_entry, timestamp, self.end_time))
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800632 self.alive_children = None
633
634 def events(self):
635 """Gets children added/removed events of this subtree.
636
637 Returns:
638 list of (timestamp, deps_name, deps, end_flag):
639 timestamp: timestamp of event
640 deps_name: name of this subtree
641 deps: Deps object of given project
642 end_flag: True indicates this is the last event of this deps tree
643 """
644 assert self.snapshots
645 assert self.alive_children is None, ('events() is valid only after '
646 'no_more_snapshot() is invoked')
647
648 result = []
649
650 last_deps = None
651 for timestamp, deps in self.snapshots.items():
Kuang-che Wu3a7352d2018-10-20 17:22:03 +0800652 result.append((timestamp, self.entry, deps, False))
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800653 last_deps = deps
654
655 assert last_deps
Kuang-che Wu3a7352d2018-10-20 17:22:03 +0800656 result.append((self.end_time, self.entry, last_deps, True))
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800657
658 for subtree in self.subtrees:
659 for event in subtree.events():
660 result.append(event)
661
Kuang-che Wu1ad2c0e2019-02-26 00:41:10 +0800662 result.sort(key=lambda x: x[0])
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800663
664 return result
665
Zheng-Jie Chang8b8a4822020-06-24 13:24:02 +0800666 def iter_forest(self):
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800667 """Iterates snapshots of project dependency state.
668
669 Yields:
670 (timestamp, path_specs):
671 timestamp: time of snapshot
Zheng-Jie Chang8b8a4822020-06-24 13:24:02 +0800672 forest: A dict indicates path => deps mapping
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800673 """
674 forest = {}
675 # Group by timestamp
676 for timestamp, events in itertools.groupby(self.events(),
677 operator.itemgetter(0)):
678 # It's possible that one deps is removed and added at the same timestamp,
679 # i.e. modification, so use counter to track.
680 end_counter = collections.Counter()
681
Kuang-che Wu3a7352d2018-10-20 17:22:03 +0800682 for timestamp, entry, deps, end in events:
683 forest[entry] = deps
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800684 if end:
Kuang-che Wu3a7352d2018-10-20 17:22:03 +0800685 end_counter[entry] += 1
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800686 else:
Kuang-che Wu3a7352d2018-10-20 17:22:03 +0800687 end_counter[entry] -= 1
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800688
Zheng-Jie Chang8b8a4822020-06-24 13:24:02 +0800689 yield timestamp, forest
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800690
691 # Remove deps which are removed at this timestamp.
Kuang-che Wu3a7352d2018-10-20 17:22:03 +0800692 for entry, count in end_counter.items():
693 assert -1 <= count <= 1, (timestamp, entry)
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800694 if count == 1:
Kuang-che Wu3a7352d2018-10-20 17:22:03 +0800695 del forest[entry]
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800696
Zheng-Jie Chang8b8a4822020-06-24 13:24:02 +0800697 def iter_path_specs(self):
698 """Iterates snapshots of project dependency state.
699
700 Yields:
701 (timestamp, path_specs):
702 timestamp: time of snapshot
703 path_specs: dict of path_spec entries
704 """
705 for timestamp, forest in self.iter_forest():
706 path_specs = {}
707 # Merge Deps at time `timestamp` into single path_specs.
708 for deps in forest.values():
709 for path, dep in deps.entries.items():
710 path_specs[path] = dep.as_path_spec()
711 yield timestamp, path_specs
712
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800713
Kuang-che Wu23192ad2020-03-11 18:12:46 +0800714class DepsParser:
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800715 """Gclient DEPS file parser."""
716
717 def __init__(self, project_root, code_storage):
718 self.project_root = project_root
719 self.code_storage = code_storage
720
Kuang-che Wu067ff292019-02-14 18:16:23 +0800721 def load_single_deps(self, content):
722
723 def var_function(name):
724 return '{%s}' % name
725
Zheng-Jie Chang381e4162020-08-03 14:26:54 +0800726 def str_function(name):
727 return str(name)
728
729 global_scope = dict(Var=var_function, Str=str_function)
Kuang-che Wu067ff292019-02-14 18:16:23 +0800730 local_scope = {}
Kuang-che Wuc0baf752020-06-29 11:32:26 +0800731 six.exec_(content, global_scope, local_scope)
Kuang-che Wu067ff292019-02-14 18:16:23 +0800732 return local_scope
733
Zheng-Jie Chang8b8a4822020-06-24 13:24:02 +0800734 def parse_single_deps(self,
735 content,
736 parent_vars=None,
737 parent_path='',
738 parent_dep=None):
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800739 """Parses DEPS file without recursion.
740
741 Args:
742 content: file content of DEPS file
743 parent_vars: variables inherent from parent DEPS
744 parent_path: project path of parent DEPS file
Zheng-Jie Chang8b8a4822020-06-24 13:24:02 +0800745 parent_dep: A corresponding Dep object in parent DEPS
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800746
747 Returns:
748 Deps object
749 """
750
Kuang-che Wu067ff292019-02-14 18:16:23 +0800751 local_scope = self.load_single_deps(content)
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800752 deps = Deps()
Kuang-che Wu067ff292019-02-14 18:16:23 +0800753
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800754 local_scope.setdefault('vars', {})
755 if parent_vars:
756 local_scope['vars'].update(parent_vars)
757 deps.variables = local_scope['vars']
758
759 # Warnings for old usages which we don't support.
760 for name in deps.variables:
761 if name.startswith('RECURSEDEPS_') or name.endswith('_DEPS_file'):
762 logger.warning('%s is deprecated and not supported recursion syntax',
763 name)
764 if 'deps_os' in local_scope:
765 logger.warning('deps_os is no longer supported')
Zheng-Jie Chang7ab92082020-05-29 19:39:59 +0800766
767 if 'allowed_hosts' in local_scope:
768 deps.allowed_hosts = set(local_scope.get('allowed_hosts'))
769 deps.hooks = local_scope.get('hooks', [])
770 deps.pre_deps_hooks = local_scope.get('pre_deps_hooks', [])
Zheng-Jie Chang8b8a4822020-06-24 13:24:02 +0800771 deps.gn_args_from = local_scope.get('gclient_gn_args_from')
Zheng-Jie Chang7ab92082020-05-29 19:39:59 +0800772 deps.gn_args_file = local_scope.get('gclient_gn_args_file')
773 deps.gn_args = local_scope.get('gclient_gn_args', [])
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800774
Zheng-Jie Chang8b8a4822020-06-24 13:24:02 +0800775 # recalculate hook path
776 use_relative_hooks = local_scope.get('use_relative_hooks', False)
777 if use_relative_hooks:
778 assert local_scope.get('use_relative_paths', False)
779 for hook in deps.hooks:
780 hook['cwd'] = os.path.join(parent_path, hook.get('cwd', ''))
781 for pre_deps_hook in deps.pre_deps_hooks:
782 pre_deps_hook['cwd'] = os.path.join(parent_path,
783 pre_deps_hook.get('cwd', ''))
784
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800785 for path, dep_entry in local_scope['deps'].items():
Zheng-Jie Chang8b8a4822020-06-24 13:24:02 +0800786 # recalculate path
Kuang-che Wu058ac042019-03-18 14:14:53 +0800787 path = path.format(**deps.variables)
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800788 if local_scope.get('use_relative_paths', False):
789 path = os.path.join(parent_path, path)
Kuang-che Wu058ac042019-03-18 14:14:53 +0800790 path = os.path.normpath(path)
Zheng-Jie Chang8b8a4822020-06-24 13:24:02 +0800791
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800792 dep = Dep(path, deps.variables, dep_entry)
Zheng-Jie Chang8b8a4822020-06-24 13:24:02 +0800793 eval_condition = dep.eval_condition()
794
795 # update condition
796 if parent_dep and parent_dep.condition:
797 tmp_dict = {'condition': dep.condition}
798 gclient_eval.UpdateCondition(tmp_dict, 'and', parent_dep.condition)
799 dep.condition = tmp_dict['condition']
800
801 if not eval_condition:
Zheng-Jie Chang7ab92082020-05-29 19:39:59 +0800802 deps.ignored_entries[path] = dep
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800803 continue
804
805 # TODO(kcwu): support dep_type=cipd http://crbug.com/846564
806 if dep.dep_type != 'git':
Kuang-che Wuced2dbf2019-01-30 23:13:24 +0800807 warning_key = ('dep_type', dep.dep_type, path)
808 if warning_key not in emitted_warnings:
809 emitted_warnings.add(warning_key)
810 logger.warning('dep_type=%s is not supported yet: %s', dep.dep_type,
811 path)
Zheng-Jie Chang7ab92082020-05-29 19:39:59 +0800812 deps.ignored_entries[path] = dep
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800813 continue
814
815 deps.entries[path] = dep
816
817 recursedeps = []
Kuang-che Wu3a7352d2018-10-20 17:22:03 +0800818 for recurse_entry in local_scope.get('recursedeps', []):
819 # Normalize entries.
820 if isinstance(recurse_entry, tuple):
821 path, deps_file = recurse_entry
822 else:
823 assert isinstance(path, str)
824 path, deps_file = recurse_entry, 'DEPS'
825
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800826 if local_scope.get('use_relative_paths', False):
827 path = os.path.join(parent_path, path)
828 path = path.format(**deps.variables)
829 if path in deps.entries:
Kuang-che Wu3a7352d2018-10-20 17:22:03 +0800830 recursedeps.append((path, deps_file))
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800831 deps.recursedeps = recursedeps
832
833 return deps
834
835 def construct_deps_tree(self,
836 tstree,
837 repo_url,
838 at,
839 after,
840 before,
841 parent_vars=None,
842 parent_path='',
Zheng-Jie Chang8b8a4822020-06-24 13:24:02 +0800843 parent_dep=None,
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800844 deps_file='DEPS'):
845 """Processes DEPS recursively of given time period.
846
847 This method parses all commits of DEPS between time `after` and `before`,
848 segments recursive dependencies into subtrees if they are changed, and
849 processes subtrees recursively.
850
851 The parsed results (multiple revisions of DEPS file) are stored in `tstree`.
852
853 Args:
854 tstree: TimeSeriesTree object
855 repo_url: remote repo url
856 at: branch or git commit id
857 after: begin of period
858 before: end of period
859 parent_vars: DEPS variables inherit from parent DEPS (including
860 custom_vars)
861 parent_path: the path of parent project of current DEPS file
Zheng-Jie Chang8b8a4822020-06-24 13:24:02 +0800862 parent_dep: A corresponding Dep object in parent DEPS
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800863 deps_file: filename of DEPS file, relative to the git repo, repo_rul
864 """
865 if '://' in repo_url:
866 git_repo = self.code_storage.cached_git_root(repo_url)
Kuang-che Wu1e49f512018-12-06 15:27:42 +0800867 if not os.path.exists(git_repo):
868 with locking.lock_file(
869 os.path.join(self.code_storage.cache_dir,
870 locking.LOCK_FILE_FOR_MIRROR_SYNC)):
871 mirror(self.code_storage, repo_url)
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800872 else:
873 git_repo = repo_url
874
875 if git_util.is_git_rev(at):
876 history = [
877 (after, at),
878 (before, at),
879 ]
880 else:
881 history = git_util.get_history(
882 git_repo,
883 deps_file,
884 branch=at,
885 after=after,
886 before=before,
Zheng-Jie Chang313eec32020-02-18 16:17:07 +0800887 padding_begin=True,
888 padding_end=True)
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800889 assert history
890
891 # If not equal, it means the file was deleted but is still referenced by
892 # its parent.
893 assert history[-1][0] == before
894
895 # TODO(kcwu): optimization: history[-1] is unused
896 for timestamp, git_rev in history[:-1]:
897 content = git_util.get_file_from_revision(git_repo, git_rev, deps_file)
898
899 deps = self.parse_single_deps(
Zheng-Jie Chang8b8a4822020-06-24 13:24:02 +0800900 content,
901 parent_vars=parent_vars,
902 parent_path=parent_path,
903 parent_dep=parent_dep)
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800904 tstree.add_snapshot(timestamp, deps, deps.recursedeps)
905
Kuang-che Wua7ddf9b2019-11-25 18:59:57 +0800906 tstree.no_more_snapshot()
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800907
908 for subtree in tstree.subtrees:
Kuang-che Wu3a7352d2018-10-20 17:22:03 +0800909 path, deps_file = subtree.entry
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800910 path_spec = subtree.parent_deps.entries[path].as_path_spec()
911 self.construct_deps_tree(
912 subtree,
913 path_spec.repo_url,
914 path_spec.at,
915 subtree.start_time,
916 subtree.end_time,
917 parent_vars=subtree.parent_deps.variables,
Kuang-che Wu3a7352d2018-10-20 17:22:03 +0800918 parent_path=path,
Zheng-Jie Chang8b8a4822020-06-24 13:24:02 +0800919 parent_dep=subtree.parent_deps.entries[path],
Kuang-che Wu3a7352d2018-10-20 17:22:03 +0800920 deps_file=deps_file)
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800921
922 def enumerate_path_specs(self, start_time, end_time, path):
923 tstree = TimeSeriesTree(None, path, start_time, end_time)
Kuang-che Wu88e96312020-10-20 16:21:11 +0800924 self.construct_deps_tree(tstree, path, DEFAULT_BRANCH_NAME, start_time,
925 end_time)
Kuang-che Wu8a28a9d2018-09-11 17:43:36 +0800926 return tstree.iter_path_specs()
Kuang-che Wu3eb6b502018-06-06 16:15:18 +0800927
Zheng-Jie Chang8b8a4822020-06-24 13:24:02 +0800928 def enumerate_gclient_solutions(self, start_time, end_time, path):
929 tstree = TimeSeriesTree(None, path, start_time, end_time)
Kuang-che Wu88e96312020-10-20 16:21:11 +0800930 self.construct_deps_tree(tstree, path, DEFAULT_BRANCH_NAME, start_time,
931 end_time)
Zheng-Jie Chang8b8a4822020-06-24 13:24:02 +0800932 return tstree.iter_forest()
933
934 def flatten(self, solutions, entry_point):
935 """Flatten all given Deps
936
937 Args:
938 solutions: A name => Deps dict, name can be either a str or a tuple.
939 entry_point (str): An entry_point name of solutions.
940
941 Returns:
942 Deps: A flatten Deps.
943 """
944
945 def _add_unvisited_recursedeps(deps_queue, visited, deps):
946 for name in deps.recursedeps:
947 if name not in visited:
948 visited.add(name)
949 deps_queue.put(name)
950
951 result = solutions[entry_point]
952 deps_queue = queue.SimpleQueue()
953 visited = set()
954 visited.add(entry_point)
955 _add_unvisited_recursedeps(deps_queue, visited, solutions[entry_point])
956
957 # BFS to merge `deps` into `result`
958 while not deps_queue.empty():
959 deps_name = deps_queue.get()
960 deps = solutions[deps_name]
961
962 result.allowed_hosts.update(deps.allowed_hosts)
963 for key, value in deps.variables.items():
964 assert key not in result.variables or deps.variables[key] == value
965 result.variables[key] = value
966 result.pre_deps_hooks += deps.pre_deps_hooks
967 result.hooks += deps.hooks
968
969 for dep in deps.entries.values():
970 assert dep.path not in result.entries or result.entries.get(
971 dep.path) == dep
972 result.entries[dep.path] = dep
973
974 for dep in deps.ignored_entries.values():
975 assert (dep.path not in result.ignored_entries or
976 result.ignored_entries.get(dep.path) == dep)
977 result.ignored_entries[dep.path] = dep
978
979 _add_unvisited_recursedeps(deps_queue, visited, deps)
980
981 # If gn_args_from is set in root DEPS, overwrite gn arguments
982 if solutions[entry_point].gn_args_from:
983 gn_args_dep = solutions[(solutions[entry_point].gn_args_from, 'DEPS')]
984 result.gn_args = gn_args_dep.gn_args
985 result.gn_args_file = gn_args_dep.gn_args_file
986
987 return result
988
Kuang-che Wu3eb6b502018-06-06 16:15:18 +0800989
990class GclientCache(codechange.CodeStorage):
991 """Gclient git cache."""
992
993 def __init__(self, cache_dir):
994 self.cache_dir = cache_dir
995
996 def _url_to_cache_dir(self, url):
997 # ref: depot_tools' git_cache.Mirror.UrlToCacheDir
Kuang-che Wua7ddf9b2019-11-25 18:59:57 +0800998 parsed = urllib.parse.urlparse(url)
Kuang-che Wu3eb6b502018-06-06 16:15:18 +0800999 norm_url = parsed.netloc + parsed.path
1000 if norm_url.endswith('.git'):
1001 norm_url = norm_url[:-len('.git')]
Kuang-che Wu5fef2912019-04-15 16:18:29 +08001002 norm_url = norm_url.replace('googlesource.com/a/', 'googlesource.com/')
Kuang-che Wu3eb6b502018-06-06 16:15:18 +08001003 return norm_url.replace('-', '--').replace('/', '-').lower()
1004
1005 def cached_git_root(self, repo_url):
1006 cache_path = self._url_to_cache_dir(repo_url)
1007 return os.path.join(self.cache_dir, cache_path)
Kuang-che Wu6948ecc2018-09-11 17:43:49 +08001008
Kuang-che Wu6948ecc2018-09-11 17:43:49 +08001009 def add_to_project_list(self, project_root, path, repo_url):
Kuang-che Wu067ff292019-02-14 18:16:23 +08001010 projects = load_gclient_entries(project_root)
Kuang-che Wu6948ecc2018-09-11 17:43:49 +08001011
1012 projects[path] = repo_url
1013
Kuang-che Wu067ff292019-02-14 18:16:23 +08001014 write_gclient_entries(project_root, projects)
Kuang-che Wu6948ecc2018-09-11 17:43:49 +08001015
1016 def remove_from_project_list(self, project_root, path):
Kuang-che Wu067ff292019-02-14 18:16:23 +08001017 projects = load_gclient_entries(project_root)
Kuang-che Wu6948ecc2018-09-11 17:43:49 +08001018
1019 if path in projects:
1020 del projects[path]
1021
Kuang-che Wu067ff292019-02-14 18:16:23 +08001022 write_gclient_entries(project_root, projects)