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