#[cfg(not(feature = "download_dart_sdk"))]
#[cfg(not(feature = "dart_api_dl"))]
use std::env::VarError;
use std::{env, fmt, fs::OpenOptions, io::Write, path::PathBuf};
#[cfg(not(feature = "dart_api_dl"))]
use std::{
error::Error as StdError,
fs::{self, File},
io::{self, Error as IoError, ErrorKind as IoErrorKind, Read},
path::Path,
time::Duration,
};
#[cfg(feature = "dart_api_dl")]
use bindgen::EnumVariation;
use chrono::{DateTime, SecondsFormat, Utc};
#[cfg(not(feature = "dart_api_dl"))]
use reqwest::StatusCode;
#[cfg(not(feature = "dart_api_dl"))]
use sha2::{Digest, Sha256};
#[cfg(not(feature = "dart_api_dl"))]
use zip::ZipArchive;
fn log(msg: &str) {
let now: DateTime<Utc> = Utc::now();
let mut file = OpenOptions::new()
.append(true)
.create(true)
.open(PathBuf::from(env::var("OUT_DIR").unwrap()).join("build.log"))
.unwrap();
writeln!(file, "[{}] {}", now.to_rfc3339_opts(SecondsFormat::Secs, true), msg).unwrap();
}
#[cfg(not(feature = "download_dart_sdk"))]
#[cfg(not(feature = "dart_api_dl"))]
fn find_local_dart_sdk() -> Option<String> {
log("INFO: searching for local Dart SDK");
let dart_sdk: Result<String, VarError> = env::var("dart_sdk");
if let Ok(dart_sdk) = dart_sdk {
Some(dart_sdk)
} else {
let paths: Vec<PathBuf> =
env::split_paths(&env::var("PATH").expect("Could not find $PATH variable.")).collect();
for i in &paths {
if i.components().any(|x| x.as_os_str() == "dart") {
let mut path = i.clone();
while !path.is_dir() || path.file_name().unwrap() != "dart-sdk" {
path.pop();
}
if !path.as_os_str().is_empty() {
return Some(path.to_str().unwrap().to_string());
}
}
}
None
}
}
#[cfg(not(feature = "dart_api_dl"))]
#[cfg(not(feature = "download_dart_sdk"))]
fn find_local_flutter_sdk() -> Option<String> {
log("INFO: searching for local flutter sdk");
let flutter_sdk: Result<String, VarError> = env::var("fluter_sdk");
if let Ok(flutter_sdk) = flutter_sdk {
Some(flutter_sdk)
} else {
let paths: Vec<PathBuf> =
env::split_paths(&env::var("PATH").expect("Could not find $PATH variable.")).collect();
for i in &paths {
if i.components().any(|x| x.as_os_str() == "flutter") {
let mut path = i.clone();
while !path.is_dir() || path.file_name().unwrap() != "bin" {
path.pop();
}
if !path.as_os_str().is_empty() {
path.push("cache");
path.push("dart-sdk");
return Some(path.to_str().unwrap().to_string());
}
}
}
None
}
}
#[allow(dead_code)]
#[derive(Debug, Clone, Copy)]
enum DartSdkChannel {
Stable,
Beta,
Dev,
}
impl fmt::Display for DartSdkChannel {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
DartSdkChannel::Stable => write!(f, "stable"),
DartSdkChannel::Beta => write!(f, "beta"),
DartSdkChannel::Dev => write!(f, "dev"),
}
}
}
#[cfg(not(feature = "dart_api_dl"))]
fn download_dart_sdk(channel: DartSdkChannel) -> Result<String, Box<dyn StdError>> {
log("INFO: attempting to download dart-sdk");
let platform = match env::consts::OS {
"linux" => "linux",
"macos" => "macos",
"windows" => "windows",
_ => {
const ERROR: &str = "ERROR: unknown/unsupported OS";
log(ERROR);
return Err(Box::new(IoError::new(IoErrorKind::Unsupported, ERROR)));
},
};
let arch = match env::consts::ARCH {
"x86_64" => "x64",
"arm" => "arm",
"arm64" => "arm64",
"ia32" => "ia32",
_ => {
const ERROR: &str = "ERROR: unknown/unsupported CPU architecture";
log(ERROR);
return Err(Box::new(IoError::new(IoErrorKind::Unsupported, ERROR)));
},
};
let dart_sdk_download_url: String = format!(
"https://storage.googleapis.com/dart-archive/channels/{_channel}/release/latest/sdk/dartsdk-{_platform}-{_arch}-release.zip",
_channel = channel,
_platform = platform,
_arch = arch,
);
let dart_sdk_shasum_download_url: String = format!(
"https://storage.googleapis.com/dart-archive/channels/{_channel}/release/latest/sdk/dartsdk-{_platform}-{_arch}-release.zip.sha256sum",
_channel = channel,
_platform = platform,
_arch = arch,
);
fn download<T>(url: T) -> Result<(), Box<dyn StdError>>
where T: reqwest::IntoUrl {
log(&format!("INFO: downloading \"{}\"", url.as_str()));
log("INFO: Download will take a while. This is normal.");
let is_shasum = url.as_str().contains(".sha256sum");
let client = reqwest::blocking::Client::builder()
.timeout(Duration::from_secs(180))
.build()?;
let response = client.get(url).send()?;
match response.status() {
StatusCode::OK => {
let cargo_home = env::var("CARGO_HOME").expect("Could not find $CARGO_HOME variable.");
let file_path = if is_shasum {
format!("{}/dart-sdk.zip.sha256sum", cargo_home)
} else {
format!("{}/dart-sdk.zip", cargo_home)
};
let mut file = match File::create(&file_path) {
Ok(file) => file,
Err(e) => {
if e.kind() == IoErrorKind::AlreadyExists {
log(&format!("WARNING: \"{}\" already exists. Deleting", &file_path));
#[cfg(not(feature = "ci"))]
println!("cargo:warning=\"{}\" already exists. Deleting", &file_path);
fs::remove_file(&file_path)?;
File::create(&file_path)?
} else {
return Err(Box::new(e));
}
},
};
file.write_all(&response.bytes()?)?;
log("INFO: Successfully downloaded resource");
Ok(())
},
_ => {
let http_error = format!("ERROR: HTTP error: {}", response.status());
let error = format!("ERROR: could not download: {{{}}}", http_error);
log(&error);
Err(Box::new(IoError::new(IoErrorKind::Other, error)))
},
}
}
fn check_sha256_checksum(file_path: &str, hash_path: &str) -> Result<(), Box<dyn StdError>> {
log(&format!("INFO: checking integrity of \"{}\"", file_path));
let mut file = File::open(file_path)?;
let mut hash_file = File::open(hash_path)?;
let mut file_buffer = Vec::new();
let mut hash_file_buffer = Vec::new();
file.read_to_end(&mut file_buffer)?;
hash_file.read_to_end(&mut hash_file_buffer)?;
let expected_hash = String::from_utf8(hash_file_buffer)?;
let mut hasher = Sha256::new();
hasher.update(&file_buffer);
let actual_hash = hasher.finalize();
let acutal_hash = format!("{:x}", actual_hash);
if expected_hash.contains(&acutal_hash) {
log("INFO: integrity check successful");
Ok(())
} else {
let error = format!(
"ERROR: integrity check failed. Expected hash: `{}`, Actual hash: `{}`",
expected_hash, acutal_hash
);
log(&error);
Err(Box::new(IoError::new(IoErrorKind::Other, error)))
}
}
fn unzip_file(file_path: &str, destination: &str) -> Result<(), Box<dyn StdError>> {
log("INFO: attempting to unzip Dart SDK");
let file = File::open(file_path)?;
let mut archive = ZipArchive::new(file)?;
for i in 0..archive.len() {
let mut file = archive.by_index(i)?;
let outpath = Path::new(destination).join(file.name());
if file.name().ends_with('/') {
fs::create_dir_all(&outpath)?;
} else {
if let Some(p) = outpath.parent() {
if !p.exists() {
fs::create_dir_all(p)?;
}
}
if outpath.exists() {
fs::remove_file(&outpath)?;
}
let mut outfile = File::create(&outpath)?;
io::copy(&mut file, &mut outfile)?;
}
}
log("INFO: successfully unzipped Dart SDK");
Ok(())
}
let cargo_home = env::var("CARGO_HOME").expect("Could not find $CARGO_HOME variable.");
let dart_sdk_download_res = download(dart_sdk_download_url);
let dart_sdk_shasum_download_res = download(dart_sdk_shasum_download_url);
if dart_sdk_download_res.is_ok() {
if dart_sdk_shasum_download_res.is_ok() {
if check_sha256_checksum(
&format!("{}/dart-sdk.zip", cargo_home),
&format!("{}/dart-sdk.zip.sha256sum", cargo_home),
)
.is_ok()
{
log("INFO: successfully downloaded Dart SDK");
if unzip_file(
&format!("{}/dart-sdk.zip", cargo_home),
&format!("{}/dart-sdk", cargo_home),
)
.is_ok()
{
log("INFO: successfully unzipped Dart SDK");
Ok(format!("{}/dart-sdk/dart-sdk", cargo_home))
} else {
let error = unzip_file(
&format!("{}/dart-sdk.zip", cargo_home),
&format!("{}/dart-sdk", cargo_home),
)
.unwrap_err();
log(&format!("ERROR: failed to unzip Dart SDK: {{{}}}", error));
Err(error)
}
} else {
let error = check_sha256_checksum(
&format!("{}/dart-sdk.zip", cargo_home),
&format!("{}/dart-sdk.zip.sha256sum", cargo_home),
)
.unwrap_err();
log(&format!("ERROR: failed to check shasum: {{{}}}", error));
Err(error)
}
} else {
#[allow(clippy::unnecessary_unwrap)]
let error = dart_sdk_shasum_download_res.unwrap_err();
log(&format!("ERROR: failed to download Dart SDK shasum: {{{}}}", error));
Err(error)
}
} else {
#[allow(clippy::unnecessary_unwrap)]
let error = dart_sdk_download_res.unwrap_err();
log(&format!("ERROR: failed to download Dart SDK: {{{}}}", error));
Err(error)
}
}
#[cfg(not(feature = "dart_api_dl"))]
#[cfg(not(feature = "download_dart_sdk"))]
fn get_dart_sdk(channel: DartSdkChannel) -> String {
log("INFO: searching for Dart SDK");
log("INFO: attempting to use local Dart SDK");
let dart_sdk = match find_local_dart_sdk() {
Some(x) => x,
None => {
log("INFO: failed to find local Dart SDK, trying to use local flutter sdk");
match find_local_flutter_sdk() {
Some(x) => x,
None => {
log("INFO: failed to find local flutter sdk, attempting to download the official dart-sdk");
match download_dart_sdk(channel) {
Ok(x) => x,
Err(e) => {
log(&format!("{}", e));
panic!("{}", e);
},
}
},
}
},
};
dart_sdk
}
#[allow(unreachable_code)]
#[cfg(not(feature = "dart_api_dl"))]
fn get_dart_sdk_channel() -> DartSdkChannel {
#[cfg(all(
feature = "download_dart_sdk_stable",
feature = "download_dart_sdk_beta",
feature = "download_dart_sdk_dev"
))]
{
const WARNING: &str = "WARNING: more than one `download_dart_sdk_*` feature is enabled, defaulting to stable";
log(WARNING);
#[cfg(not(feature = "ci"))]
println!("cargo:warning={}", WARNING);
return DartSdkChannel::Stable;
}
#[cfg(feature = "download_dart_sdk_stable")]
return DartSdkChannel::Stable;
#[cfg(feature = "download_dart_sdk_beta")]
return DartSdkChannel::Beta;
#[cfg(feature = "download_dart_sdk_dev")]
return DartSdkChannel::Dev;
#[cfg(not(any(
feature = "download_dart_sdk_stable",
feature = "download_dart_sdk_beta",
feature = "download_dart_sdk_dev"
)))]
return DartSdkChannel::Stable;
}
#[cfg(not(feature = "dart_api_dl"))]
pub fn codegen() {
log("INFO: emitting compiler flags");
#[cfg(not(feature = "download_dart_sdk"))]
let _dart_sdk_path = get_dart_sdk(get_dart_sdk_channel());
#[cfg(feature = "download_dart_sdk")]
let _dart_sdk_path = download_dart_sdk(get_dart_sdk_channel()).unwrap();
let dart_sdk_path = PathBuf::from(&_dart_sdk_path);
log(&format!(
"INFO: using Dart SDK at: \"{}\"",
dart_sdk_path.to_str().unwrap()
));
let target_os = env::var("CARGO_CFG_TARGET_OS");
match target_os.as_ref().map(|x| &**x) {
Ok("windows") => {
log("INFO: target OS is windows, adding extra compile flags for linking against Dart SDK binaries");
let dart_sdk_bin_path: PathBuf = dart_sdk_path.join("bin");
let dart_sdk_lib_path = dart_sdk_path.join("bin").join("dart.lib");
if !dart_sdk_lib_path.exists() {
let error = &format!(
"ERROR: Dart SDK binaries not found at \"{}\\.{{exe&lib}}\". Please ensure that the Dart SDK is \
installed correctly.",
dart_sdk_bin_path.to_str().unwrap()
);
log(error);
panic!("{}", error);
}
log(&format!(
"INFO: successfully found Dart SDK binaries at: \"{}\"",
dart_sdk_bin_path.to_str().unwrap()
));
println!("cargo:rustc-link-search=native={}", dart_sdk_bin_path.to_str().unwrap());
println!("cargo:rustc-link-lib=static=dart");
},
_ => log("INFO: target OS is not windows, skipping extra compile flags for linking against Dart SDK binaries"),
}
let dart_sdk_include_dir = dart_sdk_path.join("include");
let bindings = bindgen::Builder::default()
.header(
dart_sdk_include_dir
.join("dart_api.h")
.to_str()
.expect("ERROR: could not find path `dart_api_dl.h`"),
)
.header(
dart_sdk_include_dir
.join("dart_version.h")
.to_str()
.expect("ERROR: could not find path `dart_version.h`"),
)
.header(
dart_sdk_include_dir
.join("dart_native_api.h")
.to_str()
.expect("ERROR: could not find path `dart_native_api.h`"),
)
.header(
dart_sdk_include_dir
.join("dart_tools_api.h")
.to_str()
.expect("ERROR: could not find path `dart_tools_api.h`"),
)
.clang_arg("-DDART_SHARED_LIB")
.generate()
.expect("ERROR: bindgen failed to generate bindings");
let out_path = PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("ERROR: Could not find $CARGO_MANIFEST_DIR"));
bindings
.write_to_file(out_path.join("src/bindings/mod.rs"))
.expect("ERROR: failed to write bindings to file");
log("INFO: finished emitting compiler flags");
}
#[cfg(feature = "dart_api_dl")]
pub fn codegen() {
static DL_ENABLED_FUNCTIONS: &[&str] = &["Dart_InitializeApiDL"];
static DL_ENABLED_TYPES: &[&str] = &[
"Dart_.+_DL",
"Dart_CObject",
"Dart_Handle",
"Dart_PersistentHandle",
"Dart_WeakPersistentHandle",
"Dart_HandleFinalizer",
"Dart_FinalizableHandle",
"Dart_CObject_Type",
"Dart_TypedData_Type",
];
static DL_ENABLED_VARS: &[&str] = &["Dart_.+_DL", "DART_API_DL_MAJOR_VERSION", "DART_API_DL_MINOR_VERSION"];
let dart_sdk_include_dir = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap())
.join("dart-sdk")
.join("include");
let mut builder = bindgen::Builder::default()
.header(
dart_sdk_include_dir
.join("dart_api_dl.h")
.to_str()
.expect("ERROR: could not find path `dart_api_dl.h`"),
)
.header(
dart_sdk_include_dir
.join("dart_version.h")
.to_str()
.expect("ERROR: could not find path `dart_version.h`"),
)
.header(
dart_sdk_include_dir
.join("dart_native_api.h")
.to_str()
.expect("ERROR: could not find path `dart_native_api.h`"),
)
.header(
dart_sdk_include_dir
.join("dart_tools_api.h")
.to_str()
.expect("ERROR: could not find path `dart_tools_api.h`"),
)
.parse_callbacks(Box::new(bindgen::CargoCallbacks))
.default_enum_style(EnumVariation::NewType {
is_bitfield: false,
is_global: true,
});
for function_ in DL_ENABLED_FUNCTIONS {
builder = builder.allowlist_function(function_);
}
for type_ in DL_ENABLED_TYPES {
builder = builder.allowlist_type(type_);
}
for variable_ in DL_ENABLED_VARS {
builder = builder.allowlist_var(variable_);
}
let bindings = builder
.generate()
.expect("ERROR: Failed to generate dart_api_dl binding");
let out_path = PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("ERROR: Could not find $CARGO_MANIFEST_DIR"));
bindings
.write_to_file(out_path.join("src/bindings_api_dl/mod.rs"))
.expect("ERROR: failed to write dart_api_dl bindings to file");
let dart_dl_glue_path = dart_sdk_include_dir.join("dart_api_dl.c");
cc::Build::new()
.file(dart_dl_glue_path)
.include(dart_sdk_include_dir)
.compile("dart_api_dl");
}
fn main() {
#[cfg(not(feature = "ci"))]
println!(
"cargo:warning=INFO: build log is located at: `{}`",
PathBuf::from(env::var("OUT_DIR").unwrap())
.join("build.log")
.to_str()
.unwrap()
);
log("------------------------------");
log("INFO: starting build script");
if !cfg!(feature = "docs_only") ||
cfg!(all(feature = "docs_only", feature = "download_dart_sdk")) ||
cfg!(all(feature = "docs_only", feature = "download_dart_sdk_stable")) ||
cfg!(all(feature = "docs_only", feature = "download_dart_sdk_beta")) ||
cfg!(all(feature = "docs_only", feature = "download_dart_sdk_dev"))
{
codegen();
}
log("INFO: finished build script");
log("------------------------------");
}