1"""Functions common across configure rules."""
2
3BAZEL_SH = "BAZEL_SH"
4PYTHON_BIN_PATH = "PYTHON_BIN_PATH"
5PYTHON_LIB_PATH = "PYTHON_LIB_PATH"
6TF_PYTHON_CONFIG_REPO = "TF_PYTHON_CONFIG_REPO"
7
8def auto_config_fail(msg):
9    """Output failure message when auto configuration fails."""
10    red = "\033[0;31m"
11    no_color = "\033[0m"
12    fail("%sConfiguration Error:%s %s\n" % (red, no_color, msg))
13
14def which(repository_ctx, program_name):
15    """Returns the full path to a program on the execution platform.
16
17    Args:
18      repository_ctx: the repository_ctx
19      program_name: name of the program on the PATH
20
21    Returns:
22      The full path to a program on the execution platform.
23    """
24    if is_windows(repository_ctx):
25        if not program_name.endswith(".exe"):
26            program_name = program_name + ".exe"
27        return execute(
28            repository_ctx,
29            ["C:\\Windows\\System32\\where.exe", program_name],
30        ).stdout.replace("\\", "\\\\").rstrip()
31
32    return execute(repository_ctx, ["which", program_name]).stdout.rstrip()
33
34def get_python_bin(repository_ctx):
35    """Gets the python bin path.
36
37    Args:
38      repository_ctx: the repository_ctx
39
40    Returns:
41      The python bin path.
42    """
43    python_bin = get_host_environ(repository_ctx, PYTHON_BIN_PATH)
44    if python_bin != None:
45        return python_bin
46
47    # First check for an explicit "python3"
48    python_bin = which(repository_ctx, "python3")
49    if python_bin != None:
50        return python_bin
51
52    # Some systems just call pythone3 "python"
53    python_bin = which(repository_ctx, "python")
54    if python_bin != None:
55        return python_bin
56
57    auto_config_fail("Cannot find python in PATH, please make sure " +
58                     "python is installed and add its directory in PATH, or --define " +
59                     "%s='/something/else'.\nPATH=%s" % (
60                         PYTHON_BIN_PATH,
61                         get_environ("PATH", ""),
62                     ))
63    return python_bin  # unreachable
64
65def get_bash_bin(repository_ctx):
66    """Gets the bash bin path.
67
68    Args:
69      repository_ctx: the repository_ctx
70
71    Returns:
72      The bash bin path.
73    """
74    bash_bin = get_host_environ(repository_ctx, BAZEL_SH)
75    if bash_bin != None:
76        return bash_bin
77    bash_bin_path = which(repository_ctx, "bash")
78    if bash_bin_path == None:
79        auto_config_fail("Cannot find bash in PATH, please make sure " +
80                         "bash is installed and add its directory in PATH, or --define " +
81                         "%s='/path/to/bash'.\nPATH=%s" % (
82                             BAZEL_SH,
83                             get_environ("PATH", ""),
84                         ))
85    return bash_bin_path
86
87def read_dir(repository_ctx, src_dir):
88    """Returns a sorted list with all files in a directory.
89
90    Finds all files inside a directory, traversing subfolders and following
91    symlinks.
92
93    Args:
94      repository_ctx: the repository_ctx
95      src_dir: the directory to traverse
96
97    Returns:
98      A sorted list with all files in a directory.
99    """
100    if is_windows(repository_ctx):
101        src_dir = src_dir.replace("/", "\\")
102        find_result = execute(
103            repository_ctx,
104            ["C:\\Windows\\System32\\cmd.exe", "/c", "dir", src_dir, "/b", "/s", "/a-d"],
105            empty_stdout_fine = True,
106        )
107
108        # src_files will be used in genrule.outs where the paths must
109        # use forward slashes.
110        result = find_result.stdout.replace("\\", "/")
111    else:
112        find_result = execute(
113            repository_ctx,
114            ["find", src_dir, "-follow", "-type", "f"],
115            empty_stdout_fine = True,
116        )
117        result = find_result.stdout
118    return sorted(result.splitlines())
119
120def get_environ(repository_ctx, name, default_value = None):
121    """Returns the value of an environment variable on the execution platform.
122
123    Args:
124      repository_ctx: the repository_ctx
125      name: the name of environment variable
126      default_value: the value to return if not set
127
128    Returns:
129      The value of the environment variable 'name' on the execution platform
130      or 'default_value' if it's not set.
131    """
132    if is_windows(repository_ctx):
133        result = execute(
134            repository_ctx,
135            ["C:\\Windows\\System32\\cmd.exe", "/c", "echo", "%" + name + "%"],
136            empty_stdout_fine = True,
137        )
138    else:
139        cmd = "echo -n \"$%s\"" % name
140        result = execute(
141            repository_ctx,
142            [get_bash_bin(repository_ctx), "-c", cmd],
143            empty_stdout_fine = True,
144        )
145    if len(result.stdout) == 0:
146        return default_value
147    return result.stdout
148
149def get_host_environ(repository_ctx, name, default_value = None):
150    """Returns the value of an environment variable on the host platform.
151
152    The host platform is the machine that Bazel runs on.
153
154    Args:
155      repository_ctx: the repository_ctx
156      name: the name of environment variable
157
158    Returns:
159      The value of the environment variable 'name' on the host platform.
160    """
161    if name in repository_ctx.os.environ:
162        return repository_ctx.os.environ.get(name).strip()
163
164    if hasattr(repository_ctx.attr, "environ") and name in repository_ctx.attr.environ:
165        return repository_ctx.attr.environ.get(name).strip()
166
167    return default_value
168
169def is_windows(repository_ctx):
170    """Returns true if the execution platform is Windows.
171
172    Args:
173      repository_ctx: the repository_ctx
174
175    Returns:
176      If the execution platform is Windows.
177    """
178    os_name = ""
179    if hasattr(repository_ctx.attr, "exec_properties") and "OSFamily" in repository_ctx.attr.exec_properties:
180        os_name = repository_ctx.attr.exec_properties["OSFamily"]
181    else:
182        os_name = repository_ctx.os.name
183
184    return os_name.lower().find("windows") != -1
185
186def get_cpu_value(repository_ctx):
187    """Returns the name of the host operating system.
188
189    Args:
190      repository_ctx: The repository context.
191    Returns:
192      A string containing the name of the host operating system.
193    """
194    if is_windows(repository_ctx):
195        return "Windows"
196    result = raw_exec(repository_ctx, ["uname", "-s"])
197    return result.stdout.strip()
198
199def execute(
200        repository_ctx,
201        cmdline,
202        error_msg = None,
203        error_details = None,
204        empty_stdout_fine = False):
205    """Executes an arbitrary shell command.
206
207    Args:
208      repository_ctx: the repository_ctx object
209      cmdline: list of strings, the command to execute
210      error_msg: string, a summary of the error if the command fails
211      error_details: string, details about the error or steps to fix it
212      empty_stdout_fine: bool, if True, an empty stdout result is fine,
213        otherwise it's an error
214    Returns:
215      The result of repository_ctx.execute(cmdline)
216    """
217    result = raw_exec(repository_ctx, cmdline)
218    if result.stderr or not (empty_stdout_fine or result.stdout):
219        fail(
220            "\n".join([
221                error_msg.strip() if error_msg else "Repository command failed",
222                result.stderr.strip(),
223                error_details if error_details else "",
224            ]),
225        )
226    return result
227
228def raw_exec(repository_ctx, cmdline):
229    """Executes a command via repository_ctx.execute() and returns the result.
230
231    This method is useful for debugging purposes. For example, to print all
232    commands executed as well as their return code.
233
234    Args:
235      repository_ctx: the repository_ctx
236      cmdline: the list of args
237
238    Returns:
239      The 'exec_result' of repository_ctx.execute().
240    """
241    return repository_ctx.execute(cmdline)
242
243def files_exist(repository_ctx, paths, bash_bin = None):
244    """Checks which files in paths exists.
245
246    Args:
247      repository_ctx: the repository_ctx
248      paths: a list of paths
249      bash_bin: path to the bash interpreter
250
251    Returns:
252      Returns a list of Bool. True means that the path at the
253      same position in the paths list exists.
254    """
255    if bash_bin == None:
256        bash_bin = get_bash_bin(repository_ctx)
257
258    cmd_tpl = "[ -e \"%s\" ] && echo True || echo False"
259    cmds = [cmd_tpl % path for path in paths]
260    cmd = " ; ".join(cmds)
261
262    stdout = execute(repository_ctx, [bash_bin, "-c", cmd]).stdout.strip()
263    return [val == "True" for val in stdout.splitlines()]
264
265def realpath(repository_ctx, path, bash_bin = None):
266    """Returns the result of "realpath path".
267
268    Args:
269      repository_ctx: the repository_ctx
270      path: a path on the file system
271      bash_bin: path to the bash interpreter
272
273    Returns:
274      Returns the result of "realpath path"
275    """
276    if bash_bin == None:
277        bash_bin = get_bash_bin(repository_ctx)
278
279    return execute(repository_ctx, [bash_bin, "-c", "realpath \"%s\"" % path]).stdout.strip()
280
281def err_out(result):
282    """Returns stderr if set, else stdout.
283
284    This function is a workaround for a bug in RBE where stderr is returned as stdout. Instead
285    of using result.stderr use err_out(result) instead.
286
287    Args:
288      result: the exec_result.
289
290    Returns:
291      The stderr if set, else stdout
292    """
293    if len(result.stderr) == 0:
294        return result.stdout
295    return result.stderr
296
297def config_repo_label(config_repo, target):
298    """Construct a label from config_repo and target.
299
300    This function exists to ease the migration from preconfig to remote config. In preconfig
301    the TF_*_CONFIG_REPO environ variables are set to packages in the main repo while in
302    remote config they will point to remote repositories.
303
304    Args:
305      config_repo: a remote repository or package.
306      target: a target
307    Returns:
308      A label constructed from config_repo and target.
309    """
310    if config_repo.startswith("@") and not config_repo.find("//") > 0:
311        # remote config is being used.
312        return Label(config_repo + "//" + target)
313    elif target.startswith(":"):
314        return Label(config_repo + target)
315    else:
316        return Label(config_repo + "/" + target)
317