Skip to content

Commit

Permalink
Suggest adding no_main for error[E0601] (#1113)
Browse files Browse the repository at this point in the history
* Use duct to build/invoke cargo command

* Push command to args

* Ignore cargo tree stdout

* Suggest adding `no_main`

* Read one byte at a time to get the progress bar

* Refactor to separate method

* Update crates/build/src/lib.rs

Co-authored-by: Michael Müller <[email protected]>

* Detect term size

* Update suggestion and exit on error

* Update message

* Hardcode term size

* Hardcode term size

* Only detect term size on non windows OS

* Red

* Use term_size for windows

* use term_size as _;

---------

Co-authored-by: Michael Müller <[email protected]>
  • Loading branch information
ascjones and cmichi authored May 17, 2023
1 parent 247d13c commit 85688a6
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 55 deletions.
44 changes: 44 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions crates/build/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ blake2 = "0.10.6"
cargo_metadata = "0.15.4"
colored = "2.0.0"
clap = { version = "4.2.7", features = ["derive", "env"] }
duct = "0.13.6"
heck = "0.4.0"
hex = "0.4.3"
impl-serde = "0.4.0"
Expand All @@ -31,6 +32,7 @@ semver = { version = "1.0.17", features = ["serde"] }
serde = { version = "1", default-features = false, features = ["derive"] }
serde_json = "1.0.96"
tempfile = "3.5.0"
term_size = "0.3.2"
url = { version = "2.3.1", features = ["serde"] }
wasm-opt = "0.112.0"
which = "4.4.0"
Expand Down
69 changes: 64 additions & 5 deletions crates/build/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,9 @@ use parity_wasm::elements::{
};
use semver::Version;
use std::{
collections::VecDeque,
fs,
io,
path::{
Path,
PathBuf,
Expand Down Expand Up @@ -291,9 +293,10 @@ fn exec_cargo_for_onchain_target(
None
};

util::invoke_cargo(command, &args, manifest_path.directory(), verbosity, env)?;
let cargo =
util::cargo_cmd(command, &args, manifest_path.directory(), verbosity, env);

Ok(())
invoke_cargo_and_scan_for_error(cargo)
};

if unstable_flags.original_manifest {
Expand All @@ -320,6 +323,51 @@ fn exec_cargo_for_onchain_target(
Ok(())
}

/// Executes the supplied cargo command, reading the output and scanning for known errors.
/// Writes the captured stderr back to stderr and maintains the cargo tty progress bar.
fn invoke_cargo_and_scan_for_error(cargo: duct::Expression) -> Result<()> {
macro_rules! eprintln_red {
($value:expr) => {{
use colored::Colorize as _;
::std::eprintln!("{}", $value.bright_red().bold());
}};
}

let cargo = util::cargo_tty_output(cargo);

let missing_main_err = "error[E0601]".as_bytes();
let mut err_buf = VecDeque::with_capacity(missing_main_err.len());

let mut reader = cargo.stderr_to_stdout().reader()?;
let mut buffer = [0u8; 1];

loop {
let bytes_read = io::Read::read(&mut reader, &mut buffer)?;
for byte in buffer[0..bytes_read].iter() {
err_buf.push_back(*byte);
if err_buf.len() > missing_main_err.len() {
let byte = err_buf.pop_front().expect("buffer is not empty");
io::Write::write(&mut io::stderr(), &[byte])?;
}
}
if missing_main_err == err_buf.make_contiguous() {
eprintln_red!("\nExited with error: [E0601]");
eprintln_red!(
"Your contract must be annotated with the `no_main` attribute.\n"
);
eprintln_red!("Examples how to do this:");
eprintln_red!(" - `#![cfg_attr(not(feature = \"std\"), no_std, no_main)]`");
eprintln_red!(" - `#[no_main]`\n");
return Err(anyhow::anyhow!("missing `no_main` attribute"))
}
if bytes_read == 0 {
break
}
buffer = [0u8; 1];
}
Ok(())
}

/// Executes `cargo dylint` with the ink! linting driver that is built during
/// the `build.rs`.
///
Expand Down Expand Up @@ -357,8 +405,15 @@ fn exec_cargo_dylint(crate_metadata: &CrateMetadata, verbosity: Verbosity) -> Re
Ok(())
})?
.using_temp(|manifest_path| {
util::invoke_cargo("dylint", &args, manifest_path.directory(), verbosity, env)
.map(|_| ())
let cargo = util::cargo_cmd(
"dylint",
&args,
manifest_path.directory(),
verbosity,
env,
);
cargo.run()?;
Ok(())
})?;

Ok(())
Expand Down Expand Up @@ -548,7 +603,11 @@ fn assert_compatible_ink_dependencies(
) -> Result<()> {
for dependency in ["parity-scale-codec", "scale-info"].iter() {
let args = ["-i", dependency, "--duplicates"];
let _ = util::invoke_cargo("tree", args, manifest_path.directory(), verbosity, vec![])
let cargo =
util::cargo_cmd("tree", args, manifest_path.directory(), verbosity, vec![]);
cargo
.stdout_null()
.run()
.with_context(|| {
format!(
"Mismatching versions of `{dependency}` were found!\n\
Expand Down
7 changes: 4 additions & 3 deletions crates/build/src/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,16 +149,17 @@ pub(crate) fn execute(
network.append_to_args(&mut args);
features.append_to_args(&mut args);

let stdout = util::invoke_cargo(
let cmd = util::cargo_cmd(
"run",
args,
crate_metadata.manifest_path.directory(),
verbosity,
vec![],
)?;
);
let output = cmd.stdout_capture().run()?;

let ink_meta: serde_json::Map<String, serde_json::Value> =
serde_json::from_slice(&stdout)?;
serde_json::from_slice(&output.stdout)?;
let metadata = ContractMetadata::new(source, contract, user, ink_meta);
{
let mut metadata = metadata.clone();
Expand Down
94 changes: 47 additions & 47 deletions crates/build/src/util/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,13 @@
pub mod tests;

use crate::Verbosity;
use anyhow::{
Context,
Result,
};
use anyhow::Result;
use duct::Expression;
use std::{
ffi::OsStr,
ffi::OsString,
path::Path,
process::Command,
};
use term_size as _;

// Returns the current Rust toolchain formatted by `<channel>-<target-triple>`.
pub(crate) fn rust_toolchain() -> Result<String> {
Expand All @@ -36,75 +34,77 @@ pub(crate) fn rust_toolchain() -> Result<String> {
Ok(toolchain)
}

/// Invokes `cargo` with the subcommand `command` and the supplied `args`.
/// Builds an [`Expression`] for invoking `cargo`.
///
/// In case `working_dir` is set, the command will be invoked with that folder
/// as the working directory.
///
/// In case `env` is given environment variables can be either set or unset:
/// * To _set_ push an item a la `("VAR_NAME", Some("VAR_VALUE"))` to the `env` vector.
/// * To _unset_ push an item a la `("VAR_NAME", None)` to the `env` vector.
///
/// If successful, returns the stdout bytes.
pub fn invoke_cargo<I, S, P>(
pub fn cargo_cmd<I, S, P>(
command: &str,
args: I,
working_dir: Option<P>,
verbosity: Verbosity,
env: Vec<(&str, Option<String>)>,
) -> Result<Vec<u8>>
) -> Expression
where
I: IntoIterator<Item = S> + std::fmt::Debug,
S: AsRef<OsStr>,
S: Into<OsString>,
P: AsRef<Path>,
{
let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".to_string());
let mut cmd = Command::new(cargo);
let mut cmd_args = Vec::new();

cmd_args.push(command);
cmd_args.push("--color=always");

match verbosity {
Verbosity::Quiet => cmd_args.push("--quiet"),
Verbosity::Verbose => {
if command != "dylint" {
cmd_args.push("--verbose")
}
}
Verbosity::Default => (),
};

let mut cmd_args: Vec<OsString> = cmd_args.iter().map(Into::into).collect();
for arg in args {
cmd_args.push(arg.into());
}

let mut cmd = duct::cmd(cargo, &cmd_args);

env.iter().for_each(|(env_key, maybe_env_val)| {
match maybe_env_val {
Some(env_val) => cmd.env(env_key, env_val),
None => cmd.env_remove(env_key),
Some(env_val) => cmd = cmd.env(env_key, env_val),
None => cmd = cmd.env_remove(env_key),
};
});

if let Some(path) = working_dir {
tracing::debug!("Setting cargo working dir to '{}'", path.as_ref().display());
cmd.current_dir(path);
cmd = cmd.dir(path.as_ref());
}

cmd.arg(command);
cmd.args(args);
match verbosity {
Verbosity::Quiet => cmd.arg("--quiet"),
Verbosity::Verbose => {
if command != "dylint" {
cmd.arg("--verbose")
} else {
&mut cmd
}
}
Verbosity::Default => &mut cmd,
};
cmd
}

tracing::debug!("Invoking cargo: {:?}", cmd);

let child = cmd
// capture the stdout to return from this function as bytes
.stdout(std::process::Stdio::piped())
.spawn()
.context(format!("Error executing `{cmd:?}`"))?;
let output = child.wait_with_output()?;

if output.status.success() {
Ok(output.stdout)
} else {
anyhow::bail!(
"`{:?}` failed with exit code: {:?}",
cmd,
output.status.code()
);
}
/// Configures the cargo command to output colour and the progress bar.
pub fn cargo_tty_output(cmd: Expression) -> Expression {
#[cfg(windows)]
let term_size = "100";

#[cfg(not(windows))]
let term_size = term_size::dimensions_stderr()
.map(|(width, _)| width.to_string())
.unwrap_or_else(|| "100".to_string());

cmd.env("CARGO_TERM_COLOR", "always")
.env("CARGO_TERM_PROGRESS_WIDTH", term_size)
.env("CARGO_TERM_PROGRESS_WHEN", "always")
}

/// Returns the base name of the path.
Expand Down

0 comments on commit 85688a6

Please sign in to comment.