lio-uring 0.3.1

Production-ready, safe, and ergonomic Rust interface to Linux io_uring
Documentation
fn main() {
  let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default();
  if target_os != "linux" {
    // Skip build on non-Linux - the crate won't be usable but won't break workspace commands
    return;
  }

  build_linux();
}

fn copy_dir_recursive(src: &std::path::Path, dst: &std::path::Path) {
  use std::fs;
  fs::create_dir_all(dst).unwrap();
  for entry in fs::read_dir(src).unwrap() {
    let entry = entry.unwrap();
    let ty = entry.file_type().unwrap();
    let src_path = entry.path();
    let dst_path = dst.join(entry.file_name());
    if ty.is_dir() {
      copy_dir_recursive(&src_path, &dst_path);
    } else if ty.is_file() {
      fs::copy(&src_path, &dst_path).unwrap();
    }
    // Skip symlinks
  }
}

fn build_linux() {
  use std::{env, fs, path::PathBuf, process::Command};

  let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
  let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());

  // Copy liburing to OUT_DIR so configure doesn't modify source directory
  let liburing_src = manifest_dir.join("liburing");
  let libdir_path = out_dir.join("liburing");

  // Always fresh copy to pick up source changes
  if libdir_path.exists() {
    fs::remove_dir_all(&libdir_path).unwrap();
  }
  copy_dir_recursive(&liburing_src, &libdir_path);

  let headers_path = manifest_dir.join("include/liburing_wrapper.h");

  // Rerun if liburing sources change
  println!("cargo:rerun-if-changed={}", liburing_src.display());
  println!("cargo:rerun-if-changed={}", headers_path.display());

  // Run configure to generate necessary headers
  let result = Command::new("./configure")
    .current_dir(&libdir_path)
    .output()
    .expect("configure script failed");

  if !result.status.success() {
    panic!(
      "failed to configure: stdout:\n{}\n\nstderr:\n{}",
      String::from_utf8_lossy(&result.stdout),
      String::from_utf8_lossy(&result.stderr)
    );
  }

  // Compile the main liburing source files
  let src_dir = libdir_path.join("src");

  let sources = ["setup.c", "queue.c", "syscall.c", "register.c"];

  let mut obj_files = Vec::new();

  for source in &sources {
    let src_file = src_dir.join(source);
    let obj_file = out_dir.join(source.replace(".c", ".o"));

    let status = Command::new("clang")
      .arg("-c")
      .arg("-o")
      .arg(&obj_file)
      .arg(&src_file)
      .arg(format!("-I{}", src_dir.join("include").display()))
      .arg("-D_GNU_SOURCE")
      .arg("-Wno-unused-parameter")
      .status()
      .expect("Failed to compile liburing source file");

    if !status.success() {
      panic!("Failed to compile {}", source);
    }

    obj_files.push(obj_file);
  }

  // Create static library (.a file) from object files
  let lib_path = out_dir.join("liburing.a");
  let status = Command::new("ar")
    .arg("rcs")
    .arg(&lib_path)
    .args(&obj_files)
    .status()
    .expect("Failed to create static library");

  if !status.success() {
    panic!("Failed to create liburing.a");
  }

  println!("cargo:rustc-link-search=native={}", out_dir.display());
  println!("cargo:rustc-link-lib=static=uring");

  // The bindgen::Builder is the main entry point
  // to bindgen, and lets you build up options for
  // the resulting bindings.
  let bindings = bindgen::Builder::default()
    // The input header we would like to generate
    // bindings for.
    .header(headers_path.to_str().unwrap())
    // Add clang args to find the includes and define necessary macros
    .clang_arg(format!("-I{}", src_dir.join("include").display()))
    .clang_arg("-D_GNU_SOURCE")
    // Wrap static inline functions so they can be called from Rust
    .wrap_static_fns(true)
    .wrap_static_fns_path(out_dir.join("liburing_wrapper"))
    // Tell cargo to invalidate the built crate whenever any of the
    // included header files changed.
    .parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
    // Finish the builder and generate the bindings.
    .generate()
    // Unwrap the Result and panic on failure.
    .expect("Unable to generate bindings");

  // Write the bindings to the $OUT_DIR/bindings.rs file.
  let out_path =
    PathBuf::from(env::var("OUT_DIR").unwrap()).join("bindings.rs");
  bindings.write_to_file(out_path).expect("Couldn't write bindings!");

  // Compile the wrapper file generated by bindgen for static inline functions
  let wrapper_c = out_dir.join("liburing_wrapper.c");
  if wrapper_c.exists() {
    let wrapper_obj = out_dir.join("liburing_wrapper.o");
    let status = Command::new("clang")
      .arg("-c")
      .arg("-o")
      .arg(&wrapper_obj)
      .arg(&wrapper_c)
      .arg(format!("-I{}", manifest_dir.display()))
      .arg(format!("-I{}", src_dir.join("include").display()))
      .arg("-D_GNU_SOURCE")
      .arg("-Wno-unused-parameter")
      .status()
      .expect("Failed to compile wrapper file");

    if !status.success() {
      panic!("Failed to compile liburing_wrapper.c");
    }

    // Add wrapper to the static library
    let status = Command::new("ar")
      .arg("r")
      .arg(&lib_path)
      .arg(&wrapper_obj)
      .status()
      .expect("Failed to add wrapper to library");

    if !status.success() {
      panic!("Failed to add wrapper to liburing.a");
    }
  }
}