use std::path::Path; use std::path::PathBuf; use std::{env, process}; use bindgen::CargoCallbacks; fn get_cargo_target_dir() -> Result> { let skip_triple = std::env::var("TARGET")? == std::env::var("HOST")?; let skip_parent_dirs = if skip_triple { 4 } else { 5 }; let out_dir = std::path::PathBuf::from(std::env::var("OUT_DIR")?); let mut current = out_dir.as_path(); for _ in 0..skip_parent_dirs { current = current.parent().ok_or("not found")?; } Ok(std::path::PathBuf::from(current)) } fn main() { // Find compiler: // 1. GOC // 2. /usr/local/go/bin/go // 3. system "go" let compiler = match env::var("GOC") { Ok(c) => c, Err(_) => { if Path::new("/usr/local/go/bin/go").exists() { "/usr/local/go/bin/go".to_string() } else { "go".to_string() } } }; println!("using go compiler {}", compiler); let c_compiler = cc::Build::new().try_get_compiler().unwrap(); let compile_config = get_compile_config(); let out_dir = env::var("OUT_DIR").unwrap(); let out_path = PathBuf::from(out_dir); let out_file = compile_config.lib_filename.clone(); let out = out_path.join(out_file); let mut command = process::Command::new(compiler); command.args([ "build", "-buildmode", compile_config.link_type.as_str(), "-o", out.display().to_string().as_str(), "main.go", ]); command.env("CGO_ENABLED", "1"); command.env("CC", c_compiler.path()); command.env("GOARCH", compile_config.goarch.clone()); command.env("GOOS", compile_config.goos.clone()); println!("running go compile command: {:?}", command); let mut child = command.spawn().unwrap(); let status = child.wait().unwrap(); println!("{}", status); if !status.success() { panic!("`{:?}` exited with status code {}", command, status); } println!("Go compile success"); println!( "cargo:rustc-link-search={}", env::var("OUT_DIR").unwrap() ); if compile_config.link_type == "c-shared" { copy_shared_lib(&compile_config); println!("cargo:rustc-link-lib=dylib=nebula"); } else { println!("cargo:rustc-link-lib=static=nebula"); } //let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); //println!("cargo:rustc-link-search={}", out_path.display()); println!("cargo:rerun-if-changed=go.mod"); println!("cargo:rerun-if-changed=go.sum"); println!("cargo:rerun-if-changed=main.go"); println!("Generating bindings"); let bindings = bindgen::Builder::default() .header(out_path.join(compile_config.header_filename).display().to_string()) .parse_callbacks(Box::new(CargoCallbacks::new())) .generate() .expect("Error generating CFFI bindings"); bindings .write_to_file(out_path.join("bindings.rs")) .expect("Couldn't write bindings!"); } fn copy_shared_lib(go_compile_config: &GoCompileConfig) { let target_dir = get_cargo_target_dir().unwrap(); let target_file = target_dir.join(go_compile_config.lib_filename.clone()); let out_dir = env::var("OUT_DIR").unwrap(); let out_path = PathBuf::from(out_dir); let out_file = go_compile_config.lib_filename.clone(); let out = out_path.join(out_file); std::fs::copy(out, target_file).unwrap(); } // Go build supported modes. // Pulled from golang/go/src/internal/platform/supported.go // c-archive: // aix, darwin, ios, windows // linux/386, linux/amd64, linux/arm, linux/armbe, linux/arm64, linux/arm64be, linux/loong64, linux/ppc64le, linux/riscv64, linux/s390x // freebsd/amd64 // c-shared: // linux/amd64, linux/arm, linux/arm64, linux/loong64, linux/386, linux/ppc64le, linux/riscv64, linux/s390x // android/amd64, android/arm, android/arm64, android/386 // freebsd/amd64 // darwin/amd64, darwin/arm64 // windows/amd64, windows/386, windows/arm64 struct GoCompileConfig { goarch: String, goos: String, link_type: String, lib_filename: String, header_filename: String } fn get_compile_config() -> GoCompileConfig { let goarch = goarch(); let goos = goos(); let platform_value = format!("{}/{}", goos, goarch); let (preferred_link_type, lib_filename, header_filename) = match (goos.as_str(), goarch.as_str()) { ("darwin", _) => ("c-archive", "libnebula.a", "libnebula.h"), ("windows", _) => ("c-archive", "libnebula.a", "libnebula.h"), ("linux", "386") | ("linux", "amd64") | ("linux", "arm") | ("linux", "armbe") | ("linux", "arm64") | ("linux", "arm64be") | ("linux", "loong64") | ("linux", "ppc64le") | ("linux", "riscv64") | ("linux", "s390x") => ("c-archive", "libnebula.a", "libnebula.h"), ("freebsd", "amd64") => ("c-archive", "libnebula.a", "libnebula.h"), _ => panic!("unsupported platform {} / {}", env::var("TARGET").unwrap(), platform_value) }; GoCompileConfig { goarch, goos, link_type: preferred_link_type.to_string(), lib_filename: lib_filename.to_string(), header_filename: header_filename.to_string() } } fn goarch() -> String { match env::var("CARGO_CFG_TARGET_ARCH").unwrap().as_str() { "x86" => "386", "x86_64" => "amd64", "mips" => "mips", "powerpc" => "ppc", "powerpc64" => "ppc64", "arm" => "arm", "aarch64" => "arm64", arch => panic!("unsupported architecture {arch}"), } .to_string() } fn goos() -> String { match env::var("CARGO_CFG_TARGET_OS").unwrap().as_str() { "windows" => "windows", "macos" => "darwin", "ios" => "darwin", "linux" => "linux", "android" => "android", "freebsd" => "freebsd", "dragonfly" => "dragonfly", "openbsd" => "openbsd", "netbsd" => "netbsd", os => panic!("unsupported operating system {os}"), } .to_string() }