use self::{
error::VyperCompilationError,
input::{VyperInput, VyperVersionedInput},
parser::VyperParsedSource,
};
use super::{Compiler, CompilerOutput, Language};
use crate::{
artifacts::Source,
error::{Result, SolcError},
};
use core::fmt;
use output::VyperOutput;
use semver::Version;
use serde::{de::DeserializeOwned, Serialize};
use std::{
path::{Path, PathBuf},
process::{Command, Stdio},
str::FromStr,
};
pub mod error;
pub mod input;
pub mod output;
pub mod parser;
pub mod settings;
pub use settings::VyperSettings;
pub const VYPER_EXTENSIONS: &[&str] = &["vy", "vyi"];
pub const VYPER_INTERFACE_EXTENSION: &str = "vyi";
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
#[non_exhaustive]
pub struct VyperLanguage;
impl Language for VyperLanguage {
const FILE_EXTENSIONS: &'static [&'static str] = VYPER_EXTENSIONS;
}
impl fmt::Display for VyperLanguage {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Vyper")
}
}
#[derive(Debug, Clone)]
pub struct Vyper {
pub path: PathBuf,
pub version: Version,
}
impl Vyper {
pub fn new(path: impl AsRef<Path>) -> Result<Self> {
let path = path.as_ref();
let version = Self::version(path)?;
Ok(Self { path: path.into(), version })
}
pub fn compile_source(&self, path: impl AsRef<Path>) -> Result<VyperOutput> {
let path = path.as_ref();
let input =
VyperInput::new(Source::read_all_from(path, VYPER_EXTENSIONS)?, Default::default());
self.compile(&input)
}
pub fn compile_exact(&self, input: &VyperInput) -> Result<VyperOutput> {
let mut out = self.compile(input)?;
out.retain_files(input.sources.keys().map(|p| p.as_path()));
Ok(out)
}
pub fn compile<T: Serialize>(&self, input: &T) -> Result<VyperOutput> {
self.compile_as(input)
}
pub fn compile_as<T: Serialize, D: DeserializeOwned>(&self, input: &T) -> Result<D> {
let output = self.compile_output(input)?;
let output = std::str::from_utf8(&output).map_err(|_| SolcError::InvalidUtf8)?;
trace!("vyper compiler output: {}", output);
Ok(serde_json::from_str(output)?)
}
#[instrument(name = "compile", level = "debug", skip_all)]
pub fn compile_output<T: Serialize>(&self, input: &T) -> Result<Vec<u8>> {
let mut cmd = Command::new(&self.path);
cmd.arg("--standard-json")
.stdin(Stdio::piped())
.stderr(Stdio::piped())
.stdout(Stdio::piped());
trace!(input=%serde_json::to_string(input).unwrap_or_else(|e| e.to_string()));
debug!(?cmd, "compiling");
let mut child = cmd.spawn().map_err(self.map_io_err())?;
debug!("spawned");
let stdin = child.stdin.as_mut().unwrap();
serde_json::to_writer(stdin, input)?;
debug!("wrote JSON input to stdin");
let output = child.wait_with_output().map_err(self.map_io_err())?;
debug!(%output.status, output.stderr = ?String::from_utf8_lossy(&output.stderr), "finished");
if output.status.success() {
Ok(output.stdout)
} else {
Err(SolcError::solc_output(&output))
}
}
#[instrument(level = "debug", skip_all)]
pub fn version(vyper: impl Into<PathBuf>) -> Result<Version> {
let vyper = vyper.into();
let mut cmd = Command::new(vyper.clone());
cmd.arg("--version").stdin(Stdio::piped()).stderr(Stdio::piped()).stdout(Stdio::piped());
debug!(?cmd, "getting Vyper version");
let output = cmd.output().map_err(|e| SolcError::io(e, vyper))?;
trace!(?output);
if output.status.success() {
let stdout = String::from_utf8_lossy(&output.stdout);
Ok(Version::from_str(&stdout.trim().replace("rc", "-rc"))?)
} else {
Err(SolcError::solc_output(&output))
}
}
fn map_io_err(&self) -> impl FnOnce(std::io::Error) -> SolcError + '_ {
move |err| SolcError::io(err, &self.path)
}
}
impl Compiler for Vyper {
type Settings = VyperSettings;
type CompilationError = VyperCompilationError;
type ParsedSource = VyperParsedSource;
type Input = VyperVersionedInput;
type Language = VyperLanguage;
fn compile(&self, input: &Self::Input) -> Result<CompilerOutput<VyperCompilationError>> {
self.compile(input).map(Into::into)
}
fn available_versions(&self, _language: &Self::Language) -> Vec<super::CompilerVersion> {
vec![super::CompilerVersion::Installed(Version::new(
self.version.major,
self.version.minor,
self.version.patch,
))]
}
}