| #!/usr/bin/python | |
| # | |
| # Copyright 2019 Google LLC | |
| # | |
| # Use of this source code is governed by a BSD-style license that can be | |
| # found in the LICENSE file. | |
| import argparse | |
| import os | |
| import re | |
| import sys | |
| from io import StringIO | |
| parser = argparse.ArgumentParser() | |
| parser.add_argument('-n', '--dry-run', action='store_true', | |
| help='Just check there is nothing to rewrite.') | |
| parser.add_argument('sources', nargs='*', | |
| help='Source files to rewrite, or all if empty.') | |
| args = parser.parse_args() | |
| roots = [ | |
| 'bench', | |
| 'dm', | |
| 'docs', | |
| 'experimental', | |
| 'fuzz', | |
| 'gm', | |
| 'include', | |
| 'modules', | |
| 'platform_tools/android/apps', | |
| 'samplecode', | |
| 'src', | |
| 'tests', | |
| 'third_party/etc1', | |
| 'third_party/gif', | |
| 'tools' | |
| ] | |
| ignorelist = [ | |
| # Don't count our local Vulkan headers as Skia headers; | |
| # we don't want #include <vulkan/vulkan_foo.h> rewritten to point to them. | |
| 'include/third_party/vulkan', | |
| # Some node_modules/ files (used by CanvasKit et al) have c++ code which we should ignore. | |
| 'node_modules', | |
| 'include/third_party/skcms', | |
| 'src/gpu/vk/vulkanmemoryallocator', | |
| ] | |
| assert '/' in [os.sep, os.altsep] | |
| def fix_path(p): | |
| return p.replace(os.sep, os.altsep) if os.altsep else p | |
| # Map short name -> absolute path for all Skia headers. Duplicate short names are allowed, the | |
| # stored value will hold a list of absolute paths. If a short name is found in source code and the | |
| # absolute path is ambiguous, an error message is reported. | |
| headers = {} | |
| for root in roots: | |
| for path, _, files in os.walk(root): | |
| if not any(snippet in fix_path(path) for snippet in ignorelist): | |
| for file_name in files: | |
| if file_name.endswith('.h') and not file_name in ignorelist: | |
| full_name = os.path.abspath(os.path.join(path, file_name)) | |
| include_path = fix_path(os.path.relpath(full_name, '.')) | |
| headers.setdefault(file_name, []).append(include_path) | |
| def to_rewrite(): | |
| if args.sources: | |
| for path in args.sources: | |
| yield path | |
| else: | |
| for root in roots: | |
| for path, _, files in os.walk(root): | |
| for file_name in files: | |
| yield os.path.join(path, file_name) | |
| # Rewrite any #includes relative to Skia's top-level directory and sort forward declarations. | |
| fwd_decl_re = re.compile(r'^\s*(?:class|struct)\s+[A-Za-z_]\w*\s*;\s*$') | |
| need_rewriting = [] | |
| rewrite_is_automatic = True | |
| for file_path in to_rewrite(): | |
| if ('/generated/' in file_path or | |
| 'tests/sksl/' in file_path or | |
| 'third_party/skcms' in file_path or | |
| 'modules/skcms' in file_path or | |
| 'tools/window' in file_path or | |
| file_path.startswith('bazel/rbe') or | |
| 'example/external_client/' in file_path or | |
| # We intentionally list SkUserConfig.h not from the root in this file. | |
| file_path == 'include/private/SkLoadUserConfig.h'): | |
| continue | |
| if (file_path.endswith('.h') or | |
| file_path.endswith('.c') or | |
| file_path.endswith('.m') or | |
| file_path.endswith('.mm') or | |
| file_path.endswith('.inc') or | |
| file_path.endswith('.cc') or | |
| file_path.endswith('.cpp')): | |
| # Read the whole file into memory. | |
| lines = open(file_path).readlines() | |
| # Write it back out again line by line with substitutions for #includes. | |
| output = StringIO() if args.dry_run else open(file_path, 'w', newline='\n') | |
| includes = [] | |
| fwd_decls = [] | |
| prev_line_is_template = False | |
| def flush_includes(): | |
| # Deduplicate includes in this block. If a file needs to be included | |
| # multiple times, the separate includes should go in different blocks. | |
| for inc in sorted(list(set(includes))): | |
| output.write(inc.strip('\n') + '\n') | |
| includes.clear() | |
| def flush_fwd_decls(): | |
| for decl in sorted(list(set(fwd_decls))): | |
| output.write(decl.strip('\n') + '\n') | |
| fwd_decls.clear() | |
| for line in lines: | |
| parts = line.replace('<', '"').replace('>', '"').split('"') | |
| if (len(parts) == 3 | |
| and '#' in parts[0] | |
| and 'include' in parts[0] | |
| and not parts[1].startswith('partition_alloc/') | |
| and os.path.basename(parts[1]) in headers): | |
| flush_fwd_decls() | |
| include_paths = headers[os.path.basename(parts[1])] | |
| if (len(include_paths) == 1): | |
| header = include_paths[0] | |
| else: | |
| # This either represents a valid include path in include_paths, or it preserves the | |
| # original ambiguous include that has to be manually fixed. | |
| header = parts[1] | |
| if not (parts[1] in include_paths): | |
| need_rewriting.append(file_path) | |
| rewrite_is_automatic = False | |
| print('File has an ambiguous relative include: ' + file_path) | |
| print('Replace "' + parts[1] + '" with one of:') | |
| for opt in include_paths: | |
| print('\t' + opt) | |
| includes.append(parts[0] + '"%s"' % header + parts[2]) | |
| prev_line_is_template = False | |
| elif fwd_decl_re.match(line) and not prev_line_is_template: | |
| flush_includes() | |
| fwd_decls.append(line) | |
| prev_line_is_template = False | |
| else: | |
| flush_includes() | |
| flush_fwd_decls() | |
| output.write(line.strip('\n') + '\n') | |
| prev_line_is_template = line.strip().startswith('template') | |
| # Fix any straggling includes or forward declarations. | |
| flush_includes() | |
| flush_fwd_decls() | |
| if args.dry_run and output.getvalue() != open(file_path).read(): | |
| need_rewriting.append(file_path) | |
| output.close() | |
| if need_rewriting: | |
| print('Some files need rewritten #includes or sorted forward declarations:') | |
| for path in need_rewriting: | |
| print('\t' + path) | |
| if rewrite_is_automatic: | |
| print('To do this automatically, run') | |
| print('python3 tools/rewrite_includes.py ' + ' '.join(need_rewriting)) | |
| else: | |
| print('Automatic rewritting of includes not possible.') | |
| sys.exit(1) |