blob: dafffaa80fc386919f695609b7dc3c042defc0d5 [file] [log] [blame]
Kuang-che Wu3eb6b502018-06-06 16:15:18 +08001# Copyright 2018 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4"""Gclient utility."""
5
6from __future__ import print_function
7import logging
8import os
9import urlparse
10
11from bisect_kit import codechange
12from bisect_kit import util
13
14logger = logging.getLogger(__name__)
15
16
17def config(gclient_dir, url, cache_dir=None, deps_file=None):
18 """Simply wrapper of `gclient config`.
19
20 Args:
21 gclient_dir: root directory of gclient project
22 url: URL of gclient configuration files
23 cache_dir: gclient's git cache folder
24 deps_file: override the default DEPS file name
25 """
26 cmd = ['gclient', 'config']
27 if deps_file:
28 cmd += ['--deps-file', deps_file]
29 if cache_dir:
30 cmd += ['--cache-dir', cache_dir]
31 cmd.append(url)
32
33 util.check_call(*cmd, cwd=gclient_dir)
34
35
36def sync(gclient_dir, with_branch_heads=False, with_tags=False, jobs=8):
37 """Simply wrapper of `gclient sync`.
38
39 Args:
40 gclient_dir: root directory of gclient project
41 with_branch_heads: whether to clone git `branch_heads` refspecs
42 with_tags: whether to clone git tags
43 jobs: how many workers running in parallel
44 """
45 cmd = ['gclient', 'sync', '--jobs', str(jobs)]
46 if with_branch_heads:
47 cmd.append('--with_branch_heads')
48 if with_tags:
49 cmd.append('--with_tags')
50 util.check_call(*cmd, cwd=gclient_dir)
51
52
53def _eval_condition(condition, dep_vars, custom_vars):
54 """Evaluate condition for DEPS parsing.
55
56 Args:
57 condition: python expression
58 dep_vars: variables defined in the DEPS file
59 custom_vars: custom variables
60
61 Returns:
62 eval result
63 """
64 vars_dict = {
65 # default os: linux
66 'checkout_android': False,
67 'checkout_chromeos': False,
68 'checkout_fuchsia': False,
69 'checkout_ios': False,
70 'checkout_linux': True,
71 'checkout_mac': False,
72 'checkout_win': False,
73 # default cpu: x64
74 'checkout_arm64': False,
75 'checkout_arm': False,
76 'checkout_mips': False,
77 'checkout_ppc': False,
78 'checkout_s390': False,
79 'checkout_x64': True,
80 'checkout_x86': False,
81 'False': False,
82 'None': None,
83 'True': True,
84 }
85 vars_dict.update(dep_vars)
86 vars_dict.update(custom_vars)
87 # pylint: disable=eval-used
88 return eval(condition, vars_dict)
89
90
91def _normalize_dep(dep):
92 if isinstance(dep, str):
93 result = {'url': dep}
94 else:
95 assert isinstance(dep, dict)
96 result = dep.copy()
97 result.setdefault('dep_type', 'git')
98 return result
99
100
101def parse_deps(content, custom_vars):
102 """Parses DEPS file.
103
104 Args:
105 content: file content of DEPS file
106 custom_vars: custom variables
107
108 Returns:
109 path to PathSpec dict
110 """
111
112 def var_function(name):
113 return '{%s}' % name
114
115 global_scope = dict(Var=var_function)
116 local_scope = {}
117 try:
118 exec (content, global_scope, local_scope)
119 except SyntaxError:
120 raise
121
122 local_scope.setdefault('vars', {})
123 for name in local_scope['vars']:
124 if name.startswith('RECURSEDEPS_') or name.endswith('_DEPS_file'):
125 logger.warning('%s is deprecated and not supported recursion syntax',
126 name)
127 if 'recursedeps' in local_scope:
128 # TODO(kcwu): support recursedeps
129 logger.warning('recursedeps is not supported yet')
130
131 # Merge 'deps_os' deps into 'deps' dict.
132 for os_name, os_deps in local_scope.get('deps_os', {}).items():
133 os_condition = 'checkout_%s' % (os_name if os_name != 'unix' else 'linux')
134 if not _eval_condition(os_condition, local_scope['vars'], custom_vars):
135 continue
136
137 for path, os_dep in os_deps.items():
138 dep = local_scope['deps'].setdefault(path, {'condition': 'False'})
139 os_dep = _normalize_dep(os_dep)
140 if os_dep['dep_type'] != 'git' or not os_dep.get('url'):
141 logger.error('deps_os[%s][%s] should be git dep: %s', os_name, path,
142 os_dep)
143 continue
144 if 'url' in dep:
145 assert dep['url'] == os_dep['url']
146 else:
147 dep['url'] = os_dep['url']
148
149 new_condition = '(%s) or (%s)' % (dep.get('condition', 'True'),
150 os_dep.get('condition', 'True'))
151 dep['condition'] = new_condition
152
153 result = {}
154 for path, dep in local_scope['deps'].items():
155 dep = _normalize_dep(dep)
156 condition = dep.get('condition')
157 if condition and not _eval_condition(condition, local_scope['vars'],
158 custom_vars):
159 continue
160 # TODO(kcwu): support dep_type=cipd http://crbug.com/846564
161 assert dep['dep_type'] == 'git'
162 url = dep['url']
163
164 url = url.format(**local_scope['vars'])
165 repo_url, at = url.split('@')
166 result[path] = codechange.PathSpec(path, repo_url, at)
167 return result
168
169
170class GclientCache(codechange.CodeStorage):
171 """Gclient git cache."""
172
173 def __init__(self, cache_dir):
174 self.cache_dir = cache_dir
175
176 def _url_to_cache_dir(self, url):
177 # ref: depot_tools' git_cache.Mirror.UrlToCacheDir
178 parsed = urlparse.urlparse(url)
179 norm_url = parsed.netloc + parsed.path
180 if norm_url.endswith('.git'):
181 norm_url = norm_url[:-len('.git')]
182 return norm_url.replace('-', '--').replace('/', '-').lower()
183
184 def cached_git_root(self, repo_url):
185 cache_path = self._url_to_cache_dir(repo_url)
186 return os.path.join(self.cache_dir, cache_path)