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