1# Copyright (C) 2018 and later: Unicode, Inc. and others.
2# License & terms of use: http://www.unicode.org/copyright.html
3
4# Python 2/3 Compatibility (ICU-20299)
5# TODO(ICU-20301): Remove this.
6from __future__ import print_function
7
8from . import *
9from .. import *
10from .. import utils
11from ..request_types import *
12
13import os
14import shutil
15import subprocess
16import sys
17
18def run(build_dirs, requests, common_vars, verbose=True, **kwargs):
19    for bd in build_dirs:
20        makedirs(bd.format(**common_vars))
21    for request in requests:
22        status = run_helper(request, common_vars, verbose=verbose, **kwargs)
23        if status != 0:
24            print("!!! ERROR executing above command line: exit code %d" % status)
25            return 1
26    if verbose:
27        print("All data build commands executed")
28    return 0
29
30def makedirs(dirs):
31    """makedirs compatible between Python 2 and 3"""
32    try:
33        # Python 3 version
34        os.makedirs(dirs, exist_ok=True)
35    except TypeError as e:
36        # Python 2 version
37        try:
38            os.makedirs(dirs)
39        except OSError as e:
40            if e.errno != errno.EEXIST:
41                raise e
42
43def run_helper(request, common_vars, platform, tool_dir, verbose, tool_cfg=None, **kwargs):
44    if isinstance(request, PrintFileRequest):
45        output_path = "{DIRNAME}/{FILENAME}".format(
46            DIRNAME = utils.dir_for(request.output_file).format(**common_vars),
47            FILENAME = request.output_file.filename,
48        )
49        if verbose:
50            print("Printing to file: %s" % output_path)
51        with open(output_path, "w") as f:
52            f.write(request.content)
53        return 0
54    if isinstance(request, CopyRequest):
55        input_path = "{DIRNAME}/{FILENAME}".format(
56            DIRNAME = utils.dir_for(request.input_file).format(**common_vars),
57            FILENAME = request.input_file.filename,
58        )
59        output_path = "{DIRNAME}/{FILENAME}".format(
60            DIRNAME = utils.dir_for(request.output_file).format(**common_vars),
61            FILENAME = request.output_file.filename,
62        )
63        if verbose:
64            print("Copying file to: %s" % output_path)
65        shutil.copyfile(input_path, output_path)
66        return 0
67    if isinstance(request, VariableRequest):
68        # No-op
69        return 0
70
71    assert isinstance(request.tool, IcuTool)
72    if platform == "windows":
73        cmd_template = "{TOOL_DIR}/{TOOL}/{TOOL_CFG}/{TOOL}.exe {{ARGS}}".format(
74            TOOL_DIR = tool_dir,
75            TOOL_CFG = tool_cfg,
76            TOOL = request.tool.name,
77            **common_vars
78        )
79    elif platform == "unix":
80        cmd_template = "{TOOL_DIR}/{TOOL} {{ARGS}}".format(
81            TOOL_DIR = tool_dir,
82            TOOL = request.tool.name,
83            **common_vars
84        )
85    elif platform == "bazel":
86        cmd_template = "{TOOL_DIR}/{TOOL}/{TOOL} {{ARGS}}".format(
87            TOOL_DIR = tool_dir,
88            TOOL = request.tool.name,
89            **common_vars
90        )
91    else:
92        raise ValueError("Unknown platform: %s" % platform)
93
94    if isinstance(request, RepeatedExecutionRequest):
95        for loop_vars in utils.repeated_execution_request_looper(request):
96            command_line = utils.format_repeated_request_command(
97                request,
98                cmd_template,
99                loop_vars,
100                common_vars
101            )
102            if platform == "windows":
103                # Note: this / to \ substitution may be too aggressive?
104                command_line = command_line.replace("/", "\\")
105            returncode = run_shell_command(command_line, platform, verbose)
106            if returncode != 0:
107                return returncode
108        return 0
109    if isinstance(request, SingleExecutionRequest):
110        command_line = utils.format_single_request_command(
111            request,
112            cmd_template,
113            common_vars
114        )
115        if platform == "windows":
116            # Note: this / to \ substitution may be too aggressive?
117            command_line = command_line.replace("/", "\\")
118        returncode = run_shell_command(command_line, platform, verbose)
119        return returncode
120    assert False
121
122def run_shell_command(command_line, platform, verbose):
123    changed_windows_comspec = False
124    # If the command line length on Windows exceeds the absolute maximum that CMD supports (8191), then
125    # we temporarily switch over to use PowerShell for the command, and then switch back to CMD.
126    # We don't want to use PowerShell for everything though, as it tends to be slower.
127    if (platform == "windows"):
128        previous_comspec = os.environ["COMSPEC"]
129        # Add 7 to the length for the argument /c with quotes.
130        # For example:  C:\WINDOWS\system32\cmd.exe /c "<command_line>"
131        if ((len(previous_comspec) + len(command_line) + 7) > 8190):
132            if verbose:
133                print("Command length exceeds the max length for CMD on Windows, using PowerShell instead.")
134            os.environ["COMSPEC"] = 'powershell'
135            changed_windows_comspec = True
136    if verbose:
137        print("Running: %s" % command_line)
138        returncode = subprocess.call(
139            command_line,
140            shell = True
141        )
142    else:
143        # Pipe output to /dev/null in quiet mode
144        with open(os.devnull, "w") as devnull:
145            returncode = subprocess.call(
146                command_line,
147                shell = True,
148                stdout = devnull,
149                stderr = devnull
150            )
151    if changed_windows_comspec:
152        os.environ["COMSPEC"] = previous_comspec
153    if returncode != 0:
154        print("Command failed: %s" % command_line, file=sys.stderr)
155    return returncode
156