blob: 8802d78e91f2fcfdb99ec6f6a3ae35c2b1501f46 [file] [log] [blame]
Ryan Cuib603c542011-07-27 16:00:11 -07001#!/usr/bin/python
2# Copyright (c) 2011 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
6"""Script that resets your Chrome GIT checkout."""
7
Chris Sosa4f6ffaf2012-05-01 17:05:44 -07008import logging
Ryan Cuib603c542011-07-27 16:00:11 -07009import optparse
10import os
Ryan Cui7d7c88b2011-11-01 22:11:55 -070011import re
Ryan Cuib603c542011-07-27 16:00:11 -070012
Brian Harring503f3ab2012-03-09 21:39:41 -080013from chromite.buildbot import repository
Brian Harring1b8c4c82012-05-29 23:03:04 -070014from chromite.lib import cros_build_lib
David James97d95872012-11-16 15:09:56 -080015from chromite.lib import git
Brian Harringaf019fb2012-05-10 15:06:13 -070016from chromite.lib import osutils
Ryan Cuib603c542011-07-27 16:00:11 -070017
18
Ryan Cui44360de2011-08-22 16:50:20 -070019_CHROMIUM_ROOT = 'chromium'
20_CHROMIUM_SRC_ROOT = os.path.join(_CHROMIUM_ROOT, 'src')
Ryan Cuid1024a82011-08-26 17:58:36 -070021_CHROMIUM_SRC_INTERNAL = os.path.join(_CHROMIUM_ROOT, 'src-internal')
Ben Chan84ff1182012-07-02 16:17:10 -070022_CHROMIUM_SRC_DEPS = os.path.join(_CHROMIUM_SRC_ROOT, 'DEPS')
23_CHROMIUM_CROS_DEP_KEYS = [ 'src/third_party/cros_system_api' ]
Ryan Cuib603c542011-07-27 16:00:11 -070024
25
26def _LoadDEPS(deps_content):
27 """Load contents of DEPS file into a dictionary.
28
29 Arguments:
30 deps_content: The contents of the .DEPS.git file.
31
32 Returns:
33 A dictionary indexed by the top level items in the structure - i.e.,
34 'hooks', 'deps', 'os_deps'.
35 """
36 class FromImpl:
37 """From syntax is not supported."""
38 def __init__(self, module_name, var_name):
39 raise NotImplementedError('The From() syntax is not supported in'
40 'chrome_set_ver.')
41
42 class _VarImpl:
Ryan Cui446e8e72011-09-23 18:02:41 -070043 def __init__(self, custom_vars, local_scope):
Ryan Cuib603c542011-07-27 16:00:11 -070044 self._custom_vars = custom_vars
45 self._local_scope = local_scope
46
47 def Lookup(self, var_name):
48 """Implements the Var syntax."""
49 if var_name in self._custom_vars:
50 return self._custom_vars[var_name]
51 elif var_name in self._local_scope.get('vars', {}):
52 return self._local_scope['vars'][var_name]
53 raise Exception('Var is not defined: %s' % var_name)
54
David James43e14f42012-12-14 13:44:16 -080055 my_locals = {}
56 var = _VarImpl({}, my_locals)
57 my_globals = {'From': FromImpl, 'Var': var.Lookup, 'deps_os': {}}
58 exec(deps_content) in my_globals, my_locals
59 return my_locals
Ryan Cuib603c542011-07-27 16:00:11 -070060
61
Ryan Cui446e8e72011-09-23 18:02:41 -070062def _CreateCrosSymlink(repo_root):
63 """Create symlinks to CrOS projects specified in the cros_DEPS/DEPS file."""
Ben Chan84ff1182012-07-02 16:17:10 -070064 deps_file = os.path.join(repo_root, _CHROMIUM_SRC_DEPS)
65 _, merged_deps = GetParsedDeps(deps_file)
Ryan Cui446e8e72011-09-23 18:02:41 -070066 chromium_root = os.path.join(repo_root, _CHROMIUM_ROOT)
Ryan Cuiaf3e2eb2011-09-30 16:43:03 -070067
Ben Chan84ff1182012-07-02 16:17:10 -070068 # TODO(rcui): Infer Chromium OS dependencies from the Chromium DEPS file.
69 cros_merged_deps = dict((k, v) for (k, v) in merged_deps.iteritems()
70 if k in _CHROMIUM_CROS_DEP_KEYS)
71 mappings = GetPathToProjectMappings(cros_merged_deps)
Ryan Cui446e8e72011-09-23 18:02:41 -070072 for rel_path, project in mappings.iteritems():
73 link_dir = os.path.join(chromium_root, rel_path)
74 target_dir = os.path.join(repo_root,
David James97d95872012-11-16 15:09:56 -080075 git.GetProjectDir(repo_root, project))
Ryan Cui446e8e72011-09-23 18:02:41 -070076 path_to_target = os.path.relpath(target_dir, os.path.dirname(link_dir))
77 if not os.path.exists(link_dir):
78 os.symlink(path_to_target, link_dir)
79
80
81def _ExtractProjectFromUrl(repo_url):
82 """Get the Gerrit project name from an url."""
Ryan Cui7d7c88b2011-11-01 22:11:55 -070083 # Example: 'ssh://gerrit-int.chromium.org:12121/my/project.git' would parse to
84 # 'my/project'. See unit test for more examples. For 'URL's like
85 # '../abc/efg' leave as-is to support unit tests.
86 mo = re.match(r'(?:[\w\+]+://[^/]+/)?(.*)', repo_url)
87 return os.path.splitext(mo.group(1))[0]
Ryan Cuib603c542011-07-27 16:00:11 -070088
89
Ryan Cuid1024a82011-08-26 17:58:36 -070090def _ExtractProjectFromEntry(entry):
91 """From a deps entry extract the Gerrit project name.
92
93 Arguments:
Ryan Cui65e1f9a2011-09-21 17:06:43 -070094 entry: The deps entry in the format ${url_prefix}/${project_name}@${hash}.
Ryan Cuid1024a82011-08-26 17:58:36 -070095 """
96 # We only support Gerrit urls, where the path is the project name.
97 repo_url = entry.partition('@')[0]
Ryan Cui446e8e72011-09-23 18:02:41 -070098 return _ExtractProjectFromUrl(repo_url)
Ryan Cuid1024a82011-08-26 17:58:36 -070099
100
101def GetPathToProjectMappings(deps):
102 """Get dictionary relating path to Gerrit project names.
103
104 Arguments:
105 deps: a dictionary indexed by repo paths. The same format as the 'deps'
106 entry in the '.DEPS.git' file.
107 """
108 mappings = {}
109 for rel_path, entry in deps.iteritems():
110 mappings[rel_path] = _ExtractProjectFromEntry(entry)
111
112 return mappings
113
114
Ryan Cui446e8e72011-09-23 18:02:41 -0700115class ProjectException(Exception):
116 """Raised by Project class when a Pin error """
117 pass
Ryan Cuib75c3032011-08-10 16:19:39 -0700118
119
Brian Harring609dc4e2012-05-07 02:17:44 -0700120def _IsGitStoreInRepo(path):
121 """Checks if the git repo rooted at a directory is in repo's storage.
122
123 Note that just because a pathway is in .repo, does *not* mean that
124 repo can actually use it (the git repo must be in the manifest for
125 that to be true).
126 """
David James97d95872012-11-16 15:09:56 -0800127 repo_dir = os.path.realpath(git.FindRepoDir(path))
Brian Harring609dc4e2012-05-07 02:17:44 -0700128 git_objects_dir = os.path.realpath(os.path.join(path, '.git/objects'))
129 return git_objects_dir.startswith(repo_dir)
130
131
Ryan Cui446e8e72011-09-23 18:02:41 -0700132class Project(object):
133 """Encapsulates functionality to pin a project to a specific commit."""
134 def __init__(self, repo_root, project_url, rel_path):
135 """Construct the class.
136
137 Arguments:
138 repo_root: The root of the repo checkout.
139 project_url: The Gerrit url of the project.
140 rel_path: The path the project is expected to be checked out to. Relative
141 to <repo_root>/chromium.
142 """
143 self.repo_root = repo_root
144 self.chromium_root = os.path.join(self.repo_root, _CHROMIUM_ROOT)
145 self.project_url = project_url
146 self.rel_path = rel_path
147 self.abs_path = os.path.join(self.chromium_root, self.rel_path)
148 self.manifest_rel_path = os.path.join(_CHROMIUM_ROOT, self.rel_path)
149 self.project_name = _ExtractProjectFromUrl(self.project_url)
150
151 def _ResetProject(self, commit_hash):
152 """Actually pin project to the specified commit hash."""
David James97d95872012-11-16 15:09:56 -0800153 if not git.DoesCommitExistInRepo(self.abs_path, commit_hash):
Brian Harring1b8c4c82012-05-29 23:03:04 -0700154 cros_build_lib.Die(
155 'Commit %s not found in %s.\n'
156 "You probably need to run 'repo sync --jobs=<jobs>' "
157 'to update your checkout.'
158 % (commit_hash, self.abs_path))
Ryan Cui446e8e72011-09-23 18:02:41 -0700159
Brian Harring1b8c4c82012-05-29 23:03:04 -0700160 result = cros_build_lib.RunCommand(['git', 'checkout', commit_hash],
161 error_code_ok=True, cwd=self.abs_path)
Ryan Cui446e8e72011-09-23 18:02:41 -0700162 if result.returncode != 0:
Brian Harring1b8c4c82012-05-29 23:03:04 -0700163 cros_build_lib.Warning('Failed to pin project %s.\n'
164 'You probably have uncommited local changes.'
165 % self.abs_path)
Ryan Cui446e8e72011-09-23 18:02:41 -0700166
167 def _PrepareProject(self):
168 """Make sure the project is synced properly and is ready for pinning."""
David James97d95872012-11-16 15:09:56 -0800169 handler = git.ManifestCheckout.Cached(self.repo_root)
David James1bd7fae2012-04-22 10:14:38 -0700170 path_to_project_dict = dict(([attrs['path'], project]) for project, attrs
171 in handler.projects.iteritems())
Ryan Cui446e8e72011-09-23 18:02:41 -0700172
173 # TODO(rcui): Handle case where a dependency never makes it to the manifest
174 # (i.e., dep path added as double checkout, and then gets deleted). We need
175 # to delete those. crosbug/22123.
David James97d95872012-11-16 15:09:56 -0800176 if not git.IsGitRepo(self.abs_path):
Ryan Cui446e8e72011-09-23 18:02:41 -0700177 if self.manifest_rel_path in path_to_project_dict:
178 raise ProjectException('%s in full layout manifest but not in working '
179 "tree. Please run 'repo sync %s'"
180 % (self.manifest_rel_path,
181 path_to_project_dict[self.manifest_rel_path]))
182 else:
Brian Harring1b8c4c82012-05-29 23:03:04 -0700183 cros_build_lib.Warning(
184 'Project %s is not in the manifest. Automatically checking out '
185 'to %s.\n' % (self.project_url, self.abs_path))
Ryan Cui446e8e72011-09-23 18:02:41 -0700186 repository.CloneGitRepo(self.abs_path, self.project_url)
Brian Harring1b8c4c82012-05-29 23:03:04 -0700187 cros_build_lib.RunCommand(
188 ['git', 'checkout',
David James97d95872012-11-16 15:09:56 -0800189 git.GetGitRepoRevision(self.abs_path)],
Brian Harring1b8c4c82012-05-29 23:03:04 -0700190 cwd=self.abs_path)
Brian Harring609dc4e2012-05-07 02:17:44 -0700191 elif not _IsGitStoreInRepo(self.abs_path):
Ryan Cui446e8e72011-09-23 18:02:41 -0700192 if self.manifest_rel_path in path_to_project_dict:
193 # If path is now in the manifest, tell user to manually delete our
194 # managed checkout and re-sync.
195 raise ProjectException('%s needs to be replaced. Please remove the '
196 "directory and run 'repo sync %s'"
197 % (self.manifest_rel_path,
198 path_to_project_dict[self.manifest_rel_path]))
199 else:
200 # If not managed by Repo we need to perform sync.
Brian Harring1b8c4c82012-05-29 23:03:04 -0700201 cros_build_lib.RunCommand(['git', 'pull', '--rebase', self.project_url],
202 cwd=self.abs_path)
Ryan Cui446e8e72011-09-23 18:02:41 -0700203 elif not os.path.islink(self.abs_path):
204 # Skip symlinks - we don't want to error out for the cros.DEPS projects.
205 if self.manifest_rel_path not in path_to_project_dict:
206 # If it is 'managed by repo' but not in the manifest, repo tried
207 # deleting it but failed because of local changes.
208 raise ProjectException('%s is no longer in the manifest but has local '
209 'changes. Please remove and try again.'
210 % self.manifest_rel_path)
211 elif self.project_name != path_to_project_dict[self.manifest_rel_path]:
Brian Harring1b8c4c82012-05-29 23:03:04 -0700212 cros_build_lib.Die(
213 '.DEPS.git for %s conflicts with manifest.xml! Running with '
214 'older .DEPS.git files are not yet supported. '
215 "Please run'repo sync --jobs=<jobs>' to sync everything up."
216 % self.manifest_rel_path)
Ryan Cui446e8e72011-09-23 18:02:41 -0700217
218 def Pin(self, commit_hash):
219 """Attempt to pin the project to the specified commit hash.
220
221 Arguments:
222 commit_hash: The commit to pin the project to.
223
224 Raises:
225 ProjectException when an error occurs.
226 """
227 self._PrepareProject()
David James97d95872012-11-16 15:09:56 -0800228 if git.GetCurrentBranch(self.abs_path):
Brian Harring1b8c4c82012-05-29 23:03:04 -0700229 cros_build_lib.Warning("Not pinning project %s that's checked out to a "
230 'development branch.' % self.rel_path)
Ryan Cui446e8e72011-09-23 18:02:41 -0700231 elif (commit_hash and
David James97d95872012-11-16 15:09:56 -0800232 (commit_hash != git.GetGitRepoRevision(self.abs_path))):
Ryan Cui446e8e72011-09-23 18:02:41 -0700233 print 'Pinning project %s' % self.rel_path
234 self._ResetProject(commit_hash)
235 else:
Brian Harring1b8c4c82012-05-29 23:03:04 -0700236 cros_build_lib.Debug('Skipping project %s, already pinned'
237 % self.rel_path)
Ryan Cui446e8e72011-09-23 18:02:41 -0700238
239
240def _ResetGitCheckout(repo_root, deps):
Ryan Cuib603c542011-07-27 16:00:11 -0700241 """Reset chrome repos to hashes specified in the DEPS file.
242
243 Arguments:
244 chromium_root: directory where chromium is checked out - level above 'src'.
245 deps: a dictionary indexed by repo paths. The same format as the 'deps'
246 entry in the '.DEPS.git' file.
247 """
Ryan Cui446e8e72011-09-23 18:02:41 -0700248 errors = []
Ryan Cuib603c542011-07-27 16:00:11 -0700249 for rel_path, project_hash in deps.iteritems():
Ryan Cuid1024a82011-08-26 17:58:36 -0700250 repo_url, _, commit_hash = project_hash.partition('@')
Ryan Cui446e8e72011-09-23 18:02:41 -0700251 project = Project(repo_root, repo_url, rel_path)
252 try:
253 project.Pin(commit_hash)
254 except ProjectException as e:
255 errors.append(str(e))
Ryan Cui65e1f9a2011-09-21 17:06:43 -0700256
Ryan Cui446e8e72011-09-23 18:02:41 -0700257 if errors:
Brian Harring1b8c4c82012-05-29 23:03:04 -0700258 cros_build_lib.Die('Errors encountered:\n* ' + '\n* '.join(errors))
Ryan Cuib603c542011-07-27 16:00:11 -0700259
260
261def _RunHooks(chromium_root, hooks):
262 """Run the hooks contained in the DEPS file.
263
264 Arguments:
265 chromium_root: directory where chromium is checked out - level above 'src'.
266 hooks: a list of hook dictionaries. The same format as the 'hooks' entry
267 in the '.DEPS.git' file.
268 """
269 for hook in hooks:
Ryan Cuiaf3e2eb2011-09-30 16:43:03 -0700270 print '[running hook] %s' % ' '.join(hook['action'])
Brian Harring1b8c4c82012-05-29 23:03:04 -0700271 cros_build_lib.RunCommand(hook['action'], cwd=chromium_root)
Ryan Cuib603c542011-07-27 16:00:11 -0700272
273
Ryan Cui44360de2011-08-22 16:50:20 -0700274def _MergeDeps(dest, update):
275 """Merge the dependencies specified in two dictionaries.
276
277 Arguments:
278 dest: The dictionary that will be merged into.
279 update: The dictionary whose elements will be merged into dest.
280 """
281 assert(not set(dest.keys()).intersection(set(update.keys())))
282 dest.update(update)
283 return dest
284
285
Ryan Cui97b2f302011-10-24 17:34:05 -0700286def GetParsedDeps(deps_file):
Ryan Cui6c851112011-08-11 21:51:37 -0700287 """Returns the full parsed DEPS file dictionary, and merged deps.
288
289 Arguments:
Ryan Cui65e1f9a2011-09-21 17:06:43 -0700290 deps_file: Path to the .DEPS.git file.
Ryan Cui6c851112011-08-11 21:51:37 -0700291
292 Returns:
293 An (x,y) tuple. x is a dictionary containing the contents of the DEPS file,
294 and y is a dictionary containing the result of merging unix and common deps.
295 """
Brian Harringaf019fb2012-05-10 15:06:13 -0700296 deps = _LoadDEPS(osutils.ReadFile(deps_file, 'rU'))
Ryan Cui44360de2011-08-22 16:50:20 -0700297 merged_deps = deps.get('deps', {})
Ryan Cuid1024a82011-08-26 17:58:36 -0700298 unix_deps = deps.get('deps_os', {}).get('unix', {})
Ryan Cui44360de2011-08-22 16:50:20 -0700299 merged_deps = _MergeDeps(merged_deps, unix_deps)
Ryan Cui6c851112011-08-11 21:51:37 -0700300 return deps, merged_deps
301
302
Brian Harring30675052012-02-29 12:18:22 -0800303def main(argv):
Ryan Cuib603c542011-07-27 16:00:11 -0700304
Ryan Cui9ef7c922011-08-17 11:48:02 -0700305 usage = 'usage: %prog [-d <DEPS.git file>] [command]'
306 parser = optparse.OptionParser(usage=usage)
Ryan Cui6c851112011-08-11 21:51:37 -0700307
Ryan Cui65e1f9a2011-09-21 17:06:43 -0700308 # TODO(rcui): have -d accept a URL.
Ryan Cuib603c542011-07-27 16:00:11 -0700309 parser.add_option('-d', '--deps', default=None,
310 help=('DEPS file to use. Defaults to '
311 '<chrome_src_root>/.DEPS.git'))
312 parser.add_option('--internal', action='store_false', dest='internal',
313 default=True, help='Allow chrome-internal URLs')
Ryan Cui9ef7c922011-08-17 11:48:02 -0700314 parser.add_option('--runhooks', action='store_true', dest='runhooks',
315 default=False, help="Run hooks as well.")
Ryan Cui65e1f9a2011-09-21 17:06:43 -0700316 parser.add_option('-v', '--verbose', default=False, action='store_true',
317 help='Run with debug output.')
Ryan Cui446e8e72011-09-23 18:02:41 -0700318 (options, _inputs) = parser.parse_args(argv)
Ryan Cuib603c542011-07-27 16:00:11 -0700319
Ryan Cui65e1f9a2011-09-21 17:06:43 -0700320 # Set cros_build_lib debug level to hide RunCommand spew.
321 if options.verbose:
Brian Harring1b8c4c82012-05-29 23:03:04 -0700322 cros_build_lib.logger.setLevel(logging.DEBUG)
Ryan Cui65e1f9a2011-09-21 17:06:43 -0700323 else:
Brian Harring1b8c4c82012-05-29 23:03:04 -0700324 cros_build_lib.logger.setLevel(logging.WARNING)
Ryan Cui65e1f9a2011-09-21 17:06:43 -0700325
Brian Harring1b8c4c82012-05-29 23:03:04 -0700326 if cros_build_lib.IsInsideChroot():
Ryan Cuie52602e2011-11-03 15:54:35 -0700327 ssh_path = '/usr/bin/ssh_no_update'
328 if os.path.isfile(ssh_path):
329 os.environ['GIT_SSH'] = ssh_path
330 else:
Brian Harring1b8c4c82012-05-29 23:03:04 -0700331 cros_build_lib.Warning(
332 "Can't find %s. Run build_packages or setup_board to update "
333 "your choot." % ssh_path)
Ryan Cuie52602e2011-11-03 15:54:35 -0700334
David James97d95872012-11-16 15:09:56 -0800335 repo_root = git.FindRepoCheckoutRoot()
Ryan Cuib75c3032011-08-10 16:19:39 -0700336 chromium_src_root = os.path.join(repo_root, _CHROMIUM_SRC_ROOT)
Ryan Cui4f492d92012-09-20 16:36:00 -0700337 if not os.path.isdir(os.path.join(chromium_src_root, '.git')):
338 error_msg = 'chromium checkout not found at %s.\n' % chromium_src_root
339
340 manifest = os.path.join(repo_root, '.repo', 'manifest.xml')
341 if os.path.basename(os.path.realpath(manifest)) == 'gerrit-source.xml':
342 error_msg += ("Please run repo sync and try again.")
343 else:
344 link = 'http://www.chromium.org/chromium-os/new-chrome-developer-workflow'
345 error_msg += ('Detected you are not using gerrit_source.xml. Follow '
346 'instructions at %s to use repo checkout of chrome.' % link)
347
348 cros_build_lib.Die(error_msg)
Ryan Cuib603c542011-07-27 16:00:11 -0700349
Ryan Cui65e1f9a2011-09-21 17:06:43 -0700350 # Add DEPS files to parse.
Ryan Cui44360de2011-08-22 16:50:20 -0700351 deps_files_to_parse = []
Ryan Cuib603c542011-07-27 16:00:11 -0700352 if options.deps:
Ryan Cui44360de2011-08-22 16:50:20 -0700353 deps_files_to_parse.append(options.deps)
354 else:
355 deps_files_to_parse.append(os.path.join(chromium_src_root, '.DEPS.git'))
Ryan Cuib603c542011-07-27 16:00:11 -0700356
Ryan Cuid1024a82011-08-26 17:58:36 -0700357 internal_deps = os.path.join(repo_root, _CHROMIUM_SRC_INTERNAL, '.DEPS.git')
358 if os.path.exists(internal_deps):
359 deps_files_to_parse.append(internal_deps)
360
Ryan Cui65e1f9a2011-09-21 17:06:43 -0700361 # Prepare source tree for resetting.
Ryan Cui44360de2011-08-22 16:50:20 -0700362 chromium_root = os.path.join(repo_root, _CHROMIUM_ROOT)
Ryan Cuib75c3032011-08-10 16:19:39 -0700363 _CreateCrosSymlink(repo_root)
Ryan Cui44360de2011-08-22 16:50:20 -0700364
Ryan Cui65e1f9a2011-09-21 17:06:43 -0700365 # Parse DEPS files and store hooks.
Ryan Cui44360de2011-08-22 16:50:20 -0700366 hook_dicts = []
367 for deps_file in deps_files_to_parse:
Ryan Cui97b2f302011-10-24 17:34:05 -0700368 deps, merged_deps = GetParsedDeps(deps_file)
Ryan Cui446e8e72011-09-23 18:02:41 -0700369 _ResetGitCheckout(repo_root, merged_deps)
Ryan Cui44360de2011-08-22 16:50:20 -0700370 hook_dicts.append(deps.get('hooks', {}))
371
Ryan Cui65e1f9a2011-09-21 17:06:43 -0700372 # Run hooks after checkout has been reset properly.
Ryan Cui44360de2011-08-22 16:50:20 -0700373 if options.runhooks:
374 for hooks in hook_dicts:
375 _RunHooks(chromium_root, hooks)