mirror of
https://github.com/darlinghq/darling-icu.git
synced 2024-11-27 05:20:22 +00:00
361 lines
12 KiB
Python
361 lines
12 KiB
Python
# Copyright (C) 2018 and later: Unicode, Inc. and others.
|
|
# License & terms of use: http://www.unicode.org/copyright.html
|
|
|
|
# Python 2/3 Compatibility (ICU-20299)
|
|
# TODO(ICU-20301): Remove this.
|
|
from __future__ import print_function
|
|
|
|
import argparse
|
|
import glob as pyglob
|
|
import io as pyio
|
|
import json
|
|
import os
|
|
import sys
|
|
|
|
from . import *
|
|
from .comment_stripper import CommentStripper
|
|
from .request_types import CopyRequest
|
|
from .renderers import makefile, common_exec
|
|
from . import filtration, utils
|
|
|
|
flag_parser = argparse.ArgumentParser(
|
|
description = """Generates rules for building ICU binary data files from text
|
|
and other input files in source control.
|
|
|
|
Use the --mode option to declare how to execute those rules, either exporting
|
|
the rules to a Makefile or spawning child processes to run them immediately:
|
|
|
|
--mode=gnumake prints a Makefile to standard out.
|
|
--mode=unix-exec spawns child processes in a Unix-like environment.
|
|
--mode=windows-exec spawns child processes in a Windows-like environment.
|
|
|
|
Tips for --mode=unix-exec
|
|
=========================
|
|
|
|
Create two empty directories for out_dir and tmp_dir. They will get filled
|
|
with a lot of intermediate files.
|
|
|
|
Set LD_LIBRARY_PATH to include the lib directory. e.g., from icu4c/source:
|
|
|
|
$ LD_LIBRARY_PATH=lib PYTHONPATH=python python3 -m icutools.databuilder ...
|
|
|
|
Once icutools.databuilder finishes, you have compiled the data, but you have
|
|
not packaged it into a .dat or .so file. This is done by the separate pkgdata
|
|
tool in bin. Read the docs of pkgdata:
|
|
|
|
$ LD_LIBRARY_PATH=lib ./bin/pkgdata --help
|
|
|
|
Example command line to call pkgdata:
|
|
|
|
$ LD_LIBRARY_PATH=lib ./bin/pkgdata -m common -p icudt63l -c \\
|
|
-O data/icupkg.inc -s $OUTDIR -d $TMPDIR $TMPDIR/icudata.lst
|
|
|
|
where $OUTDIR and $TMPDIR are your out and tmp directories, respectively.
|
|
The above command will create icudt63l.dat in the tmpdir.
|
|
|
|
Command-Line Arguments
|
|
======================
|
|
""",
|
|
formatter_class = argparse.RawDescriptionHelpFormatter
|
|
)
|
|
|
|
arg_group_required = flag_parser.add_argument_group("required arguments")
|
|
arg_group_required.add_argument(
|
|
"--mode",
|
|
help = "What to do with the generated rules.",
|
|
choices = ["gnumake", "unix-exec", "windows-exec", "bazel-exec"],
|
|
required = True
|
|
)
|
|
|
|
flag_parser.add_argument(
|
|
"--src_dir",
|
|
help = "Path to data source folder (icu4c/source/data).",
|
|
default = "."
|
|
)
|
|
flag_parser.add_argument(
|
|
"--filter_file",
|
|
metavar = "PATH",
|
|
help = "Path to an ICU data filter JSON file.",
|
|
default = None
|
|
)
|
|
flag_parser.add_argument(
|
|
"--include_uni_core_data",
|
|
help = "Include the full Unicode core data in the dat file.",
|
|
default = False,
|
|
action = "store_true"
|
|
)
|
|
flag_parser.add_argument(
|
|
"--seqmode",
|
|
help = "Whether to optimize rules to be run sequentially (fewer threads) or in parallel (many threads). Defaults to 'sequential', which is better for unix-exec and windows-exec modes. 'parallel' is often better for massively parallel build systems.",
|
|
choices = ["sequential", "parallel"],
|
|
default = "sequential"
|
|
)
|
|
flag_parser.add_argument(
|
|
"--verbose",
|
|
help = "Print more verbose output (default false).",
|
|
default = False,
|
|
action = "store_true"
|
|
)
|
|
|
|
arg_group_exec = flag_parser.add_argument_group("arguments for unix-exec and windows-exec modes")
|
|
arg_group_exec.add_argument(
|
|
"--out_dir",
|
|
help = "Path to where to save output data files.",
|
|
default = "icudata"
|
|
)
|
|
arg_group_exec.add_argument(
|
|
"--tmp_dir",
|
|
help = "Path to where to save temporary files.",
|
|
default = "icutmp"
|
|
)
|
|
arg_group_exec.add_argument(
|
|
"--tool_dir",
|
|
help = "Path to where to find binary tools (genrb, etc).",
|
|
default = "../bin"
|
|
)
|
|
arg_group_exec.add_argument(
|
|
"--tool_cfg",
|
|
help = "The build configuration of the tools. Used in 'windows-exec' mode only.",
|
|
default = "x86/Debug"
|
|
)
|
|
|
|
|
|
class Config(object):
|
|
|
|
def __init__(self, args):
|
|
# Process arguments
|
|
self.max_parallel = (args.seqmode == "parallel")
|
|
|
|
# Boolean: Whether to include core Unicode data files in the .dat file
|
|
self.include_uni_core_data = args.include_uni_core_data
|
|
|
|
# Default fields before processing filter file
|
|
self.filters_json_data = {}
|
|
self.filter_dir = "ERROR_NO_FILTER_FILE"
|
|
|
|
# Process filter file
|
|
if args.filter_file:
|
|
try:
|
|
with open(args.filter_file, "r") as f:
|
|
print("Note: Applying filters from %s." % args.filter_file, file=sys.stderr)
|
|
self._parse_filter_file(f)
|
|
except IOError:
|
|
print("Error: Could not read filter file %s." % args.filter_file, file=sys.stderr)
|
|
exit(1)
|
|
self.filter_dir = os.path.abspath(os.path.dirname(args.filter_file))
|
|
|
|
# Either "unihan" or "implicithan"
|
|
self.coll_han_type = "unihan"
|
|
if "collationUCAData" in self.filters_json_data:
|
|
self.coll_han_type = self.filters_json_data["collationUCAData"]
|
|
|
|
# Either "additive" or "subtractive"
|
|
self.strategy = "subtractive"
|
|
if "strategy" in self.filters_json_data:
|
|
self.strategy = self.filters_json_data["strategy"]
|
|
|
|
# True or False (could be extended later to support enum/list)
|
|
self.use_pool_bundle = True
|
|
if "usePoolBundle" in self.filters_json_data:
|
|
self.use_pool_bundle = self.filters_json_data["usePoolBundle"]
|
|
|
|
def _parse_filter_file(self, f):
|
|
# Use the Hjson parser if it is available; otherwise, use vanilla JSON.
|
|
try:
|
|
import hjson
|
|
self.filters_json_data = hjson.load(f)
|
|
except ImportError:
|
|
self.filters_json_data = json.load(CommentStripper(f))
|
|
|
|
# Optionally pre-validate the JSON schema before further processing.
|
|
# Some schema errors will be caught later, but this step ensures
|
|
# maximal validity.
|
|
try:
|
|
import jsonschema
|
|
schema_path = os.path.join(os.path.dirname(__file__), "filtration_schema.json")
|
|
with open(schema_path) as schema_f:
|
|
schema = json.load(CommentStripper(schema_f))
|
|
validator = jsonschema.Draft4Validator(schema)
|
|
for error in validator.iter_errors(self.filters_json_data, schema):
|
|
print("WARNING: ICU data filter JSON file:", error.message,
|
|
"at", "".join(
|
|
"[%d]" % part if isinstance(part, int) else ".%s" % part
|
|
for part in error.absolute_path
|
|
),
|
|
file=sys.stderr)
|
|
except ImportError:
|
|
print("Tip: to validate your filter file, install the Pip package 'jsonschema'", file=sys.stderr)
|
|
pass
|
|
|
|
|
|
def add_copy_input_requests(requests, config, common_vars):
|
|
files_to_copy = set()
|
|
for request in requests:
|
|
request_files = request.all_input_files()
|
|
# Also add known dependency txt files as possible inputs.
|
|
# This is required for translit rule files.
|
|
if hasattr(request, "dep_targets"):
|
|
request_files += [
|
|
f for f in request.dep_targets if isinstance(f, InFile)
|
|
]
|
|
for f in request_files:
|
|
if isinstance(f, InFile):
|
|
files_to_copy.add(f)
|
|
|
|
result = []
|
|
id = 0
|
|
|
|
json_data = config.filters_json_data["fileReplacements"]
|
|
dirname = json_data["directory"]
|
|
for directive in json_data["replacements"]:
|
|
if type(directive) == str:
|
|
input_file = LocalFile(dirname, directive)
|
|
output_file = InFile(directive)
|
|
else:
|
|
input_file = LocalFile(dirname, directive["src"])
|
|
output_file = InFile(directive["dest"])
|
|
result += [
|
|
CopyRequest(
|
|
name = "input_copy_%d" % id,
|
|
input_file = input_file,
|
|
output_file = output_file
|
|
)
|
|
]
|
|
files_to_copy.remove(output_file)
|
|
id += 1
|
|
|
|
for f in files_to_copy:
|
|
result += [
|
|
CopyRequest(
|
|
name = "input_copy_%d" % id,
|
|
input_file = SrcFile(f.filename),
|
|
output_file = f
|
|
)
|
|
]
|
|
id += 1
|
|
|
|
result += requests
|
|
return result
|
|
|
|
|
|
class IO(object):
|
|
"""I/O operations required when computing the build actions"""
|
|
|
|
def __init__(self, src_dir):
|
|
self.src_dir = src_dir
|
|
|
|
def glob(self, pattern):
|
|
absolute_paths = pyglob.glob(os.path.join(self.src_dir, pattern))
|
|
# Strip off the absolute path suffix so we are left with a relative path.
|
|
relative_paths = [v[len(self.src_dir)+1:] for v in sorted(absolute_paths)]
|
|
# For the purposes of icutools.databuilder, force Unix-style directory separators.
|
|
# Within the Python code, including BUILDRULES.py and user-provided config files,
|
|
# directory separators are normalized to '/', including on Windows platforms.
|
|
return [v.replace("\\", "/") for v in relative_paths]
|
|
|
|
def read_locale_deps(self, tree):
|
|
return self._read_json("%s/LOCALE_DEPS.json" % tree)
|
|
|
|
def _read_json(self, filename):
|
|
with pyio.open(os.path.join(self.src_dir, filename), "r", encoding="utf-8-sig") as f:
|
|
return json.load(CommentStripper(f))
|
|
|
|
|
|
def main(argv):
|
|
args = flag_parser.parse_args(argv)
|
|
config = Config(args)
|
|
|
|
if args.mode == "gnumake":
|
|
makefile_vars = {
|
|
"SRC_DIR": "$(srcdir)",
|
|
"IN_DIR": "$(srcdir)",
|
|
"INDEX_NAME": "res_index"
|
|
}
|
|
makefile_env = ["ICUDATA_CHAR", "OUT_DIR", "TMP_DIR"]
|
|
common = {
|
|
key: "$(%s)" % key
|
|
for key in list(makefile_vars.keys()) + makefile_env
|
|
}
|
|
common["FILTERS_DIR"] = config.filter_dir
|
|
common["CWD_DIR"] = os.getcwd()
|
|
else:
|
|
makefile_vars = None
|
|
common = {
|
|
"SRC_DIR": args.src_dir,
|
|
"IN_DIR": args.src_dir,
|
|
"OUT_DIR": args.out_dir,
|
|
"TMP_DIR": args.tmp_dir,
|
|
"FILTERS_DIR": config.filter_dir,
|
|
"CWD_DIR": os.getcwd(),
|
|
"INDEX_NAME": "res_index",
|
|
# TODO: Pull this from configure script:
|
|
"ICUDATA_CHAR": "l"
|
|
}
|
|
|
|
# Automatically load BUILDRULES from the src_dir
|
|
sys.path.append(args.src_dir)
|
|
try:
|
|
import BUILDRULES
|
|
except ImportError:
|
|
print("Cannot find BUILDRULES! Did you set your --src_dir?", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
io = IO(args.src_dir)
|
|
requests = BUILDRULES.generate(config, io, common)
|
|
|
|
if "fileReplacements" in config.filters_json_data:
|
|
tmp_in_dir = "{TMP_DIR}/in".format(**common)
|
|
if makefile_vars:
|
|
makefile_vars["IN_DIR"] = tmp_in_dir
|
|
else:
|
|
common["IN_DIR"] = tmp_in_dir
|
|
requests = add_copy_input_requests(requests, config, common)
|
|
|
|
requests = filtration.apply_filters(requests, config, io)
|
|
requests = utils.flatten_requests(requests, config, common)
|
|
|
|
build_dirs = utils.compute_directories(requests)
|
|
|
|
if args.mode == "gnumake":
|
|
print(makefile.get_gnumake_rules(
|
|
build_dirs,
|
|
requests,
|
|
makefile_vars,
|
|
common_vars = common
|
|
))
|
|
elif args.mode == "windows-exec":
|
|
return common_exec.run(
|
|
platform = "windows",
|
|
build_dirs = build_dirs,
|
|
requests = requests,
|
|
common_vars = common,
|
|
tool_dir = args.tool_dir,
|
|
tool_cfg = args.tool_cfg,
|
|
verbose = args.verbose,
|
|
)
|
|
elif args.mode == "unix-exec":
|
|
return common_exec.run(
|
|
platform = "unix",
|
|
build_dirs = build_dirs,
|
|
requests = requests,
|
|
common_vars = common,
|
|
tool_dir = args.tool_dir,
|
|
verbose = args.verbose,
|
|
)
|
|
elif args.mode == "bazel-exec":
|
|
return common_exec.run(
|
|
platform = "bazel",
|
|
build_dirs = build_dirs,
|
|
requests = requests,
|
|
common_vars = common,
|
|
tool_dir = args.tool_dir,
|
|
verbose = args.verbose,
|
|
)
|
|
else:
|
|
print("Mode not supported: %s" % args.mode)
|
|
return 1
|
|
return 0
|
|
|
|
if __name__ == "__main__":
|
|
exit(main(sys.argv[1:]))
|