Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
178 changes: 165 additions & 13 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
@@ -1,26 +1,178 @@
name: Rust
name: CI

on:
push:
branches: [ main ]
branches: [main]
pull_request:
branches: [ main ]
branches: [main]
workflow_dispatch:

env:
CARGO_TERM_COLOR: always

jobs:
# ──────────────────────────────────────────────
# Lint: rustfmt + clippy
# ──────────────────────────────────────────────
lint:
name: Lint
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
with:
submodules: recursive

- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy

- uses: Swatinem/rust-cache@v2

- name: Check formatting
run: cargo fmt --check

- name: Clippy
run: cargo clippy -- -D warnings

# ──────────────────────────────────────────────
# Native build + test matrix
# 6 runners × 2 profiles = 12 jobs
# ──────────────────────────────────────────────
build:
name: ${{ matrix.name }} (${{ matrix.profile }})
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
# Linux x64
- name: Linux x64
runner: ubuntu-24.04
target: x86_64-unknown-linux-gnu
profile: debug
- name: Linux x64
runner: ubuntu-24.04
target: x86_64-unknown-linux-gnu
profile: release

# Linux arm64
- name: Linux arm64
runner: ubuntu-24.04-arm
target: aarch64-unknown-linux-gnu
profile: debug
- name: Linux arm64
runner: ubuntu-24.04-arm
target: aarch64-unknown-linux-gnu
profile: release

# Windows x64
- name: Windows x64
runner: windows-latest
target: x86_64-pc-windows-msvc
profile: debug
- name: Windows x64
runner: windows-latest
target: x86_64-pc-windows-msvc
profile: release

# Windows arm64
- name: Windows arm64
runner: windows-11-arm
target: aarch64-pc-windows-msvc
profile: debug
- name: Windows arm64
runner: windows-11-arm
target: aarch64-pc-windows-msvc
profile: release

# macOS x64 (Intel)
- name: macOS x64
runner: macos-15-intel
target: x86_64-apple-darwin
profile: debug
- name: macOS x64
runner: macos-15-intel
target: x86_64-apple-darwin
profile: release

# macOS arm64 (Apple Silicon)
- name: macOS arm64
runner: macos-15
target: aarch64-apple-darwin
profile: debug
- name: macOS arm64
runner: macos-15
target: aarch64-apple-darwin
profile: release

steps:
- uses: actions/checkout@v4
with:
submodules: recursive

- uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}

- uses: Swatinem/rust-cache@v2
with:
key: ${{ matrix.target }}-${{ matrix.profile }}

# Linux: install libclang for bindgen
- name: Install libclang (Linux)
if: runner.os == 'Linux'
run: sudo apt-get update && sudo apt-get install -y libclang-dev

- name: Build
run: cargo build --target ${{ matrix.target }} --verbose ${{ matrix.profile == 'release' && '--release' || '' }}

- name: Test
run: cargo test --target ${{ matrix.target }} --verbose ${{ matrix.profile == 'release' && '--release' || '' }}

runs-on: ubuntu-latest
- name: Build KCP native test
run: |
cmake -S kcp -B kcp/build -DBUILD_TESTING=ON
cmake --build kcp/build --target kcp_test

# ──────────────────────────────────────────────
# Cross-compilation via zigbuild (musl targets)
# 2 targets × 2 profiles = 4 jobs
# ──────────────────────────────────────────────
cross:
name: Cross ${{ matrix.target }} (${{ matrix.profile }})
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
include:
- target: aarch64-unknown-linux-musl
profile: debug
- target: aarch64-unknown-linux-musl
profile: release
- target: x86_64-unknown-linux-musl
profile: debug
- target: x86_64-unknown-linux-musl
profile: release

steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: recursive

- name: Build
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose
- uses: actions/checkout@v4
with:
submodules: recursive

- uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}

- uses: mlugg/setup-zig@v2

- uses: Swatinem/rust-cache@v2
with:
key: cross-${{ matrix.target }}-${{ matrix.profile }}

- name: Install libclang and cargo-zigbuild
run: |
sudo apt-get update && sudo apt-get install -y libclang-dev
cargo install cargo-zigbuild

- name: Build (zigbuild)
run: cargo zigbuild --target ${{ matrix.target }} --verbose ${{ matrix.profile == 'release' && '--release' || '' }}
18 changes: 10 additions & 8 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,22 @@ license = "MIT"
keywords = ["kcp", "bindings"]

[dependencies]
anyhow = "1.0.95"
auto_impl = "1.2.1"
bitflags = "2.8.0"
bytes = "1"
tokio = { version = "1", features = ["full"] }
tokio-util = { version = "0.7.13" }
dashmap = "6.1.0"
zerocopy = { version = "0.7", features = ["derive", "simd"] }
bitflags = "2.8.0"
parking_lot = "0.12.3"
auto_impl = "1.2.1"
rand = "0.8.5"
thiserror = "2.0.11"
anyhow = "1.0.95"
tokio = { version = "1", features = ["full"] }
tokio-util = { version = "0.7.13" }
tracing = "0.1.41"
tracing-subscriber = "0.3.19"
rand = "0.8.5"
zerocopy = { version = "0.7", features = ["derive", "simd"] }

[build-dependencies]
bindgen = { version="0.72.1", default-features=false, features=["runtime"]}
bindgen = { version = "0.72.1", default-features = false, features = [
"runtime",
] }
cc = "1.2.10"
152 changes: 136 additions & 16 deletions build.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,144 @@
use std::env;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};

fn zig_target(target: &str) -> String {
let mut parts = target.split('-');
let arch = parts.next().unwrap_or(target);
let _vendor = parts.next();
let rest = parts.collect::<Vec<_>>();

if rest.is_empty() {
target.to_owned()
} else {
format!("{arch}-{}", rest.join("-"))
}
}

fn zig_include_dirs(target: &str) -> Option<Vec<String>> {
let output = Command::new("zig")
.args([
"cc",
&format!("--target={}", zig_target(target)),
"-E",
"-x",
"c",
"-",
"-v",
])
.stdin(Stdio::null())
.output()
.ok()?;

if !output.status.success() {
return None;
}

let stderr = String::from_utf8(output.stderr).ok()?;
let start = stderr.find("#include <...> search starts here:")?;
let end = stderr[start..].find("End of search list.")? + start;
let include_block = &stderr[start..end];

let include_dirs = include_block
.lines()
.skip(1)
.map(str::trim)
.filter(|line| !line.is_empty() && !line.starts_with("#include"))
.map(ToOwned::to_owned)
.collect::<Vec<_>>();

Some(include_dirs)
}

fn is_zigbuild(target: &str) -> bool {
let cc_key = format!("CC_{}", target.replace('-', "_"));
if let Ok(cc) = env::var(&cc_key) {
return cc.contains("zigcc");
}
if let Ok(cc) = env::var("CC") {
return cc.contains("zigcc");
}
false
}

fn generate_bindings(target: &str) {
let extra_header_path = env::var("KCP_SYS_EXTRA_HEADER_PATH").unwrap_or_default();
let extra_header_paths = extra_header_path
.split(':')
.filter(|s| !s.is_empty())
.map(|p| format!("-I{p}"));

let mut bindings = bindgen::Builder::default()
.header("wrapper.h")
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
.allowlist_function("ikcp_.*")
.use_core();

bindings = bindings.clang_args(extra_header_paths);

let host = env::var("HOST").unwrap();
if target != host {
if is_zigbuild(target) {
bindings = bindings.clang_arg(format!("--target={}", zig_target(target)));

if let Some(include_dirs) = zig_include_dirs(target) {
for include_dir in include_dirs {
bindings = bindings.clang_arg("-isystem").clang_arg(include_dir);
}
}
} else {
bindings = bindings.clang_arg(format!("--target={target}"));
}
}

let bindings = bindings.generate().expect("Unable to generate bindings");

let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("bindings.rs");
bindings
.write_to_file(out_path)
.expect("Couldn't write bindings!");
}

fn find_llvm_ar() -> Option<PathBuf> {
// ARM Mac (Apple Silicon)
let arm_path = PathBuf::from("/opt/homebrew/opt/llvm/bin/llvm-ar");
if arm_path.exists() {
return Some(arm_path);
}
// Intel Mac
let intel_path = PathBuf::from("/usr/local/opt/llvm/bin/llvm-ar");
if intel_path.exists() {
return Some(intel_path);
}
// Fallback: check PATH (MacPorts, Nix, manual installs, etc.)
Command::new("which")
.arg("llvm-ar")
.output()
.ok()
.filter(|o| o.status.success())
.and_then(|o| {
let path = String::from_utf8(o.stdout).ok()?;
let path = path.trim();
if path.is_empty() {
None
} else {
Some(PathBuf::from(path))
}
})
}

fn main() {
println!("cargo:rustc-link-lib=kcp");
let dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let fulldir = Path::new(&dir).join("kcp");
let target = env::var("TARGET").unwrap();

let mut config = cc::Build::new();
if target.contains("apple-darwin") {
if let Some(llvm_ar) = find_llvm_ar() {
config.archiver(llvm_ar);
}
}
config.include(fulldir.clone());
config.file(fulldir.join("ikcp.c"));
config.opt_level(3);
Expand All @@ -17,21 +149,9 @@ fn main() {
println!("cargo:rerun-if-changed=kcp/ikcp.h");
println!("cargo:rerun-if-changed=kcp/ikcp.c");
println!("cargo:rerun-if-changed=wrapper.h");
println!("cargo:rerun-if-env-changed=KCP_SYS_EXTRA_HEADER_PATH");
println!("cargo:rerun-if-env-changed=CC_{}", target.replace('-', "_"));
println!("cargo:rerun-if-env-changed=CC");

let extra_header_path = std::env::var("KCP_SYS_EXTRA_HEADER_PATH").unwrap_or_default();
let extra_header_paths = extra_header_path.split(":").filter(|s| !s.is_empty()).collect::<Vec<_>>();

let bindings = bindgen::Builder::default()
.header("wrapper.h")
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
.clang_args(extra_header_paths.iter().map(|p| format!("-I{}", p)))
.allowlist_function("ikcp_.*")
.use_core()
.generate()
.expect("Unable to generate bindings");

let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
bindings
.write_to_file(out_path.join("bindings.rs"))
.expect("Couldn't write bindings!");
generate_bindings(&target);
}
2 changes: 1 addition & 1 deletion kcp
Submodule kcp updated 2 files
+1 −1 CMakeLists.txt
+34 −19 README.en.md
Loading
Loading