blob: a910b89341b60afd1a5434037be8ee09fafb5dfa [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
8import logging
9import os
Kuang-che Wu6948ecc2018-09-11 17:43:49 +080010import pprint
Kuang-che Wub17b3b92018-09-04 18:12:11 +080011import sys
Kuang-che Wu3eb6b502018-06-06 16:15:18 +080012import urlparse
13
14from bisect_kit import codechange
15from bisect_kit import util
16
17logger = logging.getLogger(__name__)
18
19
20def config(gclient_dir, url, cache_dir=None, deps_file=None):
21 """Simply wrapper of `gclient config`.
22
23 Args:
24 gclient_dir: root directory of gclient project
25 url: URL of gclient configuration files
26 cache_dir: gclient's git cache folder
27 deps_file: override the default DEPS file name
28 """
29 cmd = ['gclient', 'config']
30 if deps_file:
31 cmd += ['--deps-file', deps_file]
32 if cache_dir:
33 cmd += ['--cache-dir', cache_dir]
34 cmd.append(url)
35
36 util.check_call(*cmd, cwd=gclient_dir)
37
38
39def sync(gclient_dir, with_branch_heads=False, with_tags=False, jobs=8):
40 """Simply wrapper of `gclient sync`.
41
42 Args:
43 gclient_dir: root directory of gclient project
44 with_branch_heads: whether to clone git `branch_heads` refspecs
45 with_tags: whether to clone git tags
46 jobs: how many workers running in parallel
47 """
48 cmd = ['gclient', 'sync', '--jobs', str(jobs)]
49 if with_branch_heads:
50 cmd.append('--with_branch_heads')
51 if with_tags:
52 cmd.append('--with_tags')
53 util.check_call(*cmd, cwd=gclient_dir)
54
55
Kuang-che Wub17b3b92018-09-04 18:12:11 +080056# Copied from depot_tools' gclient.py
57_PLATFORM_MAPPING = {
58 'cygwin': 'win',
59 'darwin': 'mac',
60 'linux2': 'linux',
61 'win32': 'win',
62 'aix6': 'aix',
63}
64
65
66def _detect_host_os():
67 return _PLATFORM_MAPPING[sys.platform]
68
69
Kuang-che Wu3eb6b502018-06-06 16:15:18 +080070def _eval_condition(condition, dep_vars, custom_vars):
71 """Evaluate condition for DEPS parsing.
72
73 Args:
74 condition: python expression
75 dep_vars: variables defined in the DEPS file
76 custom_vars: custom variables
77
78 Returns:
79 eval result
80 """
81 vars_dict = {
82 # default os: linux
83 'checkout_android': False,
84 'checkout_chromeos': False,
85 'checkout_fuchsia': False,
86 'checkout_ios': False,
87 'checkout_linux': True,
88 'checkout_mac': False,
89 'checkout_win': False,
90 # default cpu: x64
91 'checkout_arm64': False,
92 'checkout_arm': False,
93 'checkout_mips': False,
94 'checkout_ppc': False,
95 'checkout_s390': False,
96 'checkout_x64': True,
97 'checkout_x86': False,
Kuang-che Wub17b3b92018-09-04 18:12:11 +080098 'host_os': _detect_host_os(),
Kuang-che Wu3eb6b502018-06-06 16:15:18 +080099 'False': False,
100 'None': None,
101 'True': True,
102 }
103 vars_dict.update(dep_vars)
104 vars_dict.update(custom_vars)
105 # pylint: disable=eval-used
106 return eval(condition, vars_dict)
107
108
109def _normalize_dep(dep):
110 if isinstance(dep, str):
111 result = {'url': dep}
112 else:
113 assert isinstance(dep, dict)
114 result = dep.copy()
115 result.setdefault('dep_type', 'git')
116 return result
117
118
119def parse_deps(content, custom_vars):
120 """Parses DEPS file.
121
122 Args:
123 content: file content of DEPS file
124 custom_vars: custom variables
125
126 Returns:
127 path to PathSpec dict
128 """
129
130 def var_function(name):
131 return '{%s}' % name
132
133 global_scope = dict(Var=var_function)
134 local_scope = {}
135 try:
Kuang-che Wu6e4beca2018-06-27 17:45:02 +0800136 exec (content, global_scope, local_scope) # pylint: disable=exec-used
Kuang-che Wu3eb6b502018-06-06 16:15:18 +0800137 except SyntaxError:
138 raise
139
140 local_scope.setdefault('vars', {})
141 for name in local_scope['vars']:
142 if name.startswith('RECURSEDEPS_') or name.endswith('_DEPS_file'):
143 logger.warning('%s is deprecated and not supported recursion syntax',
144 name)
145 if 'recursedeps' in local_scope:
146 # TODO(kcwu): support recursedeps
147 logger.warning('recursedeps is not supported yet')
148
149 # Merge 'deps_os' deps into 'deps' dict.
150 for os_name, os_deps in local_scope.get('deps_os', {}).items():
151 os_condition = 'checkout_%s' % (os_name if os_name != 'unix' else 'linux')
152 if not _eval_condition(os_condition, local_scope['vars'], custom_vars):
153 continue
154
155 for path, os_dep in os_deps.items():
156 dep = local_scope['deps'].setdefault(path, {'condition': 'False'})
157 os_dep = _normalize_dep(os_dep)
158 if os_dep['dep_type'] != 'git' or not os_dep.get('url'):
159 logger.error('deps_os[%s][%s] should be git dep: %s', os_name, path,
160 os_dep)
161 continue
162 if 'url' in dep:
163 assert dep['url'] == os_dep['url']
164 else:
165 dep['url'] = os_dep['url']
166
167 new_condition = '(%s) or (%s)' % (dep.get('condition', 'True'),
168 os_dep.get('condition', 'True'))
169 dep['condition'] = new_condition
170
171 result = {}
172 for path, dep in local_scope['deps'].items():
173 dep = _normalize_dep(dep)
174 condition = dep.get('condition')
175 if condition and not _eval_condition(condition, local_scope['vars'],
176 custom_vars):
177 continue
178 # TODO(kcwu): support dep_type=cipd http://crbug.com/846564
179 assert dep['dep_type'] == 'git'
180 url = dep['url']
181
182 url = url.format(**local_scope['vars'])
183 repo_url, at = url.split('@')
184 result[path] = codechange.PathSpec(path, repo_url, at)
185 return result
186
187
188class GclientCache(codechange.CodeStorage):
189 """Gclient git cache."""
190
191 def __init__(self, cache_dir):
192 self.cache_dir = cache_dir
193
194 def _url_to_cache_dir(self, url):
195 # ref: depot_tools' git_cache.Mirror.UrlToCacheDir
196 parsed = urlparse.urlparse(url)
197 norm_url = parsed.netloc + parsed.path
198 if norm_url.endswith('.git'):
199 norm_url = norm_url[:-len('.git')]
200 return norm_url.replace('-', '--').replace('/', '-').lower()
201
202 def cached_git_root(self, repo_url):
203 cache_path = self._url_to_cache_dir(repo_url)
204 return os.path.join(self.cache_dir, cache_path)
Kuang-che Wu6948ecc2018-09-11 17:43:49 +0800205
206 def _load_project_list(self, project_root):
207 repo_project_list = os.path.join(project_root, '.gclient_entries')
208 scope = {}
209 exec open(repo_project_list) in scope # pylint: disable=exec-used
210 return scope.get('entries', {})
211
212 def _save_project_list(self, project_root, projects):
213 repo_project_list = os.path.join(project_root, '.gclient_entries')
214 content = 'entries = {\n'
215 for item in sorted(projects.items()):
216 content += ' %s: %s,\n' % map(pprint.pformat, item)
217 content += '}\n'
218 with open(repo_project_list, 'w') as f:
219 f.write(content)
220
221 def add_to_project_list(self, project_root, path, repo_url):
222 projects = self._load_project_list(project_root)
223
224 projects[path] = repo_url
225
226 self._save_project_list(project_root, projects)
227
228 def remove_from_project_list(self, project_root, path):
229 projects = self._load_project_list(project_root)
230
231 if path in projects:
232 del projects[path]
233
234 self._save_project_list(project_root, projects)