dpdk-rs 1.1.6

Rust Bindings for Libdpdk
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

use anyhow::Result;
use bindgen::{
    Bindings,
    Builder,
};
use cc::Build;
use std::{
    env,
    path::Path,
};

#[cfg(target_os = "windows")]
fn os_build() -> Result<()> {
    use std::path::PathBuf;

    let out_dir_s: String = env::var("OUT_DIR")?;
    let out_dir: &Path = Path::new(&out_dir_s);

    let libdpdk_path: String = env::var("LIBDPDK_PATH")?;

    let include_path: String = format!("{}{}", libdpdk_path, "\\include");
    let library_path: String = format!("{}{}", libdpdk_path, "\\lib");

    let libraries: Vec<&str> = vec![
        "librte_bitratestats",
        "librte_bus_auxiliary",
        "librte_bus_pci",
        "librte_bus_vdev",
        "librte_cfgfile",
        "librte_cmdline",
        "librte_common_iavf",
        "librte_common_idpf",
        "librte_common_mlx5",
        "librte_cryptodev",
        "librte_crypto_mlx5",
        "librte_dmadev",
        "librte_dma_idxd",
        "librte_dma_ioat",
        "librte_dma_skeleton",
        "librte_eal",
        "librte_ethdev",
        "librte_gpudev",
        "librte_gro",
        "librte_gso",
        "librte_hash",
        "librte_kvargs",
        "librte_latencystats",
        "librte_mbuf",
        "librte_mempool",
        "librte_mempool_ring",
        "librte_mempool_stack",
        "librte_meter",
        "librte_metrics",
        "librte_net",
        "librte_net_e1000",
        "librte_net_i40e",
        "librte_net_iavf",
        "librte_net_ice",
        "librte_net_ixgbe",
        "librte_net_mlx5",
        "librte_net_octeon_ep",
        "librte_net_vmxnet3",
        "librte_pci",
        "librte_rcu",
        "librte_ring",
        "librte_security",
        "librte_stack",
        "librte_telemetry",
        "librte_timer",
        "mlx5devx",
    ];

    let cflags: &str = "-mavx";

    // Step 1: Now that we've compiled and installed DPDK, point cargo to the libraries.
    println!("cargo:rustc-link-search={}", library_path);

    for lib in &libraries {
        println!("cargo:rustc-link-lib=static:-bundle,+whole-archive={}", lib);
    }

    println!("cargo:rustc-link-lib=dylib={}", "setupapi");
    println!("cargo:rustc-link-lib=dylib={}", "dbghelp");
    println!("cargo:rustc-link-lib=dylib={}", "mincore");

    // Step 2: Generate bindings for the DPDK headers.
    let bindings: Bindings = Builder::default()
        .clang_arg(&format!("-I{}", include_path))
        .allowlist_recursively(true)
        .allowlist_function("rte_auxiliary_register")
        .allowlist_function("rte_delay_us_block")
        .allowlist_function("rte_eal_init")
        .allowlist_function("rte_eth_conf")
        .allowlist_function("rte_eth_dev_configure")
        .allowlist_function("rte_eth_dev_count_avail")
        .allowlist_function("rte_eth_dev_flow_ctrl_get")
        .allowlist_function("rte_eth_dev_flow_ctrl_set")
        .allowlist_function("rte_eth_dev_get_mtu")
        .allowlist_function("rte_eth_dev_info_get")
        .allowlist_function("rte_eth_dev_is_valid_port")
        .allowlist_function("rte_eth_dev_set_mtu")
        .allowlist_function("rte_eth_dev_socket_id")
        .allowlist_function("rte_eth_dev_start")
        .allowlist_function("rte_eth_find_next_owned_by")
        .allowlist_function("rte_eth_link_get_nowait")
        .allowlist_function("rte_eth_macaddr_get")
        .allowlist_function("rte_eth_promiscuous_enable")
        .allowlist_function("rte_eth_rx_burst")
        .allowlist_function("rte_eth_rx_queue_setup")
        .allowlist_function("rte_eth_tx_burst")
        .allowlist_function("rte_eth_tx_queue_setup")
        .allowlist_function("rte_mempool_avail_count")
        .allowlist_function("rte_mempool_create_empty")
        .allowlist_function("rte_mempool_free")
        .allowlist_function("rte_mempool_in_use_count")
        .allowlist_function("rte_mempool_mem_iter")
        .allowlist_function("rte_mempool_obj_iter")
        .allowlist_function("rte_mempool_populate_default")
        .allowlist_function("rte_pktmbuf_clone")
        .allowlist_function("rte_pktmbuf_init")
        .allowlist_function("rte_pktmbuf_pool_create")
        .allowlist_function("rte_pktmbuf_pool_init")
        .allowlist_function("rte_pktmbuf_prepend")
        .allowlist_function("rte_socket_id")
        .allowlist_function("rte_strerror")
        .allowlist_type("rte_eth_fc_conf")
        .allowlist_type("rte_eth_rxconf")
        .allowlist_type("rte_eth_txconf")
        .allowlist_type("rte_ether_addr")
        .allowlist_type("rte_mbuf")
        .allowlist_type("rte_mempool")
        .allowlist_type("rte_pktmbuf_pool_private")
        .allowlist_var("RTE_ETH_DEV_NO_OWNER")
        .allowlist_var("RTE_ETH_LINK_FULL_DUPLEX")
        .allowlist_var("RTE_ETH_LINK_UP")
        .allowlist_var("RTE_ETH_MQ_RX_RSS")
        .allowlist_var("RTE_ETH_MQ_TX_NONE")
        .allowlist_var("RTE_ETH_RSS_IP")
        .allowlist_var("RTE_ETH_RX_OFFLOAD_IPV4_CKSUM")
        .allowlist_var("RTE_ETH_RX_OFFLOAD_TCP_CKSUM")
        .allowlist_var("RTE_ETH_RX_OFFLOAD_UDP_CKSUM")
        .allowlist_var("RTE_ETH_TX_OFFLOAD_MULTI_SEGS")
        .allowlist_var("RTE_ETH_TX_OFFLOAD_TCP_CKSUM")
        .allowlist_var("RTE_ETH_TX_OFFLOAD_UDP_CKSUM")
        .allowlist_var("RTE_ETHER_MAX_JUMBO_FRAME_LEN")
        .allowlist_var("RTE_ETHER_MAX_JUMBO_FRAME")
        .allowlist_var("RTE_ETHER_MAX_LEN")
        .allowlist_var("RTE_MAX_ETHPORTS")
        .allowlist_var("RTE_MBUF_DEFAULT_BUF_SIZE")
        .allowlist_var("RTE_PKTMBUF_HEADROOM")
        .clang_arg(cflags)
        .header("wrapper.h")
        .parse_callbacks(Box::new(bindgen::CargoCallbacks))
        .generate_comments(false)
        .generate()?;
    let bindings_out: PathBuf = out_dir.join("bindings.rs");
    bindings.write_to_file(bindings_out)?;

    // Step 3: Compile a stub file so Rust can access `inline` functions in the headers
    // that aren't compiled into the libraries.
    let mut builder: Build = cc::Build::new();
    builder.opt_level(3);
    builder.flag("-march=native");
    builder.file("inlined.c");
    builder.include(include_path);
    builder.compile("inlined");

    Ok(())
}

#[cfg(target_os = "linux")]
fn os_build() -> Result<()> {
    use std::process::Command;

    let out_dir_s = env::var("OUT_DIR").unwrap();
    let out_dir = Path::new(&out_dir_s);

    println!("cargo:rerun-if-env-changed=PKG_CONFIG_PATH");
    let cflags_bytes = Command::new("pkg-config")
        .args(&["--cflags", "libdpdk"])
        .output()
        .unwrap_or_else(|e| panic!("Failed pkg-config cflags: {:?}", e))
        .stdout;
    let mut cflags = String::from_utf8(cflags_bytes).unwrap();

    if cflags.is_empty() {
        cflags = env::var("CFLAGS").unwrap_or_else(|_| String::from(""));
        if cflags.is_empty() {
            panic!("CFLAGS is empty or not set");
        }
    }

    let mut header_locations = vec![];

    for flag in cflags.split(' ') {
        if flag.starts_with("-I") {
            let header_location = flag[2..].trim();
            header_locations.push(header_location);
        }
    }

    let ldflags_bytes = Command::new("pkg-config")
        .args(&["--libs", "libdpdk"])
        .output()
        .unwrap_or_else(|e| panic!("Failed pkg-config ldflags: {:?}", e))
        .stdout;
    let ldflags = String::from_utf8(ldflags_bytes).unwrap();

    let mut library_location = None;
    let mut lib_names = vec![];

    for flag in ldflags.split(' ') {
        if flag.starts_with("-L") {
            library_location = Some(&flag[2..]);
        } else if flag.starts_with("-l") {
            lib_names.push(&flag[2..]);
        }
    }

    // Link in `librte_net_mlx5` and its dependencies if desired.
    #[cfg(feature = "mlx5")]
    {
        lib_names.extend(&["rte_net_mlx5", "rte_bus_pci", "rte_bus_vdev", "rte_common_mlx5"]);
    }

    // Step 1: Now that we've compiled and installed DPDK, point cargo to the libraries.
    if let Some(location) = library_location {
        println!("cargo:rustc-link-search=native={}", location);
    }

    for lib_name in &lib_names {
        println!("cargo:rustc-link-lib=dylib={}", lib_name);
    }

    // Step 2: Generate bindings for the DPDK headers.
    let mut builder: Builder = Builder::default();
    for header_location in &header_locations {
        builder = builder.clang_arg(&format!("-I{}", header_location));
    }
    let bindings: Bindings = builder
        .allowlist_recursively(true)
        .allowlist_function("rte_auxiliary_register")
        .allowlist_function("rte_delay_us_block")
        .allowlist_function("rte_eal_init")
        .allowlist_function("rte_eth_conf")
        .allowlist_function("rte_eth_dev_configure")
        .allowlist_function("rte_eth_dev_count_avail")
        .allowlist_function("rte_eth_dev_flow_ctrl_get")
        .allowlist_function("rte_eth_dev_flow_ctrl_set")
        .allowlist_function("rte_eth_dev_get_mtu")
        .allowlist_function("rte_eth_dev_info_get")
        .allowlist_function("rte_eth_dev_is_valid_port")
        .allowlist_function("rte_eth_dev_set_mtu")
        .allowlist_function("rte_eth_dev_socket_id")
        .allowlist_function("rte_eth_dev_start")
        .allowlist_function("rte_eth_find_next_owned_by")
        .allowlist_function("rte_eth_link_get_nowait")
        .allowlist_function("rte_eth_macaddr_get")
        .allowlist_function("rte_eth_promiscuous_enable")
        .allowlist_function("rte_eth_rx_burst")
        .allowlist_function("rte_eth_rx_queue_setup")
        .allowlist_function("rte_eth_tx_burst")
        .allowlist_function("rte_eth_tx_queue_setup")
        .allowlist_function("rte_mempool_avail_count")
        .allowlist_function("rte_mempool_create_empty")
        .allowlist_function("rte_mempool_free")
        .allowlist_function("rte_mempool_in_use_count")
        .allowlist_function("rte_mempool_mem_iter")
        .allowlist_function("rte_mempool_obj_iter")
        .allowlist_function("rte_mempool_populate_default")
        .allowlist_function("rte_pktmbuf_clone")
        .allowlist_function("rte_pktmbuf_init")
        .allowlist_function("rte_pktmbuf_pool_create")
        .allowlist_function("rte_pktmbuf_pool_init")
        .allowlist_function("rte_pktmbuf_prepend")
        .allowlist_function("rte_socket_id")
        .allowlist_function("rte_strerror")
        .allowlist_type("rte_eth_fc_conf")
        .allowlist_type("rte_eth_rxconf")
        .allowlist_type("rte_eth_txconf")
        .allowlist_type("rte_ether_addr")
        .allowlist_type("rte_mbuf")
        .allowlist_type("rte_mempool")
        .allowlist_type("rte_pktmbuf_pool_private")
        .allowlist_var("RTE_ETH_DEV_NO_OWNER")
        .allowlist_var("RTE_ETH_LINK_FULL_DUPLEX")
        .allowlist_var("RTE_ETH_LINK_UP")
        .allowlist_var("RTE_ETH_MQ_RX_RSS")
        .allowlist_var("RTE_ETH_MQ_TX_NONE")
        .allowlist_var("RTE_ETH_RSS_IP")
        .allowlist_var("RTE_ETH_RX_OFFLOAD_IPV4_CKSUM")
        .allowlist_var("RTE_ETH_RX_OFFLOAD_TCP_CKSUM")
        .allowlist_var("RTE_ETH_RX_OFFLOAD_UDP_CKSUM")
        .allowlist_var("RTE_ETH_TX_OFFLOAD_MULTI_SEGS")
        .allowlist_var("RTE_ETH_TX_OFFLOAD_TCP_CKSUM")
        .allowlist_var("RTE_ETH_TX_OFFLOAD_UDP_CKSUM")
        .allowlist_var("RTE_ETHER_MAX_JUMBO_FRAME_LEN")
        .allowlist_var("RTE_ETHER_MAX_JUMBO_FRAME")
        .allowlist_var("RTE_ETHER_MAX_LEN")
        .allowlist_var("RTE_MAX_ETHPORTS")
        .allowlist_var("RTE_MBUF_DEFAULT_BUF_SIZE")
        .allowlist_var("RTE_PKTMBUF_HEADROOM")
        .clang_arg("-mavx")
        .header("wrapper.h")
        .parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
        .generate_comments(false)
        .generate()
        .unwrap_or_else(|e| panic!("Failed to generate bindings: {:?}", e));
    let bindings_out = out_dir.join("bindings.rs");
    bindings.write_to_file(bindings_out).expect("Failed to write bindings");

    // Step 3: Compile a stub file so Rust can access `inline` functions in the headers
    // that aren't compiled into the libraries.
    let mut builder: Build = cc::Build::new();
    builder.opt_level(3);
    builder.pic(true);
    builder.flag("-march=native");
    builder.file("inlined.c");
    for header_location in &header_locations {
        builder.include(header_location);
    }
    builder.compile("inlined");
    Ok(())
}

fn main() {
    match os_build() {
        Ok(()) => {},
        Err(e) => panic!("Failed to generate bindings: {:?}", e),
    }
}