use std::env;
use std::path::PathBuf;
use std::process::Command;
fn main() {
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
let project_root = manifest_dir.join("../..").canonicalize()
.expect("Failed to find project root");
let lib_path = build_libcc(&out_dir, &project_root);
let lib_dir = lib_path.parent().unwrap();
println!("cargo:rustc-link-search=native={}", lib_dir.display());
println!("cargo:rustc-link-lib=static=cc");
#[cfg(target_os = "macos")]
{
println!("cargo:rustc-link-lib=framework=CoreFoundation");
println!("cargo:rustc-link-lib=framework=Security");
println!("cargo:rustc-link-lib=resolv");
}
#[cfg(target_os = "linux")]
{
println!("cargo:rustc-link-lib=pthread");
println!("cargo:rustc-link-lib=dl");
println!("cargo:rustc-link-lib=resolv");
}
#[cfg(target_os = "windows")]
{
println!("cargo:rustc-link-lib=ws2_32");
println!("cargo:rustc-link-lib=advapi32");
println!("cargo:rustc-link-lib=ntdll");
println!("cargo:rustc-link-lib=userenv");
}
build_and_install_helper(&out_dir, &project_root);
let bindings_c_dir = manifest_dir.join("../c");
if bindings_c_dir.exists() {
emit_rerun_if_changed_go(&bindings_c_dir);
}
let internal_ipc_dir = project_root.join("internal/ipc");
if internal_ipc_dir.exists() {
emit_rerun_if_changed_go(&internal_ipc_dir);
}
let helper_dir = project_root.join("cmd/cc-helper");
if helper_dir.exists() {
println!("cargo:rerun-if-changed={}", helper_dir.display());
}
}
fn build_libcc(out_dir: &PathBuf, project_root: &PathBuf) -> PathBuf {
let bindings_c = project_root.join("bindings/c");
if !bindings_c.exists() {
panic!(
"Could not find Go source at {}. \
Make sure you're building from within the cc repository.",
bindings_c.display()
);
}
let target = env::var("TARGET").unwrap_or_default();
let lib_path = if target.contains("msvc") {
out_dir.join("cc.lib")
} else {
out_dir.join("libcc.a")
};
if !needs_rebuild(&lib_path, &bindings_c) {
return lib_path;
}
println!("cargo:warning=Building libcc.a from Go source...");
let status = Command::new("go")
.args([
"build",
"-buildmode=c-archive",
"-o",
lib_path.to_str().unwrap(),
"./bindings/c",
])
.current_dir(project_root)
.env("CGO_ENABLED", "1")
.status()
.expect("Failed to execute go build. Is Go installed?");
if !status.success() {
panic!(
"Failed to build libcc. go build exited with status: {}",
status
);
}
println!("cargo:warning=Successfully built {}", lib_path.display());
lib_path
}
fn build_and_install_helper(out_dir: &PathBuf, project_root: &PathBuf) {
let helper_name = if cfg!(target_os = "windows") {
"cc-helper.exe"
} else {
"cc-helper"
};
let helper_build_path = out_dir.join(helper_name);
let helper_src = project_root.join("cmd/cc-helper");
if !helper_src.exists() {
println!("cargo:warning=cc-helper source not found, skipping helper build");
return;
}
let needs_build = !helper_build_path.exists() || {
let helper_mtime = std::fs::metadata(&helper_build_path)
.and_then(|m| m.modified())
.ok();
helper_mtime.map_or(true, |build_time| {
walkdir_newer_than(&helper_src, build_time)
})
};
if needs_build {
println!("cargo:warning=Building cc-helper...");
let status = Command::new("go")
.args([
"build",
"-o",
helper_build_path.to_str().unwrap(),
"./cmd/cc-helper",
])
.current_dir(project_root)
.status();
match status {
Ok(s) if s.success() => {
println!("cargo:warning=Successfully built cc-helper");
#[cfg(target_os = "macos")]
{
let entitlements = project_root.join("tools/entitlements.xml");
if entitlements.exists() {
let sign_status = Command::new("codesign")
.args([
"--sign", "-",
"--entitlements", entitlements.to_str().unwrap(),
"--force",
helper_build_path.to_str().unwrap(),
])
.status();
match sign_status {
Ok(s) if s.success() => {
println!("cargo:warning=Codesigned cc-helper with entitlements");
}
_ => {
println!("cargo:warning=Failed to codesign cc-helper");
}
}
}
}
}
_ => {
println!("cargo:warning=Failed to build cc-helper");
return;
}
}
}
if let Some(profile_dir) = find_profile_dir(out_dir) {
let dest = profile_dir.join(helper_name);
if copy_if_different(&helper_build_path, &dest) {
println!("cargo:warning=Installed cc-helper to {}", dest.display());
}
let deps_dir = profile_dir.join("deps");
if deps_dir.exists() {
let dest = deps_dir.join(helper_name);
if copy_if_different(&helper_build_path, &dest) {
println!("cargo:warning=Installed cc-helper to {}", dest.display());
}
}
let examples_dir = profile_dir.join("examples");
if examples_dir.exists() {
let dest = examples_dir.join(helper_name);
if copy_if_different(&helper_build_path, &dest) {
println!("cargo:warning=Installed cc-helper to {}", dest.display());
}
}
}
}
fn needs_rebuild(lib_path: &PathBuf, bindings_c: &PathBuf) -> bool {
if !lib_path.exists() {
return true;
}
let lib_mtime = match std::fs::metadata(lib_path).and_then(|m| m.modified()) {
Ok(t) => t,
Err(_) => return true,
};
if walkdir_newer_than(bindings_c, lib_mtime) {
return true;
}
let internal_ipc = bindings_c.join("../../internal/ipc");
if internal_ipc.exists() && walkdir_newer_than(&internal_ipc, lib_mtime) {
return true;
}
false
}
fn walkdir_newer_than(dir: &PathBuf, threshold: std::time::SystemTime) -> bool {
if let Ok(entries) = std::fs::read_dir(dir) {
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
if walkdir_newer_than(&path, threshold) {
return true;
}
} else if path.extension().map_or(false, |e| e == "go") {
if let Ok(meta) = std::fs::metadata(&path) {
if let Ok(mtime) = meta.modified() {
if mtime > threshold {
return true;
}
}
}
}
}
}
false
}
fn emit_rerun_if_changed_go(dir: &PathBuf) {
if let Ok(entries) = std::fs::read_dir(dir) {
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
emit_rerun_if_changed_go(&path);
} else if path.extension().map_or(false, |e| e == "go") {
println!("cargo:rerun-if-changed={}", path.display());
}
}
}
}
fn find_profile_dir(out_dir: &PathBuf) -> Option<PathBuf> {
let hash_dir = out_dir.parent()?; let build_dir = hash_dir.parent()?; let profile_dir = build_dir.parent()?;
if build_dir.file_name().map_or(false, |n| n == "build") {
Some(profile_dir.to_path_buf())
} else {
None
}
}
fn copy_if_different(src: &PathBuf, dest: &PathBuf) -> bool {
if let (Ok(src_meta), Ok(dest_meta)) = (std::fs::metadata(src), std::fs::metadata(dest)) {
if src_meta.len() == dest_meta.len() {
return false;
}
}
if let Err(e) = std::fs::copy(src, dest) {
println!("cargo:warning=Failed to copy cc-helper: {}", e);
return false;
}
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
if let Ok(meta) = std::fs::metadata(dest) {
let mut perms = meta.permissions();
perms.set_mode(0o755);
let _ = std::fs::set_permissions(dest, perms);
}
}
true
}