upload_screenshots.py 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  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. """A tool to upload translation screenshots to Google Cloud Storage.
  6. This tool searches the current repo for .png files associated with .grd or
  7. .grdp files. It uploads the images to a Cloud Storage bucket and generates .sha1
  8. files. Finally, it asks the user if they want to add the .sha1 files to their
  9. CL.
  10. Images must be named the same as the UI strings they represent
  11. (e.g. IDS_HELLO.png for IDS_HELLO). The tool does NOT try to parse .grd/.grdp
  12. files, so it doesn't know whether an image file corresponds to a message or not.
  13. It will attempt to upload the image anyways.
  14. """
  15. from __future__ import print_function
  16. try:
  17. # In Python2, override input with raw_input for compatibility.
  18. input = raw_input # pylint: disable=redefined-builtin
  19. except NameError:
  20. pass
  21. import argparse
  22. import sys
  23. import os
  24. import subprocess
  25. import helper.translation_helper as translation_helper
  26. import helper.git_helper as git_helper
  27. here = os.path.dirname(os.path.realpath(__file__))
  28. src_path = os.path.normpath(os.path.join(here, '..', '..'))
  29. depot_tools_path = os.path.normpath(
  30. os.path.join(src_path, 'third_party', 'depot_tools'))
  31. sys.path.insert(0, depot_tools_path)
  32. import upload_to_google_storage
  33. import download_from_google_storage
  34. sys.path.remove(depot_tools_path)
  35. # Translation expectations file for the clank repo.
  36. INTERNAL_TRANSLATION_EXPECTATIONS_PATH = os.path.join(
  37. 'clank', 'tools', 'translation_expectations.pyl')
  38. # Translation expectations file for the Chromium repo.
  39. TRANSLATION_EXPECTATIONS_PATH = os.path.join('tools', 'gritsettings',
  40. 'translation_expectations.pyl')
  41. # URL of the bucket used for storing screenshots.
  42. # This is writable by @google.com accounts, readable by everyone.
  43. BUCKET_URL = 'gs://chromium-translation-screenshots'
  44. if sys.platform.startswith('win'):
  45. # Use the |git.bat| in the depot_tools/ on Windows.
  46. GIT = 'git.bat'
  47. else:
  48. GIT = 'git'
  49. def query_yes_no(question, default='no'):
  50. """Ask a yes/no question via input() and return their answer.
  51. "question" is a string that is presented to the user.
  52. "default" is the presumed answer if the user just hits <Enter>.
  53. It must be "yes" (the default), "no" or None (meaning
  54. an answer is required of the user).
  55. The "answer" return value is True for "yes" or False for "no".
  56. """
  57. if default is None:
  58. prompt = '[y/n] '
  59. elif default == 'yes':
  60. prompt = '[Y/n] '
  61. elif default == 'no':
  62. prompt = '[y/N] '
  63. else:
  64. raise ValueError("invalid default answer: '%s'" % default)
  65. valid = {'yes': True, 'y': True, 'ye': True, 'no': False, 'n': False}
  66. while True:
  67. print(question, prompt)
  68. choice = input().lower()
  69. if default is not None and choice == '':
  70. return valid[default]
  71. if choice in valid:
  72. return valid[choice]
  73. print("Please respond with 'yes' or 'no' (or 'y' or 'n').")
  74. def find_screenshots(repo_root, translation_expectations):
  75. """Returns a list of translation related .png files in the repository."""
  76. translatable_grds = translation_helper.get_translatable_grds(
  77. repo_root, git_helper.list_grds_in_repository(repo_root),
  78. translation_expectations)
  79. # Add the paths of grds and any files they include. This includes grdp files
  80. # and files included via <structure> elements.
  81. src_paths = []
  82. for grd in translatable_grds:
  83. src_paths.append(grd.path)
  84. src_paths.extend(grd.grdp_paths)
  85. src_paths.extend(grd.structure_paths)
  86. screenshots = []
  87. for grd_path in src_paths:
  88. # Convert grd_path.grd to grd_path_grd/ directory.
  89. name, ext = os.path.splitext(os.path.basename(grd_path))
  90. relative_screenshots_dir = os.path.relpath(
  91. os.path.dirname(grd_path), repo_root)
  92. screenshots_dir = os.path.realpath(
  93. os.path.join(repo_root,
  94. os.path.join(relative_screenshots_dir,
  95. name + ext.replace('.', '_'))))
  96. # Grab all the .png files under the screenshot directory. On a clean
  97. # checkout this should be an empty list, as the repo should only contain
  98. # .sha1 files of previously uploaded screenshots.
  99. if not os.path.exists(screenshots_dir):
  100. continue
  101. for f in os.listdir(screenshots_dir):
  102. if f in ('OWNERS', 'README.md', 'DIR_METADATA') or f.endswith('.sha1'):
  103. continue
  104. if not f.endswith('.png'):
  105. print('File with unexpected extension: %s in %s' % (f, screenshots_dir))
  106. continue
  107. screenshots.append(os.path.join(screenshots_dir, f))
  108. return screenshots
  109. def main():
  110. parser = argparse.ArgumentParser(
  111. description='Upload translation screenshots to Google Cloud Storage')
  112. parser.add_argument(
  113. '-n',
  114. '--dry-run',
  115. action='store_true',
  116. help='Don\'t actually upload the images')
  117. parser.add_argument(
  118. '-c',
  119. '--clank_internal',
  120. action='store_true',
  121. help='Upload screenshots for strings in the downstream clank directory')
  122. args = parser.parse_args()
  123. if args.clank_internal:
  124. screenshots = find_screenshots(
  125. os.path.join(src_path, "clank"),
  126. os.path.join(src_path, INTERNAL_TRANSLATION_EXPECTATIONS_PATH))
  127. else:
  128. screenshots = find_screenshots(
  129. src_path, os.path.join(src_path, TRANSLATION_EXPECTATIONS_PATH))
  130. if not screenshots:
  131. print ("No screenshots found.\n\n"
  132. "- Screenshots must be located in the correct directory.\n"
  133. " E.g. For IDS_HELLO_WORLD message in path/to/file.grd, save the "
  134. "screenshot at path/to/file_grd/IDS_HELLO_WORLD.png.\n"
  135. "- If you added a new, uncommitted .grd file, `git add` it so that "
  136. "this script can pick up its screenshot directory.")
  137. sys.exit(0)
  138. print('Found %d updated screenshot(s): ' % len(screenshots))
  139. for s in screenshots:
  140. print(' %s' % s)
  141. print()
  142. if not query_yes_no('Do you want to upload these to Google Cloud Storage?\n\n'
  143. 'FILES WILL BE VISIBLE TO A LARGE NUMBER OF PEOPLE. '
  144. 'DO NOT UPLOAD ANYTHING CONFIDENTIAL.'):
  145. sys.exit(0)
  146. # Creating a standard gsutil object, assuming there are depot_tools
  147. # and everything related is set up already.
  148. gsutil_path = os.path.abspath(os.path.join(depot_tools_path, 'gsutil.py'))
  149. gsutil = download_from_google_storage.Gsutil(gsutil_path, boto_path=None)
  150. if not args.dry_run:
  151. if upload_to_google_storage.upload_to_google_storage(
  152. input_filenames=screenshots,
  153. base_url=BUCKET_URL,
  154. gsutil=gsutil,
  155. force=False,
  156. use_md5=False,
  157. num_threads=10,
  158. skip_hashing=False,
  159. gzip=None) != 0:
  160. print ('Error uploading screenshots. Try running '
  161. '`download_from_google_storage --config`.')
  162. sys.exit(1)
  163. print()
  164. print('Images are uploaded and their signatures are calculated:')
  165. signatures = ['%s.sha1' % s for s in screenshots]
  166. for s in signatures:
  167. print(' %s' % s)
  168. print()
  169. # Always ask if the .sha1 files should be added to the CL, even if they are
  170. # already part of the CL. If the files are not modified, adding again is a
  171. # no-op.
  172. if not query_yes_no('Do you want to add these files to your CL?',
  173. default='yes'):
  174. sys.exit(0)
  175. if not args.dry_run:
  176. git_helper.git_add(signatures, src_path)
  177. print('DONE.')
  178. if __name__ == '__main__':
  179. main()