//! The build module is used to build the project using the enabled features. //! The features are correctly set when exactly one of the following features is enabled: //! - `local-tdlib` //! - `pkg-config` //! - `download-tdlib` #[allow(dead_code)] #[cfg(not(any(feature = "docs", feature = "pkg-config")))] const TDLIB_VERSION: &str = "1.8.29"; #[cfg(feature = "download-tdlib")] const TDLIB_CARGO_PKG_VERSION: &str = "1.2.0"; // WARNING: This function is not used in the current version of the library. // #[cfg(not(any(feature = "docs", feature = "pkg-config", feature = "download-tdlib")))] // fn copy_local_tdlib() { // match std::env::var("LOCAL_TDLIB_PATH") { // Ok(tdlib_path) => { // let out_dir = std::env::var("OUT_DIR").unwrap(); // let prefix = format!("{}/tdlib", out_dir); // copy_dir_all(std::path::Path::new(&tdlib_path), std::path::Path::new(&prefix)).unwrap(); // } // Err(_) => { // panic!("The LOCAL_TDLIB_PATH env variable must be set to the path of the tdlib folder"); // } // }; // } #[cfg(feature = "download-tdlib")] /// Copy all files from a directory to another. /// It assumes that the source directory exists. /// If the destination directory does not exist, it will be created. fn copy_dir_all( src: impl AsRef, dst: impl AsRef, ) -> std::io::Result<()> { std::fs::create_dir_all(&dst)?; for entry in std::fs::read_dir(src)? { let entry = entry?; let ty = entry.file_type()?; if ty.is_dir() { copy_dir_all(entry.path(), dst.as_ref().join(entry.file_name()))?; } else { std::fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?; } } Ok(()) } #[cfg(feature = "download-tdlib")] /// Download the tdlib library from the GitHub release page. /// The function will download the tdlib library from the GitHub release page, and extract the /// files in the OUT_DIR/tdlib folder. /// The OUT_DIR environment variable is set by Cargo and points to the target directory. /// The OS and architecture currently supported are: /// - Linux x86_64 /// - Linux aarch64 /// - Windows x86_64 /// - Windows aarch64 /// - MacOS x86_64 /// - MacOS aarch64 /// /// If the OS or architecture is not supported, the function will panic. fn download_tdlib() { let base_url = "https://github.com/FedericoBruzzone/tdlib-rs/releases/download"; let url = format!( "{}/v{}/tdlib-{}-{}-{}.zip", base_url, TDLIB_CARGO_PKG_VERSION, TDLIB_VERSION, std::env::var("CARGO_CFG_TARGET_OS").unwrap(), std::env::var("CARGO_CFG_TARGET_ARCH").unwrap(), ); let out_dir = std::env::var("OUT_DIR").unwrap(); let tdlib_dir = format!("{}/tdlib", &out_dir); let zip_path = format!("{}.zip", &tdlib_dir); // Create the request let response = reqwest::blocking::get(&url).unwrap(); // Check if the response status is successful if response.status().is_success() { // Create a file to write to let mut dest = std::fs::File::create(&zip_path).unwrap(); // Get the response bytes and write to the file let content = response.bytes().unwrap(); std::io::copy(&mut content.as_ref(), &mut dest).unwrap(); } else { panic!( "[{}] Failed to download file: {}\n{}\n{}", "Your OS or architecture may be unsupported.", "Please try using the `pkg-config` or `local-tdlib` features.", response.status(), &url ) } let mut archive = zip::ZipArchive::new(std::fs::File::open(&zip_path).unwrap()).unwrap(); for i in 0..archive.len() { let mut file = archive.by_index(i).unwrap(); let outpath = std::path::Path::new(&out_dir).join(file.name()); if (*file.name()).ends_with('/') { std::fs::create_dir_all(&outpath).unwrap(); } else { if let Some(p) = outpath.parent() { if !p.exists() { std::fs::create_dir_all(p).unwrap(); } } let mut outfile = std::fs::File::create(&outpath).unwrap(); std::io::copy(&mut file, &mut outfile).unwrap(); } // Get and set permissions #[cfg(unix)] { use std::os::unix::fs::PermissionsExt; if let Some(mode) = file.unix_mode() { std::fs::set_permissions(&outpath, std::fs::Permissions::from_mode(mode)).unwrap(); } } } let _ = std::fs::remove_file(&zip_path); } #[cfg(any(feature = "download-tdlib", feature = "local-tdlib"))] /// Build the project using the `download-tdlib` or `local-tdlib` feature. /// # Arguments /// - `lib_path`: The path where the tdlib library is located. If `None`, the path will be the `OUT_DIR` environment variable. /// /// The function will pass to the `rustc` the following flags: /// - `cargo:rustc-link-search=native=.../tdlib/lib` /// - `cargo:include=.../tdlib/include` /// - `cargo:rustc-link-lib=dylib=tdjson` /// - `cargo:rustc-link-arg=-Wl,-rpath,.../tdlib/lib` /// - `cargo:rustc-link-search=native=.../tdlib/bin` (only for Windows) /// /// The `...` represents the `dest_path` or the `OUT_DIR` environment variable. /// /// If the tdlib library is not found at the specified path, the function will panic. /// /// The function will panic if the tdlib library is not found at the specified path. fn generic_build(lib_path: Option) { let correct_lib_path: String; match lib_path { Some(lib_path) => { if lib_path.ends_with('/') || lib_path.ends_with('\\') { correct_lib_path = lib_path[..lib_path.len() - 1].to_string(); } else { correct_lib_path = lib_path.to_string(); } } None => { correct_lib_path = format!("{}/tdlib", std::env::var("OUT_DIR").unwrap()); } } let prefix = correct_lib_path.to_string(); let include_dir = format!("{prefix}/include"); let lib_dir = format!("{prefix}/lib"); let mut_lib_path = { #[cfg(any( all(target_os = "linux", target_arch = "x86_64"), all(target_os = "linux", target_arch = "aarch64") ))] { format!("{lib_dir}/libtdjson.so.{TDLIB_VERSION}") } #[cfg(any( all(target_os = "macos", target_arch = "x86_64"), all(target_os = "macos", target_arch = "aarch64"), all(target_os = "ios", target_arch = "x86_64"), all(target_os = "ios", target_arch = "aarch64") ))] { format!("{lib_dir}/libtdjson.{TDLIB_VERSION}.dylib") } #[cfg(any( all(target_os = "windows", target_arch = "x86_64"), all(target_os = "windows", target_arch = "aarch64") ))] { format!(r"{lib_dir}\tdjson.lib") } }; if !std::path::PathBuf::from(mut_lib_path.clone()).exists() { panic!("tdjson shared library not found at {mut_lib_path}"); } // This should be not necessary, but it is a workaround because windows does not find the // tdjson.dll using the commands below. // TODO: investigate and if it is a bug in `cargo` or `rustc`, open an issue to `cargo` to fix // this. #[cfg(any( all(target_os = "windows", target_arch = "x86_64"), all(target_os = "windows", target_arch = "aarch64") ))] { let bin_dir = format!(r"{prefix}\bin"); let cargo_bin = format!("{}/.cargo/bin", dirs::home_dir().unwrap().to_str().unwrap()); let libcrypto3x64 = format!(r"{bin_dir}\libcrypto-3-x64.dll"); let libssl3x64 = format!(r"{bin_dir}\libssl-3-x64.dll"); let tdjson = format!(r"{bin_dir}\tdjson.dll"); let zlib1 = format!(r"{bin_dir}\zlib1.dll"); let cargo_libcrypto3x64 = format!(r"{cargo_bin}\libcrypto-3-x64.dll"); let cargo_libssl3x64 = format!(r"{cargo_bin}\libssl-3-x64.dll"); let cargo_tdjson = format!(r"{cargo_bin}\tdjson.dll"); let cargo_zlib1 = format!(r"{cargo_bin}\zlib1.dll"); // Delete the files if they exist let _ = std::fs::remove_file(&cargo_libcrypto3x64); let _ = std::fs::remove_file(&cargo_libssl3x64); let _ = std::fs::remove_file(&cargo_tdjson); let _ = std::fs::remove_file(&cargo_zlib1); // Move all files to cargo_bin let _ = std::fs::copy(libcrypto3x64.clone(), cargo_libcrypto3x64.clone()); let _ = std::fs::copy(libssl3x64.clone(), cargo_libssl3x64.clone()); let _ = std::fs::copy(tdjson.clone(), cargo_tdjson.clone()); let _ = std::fs::copy(zlib1.clone(), cargo_zlib1.clone()); } #[cfg(any( all(target_os = "windows", target_arch = "x86_64"), all(target_os = "windows", target_arch = "aarch64") ))] { let bin_dir = format!(r"{prefix}\bin"); println!("cargo:rustc-link-search=native={bin_dir}"); } println!("cargo:rustc-link-search=native={lib_dir}"); println!("cargo:include={include_dir}"); println!("cargo:rustc-link-lib=dylib=tdjson"); println!("cargo:rustc-link-arg=-Wl,-rpath,{lib_dir}"); } /// Check if the features are correctly set. /// The features are correctly set when exactly one of the following features is enabled: /// - `local-tdlib` /// - `pkg-config` /// - `download-tdlib` /// - `docs` (only for tdlib documentation) /// /// The following features cannot be enabled at the same time: /// - `docs` and `pkg-config` /// - `docs` and `download-tdlib` /// - `docs` and `local-tdlib` /// - `pkg-config` and `local-tdlib` /// - `pkg-config` and `download-tdlib` /// - `local-tdlib` and `download-tdlib` /// /// If the features are not correctly set, the function will generate a compile error pub fn check_features() { // #[cfg(not(any(feature = "docs", feature = "local-tdlib", feature = "pkg-config", feature = "download-tdlib")))] // println!("cargo:warning=No features enabled, you must enable at least one of the following features: docs, local-tdlib, pkg-config, download-tdlib"); // compile_error!("You must enable at least one of the following features: docs, local-tdlib, pkg-config, download-tdlib"); #[cfg(all(feature = "docs", feature = "pkg-config"))] compile_error!( "feature \"docs\" and feature \"pkg-config\" cannot be enabled at the same time" ); #[cfg(all(feature = "docs", feature = "download-tdlib"))] compile_error!( "feature \"docs\" and feature \"download-tdlib\" cannot be enabled at the same time" ); #[cfg(all(feature = "docs", feature = "local-tdlib"))] compile_error!( "feature \"docs\" and feature \"local-tdlib\" cannot be enabled at the same time" ); #[cfg(all(feature = "pkg-config", feature = "local-tdlib"))] compile_error!( "feature \"pkg-config\" and feature \"local-tdlib\" cannot be enabled at the same time" ); #[cfg(all(feature = "pkg-config", feature = "download-tdlib"))] compile_error!( "feature \"pkg-config\" and feature \"download-tdlib\" cannot be enabled at the same time" ); #[cfg(all(feature = "local-tdlib", feature = "download-tdlib"))] compile_error!( "feature \"local-tdlib\" and feature \"download-tdlib\" cannot be enabled at the same time" ); } /// Set the `rerun-if-changed` and `rerun-if-env-changed` flags for the build script. /// The `rerun-if-changed` flag is set for the `build.rs` file. /// The `rerun-if-env-changed` flag is set for the `LOCAL_TDLIB_PATH` environment variable. pub fn set_rerun_if() { #[cfg(feature = "local-tdlib")] println!("cargo:rerun-if-env-changed=LOCAL_TDLIB_PATH"); println!("cargo:rerun-if-changed=build.rs"); } #[cfg(any(feature = "pkg-config", feature = "docs"))] #[allow(clippy::needless_doctest_main)] /// Build the project using the `pkg-config` feature. /// Using the `pkg-config` feature, the function will probe the system dependencies. /// It means that the function assumes that the tdlib library is compiled in the system. /// It requires the following variables to be set: /// - `PKG_CONFIG_PATH=$HOME/lib/tdlib/lib/pkgconfig/:$PKG_CONFIG_PATH` /// - `LD_LIBRARY_PATH=$HOME/lib/tdlib/lib/:$LD_LIBRARY_PATH` /// /// If the variables are not set, the function will panic. /// /// # Example /// Cargo.toml: /// ```toml /// [dependencies] /// tdlib = { version = "...", features = ["pkg-config"] } /// ``` /// /// build.rs: /// ```rust /// fn main() { /// tdlib_rs::build::check_features(); /// tdlib_rs::build::set_rerun_if(); /// tdlib_rs::build::build_pkg_config(); /// // Other build configurations /// // ... /// } /// ``` pub fn build_pkg_config() { #[cfg(not(feature = "docs"))] { system_deps::Config::new().probe().unwrap(); } } #[cfg(any(feature = "download-tdlib", feature = "docs"))] #[allow(clippy::needless_doctest_main)] #[allow(unused_variables)] /// Build the project using the `download-tdlib` feature. /// /// # Arguments /// - `dest_path`: The destination path where the tdlib library will be copied. If `None`, the path will be the `OUT_DIR` environment variable. /// /// Note that this function will pass to the `rustc` the following flags: /// - `cargo:rustc-link-search=native=.../tdlib/lib` /// - `cargo:include=.../tdlib/include` /// - `cargo:rustc-link-lib=dylib=tdjson` /// - `cargo:rustc-link-arg=-Wl,-rpath,.../tdlib/lib` /// - `cargo:rustc-link-search=native=.../tdlib/bin` (only for Windows) /// /// The `...` represents the `dest_path` or the `OUT_DIR` environment variable. /// /// The function will download the tdlib library from the GitHub release page. /// Using the `download-tdlib` feature, no system dependencies are required. /// The OS and architecture currently supported are: /// - Linux x86_64 /// - Linux aarch64 /// - Windows x86_64 /// - Windows aarch64 /// - MacOS x86_64 /// - MacOS aarch64 /// /// If the OS or architecture is not supported, the function will panic. /// /// # Example /// Cargo.toml: /// ```toml /// [dependencies] /// tdlib = { version = "...", features = ["download-tdlib"] } /// /// [build-dependencies] /// tdlib = { version = "...", features = [ "download-tdlib" ] } /// ``` /// /// build.rs: /// ```rust /// fn main() { /// tdlib_rs::build::check_features(); /// tdlib_rs::build::set_rerun_if(); /// tdlib_rs::build::build_download_tdlib(None); /// // Other build configurations /// // ... /// } /// ``` pub fn build_download_tdlib(dest_path: Option) { #[cfg(not(feature = "docs"))] { download_tdlib(); if let Some(dest_path) = &dest_path { let out_dir = std::env::var("OUT_DIR").unwrap(); let tdlib_dir = format!("{}/tdlib", &out_dir); copy_dir_all( std::path::Path::new(&tdlib_dir), std::path::Path::new(&dest_path), ) .unwrap(); } generic_build(dest_path); } } #[cfg(any(feature = "local-tdlib", feature = "docs"))] #[allow(clippy::needless_doctest_main)] /// Build the project using the `local-tdlib` feature. /// Using the `local-tdlib` feature, the function will copy the tdlib library from the /// `LOCAL_TDLIB_PATH` environment variable. /// The tdlib folder must contain the `lib` and `include` folders. /// You can directly download the tdlib library from the [TDLib Release GitHub page](https://github.com/FedericoBruzzone/tdlib-rs/releases). /// /// The `LOCAL_TDLIB_PATH` environment variable must be set to the path of the tdlib folder. /// /// The function will pass to the `rustc` the following flags: /// - `cargo:rustc-link-search=native=.../tdlib/lib` /// - `cargo:include=.../tdlib/include` /// - `cargo:rustc-link-lib=dylib=tdjson` /// - `cargo:rustc-link-arg=-Wl,-rpath,.../tdlib/lib` /// - `cargo:rustc-link-search=native=.../tdlib/bin` (only for Windows) /// /// The `...` represents the `LOCAL_TDLIB_PATH` environment variable. /// /// If the `LOCAL_TDLIB_PATH` environment variable is not set, the function will panic. /// /// # Example /// Cargo.toml: /// ```toml /// [dependencies] /// tdlib = { version = "...", features = ["local-tdlib"] } /// /// [build-dependencies] /// tdlib = { version = "...", features = [ "download-tdlib" ] } /// ``` /// /// build.rs: /// ```rust /// fn main() { /// tdlib_rs::build::check_features(); /// tdlib_rs::build::set_rerun_if(); /// tdlib_rs::build::build_local_tdlib(); /// // Other build configurations /// // ... /// } /// ``` pub fn build_local_tdlib() { #[cfg(not(feature = "docs"))] { // copy_local_tdlib(); let path = std::env::var("LOCAL_TDLIB_PATH").unwrap(); generic_build(Some(path)); } } #[allow(clippy::needless_doctest_main)] /// Build the project using the enabled features. /// /// # Arguments /// - `dest_path`: The destination path where the tdlib library will be copied. If `None`, the path /// will be the `OUT_DIR` environment variable. This argument is used only when the /// `download-tdlib` feature is enabled. /// /// The function will check if the features are correctly set. /// The function will set the `rerun-if-changed` and `rerun-if-env-changed` flags for the build /// script. /// The function will build the project using the enabled feature. /// /// # Example /// Cargo.toml: /// ```toml /// [dependencies] /// tdlib = { version = "...", features = ["download-tdlib"] } /// /// [build-dependencies] /// tdlib = { version = "...", features = [ "download-tdlib" ] } /// ``` /// /// build.rs: /// ```rust /// fn main() { /// tdlib_rs::build::build(None); /// // Other build configurations /// // ... /// } /// ``` pub fn build(_dest_path: Option) { check_features(); set_rerun_if(); #[cfg(feature = "pkg-config")] build_pkg_config(); #[cfg(feature = "download-tdlib")] build_download_tdlib(_dest_path); #[cfg(feature = "local-tdlib")] build_local_tdlib(); }