configure.py 9.3 KB

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