blob: 2b5969384ce78bf97468401856c5dfbd3481f7bc [file] [log] [blame]
#!/usr/bin/env vpython
# Copyright 2017 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Tool to move Blink source from third_party/WebKit to third_party/blink.
See https://docs.google.com/document/d/1l3aPv1Wx__SpRkdOhvJz8ciEGigNT3wFKv78XiuW0Tw/edit?usp=sharing#heading=h.o225wrxp242h
for the details.
"""
import argparse
import logging
import os
import re
import sys
from functools import partial
# Without abspath(), PathFinder can't find chromium_base correctly.
sys.path.append(os.path.abspath(
os.path.join(os.path.dirname(__file__), '..', '..', '..',
'third_party', 'WebKit', 'Tools', 'Scripts')))
from blinkpy.common.name_style_converter import NameStyleConverter
from plan_blink_move import plan_blink_move
from plan_blink_move import relative_dest
from webkitpy.common.checkout.git import Git
from webkitpy.common.path_finder import get_chromium_src_dir
from webkitpy.common.path_finder import get_scripts_dir
from webkitpy.common.system.executive import Executive
from webkitpy.common.system.filesystem import FileSystem
_log = logging.getLogger('move_blink_source')
class FileType(object):
NONE = 0
BUILD = 1
BLINK_BUILD = 2
OWNERS = 3
DEPS = 4
MOJOM = 5
TYPEMAP = 6
BLINK_BUILD_PY = 7
LAYOUT_TESTS_WITH_MOJOM = 8
@staticmethod
def detect(path):
slash_dir, basename = os.path.split(path)
slash_dir = slash_dir.replace(os.path.sep, '/')
if basename == 'DEPS':
return FileType.DEPS
if basename == 'OWNERS':
return FileType.OWNERS
if basename.endswith('.mojom'):
return FileType.MOJOM
if basename.endswith('.typemap'):
return FileType.TYPEMAP
if basename.endswith('.py') and 'third_party/WebKit/Source/build' in slash_dir:
return FileType.BLINK_BUILD_PY
if basename.endswith(('.gn', '.gni')):
if 'third_party/WebKit' in path or 'third_party/blink' in slash_dir:
return FileType.BLINK_BUILD
if 'third_party' in slash_dir:
return FileType.NONE
return FileType.BUILD
if basename.endswith('.html') and re.search(
r'third_party/WebKit/LayoutTests/(geolocation-api|installedapp|' +
r'media/mediasession|payments|presentation|webshare)', slash_dir):
return FileType.LAYOUT_TESTS_WITH_MOJOM
return FileType.NONE
class MoveBlinkSource(object):
def __init__(self, fs, options, repo_root):
self._fs = fs
self._options = options
_log.debug(options)
self._repo_root = repo_root
# The following fields are initialized in _create_basename_maps.
self._basename_map = None
self._basename_re = None
self._idl_generated_impl_headers = None
# _checked_in_header_re is used to distinguish checked-in header files
# and generated header files.
self._checked_in_header_re = None
self._updated_files = []
def update(self):
_log.info('Planning renaming ...')
file_pairs = plan_blink_move(self._fs, [])
_log.info('Will move %d files', len(file_pairs))
self._create_basename_maps(file_pairs)
dirs = self._update_file_content()
# Updates #includes in files in directories with updated DEPS +
# third_party/WebKit/{Source,common,public}.
self._append_unless_upper_dir_exists(dirs, self._fs.join(self._repo_root, 'third_party', 'WebKit', 'Source'))
self._append_unless_upper_dir_exists(dirs, self._fs.join(self._repo_root, 'third_party', 'WebKit', 'common'))
self._append_unless_upper_dir_exists(dirs, self._fs.join(self._repo_root, 'third_party', 'WebKit', 'public'))
self._append_unless_upper_dir_exists(dirs, self._fs.join(self._repo_root, 'mojo', 'public', 'tools',
'bindings', 'generators', 'cpp_templates'))
self._update_cpp_includes_in_directories(dirs)
# Content update for individual files.
# The following is a list of tuples.
# Tuple: (<file path relative to repo root>, [replacement commands])
# Command: a callable object, or
# a tuple of (<original string>, <new string>).
file_replacement_list = [
('DEPS',
[('src/third_party/WebKit/Source/devtools',
'src/third_party/blink/renderer/devtools')]),
('WATCHLISTS',
[('third_party/WebKit/Source', 'third_party/blink/renderer'),
('third_party/WebKit/public', 'third_party/blink/renderer/public')]),
('build/check_gn_headers_whitelist.txt',
[('third_party/WebKit/Source', 'third_party/blink/renderer'),
('third_party/WebKit/public', 'third_party/blink/renderer/public'),
self._update_basename]),
('testing/buildbot/gn_isolate_map.pyl',
[('third_party/WebKit/Source', 'third_party/blink/renderer')]),
('third_party/WebKit/Source/BUILD.gn',
[('$root_gen_dir/third_party/WebKit',
'$root_gen_dir/third_party/blink/renderer')]),
('third_party/WebKit/Source/config.gni',
[('snake_case_source_files = false',
'snake_case_source_files = true')]),
('third_party/WebKit/Source/core/css/CSSProperties.json5',
[self._update_basename]),
('third_party/WebKit/Source/core/css/ComputedStyleExtraFields.json5',
[self._update_basename]),
('third_party/WebKit/Source/core/css/ComputedStyleFieldAliases.json5',
[self._update_basename]),
('third_party/WebKit/Source/core/html/parser/create-html-entity-table',
[self._update_basename]),
('third_party/WebKit/Source/core/inspector/inspector_protocol_config.json',
[self._update_basename]),
('third_party/WebKit/Source/core/probe/CoreProbes.json5',
[self._update_basename]),
('third_party/WebKit/Source/core/testing/InternalSettings.h',
[('InternalSettingsGenerated.h', 'internal_settings_generated.h')]),
('third_party/WebKit/Source/core/testing/Internals.cpp',
[('InternalRuntimeFlags.h', 'internal_runtime_flags.h')]),
('third_party/WebKit/Source/platform/probe/PlatformProbes.json5',
[self._update_basename]),
('third_party/WebKit/public/BUILD.gn',
[('$root_gen_dir/third_party/WebKit',
'$root_gen_dir/third_party/blink/renderer')]),
('third_party/WebKit/public/blink_resources.grd',
[('../Source/', '../')]),
('tools/android/eclipse/.classpath',
[('third_party/WebKit/public', 'third_party/blink/renderer/public')]),
('tools/android/loading/cloud/backend/deploy.sh',
[('third_party/WebKit/Source', 'third_party/blink/renderer')]),
('tools/android/loading/emulation_unittest.py',
[('third_party/WebKit/Source', 'third_party/blink/renderer')]),
('tools/android/loading/options.py',
[('third_party/WebKit/Source', 'third_party/blink/renderer')]),
('tools/android/loading/request_track.py',
[('third_party/WebKit/Source', 'third_party/blink/renderer')]),
('tools/gritsettings/resource_ids',
[('third_party/WebKit/public', 'third_party/blink/renderer/public'),
('third_party/WebKit/Source', 'third_party/blink/renderer')]),
('tools/metrics/actions/extract_actions.py',
[('third_party/WebKit/Source', 'third_party/blink/renderer')]),
('tools/metrics/histograms/update_editor_commands.py',
[('third_party/WebKit/Source/core/editing/EditorCommand.cpp',
'third_party/blink/renderer/core/editing/editor_command.cc')]),
('tools/metrics/histograms/update_use_counter_css.py',
[('third_party/WebKit/Source/core/frame/UseCounter.cpp',
'third_party/blink/renderer/core/frame/use_counter.cc')]),
('tools/metrics/histograms/update_use_counter_feature_enum.py',
[('third_party/WebKit/public', 'third_party/blink/renderer/public')]),
]
for file_path, replacement_list in file_replacement_list:
self._update_single_file_content(file_path, replacement_list, should_write=self._options.run)
if self._options.run:
_log.info('Formatting updated %d files ...', len(self._updated_files))
git = Git(cwd=self._repo_root)
# |git cl format| can't handle too many files at once.
while len(self._updated_files) > 0:
end_index = 100
if end_index > len(self._updated_files):
end_index = len(self._updated_files)
git.run(['cl', 'format'] + self._updated_files[:end_index])
self._updated_files = self._updated_files[end_index:]
_log.info('Make a local commit ...')
git.commit_locally_with_message("""The Great Blink mv for source files, part 1.
Update file contents without moving files.
Bug: 768828
""")
def move(self):
_log.info('Planning renaming ...')
file_pairs = plan_blink_move(self._fs, [])
_log.info('Will move %d files', len(file_pairs))
git = Git(cwd=self._repo_root)
for i, (src, dest) in enumerate(file_pairs):
src_from_repo = self._fs.join('third_party', 'WebKit', src)
dest_from_repo = self._fs.join('third_party', 'blink', dest)
self._fs.maybe_make_directory(self._repo_root, 'third_party', 'blink', self._fs.dirname(dest))
if self._options.run_git:
if git.exists(src_from_repo):
git.move(src_from_repo, dest_from_repo)
_log.info('[%d/%d] Git moved %s', i + 1, len(file_pairs), src)
else:
_log.info('%s is not in the repository', src)
else:
self._fs.move(self._fs.join(self._repo_root, src_from_repo),
self._fs.join(self._repo_root, dest_from_repo))
_log.info('[%d/%d] Moved %s', i + 1, len(file_pairs), src)
self._update_single_file_content(
'build/get_landmines.py',
[('\ndef main', ' print \'The Great Blink mv for source files (crbug.com/768828)\'\n\ndef main')])
_log.info('Run run-bindings-tests ...')
Executive().run_command(['python',
self._fs.join(get_scripts_dir(), 'run-bindings-tests'),
'--reset-results'],
cwd=self._repo_root)
if self._options.run_git:
_log.info('Make a local commit ...')
git.commit_locally_with_message("""The Great Blink mv for source files, part 2.
Move and rename files.
Bug: 768828
""")
def _create_basename_maps(self, file_pairs):
basename_map = {}
# Generated inspector/protocol/* contains a lot of names duplicated with
# checked-in core files. We don't want to rename them, and don't want to
# replace them in BUILD.gn and #include accidentally.
pattern = r'(?<!inspector/protocol/)\b('
idl_headers = set()
header_pattern = r'(?<!inspector/protocol/)\b('
for source, dest in file_pairs:
_, source_base = self._fs.split(source)
_, dest_base = self._fs.split(dest)
# ConditionalFeaturesForCore.h in bindings/tests/results/modules/
# confuses generated/checked-in detection in _replace_include_path().
if 'bindings/tests' in source.replace('\\', '/'):
continue
if source_base.endswith('.h'):
header_pattern += re.escape(source_base) + '|'
if source_base == dest_base:
continue
basename_map[source_base] = dest_base
pattern += re.escape(source_base) + '|'
# IDL sometimes generates implementation files as well as
# binding files. We'd like to update #includes for such files.
if source_base.endswith('.idl'):
source_header = source_base.replace('.idl', '.h')
basename_map[source_header] = dest_base.replace('.idl', '.h')
pattern += re.escape(source_header) + '|'
idl_headers.add(source_header)
_log.info('Rename %d files for snake_case', len(basename_map))
self._basename_map = basename_map
self._basename_re = re.compile(pattern[0:len(pattern) - 1] + ')(?=["\']|$)')
self._idl_generated_impl_headers = idl_headers
self._checked_in_header_re = re.compile(header_pattern[0:len(header_pattern) - 1] + ')$')
def _shorten_path(self, path):
if path.startswith(self._repo_root):
return path[len(self._repo_root) + 1:]
return path
@staticmethod
def _filter_file(fs, dirname, basename):
return FileType.detect(fs.join(dirname, basename)) != FileType.NONE
def _update_build(self, content):
content = content.replace('//third_party/WebKit/Source', '//third_party/blink/renderer')
content = content.replace('//third_party/WebKit/common', '//third_party/blink/common')
content = content.replace('//third_party/WebKit/public', '//third_party/blink/renderer/public')
# export_header_blink exists outside of Blink too.
content = content.replace('export_header_blink = "third_party/WebKit/public/platform/WebCommon.h"',
'export_header_blink = "third_party/blink/renderer/public/platform/web_common.h"')
return content
def _update_blink_build(self, content):
content = self._update_build(content)
# Update visibility=[...]
content = content.replace('//third_party/WebKit/*', '//third_party/blink/*')
content = content.replace('//third_party/WebKit/Source/*', '//third_party/blink/renderer/*')
content = content.replace('//third_party/WebKit/public/*', '//third_party/blink/renderer/public/*')
return self._update_basename(content)
def _update_owners(self, content):
content = content.replace('//third_party/WebKit/Source', '//third_party/blink/renderer')
content = content.replace('//third_party/WebKit/common', '//third_party/blink/common')
content = content.replace('//third_party/WebKit/public', '//third_party/blink/renderer/public')
return content
def _update_deps(self, content):
original_content = content
content = content.replace('third_party/WebKit/Source', 'third_party/blink/renderer')
content = content.replace('third_party/WebKit/common', 'third_party/blink/common')
content = content.replace('third_party/WebKit/public', 'third_party/blink/renderer/public')
content = content.replace('third_party/WebKit', 'third_party/blink')
if original_content == content:
return content
return self._update_basename(content)
def _update_mojom(self, content):
content = content.replace('third_party/WebKit/public', 'third_party/blink/renderer/public')
return content
def _update_typemap(self, content):
content = content.replace('//third_party/WebKit/Source', '//third_party/blink/renderer')
content = content.replace('//third_party/WebKit/common', '//third_party/blink/common')
content = content.replace('//third_party/WebKit/public', '//third_party/blink/renderer/public')
return self._update_basename(content)
def _update_blink_build_py(self, content):
# We don't prepend 'third_party/blink/renderer/' to matched basenames
# because it won't affect build and manual update after the great mv is
# enough.
return self._update_basename(content)
def _update_layout_tests(self, content):
return content.replace('file:///gen/third_party/WebKit/', 'file:///gen/third_party/blink/renderer/')
def _update_basename(self, content):
return self._basename_re.sub(lambda match: self._basename_map[match.group(1)], content)
@staticmethod
def _append_unless_upper_dir_exists(dirs, new_dir):
for i in range(0, len(dirs)):
if new_dir.startswith(dirs[i]):
return
if dirs[i].startswith(new_dir):
dirs[i] = new_dir
return
dirs.append(new_dir)
def _update_file_content(self):
_log.info('Find *.gn, *.mojom, *.py, *.typemap, DEPS, and OWNERS ...')
files = self._fs.files_under(
self._repo_root, dirs_to_skip=['.git', 'out'], file_filter=self._filter_file)
_log.info('Scan contents of %d files ...', len(files))
updated_deps_dirs = []
for file_path in files:
file_type = FileType.detect(file_path)
original_content = self._fs.read_text_file(file_path)
content = original_content
if file_type == FileType.BUILD:
content = self._update_build(content)
elif file_type == FileType.BLINK_BUILD:
content = self._update_blink_build(content)
elif file_type == FileType.OWNERS:
content = self._update_owners(content)
elif file_type == FileType.DEPS:
if self._fs.dirname(file_path) == self._repo_root:
_log.info("Skip //DEPS")
continue
content = self._update_deps(content)
elif file_type == FileType.MOJOM:
content = self._update_mojom(content)
elif file_type == FileType.TYPEMAP:
content = self._update_typemap(content)
elif file_type == FileType.BLINK_BUILD_PY:
content = self._update_blink_build_py(content)
elif file_type == FileType.LAYOUT_TESTS_WITH_MOJOM:
content = self._update_layout_tests(content)
if original_content == content:
continue
if self._options.run:
self._fs.write_text_file(file_path, content)
self._updated_files.append(file_path)
if file_type == FileType.DEPS:
self._append_unless_upper_dir_exists(updated_deps_dirs, self._fs.dirname(file_path))
_log.info('Updated %s', self._shorten_path(file_path))
return updated_deps_dirs
def _update_cpp_includes_in_directories(self, dirs):
for dirname in dirs:
_log.info('Processing #include in %s ...', self._shorten_path(dirname))
files = self._fs.files_under(
dirname, file_filter=lambda fs, _, basename: basename.endswith(
('.h', '.cc', '.cpp', '.mm', '.cc.tmpl', '.cpp.tmpl',
'.h.tmpl', 'XPathGrammar.y', '.gperf')))
for file_path in files:
original_content = self._fs.read_text_file(file_path)
content = self._update_cpp_includes(original_content)
if file_path.endswith('.h') and '/third_party/WebKit/public/' in file_path.replace('\\', '/'):
content = self._update_basename_only_includes(content, file_path)
if file_path.endswith('.h') and '/third_party/WebKit/' in file_path.replace('\\', '/'):
content = self._update_include_guard(content, file_path)
if original_content == content:
continue
if self._options.run:
self._fs.write_text_file(file_path, content)
self._updated_files.append(file_path)
_log.info('Updated %s', self._shorten_path(file_path))
def _replace_include_path(self, match):
include_or_import = match.group(1)
path = match.group(2)
# If |path| starts with 'third_party/WebKit', we should adjust the
# directory name for third_party/blink, and replace its basename by
# self._basename_map.
#
# If |path| starts with a Blink-internal directory such as bindings,
# core, modules, platform, public, it refers to a checked-in file, or a
# generated file. For the former, we should add
# 'third_party/blink/renderer/' and replace the basename. For the
# latter, we should update the basename for a name mapped from an IDL
# renaming, and should not add 'third_party/blink/renderer'.
if path.startswith('third_party/WebKit'):
path = path.replace('third_party/WebKit/Source', 'third_party/blink/renderer')
path = path.replace('third_party/WebKit/common', 'third_party/blink/common')
path = path.replace('third_party/WebKit/public', 'third_party/blink/renderer/public')
path = self._update_basename(path)
return '#%s "%s"' % (include_or_import, path)
match = self._checked_in_header_re.search(path)
if match:
if match.group(1) in self._basename_map:
path = 'third_party/blink/renderer/' + path[:match.start(1)] + self._basename_map[match.group(1)]
else:
path = 'third_party/blink/renderer/' + path
elif 'core/inspector/protocol/' not in path:
basename_start = path.rfind('/') + 1
basename = path[basename_start:]
if basename in self._idl_generated_impl_headers:
path = path[:basename_start] + self._basename_map[basename]
elif basename.startswith('V8'):
path = path[:basename_start] + NameStyleConverter(basename[:len(basename) - 2]).to_snake_case() + '.h'
return '#%s "%s"' % (include_or_import, path)
def _update_cpp_includes(self, content):
pattern = re.compile(r'#(include|import)\s+"((bindings|core|modules|platform|public|' +
r'third_party/WebKit/(Source|common|public))/[-_\w/.]+)"')
return pattern.sub(self._replace_include_path, content)
def _replace_basename_only_include(self, subdir, source_path, match):
source_basename = match.group(1)
if source_basename in self._basename_map:
return '#include "third_party/blink/renderer/public/%s/%s"' % (subdir, self._basename_map[source_basename])
_log.warning('Basename-only %s in %s', match.group(0), self._shorten_path(source_path))
return match.group(0)
def _update_basename_only_includes(self, content, source_path):
if not source_path.endswith('.h') or '/third_party/WebKit/public/' not in source_path.replace('\\', '/'):
return
# In public/ header files, we should replace |#include "WebFoo.h"|
# with |#include "third_party/blink/renderer/public/platform-or-web/web_foo.h"|
subdir = self._fs.basename(self._fs.dirname(source_path))
# subdir is 'web' or 'platform'.
return re.sub(r'#include\s+"(\w+\.h)"',
partial(self._replace_basename_only_include, subdir, source_path), content)
def _update_include_guard(self, content, source_path):
current_guard = re.sub(r'[.]', '_', self._fs.basename(source_path))
new_path = relative_dest(self._fs, self._fs.relpath(
source_path, start=self._fs.join(self._repo_root, 'third_party', 'WebKit')))
new_guard = 'THIRD_PARTY_BLINK_' + re.sub(r'[\\/.]', '_', new_path.upper()) + '_'
content = re.sub(r'#ifndef\s+(WTF_)?' + current_guard, '#ifndef ' + new_guard, content);
content = re.sub(r'#define\s+(WTF_)?' + current_guard, '#define ' + new_guard, content);
content = re.sub(r'#endif\s+//\s+(WTF_)?' + current_guard, '#endif // ' + new_guard, content);
return content
def _update_single_file_content(self, file_path, replace_list, should_write=True):
full_path = self._fs.join(self._repo_root, file_path)
original_content = self._fs.read_text_file(full_path)
content = original_content
for command in replace_list:
if isinstance(command, tuple):
src, dest = command
content = content.replace(src, dest)
elif callable(command):
content = command(content)
else:
raise TypeError('A tuple or a function is expected.')
if content != original_content:
if should_write:
self._fs.write_text_file(full_path, content)
self._updated_files.append(full_path)
_log.info('Updated %s', file_path)
else:
_log.warning('%s does not contain specified source strings.', file_path)
def main():
logging.basicConfig(level=logging.DEBUG,
format='[%(asctime)s %(levelname)s %(name)s] %(message)s',
datefmt='%H:%M:%S')
parser = argparse.ArgumentParser(description='Blink source mover')
sub_parsers = parser.add_subparsers()
update_parser = sub_parsers.add_parser('update')
update_parser.set_defaults(command='update')
update_parser.add_argument('--run', dest='run', action='store_true',
help='Update file contents')
move_parser = sub_parsers.add_parser('move')
move_parser.set_defaults(command='move')
move_parser.add_argument('--git', dest='run_git', action='store_true',
help='Run |git mv| command instead of |mv|.')
options = parser.parse_args()
mover = MoveBlinkSource(FileSystem(), options, get_chromium_src_dir())
if options.command == 'update':
mover.update()
elif options.command == 'move':
mover.move()
if __name__ == '__main__':
main()