• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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::io::prelude::*;
6 use std::io::BufReader;
7 use std::path::{Path, PathBuf};
8 use std::{env, fs, io};
9 
10 use cmake::Config as CmakeConfig;
11 use pkg_config::{Config as PkgConfig, Library};
12 use walkdir::WalkDir;
13 
14 const GRPC_VERSION: &str = "1.35.0";
15 
probe_library(library: &str, cargo_metadata: bool) -> Library16 fn probe_library(library: &str, cargo_metadata: bool) -> Library {
17     match PkgConfig::new()
18         .atleast_version(GRPC_VERSION)
19         .cargo_metadata(cargo_metadata)
20         .probe(library)
21     {
22         Ok(lib) => lib,
23         Err(e) => panic!("can't find library {} via pkg-config: {:?}", library, e),
24     }
25 }
26 
prepare_grpc()27 fn prepare_grpc() {
28     let modules = vec![
29         "grpc",
30         "grpc/third_party/cares/cares",
31         "grpc/third_party/address_sorting",
32         "grpc/third_party/abseil-cpp",
33         "grpc/third_party/re2",
34     ];
35 
36     for module in modules {
37         if is_directory_empty(module).unwrap_or(true) {
38             panic!(
39                 "Can't find module {}. You need to run `git submodule \
40                  update --init --recursive` first to build the project.",
41                 module
42             );
43         }
44     }
45 }
46 
is_directory_empty<P: AsRef<Path>>(p: P) -> Result<bool, io::Error>47 fn is_directory_empty<P: AsRef<Path>>(p: P) -> Result<bool, io::Error> {
48     let mut entries = fs::read_dir(p)?;
49     Ok(entries.next().is_none())
50 }
51 
trim_start<'a>(s: &'a str, prefix: &str) -> Option<&'a str>52 fn trim_start<'a>(s: &'a str, prefix: &str) -> Option<&'a str> {
53     if s.starts_with(prefix) {
54         Some(s.trim_start_matches(prefix))
55     } else {
56         None
57     }
58 }
59 
60 /// If cache is stale, remove it to avoid compilation failure.
clean_up_stale_cache(cxx_compiler: String)61 fn clean_up_stale_cache(cxx_compiler: String) {
62     // We don't know the cmake output path before it's configured.
63     let build_dir = format!("{}/build", env::var("OUT_DIR").unwrap());
64     let path = format!("{}/CMakeCache.txt", build_dir);
65     let f = match std::fs::File::open(&path) {
66         Ok(f) => BufReader::new(f),
67         // It may be an empty directory.
68         Err(_) => return,
69     };
70     let cache_stale = f.lines().any(|l| {
71         let l = l.unwrap();
72         trim_start(&l, "CMAKE_CXX_COMPILER:").map_or(false, |s| {
73             let mut splits = s.splitn(2, '=');
74             splits.next();
75             splits.next().map_or(false, |p| p != cxx_compiler)
76         })
77     });
78     // CMake can't handle compiler change well, it will invalidate cache without respecting command
79     // line settings and result in configuration failure.
80     // See https://gitlab.kitware.com/cmake/cmake/-/issues/18959.
81     if cache_stale {
82         let _ = fs::remove_dir_all(&build_dir);
83     }
84 }
85 
build_grpc(cc: &mut cc::Build, library: &str)86 fn build_grpc(cc: &mut cc::Build, library: &str) {
87     prepare_grpc();
88 
89     let target = env::var("TARGET").unwrap();
90     let dst = {
91         let mut config = CmakeConfig::new("grpc");
92 
93         if get_env("CARGO_CFG_TARGET_OS").map_or(false, |s| s == "macos") {
94             config.cxxflag("-stdlib=libc++");
95         }
96 
97         // Ensure CoreFoundation be found in macos or ios
98         if get_env("CARGO_CFG_TARGET_OS").map_or(false, |s| s == "macos")
99             || get_env("CARGO_CFG_TARGET_OS").map_or(false, |s| s == "ios")
100         {
101             println!("cargo:rustc-link-lib=framework=CoreFoundation");
102         }
103 
104         let cxx_compiler = if let Some(val) = get_env("CXX") {
105             config.define("CMAKE_CXX_COMPILER", val.clone());
106             val
107         } else if env::var("CARGO_CFG_TARGET_ENV").unwrap() == "musl" {
108             config.define("CMAKE_CXX_COMPILER", "g++");
109             "g++".to_owned()
110         } else {
111             format!("{}", cc.get_compiler().path().display())
112         };
113         clean_up_stale_cache(cxx_compiler);
114 
115         // Cross-compile support for iOS
116         match target.as_str() {
117             "aarch64-apple-ios" => {
118                 config
119                     .define("CMAKE_OSX_SYSROOT", "iphoneos")
120                     .define("CMAKE_OSX_ARCHITECTURES", "arm64");
121             }
122             "armv7-apple-ios" => {
123                 config
124                     .define("CMAKE_OSX_SYSROOT", "iphoneos")
125                     .define("CMAKE_OSX_ARCHITECTURES", "armv7");
126             }
127             "armv7s-apple-ios" => {
128                 config
129                     .define("CMAKE_OSX_SYSROOT", "iphoneos")
130                     .define("CMAKE_OSX_ARCHITECTURES", "armv7s");
131             }
132             "i386-apple-ios" => {
133                 config
134                     .define("CMAKE_OSX_SYSROOT", "iphonesimulator")
135                     .define("CMAKE_OSX_ARCHITECTURES", "i386");
136             }
137             "x86_64-apple-ios" => {
138                 config
139                     .define("CMAKE_OSX_SYSROOT", "iphonesimulator")
140                     .define("CMAKE_OSX_ARCHITECTURES", "x86_64");
141             }
142             _ => {}
143         };
144 
145         // Allow overriding of the target passed to cmake
146         // (needed for Android crosscompile)
147         if let Ok(val) = env::var("CMAKE_TARGET_OVERRIDE") {
148             config.target(&val);
149         }
150 
151         // We don't need to generate install targets.
152         config.define("gRPC_INSTALL", "false");
153         // We don't need to build csharp target.
154         config.define("gRPC_BUILD_CSHARP_EXT", "false");
155         // We don't need to build codegen target.
156         config.define("gRPC_BUILD_CODEGEN", "false");
157         // We don't need to build benchmarks.
158         config.define("gRPC_BENCHMARK_PROVIDER", "none");
159         config.define("gRPC_SSL_PROVIDER", "package");
160         if cfg!(feature = "openssl") {
161             if cfg!(feature = "openssl-vendored") {
162                 config.register_dep("openssl");
163             }
164         } else {
165             build_boringssl(&mut config);
166         }
167         if cfg!(feature = "no-omit-frame-pointer") {
168             config
169                 .cflag("-fno-omit-frame-pointer")
170                 .cxxflag("-fno-omit-frame-pointer");
171         }
172         // Uses zlib from libz-sys.
173         setup_libz(&mut config);
174         config.build_target(library).uses_cxx11().build()
175     };
176 
177     let lib_suffix = if target.contains("msvc") {
178         ".lib"
179     } else {
180         ".a"
181     };
182     let build_dir = format!("{}/build", dst.display());
183     for e in WalkDir::new(&build_dir) {
184         let e = e.unwrap();
185         if e.file_name().to_string_lossy().ends_with(lib_suffix) {
186             println!(
187                 "cargo:rustc-link-search=native={}",
188                 e.path().parent().unwrap().display()
189             );
190         }
191     }
192 
193     let collect = |path, to: &mut HashSet<_>| {
194         let f = fs::File::open(format!("{}/libs/opt/pkgconfig/{}.pc", build_dir, path)).unwrap();
195         for l in io::BufReader::new(f).lines() {
196             let l = l.unwrap();
197             if l.starts_with("Libs: ") {
198                 for lib in l.split_whitespace() {
199                     if let Some(s) = lib.strip_prefix("-l") {
200                         to.insert(s.to_string());
201                     }
202                 }
203             }
204         }
205     };
206     let mut libs = HashSet::new();
207     collect("gpr", &mut libs);
208     collect(library, &mut libs);
209     for l in libs {
210         println!("cargo:rustc-link-lib=static={}", l);
211     }
212 
213     if cfg!(feature = "secure") {
214         if cfg!(feature = "openssl") && !cfg!(feature = "openssl-vendored") {
215             figure_ssl_path(&build_dir);
216         } else {
217             println!("cargo:rustc-link-lib=static=ssl");
218             println!("cargo:rustc-link-lib=static=crypto");
219         }
220     } else {
221         // grpc_unsecure.pc is not accurate, see also grpc/grpc#24512.
222         println!("cargo:rustc-link-lib=static=upb");
223         println!("cargo:rustc-link-lib=static=cares");
224         println!("cargo:rustc-link-lib=static=z");
225         println!("cargo:rustc-link-lib=static=address_sorting");
226     }
227 
228     cc.include("grpc/include");
229 }
230 
figure_ssl_path(build_dir: &str)231 fn figure_ssl_path(build_dir: &str) {
232     let path = format!("{}/CMakeCache.txt", build_dir);
233     let f = BufReader::new(std::fs::File::open(&path).unwrap());
234     let mut cnt = 0;
235     for l in f.lines() {
236         let l = l.unwrap();
237         let t = trim_start(&l, "OPENSSL_CRYPTO_LIBRARY:FILEPATH=")
238             .or_else(|| trim_start(&l, "OPENSSL_SSL_LIBRARY:FILEPATH="));
239         if let Some(s) = t {
240             let path = Path::new(s);
241             println!(
242                 "cargo:rustc-link-search=native={}",
243                 path.parent().unwrap().display()
244             );
245             cnt += 1;
246         }
247     }
248     if cnt != 2 {
249         panic!(
250             "CMake cache invalid, file {} contains {} ssl keys!",
251             path, cnt
252         );
253     }
254     println!("cargo:rustc-link-lib=ssl");
255     println!("cargo:rustc-link-lib=crypto");
256 }
257 
build_boringssl(config: &mut CmakeConfig)258 fn build_boringssl(config: &mut CmakeConfig) {
259     let boringssl_artifact = boringssl_src::Build::new().build();
260     config.define(
261         "OPENSSL_ROOT_DIR",
262         format!("{}", boringssl_artifact.root_dir().display()),
263     );
264     // To avoid linking system library, set lib path explicitly.
265     println!(
266         "cargo:rustc-link-search=native={}",
267         boringssl_artifact.lib_dir().display()
268     );
269 }
270 
setup_libz(config: &mut CmakeConfig)271 fn setup_libz(config: &mut CmakeConfig) {
272     config.define("gRPC_ZLIB_PROVIDER", "package");
273     config.register_dep("Z");
274     // cmake script expect libz.a being under ${DEP_Z_ROOT}/lib, but libz-sys crate put it
275     // under ${DEP_Z_ROOT}/build. Append the path to CMAKE_PREFIX_PATH to get around it.
276     let zlib_root = env::var("DEP_Z_ROOT").unwrap();
277     let prefix_path = if let Ok(prefix_path) = env::var("CMAKE_PREFIX_PATH") {
278         format!("{};{}/build", prefix_path, zlib_root)
279     } else {
280         format!("{}/build", zlib_root)
281     };
282     // To avoid linking system library, set lib path explicitly.
283     println!("cargo:rustc-link-search=native={}/build", zlib_root);
284     println!("cargo:rustc-link-search=native={}/lib", zlib_root);
285     env::set_var("CMAKE_PREFIX_PATH", prefix_path);
286 }
287 
get_env(name: &str) -> Option<String>288 fn get_env(name: &str) -> Option<String> {
289     println!("cargo:rerun-if-env-changed={}", name);
290     match env::var(name) {
291         Ok(s) => Some(s),
292         Err(VarError::NotPresent) => None,
293         Err(VarError::NotUnicode(s)) => {
294             panic!("unrecognize env var of {}: {:?}", name, s.to_string_lossy());
295         }
296     }
297 }
298 
299 // Generate the bindings to grpc C-core.
300 // Try to disable the generation of platform-related bindings.
301 #[cfg(feature = "use-bindgen")]
bindgen_grpc(file_path: &PathBuf)302 fn bindgen_grpc(file_path: &PathBuf) {
303     // create a config to generate binding file
304     let mut config = bindgen::Builder::default();
305     if cfg!(feature = "secure") {
306         config = config.clang_arg("-DGRPC_SYS_SECURE");
307     }
308 
309     if get_env("CARGO_CFG_TARGET_OS").map_or(false, |s| s == "windows") {
310         config = config.clang_arg("-D _WIN32_WINNT=0x600");
311     }
312 
313     // Search header files with API interface
314     let mut headers = Vec::new();
315     for result in WalkDir::new(Path::new("./grpc/include")) {
316         let dent = result.expect("Error happened when search headers");
317         if !dent.file_type().is_file() {
318             continue;
319         }
320         let mut file = fs::File::open(dent.path()).expect("couldn't open headers");
321         let mut buf = String::new();
322         file.read_to_string(&mut buf)
323             .expect("Coundn't read header content");
324         if buf.contains("GRPCAPI") || buf.contains("GPRAPI") {
325             headers.push(String::from(dent.path().to_str().unwrap()));
326         }
327     }
328 
329     // To control the order of bindings
330     headers.sort();
331     for path in headers {
332         config = config.header(path);
333     }
334 
335     println!("cargo:rerun-if-env-changed=TEST_BIND");
336     let gen_tests = env::var("TEST_BIND").map_or(false, |s| s == "1");
337 
338     let cfg = config
339         .header("grpc_wrap.cc")
340         .clang_arg("-xc++")
341         .clang_arg("-I./grpc/include")
342         .clang_arg("-std=c++11")
343         .rustfmt_bindings(true)
344         .impl_debug(true)
345         .size_t_is_usize(true)
346         .disable_header_comment()
347         .whitelist_function(r"\bgrpc_.*")
348         .whitelist_function(r"\bgpr_.*")
349         .whitelist_function(r"\bgrpcwrap_.*")
350         .whitelist_var(r"\bGRPC_.*")
351         .whitelist_type(r"\bgrpc_.*")
352         .whitelist_type(r"\bgpr_.*")
353         .whitelist_type(r"\bgrpcwrap_.*")
354         .whitelist_type(r"\bcensus_context.*")
355         .whitelist_type(r"\bverify_peer_options.*")
356         .blacklist_type(r"(__)?pthread.*")
357         .blacklist_function(r"\bgpr_mu_.*")
358         .blacklist_function(r"\bgpr_cv_.*")
359         .blacklist_function(r"\bgpr_once_.*")
360         .blacklist_type(r"gpr_mu")
361         .blacklist_type(r"gpr_cv")
362         .blacklist_type(r"gpr_once")
363         .constified_enum_module(r"grpc_status_code")
364         .layout_tests(gen_tests)
365         .default_enum_style(bindgen::EnumVariation::Rust {
366             non_exhaustive: false,
367         });
368     println!("running {}", cfg.command_line_flags().join(" "));
369     cfg.generate()
370         .expect("Unable to generate grpc bindings")
371         .write_to_file(file_path)
372         .expect("Couldn't write bindings!");
373 }
374 
375 // Determine if need to update bindings. Supported platforms do not
376 // need to be updated by default unless the UPDATE_BIND is specified.
377 // Other platforms use bindgen to generate the bindings every time.
config_binding_path()378 fn config_binding_path() {
379     let target = env::var("TARGET").unwrap();
380     let file_path: PathBuf = match target.as_str() {
381         "x86_64-unknown-linux-gnu" | "aarch64-unknown-linux-gnu" => {
382             // Cargo treats nonexistent files changed, so we only emit the rerun-if-changed
383             // directive when we expect the target-specific pre-generated binding file to be
384             // present.
385             println!("cargo:rerun-if-changed=bindings/{}-bindings.rs", &target);
386 
387             let file_path = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap())
388                 .join("bindings")
389                 .join(format!("{}-bindings.rs", &target));
390 
391             #[cfg(feature = "use-bindgen")]
392             if env::var("UPDATE_BIND").is_ok() {
393                 bindgen_grpc(&file_path);
394             }
395 
396             file_path
397         }
398         _ => {
399             let file_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("grpc-bindings.rs");
400 
401             #[cfg(feature = "use-bindgen")]
402             bindgen_grpc(&file_path);
403 
404             file_path
405         }
406     };
407 
408     println!(
409         "cargo:rustc-env=BINDING_PATH={}",
410         file_path.to_str().unwrap()
411     );
412 }
413 
main()414 fn main() {
415     println!("cargo:rerun-if-changed=grpc_wrap.cc");
416     println!("cargo:rerun-if-changed=grpc");
417     println!("cargo:rerun-if-env-changed=UPDATE_BIND");
418 
419     // create a builder to compile grpc_wrap.cc
420     let mut cc = cc::Build::new();
421 
422     let library = if cfg!(feature = "secure") {
423         cc.define("GRPC_SYS_SECURE", None);
424         "grpc"
425     } else {
426         "grpc_unsecure"
427     };
428 
429     if get_env("CARGO_CFG_TARGET_OS").map_or(false, |s| s == "windows") {
430         // At lease vista
431         cc.define("_WIN32_WINNT", Some("0x600"));
432     }
433 
434     if get_env("GRPCIO_SYS_USE_PKG_CONFIG").map_or(false, |s| s == "1") {
435         // Print cargo metadata.
436         let lib_core = probe_library(library, true);
437         for inc_path in lib_core.include_paths {
438             cc.include(inc_path);
439         }
440     } else {
441         build_grpc(&mut cc, library);
442     }
443 
444     cc.cpp(true);
445     if !cfg!(target_env = "msvc") {
446         cc.flag("-std=c++11");
447     }
448     cc.file("grpc_wrap.cc");
449     cc.warnings_into_errors(true);
450     cc.compile("libgrpc_wrap.a");
451 
452     config_binding_path();
453 }
454