use std::{
borrow::Cow,
env,
ffi::OsStr,
fs::File,
io::{prelude::*, Write},
iter::Iterator,
path::{Path, PathBuf},
process::Command,
};
use anyhow::Result;
use walkdir::WalkDir;
use zip::{write::FileOptions, CompressionMethod, ZipWriter};
use platforms::{TARGET_ARCH, TARGET_ENV, TARGET_OS};
use substrate_build_script_utils::rerun_if_git_head_changed;
const DEFAULT_UNIX_PERMISSIONS: u32 = 0o755;
fn main() {
generate_cargo_keys();
rerun_if_git_head_changed();
let manifest_dir: PathBuf = env::var("CARGO_MANIFEST_DIR")
.expect("CARGO_MANIFEST_DIR should be set by cargo")
.into();
let out_dir: PathBuf = env::var("OUT_DIR")
.expect("OUT_DIR should be set by cargo")
.into();
let res = zip_template_and_build_dylint_driver(manifest_dir, out_dir);
match res {
Ok(()) => std::process::exit(0),
Err(err) => {
eprintln!("Encountered error: {:?}", err);
std::process::exit(1)
}
}
}
fn zip_template_and_build_dylint_driver(manifest_dir: PathBuf, out_dir: PathBuf) -> Result<()> {
zip_template(&manifest_dir, &out_dir)?;
check_dylint_link_installed()?;
let dylint_driver_dst_file = out_dir.join("ink-dylint-driver.zip");
let ink_dylint_driver_dir = manifest_dir.join("ink_linting");
let ink_dylint_driver_dir = ink_dylint_driver_dir.canonicalize().map_err(|err| {
anyhow::anyhow!(
"Unable to canonicalize '{:?}': {:?}\nDoes the folder exist? {}",
ink_dylint_driver_dir,
err,
ink_dylint_driver_dir.exists()
)
})?;
let original_name = ink_dylint_driver_dir.join("_Cargo.toml");
if !original_name.exists() {
anyhow::bail!("'{:?}' does not exist", original_name);
}
let tmp_name = ink_dylint_driver_dir.join("Cargo.toml");
std::fs::rename(&original_name, &tmp_name).map_err(|err| {
anyhow::anyhow!(
"Failed renaming '{:?}' to '{:?}': {:?}",
original_name,
tmp_name,
err
)
})?;
let res = build_and_zip_dylint_driver(ink_dylint_driver_dir, out_dir, dylint_driver_dst_file);
std::fs::rename(&tmp_name, &original_name).map_err(|err| {
anyhow::anyhow!(
"Failed renaming '{:?}' to '{:?}': {:?}",
tmp_name,
original_name,
err
)
})?;
res
}
fn zip_template(manifest_dir: &Path, out_dir: &Path) -> Result<()> {
let template_dir = manifest_dir.join("templates").join("new");
let template_dst_file = out_dir.join("template.zip");
println!(
"Creating template zip: template_dir '{}', destination archive '{}'",
template_dir.display(),
template_dst_file.display()
);
zip_dir(&template_dir, &template_dst_file, CompressionMethod::Stored).map(|_| {
println!(
"Done: {} written to {}",
template_dir.display(),
template_dst_file.display()
);
})
}
#[cfg(feature = "cargo-clippy")]
fn build_and_zip_dylint_driver(
_ink_dylint_driver_dir: PathBuf,
_out_dir: PathBuf,
dylint_driver_dst_file: PathBuf,
) -> Result<()> {
File::create(dylint_driver_dst_file)
.map_err(|err| {
anyhow::anyhow!(
"Failed creating an empty ink-dylint-driver.zip file: {:?}",
err
)
})
.map(|_| ())
}
#[cfg(not(feature = "cargo-clippy"))]
fn build_and_zip_dylint_driver(
ink_dylint_driver_dir: PathBuf,
out_dir: PathBuf,
dylint_driver_dst_file: PathBuf,
) -> Result<()> {
let mut cmd = Command::new("cargo");
let manifest_arg = format!(
"--manifest-path={}",
ink_dylint_driver_dir.join("Cargo.toml").display()
);
let target_dir = format!("--target-dir={}", out_dir.display());
cmd.args(vec![
"build",
"--release",
"--locked",
&target_dir,
&manifest_arg,
]);
cmd.env_remove("RUSTC_WRAPPER");
cmd.env_remove("RUSTUP_TOOLCHAIN");
cmd.env_remove("CARGO_TARGET_DIR");
println!(
"Setting cargo working dir to '{}'",
ink_dylint_driver_dir.display()
);
cmd.current_dir(ink_dylint_driver_dir.clone());
println!("Invoking cargo: {:?}", cmd);
let child = cmd
.stdout(std::process::Stdio::piped())
.spawn()?;
let output = child.wait_with_output()?;
if !output.status.success() {
anyhow::bail!(
"`{:?}` failed with exit code: {:?}",
cmd,
output.status.code()
);
}
println!(
"Creating ink-dylint-driver.zip: destination archive '{}'",
dylint_driver_dst_file.display()
);
zip_dylint_driver(
&out_dir.join("release"),
&dylint_driver_dst_file,
CompressionMethod::Stored,
)
.map(|_| {
println!(
"Done: {} written to {}",
ink_dylint_driver_dir.display(),
dylint_driver_dst_file.display()
);
()
})
}
fn zip_dir(src_dir: &Path, dst_file: &Path, method: CompressionMethod) -> Result<()> {
if !src_dir.exists() {
anyhow::bail!("src_dir '{}' does not exist", src_dir.display());
}
if !src_dir.is_dir() {
anyhow::bail!("src_dir '{}' is not a directory", src_dir.display());
}
let file = File::create(dst_file)?;
let walkdir = WalkDir::new(src_dir);
let it = walkdir.into_iter().filter_map(|e| e.ok());
let mut zip = ZipWriter::new(file);
let options = FileOptions::default()
.compression_method(method)
.unix_permissions(DEFAULT_UNIX_PERMISSIONS);
let mut buffer = Vec::new();
for entry in it {
let path = entry.path();
let mut name = path.strip_prefix(&src_dir)?.to_path_buf();
if name.file_name() == Some(OsStr::new("_Cargo.toml")) {
name.set_file_name("Cargo.toml");
}
let file_path = name.as_os_str().to_string_lossy();
if path.is_file() {
zip.start_file(file_path, options)?;
let mut f = File::open(path)?;
f.read_to_end(&mut buffer)?;
zip.write_all(&*buffer)?;
buffer.clear();
} else if !name.as_os_str().is_empty() {
zip.add_directory(file_path, options)?;
}
}
zip.finish()?;
Ok(())
}
#[cfg(not(feature = "cargo-clippy"))]
fn zip_dylint_driver(src_dir: &Path, dst_file: &Path, method: CompressionMethod) -> Result<()> {
if !src_dir.exists() {
anyhow::bail!("src_dir '{}' does not exist", src_dir.display());
}
if !src_dir.is_dir() {
anyhow::bail!("src_dir '{}' is not a directory", src_dir.display());
}
let file = File::create(dst_file)?;
let mut zip = ZipWriter::new(file);
let options = FileOptions::default()
.compression_method(method)
.unix_permissions(DEFAULT_UNIX_PERMISSIONS);
let mut buffer = Vec::new();
let walkdir = WalkDir::new(src_dir);
let it = walkdir.into_iter().filter_map(|e| e.ok());
for entry in it {
let path = entry.path();
let name = path.strip_prefix(&src_dir)?.to_path_buf();
let file_path = name.as_os_str().to_string_lossy();
if path.is_file() && path.display().to_string().contains("libink_linting@") {
zip.start_file(file_path, options)?;
let mut f = File::open(path)?;
f.read_to_end(&mut buffer)?;
zip.write_all(&*buffer)?;
buffer.clear();
zip.finish()?;
break;
}
}
Ok(())
}
fn generate_cargo_keys() {
let output = Command::new("git")
.args(&["rev-parse", "--short", "HEAD"])
.output();
let commit = match output {
Ok(o) if o.status.success() => {
let sha = String::from_utf8_lossy(&o.stdout).trim().to_owned();
Cow::from(sha)
}
Ok(o) => {
println!("cargo:warning=Git command failed with status: {}", o.status);
Cow::from("unknown")
}
Err(err) => {
println!("cargo:warning=Failed to execute git command: {}", err);
Cow::from("unknown")
}
};
println!(
"cargo:rustc-env=CARGO_CONTRACT_CLI_IMPL_VERSION={}",
get_version(&commit)
)
}
fn get_version(impl_commit: &str) -> String {
let commit_dash = if impl_commit.is_empty() { "" } else { "-" };
format!(
"{}{}{}-{}",
std::env::var("CARGO_PKG_VERSION").unwrap_or_default(),
commit_dash,
impl_commit,
get_platform(),
)
}
fn get_platform() -> String {
let env_dash = if TARGET_ENV.is_some() { "-" } else { "" };
format!(
"{}-{}{}{}",
TARGET_ARCH.as_str(),
TARGET_OS.as_str(),
env_dash,
TARGET_ENV.map(|x| x.as_str()).unwrap_or(""),
)
}
fn check_dylint_link_installed() -> Result<()> {
let which = which::which("dylint-link");
if which.is_err() {
anyhow::bail!(
"dylint-link was not found!\n\
Make sure it is installed and the binary is in your PATH environment.\n\n\
You can install it by executing `cargo install dylint-link`."
);
}
Ok(())
}