#[cfg(not(feature = "download_dart_sdk"))]
use std::env::VarError;
use std::{
env,
error::Error as StdError,
fs::{self, File, OpenOptions},
io::{self, Error as IoError, ErrorKind as IoErrorKind, Read, Write},
path::{Path, PathBuf},
time::Duration,
};
use chrono::{DateTime, SecondsFormat, Utc};
use reqwest::StatusCode;
use sha2::{Digest, Sha256};
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"))]
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 {
return 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 = "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 {
return 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
}
}
enum DartSdkChannel {
Stable,
Beta,
Dev,
}
impl DartSdkChannel {
fn to_string(&self) -> String {
match self {
DartSdkChannel::Stable => "stable".to_string(),
DartSdkChannel::Beta => "beta".to_string(),
DartSdkChannel::Dev => "dev".to_string(),
}
}
#[allow(dead_code)]
fn from_string(s: &str) -> Option<DartSdkChannel> {
match s {
"stable" => Some(DartSdkChannel::Stable),
"beta" => Some(DartSdkChannel::Beta),
"dev" => Some(DartSdkChannel::Dev),
_ => None,
}
}
}
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.to_string(),
_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.to_string(),
_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()?)?;
Ok(log("INFO: Successfully downloaded resource"))
},
_ => {
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) {
Ok(log("INFO: integrity check successful"))
} 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)?;
}
}
Ok(log("INFO: successfully unzipped Dart SDK"))
}
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");
return 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));
return 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));
return Err(error);
}
} else {
let error = dart_sdk_shasum_download_res.unwrap_err();
log(&format!("ERROR: failed to download Dart SDK shasum: {{{}}}", error));
return Err(error);
}
} else {
let error = dart_sdk_download_res.unwrap_err();
log(&format!("ERROR: failed to download Dart SDK: {{{}}}", error));
return Err(error);
}
}
#[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)]
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;
}
fn emit_compiler_flags() {
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_header_wrapper = PathBuf::from("./bindgen/dart_sdk_wrapper.h");
if !dart_sdk_header_wrapper.exists() {
let error = &format!(
"ERROR: Dart SDK header wrapper not found at \"{}\". Please ensure that Dart-sys is not corrupt. Proceed \
with caution.",
dart_sdk_header_wrapper.to_str().unwrap()
);
log(error);
panic!("{}", error);
}
let bindings = bindgen::Builder::default()
.header("./bindgen/dart_sdk_wrapper.h")
.clang_arg(format!(
"--include-directory={}",
dart_sdk_path.join("include").to_str().unwrap()
))
.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");
}
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"))
{
emit_compiler_flags();
}
log("INFO: finished build script");
log("------------------------------");
}