1 // Copyright 2019 TiKV Project Authors. Licensed under Apache-2.0.
2 
3 use std::env;
4 use std::env::VarError;
5 use std::fs;
6 use std::io::Read;
7 use std::path::{Path, PathBuf};
8 use walkdir::WalkDir;
9 
10 // Platform-Specific Instructions: Regenerating gRPC Bindings for Windows
11 
12 // If you need to manually update the 'android/grpc-bindings.rs' file on Windows,
13 // follow these steps:
14 
15 // 1. LLVM Setup
16 //    - Download and install the pre-built LLVM binaries from:
17 //      https://releases.llvm.org/download.html
18 //    - Update the `LIBCLANG_PATH` environment variable to point to the 'bin'
19 //      directory within your LLVM installation. (e.g., `C:\Program Files\LLVM\bin`)
20 
21 // 2. Environment Configuration
22 //    - Set the `GRPCIO_SYS_GRPC_INCLUDE_PATH` environment variable to the
23 //      location of your gRPC 'include' directory.
24 
25 // 3. Building the Bindings
26 //    - Run the following Cargo command to regenerate the bindings:
27 //      `cargo build --features _gen-bindings`
28 
29 include!("../link-deps.rs");
30 
get_env(name: &str) -> Option<String>31 fn get_env(name: &str) -> Option<String> {
32     println!("cargo:rerun-if-env-changed={name}");
33     match env::var(name) {
34         Ok(s) => Some(s),
35         Err(VarError::NotPresent) => None,
36         Err(VarError::NotUnicode(s)) => {
37             panic!("unrecognize env var of {name}: {:?}", s.to_string_lossy());
38         }
39     }
40 }
41 
42 // Generate the bindings to grpc C-core.
43 // Try to disable the generation of platform-related bindings.
44 #[cfg(any(
45     feature = "_gen-bindings",
46     not(all(
47         any(target_os = "linux", target_os = "macos"),
48         any(target_arch = "x86_64", target_arch = "aarch64")
49     ))
50 ))]
bindgen_grpc(file_path: &Path)51 fn bindgen_grpc(file_path: &Path) {
52     // create a config to generate binding file
53     let mut config = bindgen::Builder::default();
54     if cfg!(feature = "_secure") {
55         config = config.clang_arg("-DGRPC_SYS_SECURE");
56     }
57 
58     if get_env("CARGO_CFG_TARGET_OS").map_or(false, |s| s == "windows") {
59         config = config.clang_arg("-D _WIN32_WINNT=0x600");
60     }
61 
62     // Search header files with API interface
63     let mut headers = Vec::new();
64     for result in WalkDir::new(Path::new("./grpc/include")) {
65         let dent = result.expect("Error happened when search headers");
66         if !dent.file_type().is_file() {
67             continue;
68         }
69         let mut file = fs::File::open(dent.path()).expect("couldn't open headers");
70         let mut buf = String::new();
71         file.read_to_string(&mut buf)
72             .expect("Coundn't read header content");
73         if buf.contains("GRPCAPI") || buf.contains("GPRAPI") {
74             headers.push(String::from(dent.path().to_str().unwrap()));
75         }
76     }
77 
78     // To control the order of bindings
79     headers.sort();
80     for path in headers {
81         config = config.header(path);
82     }
83 
84     println!("cargo:rerun-if-env-changed=TEST_BIND");
85     let gen_tests = env::var("TEST_BIND").map_or(false, |s| s == "1");
86 
87     let cfg = config
88         .header("grpc_wrap.cc")
89         .clang_arg("-xc++")
90         .clang_arg("-I./grpc/include")
91         .clang_arg("-std=c++11")
92         .rustfmt_bindings(true)
93         .impl_debug(true)
94         .size_t_is_usize(true)
95         .disable_header_comment()
96         .allowlist_function(r"\bgrpc_.*")
97         .allowlist_function(r"\bgpr_.*")
98         .allowlist_function(r"\bgrpcwrap_.*")
99         .allowlist_var(r"\bGRPC_.*")
100         .allowlist_type(r"\bgrpc_.*")
101         .allowlist_type(r"\bgpr_.*")
102         .allowlist_type(r"\bgrpcwrap_.*")
103         .allowlist_type(r"\bcensus_context.*")
104         .allowlist_type(r"\bverify_peer_options.*")
105         // Block all system headers.
106         .blocklist_file(r"^/.*")
107         .blocklist_function(r"\bgpr_mu_.*")
108         .blocklist_function(r"\bgpr_cv_.*")
109         .blocklist_function(r"\bgpr_once_.*")
110         .blocklist_type(r"gpr_mu")
111         .blocklist_type(r"gpr_cv")
112         .blocklist_type(r"gpr_once")
113         .constified_enum_module(r"grpc_status_code")
114         .layout_tests(gen_tests)
115         .default_enum_style(bindgen::EnumVariation::Rust {
116             non_exhaustive: false,
117         });
118     println!("running {}", cfg.command_line_flags().join(" "));
119     cfg.generate()
120         .expect("Unable to generate grpc bindings")
121         .write_to_file(file_path)
122         .expect("Couldn't write bindings!");
123 }
124 
125 // Determine if need to update bindings. Supported platforms do not
126 // need to be updated by default unless the _gen-bindings feature is specified.
config_binding_path()127 fn config_binding_path() {
128     let target = env::var("TARGET").unwrap();
129     let file_path: PathBuf = match target.as_str() {
130         "x86_64-unknown-linux-gnu"
131         | "x86_64-unknown-linux-musl"
132         | "aarch64-unknown-linux-musl"
133         | "aarch64-unknown-linux-gnu"
134         | "x86_64-apple-darwin"
135         | "aarch64-apple-darwin" => {
136             // Cargo treats nonexistent files changed, so we only emit the rerun-if-changed
137             // directive when we expect the target-specific pre-generated binding file to be
138             // present.
139             println!("cargo:rerun-if-changed=bindings/bindings.rs");
140 
141             PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap())
142                 .join("bindings")
143                 .join("bindings.rs")
144         }
145         _ => PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap())
146             .join("android")
147             .join("grpc-bindings.rs"),
148     };
149     #[cfg(feature = "_gen-bindings")]
150     {
151         // On some system (like Windows), stack size of main thread may
152         // be too small.
153         let f = file_path.clone();
154         std::thread::Builder::new()
155             .stack_size(8 * 1024 * 1024)
156             .name("bindgen_grpc".to_string())
157             .spawn(move || bindgen_grpc(&f))
158             .unwrap()
159             .join()
160             .unwrap();
161     }
162 
163     println!(
164         "cargo:rustc-env=BINDING_PATH={}",
165         file_path.to_str().unwrap()
166     );
167 }
168 
main()169 fn main() {
170     println!("cargo:rerun-if-changed=grpc_wrap.cc");
171     println!("cargo:rerun-if-changed=grpc");
172 
173     // create a builder to compile grpc_wrap.cc
174     let mut cc = cc::Build::new();
175 
176     if get_env("CARGO_CFG_TARGET_OS").map_or(false, |s| s == "windows") {
177         // At lease vista
178         cc.define("_WIN32_WINNT", Some("0x600"));
179     }
180 
181     let mut include_paths: Vec<PathBuf> = Vec::new();
182     let include_path = get_env("GRPCIO_SYS_GRPC_INCLUDE_PATH");
183     if include_path.is_none() {
184         panic!("$GRPCIO_SYS_GRPC_INCLUDE_PATH is not set");
185     }
186     include_paths.push(include_path.unwrap().into());
187     for inc_path in include_paths {
188         cc.include(inc_path);
189     }
190 
191     cc.cpp(true);
192     if !cfg!(target_env = "msvc") {
193         cc.flag("-std=c++11");
194     }
195     cc.file("grpc_wrap.cc");
196     cc.warnings_into_errors(true);
197     cc.compile("libgrpc_wrap.a");
198 
199     config_binding_path();
200 }
201