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