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 17def resolve_labels(repository_ctx, labels): 18 """Resolves a collection of labels to their paths. 19 20 Label resolution can cause the evaluation of Starlark functions to restart. 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. 25 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. 30 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. 34 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]) 39 40def escape_string(arg): 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 46 47def split_escaped(string, delimiter): 48 """Split string on the delimiter unless %-escaped. 49 50 Examples: 51 Basic usage: 52 split_escaped("a:b:c", ":") -> [ "a", "b", "c" ] 53 54 Delimeter that is not supposed to be splitten on has to be %-escaped: 55 split_escaped("a%:b", ":") -> [ "a:b" ] 56 57 Literal % can be represented by escaping it as %%: 58 split_escaped("a%%b", ":") -> [ "a%b" ] 59 60 Consecutive delimiters produce empty strings: 61 split_escaped("a::b", ":") -> [ "a", "", "", "b" ] 62 63 Args: 64 string: The string to be split. 65 delimiter: Non-empty string not containing %-sign to be used as a 66 delimiter. 67 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") 75 76 i = 0 77 result = [] 78 accumulator = [] 79 length = len(string) 80 delimiter_length = len(delimiter) 81 82 if not string: 83 return [] 84 85 # Iterate over the length of string since Starlark doesn't have while loops 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 103 104 # Append the last group still in accumulator 105 result.append("".join(accumulator)) 106 return result 107 108def auto_configure_fail(msg): 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)) 113 114def auto_configure_warning(msg): 115 """Output warning message during auto configuration.""" 116 yellow = "\033[1;33m" 117 no_color = "\033[0m" 118 119 # buildifier: disable=print 120 print("\n%sAuto-Configuration Warning:%s %s\n" % (yellow, no_color, msg)) 121 122def get_env_var(repository_ctx, name, default = None, enable_warning = True): 123 """Find an environment variable in system path. Doesn't %-escape the value! 124 125 Args: 126 repository_ctx: The repository context. 127 name: Name of the environment variable. 128 default: Default value to be used when such environment variable is not present. 129 enable_warning: Show warning if the variable is not present. 130 Returns: 131 value of the environment variable or default. 132 """ 133 134 if name in repository_ctx.os.environ: 135 return repository_ctx.os.environ[name] 136 if default != None: 137 if enable_warning: 138 auto_configure_warning("'%s' environment variable is not set, using '%s' as default" % (name, default)) 139 return default 140 return auto_configure_fail("'%s' environment variable is not set" % name) 141 142def which(repository_ctx, cmd, default = None): 143 """A wrapper around repository_ctx.which() to provide a fallback value. Doesn't %-escape the value! 144 145 Args: 146 repository_ctx: The repository context. 147 cmd: name of the executable to resolve. 148 default: Value to be returned when such executable couldn't be found. 149 Returns: 150 absolute path to the cmd or default when not found. 151 """ 152 result = repository_ctx.which(cmd) 153 return default if result == None else str(result) 154 155def which_cmd(repository_ctx, cmd, default = None): 156 """Find cmd in PATH using repository_ctx.which() and fail if cannot find it. Doesn't %-escape the cmd! 157 158 Args: 159 repository_ctx: The repository context. 160 cmd: name of the executable to resolve. 161 default: Value to be returned when such executable couldn't be found. 162 Returns: 163 absolute path to the cmd or default when not found. 164 """ 165 result = repository_ctx.which(cmd) 166 if result != None: 167 return str(result) 168 path = get_env_var(repository_ctx, "PATH") 169 if default != None: 170 auto_configure_warning("Cannot find %s in PATH, using '%s' as default.\nPATH=%s" % (cmd, default, path)) 171 return default 172 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)) 173 return str(result) 174 175def execute( 176 repository_ctx, 177 command, 178 environment = None, 179 expect_failure = False): 180 """Execute a command, return stdout if succeed and throw an error if it fails. Doesn't %-escape the result! 181 182 Args: 183 repository_ctx: The repository context. 184 command: command to execute. 185 environment: dictionary with environment variables to set for the command. 186 expect_failure: True if the command is expected to fail. 187 Returns: 188 stdout of the executed command. 189 """ 190 if environment: 191 result = repository_ctx.execute(command, environment = environment) 192 else: 193 result = repository_ctx.execute(command) 194 if expect_failure != (result.return_code != 0): 195 if expect_failure: 196 auto_configure_fail( 197 "expected failure, command %s, stderr: (%s)" % ( 198 command, 199 result.stderr, 200 ), 201 ) 202 else: 203 auto_configure_fail( 204 "non-zero exit code: %d, command %s, stderr: (%s)" % ( 205 result.return_code, 206 command, 207 result.stderr, 208 ), 209 ) 210 stripped_stdout = result.stdout.strip() 211 if not stripped_stdout: 212 auto_configure_fail( 213 "empty output from command %s, stderr: (%s)" % (command, result.stderr), 214 ) 215 return stripped_stdout 216 217def get_cpu_value(repository_ctx): 218 """Compute the cpu_value based on the OS name. Doesn't %-escape the result! 219 220 Args: 221 repository_ctx: The repository context. 222 Returns: 223 One of (darwin, freebsd, x64_windows, ppc, s390x, arm, aarch64, k8, piii) 224 """ 225 os_name = repository_ctx.os.name.lower() 226 if os_name.startswith("mac os"): 227 return "darwin" 228 if os_name.find("freebsd") != -1: 229 return "freebsd" 230 if os_name.find("windows") != -1: 231 return "x64_windows" 232 233 # Use uname to figure out whether we are on x86_32 or x86_64 234 result = repository_ctx.execute(["uname", "-m"]) 235 if result.stdout.strip() in ["power", "ppc64le", "ppc", "ppc64"]: 236 return "ppc" 237 if result.stdout.strip() in ["s390x"]: 238 return "s390x" 239 if result.stdout.strip() in ["arm", "armv7l"]: 240 return "arm" 241 if result.stdout.strip() in ["aarch64"]: 242 return "aarch64" 243 return "k8" if result.stdout.strip() in ["amd64", "x86_64", "x64"] else "piii" 244 245def is_cc_configure_debug(repository_ctx): 246 """Returns True if CC_CONFIGURE_DEBUG is set to 1.""" 247 env = repository_ctx.os.environ 248 return "CC_CONFIGURE_DEBUG" in env and env["CC_CONFIGURE_DEBUG"] == "1" 249 250def build_flags(flags): 251 """Convert `flags` to a string of flag fields.""" 252 return "\n".join([" flag: '" + flag + "'" for flag in flags]) 253 254def get_starlark_list(values): 255 """Convert a list of string into a string that can be passed to a rule attribute.""" 256 if not values: 257 return "" 258 return "\"" + "\",\n \"".join(values) + "\"" 259 260def auto_configure_warning_maybe(repository_ctx, msg): 261 """Output warning message when CC_CONFIGURE_DEBUG is enabled.""" 262 if is_cc_configure_debug(repository_ctx): 263 auto_configure_warning(msg) 264 265def write_builtin_include_directory_paths(repository_ctx, cc, directories, file_suffix = ""): 266 """Generate output file named 'builtin_include_directory_paths' in the root of the repository.""" 267 if get_env_var(repository_ctx, "BAZEL_IGNORE_SYSTEM_HEADERS_VERSIONS", "0", False) == "1": 268 repository_ctx.file( 269 "builtin_include_directory_paths" + file_suffix, 270 """This file is generated by cc_configure and normally contains builtin include directories 271that C++ compiler reported. But because BAZEL_IGNORE_SYSTEM_HEADERS_VERSIONS was set to 1, 272header include directory paths are intentionally not put there. 273""", 274 ) 275 else: 276 repository_ctx.file( 277 "builtin_include_directory_paths" + file_suffix, 278 """This file is generated by cc_configure and contains builtin include directories 279that %s reported. This file is a dependency of every compilation action and 280changes to it will be reflected in the action cache key. When some of these 281paths change, Bazel will make sure to rerun the action, even though none of 282declared action inputs or the action commandline changes. 283 284%s 285""" % (cc, "\n".join(directories)), 286 ) 287