blob: 94d9ee91881120ddfbe01e52563eb69d37249825 [file] [log] [blame]
kjellander@webrtc.org89256622014-08-20 12:10:111#!/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.
kjellander@webrtc.org89256622014-08-20 12:10:1116"""
17
18
19import ctypes
20import errno
21import logging
22import optparse
23import os
24import shelve
25import shutil
26import subprocess
27import sys
28import textwrap
29
30
31DIRECTORIES = [
32 'build',
33 'buildtools',
kjellandere26e7872016-03-04 22:39:2834 'mojo', # TODO(kjellander): Remove, see webrtc:5629.
kjellander@webrtc.org89256622014-08-20 12:10:1135 'testing',
metzmanf89a5712016-07-25 09:14:0936 'third_party/afl',
kjellander@webrtc.org89256622014-08-20 12:10:1137 'third_party/binutils',
38 'third_party/boringssl',
39 'third_party/colorama',
40 'third_party/drmemory',
41 'third_party/expat',
hbosa9a1d2a2016-01-11 18:19:0242 'third_party/ffmpeg',
kjellander@webrtc.org4e4fe4f2014-10-01 08:03:1943 'third_party/instrumented_libraries',
kjellander@webrtc.org89256622014-08-20 12:10:1144 'third_party/jsoncpp',
Henrik Kjellander26ab91b2015-11-26 09:26:3245 'third_party/libc++-static',
kjellander@webrtc.org2addf3f2016-04-04 06:33:1246 'third_party/libFuzzer',
kjellander@webrtc.org89256622014-08-20 12:10:1147 'third_party/libjpeg',
48 'third_party/libjpeg_turbo',
49 'third_party/libsrtp',
kjellandere26e7872016-03-04 22:39:2850 'third_party/libvpx',
kjellander@webrtc.org89256622014-08-20 12:10:1151 'third_party/libyuv',
52 'third_party/llvm-build',
Patrik Höglundc92c23d92015-08-31 09:30:1453 'third_party/lss',
kjellander@webrtc.org89256622014-08-20 12:10:1154 'third_party/nss',
tkchin@webrtc.org3a63a3c2015-01-06 07:21:3455 'third_party/ocmock',
hbosa9a1d2a2016-01-11 18:19:0256 'third_party/openh264',
kjellander@webrtc.org89256622014-08-20 12:10:1157 'third_party/openmax_dl',
58 'third_party/opus',
Patrik Höglundc92c23d92015-08-31 09:30:1459 'third_party/proguard',
kjellander@webrtc.org89256622014-08-20 12:10:1160 'third_party/protobuf',
61 'third_party/sqlite',
62 'third_party/syzygy',
63 'third_party/usrsctp',
64 'third_party/yasm',
kjellander@webrtc.org3bd41562014-09-01 11:06:3765 'third_party/zlib',
kjellander752f36f2016-03-22 23:56:0166 'third_party/WebKit', # TODO(kjellander): Remove, see webrtc:5629.
kjellander@webrtc.org89256622014-08-20 12:10:1167 'tools/clang',
68 'tools/generate_library_loader',
hbos5602f652016-01-15 09:38:3469 'tools/generate_stubs',
kjellander@webrtc.org89256622014-08-20 12:10:1170 'tools/gn',
71 'tools/gyp',
kjellandere532aec2016-04-18 03:08:2072 'tools/luci-go',
kjellander17849fc2016-02-24 08:04:4473 'tools/mb',
kjellander@webrtc.org89256622014-08-20 12:10:1174 'tools/memory',
75 'tools/protoc_wrapper',
76 'tools/python',
77 'tools/swarming_client',
78 'tools/valgrind',
Andrew MacDonald65de7d22015-05-19 18:37:3479 'tools/vim',
kjellander@webrtc.org89256622014-08-20 12:10:1180 'tools/win',
Henrik Kjellander9589e2a2015-10-22 04:48:2181 'tools/xdisplaycheck',
kjellander@webrtc.org89256622014-08-20 12:10:1182]
83
kjellander@webrtc.org3bd41562014-09-01 11:06:3784from sync_chromium import get_target_os_list
Henrik Kjellanderca843022015-06-09 08:51:2285target_os = get_target_os_list()
86if 'android' in target_os:
kjellander@webrtc.org3bd41562014-09-01 11:06:3787 DIRECTORIES += [
88 'base',
ehmaldonado9f73f7a2016-07-28 08:20:2289 'third_party/accessibility_test_framework',
Henrik Kjellander94a12322015-06-09 12:56:2090 'third_party/android_platform',
kjellander@webrtc.org3bd41562014-09-01 11:06:3791 'third_party/android_tools',
ehmaldonado9f73f7a2016-07-28 08:20:2292 'third_party/apache_velocity',
kjellander@webrtc.orgcbe7ca82015-01-06 07:24:2793 'third_party/appurify-python',
kjellander@webrtc.orgb8caf6a2014-09-30 18:05:0294 'third_party/ashmem',
ehmaldonado9f73f7a2016-07-28 08:20:2295 'third_party/bouncycastle',
kjellander34a70542015-12-06 18:32:3496 'third_party/catapult',
kjellanderb419d6e2016-08-04 07:00:3797 'third_party/closure_compiler',
ehmaldonado9f73f7a2016-07-28 08:20:2298 'third_party/guava',
99 'third_party/hamcrest',
kjellander53761002015-11-10 18:58:22100 'third_party/icu',
ehmaldonado9f73f7a2016-07-28 08:20:22101 'third_party/icu4j',
Henrik Kjellandereecbab72015-09-16 17:19:04102 'third_party/ijar',
ehmaldonado9f73f7a2016-07-28 08:20:22103 'third_party/intellij',
kjellander@webrtc.orgb8caf6a2014-09-30 18:05:02104 'third_party/jsr-305',
Henrik Kjellander10ba3ee2015-04-29 12:47:53105 'third_party/junit',
kjellander@webrtc.orgb8caf6a2014-09-30 18:05:02106 'third_party/libxml',
Henrik Kjellander10ba3ee2015-04-29 12:47:53107 'third_party/mockito',
kjellander@webrtc.orgb8caf6a2014-09-30 18:05:02108 'third_party/modp_b64',
ehmaldonado9f73f7a2016-07-28 08:20:22109 'third_party/ow2_asm',
kjellander@webrtc.orgcbe7ca82015-01-06 07:24:27110 'third_party/requests',
Henrik Kjellander10ba3ee2015-04-29 12:47:53111 'third_party/robolectric',
ehmaldonado9f73f7a2016-07-28 08:20:22112 'third_party/sqlite4java',
primianob332e5d2016-01-25 16:13:05113 'third_party/tcmalloc',
kjellander@webrtc.org3bd41562014-09-01 11:06:37114 'tools/android',
kjellander@webrtc.orgbfdee692015-02-03 15:23:34115 'tools/grit',
kjellander34a70542015-12-06 18:32:34116 'tools/telemetry',
kjellander@webrtc.org3bd41562014-09-01 11:06:37117 ]
tommide3b0292016-04-22 08:47:02118else:
119 DIRECTORIES += [
120 'base/third_party/libevent',
121 ]
122
Henrik Kjellanderca843022015-06-09 08:51:22123if 'ios' in target_os:
124 DIRECTORIES.append('third_party/class-dump')
kjellander@webrtc.org3bd41562014-09-01 11:06:37125
kjellander@webrtc.org89256622014-08-20 12:10:11126FILES = {
Henrik Kjellanderd6d27e72015-09-25 20:19:11127 'tools/isolate_driver.py': None,
kjellander@webrtc.org89256622014-08-20 12:10:11128 'third_party/BUILD.gn': None,
kjellander@webrtc.org89256622014-08-20 12:10:11129}
130
kjellander@webrtc.orge94f83a2014-09-18 13:47:23131ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
kjellander@webrtc.org89256622014-08-20 12:10:11132CHROMIUM_CHECKOUT = os.path.join('chromium', 'src')
133LINKS_DB = 'links'
134
135# Version management to make future upgrades/downgrades easier to support.
136SCHEMA_VERSION = 1
137
138
139def query_yes_no(question, default=False):
140 """Ask a yes/no question via raw_input() and return their answer.
141
142 Modified from http://stackoverflow.com/a/3041990.
143 """
144 prompt = " [%s/%%s]: "
145 prompt = prompt % ('Y' if default is True else 'y')
146 prompt = prompt % ('N' if default is False else 'n')
147
148 if default is None:
149 default = 'INVALID'
150
151 while True:
152 sys.stdout.write(question + prompt)
153 choice = raw_input().lower()
154 if choice == '' and default != 'INVALID':
155 return default
156
157 if 'yes'.startswith(choice):
158 return True
159 elif 'no'.startswith(choice):
160 return False
161
162 print "Please respond with 'yes' or 'no' (or 'y' or 'n')."
163
164
165# Actions
166class Action(object):
167 def __init__(self, dangerous):
168 self.dangerous = dangerous
169
170 def announce(self, planning):
171 """Log a description of this action.
172
173 Args:
174 planning - True iff we're in the planning stage, False if we're in the
175 doit stage.
176 """
177 pass
178
179 def doit(self, links_db):
180 """Execute the action, recording what we did to links_db, if necessary."""
181 pass
182
183
184class Remove(Action):
185 def __init__(self, path, dangerous):
186 super(Remove, self).__init__(dangerous)
187 self._priority = 0
188 self._path = path
189
190 def announce(self, planning):
191 log = logging.warn
192 filesystem_type = 'file'
193 if not self.dangerous:
194 log = logging.info
195 filesystem_type = 'link'
196 if planning:
197 log('Planning to remove %s: %s', filesystem_type, self._path)
198 else:
199 log('Removing %s: %s', filesystem_type, self._path)
200
Henrik Kjellander57e5fd22015-05-25 10:55:39201 def doit(self, _):
kjellander@webrtc.org89256622014-08-20 12:10:11202 os.remove(self._path)
203
204
205class Rmtree(Action):
206 def __init__(self, path):
207 super(Rmtree, self).__init__(dangerous=True)
208 self._priority = 0
209 self._path = path
210
211 def announce(self, planning):
212 if planning:
213 logging.warn('Planning to remove directory: %s', self._path)
214 else:
215 logging.warn('Removing directory: %s', self._path)
216
Henrik Kjellander57e5fd22015-05-25 10:55:39217 def doit(self, _):
kjellander@webrtc.org89256622014-08-20 12:10:11218 if sys.platform.startswith('win'):
219 # shutil.rmtree() doesn't work on Windows if any of the directories are
220 # read-only, which svn repositories are.
221 subprocess.check_call(['rd', '/q', '/s', self._path], shell=True)
222 else:
223 shutil.rmtree(self._path)
224
225
226class Makedirs(Action):
227 def __init__(self, path):
228 super(Makedirs, self).__init__(dangerous=False)
229 self._priority = 1
230 self._path = path
231
Henrik Kjellander57e5fd22015-05-25 10:55:39232 def doit(self, _):
kjellander@webrtc.org89256622014-08-20 12:10:11233 try:
234 os.makedirs(self._path)
235 except OSError as e:
236 if e.errno != errno.EEXIST:
237 raise
238
239
240class Symlink(Action):
241 def __init__(self, source_path, link_path):
242 super(Symlink, self).__init__(dangerous=False)
243 self._priority = 2
244 self._source_path = source_path
245 self._link_path = link_path
246
247 def announce(self, planning):
248 if planning:
249 logging.info(
250 'Planning to create link from %s to %s', self._link_path,
251 self._source_path)
252 else:
253 logging.debug(
254 'Linking from %s to %s', self._link_path, self._source_path)
255
256 def doit(self, links_db):
257 # Files not in the root directory need relative path calculation.
258 # On Windows, use absolute paths instead since NTFS doesn't seem to support
259 # relative paths for symlinks.
260 if sys.platform.startswith('win'):
261 source_path = os.path.abspath(self._source_path)
262 else:
263 if os.path.dirname(self._link_path) != self._link_path:
264 source_path = os.path.relpath(self._source_path,
265 os.path.dirname(self._link_path))
266
267 os.symlink(source_path, os.path.abspath(self._link_path))
268 links_db[self._source_path] = self._link_path
269
270
271class LinkError(IOError):
272 """Failed to create a link."""
273 pass
274
275
kjellander844dd2a2016-04-05 07:13:58276# Use junctions instead of symlinks on the Windows platform.
kjellander@webrtc.org89256622014-08-20 12:10:11277if sys.platform.startswith('win'):
278 def symlink(source_path, link_path):
kjellander844dd2a2016-04-05 07:13:58279 if os.path.isdir(source_path):
280 subprocess.check_call(['cmd.exe', '/c', 'mklink', '/J', link_path,
281 source_path])
282 else:
283 # Don't create symlinks to files on Windows, just copy the file instead
284 # (there's no way to create a link without administrator's privileges).
285 shutil.copy(source_path, link_path)
kjellander@webrtc.org89256622014-08-20 12:10:11286 os.symlink = symlink
287
288
Henrik Kjellander57e5fd22015-05-25 10:55:39289class WebRTCLinkSetup(object):
kjellander@webrtc.org89256622014-08-20 12:10:11290 def __init__(self, links_db, force=False, dry_run=False, prompt=False):
291 self._force = force
292 self._dry_run = dry_run
293 self._prompt = prompt
294 self._links_db = links_db
295
296 def CreateLinks(self, on_bot):
297 logging.debug('CreateLinks')
298 # First, make a plan of action
299 actions = []
300
301 for source_path, link_path in FILES.iteritems():
302 actions += self._ActionForPath(
303 source_path, link_path, check_fn=os.path.isfile, check_msg='files')
304 for source_dir in DIRECTORIES:
305 actions += self._ActionForPath(
306 source_dir, None, check_fn=os.path.isdir,
307 check_msg='directories')
308
kjellander@webrtc.orge94f83a2014-09-18 13:47:23309 if not on_bot and self._force:
310 # When making the manual switch from legacy SVN checkouts to the new
311 # Git-based Chromium DEPS, the .gclient_entries file that contains cached
312 # URLs for all DEPS entries must be removed to avoid future sync problems.
313 entries_file = os.path.join(os.path.dirname(ROOT_DIR), '.gclient_entries')
314 if os.path.exists(entries_file):
315 actions.append(Remove(entries_file, dangerous=True))
316
kjellander@webrtc.org89256622014-08-20 12:10:11317 actions.sort()
318
319 if self._dry_run:
320 for action in actions:
321 action.announce(planning=True)
322 logging.info('Not doing anything because dry-run was specified.')
323 sys.exit(0)
324
325 if any(a.dangerous for a in actions):
326 logging.warn('Dangerous actions:')
327 for action in (a for a in actions if a.dangerous):
328 action.announce(planning=True)
329 print
330
331 if not self._force:
332 logging.error(textwrap.dedent("""\
333 @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
334 A C T I O N R E Q I R E D
335 @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
336
kjellander844dd2a2016-04-05 07:13:58337 Setting up the checkout requires creating symlinks to directories in the
338 Chromium checkout inside chromium/src.
339 To avoid disrupting developers, we've chosen to not delete directories
340 forcibly, in case you have some work in progress in one of them :)
kjellander@webrtc.org89256622014-08-20 12:10:11341
342 ACTION REQUIRED:
343 Before running `gclient sync|runhooks` again, you must run:
344 %s%s --force
345
346 Which will replace all directories which now must be symlinks, after
347 prompting with a summary of the work-to-be-done.
kjellander844dd2a2016-04-05 07:13:58348 """), 'python ' if sys.platform.startswith('win') else '', __file__)
kjellander@webrtc.org89256622014-08-20 12:10:11349 sys.exit(1)
350 elif self._prompt:
351 if not query_yes_no('Would you like to perform the above plan?'):
352 sys.exit(1)
353
354 for action in actions:
355 action.announce(planning=False)
356 action.doit(self._links_db)
357
358 if not on_bot and self._force:
359 logging.info('Completed!\n\nNow run `gclient sync|runhooks` again to '
360 'let the remaining hooks (that probably were interrupted) '
361 'execute.')
362
363 def CleanupLinks(self):
364 logging.debug('CleanupLinks')
365 for source, link_path in self._links_db.iteritems():
366 if source == 'SCHEMA_VERSION':
367 continue
368 if os.path.islink(link_path) or sys.platform.startswith('win'):
369 # os.path.islink() always returns false on Windows
370 # See http://bugs.python.org/issue13143.
371 logging.debug('Removing link to %s at %s', source, link_path)
372 if not self._dry_run:
373 if os.path.exists(link_path):
374 if sys.platform.startswith('win') and os.path.isdir(link_path):
Henrik Kjellanderc444de62015-04-29 09:27:22375 subprocess.check_call(['rmdir', '/q', '/s', link_path],
376 shell=True)
kjellander@webrtc.org89256622014-08-20 12:10:11377 else:
378 os.remove(link_path)
379 del self._links_db[source]
380
381 @staticmethod
382 def _ActionForPath(source_path, link_path=None, check_fn=None,
383 check_msg=None):
384 """Create zero or more Actions to link to a file or directory.
385
kjellander844dd2a2016-04-05 07:13:58386 This will be a symlink on POSIX platforms. On Windows it will result in:
387 * a junction for directories
388 * a copied file for single files.
kjellander@webrtc.org89256622014-08-20 12:10:11389
390 Args:
391 source_path: Path relative to the Chromium checkout root.
392 For readability, the path may contain slashes, which will
393 automatically be converted to the right path delimiter on Windows.
394 link_path: The location for the link to create. If omitted it will be the
395 same path as source_path.
396 check_fn: A function returning true if the type of filesystem object is
397 correct for the attempted call. Otherwise an error message with
398 check_msg will be printed.
399 check_msg: String used to inform the user of an invalid attempt to create
400 a file.
401 Returns:
402 A list of Action objects.
403 """
404 def fix_separators(path):
405 if sys.platform.startswith('win'):
406 return path.replace(os.altsep, os.sep)
407 else:
408 return path
409
410 assert check_fn
411 assert check_msg
412 link_path = link_path or source_path
413 link_path = fix_separators(link_path)
414
415 source_path = fix_separators(source_path)
416 source_path = os.path.join(CHROMIUM_CHECKOUT, source_path)
417 if os.path.exists(source_path) and not check_fn:
kjellander844dd2a2016-04-05 07:13:58418 raise LinkError('Can only to link to %s: tried to link to: %s' %
419 (check_msg, source_path))
kjellander@webrtc.org89256622014-08-20 12:10:11420
421 if not os.path.exists(source_path):
422 logging.debug('Silently ignoring missing source: %s. This is to avoid '
423 'errors on platform-specific dependencies.', source_path)
424 return []
425
426 actions = []
427
428 if os.path.exists(link_path) or os.path.islink(link_path):
429 if os.path.islink(link_path):
430 actions.append(Remove(link_path, dangerous=False))
431 elif os.path.isfile(link_path):
432 actions.append(Remove(link_path, dangerous=True))
433 elif os.path.isdir(link_path):
434 actions.append(Rmtree(link_path))
435 else:
436 raise LinkError('Don\'t know how to plan: %s' % link_path)
437
438 # Create parent directories to the target link if needed.
439 target_parent_dirs = os.path.dirname(link_path)
440 if (target_parent_dirs and
441 target_parent_dirs != link_path and
442 not os.path.exists(target_parent_dirs)):
443 actions.append(Makedirs(target_parent_dirs))
444
445 actions.append(Symlink(source_path, link_path))
446
447 return actions
448
449def _initialize_database(filename):
450 links_database = shelve.open(filename)
451
452 # Wipe the database if this version of the script ends up looking at a
453 # newer (future) version of the links db, just to be sure.
454 version = links_database.get('SCHEMA_VERSION')
455 if version and version != SCHEMA_VERSION:
456 logging.info('Found database with schema version %s while this script only '
457 'supports %s. Wiping previous database contents.', version,
458 SCHEMA_VERSION)
459 links_database.clear()
460 links_database['SCHEMA_VERSION'] = SCHEMA_VERSION
461 return links_database
462
463
464def main():
465 on_bot = os.environ.get('CHROME_HEADLESS') == '1'
466
467 parser = optparse.OptionParser()
468 parser.add_option('-d', '--dry-run', action='store_true', default=False,
469 help='Print what would be done, but don\'t perform any '
470 'operations. This will automatically set logging to '
471 'verbose.')
472 parser.add_option('-c', '--clean-only', action='store_true', default=False,
473 help='Only clean previously created links, don\'t create '
474 'new ones. This will automatically set logging to '
475 'verbose.')
476 parser.add_option('-f', '--force', action='store_true', default=on_bot,
477 help='Force link creation. CAUTION: This deletes existing '
478 'folders and files in the locations where links are '
479 'about to be created.')
480 parser.add_option('-n', '--no-prompt', action='store_false', dest='prompt',
481 default=(not on_bot),
482 help='Prompt if we\'re planning to do a dangerous action')
483 parser.add_option('-v', '--verbose', action='store_const',
484 const=logging.DEBUG, default=logging.INFO,
485 help='Print verbose output for debugging.')
486 options, _ = parser.parse_args()
487
488 if options.dry_run or options.force or options.clean_only:
489 options.verbose = logging.DEBUG
490 logging.basicConfig(format='%(message)s', level=options.verbose)
491
492 # Work from the root directory of the checkout.
493 script_dir = os.path.dirname(os.path.abspath(__file__))
494 os.chdir(script_dir)
495
496 if sys.platform.startswith('win'):
497 def is_admin():
498 try:
499 return os.getuid() == 0
500 except AttributeError:
501 return ctypes.windll.shell32.IsUserAnAdmin() != 0
kjellander844dd2a2016-04-05 07:13:58502 if is_admin():
503 logging.warning('WARNING: On Windows, you no longer need run as '
504 'administrator. Please run with user account privileges.')
kjellander@webrtc.org89256622014-08-20 12:10:11505
506 if not os.path.exists(CHROMIUM_CHECKOUT):
507 logging.error('Cannot find a Chromium checkout at %s. Did you run "gclient '
508 'sync" before running this script?', CHROMIUM_CHECKOUT)
509 return 2
510
511 links_database = _initialize_database(LINKS_DB)
512 try:
513 symlink_creator = WebRTCLinkSetup(links_database, options.force,
514 options.dry_run, options.prompt)
515 symlink_creator.CleanupLinks()
516 if not options.clean_only:
517 symlink_creator.CreateLinks(on_bot)
518 except LinkError as e:
519 print >> sys.stderr, e.message
520 return 3
521 finally:
522 links_database.close()
523 return 0
524
525
526if __name__ == '__main__':
527 sys.exit(main())