Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(249)

Side by Side Diff: setup_links.py

Issue 1414343008: DEPS: Sync Git subtree mirrors instead of symlinking into chromium/src (Closed) Base URL: https://chromium.googlesource.com/external/webrtc.git@master
Patch Set: Rebased Created 3 years, 12 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « chromium/README ('k') | sync_chromium.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 #!/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
12 WebRTC standalone shares a lot of dependencies and build tools with Chromium.
13 To do this, many of the paths of a Chromium checkout is emulated by creating
14 symlinks to files and directories. This script handles the setup of symlinks to
15 achieve this.
16 """
17
18
19 import ctypes
20 import errno
21 import logging
22 import optparse
23 import os
24 import shelve
25 import shutil
26 import subprocess
27 import sys
28 import textwrap
29
30
31 DIRECTORIES = [
32 'base',
33 'build',
34 'buildtools',
35 'testing',
36 'third_party/afl',
37 'third_party/binutils',
38 'third_party/boringssl',
39 'third_party/catapult',
40 'third_party/closure_compiler',
41 'third_party/colorama',
42 'third_party/expat',
43 'third_party/ffmpeg',
44 'third_party/instrumented_libraries',
45 'third_party/jsoncpp',
46 'third_party/libFuzzer',
47 'third_party/libjpeg',
48 'third_party/libjpeg_turbo',
49 'third_party/libsrtp',
50 'third_party/libvpx',
51 'third_party/libyuv',
52 'third_party/lss',
53 'third_party/ocmock',
54 'third_party/openh264',
55 'third_party/openmax_dl',
56 'third_party/opus',
57 'third_party/proguard',
58 'third_party/protobuf',
59 'third_party/sqlite',
60 'third_party/usrsctp',
61 'third_party/yasm',
62 'third_party/zlib',
63 'tools/clang',
64 'tools/clang_format_merge_driver',
65 'tools/determinism',
66 'tools/generate_library_loader',
67 'tools/generate_stubs',
68 'tools/gn',
69 'tools/grit',
70 'tools/gyp',
71 'tools/luci-go',
72 'tools/memory',
73 'tools/protoc_wrapper',
74 'tools/python',
75 'tools/swarming_client',
76 'tools/valgrind',
77 'tools/vim',
78 'tools/win',
79 ]
80
81 from sync_chromium import get_target_os_list
82 target_os = get_target_os_list()
83 if 'android' in target_os:
84 DIRECTORIES += [
85 'third_party/accessibility_test_framework',
86 'third_party/android_platform',
87 'third_party/android_support_test_runner',
88 'third_party/android_tools',
89 'third_party/apache_velocity',
90 'third_party/ashmem',
91 'third_party/bouncycastle',
92 'third_party/byte_buddy',
93 'third_party/ced',
94 'third_party/espresso',
95 'third_party/guava',
96 'third_party/hamcrest',
97 'third_party/icu',
98 'third_party/icu4j',
99 'third_party/ijar',
100 'third_party/intellij',
101 'third_party/javax_inject',
102 'third_party/jsr-305',
103 'third_party/junit',
104 'third_party/libxml',
105 'third_party/mockito',
106 'third_party/modp_b64',
107 'third_party/objenesis',
108 'third_party/ow2_asm',
109 'third_party/requests',
110 'third_party/robolectric',
111 'third_party/sqlite4java',
112 'third_party/tcmalloc',
113 'tools/android',
114 ]
115
116 FILES = {
117 'third_party/BUILD.gn': None,
118 }
119
120 ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
121 CHROMIUM_CHECKOUT = os.path.join('chromium', 'src')
122 LINKS_DB = 'links'
123
124 # Version management to make future upgrades/downgrades easier to support.
125 SCHEMA_VERSION = 1
126
127
128 def query_yes_no(question, default=False):
129 """Ask a yes/no question via raw_input() and return their answer.
130
131 Modified from http://stackoverflow.com/a/3041990.
132 """
133 prompt = " [%s/%%s]: "
134 prompt = prompt % ('Y' if default is True else 'y')
135 prompt = prompt % ('N' if default is False else 'n')
136
137 if default is None:
138 default = 'INVALID'
139
140 while True:
141 sys.stdout.write(question + prompt)
142 choice = raw_input().lower()
143 if choice == '' and default != 'INVALID':
144 return default
145
146 if 'yes'.startswith(choice):
147 return True
148 elif 'no'.startswith(choice):
149 return False
150
151 print "Please respond with 'yes' or 'no' (or 'y' or 'n')."
152
153
154 # Actions
155 class Action(object):
156 def __init__(self, dangerous):
157 self.dangerous = dangerous
158
159 def announce(self, planning):
160 """Log a description of this action.
161
162 Args:
163 planning - True iff we're in the planning stage, False if we're in the
164 doit stage.
165 """
166 pass
167
168 def doit(self, links_db):
169 """Execute the action, recording what we did to links_db, if necessary."""
170 pass
171
172
173 class Remove(Action):
174 def __init__(self, path, dangerous):
175 super(Remove, self).__init__(dangerous)
176 self._priority = 0
177 self._path = path
178
179 def announce(self, planning):
180 log = logging.warn
181 filesystem_type = 'file'
182 if not self.dangerous:
183 log = logging.info
184 filesystem_type = 'link'
185 if planning:
186 log('Planning to remove %s: %s', filesystem_type, self._path)
187 else:
188 log('Removing %s: %s', filesystem_type, self._path)
189
190 def doit(self, _):
191 os.remove(self._path)
192
193
194 class Rmtree(Action):
195 def __init__(self, path):
196 super(Rmtree, self).__init__(dangerous=True)
197 self._priority = 0
198 self._path = path
199
200 def announce(self, planning):
201 if planning:
202 logging.warn('Planning to remove directory: %s', self._path)
203 else:
204 logging.warn('Removing directory: %s', self._path)
205
206 def doit(self, _):
207 if sys.platform.startswith('win'):
208 # shutil.rmtree() doesn't work on Windows if any of the directories are
209 # read-only, which svn repositories are.
210 subprocess.check_call(['rd', '/q', '/s', self._path], shell=True)
211 else:
212 shutil.rmtree(self._path)
213
214
215 class Makedirs(Action):
216 def __init__(self, path):
217 super(Makedirs, self).__init__(dangerous=False)
218 self._priority = 1
219 self._path = path
220
221 def doit(self, _):
222 try:
223 os.makedirs(self._path)
224 except OSError as e:
225 if e.errno != errno.EEXIST:
226 raise
227
228
229 class Symlink(Action):
230 def __init__(self, source_path, link_path):
231 super(Symlink, self).__init__(dangerous=False)
232 self._priority = 2
233 self._source_path = source_path
234 self._link_path = link_path
235
236 def announce(self, planning):
237 if planning:
238 logging.info(
239 'Planning to create link from %s to %s', self._link_path,
240 self._source_path)
241 else:
242 logging.debug(
243 'Linking from %s to %s', self._link_path, self._source_path)
244
245 def doit(self, links_db):
246 # Files not in the root directory need relative path calculation.
247 # On Windows, use absolute paths instead since NTFS doesn't seem to support
248 # relative paths for symlinks.
249 if sys.platform.startswith('win'):
250 source_path = os.path.abspath(self._source_path)
251 else:
252 if os.path.dirname(self._link_path) != self._link_path:
253 source_path = os.path.relpath(self._source_path,
254 os.path.dirname(self._link_path))
255
256 os.symlink(source_path, os.path.abspath(self._link_path))
257 links_db[self._source_path] = self._link_path
258
259
260 class LinkError(IOError):
261 """Failed to create a link."""
262 pass
263
264
265 # Use junctions instead of symlinks on the Windows platform.
266 if sys.platform.startswith('win'):
267 def symlink(source_path, link_path):
268 if os.path.isdir(source_path):
269 subprocess.check_call(['cmd.exe', '/c', 'mklink', '/J', link_path,
270 source_path])
271 else:
272 # Don't create symlinks to files on Windows, just copy the file instead
273 # (there's no way to create a link without administrator's privileges).
274 shutil.copy(source_path, link_path)
275 os.symlink = symlink
276
277
278 class WebRTCLinkSetup(object):
279 def __init__(self, links_db, force=False, dry_run=False, prompt=False):
280 self._force = force
281 self._dry_run = dry_run
282 self._prompt = prompt
283 self._links_db = links_db
284
285 def CreateLinks(self, on_bot):
286 logging.debug('CreateLinks')
287 # First, make a plan of action
288 actions = []
289
290 for source_path, link_path in FILES.iteritems():
291 actions += self._ActionForPath(
292 source_path, link_path, check_fn=os.path.isfile, check_msg='files')
293 for source_dir in DIRECTORIES:
294 actions += self._ActionForPath(
295 source_dir, None, check_fn=os.path.isdir,
296 check_msg='directories')
297
298 if not on_bot and self._force:
299 # When making the manual switch from legacy SVN checkouts to the new
300 # Git-based Chromium DEPS, the .gclient_entries file that contains cached
301 # URLs for all DEPS entries must be removed to avoid future sync problems.
302 entries_file = os.path.join(os.path.dirname(ROOT_DIR), '.gclient_entries')
303 if os.path.exists(entries_file):
304 actions.append(Remove(entries_file, dangerous=True))
305
306 actions.sort()
307
308 if self._dry_run:
309 for action in actions:
310 action.announce(planning=True)
311 logging.info('Not doing anything because dry-run was specified.')
312 sys.exit(0)
313
314 if any(a.dangerous for a in actions):
315 logging.warn('Dangerous actions:')
316 for action in (a for a in actions if a.dangerous):
317 action.announce(planning=True)
318 print
319
320 if not self._force:
321 logging.error(textwrap.dedent("""\
322 @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
323 A C T I O N R E Q I R E D
324 @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
325
326 Setting up the checkout requires creating symlinks to directories in the
327 Chromium checkout inside chromium/src.
328 To avoid disrupting developers, we've chosen to not delete directories
329 forcibly, in case you have some work in progress in one of them :)
330
331 ACTION REQUIRED:
332 Before running `gclient sync|runhooks` again, you must run:
333 %s%s --force
334
335 Which will replace all directories which now must be symlinks, after
336 prompting with a summary of the work-to-be-done.
337 """), 'python ' if sys.platform.startswith('win') else '', __file__)
338 sys.exit(1)
339 elif self._prompt:
340 if not query_yes_no('Would you like to perform the above plan?'):
341 sys.exit(1)
342
343 for action in actions:
344 action.announce(planning=False)
345 action.doit(self._links_db)
346
347 if not on_bot and self._force:
348 logging.info('Completed!\n\nNow run `gclient sync|runhooks` again to '
349 'let the remaining hooks (that probably were interrupted) '
350 'execute.')
351
352 def CleanupLinks(self):
353 logging.debug('CleanupLinks')
354 for source, link_path in self._links_db.iteritems():
355 if source == 'SCHEMA_VERSION':
356 continue
357 if os.path.islink(link_path) or sys.platform.startswith('win'):
358 # os.path.islink() always returns false on Windows
359 # See http://bugs.python.org/issue13143.
360 logging.debug('Removing link to %s at %s', source, link_path)
361 if not self._dry_run:
362 if os.path.exists(link_path):
363 if sys.platform.startswith('win') and os.path.isdir(link_path):
364 subprocess.check_call(['rmdir', '/q', '/s', link_path],
365 shell=True)
366 else:
367 os.remove(link_path)
368 del self._links_db[source]
369
370 @staticmethod
371 def _ActionForPath(source_path, link_path=None, check_fn=None,
372 check_msg=None):
373 """Create zero or more Actions to link to a file or directory.
374
375 This will be a symlink on POSIX platforms. On Windows it will result in:
376 * a junction for directories
377 * a copied file for single files.
378
379 Args:
380 source_path: Path relative to the Chromium checkout root.
381 For readability, the path may contain slashes, which will
382 automatically be converted to the right path delimiter on Windows.
383 link_path: The location for the link to create. If omitted it will be the
384 same path as source_path.
385 check_fn: A function returning true if the type of filesystem object is
386 correct for the attempted call. Otherwise an error message with
387 check_msg will be printed.
388 check_msg: String used to inform the user of an invalid attempt to create
389 a file.
390 Returns:
391 A list of Action objects.
392 """
393 def fix_separators(path):
394 if sys.platform.startswith('win'):
395 return path.replace(os.altsep, os.sep)
396 else:
397 return path
398
399 assert check_fn
400 assert check_msg
401 link_path = link_path or source_path
402 link_path = fix_separators(link_path)
403
404 source_path = fix_separators(source_path)
405 source_path = os.path.join(CHROMIUM_CHECKOUT, source_path)
406 if os.path.exists(source_path) and not check_fn:
407 raise LinkError('Can only to link to %s: tried to link to: %s' %
408 (check_msg, source_path))
409
410 if not os.path.exists(source_path):
411 logging.debug('Silently ignoring missing source: %s. This is to avoid '
412 'errors on platform-specific dependencies.', source_path)
413 return []
414
415 actions = []
416
417 if os.path.exists(link_path) or os.path.islink(link_path):
418 if os.path.islink(link_path):
419 actions.append(Remove(link_path, dangerous=False))
420 elif os.path.isfile(link_path):
421 actions.append(Remove(link_path, dangerous=True))
422 elif os.path.isdir(link_path):
423 actions.append(Rmtree(link_path))
424 else:
425 raise LinkError('Don\'t know how to plan: %s' % link_path)
426
427 # Create parent directories to the target link if needed.
428 target_parent_dirs = os.path.dirname(link_path)
429 if (target_parent_dirs and
430 target_parent_dirs != link_path and
431 not os.path.exists(target_parent_dirs)):
432 actions.append(Makedirs(target_parent_dirs))
433
434 actions.append(Symlink(source_path, link_path))
435
436 return actions
437
438 def _initialize_database(filename):
439 links_database = shelve.open(filename)
440
441 # Wipe the database if this version of the script ends up looking at a
442 # newer (future) version of the links db, just to be sure.
443 version = links_database.get('SCHEMA_VERSION')
444 if version and version != SCHEMA_VERSION:
445 logging.info('Found database with schema version %s while this script only '
446 'supports %s. Wiping previous database contents.', version,
447 SCHEMA_VERSION)
448 links_database.clear()
449 links_database['SCHEMA_VERSION'] = SCHEMA_VERSION
450 return links_database
451
452
453 def main():
454 on_bot = os.environ.get('CHROME_HEADLESS') == '1'
455
456 parser = optparse.OptionParser()
457 parser.add_option('-d', '--dry-run', action='store_true', default=False,
458 help='Print what would be done, but don\'t perform any '
459 'operations. This will automatically set logging to '
460 'verbose.')
461 parser.add_option('-c', '--clean-only', action='store_true', default=False,
462 help='Only clean previously created links, don\'t create '
463 'new ones. This will automatically set logging to '
464 'verbose.')
465 parser.add_option('-f', '--force', action='store_true', default=on_bot,
466 help='Force link creation. CAUTION: This deletes existing '
467 'folders and files in the locations where links are '
468 'about to be created.')
469 parser.add_option('-n', '--no-prompt', action='store_false', dest='prompt',
470 default=(not on_bot),
471 help='Prompt if we\'re planning to do a dangerous action')
472 parser.add_option('-v', '--verbose', action='store_const',
473 const=logging.DEBUG, default=logging.INFO,
474 help='Print verbose output for debugging.')
475 options, _ = parser.parse_args()
476
477 if options.dry_run or options.force or options.clean_only:
478 options.verbose = logging.DEBUG
479 logging.basicConfig(format='%(message)s', level=options.verbose)
480
481 # Work from the root directory of the checkout.
482 script_dir = os.path.dirname(os.path.abspath(__file__))
483 os.chdir(script_dir)
484
485 if sys.platform.startswith('win'):
486 def is_admin():
487 try:
488 return os.getuid() == 0
489 except AttributeError:
490 return ctypes.windll.shell32.IsUserAnAdmin() != 0
491 if is_admin():
492 logging.warning('WARNING: On Windows, you no longer need run as '
493 'administrator. Please run with user account privileges.')
494
495 if not os.path.exists(CHROMIUM_CHECKOUT):
496 logging.warning('Cannot find a Chromium checkout at %s. Did you run '
497 '"gclient sync" before running this script?',
498 CHROMIUM_CHECKOUT)
499 return 0
500
501 links_database = _initialize_database(LINKS_DB)
502 try:
503 symlink_creator = WebRTCLinkSetup(links_database, options.force,
504 options.dry_run, options.prompt)
505 symlink_creator.CleanupLinks()
506 if not options.clean_only:
507 symlink_creator.CreateLinks(on_bot)
508 except LinkError as e:
509 print >> sys.stderr, e.message
510 return 3
511 finally:
512 links_database.close()
513 return 0
514
515
516 if __name__ == '__main__':
517 sys.exit(main())
OLDNEW
« no previous file with comments | « chromium/README ('k') | sync_chromium.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698