Yun Peng | 65cda4f | 2017-06-22 11:06:11 +0200 | [diff] [blame] | 1 | # pylint: disable=g-bad-file-header |
| 2 | # Copyright 2016 The Bazel Authors. All rights reserved. |
| 3 | # |
| 4 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | # you may not use this file except in compliance with the License. |
| 6 | # You may obtain a copy of the License at |
| 7 | # |
| 8 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | # |
| 10 | # Unless required by applicable law or agreed to in writing, software |
| 11 | # distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | # See the License for the specific language governing permissions and |
| 14 | # limitations under the License. |
| 15 | """Base library for configuring the C++ toolchain.""" |
| 16 | |
jmmv | 5b02559 | 2018-05-29 12:03:21 -0700 | [diff] [blame] | 17 | def resolve_labels(repository_ctx, labels): |
vladmos | 20a042f | 2018-06-01 04:51:21 -0700 | [diff] [blame] | 18 | """Resolves a collection of labels to their paths. |
jmmv | 5b02559 | 2018-05-29 12:03:21 -0700 | [diff] [blame] | 19 | |
gregce | 587c736 | 2020-04-15 10:36:23 -0700 | [diff] [blame] | 20 | Label resolution can cause the evaluation of Starlark functions to restart. |
vladmos | 20a042f | 2018-06-01 04:51:21 -0700 | [diff] [blame] | 21 | For functions with side-effects (like the auto-configuration functions, which |
| 22 | inspect the system and touch the file system), such restarts are costly. |
| 23 | We cannot avoid the restarts, but we can minimize their penalty by resolving |
| 24 | all labels upfront. |
jmmv | 5b02559 | 2018-05-29 12:03:21 -0700 | [diff] [blame] | 25 | |
vladmos | 20a042f | 2018-06-01 04:51:21 -0700 | [diff] [blame] | 26 | Among other things, doing less work on restarts can cut analysis times by |
| 27 | several seconds and may also prevent tickling kernel conditions that cause |
| 28 | build failures. See https://github.com/bazelbuild/bazel/issues/5196 for |
| 29 | more details. |
jmmv | 5b02559 | 2018-05-29 12:03:21 -0700 | [diff] [blame] | 30 | |
vladmos | 20a042f | 2018-06-01 04:51:21 -0700 | [diff] [blame] | 31 | Args: |
| 32 | repository_ctx: The context with which to resolve the labels. |
| 33 | labels: Labels to be resolved expressed as a list of strings. |
jmmv | 5b02559 | 2018-05-29 12:03:21 -0700 | [diff] [blame] | 34 | |
vladmos | 20a042f | 2018-06-01 04:51:21 -0700 | [diff] [blame] | 35 | Returns: |
| 36 | A dictionary with the labels as keys and their paths as values. |
| 37 | """ |
| 38 | return dict([(label, repository_ctx.path(Label(label))) for label in labels]) |
jmmv | 5b02559 | 2018-05-29 12:03:21 -0700 | [diff] [blame] | 39 | |
Yun Peng | 65cda4f | 2017-06-22 11:06:11 +0200 | [diff] [blame] | 40 | def escape_string(arg): |
vladmos | 20a042f | 2018-06-01 04:51:21 -0700 | [diff] [blame] | 41 | """Escape percent sign (%) in the string so it can appear in the Crosstool.""" |
| 42 | if arg != None: |
| 43 | return str(arg).replace("%", "%%") |
| 44 | else: |
| 45 | return None |
jmmv | 5b02559 | 2018-05-29 12:03:21 -0700 | [diff] [blame] | 46 | |
hlopko | db80298 | 2018-02-27 06:09:18 -0800 | [diff] [blame] | 47 | def split_escaped(string, delimiter): |
vladmos | 20a042f | 2018-06-01 04:51:21 -0700 | [diff] [blame] | 48 | """Split string on the delimiter unless %-escaped. |
hlopko | db80298 | 2018-02-27 06:09:18 -0800 | [diff] [blame] | 49 | |
vladmos | 20a042f | 2018-06-01 04:51:21 -0700 | [diff] [blame] | 50 | Examples: |
| 51 | Basic usage: |
| 52 | split_escaped("a:b:c", ":") -> [ "a", "b", "c" ] |
hlopko | db80298 | 2018-02-27 06:09:18 -0800 | [diff] [blame] | 53 | |
vladmos | 20a042f | 2018-06-01 04:51:21 -0700 | [diff] [blame] | 54 | Delimeter that is not supposed to be splitten on has to be %-escaped: |
| 55 | split_escaped("a%:b", ":") -> [ "a:b" ] |
hlopko | db80298 | 2018-02-27 06:09:18 -0800 | [diff] [blame] | 56 | |
vladmos | 20a042f | 2018-06-01 04:51:21 -0700 | [diff] [blame] | 57 | Literal % can be represented by escaping it as %%: |
| 58 | split_escaped("a%%b", ":") -> [ "a%b" ] |
hlopko | db80298 | 2018-02-27 06:09:18 -0800 | [diff] [blame] | 59 | |
vladmos | 20a042f | 2018-06-01 04:51:21 -0700 | [diff] [blame] | 60 | Consecutive delimiters produce empty strings: |
| 61 | split_escaped("a::b", ":") -> [ "a", "", "", "b" ] |
hlopko | db80298 | 2018-02-27 06:09:18 -0800 | [diff] [blame] | 62 | |
vladmos | 20a042f | 2018-06-01 04:51:21 -0700 | [diff] [blame] | 63 | Args: |
| 64 | string: The string to be split. |
| 65 | delimiter: Non-empty string not containing %-sign to be used as a |
| 66 | delimiter. |
hlopko | db80298 | 2018-02-27 06:09:18 -0800 | [diff] [blame] | 67 | |
vladmos | 20a042f | 2018-06-01 04:51:21 -0700 | [diff] [blame] | 68 | Returns: |
| 69 | A list of substrings. |
| 70 | """ |
| 71 | if delimiter == "": |
| 72 | fail("Delimiter cannot be empty") |
| 73 | if delimiter.find("%") != -1: |
| 74 | fail("Delimiter cannot contain %-sign") |
hlopko | db80298 | 2018-02-27 06:09:18 -0800 | [diff] [blame] | 75 | |
vladmos | 20a042f | 2018-06-01 04:51:21 -0700 | [diff] [blame] | 76 | i = 0 |
| 77 | result = [] |
| 78 | accumulator = [] |
| 79 | length = len(string) |
| 80 | delimiter_length = len(delimiter) |
hlopko | db80298 | 2018-02-27 06:09:18 -0800 | [diff] [blame] | 81 | |
Marcel Hlopko | 7ebff4e | 2019-06-18 11:36:13 -0700 | [diff] [blame] | 82 | if not string: |
| 83 | return [] |
| 84 | |
gregce | 587c736 | 2020-04-15 10:36:23 -0700 | [diff] [blame] | 85 | # Iterate over the length of string since Starlark doesn't have while loops |
vladmos | 20a042f | 2018-06-01 04:51:21 -0700 | [diff] [blame] | 86 | for _ in range(length): |
| 87 | if i >= length: |
| 88 | break |
| 89 | if i + 2 <= length and string[i:i + 2] == "%%": |
| 90 | accumulator.append("%") |
| 91 | i += 2 |
| 92 | elif (i + 1 + delimiter_length <= length and |
| 93 | string[i:i + 1 + delimiter_length] == "%" + delimiter): |
| 94 | accumulator.append(delimiter) |
| 95 | i += 1 + delimiter_length |
| 96 | elif i + delimiter_length <= length and string[i:i + delimiter_length] == delimiter: |
| 97 | result.append("".join(accumulator)) |
| 98 | accumulator = [] |
| 99 | i += delimiter_length |
| 100 | else: |
| 101 | accumulator.append(string[i]) |
| 102 | i += 1 |
hlopko | db80298 | 2018-02-27 06:09:18 -0800 | [diff] [blame] | 103 | |
vladmos | 20a042f | 2018-06-01 04:51:21 -0700 | [diff] [blame] | 104 | # Append the last group still in accumulator |
| 105 | result.append("".join(accumulator)) |
| 106 | return result |
Yun Peng | 65cda4f | 2017-06-22 11:06:11 +0200 | [diff] [blame] | 107 | |
| 108 | def auto_configure_fail(msg): |
vladmos | 20a042f | 2018-06-01 04:51:21 -0700 | [diff] [blame] | 109 | """Output failure message when auto configuration fails.""" |
| 110 | red = "\033[0;31m" |
| 111 | no_color = "\033[0m" |
| 112 | fail("\n%sAuto-Configuration Error:%s %s\n" % (red, no_color, msg)) |
Yun Peng | 65cda4f | 2017-06-22 11:06:11 +0200 | [diff] [blame] | 113 | |
| 114 | def auto_configure_warning(msg): |
vladmos | 20a042f | 2018-06-01 04:51:21 -0700 | [diff] [blame] | 115 | """Output warning message during auto configuration.""" |
| 116 | yellow = "\033[1;33m" |
| 117 | no_color = "\033[0m" |
| 118 | print("\n%sAuto-Configuration Warning:%s %s\n" % (yellow, no_color, msg)) |
Yun Peng | 65cda4f | 2017-06-22 11:06:11 +0200 | [diff] [blame] | 119 | |
| 120 | def get_env_var(repository_ctx, name, default = None, enable_warning = True): |
vladmos | 20a042f | 2018-06-01 04:51:21 -0700 | [diff] [blame] | 121 | """Find an environment variable in system path. Doesn't %-escape the value!""" |
| 122 | if name in repository_ctx.os.environ: |
| 123 | return repository_ctx.os.environ[name] |
| 124 | if default != None: |
| 125 | if enable_warning: |
| 126 | auto_configure_warning("'%s' environment variable is not set, using '%s' as default" % (name, default)) |
| 127 | return default |
| 128 | auto_configure_fail("'%s' environment variable is not set" % name) |
Yun Peng | 65cda4f | 2017-06-22 11:06:11 +0200 | [diff] [blame] | 129 | |
| 130 | def which(repository_ctx, cmd, default = None): |
vladmos | 20a042f | 2018-06-01 04:51:21 -0700 | [diff] [blame] | 131 | """A wrapper around repository_ctx.which() to provide a fallback value. Doesn't %-escape the value!""" |
| 132 | result = repository_ctx.which(cmd) |
| 133 | return default if result == None else str(result) |
Yun Peng | 65cda4f | 2017-06-22 11:06:11 +0200 | [diff] [blame] | 134 | |
| 135 | def which_cmd(repository_ctx, cmd, default = None): |
vladmos | 20a042f | 2018-06-01 04:51:21 -0700 | [diff] [blame] | 136 | """Find cmd in PATH using repository_ctx.which() and fail if cannot find it. Doesn't %-escape the cmd!""" |
| 137 | result = repository_ctx.which(cmd) |
| 138 | if result != None: |
| 139 | return str(result) |
| 140 | path = get_env_var(repository_ctx, "PATH") |
| 141 | if default != None: |
| 142 | auto_configure_warning("Cannot find %s in PATH, using '%s' as default.\nPATH=%s" % (cmd, default, path)) |
| 143 | return default |
| 144 | auto_configure_fail("Cannot find %s in PATH, please make sure %s is installed and add its directory in PATH.\nPATH=%s" % (cmd, cmd, path)) |
Yun Peng | 65cda4f | 2017-06-22 11:06:11 +0200 | [diff] [blame] | 145 | return str(result) |
Yun Peng | 65cda4f | 2017-06-22 11:06:11 +0200 | [diff] [blame] | 146 | |
vladmos | 20a042f | 2018-06-01 04:51:21 -0700 | [diff] [blame] | 147 | def execute( |
| 148 | repository_ctx, |
| 149 | command, |
| 150 | environment = None, |
| 151 | expect_failure = False): |
| 152 | """Execute a command, return stdout if succeed and throw an error if it fails. Doesn't %-escape the result!""" |
| 153 | if environment: |
| 154 | result = repository_ctx.execute(command, environment = environment) |
Yun Peng | 65cda4f | 2017-06-22 11:06:11 +0200 | [diff] [blame] | 155 | else: |
vladmos | 20a042f | 2018-06-01 04:51:21 -0700 | [diff] [blame] | 156 | result = repository_ctx.execute(command) |
| 157 | if expect_failure != (result.return_code != 0): |
| 158 | if expect_failure: |
| 159 | auto_configure_fail( |
| 160 | "expected failure, command %s, stderr: (%s)" % ( |
| 161 | command, |
| 162 | result.stderr, |
| 163 | ), |
| 164 | ) |
| 165 | else: |
| 166 | auto_configure_fail( |
| 167 | "non-zero exit code: %d, command %s, stderr: (%s)" % ( |
| 168 | result.return_code, |
| 169 | command, |
| 170 | result.stderr, |
| 171 | ), |
| 172 | ) |
| 173 | stripped_stdout = result.stdout.strip() |
| 174 | if not stripped_stdout: |
| 175 | auto_configure_fail( |
| 176 | "empty output from command %s, stderr: (%s)" % (command, result.stderr), |
| 177 | ) |
| 178 | return stripped_stdout |
Yun Peng | 65cda4f | 2017-06-22 11:06:11 +0200 | [diff] [blame] | 179 | |
| 180 | def get_cpu_value(repository_ctx): |
vladmos | 20a042f | 2018-06-01 04:51:21 -0700 | [diff] [blame] | 181 | """Compute the cpu_value based on the OS name. Doesn't %-escape the result!""" |
| 182 | os_name = repository_ctx.os.name.lower() |
| 183 | if os_name.startswith("mac os"): |
| 184 | return "darwin" |
| 185 | if os_name.find("freebsd") != -1: |
| 186 | return "freebsd" |
aldersondrive | 644b7d41 | 2020-01-10 07:48:15 -0800 | [diff] [blame] | 187 | if os_name.find("openbsd") != -1: |
| 188 | return "openbsd" |
vladmos | 20a042f | 2018-06-01 04:51:21 -0700 | [diff] [blame] | 189 | if os_name.find("windows") != -1: |
| 190 | return "x64_windows" |
Yun Peng | 65cda4f | 2017-06-22 11:06:11 +0200 | [diff] [blame] | 191 | |
vladmos | 20a042f | 2018-06-01 04:51:21 -0700 | [diff] [blame] | 192 | # Use uname to figure out whether we are on x86_32 or x86_64 |
| 193 | result = repository_ctx.execute(["uname", "-m"]) |
| 194 | if result.stdout.strip() in ["power", "ppc64le", "ppc", "ppc64"]: |
| 195 | return "ppc" |
clyang82 | 21941fe | 2019-04-24 04:16:13 -0700 | [diff] [blame] | 196 | if result.stdout.strip() in ["s390x"]: |
| 197 | return "s390x" |
Arielle Albon | 886d01c | 2018-06-20 05:51:18 -0700 | [diff] [blame] | 198 | if result.stdout.strip() in ["arm", "armv7l"]: |
vladmos | 20a042f | 2018-06-01 04:51:21 -0700 | [diff] [blame] | 199 | return "arm" |
Arielle Albon | 886d01c | 2018-06-20 05:51:18 -0700 | [diff] [blame] | 200 | if result.stdout.strip() in ["aarch64"]: |
| 201 | return "aarch64" |
vladmos | 20a042f | 2018-06-01 04:51:21 -0700 | [diff] [blame] | 202 | return "k8" if result.stdout.strip() in ["amd64", "x86_64", "x64"] else "piii" |
Yun Peng | 65cda4f | 2017-06-22 11:06:11 +0200 | [diff] [blame] | 203 | |
Yun Peng | a6f0f13 | 2017-06-28 14:55:37 +0200 | [diff] [blame] | 204 | def is_cc_configure_debug(repository_ctx): |
vladmos | 20a042f | 2018-06-01 04:51:21 -0700 | [diff] [blame] | 205 | """Returns True if CC_CONFIGURE_DEBUG is set to 1.""" |
| 206 | env = repository_ctx.os.environ |
| 207 | return "CC_CONFIGURE_DEBUG" in env and env["CC_CONFIGURE_DEBUG"] == "1" |
hlopko | 2d0e27e | 2019-01-17 10:10:07 -0800 | [diff] [blame] | 208 | |
| 209 | def build_flags(flags): |
| 210 | """Convert `flags` to a string of flag fields.""" |
| 211 | return "\n".join([" flag: '" + flag + "'" for flag in flags]) |
rosica | 71bc38f | 2019-02-04 02:39:30 -0800 | [diff] [blame] | 212 | |
| 213 | def get_starlark_list(values): |
Marcel Hlopko | 8b0bfaf | 2019-09-02 08:13:18 -0700 | [diff] [blame] | 214 | """Convert a list of string into a string that can be passed to a rule attribute.""" |
Marcel Hlopko | 7ebff4e | 2019-06-18 11:36:13 -0700 | [diff] [blame] | 215 | if not values: |
| 216 | return "" |
rosica | 71bc38f | 2019-02-04 02:39:30 -0800 | [diff] [blame] | 217 | return "\"" + "\",\n \"".join(values) + "\"" |
Marcel Hlopko | ff6fa78 | 2019-06-04 05:52:05 -0700 | [diff] [blame] | 218 | |
| 219 | def auto_configure_warning_maybe(repository_ctx, msg): |
| 220 | """Output warning message when CC_CONFIGURE_DEBUG is enabled.""" |
| 221 | if is_cc_configure_debug(repository_ctx): |
| 222 | auto_configure_warning(msg) |
Marcel Hlopko | 8b0bfaf | 2019-09-02 08:13:18 -0700 | [diff] [blame] | 223 | |
| 224 | def write_builtin_include_directory_paths(repository_ctx, cc, directories, file_suffix = ""): |
| 225 | """Generate output file named 'builtin_include_directory_paths' in the root of the repository.""" |
| 226 | if get_env_var(repository_ctx, "BAZEL_IGNORE_SYSTEM_HEADERS_VERSIONS", "0", False) == "1": |
| 227 | repository_ctx.file( |
| 228 | "builtin_include_directory_paths" + file_suffix, |
| 229 | """This file is generated by cc_configure and normally contains builtin include directories |
| 230 | that C++ compiler reported. But because BAZEL_IGNORE_SYSTEM_HEADERS_VERSIONS was set to 1, |
| 231 | header include directory paths are intentionally not put there. |
| 232 | """, |
| 233 | ) |
| 234 | else: |
| 235 | repository_ctx.file( |
| 236 | "builtin_include_directory_paths" + file_suffix, |
| 237 | """This file is generated by cc_configure and contains builtin include directories |
| 238 | that %s reported. This file is a dependency of every compilation action and |
| 239 | changes to it will be reflected in the action cache key. When some of these |
| 240 | paths change, Bazel will make sure to rerun the action, even though none of |
| 241 | declared action inputs or the action commandline changes. |
| 242 | |
| 243 | %s |
| 244 | """ % (cc, "\n".join(directories)), |
| 245 | ) |