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