blob: 882329966ea2bc2766d0e57436b7170d503d782b [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.
kjellander@webrtc.org89256622014-08-20 12:10:11 +000016"""
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 14:39:28 -080034 'mojo', # TODO(kjellander): Remove, see webrtc:5629.
kjellander@webrtc.org89256622014-08-20 12:10:11 +000035 'testing',
kjellander@webrtc.org89256622014-08-20 12:10:11 +000036 'third_party/binutils',
37 'third_party/boringssl',
38 'third_party/colorama',
39 'third_party/drmemory',
40 'third_party/expat',
hbosa9a1d2a2016-01-11 10:19:02 -080041 'third_party/ffmpeg',
kjellander@webrtc.org4e4fe4f2014-10-01 08:03:19 +000042 'third_party/instrumented_libraries',
kjellander@webrtc.org89256622014-08-20 12:10:11 +000043 'third_party/jsoncpp',
Henrik Kjellander26ab91b2015-11-26 10:26:32 +010044 'third_party/libc++-static',
kjellander@webrtc.org2addf3f2016-04-04 08:33:12 +020045 'third_party/libFuzzer',
kjellander@webrtc.org89256622014-08-20 12:10:11 +000046 'third_party/libjpeg',
47 'third_party/libjpeg_turbo',
48 'third_party/libsrtp',
kjellandere26e7872016-03-04 14:39:28 -080049 'third_party/libvpx',
kjellander@webrtc.org89256622014-08-20 12:10:11 +000050 'third_party/libyuv',
51 'third_party/llvm-build',
Patrik Höglundc92c23d2015-08-31 11:30:14 +020052 'third_party/lss',
kjellander@webrtc.org89256622014-08-20 12:10:11 +000053 'third_party/nss',
tkchin@webrtc.org3a63a3c2015-01-06 07:21:34 +000054 'third_party/ocmock',
hbosa9a1d2a2016-01-11 10:19:02 -080055 'third_party/openh264',
kjellander@webrtc.org89256622014-08-20 12:10:11 +000056 'third_party/openmax_dl',
57 'third_party/opus',
Patrik Höglundc92c23d2015-08-31 11:30:14 +020058 'third_party/proguard',
kjellander@webrtc.org89256622014-08-20 12:10:11 +000059 'third_party/protobuf',
60 'third_party/sqlite',
61 'third_party/syzygy',
62 'third_party/usrsctp',
63 'third_party/yasm',
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +000064 'third_party/zlib',
kjellander752f36f2016-03-22 16:56:01 -070065 'third_party/WebKit', # TODO(kjellander): Remove, see webrtc:5629.
kjellander@webrtc.org89256622014-08-20 12:10:11 +000066 'tools/clang',
67 'tools/generate_library_loader',
hbos5602f652016-01-15 01:38:34 -080068 'tools/generate_stubs',
kjellander@webrtc.org89256622014-08-20 12:10:11 +000069 'tools/gn',
70 'tools/gyp',
kjellander17849fc2016-02-24 00:04:44 -080071 'tools/mb',
kjellander@webrtc.org89256622014-08-20 12:10:11 +000072 '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
kjellander844dd2a2016-04-05 00:13:58 -0700260# Use junctions instead of symlinks on the Windows platform.
kjellander@webrtc.org89256622014-08-20 12:10:11 +0000261if sys.platform.startswith('win'):
262 def symlink(source_path, link_path):
kjellander844dd2a2016-04-05 00:13:58 -0700263 if os.path.isdir(source_path):
264 subprocess.check_call(['cmd.exe', '/c', 'mklink', '/J', link_path,
265 source_path])
266 else:
267 # Don't create symlinks to files on Windows, just copy the file instead
268 # (there's no way to create a link without administrator's privileges).
269 shutil.copy(source_path, link_path)
kjellander@webrtc.org89256622014-08-20 12:10:11 +0000270 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
kjellander844dd2a2016-04-05 00:13:58 -0700321 Setting up the checkout requires creating symlinks to directories in the
322 Chromium checkout inside chromium/src.
323 To avoid disrupting developers, we've chosen to not delete directories
324 forcibly, in case you have some work in progress in one of them :)
kjellander@webrtc.org89256622014-08-20 12:10:11 +0000325
326 ACTION REQUIRED:
327 Before running `gclient sync|runhooks` again, you must run:
328 %s%s --force
329
330 Which will replace all directories which now must be symlinks, after
331 prompting with a summary of the work-to-be-done.
kjellander844dd2a2016-04-05 00:13:58 -0700332 """), 'python ' if sys.platform.startswith('win') else '', __file__)
kjellander@webrtc.org89256622014-08-20 12:10:11 +0000333 sys.exit(1)
334 elif self._prompt:
335 if not query_yes_no('Would you like to perform the above plan?'):
336 sys.exit(1)
337
338 for action in actions:
339 action.announce(planning=False)
340 action.doit(self._links_db)
341
342 if not on_bot and self._force:
343 logging.info('Completed!\n\nNow run `gclient sync|runhooks` again to '
344 'let the remaining hooks (that probably were interrupted) '
345 'execute.')
346
347 def CleanupLinks(self):
348 logging.debug('CleanupLinks')
349 for source, link_path in self._links_db.iteritems():
350 if source == 'SCHEMA_VERSION':
351 continue
352 if os.path.islink(link_path) or sys.platform.startswith('win'):
353 # os.path.islink() always returns false on Windows
354 # See http://bugs.python.org/issue13143.
355 logging.debug('Removing link to %s at %s', source, link_path)
356 if not self._dry_run:
357 if os.path.exists(link_path):
358 if sys.platform.startswith('win') and os.path.isdir(link_path):
Henrik Kjellanderc444de62015-04-29 11:27:22 +0200359 subprocess.check_call(['rmdir', '/q', '/s', link_path],
360 shell=True)
kjellander@webrtc.org89256622014-08-20 12:10:11 +0000361 else:
362 os.remove(link_path)
363 del self._links_db[source]
364
365 @staticmethod
366 def _ActionForPath(source_path, link_path=None, check_fn=None,
367 check_msg=None):
368 """Create zero or more Actions to link to a file or directory.
369
kjellander844dd2a2016-04-05 00:13:58 -0700370 This will be a symlink on POSIX platforms. On Windows it will result in:
371 * a junction for directories
372 * a copied file for single files.
kjellander@webrtc.org89256622014-08-20 12:10:11 +0000373
374 Args:
375 source_path: Path relative to the Chromium checkout root.
376 For readability, the path may contain slashes, which will
377 automatically be converted to the right path delimiter on Windows.
378 link_path: The location for the link to create. If omitted it will be the
379 same path as source_path.
380 check_fn: A function returning true if the type of filesystem object is
381 correct for the attempted call. Otherwise an error message with
382 check_msg will be printed.
383 check_msg: String used to inform the user of an invalid attempt to create
384 a file.
385 Returns:
386 A list of Action objects.
387 """
388 def fix_separators(path):
389 if sys.platform.startswith('win'):
390 return path.replace(os.altsep, os.sep)
391 else:
392 return path
393
394 assert check_fn
395 assert check_msg
396 link_path = link_path or source_path
397 link_path = fix_separators(link_path)
398
399 source_path = fix_separators(source_path)
400 source_path = os.path.join(CHROMIUM_CHECKOUT, source_path)
401 if os.path.exists(source_path) and not check_fn:
kjellander844dd2a2016-04-05 00:13:58 -0700402 raise LinkError('Can only to link to %s: tried to link to: %s' %
403 (check_msg, source_path))
kjellander@webrtc.org89256622014-08-20 12:10:11 +0000404
405 if not os.path.exists(source_path):
406 logging.debug('Silently ignoring missing source: %s. This is to avoid '
407 'errors on platform-specific dependencies.', source_path)
408 return []
409
410 actions = []
411
412 if os.path.exists(link_path) or os.path.islink(link_path):
413 if os.path.islink(link_path):
414 actions.append(Remove(link_path, dangerous=False))
415 elif os.path.isfile(link_path):
416 actions.append(Remove(link_path, dangerous=True))
417 elif os.path.isdir(link_path):
418 actions.append(Rmtree(link_path))
419 else:
420 raise LinkError('Don\'t know how to plan: %s' % link_path)
421
422 # Create parent directories to the target link if needed.
423 target_parent_dirs = os.path.dirname(link_path)
424 if (target_parent_dirs and
425 target_parent_dirs != link_path and
426 not os.path.exists(target_parent_dirs)):
427 actions.append(Makedirs(target_parent_dirs))
428
429 actions.append(Symlink(source_path, link_path))
430
431 return actions
432
433def _initialize_database(filename):
434 links_database = shelve.open(filename)
435
436 # Wipe the database if this version of the script ends up looking at a
437 # newer (future) version of the links db, just to be sure.
438 version = links_database.get('SCHEMA_VERSION')
439 if version and version != SCHEMA_VERSION:
440 logging.info('Found database with schema version %s while this script only '
441 'supports %s. Wiping previous database contents.', version,
442 SCHEMA_VERSION)
443 links_database.clear()
444 links_database['SCHEMA_VERSION'] = SCHEMA_VERSION
445 return links_database
446
447
448def main():
449 on_bot = os.environ.get('CHROME_HEADLESS') == '1'
450
451 parser = optparse.OptionParser()
452 parser.add_option('-d', '--dry-run', action='store_true', default=False,
453 help='Print what would be done, but don\'t perform any '
454 'operations. This will automatically set logging to '
455 'verbose.')
456 parser.add_option('-c', '--clean-only', action='store_true', default=False,
457 help='Only clean previously created links, don\'t create '
458 'new ones. This will automatically set logging to '
459 'verbose.')
460 parser.add_option('-f', '--force', action='store_true', default=on_bot,
461 help='Force link creation. CAUTION: This deletes existing '
462 'folders and files in the locations where links are '
463 'about to be created.')
464 parser.add_option('-n', '--no-prompt', action='store_false', dest='prompt',
465 default=(not on_bot),
466 help='Prompt if we\'re planning to do a dangerous action')
467 parser.add_option('-v', '--verbose', action='store_const',
468 const=logging.DEBUG, default=logging.INFO,
469 help='Print verbose output for debugging.')
470 options, _ = parser.parse_args()
471
472 if options.dry_run or options.force or options.clean_only:
473 options.verbose = logging.DEBUG
474 logging.basicConfig(format='%(message)s', level=options.verbose)
475
476 # Work from the root directory of the checkout.
477 script_dir = os.path.dirname(os.path.abspath(__file__))
478 os.chdir(script_dir)
479
480 if sys.platform.startswith('win'):
481 def is_admin():
482 try:
483 return os.getuid() == 0
484 except AttributeError:
485 return ctypes.windll.shell32.IsUserAnAdmin() != 0
kjellander844dd2a2016-04-05 00:13:58 -0700486 if is_admin():
487 logging.warning('WARNING: On Windows, you no longer need run as '
488 'administrator. Please run with user account privileges.')
kjellander@webrtc.org89256622014-08-20 12:10:11 +0000489
490 if not os.path.exists(CHROMIUM_CHECKOUT):
491 logging.error('Cannot find a Chromium checkout at %s. Did you run "gclient '
492 'sync" before running this script?', CHROMIUM_CHECKOUT)
493 return 2
494
495 links_database = _initialize_database(LINKS_DB)
496 try:
497 symlink_creator = WebRTCLinkSetup(links_database, options.force,
498 options.dry_run, options.prompt)
499 symlink_creator.CleanupLinks()
500 if not options.clean_only:
501 symlink_creator.CreateLinks(on_bot)
502 except LinkError as e:
503 print >> sys.stderr, e.message
504 return 3
505 finally:
506 links_database.close()
507 return 0
508
509
510if __name__ == '__main__':
511 sys.exit(main())