bindgen.configure (12736B)
1 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- 2 # vim: set filetype=python: 3 # This Source Code Form is subject to the terms of the Mozilla Public 4 # License, v. 2.0. If a copy of the MPL was not distributed with this 5 # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 7 8 option(env="CBINDGEN", nargs=1, help="Path to cbindgen") 9 10 11 @imports(_from="textwrap", _import="dedent") 12 def check_cbindgen_version(cbindgen, fatal=False): 13 log.debug("trying cbindgen: %s" % cbindgen) 14 15 cbindgen_min_version = Version("0.29.1") 16 17 # cbindgen x.y.z 18 version = Version(check_cmd_output(cbindgen, "--version").strip().split(" ")[1]) 19 log.debug("%s has version %s" % (cbindgen, version)) 20 if version >= cbindgen_min_version: 21 return True 22 if not fatal: 23 return False 24 25 die( 26 dedent( 27 """\ 28 cbindgen version {} is too old. At least version {} is required. 29 30 Please update using 'cargo install cbindgen --force' or running 31 './mach bootstrap', after removing the existing executable located at 32 {}. 33 """.format( 34 version, cbindgen_min_version, cbindgen 35 ) 36 ) 37 ) 38 39 40 # Similar behavior to what check_prog does. 41 has_cbindgen_input = depends("CBINDGEN")(lambda x: x) 42 bootstrap_cbindgen = depends(has_cbindgen_input)(lambda i: not i) 43 44 45 @depends_if( 46 "CBINDGEN", 47 bootstrap_search_path("cbindgen", when=bootstrap_cbindgen), 48 rust_search_path, 49 ) 50 @checking("for cbindgen") 51 @imports(_from="textwrap", _import="dedent") 52 def cbindgen(cbindgen_override, bootstrap_search_path, rust_search_path): 53 if cbindgen_override: 54 check_cbindgen_version(cbindgen_override[0], fatal=True) 55 return cbindgen_override[0] 56 57 candidates = [] 58 for path in bootstrap_search_path + rust_search_path: 59 candidate = find_program("cbindgen", [path]) 60 if not candidate: 61 continue 62 if check_cbindgen_version(candidate): 63 return candidate 64 candidates.append(candidate) 65 66 if not candidates: 67 raise FatalCheckError( 68 dedent( 69 """\ 70 Cannot find cbindgen. Please run `mach bootstrap`, 71 `cargo install cbindgen`, ensure that `cbindgen` is on your PATH, 72 or point at an executable with `CBINDGEN`. 73 """ 74 ) 75 ) 76 check_cbindgen_version(candidates[0], fatal=True) 77 78 79 set_config("CBINDGEN", cbindgen) 80 81 # Bindgen can use rustfmt to format Rust file, but it's not required. 82 option(env="RUSTFMT", nargs=1, help="Path to the rustfmt program") 83 84 rustfmt = check_prog( 85 "RUSTFMT", 86 ["rustfmt"], 87 paths=rust_search_path, 88 input="RUSTFMT", 89 allow_missing=True, 90 ) 91 92 93 option( 94 "--with-libclang-path", 95 nargs=1, 96 help="Absolute path to a directory containing Clang/LLVM libraries for bindgen (version 3.9.x or above)", 97 ) 98 option( 99 "--with-clang-path", 100 nargs=1, 101 help="Absolute path to a Clang binary for bindgen (version 3.9.x or above)", 102 ) 103 104 105 @depends( 106 "--with-clang-path", 107 configure_cache, 108 c_compiler, 109 cxx_compiler, 110 clang_search_path, 111 target, 112 target_sysroot.path, 113 android_version, 114 ) 115 @checking("for clang for bindgen", lambda x: x.path if x else "not found") 116 def bindgen_clang_compiler( 117 clang_path, 118 configure_cache, 119 c_compiler, 120 cxx_compiler, 121 clang_search_path, 122 target, 123 sysroot_path, 124 android_version, 125 ): 126 # When the target compiler is clang, use that, including flags. 127 if cxx_compiler.type == "clang": 128 if clang_path and clang_path[0] not in ( 129 c_compiler.compiler, 130 cxx_compiler.compiler, 131 ): 132 die( 133 "--with-clang-path is not valid when the target compiler is %s", 134 cxx_compiler.type, 135 ) 136 return namespace( 137 path=cxx_compiler.compiler, 138 flags=cxx_compiler.flags, 139 ) 140 # When the target compiler is clang-cl, use clang in the same directory, 141 # and figure the right flags to use. 142 if cxx_compiler.type == "clang-cl": 143 if clang_path and os.path.dirname(clang_path[0]) != os.path.dirname( 144 cxx_compiler.compiler 145 ): 146 die( 147 "--with-clang-path must point to clang in the same directory " 148 "as the target compiler" 149 ) 150 if not clang_path: 151 clang_path = [os.path.join(os.path.dirname(cxx_compiler.compiler), "clang")] 152 153 clang_path = find_program( 154 clang_path[0] if clang_path else "clang++", clang_search_path 155 ) 156 if not clang_path: 157 return 158 # Hack before bug 1617793: if the compiler is clang-cl, hack the target 159 if cxx_compiler.type == "clang-cl": 160 target = split_triplet("%s-pc-windows-msvc" % target.raw_cpu) 161 flags = [] 162 if sysroot_path: 163 flags.extend(("--sysroot", sysroot_path)) 164 info = check_compiler( 165 configure_cache, [clang_path] + flags, "C++", target, android_version 166 ) 167 # Usually, one check_compiler pass would be enough, but when cross-compiling 168 # and the host and target don't use the same default C++ standard, we don't 169 # get the --std flag, so try again. This is the same thing as valid_compiler() 170 # does in toolchain.configure. 171 if info.flags: 172 flags += info.flags 173 info = check_compiler( 174 configure_cache, [clang_path] + flags, "C++", target, android_version 175 ) 176 return namespace( 177 path=clang_path, 178 flags=flags + info.flags, 179 ) 180 181 182 @depends("--with-libclang-path", bindgen_clang_compiler, host_library_name_info, host) 183 @checking("for libclang for bindgen", lambda x: x if x else "not found") 184 @imports("glob") 185 @imports(_from="os", _import="pathsep") 186 @imports(_from="os.path", _import="split", _as="pathsplit") 187 @imports("re") 188 def bindgen_libclang_path(libclang_path, clang, library_name_info, host): 189 if not clang: 190 if libclang_path: 191 die( 192 "--with-libclang-path is not valid without a clang compiler " 193 "for bindgen" 194 ) 195 return 196 197 # Try to ensure that the clang shared library that bindgen is going 198 # to look for is actually present. The files that we search for 199 # mirror the logic in clang-sys/build.rs. 200 libclang_choices = [] 201 if host.os == "WINNT": 202 libclang_choices.append("libclang.dll") 203 libclang_choices.append( 204 "%sclang%s" % (library_name_info.dll.prefix, library_name_info.dll.suffix) 205 ) 206 if host.kernel == "Linux": 207 libclang_choices.append("libclang.so.*") 208 209 if host.os == "OpenBSD": 210 libclang_choices.append("libclang.so.*.*") 211 212 candidates = [] 213 if not libclang_path: 214 # Try to find libclang_path based on clang search dirs. 215 clang_search_dirs = check_cmd_output(clang.path, "-print-search-dirs") 216 for line in clang_search_dirs.splitlines(): 217 name, _, value = line.partition(": =") 218 if host.os == "WINNT" and name == "programs": 219 # On Windows, libclang.dll is in bin/ rather than lib/, 220 # so scan the programs search dirs. 221 # To make matters complicated, clang before version 9 uses `:` 222 # separate between paths (and `;` in newer versions) 223 if pathsep in value: 224 candidates.extend(value.split(pathsep)) 225 else: 226 for part in value.split(":"): 227 # Assume that if previous "candidate" was of length 1, 228 # it's a drive letter and the current part is the rest of 229 # the corresponding full path. 230 if candidates and len(candidates[-1]) == 1: 231 candidates[-1] += ":" + part 232 else: 233 candidates.append(part) 234 elif host.os != "WINNT" and name == "libraries": 235 # On other platforms, use the directories from the libraries 236 # search dirs that looks like $something/clang/$version. 237 for dir in value.split(pathsep): 238 dir, version = pathsplit(dir) 239 if re.match(r"[0-9.]+", version): 240 dir, name = pathsplit(dir) 241 if name == "clang": 242 candidates.append(dir) 243 else: 244 candidates.append(libclang_path[0]) 245 246 for dir in candidates: 247 for pattern in libclang_choices: 248 log.debug('Trying "%s" in "%s"', pattern, dir) 249 libs = glob.glob(os.path.join(dir, pattern)) 250 if libs: 251 return libs[0] 252 253 254 @depends(bindgen_clang_compiler, bindgen_libclang_path, build_project) 255 def bindgen_config_paths(clang, libclang, build_project): 256 # XXX: we want this code to be run for both Gecko and JS, but we don't 257 # necessarily want to force a bindgen/Rust dependency on JS just yet. 258 # Actually, we don't want to force an error if we're not building the 259 # browser generally. We therefore whitelist the projects that require 260 # bindgen facilities at this point and leave it at that. 261 if build_project in ("browser", "mobile/android"): 262 if not clang: 263 die( 264 "Could not find clang to generate run bindings for C/C++. " 265 "Please install the necessary packages, run `mach bootstrap`, " 266 "or use --with-clang-path to give the location of clang." 267 ) 268 269 if not libclang: 270 die( 271 "Could not find libclang to generate rust bindings for C/C++. " 272 "Please install the necessary packages, run `mach bootstrap`, " 273 "or use --with-libclang-path to give the path containing it." 274 ) 275 276 if clang and libclang: 277 return namespace( 278 libclang=libclang, 279 libclang_path=os.path.dirname(libclang), 280 clang_path=clang.path, 281 clang_flags=clang.flags, 282 ) 283 284 285 @depends(bindgen_config_paths.libclang, when=bindgen_config_paths) 286 @checking("that libclang is new enough", lambda s: "yes" if s else "no") 287 @imports(_from="__builtin__", _import="Exception") 288 @imports(_from="ctypes", _import="CDLL") 289 @imports(_from="textwrap", _import="dedent") 290 def min_libclang_version(libclang): 291 try: 292 lib = CDLL(libclang) 293 # We want at least 5.0. The API we test below is enough for that. 294 # Just accessing it should throw if not found. 295 fun = lib.clang_getAddressSpace 296 return True 297 except Exception as e: 298 log.debug(e) 299 die( 300 dedent( 301 """\ 302 The libclang located at {} is too old (need at least 5.0). 303 304 Please make sure to update it or point to a newer libclang using 305 --with-libclang-path. 306 """.format( 307 libclang 308 ) 309 ) 310 ) 311 return False 312 313 314 set_config("MOZ_LIBCLANG_PATH", bindgen_config_paths.libclang_path) 315 set_config("MOZ_CLANG_PATH", bindgen_config_paths.clang_path) 316 317 318 @depends( 319 target, 320 cxx_compiler, 321 bindgen_cflags_android, 322 bindgen_config_paths.clang_flags, 323 all_clang_arm_flags, 324 ) 325 def basic_bindgen_cflags( 326 target, compiler_info, android_cflags, clang_flags, all_arm_flags 327 ): 328 args = [ 329 "-x", 330 "c++", 331 "-fno-sized-deallocation", 332 "-fno-aligned-new", 333 "-DTRACING=1", 334 "-DIMPL_LIBXUL", 335 "-DMOZILLA_INTERNAL_API", 336 "-DRUST_BINDGEN", 337 ] 338 339 if target.os == "Android": 340 args += android_cflags 341 342 args += { 343 "WINNT": [ 344 "-DWIN32=1", 345 ], 346 }.get(target.os, []) 347 348 if compiler_info.type == "clang-cl": 349 args += [ 350 # To enable the builtin __builtin_offsetof so that CRT wouldn't 351 # use reinterpret_cast in offsetof() which is not allowed inside 352 # static_assert(). 353 "-D_CRT_USE_BUILTIN_OFFSETOF", 354 # Enable hidden attribute (which is not supported by MSVC and 355 # thus not enabled by default with a MSVC-compatibile build) 356 # to exclude hidden symbols from the generated file. 357 "-DHAVE_VISIBILITY_HIDDEN_ATTRIBUTE=1", 358 ] 359 360 return args + (clang_flags or []) + (all_arm_flags or []) 361 362 363 option( 364 env="BINDGEN_CFLAGS", 365 nargs=1, 366 help="Options bindgen should pass to the C/C++ parser", 367 ) 368 369 370 @depends(basic_bindgen_cflags, "BINDGEN_CFLAGS") 371 @checking("bindgen cflags", lambda s: s if s else "no") 372 def bindgen_cflags(base_flags, extra_flags): 373 flags = base_flags 374 if extra_flags and len(extra_flags): 375 flags += extra_flags[0].split() 376 return flags 377 378 379 set_config("BINDGEN_SYSTEM_FLAGS", bindgen_cflags)