rewrite_modern_objc.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. #!/usr/bin/env python
  2. # Copyright 2018 The Chromium Authors. All rights reserved.
  3. # Use of this source code is governed by a BSD-style license that can be
  4. # found in the LICENSE file.
  5. """Runs clang's "modern objective-c" rewriter on chrome code.
  6. Does the same as Xcode's Edit->Convert->To Modern Objective-C Syntax.
  7. Note that this just runs compile commands and doesn't look at build
  8. dependencies, i.e. it doesn't make sure generated headers exist. It also
  9. requires goma to be disabled. Suggested workflow: Build the target you want
  10. to convert locally with goma to create generated headers, then disable goma,
  11. re-run gn, and then run this script.
  12. Since Chrome's clang disables the rewriter, to run this you will need to
  13. build ToT clang with `-DCLANG_ENABLE_ARCMT` and (temporarily) add the following
  14. to your Chromium build args:
  15. clang_base_path = /path/to/clang
  16. clang_use_chrome_plugins = false
  17. """
  18. from __future__ import print_function
  19. import argparse
  20. import glob
  21. import json
  22. import math
  23. import os
  24. import shlex
  25. import subprocess
  26. import sys
  27. def main():
  28. # As far as I can tell, clang's ObjC rewriter can't do in-place rewriting
  29. # (the ARC rewriter can). libclang exposes functions for parsing the remap
  30. # file, but doing that manually in python seems a lot easier.
  31. parser = argparse.ArgumentParser(description=__doc__)
  32. parser.add_argument('builddir', help='build directory, e.g. out/gn')
  33. parser.add_argument('substr', default='', nargs='?',
  34. help='source dir part, eg chrome/browser/ui/cocoa')
  35. args = parser.parse_args()
  36. rewrite_dir = os.path.abspath(
  37. os.path.join(args.builddir, 'rewrite_modern_objc'))
  38. try:
  39. os.mkdir(rewrite_dir)
  40. except OSError:
  41. pass
  42. remap_file = os.path.join(rewrite_dir, 'remap')
  43. try:
  44. # Remove remap files from prior runs.
  45. os.remove(remap_file)
  46. except OSError:
  47. pass
  48. # The basic idea is to call clang's objcmt rewriter for each source file.
  49. # The rewriter writes a "remap" file containing N times 3 lines:
  50. # Name of an original source file, the original file's timestamp
  51. # at rewriting time, and the name of a temp file containing the rewritten
  52. # contents.
  53. # The rewriter gets confused if several instances run in parallel. We could
  54. # be fancy and have num_cpus rewrite dirs and combine their contents in the
  55. # end, but for now just run the rewrites serially.
  56. # First, ask ninja for the compile commands of all .m and .mm files.
  57. compdb = subprocess.check_output(
  58. ['ninja', '-C', args.builddir, '-t', 'compdb', 'objc', 'objcxx'])
  59. for cmd in json.loads(compdb):
  60. objc_file = cmd['file']
  61. if args.substr not in objc_file:
  62. continue
  63. clang_cmd = cmd['command']
  64. had_error = False
  65. if 'gomacc' in clang_cmd:
  66. print('need builddir with use_goma not set', file=sys.stderr)
  67. had_error = True
  68. if 'jumbo' in clang_cmd:
  69. print('need builddir with use_jumbo_build not set', file=sys.stderr)
  70. had_error = True
  71. if 'precompile.h-m' in clang_cmd:
  72. print(
  73. 'need builddir with enable_precompiled_headers=false',
  74. file=sys.stderr)
  75. had_error = True
  76. if had_error:
  77. sys.exit(1)
  78. # Ninja creates the directory containing the build output, but we
  79. # don't run ninja, so we need to do that ourselves.
  80. split_cmd = shlex.split(clang_cmd)
  81. o_index = split_cmd.index('-o')
  82. assert o_index != -1
  83. try:
  84. os.makedirs(os.path.dirname(split_cmd[o_index + 1]))
  85. except OSError:
  86. pass
  87. # Add flags to tell clang to do the rewriting.
  88. # Passing "-ccc-objcmt-migrate dir" doesn't give us control over each
  89. # individual setting, so use the Xclang flags. The individual flags are at
  90. # http://llvm-cs.pcc.me.uk/tools/clang/include/clang/Driver/Options.td#291
  91. # Note that -objcmt-migrate-all maps to ObjCMT_MigrateDecls in
  92. # http://llvm-cs.pcc.me.uk/tools/clang/lib/Frontend/CompilerInvocation.cpp#1479
  93. # which is not quite all the options:
  94. # http://llvm-cs.pcc.me.uk/tools/clang/include/clang/Frontend/FrontendOptions.h#248
  95. flags = ['-Xclang', '-mt-migrate-directory', '-Xclang', rewrite_dir]
  96. flags += ['-Xclang', '-objcmt-migrate-subscripting' ]
  97. flags += ['-Xclang', '-objcmt-migrate-literals' ]
  98. #flags += ['-Xclang', '-objcmt-returns-innerpointer-property'] # buggy
  99. #flags += ['-Xclang', '-objcmt-migrate-property-dot-syntax'] # do not want
  100. # objcmt-migrate-all is the same as the flags following it here (it does
  101. # not include the flags listed above it).
  102. # Probably don't want ns-nonatomic-iosonly (or atomic-property), so we
  103. # can't use migrate-alll which includes that, and have to manually set the
  104. # bits of migrate-all we do want.
  105. #flags += ['-Xclang', '-objcmt-migrate-all']
  106. #flags += ['-Xclang', '-objcmt-migrate-property'] # not sure if want
  107. flags += ['-Xclang', '-objcmt-migrate-annotation']
  108. flags += ['-Xclang', '-objcmt-migrate-instancetype']
  109. flags += ['-Xclang', '-objcmt-migrate-ns-macros']
  110. #flags += ['-Xclang', '-objcmt-migrate-protocol-conformance'] # buggy
  111. #flags += ['-Xclang', '-objcmt-atomic-property'] # not sure if want
  112. #flags += ['-Xclang', '-objcmt-ns-nonatomic-iosonly'] # not sure if want
  113. # Want, but needs careful manual review, and doesn't find everything:
  114. #flags += ['-Xclang', '-objcmt-migrate-designated-init']
  115. clang_cmd += ' ' + ' '.join(flags)
  116. print(objc_file)
  117. subprocess.check_call(clang_cmd, shell=True, cwd=cmd['directory'])
  118. if not os.path.exists(remap_file):
  119. print('no changes')
  120. return
  121. # Done with rewriting. Now the read the above-described 'remap' file and
  122. # copy modified files over the originals.
  123. remap = open(remap_file).readlines()
  124. for i in range(0, len(remap), 3):
  125. infile, mtime, outfile = map(str.strip, remap[i:i+3])
  126. if args.substr not in infile:
  127. # Ignore rewritten header files not containing args.substr too.
  128. continue
  129. if math.trunc(os.path.getmtime(infile)) != int(mtime):
  130. print('%s was modified since rewriting; exiting' % infile)
  131. sys.exit(1)
  132. os.rename(outfile, infile) # Copy rewritten file over.
  133. print('all done. commit, run `git cl format`, commit again, and upload!')
  134. if __name__ == '__main__':
  135. main()