blob: 4ef40ead0aa4de7509120cec43d1dbf2d1e2eb16 [file] [log] [blame]
kjellander@webrtc.org89256622014-08-20 12:10:11 +00001#!/usr/bin/env python
2# Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
3#
4# Use of this source code is governed by a BSD-style license
5# that can be found in the LICENSE file in the root of the source
6# tree. An additional intellectual property rights grant can be found
7# in the file PATENTS. All contributing project authors may
8# be found in the AUTHORS file in the root of the source tree.
9
10"""Setup links to a Chromium checkout for WebRTC.
11
12WebRTC standalone shares a lot of dependencies and build tools with Chromium.
13To do this, many of the paths of a Chromium checkout is emulated by creating
14symlinks to files and directories. This script handles the setup of symlinks to
15achieve this.
16
17It also handles cleanup of the legacy Subversion-based approach that was used
18before Chrome switched over their master repo from Subversion to Git.
19"""
20
21
22import ctypes
23import errno
24import logging
25import optparse
26import os
27import shelve
28import shutil
29import subprocess
30import sys
31import textwrap
32
33
34DIRECTORIES = [
35 'build',
36 'buildtools',
kjellander@webrtc.org89256622014-08-20 12:10:11 +000037 'testing',
kjellander@webrtc.org89256622014-08-20 12:10:11 +000038 'third_party/binutils',
39 'third_party/boringssl',
40 'third_party/colorama',
41 'third_party/drmemory',
42 'third_party/expat',
kjellander@webrtc.org4e4fe4f2014-10-01 08:03:19 +000043 'third_party/instrumented_libraries',
kjellander@webrtc.org89256622014-08-20 12:10:11 +000044 'third_party/jsoncpp',
Henrik Kjellander26ab91b2015-11-26 10:26:32 +010045 'third_party/libc++-static',
kjellander@webrtc.org89256622014-08-20 12:10:11 +000046 'third_party/libjpeg',
47 'third_party/libjpeg_turbo',
48 'third_party/libsrtp',
kjellander@webrtc.org7d4e6d02014-11-27 10:41:04 +000049 'third_party/libudev',
kjellanderd6024e32015-09-28 21:16:48 -070050 'third_party/libvpx_new',
kjellander@webrtc.org89256622014-08-20 12:10:11 +000051 'third_party/libyuv',
52 'third_party/llvm-build',
Patrik Höglundc92c23d2015-08-31 11:30:14 +020053 'third_party/lss',
kjellander@webrtc.org89256622014-08-20 12:10:11 +000054 'third_party/nss',
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +000055 'third_party/ocmock',
kjellander@webrtc.org89256622014-08-20 12:10:11 +000056 'third_party/openmax_dl',
57 'third_party/opus',
Patrik Höglundc92c23d2015-08-31 11:30:14 +020058 'third_party/proguard',
kjellander@webrtc.org89256622014-08-20 12:10:11 +000059 'third_party/protobuf',
60 'third_party/sqlite',
61 'third_party/syzygy',
62 'third_party/usrsctp',
63 'third_party/yasm',
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +000064 'third_party/zlib',
kjellander@webrtc.org89256622014-08-20 12:10:11 +000065 'tools/clang',
66 'tools/generate_library_loader',
67 'tools/gn',
68 'tools/gyp',
69 'tools/memory',
70 'tools/protoc_wrapper',
71 'tools/python',
72 'tools/swarming_client',
73 'tools/valgrind',
Andrew MacDonald65de7d22015-05-19 11:37:34 -070074 'tools/vim',
kjellander@webrtc.org89256622014-08-20 12:10:11 +000075 'tools/win',
Henrik Kjellander9589e2a2015-10-22 06:48:21 +020076 'tools/xdisplaycheck',
kjellander@webrtc.org89256622014-08-20 12:10:11 +000077]
78
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +000079from sync_chromium import get_target_os_list
Henrik Kjellanderca843022015-06-09 10:51:22 +020080target_os = get_target_os_list()
81if 'android' in target_os:
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +000082 DIRECTORIES += [
83 'base',
Henrik Kjellander94a12322015-06-09 14:56:20 +020084 'third_party/android_platform',
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +000085 'third_party/android_tools',
kjellander@webrtc.orgcbe7ca82015-01-06 07:24:27 +000086 'third_party/appurify-python',
kjellander@webrtc.orgb8caf6a2014-09-30 18:05:02 +000087 'third_party/ashmem',
kjellander34a70542015-12-06 10:32:34 -080088 'third_party/catapult',
kjellander53761002015-11-10 10:58:22 -080089 'third_party/icu',
Henrik Kjellandereecbab72015-09-16 19:19:04 +020090 'third_party/ijar',
kjellander@webrtc.orgb8caf6a2014-09-30 18:05:02 +000091 'third_party/jsr-305',
Henrik Kjellander10ba3ee2015-04-29 14:47:53 +020092 'third_party/junit',
kjellander@webrtc.orgb8caf6a2014-09-30 18:05:02 +000093 'third_party/libevent',
94 'third_party/libxml',
Henrik Kjellander10ba3ee2015-04-29 14:47:53 +020095 'third_party/mockito',
kjellander@webrtc.orgb8caf6a2014-09-30 18:05:02 +000096 'third_party/modp_b64',
kjellander@webrtc.orgcbe7ca82015-01-06 07:24:27 +000097 'third_party/requests',
Henrik Kjellander10ba3ee2015-04-29 14:47:53 +020098 'third_party/robolectric',
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +000099 'tools/android',
kjellander@webrtc.orgbfdee692015-02-03 15:23:34 +0000100 'tools/grit',
kjellander34a70542015-12-06 10:32:34 -0800101 'tools/telemetry',
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +0000102 ]
Henrik Kjellanderca843022015-06-09 10:51:22 +0200103if 'ios' in target_os:
104 DIRECTORIES.append('third_party/class-dump')
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +0000105
kjellander@webrtc.org89256622014-08-20 12:10:11 +0000106FILES = {
Henrik Kjellanderd6d27e72015-09-25 22:19:11 +0200107 'tools/isolate_driver.py': None,
kjellander@webrtc.org89256622014-08-20 12:10:11 +0000108 'third_party/BUILD.gn': None,
kjellander@webrtc.org89256622014-08-20 12:10:11 +0000109}
110
kjellander@webrtc.orge94f83a2014-09-18 13:47:23 +0000111ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
kjellander@webrtc.org89256622014-08-20 12:10:11 +0000112CHROMIUM_CHECKOUT = os.path.join('chromium', 'src')
113LINKS_DB = 'links'
114
115# Version management to make future upgrades/downgrades easier to support.
116SCHEMA_VERSION = 1
117
118
119def query_yes_no(question, default=False):
120 """Ask a yes/no question via raw_input() and return their answer.
121
122 Modified from http://stackoverflow.com/a/3041990.
123 """
124 prompt = " [%s/%%s]: "
125 prompt = prompt % ('Y' if default is True else 'y')
126 prompt = prompt % ('N' if default is False else 'n')
127
128 if default is None:
129 default = 'INVALID'
130
131 while True:
132 sys.stdout.write(question + prompt)
133 choice = raw_input().lower()
134 if choice == '' and default != 'INVALID':
135 return default
136
137 if 'yes'.startswith(choice):
138 return True
139 elif 'no'.startswith(choice):
140 return False
141
142 print "Please respond with 'yes' or 'no' (or 'y' or 'n')."
143
144
145# Actions
146class Action(object):
147 def __init__(self, dangerous):
148 self.dangerous = dangerous
149
150 def announce(self, planning):
151 """Log a description of this action.
152
153 Args:
154 planning - True iff we're in the planning stage, False if we're in the
155 doit stage.
156 """
157 pass
158
159 def doit(self, links_db):
160 """Execute the action, recording what we did to links_db, if necessary."""
161 pass
162
163
164class Remove(Action):
165 def __init__(self, path, dangerous):
166 super(Remove, self).__init__(dangerous)
167 self._priority = 0
168 self._path = path
169
170 def announce(self, planning):
171 log = logging.warn
172 filesystem_type = 'file'
173 if not self.dangerous:
174 log = logging.info
175 filesystem_type = 'link'
176 if planning:
177 log('Planning to remove %s: %s', filesystem_type, self._path)
178 else:
179 log('Removing %s: %s', filesystem_type, self._path)
180
Henrik Kjellander57e5fd22015-05-25 12:55:39 +0200181 def doit(self, _):
kjellander@webrtc.org89256622014-08-20 12:10:11 +0000182 os.remove(self._path)
183
184
185class Rmtree(Action):
186 def __init__(self, path):
187 super(Rmtree, self).__init__(dangerous=True)
188 self._priority = 0
189 self._path = path
190
191 def announce(self, planning):
192 if planning:
193 logging.warn('Planning to remove directory: %s', self._path)
194 else:
195 logging.warn('Removing directory: %s', self._path)
196
Henrik Kjellander57e5fd22015-05-25 12:55:39 +0200197 def doit(self, _):
kjellander@webrtc.org89256622014-08-20 12:10:11 +0000198 if sys.platform.startswith('win'):
199 # shutil.rmtree() doesn't work on Windows if any of the directories are
200 # read-only, which svn repositories are.
201 subprocess.check_call(['rd', '/q', '/s', self._path], shell=True)
202 else:
203 shutil.rmtree(self._path)
204
205
206class Makedirs(Action):
207 def __init__(self, path):
208 super(Makedirs, self).__init__(dangerous=False)
209 self._priority = 1
210 self._path = path
211
Henrik Kjellander57e5fd22015-05-25 12:55:39 +0200212 def doit(self, _):
kjellander@webrtc.org89256622014-08-20 12:10:11 +0000213 try:
214 os.makedirs(self._path)
215 except OSError as e:
216 if e.errno != errno.EEXIST:
217 raise
218
219
220class Symlink(Action):
221 def __init__(self, source_path, link_path):
222 super(Symlink, self).__init__(dangerous=False)
223 self._priority = 2
224 self._source_path = source_path
225 self._link_path = link_path
226
227 def announce(self, planning):
228 if planning:
229 logging.info(
230 'Planning to create link from %s to %s', self._link_path,
231 self._source_path)
232 else:
233 logging.debug(
234 'Linking from %s to %s', self._link_path, self._source_path)
235
236 def doit(self, links_db):
237 # Files not in the root directory need relative path calculation.
238 # On Windows, use absolute paths instead since NTFS doesn't seem to support
239 # relative paths for symlinks.
240 if sys.platform.startswith('win'):
241 source_path = os.path.abspath(self._source_path)
242 else:
243 if os.path.dirname(self._link_path) != self._link_path:
244 source_path = os.path.relpath(self._source_path,
245 os.path.dirname(self._link_path))
246
247 os.symlink(source_path, os.path.abspath(self._link_path))
248 links_db[self._source_path] = self._link_path
249
250
251class LinkError(IOError):
252 """Failed to create a link."""
253 pass
254
255
256# Handles symlink creation on the different platforms.
257if sys.platform.startswith('win'):
258 def symlink(source_path, link_path):
259 flag = 1 if os.path.isdir(source_path) else 0
260 if not ctypes.windll.kernel32.CreateSymbolicLinkW(
261 unicode(link_path), unicode(source_path), flag):
262 raise OSError('Failed to create symlink to %s. Notice that only NTFS '
263 'version 5.0 and up has all the needed APIs for '
264 'creating symlinks.' % source_path)
265 os.symlink = symlink
266
267
Henrik Kjellander57e5fd22015-05-25 12:55:39 +0200268class WebRTCLinkSetup(object):
kjellander@webrtc.org89256622014-08-20 12:10:11 +0000269 def __init__(self, links_db, force=False, dry_run=False, prompt=False):
270 self._force = force
271 self._dry_run = dry_run
272 self._prompt = prompt
273 self._links_db = links_db
274
275 def CreateLinks(self, on_bot):
276 logging.debug('CreateLinks')
277 # First, make a plan of action
278 actions = []
279
280 for source_path, link_path in FILES.iteritems():
281 actions += self._ActionForPath(
282 source_path, link_path, check_fn=os.path.isfile, check_msg='files')
283 for source_dir in DIRECTORIES:
284 actions += self._ActionForPath(
285 source_dir, None, check_fn=os.path.isdir,
286 check_msg='directories')
287
kjellander@webrtc.orge94f83a2014-09-18 13:47:23 +0000288 if not on_bot and self._force:
289 # When making the manual switch from legacy SVN checkouts to the new
290 # Git-based Chromium DEPS, the .gclient_entries file that contains cached
291 # URLs for all DEPS entries must be removed to avoid future sync problems.
292 entries_file = os.path.join(os.path.dirname(ROOT_DIR), '.gclient_entries')
293 if os.path.exists(entries_file):
294 actions.append(Remove(entries_file, dangerous=True))
295
kjellander@webrtc.org89256622014-08-20 12:10:11 +0000296 actions.sort()
297
298 if self._dry_run:
299 for action in actions:
300 action.announce(planning=True)
301 logging.info('Not doing anything because dry-run was specified.')
302 sys.exit(0)
303
304 if any(a.dangerous for a in actions):
305 logging.warn('Dangerous actions:')
306 for action in (a for a in actions if a.dangerous):
307 action.announce(planning=True)
308 print
309
310 if not self._force:
311 logging.error(textwrap.dedent("""\
312 @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
313 A C T I O N R E Q I R E D
314 @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
315
316 Because chromium/src is transitioning to Git (from SVN), we needed to
317 change the way that the WebRTC standalone checkout works. Instead of
318 individually syncing subdirectories of Chromium in SVN, we're now
319 syncing Chromium (and all of its DEPS, as defined by its own DEPS file),
320 into the `chromium/src` directory.
321
322 As such, all Chromium directories which are currently pulled by DEPS are
323 now replaced with a symlink into the full Chromium checkout.
324
325 To avoid disrupting developers, we've chosen to not delete your
326 directories forcibly, in case you have some work in progress in one of
327 them :).
328
329 ACTION REQUIRED:
330 Before running `gclient sync|runhooks` again, you must run:
331 %s%s --force
332
333 Which will replace all directories which now must be symlinks, after
334 prompting with a summary of the work-to-be-done.
335 """), 'python ' if sys.platform.startswith('win') else '', sys.argv[0])
336 sys.exit(1)
337 elif self._prompt:
338 if not query_yes_no('Would you like to perform the above plan?'):
339 sys.exit(1)
340
341 for action in actions:
342 action.announce(planning=False)
343 action.doit(self._links_db)
344
345 if not on_bot and self._force:
346 logging.info('Completed!\n\nNow run `gclient sync|runhooks` again to '
347 'let the remaining hooks (that probably were interrupted) '
348 'execute.')
349
350 def CleanupLinks(self):
351 logging.debug('CleanupLinks')
352 for source, link_path in self._links_db.iteritems():
353 if source == 'SCHEMA_VERSION':
354 continue
355 if os.path.islink(link_path) or sys.platform.startswith('win'):
356 # os.path.islink() always returns false on Windows
357 # See http://bugs.python.org/issue13143.
358 logging.debug('Removing link to %s at %s', source, link_path)
359 if not self._dry_run:
360 if os.path.exists(link_path):
361 if sys.platform.startswith('win') and os.path.isdir(link_path):
Henrik Kjellanderc444de62015-04-29 11:27:22 +0200362 subprocess.check_call(['rmdir', '/q', '/s', link_path],
363 shell=True)
kjellander@webrtc.org89256622014-08-20 12:10:11 +0000364 else:
365 os.remove(link_path)
366 del self._links_db[source]
367
368 @staticmethod
369 def _ActionForPath(source_path, link_path=None, check_fn=None,
370 check_msg=None):
371 """Create zero or more Actions to link to a file or directory.
372
373 This will be a symlink on POSIX platforms. On Windows this requires
374 that NTFS is version 5.0 or higher (Vista or newer).
375
376 Args:
377 source_path: Path relative to the Chromium checkout root.
378 For readability, the path may contain slashes, which will
379 automatically be converted to the right path delimiter on Windows.
380 link_path: The location for the link to create. If omitted it will be the
381 same path as source_path.
382 check_fn: A function returning true if the type of filesystem object is
383 correct for the attempted call. Otherwise an error message with
384 check_msg will be printed.
385 check_msg: String used to inform the user of an invalid attempt to create
386 a file.
387 Returns:
388 A list of Action objects.
389 """
390 def fix_separators(path):
391 if sys.platform.startswith('win'):
392 return path.replace(os.altsep, os.sep)
393 else:
394 return path
395
396 assert check_fn
397 assert check_msg
398 link_path = link_path or source_path
399 link_path = fix_separators(link_path)
400
401 source_path = fix_separators(source_path)
402 source_path = os.path.join(CHROMIUM_CHECKOUT, source_path)
403 if os.path.exists(source_path) and not check_fn:
404 raise LinkError('_LinkChromiumPath can only be used to link to %s: '
405 'Tried to link to: %s' % (check_msg, source_path))
406
407 if not os.path.exists(source_path):
408 logging.debug('Silently ignoring missing source: %s. This is to avoid '
409 'errors on platform-specific dependencies.', source_path)
410 return []
411
412 actions = []
413
414 if os.path.exists(link_path) or os.path.islink(link_path):
415 if os.path.islink(link_path):
416 actions.append(Remove(link_path, dangerous=False))
417 elif os.path.isfile(link_path):
418 actions.append(Remove(link_path, dangerous=True))
419 elif os.path.isdir(link_path):
420 actions.append(Rmtree(link_path))
421 else:
422 raise LinkError('Don\'t know how to plan: %s' % link_path)
423
424 # Create parent directories to the target link if needed.
425 target_parent_dirs = os.path.dirname(link_path)
426 if (target_parent_dirs and
427 target_parent_dirs != link_path and
428 not os.path.exists(target_parent_dirs)):
429 actions.append(Makedirs(target_parent_dirs))
430
431 actions.append(Symlink(source_path, link_path))
432
433 return actions
434
435def _initialize_database(filename):
436 links_database = shelve.open(filename)
437
438 # Wipe the database if this version of the script ends up looking at a
439 # newer (future) version of the links db, just to be sure.
440 version = links_database.get('SCHEMA_VERSION')
441 if version and version != SCHEMA_VERSION:
442 logging.info('Found database with schema version %s while this script only '
443 'supports %s. Wiping previous database contents.', version,
444 SCHEMA_VERSION)
445 links_database.clear()
446 links_database['SCHEMA_VERSION'] = SCHEMA_VERSION
447 return links_database
448
449
450def main():
451 on_bot = os.environ.get('CHROME_HEADLESS') == '1'
452
453 parser = optparse.OptionParser()
454 parser.add_option('-d', '--dry-run', action='store_true', default=False,
455 help='Print what would be done, but don\'t perform any '
456 'operations. This will automatically set logging to '
457 'verbose.')
458 parser.add_option('-c', '--clean-only', action='store_true', default=False,
459 help='Only clean previously created links, don\'t create '
460 'new ones. This will automatically set logging to '
461 'verbose.')
462 parser.add_option('-f', '--force', action='store_true', default=on_bot,
463 help='Force link creation. CAUTION: This deletes existing '
464 'folders and files in the locations where links are '
465 'about to be created.')
466 parser.add_option('-n', '--no-prompt', action='store_false', dest='prompt',
467 default=(not on_bot),
468 help='Prompt if we\'re planning to do a dangerous action')
469 parser.add_option('-v', '--verbose', action='store_const',
470 const=logging.DEBUG, default=logging.INFO,
471 help='Print verbose output for debugging.')
472 options, _ = parser.parse_args()
473
474 if options.dry_run or options.force or options.clean_only:
475 options.verbose = logging.DEBUG
476 logging.basicConfig(format='%(message)s', level=options.verbose)
477
478 # Work from the root directory of the checkout.
479 script_dir = os.path.dirname(os.path.abspath(__file__))
480 os.chdir(script_dir)
481
482 if sys.platform.startswith('win'):
483 def is_admin():
484 try:
485 return os.getuid() == 0
486 except AttributeError:
487 return ctypes.windll.shell32.IsUserAnAdmin() != 0
488 if not is_admin():
489 logging.error('On Windows, you now need to have administrator '
490 'privileges for the shell running %s (or '
491 '`gclient sync|runhooks`).\nPlease start another command '
Henrik Kjellander57e5fd22015-05-25 12:55:39 +0200492 'prompt as Administrator and try again.', sys.argv[0])
kjellander@webrtc.org89256622014-08-20 12:10:11 +0000493 return 1
494
495 if not os.path.exists(CHROMIUM_CHECKOUT):
496 logging.error('Cannot find a Chromium checkout at %s. Did you run "gclient '
497 'sync" before running this script?', CHROMIUM_CHECKOUT)
498 return 2
499
500 links_database = _initialize_database(LINKS_DB)
501 try:
502 symlink_creator = WebRTCLinkSetup(links_database, options.force,
503 options.dry_run, options.prompt)
504 symlink_creator.CleanupLinks()
505 if not options.clean_only:
506 symlink_creator.CreateLinks(on_bot)
507 except LinkError as e:
508 print >> sys.stderr, e.message
509 return 3
510 finally:
511 links_database.close()
512 return 0
513
514
515if __name__ == '__main__':
516 sys.exit(main())