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