blob: ee89b9d849d31289704c7abb48d23830567749ab [file] [log] [blame]
Yun Peng65cda4f2017-06-22 11:06:11 +02001# 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
jmmv5b025592018-05-29 12:03:21 -070017def resolve_labels(repository_ctx, labels):
vladmos20a042f2018-06-01 04:51:21 -070018 """Resolves a collection of labels to their paths.
jmmv5b025592018-05-29 12:03:21 -070019
gregce587c7362020-04-15 10:36:23 -070020 Label resolution can cause the evaluation of Starlark functions to restart.
vladmos20a042f2018-06-01 04:51:21 -070021 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.
jmmv5b025592018-05-29 12:03:21 -070025
vladmos20a042f2018-06-01 04:51:21 -070026 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.
jmmv5b025592018-05-29 12:03:21 -070030
vladmos20a042f2018-06-01 04:51:21 -070031 Args:
32 repository_ctx: The context with which to resolve the labels.
33 labels: Labels to be resolved expressed as a list of strings.
jmmv5b025592018-05-29 12:03:21 -070034
vladmos20a042f2018-06-01 04:51:21 -070035 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])
jmmv5b025592018-05-29 12:03:21 -070039
Yun Peng65cda4f2017-06-22 11:06:11 +020040def escape_string(arg):
vladmos20a042f2018-06-01 04:51:21 -070041 """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
jmmv5b025592018-05-29 12:03:21 -070046
hlopkodb802982018-02-27 06:09:18 -080047def split_escaped(string, delimiter):
vladmos20a042f2018-06-01 04:51:21 -070048 """Split string on the delimiter unless %-escaped.
hlopkodb802982018-02-27 06:09:18 -080049
vladmos20a042f2018-06-01 04:51:21 -070050 Examples:
51 Basic usage:
52 split_escaped("a:b:c", ":") -> [ "a", "b", "c" ]
hlopkodb802982018-02-27 06:09:18 -080053
vladmos20a042f2018-06-01 04:51:21 -070054 Delimeter that is not supposed to be splitten on has to be %-escaped:
55 split_escaped("a%:b", ":") -> [ "a:b" ]
hlopkodb802982018-02-27 06:09:18 -080056
vladmos20a042f2018-06-01 04:51:21 -070057 Literal % can be represented by escaping it as %%:
58 split_escaped("a%%b", ":") -> [ "a%b" ]
hlopkodb802982018-02-27 06:09:18 -080059
vladmos20a042f2018-06-01 04:51:21 -070060 Consecutive delimiters produce empty strings:
61 split_escaped("a::b", ":") -> [ "a", "", "", "b" ]
hlopkodb802982018-02-27 06:09:18 -080062
vladmos20a042f2018-06-01 04:51:21 -070063 Args:
64 string: The string to be split.
65 delimiter: Non-empty string not containing %-sign to be used as a
66 delimiter.
hlopkodb802982018-02-27 06:09:18 -080067
vladmos20a042f2018-06-01 04:51:21 -070068 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")
hlopkodb802982018-02-27 06:09:18 -080075
vladmos20a042f2018-06-01 04:51:21 -070076 i = 0
77 result = []
78 accumulator = []
79 length = len(string)
80 delimiter_length = len(delimiter)
hlopkodb802982018-02-27 06:09:18 -080081
Marcel Hlopko7ebff4e2019-06-18 11:36:13 -070082 if not string:
83 return []
84
gregce587c7362020-04-15 10:36:23 -070085 # Iterate over the length of string since Starlark doesn't have while loops
vladmos20a042f2018-06-01 04:51:21 -070086 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
hlopkodb802982018-02-27 06:09:18 -0800103
vladmos20a042f2018-06-01 04:51:21 -0700104 # Append the last group still in accumulator
105 result.append("".join(accumulator))
106 return result
Yun Peng65cda4f2017-06-22 11:06:11 +0200107
108def auto_configure_fail(msg):
vladmos20a042f2018-06-01 04:51:21 -0700109 """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 Peng65cda4f2017-06-22 11:06:11 +0200113
114def auto_configure_warning(msg):
vladmos20a042f2018-06-01 04:51:21 -0700115 """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 Peng65cda4f2017-06-22 11:06:11 +0200119
120def get_env_var(repository_ctx, name, default = None, enable_warning = True):
vladmos20a042f2018-06-01 04:51:21 -0700121 """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 Peng65cda4f2017-06-22 11:06:11 +0200129
130def which(repository_ctx, cmd, default = None):
vladmos20a042f2018-06-01 04:51:21 -0700131 """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 Peng65cda4f2017-06-22 11:06:11 +0200134
135def which_cmd(repository_ctx, cmd, default = None):
vladmos20a042f2018-06-01 04:51:21 -0700136 """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 Peng65cda4f2017-06-22 11:06:11 +0200145 return str(result)
Yun Peng65cda4f2017-06-22 11:06:11 +0200146
vladmos20a042f2018-06-01 04:51:21 -0700147def 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 Peng65cda4f2017-06-22 11:06:11 +0200155 else:
vladmos20a042f2018-06-01 04:51:21 -0700156 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 Peng65cda4f2017-06-22 11:06:11 +0200179
180def get_cpu_value(repository_ctx):
vladmos20a042f2018-06-01 04:51:21 -0700181 """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"
aldersondrive644b7d412020-01-10 07:48:15 -0800187 if os_name.find("openbsd") != -1:
188 return "openbsd"
vladmos20a042f2018-06-01 04:51:21 -0700189 if os_name.find("windows") != -1:
190 return "x64_windows"
Yun Peng65cda4f2017-06-22 11:06:11 +0200191
vladmos20a042f2018-06-01 04:51:21 -0700192 # 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"
clyang8221941fe2019-04-24 04:16:13 -0700196 if result.stdout.strip() in ["s390x"]:
197 return "s390x"
Arielle Albon886d01c2018-06-20 05:51:18 -0700198 if result.stdout.strip() in ["arm", "armv7l"]:
vladmos20a042f2018-06-01 04:51:21 -0700199 return "arm"
Arielle Albon886d01c2018-06-20 05:51:18 -0700200 if result.stdout.strip() in ["aarch64"]:
201 return "aarch64"
vladmos20a042f2018-06-01 04:51:21 -0700202 return "k8" if result.stdout.strip() in ["amd64", "x86_64", "x64"] else "piii"
Yun Peng65cda4f2017-06-22 11:06:11 +0200203
Yun Penga6f0f132017-06-28 14:55:37 +0200204def is_cc_configure_debug(repository_ctx):
vladmos20a042f2018-06-01 04:51:21 -0700205 """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"
hlopko2d0e27e2019-01-17 10:10:07 -0800208
209def build_flags(flags):
210 """Convert `flags` to a string of flag fields."""
211 return "\n".join([" flag: '" + flag + "'" for flag in flags])
rosica71bc38f2019-02-04 02:39:30 -0800212
213def get_starlark_list(values):
Marcel Hlopko8b0bfaf2019-09-02 08:13:18 -0700214 """Convert a list of string into a string that can be passed to a rule attribute."""
Marcel Hlopko7ebff4e2019-06-18 11:36:13 -0700215 if not values:
216 return ""
rosica71bc38f2019-02-04 02:39:30 -0800217 return "\"" + "\",\n \"".join(values) + "\""
Marcel Hlopkoff6fa782019-06-04 05:52:05 -0700218
219def 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 Hlopko8b0bfaf2019-09-02 08:13:18 -0700223
224def 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
230that C++ compiler reported. But because BAZEL_IGNORE_SYSTEM_HEADERS_VERSIONS was set to 1,
231header 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
238that %s reported. This file is a dependency of every compilation action and
239changes to it will be reflected in the action cache key. When some of these
240paths change, Bazel will make sure to rerun the action, even though none of
241declared action inputs or the action commandline changes.
242
243%s
244""" % (cc, "\n".join(directories)),
245 )