1 // Copyright 2019 TiKV Project Authors. Licensed under Apache-2.0. 2 3 use std::collections::HashSet; 4 use std::env::VarError; 5 use std::fs::File; 6 use std::io::prelude::*; 7 use std::io::BufReader; 8 use std::path::{Path, PathBuf}; 9 use std::{env, fs, io}; 10 11 use cmake::Config as CmakeConfig; 12 use pkg_config::{Config as PkgConfig, Library}; 13 use walkdir::WalkDir; 14 grpc_version() -> &'static str15 fn grpc_version() -> &'static str { 16 let mut version = env!("CARGO_PKG_VERSION").split('+'); 17 version.next().unwrap(); 18 let label = version.next().unwrap(); 19 label.split('-').next().unwrap() 20 } 21 22 include!("link-deps.rs"); 23 probe_library(library: &str, cargo_metadata: bool) -> Library24 fn probe_library(library: &str, cargo_metadata: bool) -> Library { 25 match PkgConfig::new() 26 .atleast_version(grpc_version()) 27 .cargo_metadata(cargo_metadata) 28 .probe(library) 29 { 30 Ok(lib) => lib, 31 Err(e) => panic!("can't find library {} via pkg-config: {:?}", library, e), 32 } 33 } 34 prepare_grpc()35 fn prepare_grpc() { 36 let modules = vec![ 37 "grpc", 38 "grpc/third_party/cares/cares", 39 "grpc/third_party/address_sorting", 40 "grpc/third_party/abseil-cpp", 41 "grpc/third_party/re2", 42 ]; 43 44 for module in modules { 45 if is_directory_empty(module).unwrap_or(true) { 46 panic!( 47 "Can't find module {}. You need to run `git submodule \ 48 update --init --recursive` first to build the project.", 49 module 50 ); 51 } 52 } 53 } 54 is_directory_empty<P: AsRef<Path>>(p: P) -> Result<bool, io::Error>55 fn is_directory_empty<P: AsRef<Path>>(p: P) -> Result<bool, io::Error> { 56 let mut entries = fs::read_dir(p)?; 57 Ok(entries.next().is_none()) 58 } 59 trim_start<'a>(s: &'a str, prefix: &str) -> Option<&'a str>60 fn trim_start<'a>(s: &'a str, prefix: &str) -> Option<&'a str> { 61 if s.starts_with(prefix) { 62 Some(s.trim_start_matches(prefix)) 63 } else { 64 None 65 } 66 } 67 68 /// If cache is stale, remove it to avoid compilation failure. clean_up_stale_cache(cxx_compiler: String)69 fn clean_up_stale_cache(cxx_compiler: String) { 70 // We don't know the cmake output path before it's configured. 71 let build_dir = format!("{}/build", env::var("OUT_DIR").unwrap()); 72 let path = format!("{build_dir}/CMakeCache.txt"); 73 let f = match std::fs::File::open(path) { 74 Ok(f) => BufReader::new(f), 75 // It may be an empty directory. 76 Err(_) => return, 77 }; 78 let cache_stale = f.lines().any(|l| { 79 let l = l.unwrap(); 80 trim_start(&l, "CMAKE_CXX_COMPILER:").map_or(false, |s| { 81 let mut splits = s.splitn(2, '='); 82 splits.next(); 83 splits.next().map_or(false, |p| p != cxx_compiler) 84 }) 85 }); 86 // CMake can't handle compiler change well, it will invalidate cache without respecting command 87 // line settings and result in configuration failure. 88 // See https://gitlab.kitware.com/cmake/cmake/-/issues/18959. 89 if cache_stale { 90 let _ = fs::remove_dir_all(&build_dir); 91 } 92 } 93 94 /// List packages needed for linking in working directory. list_packages(dst: &Path)95 fn list_packages(dst: &Path) { 96 env::set_var( 97 "PKG_CONFIG_PATH", 98 format!("{}/lib/pkgconfig", dst.display()), 99 ); 100 let mut cfg = PkgConfig::new(); 101 cfg.print_system_cflags(false) 102 .print_system_libs(false) 103 .env_metadata(false) 104 .cargo_metadata(false) 105 .atleast_version(grpc_version()); 106 let grpc = cfg.probe("grpc").unwrap(); 107 let mut grpc_libs: HashSet<_> = grpc.libs.iter().cloned().collect(); 108 let grpc_unsecure = cfg.probe("grpc_unsecure").unwrap(); 109 let mut grpc_unsecure_libs: HashSet<_> = grpc_unsecure.libs.iter().cloned().collect(); 110 111 // grpc_unsecure.pc is not accurate, see also grpc/grpc#24512. Should also include "address_sorting", "upb", "cares", "z". 112 const EXTRA_LIBS: [&str; 5] = ["address_sorting", "upb", "cares", "r2", "z"]; 113 grpc_unsecure_libs.extend(EXTRA_LIBS.iter().map(ToString::to_string)); 114 grpc_libs.extend(EXTRA_LIBS.iter().map(ToString::to_string)); 115 // There is no "rt" on Windows and MacOS. 116 grpc_libs.remove("rt"); 117 grpc_unsecure_libs.remove("rt"); 118 119 // ssl, crypto is managed by us according to different features. 120 grpc_libs.remove("ssl"); 121 grpc_libs.remove("crypto"); 122 123 let mut common_libs: Vec<_> = grpc_libs.intersection(&grpc_unsecure_libs).collect(); 124 let mut secure_only: Vec<_> = grpc_libs.difference(&grpc_unsecure_libs).collect(); 125 let mut unsecure_only: Vec<_> = grpc_unsecure_libs.difference(&grpc_libs).collect(); 126 127 common_libs.sort(); 128 secure_only.sort(); 129 unsecure_only.sort(); 130 131 let outputs = &[ 132 ("COMMON_DEPS", common_libs), 133 ("GRPC_DEPS", secure_only), 134 ("GRPC_UNSECURE_DEPS", unsecure_only), 135 ]; 136 137 let mut f = File::create("link-deps.rs").unwrap(); 138 f.write_all( 139 b"/// Following two arrays are generated by running pkg-config manually. We can 140 /// also choose to run pkg-config at build time, but it will requires pkg-config 141 /// in path, which is unfriendly for platforms like Windows. 142 ", 143 ) 144 .unwrap(); 145 for (name, libs) in outputs { 146 writeln!(f, "const {name}: &[&str] = &[").unwrap(); 147 for lib in libs { 148 writeln!(f, "\"{lib}\",").unwrap(); 149 } 150 writeln!(f, "];").unwrap(); 151 } 152 } 153 build_grpc(cc: &mut cc::Build, library: &str)154 fn build_grpc(cc: &mut cc::Build, library: &str) { 155 prepare_grpc(); 156 157 let target = env::var("TARGET").unwrap(); 158 let dst = { 159 let mut config = CmakeConfig::new("grpc"); 160 161 if get_env("CARGO_CFG_TARGET_OS").map_or(false, |s| s == "macos") { 162 config.cxxflag("-stdlib=libc++"); 163 println!("cargo:rustc-link-lib=resolv"); 164 } 165 166 // Ensure CoreFoundation be found in macos or ios 167 if get_env("CARGO_CFG_TARGET_OS").map_or(false, |s| s == "macos") 168 || get_env("CARGO_CFG_TARGET_OS").map_or(false, |s| s == "ios") 169 { 170 println!("cargo:rustc-link-lib=framework=CoreFoundation"); 171 } 172 173 let cxx_compiler = if let Some(val) = get_env("CXX") { 174 config.define("CMAKE_CXX_COMPILER", val.clone()); 175 val 176 } else if env::var("CARGO_CFG_TARGET_ENV").unwrap() == "musl" { 177 config.define("CMAKE_CXX_COMPILER", "g++"); 178 "g++".to_owned() 179 } else { 180 format!("{}", cc.get_compiler().path().display()) 181 }; 182 clean_up_stale_cache(cxx_compiler); 183 184 // Cross-compile support for iOS 185 match target.as_str() { 186 "aarch64-apple-ios" => { 187 config 188 .define("CMAKE_OSX_SYSROOT", "iphoneos") 189 .define("CMAKE_OSX_ARCHITECTURES", "arm64"); 190 } 191 "armv7-apple-ios" => { 192 config 193 .define("CMAKE_OSX_SYSROOT", "iphoneos") 194 .define("CMAKE_OSX_ARCHITECTURES", "armv7"); 195 } 196 "armv7s-apple-ios" => { 197 config 198 .define("CMAKE_OSX_SYSROOT", "iphoneos") 199 .define("CMAKE_OSX_ARCHITECTURES", "armv7s"); 200 } 201 "i386-apple-ios" => { 202 config 203 .define("CMAKE_OSX_SYSROOT", "iphonesimulator") 204 .define("CMAKE_OSX_ARCHITECTURES", "i386"); 205 } 206 "x86_64-apple-ios" => { 207 config 208 .define("CMAKE_OSX_SYSROOT", "iphonesimulator") 209 .define("CMAKE_OSX_ARCHITECTURES", "x86_64"); 210 } 211 _ => {} 212 }; 213 214 // Allow overriding of the target passed to cmake 215 // (needed for Android crosscompile) 216 if let Ok(val) = env::var("CMAKE_TARGET_OVERRIDE") { 217 config.target(&val); 218 } 219 220 // We don't need to generate install targets. 221 config.define("gRPC_INSTALL", cfg!(feature = "_list-package").to_string()); 222 // We don't need to build csharp target. 223 config.define("gRPC_BUILD_CSHARP_EXT", "false"); 224 // We don't need to build codegen target. 225 config.define("gRPC_BUILD_CODEGEN", "false"); 226 // We don't need to build benchmarks. 227 config.define("gRPC_BENCHMARK_PROVIDER", "none"); 228 // Check https://github.com/protocolbuffers/protobuf/issues/12185 229 config.define("ABSL_ENABLE_INSTALL", "ON"); 230 231 // `package` should only be set for secure feature, otherwise cmake will always search for 232 // ssl library. 233 if cfg!(feature = "_secure") { 234 config.define("gRPC_SSL_PROVIDER", "package"); 235 } 236 #[cfg(feature = "_secure")] 237 if cfg!(feature = "openssl") { 238 if cfg!(feature = "openssl-vendored") { 239 config.register_dep("openssl"); 240 } 241 } else { 242 #[cfg(feature = "boringssl")] 243 build_boringssl(&mut config); 244 } 245 if cfg!(feature = "no-omit-frame-pointer") { 246 config 247 .cflag("-fno-omit-frame-pointer") 248 .cxxflag("-fno-omit-frame-pointer"); 249 } 250 // Uses zlib from libz-sys. 251 setup_libz(&mut config); 252 if !cfg!(feature = "_list-package") { 253 config.build_target(library); 254 } 255 config.uses_cxx11().build() 256 }; 257 258 let lib_suffix = if target.contains("msvc") { 259 ".lib" 260 } else { 261 ".a" 262 }; 263 let build_dir = format!("{}/build", dst.display()); 264 for e in WalkDir::new(&build_dir) { 265 let e = e.unwrap(); 266 if e.file_name().to_string_lossy().ends_with(lib_suffix) { 267 println!( 268 "cargo:rustc-link-search=native={}", 269 e.path().parent().unwrap().display() 270 ); 271 } 272 } 273 274 if cfg!(feature = "_list-package") { 275 list_packages(&dst); 276 } 277 278 let libs = if library.contains("unsecure") { 279 GRPC_UNSECURE_DEPS 280 } else { 281 GRPC_DEPS 282 }; 283 for l in COMMON_DEPS.iter().chain(libs) { 284 println!("cargo:rustc-link-lib=static={l}"); 285 } 286 287 if cfg!(feature = "_secure") { 288 if cfg!(feature = "openssl") && !cfg!(feature = "openssl-vendored") { 289 figure_ssl_path(&build_dir); 290 } else { 291 println!("cargo:rustc-link-lib=static=ssl"); 292 println!("cargo:rustc-link-lib=static=crypto"); 293 } 294 } 295 296 figure_systemd_path(&build_dir); 297 298 cc.include("grpc/include"); 299 } 300 figure_systemd_path(build_dir: &str)301 fn figure_systemd_path(build_dir: &str) { 302 let path = format!("{build_dir}/CMakeCache.txt"); 303 let f = BufReader::new(std::fs::File::open(&path).unwrap()); 304 let mut libdir: Option<String> = None; 305 let mut libname: Option<String> = None; 306 for l in f.lines() { 307 let l = l.unwrap(); 308 if let Some(s) = trim_start(&l, "SYSTEMD_LIBDIR:INTERNAL=").filter(|s| !s.is_empty()) { 309 libdir = Some(s.to_owned()); 310 if libname.is_some() { 311 break; 312 } 313 } else if let Some(s) = 314 trim_start(&l, "SYSTEMD_LIBRARIES:INTERNAL=").filter(|s| !s.is_empty()) 315 { 316 libname = Some(s.to_owned()); 317 if libdir.is_some() { 318 break; 319 } 320 } 321 } 322 if let (Some(libdir), Some(libname)) = (libdir, libname) { 323 println!("cargo:rustc-link-search=native={}", libdir); 324 println!("cargo:rustc-link-lib={}", libname); 325 } 326 } 327 figure_ssl_path(build_dir: &str)328 fn figure_ssl_path(build_dir: &str) { 329 let path = format!("{build_dir}/CMakeCache.txt"); 330 let f = BufReader::new(std::fs::File::open(&path).unwrap()); 331 let mut cnt = 0; 332 for l in f.lines() { 333 let l = l.unwrap(); 334 let t = trim_start(&l, "OPENSSL_CRYPTO_LIBRARY:FILEPATH=") 335 .or_else(|| trim_start(&l, "OPENSSL_SSL_LIBRARY:FILEPATH=")); 336 if let Some(s) = t { 337 let path = Path::new(s); 338 println!( 339 "cargo:rustc-link-search=native={}", 340 path.parent().unwrap().display() 341 ); 342 cnt += 1; 343 } 344 } 345 if cnt != 2 { 346 panic!( 347 "CMake cache invalid, file {} contains {} ssl keys!", 348 path, cnt 349 ); 350 } 351 println!("cargo:rustc-link-lib=ssl"); 352 println!("cargo:rustc-link-lib=crypto"); 353 } 354 355 #[cfg(feature = "boringssl")] build_boringssl(config: &mut CmakeConfig)356 fn build_boringssl(config: &mut CmakeConfig) { 357 let boringssl_artifact = boringssl_src::Build::new().build(); 358 config.define( 359 "OPENSSL_ROOT_DIR", 360 format!("{}", boringssl_artifact.root_dir().display()), 361 ); 362 // To avoid linking system library, set lib path explicitly. 363 println!( 364 "cargo:rustc-link-search=native={}", 365 boringssl_artifact.lib_dir().display() 366 ); 367 } 368 setup_libz(config: &mut CmakeConfig)369 fn setup_libz(config: &mut CmakeConfig) { 370 config.define("gRPC_ZLIB_PROVIDER", "package"); 371 config.register_dep("Z"); 372 // cmake script expect libz.a being under ${DEP_Z_ROOT}/lib, but libz-sys crate put it 373 // under ${DEP_Z_ROOT}/build. Append the path to CMAKE_PREFIX_PATH to get around it. 374 let zlib_root = env::var("DEP_Z_ROOT").unwrap(); 375 let prefix_path = if let Ok(prefix_path) = env::var("CMAKE_PREFIX_PATH") { 376 format!("{prefix_path};{zlib_root}/build") 377 } else { 378 format!("{zlib_root}/build") 379 }; 380 // To avoid linking system library, set lib path explicitly. 381 println!("cargo:rustc-link-search=native={zlib_root}/build"); 382 println!("cargo:rustc-link-search=native={zlib_root}/lib"); 383 env::set_var("CMAKE_PREFIX_PATH", prefix_path); 384 } 385 get_env(name: &str) -> Option<String>386 fn get_env(name: &str) -> Option<String> { 387 println!("cargo:rerun-if-env-changed={name}"); 388 match env::var(name) { 389 Ok(s) => Some(s), 390 Err(VarError::NotPresent) => None, 391 Err(VarError::NotUnicode(s)) => { 392 panic!("unrecognize env var of {name}: {:?}", s.to_string_lossy()); 393 } 394 } 395 } 396 397 // Generate the bindings to grpc C-core. 398 // Try to disable the generation of platform-related bindings. 399 #[cfg(any( 400 feature = "_gen-bindings", 401 not(all( 402 any(target_os = "linux", target_os = "macos"), 403 any(target_arch = "x86_64", target_arch = "aarch64") 404 )) 405 ))] bindgen_grpc(file_path: &Path)406 fn bindgen_grpc(file_path: &Path) { 407 // create a config to generate binding file 408 let mut config = bindgen::Builder::default(); 409 if cfg!(feature = "_secure") { 410 config = config.clang_arg("-DGRPC_SYS_SECURE"); 411 } 412 413 if get_env("CARGO_CFG_TARGET_OS").map_or(false, |s| s == "windows") { 414 config = config.clang_arg("-D _WIN32_WINNT=0x600"); 415 } 416 417 // Search header files with API interface 418 let mut headers = Vec::new(); 419 for result in WalkDir::new(Path::new("./grpc/include")) { 420 let dent = result.expect("Error happened when search headers"); 421 if !dent.file_type().is_file() { 422 continue; 423 } 424 let mut file = fs::File::open(dent.path()).expect("couldn't open headers"); 425 let mut buf = String::new(); 426 file.read_to_string(&mut buf) 427 .expect("Coundn't read header content"); 428 if buf.contains("GRPCAPI") || buf.contains("GPRAPI") { 429 headers.push(String::from(dent.path().to_str().unwrap())); 430 } 431 } 432 433 // To control the order of bindings 434 headers.sort(); 435 for path in headers { 436 config = config.header(path); 437 } 438 439 println!("cargo:rerun-if-env-changed=TEST_BIND"); 440 let gen_tests = env::var("TEST_BIND").map_or(false, |s| s == "1"); 441 442 let cfg = config 443 .header("grpc_wrap.cc") 444 .clang_arg("-xc++") 445 .clang_arg("-I./grpc/include") 446 .clang_arg("-std=c++11") 447 .rustfmt_bindings(true) 448 .impl_debug(true) 449 .size_t_is_usize(true) 450 .disable_header_comment() 451 .allowlist_function(r"\bgrpc_.*") 452 .allowlist_function(r"\bgpr_.*") 453 .allowlist_function(r"\bgrpcwrap_.*") 454 .allowlist_var(r"\bGRPC_.*") 455 .allowlist_type(r"\bgrpc_.*") 456 .allowlist_type(r"\bgpr_.*") 457 .allowlist_type(r"\bgrpcwrap_.*") 458 .allowlist_type(r"\bcensus_context.*") 459 .allowlist_type(r"\bverify_peer_options.*") 460 // Block all system headers. 461 .blocklist_file(r"^/.*") 462 .blocklist_function(r"\bgpr_mu_.*") 463 .blocklist_function(r"\bgpr_cv_.*") 464 .blocklist_function(r"\bgpr_once_.*") 465 .blocklist_type(r"gpr_mu") 466 .blocklist_type(r"gpr_cv") 467 .blocklist_type(r"gpr_once") 468 .constified_enum_module(r"grpc_status_code") 469 .layout_tests(gen_tests) 470 .default_enum_style(bindgen::EnumVariation::Rust { 471 non_exhaustive: false, 472 }); 473 println!("running {}", cfg.command_line_flags().join(" ")); 474 cfg.generate() 475 .expect("Unable to generate grpc bindings") 476 .write_to_file(file_path) 477 .expect("Couldn't write bindings!"); 478 } 479 480 // Determine if need to update bindings. Supported platforms do not 481 // need to be updated by default unless the _gen-bindings feature is specified. 482 // Other platforms use bindgen to generate the bindings every time. config_binding_path()483 fn config_binding_path() { 484 let target = env::var("TARGET").unwrap(); 485 let file_path: PathBuf = match target.as_str() { 486 "x86_64-unknown-linux-gnu" 487 | "x86_64-unknown-linux-musl" 488 | "aarch64-unknown-linux-musl" 489 | "aarch64-unknown-linux-gnu" 490 | "x86_64-apple-darwin" 491 | "aarch64-apple-darwin" => { 492 // Cargo treats nonexistent files changed, so we only emit the rerun-if-changed 493 // directive when we expect the target-specific pre-generated binding file to be 494 // present. 495 println!("cargo:rerun-if-changed=bindings/bindings.rs"); 496 497 PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()) 498 .join("bindings") 499 .join("bindings.rs") 500 } 501 _ => PathBuf::from(env::var("OUT_DIR").unwrap()).join("grpc-bindings.rs"), 502 }; 503 504 #[cfg(any( 505 feature = "_gen-bindings", 506 not(all( 507 any(target_os = "linux", target_os = "macos"), 508 any(target_arch = "x86_64", target_arch = "aarch64") 509 )) 510 ))] 511 { 512 // On some system (like Windows), stack size of main thread may 513 // be too small. 514 let f = file_path.clone(); 515 std::thread::Builder::new() 516 .stack_size(8 * 1024 * 1024) 517 .name("bindgen_grpc".to_string()) 518 .spawn(move || bindgen_grpc(&f)) 519 .unwrap() 520 .join() 521 .unwrap(); 522 } 523 524 println!( 525 "cargo:rustc-env=BINDING_PATH={}", 526 file_path.to_str().unwrap() 527 ); 528 } 529 main()530 fn main() { 531 println!("cargo:rerun-if-changed=grpc_wrap.cc"); 532 println!("cargo:rerun-if-changed=grpc"); 533 534 // create a builder to compile grpc_wrap.cc 535 let mut cc = cc::Build::new(); 536 537 let library = if cfg!(feature = "_secure") { 538 cc.define("GRPC_SYS_SECURE", None); 539 "grpc" 540 } else { 541 "grpc_unsecure" 542 }; 543 544 if get_env("CARGO_CFG_TARGET_OS").map_or(false, |s| s == "windows") { 545 // At lease vista 546 cc.define("_WIN32_WINNT", Some("0x600")); 547 } 548 549 if get_env("GRPCIO_SYS_USE_PKG_CONFIG").map_or(false, |s| s == "1") { 550 // Print cargo metadata. 551 let lib_core = probe_library(library, true); 552 for inc_path in lib_core.include_paths { 553 cc.include(inc_path); 554 } 555 } else { 556 build_grpc(&mut cc, library); 557 } 558 559 cc.cpp(true); 560 if !cfg!(target_env = "msvc") { 561 cc.flag("-std=c++11"); 562 } 563 cc.file("grpc_wrap.cc"); 564 cc.warnings_into_errors(true); 565 cc.compile("libgrpc_wrap.a"); 566 567 config_binding_path(); 568 } 569