1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252
|
#!/usr/bin/env python3
# Copyright 2012 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Moves C++ files to a new location, updating any include paths that point
to them, and re-ordering headers as needed. If multiple source files are
specified, the destination must be a directory. Updates include guards in
moved header files. Assumes Chromium coding style.
Attempts to update and reorder paths used in .gyp(i) files.
Updates full-path references to files in // comments in source files.
Must run in a git checkout, as it relies on git grep for a fast way to
find files that reference the moved file.
"""
from __future__ import print_function
import optparse
import os
import re
import subprocess
import sys
import mffr
if __name__ == '__main__':
# Need to add the directory containing sort_sources.py to the Python
# classpath.
sys.path.append(os.path.abspath(os.path.join(sys.path[0], '..')))
import sort_sources
HANDLED_EXTENSIONS = ['.cc', '.mm', '.h', '.hh', '.cpp', '.mojom']
def IsHandledFile(path):
return os.path.splitext(path)[1] in HANDLED_EXTENSIONS
def MakeDestinationPath(from_path, to_path):
"""Given the from and to paths, return a correct destination path.
The initial destination path may either a full path or a directory.
Also does basic sanity checks.
"""
if not IsHandledFile(from_path):
raise Exception('Only intended to move individual source files '
'(%s does not have a recognized extension).' %
from_path)
# Remove '.', '..', etc.
to_path = os.path.normpath(to_path)
if os.path.isdir(to_path):
to_path = os.path.join(to_path, os.path.basename(from_path))
else:
dest_extension = os.path.splitext(to_path)[1]
if dest_extension not in HANDLED_EXTENSIONS:
raise Exception('Destination must be either a full path with '
'a recognized extension or a directory.')
return to_path
def MoveFile(from_path, to_path):
"""Performs a git mv command to move a file from |from_path| to |to_path|.
"""
if not os.system('git mv %s %s' % (from_path, to_path)) == 0:
raise Exception('Fatal: Failed to run git mv command.')
def UpdateIncludes(from_path, to_path):
"""Updates any includes of |from_path| to |to_path|. Paths supplied to this
function have been mapped to forward slashes.
"""
# This handles three types of include/imports:
# . C++ includes.
# . Object-C imports
# . Imports in mojom files.
files_with_changed_includes = mffr.MultiFileFindReplace(
r'(#?(include|import)\s*["<])%s([>"])' % re.escape(from_path),
r'\1%s\3' % to_path, ['*.cc', '*.h', '*.m', '*.mm', '*.cpp', '*.mojom'])
def UpdatePostMove(from_path, to_path):
"""Given a file that has moved from |from_path| to |to_path|,
updates the moved file's include guard to match the new path and
updates all references to the file in other source files. Also tries
to update references in .gyp(i) files using a heuristic.
"""
# Include paths always use forward slashes.
from_path = from_path.replace('\\', '/')
to_path = to_path.replace('\\', '/')
extension = os.path.splitext(from_path)[1]
if extension in ['.h', '.hh', '.mojom']:
UpdateIncludes(from_path, to_path)
if extension == '.mojom':
# For mojom files, update includes of generated headers.
UpdateIncludes(from_path + '.h', to_path + '.h')
UpdateIncludes(from_path + '-blink.h', to_path + '-blink.h')
UpdateIncludes(from_path + '-shared.h', to_path + '-shared.h')
UpdateIncludes(from_path + '-forward.h', to_path + '-forward.h')
else:
UpdateIncludeGuard(from_path, to_path)
# Update comments; only supports // comments, which are primarily
# used in our code.
#
# This work takes a bit of time. If this script starts feeling too
# slow, one good way to speed it up is to make the comment handling
# optional under a flag.
mffr.MultiFileFindReplace(
r'(//.*)%s' % re.escape(from_path),
r'\1%s' % to_path,
['*.cc', '*.h', '*.m', '*.mm', '*.cpp'])
# Update references in GYP and BUILD.gn files.
#
# GYP files are mostly located under the first level directory (ex.
# chrome/chrome_browser.gypi), but sometimes they are located in
# directories at a deeper level (ex. extensions/shell/app_shell.gypi). On
# the other hand, BUILD.gn files can be placed in any directories.
#
# Paths in a GYP or BUILD.gn file are relative to the directory where the
# file is placed.
#
# For instance, "chrome/browser/chromeos/device_uma.h" is listed as
# "browser/chromeos/device_uma.h" in "chrome/chrome_browser_chromeos.gypi",
# but it's listed as "device_uma.h" in "chrome/browser/chromeos/BUILD.gn".
#
# To handle this, the code here will visit directories from the top level
# src directory to the directory of |from_path| and try to update GYP and
# BUILD.gn files in each directory.
#
# The code only handles files moved/renamed within the same build file. If
# files are moved beyond the same build file, the affected build files
# should be fixed manually.
def SplitByFirstComponent(path):
"""'foo/bar/baz' -> ('foo', 'bar/baz')
'bar' -> ('bar', '')
'' -> ('', '')
"""
parts = re.split(r"[/\\]", path, maxsplit=1)
if len(parts) == 2:
return (parts[0], parts[1])
else:
return (parts[0], '')
visiting_directory = ''
from_rest = from_path
to_rest = to_path
while True:
files_with_changed_sources = mffr.MultiFileFindReplace(
r'([\'"])%s([\'"])' % from_rest,
r'\1%s\2' % to_rest,
[os.path.join(visiting_directory, 'BUILD.gn'),
os.path.join(visiting_directory, '*.gyp*')])
for changed_file in files_with_changed_sources:
sort_sources.ProcessFile(changed_file, should_confirm=False)
from_first, from_rest = SplitByFirstComponent(from_rest)
to_first, to_rest = SplitByFirstComponent(to_rest)
visiting_directory = os.path.join(visiting_directory, from_first)
if not from_rest or not to_rest or from_rest == to_rest:
break
def MakeIncludeGuardName(path_from_root):
"""Returns an include guard name given a path from root."""
guard = path_from_root.replace('/', '_')
guard = guard.replace('\\', '_')
guard = guard.replace('.', '_')
guard += '_'
return guard.upper()
def UpdateIncludeGuard(old_path, new_path):
"""Updates the include guard in a file now residing at |new_path|,
previously residing at |old_path|, with an up-to-date include guard.
Prints a warning if the update could not be completed successfully (e.g.,
because the old include guard was not formatted correctly per Chromium style).
"""
old_guard = MakeIncludeGuardName(old_path)
new_guard = MakeIncludeGuardName(new_path)
with open(new_path) as f:
contents = f.read()
new_contents = contents.replace(old_guard, new_guard)
# The file should now have three instances of the new guard: two at the top
# of the file plus one at the bottom for the comment on the #endif.
if new_contents.count(new_guard) != 3:
print('WARNING: Could not successfully update include guard; perhaps '
'old guard is not per style guide? You will have to update the '
'include guard manually. (%s)' % new_path)
with open(new_path, 'w', newline='\n') as f:
f.write(new_contents)
def main():
# We use "git rev-parse" to check if the script is run from a git checkout. It
# returns 0 even when run in the .git directory. We don't want people running
# this in the .git directory.
if (os.system('git rev-parse') != 0 or
os.path.basename(os.getcwd()) == '.git'):
print('Fatal: You must run in a git checkout.')
return 1
cwd = os.getcwd()
parent = os.path.dirname(cwd)
parser = optparse.OptionParser(usage='%prog FROM_PATH... TO_PATH')
parser.add_option('--already_moved', action='store_true',
dest='already_moved',
help='Causes the script to skip moving the file.')
parser.add_option('--no_error_for_non_source_file', action='store_false',
default='True',
dest='error_for_non_source_file',
help='Causes the script to simply print a warning on '
'encountering a non-source file rather than raising an '
'error.')
opts, args = parser.parse_args()
if len(args) < 2:
parser.print_help()
return 1
from_paths = args[:len(args)-1]
orig_to_path = args[-1]
if len(from_paths) > 1 and not os.path.isdir(orig_to_path):
print('Target %s is not a directory.' % orig_to_path)
print()
parser.print_help()
return 1
for from_path in from_paths:
if not opts.error_for_non_source_file and not IsHandledFile(from_path):
print('%s does not appear to be a source file, skipping' % (from_path))
continue
to_path = MakeDestinationPath(from_path, orig_to_path)
if not opts.already_moved:
MoveFile(from_path, to_path)
UpdatePostMove(from_path, to_path)
return 0
if __name__ == '__main__':
sys.exit(main())
|