#![allow(unused)]
use std::{collections::BTreeMap, env::var, path::PathBuf, process::*};
fn main() {
if std::env::var("DOCS_RS").is_ok() {
return;
}
println!("starting ckia_sys build script");
for e in [
"SKIA_CC",
"SKIA_CC_WRAPPER",
"SKIA_CXX",
"SKIA_CLANG_WIN",
"SKIA_CLANG_WIN_VERSION",
"SKIA_BUILD_FROM_SRC",
"SKIA_GN_ARGS",
"SKIA_COPY_LIBS",
"SKIA_SRC_DIR",
"SKIA_SRC_ARCHIVE_URL",
] {
println!("cargo:rerun-if-env-changed={e}");
}
let build_from_src = std::env::var("SKIA_BUILD_FROM_SRC")
.map(|s| s != "0")
.unwrap_or_default()
|| cfg!(feature = "build_from_src");
let is_component_build = cfg!(feature = "is_component_build");
let use_system_libs = cfg!(feature = "use_system_libs");
let lib_type = if is_component_build {
"shared"
} else {
"static"
};
let out_dir = var("OUT_DIR").expect("failed to get out_dir");
let triple = var("TARGET").expect("failed to get build target triple");
let zip_name = format!("{lib_type}_{triple}.tar.gz");
let zip_path = format!("{out_dir}/zip_name");
let major: u32 = var("CARGO_PKG_VERSION_MAJOR").unwrap().parse().unwrap();
let minor: u32 = var("CARGO_PKG_VERSION_MINOR").unwrap().parse().unwrap();
let tag = format!("v{major}.{minor}");
println!("triple: {triple}\ntag: {tag}\nout_dir: {out_dir}");
if !build_from_src {
println!("downloading binaries");
download(
&format!("https://github.com/coderedart/ckia_sys/releases/download/{tag}/{zip_name}"),
&zip_path,
)
.expect("failed to download binaries");
assert!(
Command::new("tar")
.current_dir(&out_dir)
.arg("-xzvf")
.arg(&zip_path)
.status()
.expect("failed to extract skia binaries")
.success(),
"tar command failed to extract skia binaries"
);
let mut skia_downloaded_libs = Vec::new();
for f in std::fs::read_dir(&out_dir).unwrap() {
let f = f.unwrap();
if f.file_type().unwrap().is_dir() {
continue;
}
let name = f.file_name().to_str().unwrap().to_string();
if name.ends_with(".lib")
|| name.ends_with(".a")
|| name.ends_with(".so")
|| name.ends_with(".dll")
{
skia_downloaded_libs.push(name);
}
}
for lib in skia_downloaded_libs {
#[cfg(not(windows))]
let lib = lib.trim_start_matches("lib");
if lib.ends_with(".lib") || lib.ends_with(".a") {
let lib = lib.trim_end_matches(".lib");
let lib = lib.trim_end_matches(".a");
println!("cargo:rustc-link-lib=static={lib}");
continue;
}
if lib.ends_with(".dll") || lib.ends_with(".so") {
let lib = lib.trim_end_matches(".dll");
let lib = lib.trim_end_matches(".so");
println!("cargo:rustc-link-lib=dylib={lib}");
continue;
}
panic!("library without an extension name {lib}");
}
} else {
let skia_dir = var("SKIA_SRC_DIR").unwrap_or_else(|e| {
println!("failed to get SKIA_SRC_DIR. trying to clone skia src");
let skia_src_url = var("SKIA_SRC_ARCHIVE_URL").unwrap_or_else(|e| {
println!("failed to get SKIA_SRC_ARCHIVE_URL. using default skia url");
format!("https://github.com/coderedart/skia/archive/refs/heads/ckia/{major}.tar.gz")
});
let skia_src_dir_name = "skia_src";
let skia_src_path = format!("{out_dir}/{skia_src_dir_name}");
let skia_src_tar_name = format!("{skia_src_dir_name}.tar.gz");
let skia_src_tar_path = format!("{out_dir}/{skia_src_tar_name}");
download(&skia_src_url, &skia_src_tar_path).unwrap();
std::fs::create_dir_all(&skia_src_path).unwrap();
assert!(
Command::new("tar")
.current_dir(&out_dir)
.arg("--strip-components=1") .arg("-xvzf")
.arg(&skia_src_tar_path)
.arg("-C")
.arg(&skia_src_path)
.status()
.unwrap()
.success(),
"failed to extract skia src from archive"
);
skia_src_path
});
println!("running git-sync-deps");
assert!(
Command::new("python")
.current_dir(&skia_dir)
.args(&["tools/git-sync-deps"])
.env("GIT_SYNC_DEPS_SKIP_EMSDK", "True")
.env("GIT_SYNC_DEPS_SHALLOW_CLONE", "True")
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.status()
.unwrap()
.success(),
"Cannot download skia depenedencies"
);
let cc = var("SKIA_CC").unwrap_or_else(|e| {
println!("failed to get SKIA_CC: {e}");
if Command::new("clang")
.arg("--version")
.status()
.map(|s| s.success())
.unwrap_or_default()
{
println!("found clang. using it as cc");
return "clang".to_string();
}
println!("couldn't find clang. using default cc");
String::new()
});
let cc_wrapper: String = var("SKIA_CC_WRAPPER").unwrap_or_else(|e| {
println!("failed to get SKIA_CC_WRAPPER: {e}.");
for wrapper in ["sccache", "ccache"] {
if Command::new(wrapper)
.arg("--version")
.status()
.map(|s| s.success())
.unwrap_or_default()
{
println!("found {wrapper}. using it as cc_wrapper");
return wrapper.to_string();
}
}
println!("failed to find ccache or sccache. continuing without cc_wrapper");
String::new()
});
let cxx: String = var("SKIA_CXX").unwrap_or_else(|e| {
println!("failed to get SKIA_CXX: {e}");
if Command::new("clang++")
.arg("--version")
.status()
.map(|s| s.success())
.unwrap_or_default()
{
println!("found clang++. using it as cxx");
return "clang++".to_string();
}
println!("couldn't find clang++. using default cxx");
String::new()
});
let clang_win: String = var("SKIA_CLANG_WIN").unwrap_or_else(|e| {
#[cfg(windows)]
{
println!("couldn't get SKIA_CLANG_WIN: {e}");
if std::path::Path::new("C:\\Program Files\\LLVM").try_exists().unwrap_or_default() {
println!("found C:\\Program Files\\LLVM. using it as clang_win");
return "C:\\Program Files\\LLVM".to_string();
}
println!("couldn't find C:\\Program Files\\LLVM. using empty clang_win. compilation could fail");
}
String::new()
});
let clang_win_version: String = var("SKIA_CLANG_WIN_VERSION").unwrap_or_else(|e| {
#[cfg(windows)]
{
println!("couldn't get SKIA_CLANG_WIN_VERSION: {e}");
if !clang_win.is_empty() {
println!("reading {clang_win}/lib/clang for clang versions");
let mut max_version = [0, 0, 0];
let mut max_version_dir_name = "".to_string();
if let Ok(dir_iter) = std::fs::read_dir(format!("{clang_win}\\lib\\clang")) {
for f in dir_iter {
let name = f.unwrap().file_name().to_str().unwrap().to_string();
println!("found {name}");
let mut version = [0u32; 3];
let mut parts = name.split('.');
version.fill_with(|| {
parts.next().unwrap_or_default().parse().unwrap_or_default()
});
println!("parsed version: {version:?}");
if version > max_version {
max_version = version;
max_version_dir_name = name;
}
}
if max_version[0] != 0 {
println!("setting clang_win_version to {max_version_dir_name}");
return max_version_dir_name;
} else {
eprintln!("failed ot find a clang version in {clang_win}. build might fail");
}
} else {
eprintln!("failed to read {clang_win}/lib/clang to get clang version. build might fail");
}
} }
String::new()
});
let is_official_build = var("SKIA_IS_OFFICIAL_BUILD").unwrap_or_default() != "debug";
let is_component_build = cfg!(feature = "is_component_build");
let mut args = String::new();
if !cc.is_empty() {
args.push_str(&format!("cc=\"{}\"", cc));
args.push('\n');
}
if !cc_wrapper.is_empty() {
args.push_str(&format!("cc_wrapper=\"{}\"", cc_wrapper));
args.push('\n');
}
if !cxx.is_empty() {
args.push_str(&format!("cxx=\"{}\"", cxx));
args.push('\n');
}
#[cfg(windows)]
if !clang_win.is_empty() {
args.push_str(&format!("clang_win=\"{}\"", clang_win));
args.push('\n');
}
#[cfg(windows)]
if !clang_win_version.is_empty() {
args.push_str(&format!("clang_win_version=\"{}\"", clang_win_version));
args.push('\n');
}
args.push_str("is_official_build=");
args.push_str(if is_official_build {
"true\n"
} else {
"false\n"
});
args.push_str("is_component_build=");
args.push_str(if is_component_build {
"true\n"
} else {
"false\n"
});
if !use_system_libs {
println!("not using system libs");
for arg in [
"skia_use_system_expat=false",
"skia_use_system_freetype2=false",
"skia_use_system_harfbuzz=false",
"skia_use_system_icu=false",
"skia_use_system_libjpeg_turbo=false",
"skia_use_system_libpng=false",
"skia_use_system_libwebp=false",
"skia_use_system_zlib=false",
] {
args.push_str(arg);
args.push('\n');
}
}
if let Ok(extra_args) = var("SKIA_GN_ARGS") {
println!("found SKIA_GN_ARGS var");
for arg in extra_args.split(';') {
println!("adding arg: {arg}");
args.push_str(arg);
args.push('\n');
}
}
println!("gn args:\n{args}");
std::fs::write(&format!("{out_dir}/args.gn"), &args).expect("failed to write gn args");
let gn_path = format!("{skia_dir}/bin/gn");
println!("running gn gen command: {gn_path}");
assert!(
Command::new(&gn_path)
.current_dir(&skia_dir)
.args(&["gen", &out_dir,])
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.status()
.expect("failed to run gn command")
.success(),
"Cannot generate build files with gn gen"
);
assert!(
Command::new(&gn_path)
.current_dir(&skia_dir)
.args(&["args", "--list", "--short", &out_dir,])
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.status()
.expect("failed to run gn args -list")
.success(),
"failed to get list of gn args"
);
let output = Command::new(&gn_path)
.current_dir(&skia_dir)
.args(["desc", &out_dir, "//:skia", "libs"].into_iter())
.output()
.expect("failed to run gn desc libs ");
assert!(
output.status.success(),
"failed to get libs with gn desc {}",
String::from_utf8(output.stderr).unwrap()
);
let output = String::from_utf8(output.stdout).unwrap();
let mut skia_needs_libs: Vec<String> = output.lines().map(|s| s.to_string()).collect();
println!("running ninja");
assert!(
Command::new("ninja")
.current_dir(&skia_dir)
.args(&["-C", &out_dir])
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.status()
.expect("failed to run ninja build command")
.success(),
"Cannot build skia with ninja"
);
let mut skia_built_libs = Vec::new();
for f in std::fs::read_dir(&out_dir).unwrap() {
let f = f.unwrap();
if f.file_type().unwrap().is_dir() {
continue;
}
let name = f.file_name().to_str().unwrap().to_string();
if name.ends_with(".lib")
|| name.ends_with(".a")
|| name.ends_with(".so")
|| name.ends_with(".dll")
{
skia_built_libs.push(name);
}
}
println!("skia_built_libs: {skia_built_libs:#?}");
println!("skia_needs_libs: {skia_needs_libs:#?}");
for lib in skia_built_libs {
#[cfg(not(windows))]
let lib = lib.trim_start_matches("lib");
if lib.ends_with(".lib") || lib.ends_with(".a") {
let lib = lib.trim_end_matches(".lib");
let lib = lib.trim_end_matches(".a");
println!("cargo:rustc-link-lib=static={lib}");
if let Some(pos) = skia_needs_libs.iter().position(|s| {
let s = s.trim_end_matches(".lib");
let s = s.trim_end_matches(".a");
s == lib
}) {
skia_needs_libs.swap_remove(pos);
}
continue;
}
if lib.ends_with(".dll") || lib.ends_with(".so") {
let lib = lib.trim_end_matches(".dll");
let lib = lib.trim_end_matches(".so");
println!("cargo:rustc-link-lib=dylib={lib}");
if let Some(pos) = skia_needs_libs.iter().position(|s| {
let s = s.trim_end_matches(".dll");
let s = s.trim_end_matches(".so");
s == lib
}) {
skia_needs_libs.swap_remove(pos);
}
continue;
}
panic!("library without an extension name {lib}");
}
let remaining_libs = skia_needs_libs;
println!("remaining_libs: {remaining_libs:#?}");
}
println!("cargo:rustc-link-search={out_dir}");
if let Ok(p) = var("SKIA_COPY_LIBS") {
println!("copying libs to {p}");
let mut files_to_archive = vec![];
for f in std::fs::read_dir(&out_dir).unwrap() {
let f = f.unwrap().file_name().to_str().unwrap().to_string();
if f.ends_with(".lib")
|| f.ends_with(".dat")
|| f.ends_with(".a")
|| f.ends_with(".dll")
|| f.ends_with(".so")
{
files_to_archive.push(f);
}
}
assert!(
Command::new("tar")
.current_dir(&out_dir)
.arg("-czvf")
.arg(&zip_name)
.args(files_to_archive)
.status()
.expect("failed to run tar command")
.success(),
"failed to archive libs"
);
std::fs::copy(format!("{out_dir}/{zip_name}"), format!("{p}/{zip_name}"))
.expect("failed to copy archive");
}
}
fn download(url: &String, output_file_path: &String) -> Result<(), String> {
println!("using curl to download {url} and save to {output_file_path}");
if std::process::Command::new("curl")
.arg("-L")
.arg("-f")
.arg(url)
.arg("-o")
.arg(output_file_path)
.status()
.map_err(|e| format!("failed to run curl command {e:?}"))?
.success()
{
Ok(())
} else {
Err(format!("curl command failed"))
}
}