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