#![allow(unused)] // for vars which are only used on windows, but not linux 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 manifest_dir: PathBuf = var("CARGO_MANIFEST_DIR") // .expect("failed to get manifest dir") // .into(); 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") // top directory inside archive would be something like skia-{branch} .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() }); // unless explicitly specified as debug build via the env var, we will use an official build for performance. 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"); // to avoid the whole dependency mess. 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(); // with extension names included let mut skia_needs_libs: Vec = 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" ); // with extension names 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") // follow redirects .arg("-L") // fail fast with no "error pages" output. more of a hint though, so we might still get error on stdout. // so make sure to check the actual status returned. .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")) } } /* ndk Current value (from the default) = "" From //gn/BUILDCONFIG.gn:13 ndk_api Current value (from the default) = 21 From //gn/BUILDCONFIG.gn:16 Android 5.0, Lollipop target_ar Current value (from the default) = "ar" From //gn/toolchain/BUILD.gn:33 target_cc Current value (from the default) = "clang" From //gn/toolchain/BUILD.gn:34 target_cpu Current value (from the default) = "" (Internally set; try `gn help target_cpu`.) target_cxx Current value (from the default) = "clang++" From //gn/toolchain/BUILD.gn:35 target_link Current value (from the default) = "clang++" From //gn/toolchain/BUILD.gn:55 */