Brian Silverman | 7d89e28 | 2021-11-17 17:36:54 -0800 | [diff] [blame^] | 1 | # Copyright 2021 The Bazel Authors. |
| 2 | # |
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | # you may not use this file except in compliance with the License. |
| 5 | # You may obtain a copy of the License at |
| 6 | # |
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | # |
| 9 | # Unless required by applicable law or agreed to in writing, software |
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | # See the License for the specific language governing permissions and |
| 13 | # limitations under the License. |
| 14 | |
| 15 | SUPPORTED_TARGETS = [("linux", "x86_64"), ("linux", "aarch64"), ("darwin", "x86_64")] |
| 16 | |
| 17 | host_tool_features = struct( |
| 18 | SUPPORTS_ARG_FILE = "supports_arg_file", |
| 19 | ) |
| 20 | |
| 21 | def python(rctx): |
| 22 | # Get path of the python interpreter. |
| 23 | |
| 24 | python3 = rctx.which("python3") |
| 25 | python = rctx.which("python") |
| 26 | python2 = rctx.which("python2") |
| 27 | if python3: |
| 28 | return python3 |
| 29 | elif python: |
| 30 | return python |
| 31 | elif python2: |
| 32 | return python2 |
| 33 | else: |
| 34 | fail("python not found") |
| 35 | |
| 36 | def os(rctx): |
| 37 | name = rctx.os.name |
| 38 | if name == "linux": |
| 39 | return "linux" |
| 40 | elif name == "mac os x": |
| 41 | return "darwin" |
| 42 | elif name.startswith("windows"): |
| 43 | return "windows" |
| 44 | fail("Unsupported OS: " + name) |
| 45 | |
| 46 | def os_bzl(os): |
| 47 | # Return the OS string as used in bazel platform constraints. |
| 48 | return {"darwin": "osx", "linux": "linux"}[os] |
| 49 | |
| 50 | def arch(rctx): |
| 51 | exec_result = rctx.execute([ |
| 52 | python(rctx), |
| 53 | "-c", |
| 54 | "import platform; print(platform.machine())", |
| 55 | ]) |
| 56 | if exec_result.return_code: |
| 57 | fail("Failed to detect machine architecture: \n%s\n%s" % (exec_result.stdout, exec_result.stderr)) |
| 58 | return exec_result.stdout.strip() |
| 59 | |
| 60 | # Tries to figure out if a tool supports newline separated arg files (i.e. |
| 61 | # `@file`). |
| 62 | def _tool_supports_arg_file(rctx, tool_path): |
| 63 | # We assume nothing other than that `tool_path` is an executable. |
| 64 | # |
| 65 | # First we have to find out what command line flag gets the tool to just |
| 66 | # print out some text and exit successfully. |
| 67 | # |
| 68 | # Most tools support `-v` or `--version` or (for `libtool`) `-V` but some |
| 69 | # tools don't have such an option (BSD `ranlib` and `ar`, for example). |
| 70 | # |
| 71 | # We just try all the options we know of until one works and if none work |
| 72 | # we return "None" indicating an indeterminate result. |
| 73 | opts = ( |
| 74 | ["-v", "--version", "-version", "-V"] + |
| 75 | ["-h", "--help", "-help", "-H"] |
| 76 | ) |
| 77 | |
| 78 | no_op_opt = None |
| 79 | for opt in opts: |
| 80 | if rctx.execute([tool_path, opt], timeout = 2).return_code == 0: |
| 81 | no_op_opt = opt |
| 82 | break |
| 83 | |
| 84 | if no_op_opt == None: |
| 85 | return None |
| 86 | |
| 87 | # Okay! Once we have an opt that we *know* does nothing but make the |
| 88 | # executable exit successfully, we'll stick that opt in a file and try |
| 89 | # again: |
| 90 | tmp_file = "tmp-arg-file" |
| 91 | rctx.file(tmp_file, content = "{}\n".format(no_op_opt), executable = False) |
| 92 | |
| 93 | res = rctx.execute([tool_path, "@{}".format(tmp_file)]).return_code == 0 |
| 94 | rctx.delete(tmp_file) |
| 95 | |
| 96 | return res |
| 97 | |
| 98 | def _get_host_tool_info(rctx, tool_path, features_to_test = [], tool_key = None): |
| 99 | if tool_key == None: |
| 100 | tool_key = tool_path |
| 101 | |
| 102 | if tool_path == None or not rctx.path(tool_path).exists: |
| 103 | return {} |
| 104 | |
| 105 | f = host_tool_features |
| 106 | features = {} |
| 107 | for feature in features_to_test: |
| 108 | features[feature] = { |
| 109 | f.SUPPORTS_ARG_FILE: _tool_supports_arg_file, |
| 110 | }[feature](rctx, tool_path) |
| 111 | |
| 112 | return { |
| 113 | tool_key: struct( |
| 114 | path = tool_path, |
| 115 | features = features, |
| 116 | ), |
| 117 | } |
| 118 | |
| 119 | def _extract_tool_path_and_features(tool_info): |
| 120 | # Have to support structs or dicts: |
| 121 | tool_path = tool_info.path if type(tool_info) == "struct" else tool_info["path"] |
| 122 | tool_features = tool_info.features if type(tool_info) == "struct" else tool_info["features"] |
| 123 | |
| 124 | return (tool_path, tool_features) |
| 125 | |
| 126 | def _check_host_tool_supports(host_tool_info, tool_key, features = []): |
| 127 | if tool_key in host_tool_info: |
| 128 | _, tool_features = _extract_tool_path_and_features(host_tool_info[tool_key]) |
| 129 | |
| 130 | for f in features: |
| 131 | if not f in tool_features or not tool_features[f]: |
| 132 | return False |
| 133 | |
| 134 | return True |
| 135 | else: |
| 136 | return False |
| 137 | |
| 138 | def _get_host_tool_and_assert_supports(host_tool_info, tool_key, features = []): |
| 139 | if tool_key in host_tool_info: |
| 140 | tool_path, tool_features = _extract_tool_path_and_features(host_tool_info[tool_key]) |
| 141 | |
| 142 | missing = [f for f in features if not f in tool_features or not tool_features[f]] |
| 143 | |
| 144 | if missing: |
| 145 | fail("Host tool `{key}` (`{path}`) is missing these features: `{missing}`.".format( |
| 146 | key = tool_key, |
| 147 | path = tool_path, |
| 148 | missing = missing, |
| 149 | )) |
| 150 | |
| 151 | return tool_path |
| 152 | else: |
| 153 | return False |
| 154 | |
| 155 | host_tools = struct( |
| 156 | get_tool_info = _get_host_tool_info, |
| 157 | tool_supports = _check_host_tool_supports, |
| 158 | get_and_assert = _get_host_tool_and_assert_supports, |
| 159 | ) |
| 160 | |
| 161 | def os_arch_pair(os, arch): |
| 162 | return "{}-{}".format(os, arch) |
| 163 | |
| 164 | _supported_os_arch = [os_arch_pair(os, arch) for (os, arch) in SUPPORTED_TARGETS] |
| 165 | |
| 166 | def supported_os_arch_keys(): |
| 167 | return _supported_os_arch |
| 168 | |
| 169 | def check_os_arch_keys(keys): |
| 170 | for k in keys: |
| 171 | if k and k not in _supported_os_arch: |
| 172 | fail("Unsupported {{os}}-{{arch}} key: {key}; valid keys are: {keys}".format( |
| 173 | key = k, |
| 174 | keys = ", ".join(_supported_os_arch), |
| 175 | )) |
| 176 | |
| 177 | def canonical_dir_path(path): |
| 178 | if not path.endswith("/"): |
| 179 | return path + "/" |
| 180 | return path |
| 181 | |
| 182 | def pkg_path_from_label(label): |
| 183 | if label.workspace_root: |
| 184 | return label.workspace_root + "/" + label.package |
| 185 | else: |
| 186 | return label.package |