configure_icu.py 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. #!/usr/bin/python
  2. from __future__ import print_function
  3. import hashlib
  4. import os
  5. import shutil
  6. import sys
  7. import tarfile
  8. import urllib
  9. import urllib2
  10. from zipfile import ZipFile
  11. from argparse import ArgumentParser
  12. # Parse Makefile.in to find the OBJECTS = ... list of object files
  13. # This is the officially recommended way of integrating ICU into a large project's build system
  14. def get_sources(icuroot, mkin_path):
  15. # ignore these files, similar to Node
  16. ignore = [
  17. "source/tools/toolutil/udbgutil.cpp",
  18. "source/tools/toolutil/dbgutil.cpp",
  19. ]
  20. ignore = [os.path.join(icuroot, os.path.normpath(source)) for source in ignore]
  21. def get_source(object_path):
  22. base = os.path.splitext(object_path)[0]
  23. cpp = base + ".cpp"
  24. c = base + ".c"
  25. # return None if we find a source but explicitly exclude it, compared
  26. # to raising an exception if a source is referenced that doesn't exist,
  27. # since that is more likely to be an issue with the source/ folder
  28. if cpp in ignore or c in ignore:
  29. return None
  30. if os.path.isfile(cpp):
  31. return cpp
  32. elif os.path.isfile(c):
  33. return c
  34. raise Exception("%s has no corresponding source file" % object_path)
  35. with open(mkin_path, "r") as mkin_contents:
  36. in_objs = False
  37. sources = []
  38. for line in mkin_contents:
  39. line = line.strip()
  40. if line[:7] == "OBJECTS":
  41. in_objs = True
  42. # trim " = " in "OBJECTS = {object files}"
  43. line = line[10:]
  44. elif in_objs == True and len(line) == 0:
  45. # done with OBJECTS, return
  46. return sources
  47. if in_objs == True:
  48. # trim " \" in "{object files} \"
  49. linelen = len(line)
  50. line = line[:linelen - 1].strip() if line[linelen - 1] == "\\" else line
  51. objects = map(lambda o: os.path.join(os.path.dirname(mkin_path), o), line.split())
  52. cpps = map(get_source, objects)
  53. cpps = filter(lambda cpp: cpp != None, cpps)
  54. sources.extend(cpps)
  55. # should have returned by now
  56. raise Exception("Could not extract sources from %s" % mkin_path)
  57. def get_headers(icuroot, headers_path):
  58. # ignore these files, similar to Node
  59. ignore = [
  60. "source/tools/toolutil/udbgutil.h",
  61. "source/tools/toolutil/dbgutil.h",
  62. ]
  63. ignore = [os.path.join(icuroot, os.path.normpath(source)) for source in ignore]
  64. if not os.path.isdir(headers_path):
  65. raise Exception("%s is not a valid headers path" % headers_path)
  66. headers = map(lambda h: os.path.join(headers_path, h), os.listdir(headers_path))
  67. headers = filter(lambda h: os.path.splitext(h)[1] == ".h", headers) # only include .h files
  68. headers = filter(lambda h: h not in ignore, headers) # don't include ignored headers
  69. return headers
  70. def create_msvc_props(chakra_icu_root, icu_sources_root, version):
  71. prelude = """<?xml version="1.0" encoding="utf-8"?>
  72. <!-- DO NOT EDIT THIS FILE. It is auto-generated by $ChakraCore/tools/%s -->
  73. <Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  74. <PropertyGroup>""" % os.path.basename(__file__)
  75. prop_template = """
  76. <Icu{0}{1}>
  77. {2}
  78. </Icu{0}{1}>"""
  79. conclusion = """
  80. </PropertyGroup>
  81. </Project>
  82. """
  83. joiner = ";\n "
  84. def add_props(propfile, deproot, dep):
  85. sources = get_sources(icu_sources_root, os.path.join(deproot, dep, "Makefile.in"))
  86. sources_prop = prop_template.format(dep.capitalize(), "Sources", joiner.join(sources))
  87. propfile.write(sources_prop)
  88. headers = get_headers(icu_sources_root, os.path.join(deproot, dep))
  89. headers_prop = prop_template.format(dep.capitalize(), "Headers", joiner.join(headers))
  90. propfile.write(headers_prop)
  91. with open(os.path.join(chakra_icu_root, "Chakra.ICU.props"), "w") as propfile:
  92. propfile.write(prelude)
  93. # Write all of the sources and header files to Icu${Dep}${DepKind}, such as IcuCommonSources
  94. sourceroot = os.path.join(icu_sources_root, "source")
  95. for dep in ["common", "i18n", "stubdata"]:
  96. add_props(propfile, sourceroot, dep)
  97. # tools are handled somewhat differently, since theyre in source/tools
  98. toolsroot = os.path.join(sourceroot, "tools")
  99. for dep in ["toolutil", "genccode"]:
  100. add_props(propfile, toolsroot, dep)
  101. version_parts = version.split(".")
  102. no_newline_prop_template = "\n <Icu{0}>{1}</Icu{0}>"
  103. propfile.write(no_newline_prop_template.format("VersionMajor", version_parts[0]))
  104. propfile.write(no_newline_prop_template.format("VersionMinor", version_parts[1] if len(version_parts) > 1 else "0"))
  105. propfile.write(no_newline_prop_template.format("SourceDirectory", sourceroot))
  106. include_dirs = [os.path.join(sourceroot, component) for component in ["common", "i18n"]]
  107. propfile.write(prop_template.format("Include", "Directories", joiner.join(include_dirs)))
  108. propfile.write(conclusion)
  109. def download_icu(icuroot, version, yes):
  110. # download either the zip or tar, depending on the os
  111. extension = "zip" if os.name == "nt" else "tgz"
  112. archive_file = "icu4c-{0}-src.{1}".format(version.replace(".", "_"), extension)
  113. md5_file = "icu4c-src-{0}.md5".format(version.replace(".", "_"))
  114. archive_url = "http://download.icu-project.org/files/icu4c/{0}/{1}".format(version, archive_file)
  115. md5_url = "https://ssl.icu-project.org/files/icu4c/{0}/{1}".format(version, md5_file)
  116. license_confirmation = """
  117. {1}
  118. This script downloads ICU from {0}.
  119. It is licensed to you by its publisher, not Microsoft.
  120. Microsoft is not responsible for the software.
  121. Your installation and use of ICU is subject to the publisher's terms,
  122. which are available here: http://www.unicode.org/copyright.html#License
  123. {1}
  124. """.format(archive_url, "-" * 80)
  125. if not yes:
  126. print(license_confirmation)
  127. response = raw_input("Do you agree to these terms? [Y/n] ")
  128. if response != "" and response != "y" and response != "Y":
  129. sys.exit(0)
  130. print("Downloading ICU from %s" % archive_url)
  131. archive_path = urllib.urlretrieve(archive_url)[0]
  132. print("Downloaded ICU to %s" % archive_path)
  133. # check the hash of the download zipfile/tarball
  134. checksum = ""
  135. with open(archive_path, "rb") as download:
  136. md5 = hashlib.md5()
  137. md5.update(download.read())
  138. checksum = md5.hexdigest()
  139. md5_path = os.path.join(icuroot, md5_file)
  140. md5_request = urllib2.urlopen(md5_url)
  141. md5s = md5_request.read().decode("ascii").split("\n")
  142. relevant_md5 = filter(lambda line: line[len(line) - len(archive_file):] == archive_file, md5s)
  143. if len(relevant_md5) != 1:
  144. raise Exception("Could not find md5 hash for %s in %s" % archive_file, md5_url)
  145. correct_hash = relevant_md5[0]
  146. correct_hash = correct_hash.split(" ")[0]
  147. if (correct_hash == checksum):
  148. print("MD5 checksums match, continuing")
  149. return archive_path
  150. else:
  151. raise Exception("MD5 checksums do not match. Expected %s, got %s" % correct_hash, checksum)
  152. def extract_icu(icuroot, archive_path):
  153. tempdir = os.path.normpath(os.path.join(icuroot, "temp"))
  154. print("Extracting ICU to %s" % tempdir)
  155. opener = ZipFile if os.name == "nt" else tarfile.open
  156. with opener(archive_path, "r") as archive:
  157. archive.extractall(tempdir)
  158. icu_folder = os.path.join(icuroot, "icu")
  159. if os.path.isdir(icu_folder):
  160. shutil.rmtree(icu_folder)
  161. shutil.move(os.path.join(tempdir, "icu"), icu_folder)
  162. shutil.rmtree(tempdir)
  163. def main():
  164. chakra_icu_root = os.path.normpath(os.path.join(os.path.realpath(__file__), "..", "..", "deps", "Chakra.ICU"))
  165. argparser = ArgumentParser(description = "Download and set up ICU for use in ChakraCore")
  166. argparser.add_argument("-y", "--yes", action = "store_true", help = "Skip ICU License prompt text")
  167. argparser.add_argument("version", help = "ICU version to download. Not compatible with --archive")
  168. argparser.add_argument("-i", "--icu-root",
  169. help = "Path to directory to extract ICU to. Resulting directory will contain a single subfolder, 'icu', which contains ICU's source tree",
  170. default = chakra_icu_root
  171. )
  172. argparser.add_argument("-a", "--archive", help = "Path to icu.zip (Windows) or icu.tar (POSIX) that you have already downloaded")
  173. args = argparser.parse_args()
  174. archive_path = args.archive
  175. if args.version is not None and args.archive is None:
  176. archive_path = download_icu(args.icu_root, args.version, args.yes)
  177. extract_icu(args.icu_root, archive_path)
  178. if os.name == "nt":
  179. create_msvc_props(chakra_icu_root, os.path.join(args.icu_root, "icu"), args.version)
  180. if __name__ == "__main__":
  181. main()