Merge pull request #994 from aiken-lang/conditional-modules

Conditional configuration & environment
This commit is contained in:
Matthias Benkort 2024-08-06 09:05:11 +02:00 committed by GitHub
commit e6bb13def6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 548 additions and 111 deletions

View File

@ -6,6 +6,7 @@
- **aiken-lang**: add support for `mk_cons` and `mk_pair_data` builtins. See [#964](https://github.com/aiken-lang/aiken/issues/964). @KtorZ - **aiken-lang**: add support for `mk_cons` and `mk_pair_data` builtins. See [#964](https://github.com/aiken-lang/aiken/issues/964). @KtorZ
- **aiken-lang**: pattern-matching on bytearrays is now available. See [#989](https://github.com/aiken-lang/aiken/issues/989). @KtorZ - **aiken-lang**: pattern-matching on bytearrays is now available. See [#989](https://github.com/aiken-lang/aiken/issues/989). @KtorZ
- **aiken-project**: conditional configuration and environment. See [#937](https://github.com/aiken-lang/aiken/issues/937). @KtorZ
### Changed ### Changed

View File

@ -21,6 +21,10 @@ pub const BACKPASS_VARIABLE: &str = "_backpass";
pub const CAPTURE_VARIABLE: &str = "_capture"; pub const CAPTURE_VARIABLE: &str = "_capture";
pub const PIPE_VARIABLE: &str = "_pipe"; pub const PIPE_VARIABLE: &str = "_pipe";
pub const ENV_MODULE: &str = "env";
pub const CONFIG_MODULE: &str = "config";
pub const DEFAULT_ENV_MODULE: &str = "default";
pub type TypedModule = Module<TypeInfo, TypedDefinition>; pub type TypedModule = Module<TypeInfo, TypedDefinition>;
pub type UntypedModule = Module<(), UntypedDefinition>; pub type UntypedModule = Module<(), UntypedDefinition>;
@ -28,6 +32,8 @@ pub type UntypedModule = Module<(), UntypedDefinition>;
pub enum ModuleKind { pub enum ModuleKind {
Lib, Lib,
Validator, Validator,
Env,
Config,
} }
impl ModuleKind { impl ModuleKind {
@ -38,6 +44,14 @@ impl ModuleKind {
pub fn is_lib(&self) -> bool { pub fn is_lib(&self) -> bool {
matches!(self, ModuleKind::Lib) matches!(self, ModuleKind::Lib)
} }
pub fn is_env(&self) -> bool {
matches!(self, ModuleKind::Env)
}
pub fn is_config(&self) -> bool {
matches!(self, ModuleKind::Config)
}
} }
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
@ -61,16 +75,18 @@ impl<Info, Definitions> Module<Info, Definitions> {
} }
impl UntypedModule { impl UntypedModule {
pub fn dependencies(&self) -> Vec<(String, Span)> { pub fn dependencies(&self, env_modules: &[String]) -> Vec<String> {
self.definitions() self.definitions()
.flat_map(|def| { .flat_map(|def| {
if let Definition::Use(Use { if let Definition::Use(Use { module, .. }) = def {
location, module, .. let name = module.join("/");
}) = def if name == ENV_MODULE {
{ env_modules.to_vec()
Some((module.join("/"), *location)) } else {
vec![name]
}
} else { } else {
None Vec::new()
} }
}) })
.collect() .collect()
@ -1069,6 +1085,15 @@ impl Annotation {
} }
} }
pub fn bytearray(location: Span) -> Self {
Annotation::Constructor {
name: "ByteArray".to_string(),
module: None,
arguments: vec![],
location,
}
}
pub fn data(location: Span) -> Self { pub fn data(location: Span) -> Self {
Annotation::Constructor { Annotation::Constructor {
name: "Data".to_string(), name: "Data".to_string(),

View File

@ -25,8 +25,8 @@ use ordinal::Ordinal;
use std::rc::Rc; use std::rc::Rc;
use vec1::Vec1; use vec1::Vec1;
const INDENT: isize = 2; pub const INDENT: isize = 2;
const DOCS_MAX_COLUMNS: isize = 80; pub const DOCS_MAX_COLUMNS: isize = 80;
pub fn pretty(writer: &mut String, module: UntypedModule, extra: ModuleExtra, src: &str) { pub fn pretty(writer: &mut String, module: UntypedModule, extra: ModuleExtra, src: &str) {
let intermediate = Intermediate { let intermediate = Intermediate {
@ -130,7 +130,7 @@ impl<'comments> Formatter<'comments> {
end != 0 end != 0
} }
fn definitions<'a>(&mut self, definitions: &'a [UntypedDefinition]) -> Document<'a> { pub fn definitions<'a>(&mut self, definitions: &'a [UntypedDefinition]) -> Document<'a> {
let mut has_imports = false; let mut has_imports = false;
let mut has_declarations = false; let mut has_declarations = false;
let mut imports = Vec::new(); let mut imports = Vec::new();

View File

@ -49,6 +49,7 @@ macro_rules! aiken_fn {
$module_types, $module_types,
$crate::ast::Tracing::silent(), $crate::ast::Tracing::silent(),
&mut warnings, &mut warnings,
None,
) )
.unwrap(); .unwrap();

View File

@ -38,6 +38,7 @@ fn check_module(
&module_types, &module_types,
Tracing::All(TraceLevel::Verbose), Tracing::All(TraceLevel::Verbose),
&mut warnings, &mut warnings,
None,
) )
.expect("extra dependency did not compile"); .expect("extra dependency did not compile");
module_types.insert(package.clone(), typed_module.type_info.clone()); module_types.insert(package.clone(), typed_module.type_info.clone());
@ -50,6 +51,7 @@ fn check_module(
&module_types, &module_types,
tracing, tracing,
&mut warnings, &mut warnings,
None,
); );
result result

View File

@ -7,7 +7,7 @@ use super::{
}; };
use crate::{ use crate::{
ast::{ ast::{
Annotation, CallArg, DataType, Definition, Function, ModuleConstant, ModuleKind, self, Annotation, CallArg, DataType, Definition, Function, ModuleConstant, ModuleKind,
RecordConstructor, RecordConstructorArg, Span, TypeAlias, TypedDefinition, TypedFunction, RecordConstructor, RecordConstructorArg, Span, TypeAlias, TypedDefinition, TypedFunction,
TypedPattern, UnqualifiedImport, UntypedArg, UntypedDefinition, UntypedFunction, Use, TypedPattern, UnqualifiedImport, UntypedArg, UntypedDefinition, UntypedFunction, Use,
Validator, PIPE_VARIABLE, Validator, PIPE_VARIABLE,
@ -80,11 +80,49 @@ pub struct Environment<'a> {
/// A mapping from known annotations to their resolved type. /// A mapping from known annotations to their resolved type.
pub annotations: HashMap<Annotation, Rc<Type>>, pub annotations: HashMap<Annotation, Rc<Type>>,
/// The user-defined target environment referred to as the module 'env'.
pub target_env: Option<&'a str>,
/// Warnings /// Warnings
pub warnings: &'a mut Vec<Warning>, pub warnings: &'a mut Vec<Warning>,
} }
impl<'a> Environment<'a> { impl<'a> Environment<'a> {
pub fn find_module(&self, fragments: &[String], location: Span) -> Result<&'a TypeInfo, Error> {
let mut name = fragments.join("/");
let is_env = name == ast::ENV_MODULE;
if is_env {
name = self
.target_env
.unwrap_or(ast::DEFAULT_ENV_MODULE)
.to_string()
}
self.importable_modules.get(&name).ok_or_else(|| {
if is_env {
Error::UnknownEnvironment {
name,
known_environments: self
.importable_modules
.values()
.filter_map(|m| match m.kind {
ModuleKind::Env => Some(m.name.clone()),
ModuleKind::Lib | ModuleKind::Validator | ModuleKind::Config => None,
})
.collect(),
}
} else {
Error::UnknownModule {
location,
name,
known_modules: self.importable_modules.keys().cloned().collect(),
}
}
})
}
pub fn close_scope(&mut self, data: ScopeResetData) { pub fn close_scope(&mut self, data: ScopeResetData) {
let unused = self let unused = self
.entity_usages .entity_usages
@ -351,7 +389,7 @@ impl<'a> Environment<'a> {
.ok_or_else(|| Error::UnknownModule { .ok_or_else(|| Error::UnknownModule {
location, location,
name: name.to_string(), name: name.to_string(),
imported_modules: self known_modules: self
.importable_modules .importable_modules
.keys() .keys()
.map(|t| t.to_string()) .map(|t| t.to_string())
@ -397,7 +435,7 @@ impl<'a> Environment<'a> {
.get(m) .get(m)
.ok_or_else(|| Error::UnknownModule { .ok_or_else(|| Error::UnknownModule {
name: m.to_string(), name: m.to_string(),
imported_modules: self known_modules: self
.importable_modules .importable_modules
.keys() .keys()
.map(|t| t.to_string()) .map(|t| t.to_string())
@ -705,6 +743,7 @@ impl<'a> Environment<'a> {
current_kind: &'a ModuleKind, current_kind: &'a ModuleKind,
importable_modules: &'a HashMap<String, TypeInfo>, importable_modules: &'a HashMap<String, TypeInfo>,
warnings: &'a mut Vec<Warning>, warnings: &'a mut Vec<Warning>,
target_env: Option<&'a str>,
) -> Self { ) -> Self {
let prelude = importable_modules let prelude = importable_modules
.get("aiken") .get("aiken")
@ -731,6 +770,7 @@ impl<'a> Environment<'a> {
annotations: HashMap::new(), annotations: HashMap::new(),
warnings, warnings,
entity_usages: vec![HashMap::new()], entity_usages: vec![HashMap::new()],
target_env,
} }
} }
@ -772,24 +812,16 @@ impl<'a> Environment<'a> {
location, location,
package: _, package: _,
}) => { }) => {
let name = module.join("/"); let module_info = self.find_module(module, *location)?;
// Find imported module
let module_info =
self.importable_modules
.get(&name)
.ok_or_else(|| Error::UnknownModule {
location: *location,
name: name.clone(),
imported_modules: self.imported_modules.keys().cloned().collect(),
})?;
if module_info.kind.is_validator() if module_info.kind.is_validator()
&& (self.current_kind.is_lib() || !self.current_module.starts_with("tests")) && (self.current_kind.is_lib()
|| self.current_kind.is_env()
|| !self.current_module.starts_with("tests"))
{ {
return Err(Error::ValidatorImported { return Err(Error::ValidatorImported {
location: *location, location: *location,
name, name: module.join("/"),
}); });
} }
@ -1710,7 +1742,7 @@ impl<'a> Environment<'a> {
.ok_or_else(|| Error::UnknownModule { .ok_or_else(|| Error::UnknownModule {
location, location,
name: name.to_string(), name: name.to_string(),
imported_modules: self known_modules: self
.importable_modules .importable_modules
.keys() .keys()
.map(|t| t.to_string()) .map(|t| t.to_string())

View File

@ -751,13 +751,39 @@ Perhaps, try the following:
#[diagnostic(code("unknown::module"))] #[diagnostic(code("unknown::module"))]
#[diagnostic(help( #[diagnostic(help(
"{}", "{}",
suggest_neighbor(name, imported_modules.iter(), "Did you forget to add a package as dependency?") suggest_neighbor(name, known_modules.iter(), "Did you forget to add a package as dependency?")
))] ))]
UnknownModule { UnknownModule {
#[label] #[label]
location: Span, location: Span,
name: String, name: String,
imported_modules: Vec<String>, known_modules: Vec<String>,
},
#[error(
"I couldn't find any module for the environment: '{}'\n",
name.if_supports_color(Stdout, |s| s.purple())
)]
#[diagnostic(code("unknown::environment"))]
#[diagnostic(help(
"{}{}",
if known_environments.is_empty() {
String::new()
} else {
format!(
"I know about the following environments:\n{}\n\n",
known_environments
.iter()
.map(|s| format!("─▶ {}", s.if_supports_color(Stdout, |s| s.purple())))
.collect::<Vec<_>>()
.join("\n")
)
},
suggest_neighbor(name, known_environments.iter(), "Did you forget to define this environment?")
))]
UnknownEnvironment {
name: String,
known_environments: Vec<String>,
}, },
#[error( #[error(
@ -1066,6 +1092,7 @@ impl ExtraData for Error {
| Error::UnknownModuleType { .. } | Error::UnknownModuleType { .. }
| Error::UnknownModuleValue { .. } | Error::UnknownModuleValue { .. }
| Error::UnknownRecordField { .. } | Error::UnknownRecordField { .. }
| Error::UnknownEnvironment { .. }
| Error::UnnecessarySpreadOperator { .. } | Error::UnnecessarySpreadOperator { .. }
| Error::UpdateMultiConstructorType { .. } | Error::UpdateMultiConstructorType { .. }
| Error::ValidatorImported { .. } | Error::ValidatorImported { .. }

View File

@ -956,9 +956,9 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
.ok_or_else(|| Error::UnknownModule { .ok_or_else(|| Error::UnknownModule {
name: module_alias.to_string(), name: module_alias.to_string(),
location: *module_location, location: *module_location,
imported_modules: self known_modules: self
.environment .environment
.imported_modules .importable_modules
.keys() .keys()
.map(|t| t.to_string()) .map(|t| t.to_string())
.collect(), .collect(),
@ -2327,9 +2327,9 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
.ok_or_else(|| Error::UnknownModule { .ok_or_else(|| Error::UnknownModule {
location: *location, location: *location,
name: module_name.to_string(), name: module_name.to_string(),
imported_modules: self known_modules: self
.environment .environment
.imported_modules .importable_modules
.keys() .keys()
.map(|t| t.to_string()) .map(|t| t.to_string())
.collect(), .collect(),

View File

@ -19,6 +19,7 @@ use crate::{
use std::{borrow::Borrow, collections::HashMap, ops::Deref, rc::Rc}; use std::{borrow::Borrow, collections::HashMap, ops::Deref, rc::Rc};
impl UntypedModule { impl UntypedModule {
#[allow(clippy::too_many_arguments)]
pub fn infer( pub fn infer(
mut self, mut self,
id_gen: &IdGenerator, id_gen: &IdGenerator,
@ -27,11 +28,12 @@ impl UntypedModule {
modules: &HashMap<String, TypeInfo>, modules: &HashMap<String, TypeInfo>,
tracing: Tracing, tracing: Tracing,
warnings: &mut Vec<Warning>, warnings: &mut Vec<Warning>,
env: Option<&str>,
) -> Result<TypedModule, Error> { ) -> Result<TypedModule, Error> {
let module_name = self.name.clone(); let module_name = self.name.clone();
let docs = std::mem::take(&mut self.docs); let docs = std::mem::take(&mut self.docs);
let mut environment = let mut environment =
Environment::new(id_gen.clone(), &module_name, &kind, modules, warnings); Environment::new(id_gen.clone(), &module_name, &kind, modules, warnings, env);
let mut type_names = HashMap::with_capacity(self.definitions.len()); let mut type_names = HashMap::with_capacity(self.definitions.len());
let mut value_names = HashMap::with_capacity(self.definitions.len()); let mut value_names = HashMap::with_capacity(self.definitions.len());
@ -574,18 +576,7 @@ fn infer_definition(
unqualified, unqualified,
package: _, package: _,
}) => { }) => {
let name = module.join("/"); let module_info = environment.find_module(&module, location)?;
// Find imported module
let module_info =
environment
.importable_modules
.get(&name)
.ok_or_else(|| Error::UnknownModule {
location,
name,
imported_modules: environment.imported_modules.keys().cloned().collect(),
})?;
Ok(Definition::Use(Use { Ok(Definition::Use(Use {
location, location,

View File

@ -40,6 +40,7 @@ impl LspProject {
u32::default(), u32::default(),
PropertyTest::DEFAULT_MAX_SUCCESS, PropertyTest::DEFAULT_MAX_SUCCESS,
Tracing::silent(), Tracing::silent(),
None,
); );
self.project.restore(checkpoint); self.project.restore(checkpoint);

View File

@ -1,13 +1,20 @@
use std::{fmt::Display, fs, io, path::Path};
use crate::{github::repo::LatestRelease, package_name::PackageName, paths, Error}; use crate::{github::repo::LatestRelease, package_name::PackageName, paths, Error};
use aiken_lang::ast::Span;
use semver::Version;
use miette::NamedSource;
use serde::{Deserialize, Serialize};
pub use aiken_lang::plutus_version::PlutusVersion; pub use aiken_lang::plutus_version::PlutusVersion;
use aiken_lang::{
ast::{
Annotation, ByteArrayFormatPreference, Constant, ModuleConstant, Span, UntypedDefinition,
},
expr::UntypedExpr,
parser::token::Base,
};
use miette::NamedSource;
use semver::Version;
use serde::{
de,
ser::{self, SerializeSeq, SerializeStruct},
Deserialize, Serialize,
};
use std::{collections::BTreeMap, fmt::Display, fs, io, path::Path};
#[derive(Deserialize, Serialize, Clone)] #[derive(Deserialize, Serialize, Clone)]
pub struct Config { pub struct Config {
@ -27,6 +34,195 @@ pub struct Config {
pub repository: Option<Repository>, pub repository: Option<Repository>,
#[serde(default)] #[serde(default)]
pub dependencies: Vec<Dependency>, pub dependencies: Vec<Dependency>,
#[serde(default)]
pub config: BTreeMap<String, BTreeMap<String, SimpleExpr>>,
}
#[derive(Clone, Debug)]
pub enum SimpleExpr {
Int(i64),
Bool(bool),
ByteArray(Vec<u8>, ByteArrayFormatPreference),
List(Vec<SimpleExpr>),
}
impl SimpleExpr {
pub fn as_untyped_expr(&self) -> UntypedExpr {
match self {
SimpleExpr::Bool(b) => UntypedExpr::Var {
location: Span::empty(),
name: if *b { "True" } else { "False" }.to_string(),
},
SimpleExpr::Int(i) => UntypedExpr::UInt {
location: Span::empty(),
value: format!("{i}"),
base: Base::Decimal {
numeric_underscore: false,
},
},
SimpleExpr::ByteArray(bs, preferred_format) => UntypedExpr::ByteArray {
location: Span::empty(),
bytes: bs.to_vec(),
preferred_format: *preferred_format,
},
SimpleExpr::List(es) => UntypedExpr::List {
location: Span::empty(),
elements: es.iter().map(|e| e.as_untyped_expr()).collect(),
tail: None,
},
}
}
pub fn as_definition(&self, identifier: &str) -> UntypedDefinition {
let location = Span::empty();
let (value, annotation) = match self {
SimpleExpr::Bool(..) => todo!("requires https://github.com/aiken-lang/aiken/pull/992"),
SimpleExpr::Int(i) => (
// TODO: Replace with 'self.as_untyped_expr()' after https://github.com/aiken-lang/aiken/pull/992
Constant::Int {
location,
value: format!("{i}"),
base: Base::Decimal {
numeric_underscore: false,
},
},
Some(Annotation::int(location)),
),
SimpleExpr::ByteArray(bs, preferred_format) => (
// TODO: Replace with 'self.as_untyped_expr()' after https://github.com/aiken-lang/aiken/pull/992
Constant::ByteArray {
location,
bytes: bs.to_vec(),
preferred_format: *preferred_format,
},
Some(Annotation::bytearray(location)),
),
SimpleExpr::List(..) => todo!("requires https://github.com/aiken-lang/aiken/pull/992"),
};
UntypedDefinition::ModuleConstant(ModuleConstant {
location: Span::empty(),
doc: None,
public: true,
name: identifier.to_string(),
annotation,
value: Box::new(value),
tipo: (),
})
}
}
impl Serialize for SimpleExpr {
fn serialize<S: ser::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
match self {
SimpleExpr::Bool(b) => serializer.serialize_bool(*b),
SimpleExpr::Int(i) => serializer.serialize_i64(*i),
SimpleExpr::ByteArray(bs, preferred_format) => match preferred_format {
ByteArrayFormatPreference::Utf8String => {
serializer.serialize_str(String::from_utf8(bs.to_vec()).unwrap().as_str())
}
ByteArrayFormatPreference::ArrayOfBytes(..)
| ByteArrayFormatPreference::HexadecimalString => {
let mut s = serializer.serialize_struct("ByteArray", 2)?;
s.serialize_field("bytes", &hex::encode(bs))?;
s.serialize_field("encoding", "base16")?;
s.end()
}
},
SimpleExpr::List(es) => {
let mut seq = serializer.serialize_seq(Some(es.len()))?;
for e in es {
seq.serialize_element(e)?;
}
seq.end()
}
}
}
}
impl<'a> Deserialize<'a> for SimpleExpr {
fn deserialize<D: de::Deserializer<'a>>(deserializer: D) -> Result<Self, D::Error> {
struct SimpleExprVisitor;
#[derive(Deserialize)]
enum Encoding {
#[serde(rename(deserialize = "utf8"))]
Utf8,
#[serde(rename(deserialize = "utf-8"))]
Utf8Bis,
#[serde(rename(deserialize = "hex"))]
Hex,
#[serde(rename(deserialize = "base16"))]
Base16,
}
#[derive(Deserialize)]
struct Bytes {
bytes: String,
encoding: Encoding,
}
impl<'a> de::Visitor<'a> for SimpleExprVisitor {
type Value = SimpleExpr;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("Int | Bool | ByteArray | List<any_of_those>")
}
fn visit_bool<E>(self, b: bool) -> Result<Self::Value, E> {
Ok(SimpleExpr::Bool(b))
}
fn visit_i64<E>(self, i: i64) -> Result<Self::Value, E> {
Ok(SimpleExpr::Int(i))
}
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E> {
Ok(SimpleExpr::ByteArray(
s.as_bytes().to_vec(),
ByteArrayFormatPreference::Utf8String,
))
}
fn visit_map<V>(self, map: V) -> Result<Self::Value, V::Error>
where
V: de::MapAccess<'a>,
{
let Bytes { bytes, encoding } =
Bytes::deserialize(de::value::MapAccessDeserializer::new(map))?;
match encoding {
Encoding::Hex | Encoding::Base16 => match hex::decode(&bytes) {
Err(e) => Err(de::Error::custom(format!("invalid base16 string: {e:?}"))),
Ok(bytes) => Ok(SimpleExpr::ByteArray(
bytes,
ByteArrayFormatPreference::HexadecimalString,
)),
},
Encoding::Utf8 | Encoding::Utf8Bis => Ok(SimpleExpr::ByteArray(
bytes.as_bytes().to_vec(),
ByteArrayFormatPreference::Utf8String,
)),
}
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: de::SeqAccess<'a>,
{
let mut es = Vec::new();
while let Some(e) = seq.next_element()? {
es.push(e);
}
Ok(SimpleExpr::List(es))
}
}
deserializer.deserialize_any(SimpleExprVisitor)
}
} }
fn deserialize_version<'de, D>(deserializer: D) -> Result<Version, D::Error> fn deserialize_version<'de, D>(deserializer: D) -> Result<Version, D::Error>
@ -108,6 +304,7 @@ impl Config {
}, },
source: Platform::Github, source: Platform::Github,
}], }],
config: BTreeMap::new(),
} }
} }
@ -181,3 +378,53 @@ Version: {}"#,
compiler_version(true), compiler_version(true),
) )
} }
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
#[allow(clippy::arc_with_non_send_sync)]
fn arbitrary_simple_expr() -> impl Strategy<Value = SimpleExpr> {
let leaf = prop_oneof![
(any::<i64>)().prop_map(SimpleExpr::Int),
(any::<bool>)().prop_map(SimpleExpr::Bool),
"[a-z0-9]*".prop_map(|bytes| SimpleExpr::ByteArray(
bytes.as_bytes().to_vec(),
ByteArrayFormatPreference::Utf8String
)),
"([0-9a-f][0-9a-f])*".prop_map(|bytes| SimpleExpr::ByteArray(
bytes.as_bytes().to_vec(),
ByteArrayFormatPreference::HexadecimalString
))
];
leaf.prop_recursive(3, 8, 3, |inner| {
prop_oneof![
inner.clone(),
prop::collection::vec(inner.clone(), 0..3).prop_map(SimpleExpr::List)
]
})
}
#[derive(Deserialize, Serialize)]
struct TestConfig {
expr: SimpleExpr,
}
proptest! {
#[test]
fn round_trip_simple_expr(expr in arbitrary_simple_expr()) {
let pretty = toml::to_string_pretty(&TestConfig { expr });
assert!(
matches!(
pretty.as_ref().map(|s| toml::from_str::<TestConfig>(s.as_str())),
Ok(Ok(..)),
),
"\ncounterexample: {}\n",
pretty.unwrap_or_default(),
)
}
}
}

View File

@ -127,6 +127,9 @@ pub enum Error {
#[error("I couldn't find any exportable function named '{name}' in module '{module}'.")] #[error("I couldn't find any exportable function named '{name}' in module '{module}'.")]
ExportNotFound { module: String, name: String }, ExportNotFound { module: String, name: String },
#[error("I located conditional modules under 'env', but no default one!")]
NoDefaultEnvironment,
} }
impl Error { impl Error {
@ -195,6 +198,7 @@ impl ExtraData for Error {
| Error::NoValidatorNotFound { .. } | Error::NoValidatorNotFound { .. }
| Error::MoreThanOneValidatorFound { .. } | Error::MoreThanOneValidatorFound { .. }
| Error::Module { .. } | Error::Module { .. }
| Error::NoDefaultEnvironment { .. }
| Error::ExportNotFound { .. } => None, | Error::ExportNotFound { .. } => None,
Error::Type { error, .. } => error.extra_data(), Error::Type { error, .. } => error.extra_data(),
} }
@ -224,6 +228,7 @@ impl GetSource for Error {
| Error::NoValidatorNotFound { .. } | Error::NoValidatorNotFound { .. }
| Error::MoreThanOneValidatorFound { .. } | Error::MoreThanOneValidatorFound { .. }
| Error::ExportNotFound { .. } | Error::ExportNotFound { .. }
| Error::NoDefaultEnvironment { .. }
| Error::Module { .. } => None, | Error::Module { .. } => None,
Error::DuplicateModule { second: path, .. } Error::DuplicateModule { second: path, .. }
| Error::MissingManifest { path } | Error::MissingManifest { path }
@ -252,6 +257,7 @@ impl GetSource for Error {
| Error::Json { .. } | Error::Json { .. }
| Error::MalformedStakeAddress { .. } | Error::MalformedStakeAddress { .. }
| Error::NoValidatorNotFound { .. } | Error::NoValidatorNotFound { .. }
| Error::NoDefaultEnvironment { .. }
| Error::MoreThanOneValidatorFound { .. } | Error::MoreThanOneValidatorFound { .. }
| Error::ExportNotFound { .. } | Error::ExportNotFound { .. }
| Error::Module { .. } => None, | Error::Module { .. } => None,
@ -307,6 +313,7 @@ impl Diagnostic for Error {
Error::NoValidatorNotFound { .. } => None, Error::NoValidatorNotFound { .. } => None,
Error::MoreThanOneValidatorFound { .. } => None, Error::MoreThanOneValidatorFound { .. } => None,
Error::ExportNotFound { .. } => None, Error::ExportNotFound { .. } => None,
Error::NoDefaultEnvironment { .. } => None,
Error::Module(e) => e.code().map(boxed), Error::Module(e) => e.code().map(boxed),
} }
} }
@ -330,6 +337,9 @@ impl Diagnostic for Error {
Error::MissingManifest { .. } => Some(Box::new( Error::MissingManifest { .. } => Some(Box::new(
"Try running `aiken new <REPOSITORY/PROJECT>` to initialise a project with an example manifest.", "Try running `aiken new <REPOSITORY/PROJECT>` to initialise a project with an example manifest.",
)), )),
Error::NoDefaultEnvironment { .. } => Some(Box::new(
"Environment module names are free, but there must be at least one named 'default.ak'.",
)),
Error::TomlLoading { .. } => None, Error::TomlLoading { .. } => None,
Error::Format { .. } => None, Error::Format { .. } => None,
Error::TestFailure { .. } => None, Error::TestFailure { .. } => None,
@ -408,6 +418,7 @@ impl Diagnostic for Error {
Error::MalformedStakeAddress { .. } => None, Error::MalformedStakeAddress { .. } => None,
Error::NoValidatorNotFound { .. } => None, Error::NoValidatorNotFound { .. } => None,
Error::MoreThanOneValidatorFound { .. } => None, Error::MoreThanOneValidatorFound { .. } => None,
Error::NoDefaultEnvironment { .. } => None,
Error::Module(e) => e.labels(), Error::Module(e) => e.labels(),
} }
} }
@ -419,6 +430,7 @@ impl Diagnostic for Error {
Error::ImportCycle { .. } => None, Error::ImportCycle { .. } => None,
Error::ExportNotFound { .. } => None, Error::ExportNotFound { .. } => None,
Error::Blueprint(e) => e.source_code(), Error::Blueprint(e) => e.source_code(),
Error::NoDefaultEnvironment { .. } => None,
Error::Parse { named, .. } => Some(named), Error::Parse { named, .. } => Some(named),
Error::Type { named, .. } => Some(named), Error::Type { named, .. } => Some(named),
Error::StandardIo(_) => None, Error::StandardIo(_) => None,
@ -462,6 +474,7 @@ impl Diagnostic for Error {
Error::MalformedStakeAddress { .. } => None, Error::MalformedStakeAddress { .. } => None,
Error::NoValidatorNotFound { .. } => None, Error::NoValidatorNotFound { .. } => None,
Error::MoreThanOneValidatorFound { .. } => None, Error::MoreThanOneValidatorFound { .. } => None,
Error::NoDefaultEnvironment { .. } => None,
Error::Module(e) => e.url(), Error::Module(e) => e.url(),
} }
} }
@ -476,6 +489,7 @@ impl Diagnostic for Error {
Error::Parse { .. } => None, Error::Parse { .. } => None,
Error::Type { error, .. } => error.related(), Error::Type { error, .. } => error.related(),
Error::StandardIo(_) => None, Error::StandardIo(_) => None,
Error::NoDefaultEnvironment { .. } => None,
Error::MissingManifest { .. } => None, Error::MissingManifest { .. } => None,
Error::TomlLoading { .. } => None, Error::TomlLoading { .. } => None,
Error::Format { .. } => None, Error::Format { .. } => None,
@ -512,6 +526,8 @@ pub enum Warning {
InvalidModuleName { path: PathBuf }, InvalidModuleName { path: PathBuf },
#[error("aiken.toml demands compiler version {demanded}, but you are using {current}.")] #[error("aiken.toml demands compiler version {demanded}, but you are using {current}.")]
CompilerVersionMismatch { demanded: String, current: String }, CompilerVersionMismatch { demanded: String, current: String },
#[error("No configuration found for environment {env}.")]
NoConfigurationForEnv { env: String },
} }
impl ExtraData for Warning { impl ExtraData for Warning {
@ -520,7 +536,8 @@ impl ExtraData for Warning {
Warning::NoValidators { .. } Warning::NoValidators { .. }
| Warning::DependencyAlreadyExists { .. } | Warning::DependencyAlreadyExists { .. }
| Warning::InvalidModuleName { .. } | Warning::InvalidModuleName { .. }
| Warning::CompilerVersionMismatch { .. } => None, | Warning::CompilerVersionMismatch { .. }
| Warning::NoConfigurationForEnv { .. } => None,
Warning::Type { warning, .. } => warning.extra_data(), Warning::Type { warning, .. } => warning.extra_data(),
} }
} }
@ -532,6 +549,7 @@ impl GetSource for Warning {
Warning::InvalidModuleName { path } | Warning::Type { path, .. } => Some(path.clone()), Warning::InvalidModuleName { path } | Warning::Type { path, .. } => Some(path.clone()),
Warning::NoValidators Warning::NoValidators
| Warning::DependencyAlreadyExists { .. } | Warning::DependencyAlreadyExists { .. }
| Warning::NoConfigurationForEnv { .. }
| Warning::CompilerVersionMismatch { .. } => None, | Warning::CompilerVersionMismatch { .. } => None,
} }
} }
@ -542,6 +560,7 @@ impl GetSource for Warning {
Warning::NoValidators Warning::NoValidators
| Warning::InvalidModuleName { .. } | Warning::InvalidModuleName { .. }
| Warning::DependencyAlreadyExists { .. } | Warning::DependencyAlreadyExists { .. }
| Warning::NoConfigurationForEnv { .. }
| Warning::CompilerVersionMismatch { .. } => None, | Warning::CompilerVersionMismatch { .. } => None,
} }
} }
@ -557,6 +576,7 @@ impl Diagnostic for Warning {
Warning::Type { named, .. } => Some(named), Warning::Type { named, .. } => Some(named),
Warning::NoValidators Warning::NoValidators
| Warning::InvalidModuleName { .. } | Warning::InvalidModuleName { .. }
| Warning::NoConfigurationForEnv { .. }
| Warning::DependencyAlreadyExists { .. } | Warning::DependencyAlreadyExists { .. }
| Warning::CompilerVersionMismatch { .. } => None, | Warning::CompilerVersionMismatch { .. } => None,
} }
@ -568,6 +588,7 @@ impl Diagnostic for Warning {
Warning::InvalidModuleName { .. } Warning::InvalidModuleName { .. }
| Warning::NoValidators | Warning::NoValidators
| Warning::DependencyAlreadyExists { .. } | Warning::DependencyAlreadyExists { .. }
| Warning::NoConfigurationForEnv { .. }
| Warning::CompilerVersionMismatch { .. } => None, | Warning::CompilerVersionMismatch { .. } => None,
} }
} }
@ -586,6 +607,9 @@ impl Diagnostic for Warning {
Warning::DependencyAlreadyExists { .. } => { Warning::DependencyAlreadyExists { .. } => {
Some(Box::new("aiken::packages::already_exists")) Some(Box::new("aiken::packages::already_exists"))
} }
Warning::NoConfigurationForEnv { .. } => {
Some(Box::new("aiken::project::config::missing::env"))
}
} }
} }
@ -603,6 +627,9 @@ impl Diagnostic for Warning {
Warning::DependencyAlreadyExists { .. } => Some(Box::new( Warning::DependencyAlreadyExists { .. } => Some(Box::new(
"If you need to change the version, try 'aiken packages upgrade' instead.", "If you need to change the version, try 'aiken packages upgrade' instead.",
)), )),
Warning::NoConfigurationForEnv { .. } => Some(Box::new(
"When configuration keys are missing for a target environment, no 'config' module will be created. This may lead to issues down the line.",
)),
} }
} }
} }

View File

@ -32,11 +32,12 @@ use crate::{
}; };
use aiken_lang::{ use aiken_lang::{
ast::{ ast::{
DataTypeKey, Definition, FunctionAccessKey, ModuleKind, Tracing, TypedDataType, self, DataTypeKey, Definition, FunctionAccessKey, ModuleKind, Tracing, TypedDataType,
TypedFunction, TypedFunction, UntypedDefinition,
}, },
builtins, builtins,
expr::UntypedExpr, expr::UntypedExpr,
format::{Formatter, DOCS_MAX_COLUMNS},
gen_uplc::CodeGenerator, gen_uplc::CodeGenerator,
line_numbers::LineNumbers, line_numbers::LineNumbers,
plutus_version::PlutusVersion, plutus_version::PlutusVersion,
@ -78,6 +79,12 @@ pub struct Checkpoint {
defined_modules: HashMap<String, PathBuf>, defined_modules: HashMap<String, PathBuf>,
} }
#[derive(Debug, Clone)]
enum AddModuleBy {
Source { name: String, code: String },
Path(PathBuf),
}
pub struct Project<T> pub struct Project<T>
where where
T: EventListener, T: EventListener,
@ -184,10 +191,16 @@ where
self.defined_modules = checkpoint.defined_modules; self.defined_modules = checkpoint.defined_modules;
} }
pub fn build(&mut self, uplc: bool, tracing: Tracing) -> Result<(), Vec<Error>> { pub fn build(
&mut self,
uplc: bool,
tracing: Tracing,
env: Option<String>,
) -> Result<(), Vec<Error>> {
let options = Options { let options = Options {
code_gen_mode: CodeGenMode::Build(uplc), code_gen_mode: CodeGenMode::Build(uplc),
tracing, tracing,
env,
}; };
self.compile(options) self.compile(options)
@ -205,11 +218,13 @@ where
version: self.config.version.clone(), version: self.config.version.clone(),
}); });
self.read_source_files()?; let config = self.config_definitions(None);
self.read_source_files(config)?;
let mut modules = self.parse_sources(self.config.name.clone())?; let mut modules = self.parse_sources(self.config.name.clone())?;
self.type_check(&mut modules, Tracing::silent(), false)?; self.type_check(&mut modules, Tracing::silent(), None, false)?;
let destination = destination.unwrap_or_else(|| self.root.join("docs")); let destination = destination.unwrap_or_else(|| self.root.join("docs"));
@ -250,9 +265,11 @@ where
seed: u32, seed: u32,
property_max_success: usize, property_max_success: usize,
tracing: Tracing, tracing: Tracing,
env: Option<String>,
) -> Result<(), Vec<Error>> { ) -> Result<(), Vec<Error>> {
let options = Options { let options = Options {
tracing, tracing,
env,
code_gen_mode: if skip_tests { code_gen_mode: if skip_tests {
CodeGenMode::NoOp CodeGenMode::NoOp
} else { } else {
@ -293,6 +310,32 @@ where
self.root.join("plutus.json") self.root.join("plutus.json")
} }
fn config_definitions(&mut self, env: Option<&str>) -> Option<Vec<UntypedDefinition>> {
if !self.config.config.is_empty() {
let env = env.unwrap_or(ast::DEFAULT_ENV_MODULE);
match self.config.config.get(env) {
None => {
self.warnings.push(Warning::NoConfigurationForEnv {
env: env.to_string(),
});
None
}
Some(config) => {
let mut conf_definitions = Vec::new();
for (identifier, value) in config.iter() {
conf_definitions.push(value.as_definition(identifier));
}
Some(conf_definitions)
}
}
} else {
None
}
}
pub fn compile(&mut self, options: Options) -> Result<(), Vec<Error>> { pub fn compile(&mut self, options: Options) -> Result<(), Vec<Error>> {
self.event_listener self.event_listener
.handle_event(Event::StartingCompilation { .handle_event(Event::StartingCompilation {
@ -301,11 +344,15 @@ where
version: self.config.version.clone(), version: self.config.version.clone(),
}); });
self.read_source_files()?; let env = options.env.as_deref();
let config = self.config_definitions(env);
self.read_source_files(config)?;
let mut modules = self.parse_sources(self.config.name.clone())?; let mut modules = self.parse_sources(self.config.name.clone())?;
self.type_check(&mut modules, options.tracing, true)?; self.type_check(&mut modules, options.tracing, env, true)?;
match options.code_gen_mode { match options.code_gen_mode {
CodeGenMode::Build(uplc_dump) => { CodeGenMode::Build(uplc_dump) => {
@ -610,12 +657,28 @@ where
Ok(()) Ok(())
} }
fn read_source_files(&mut self) -> Result<(), Error> { fn read_source_files(&mut self, config: Option<Vec<UntypedDefinition>>) -> Result<(), Error> {
let env = self.root.join("env");
let lib = self.root.join("lib"); let lib = self.root.join("lib");
let validators = self.root.join("validators"); let validators = self.root.join("validators");
let root = self.root.clone();
if let Some(defs) = config {
self.add_module(
AddModuleBy::Source {
name: ast::CONFIG_MODULE.to_string(),
code: Formatter::new()
.definitions(&defs[..])
.to_pretty_string(DOCS_MAX_COLUMNS),
},
&root,
ModuleKind::Config,
)?;
}
self.aiken_files(&validators, ModuleKind::Validator)?; self.aiken_files(&validators, ModuleKind::Validator)?;
self.aiken_files(&lib, ModuleKind::Lib)?; self.aiken_files(&lib, ModuleKind::Lib)?;
self.aiken_files(&env, ModuleKind::Env)?;
Ok(()) Ok(())
} }
@ -721,6 +784,7 @@ where
&mut self, &mut self,
modules: &mut ParsedModules, modules: &mut ParsedModules,
tracing: Tracing, tracing: Tracing,
env: Option<&str>,
validate_module_name: bool, validate_module_name: bool,
) -> Result<(), Vec<Error>> { ) -> Result<(), Vec<Error>> {
let our_modules: BTreeSet<String> = modules.keys().cloned().collect(); let our_modules: BTreeSet<String> = modules.keys().cloned().collect();
@ -733,6 +797,7 @@ where
&self.id_gen, &self.id_gen,
&self.config.name.to_string(), &self.config.name.to_string(),
tracing, tracing,
env,
validate_module_name, validate_module_name,
&mut self.module_sources, &mut self.module_sources,
&mut self.module_types, &mut self.module_types,
@ -879,12 +944,18 @@ where
} }
fn aiken_files(&mut self, dir: &Path, kind: ModuleKind) -> Result<(), Error> { fn aiken_files(&mut self, dir: &Path, kind: ModuleKind) -> Result<(), Error> {
let mut has_default = None;
walkdir::WalkDir::new(dir) walkdir::WalkDir::new(dir)
.follow_links(true) .follow_links(true)
.into_iter() .into_iter()
.filter_map(Result::ok) .filter_map(Result::ok)
.filter(|e| e.file_type().is_file()) .filter(|e| e.file_type().is_file())
.try_for_each(|d| { .try_for_each(|d| {
if has_default.is_none() {
has_default = Some(false);
}
let path = d.into_path(); let path = d.into_path();
let keep = is_aiken_path(&path, dir); let keep = is_aiken_path(&path, dir);
let ext = path.extension(); let ext = path.extension();
@ -895,19 +966,39 @@ where
} }
if keep { if keep {
self.add_module(path, dir, kind) if self.module_name(dir, &path).as_str() == ast::DEFAULT_ENV_MODULE {
has_default = Some(true);
}
self.add_module(AddModuleBy::Path(path), dir, kind)
} else { } else {
Ok(()) Ok(())
} }
}) })?;
if kind == ModuleKind::Env && has_default == Some(false) {
return Err(Error::NoDefaultEnvironment);
}
Ok(())
} }
fn add_module(&mut self, path: PathBuf, dir: &Path, kind: ModuleKind) -> Result<(), Error> { fn add_module(
let name = self.module_name(dir, &path); &mut self,
let code = fs::read_to_string(&path).map_err(|error| Error::FileIo { add_by: AddModuleBy,
path: path.clone(), dir: &Path,
error, kind: ModuleKind,
})?; ) -> Result<(), Error> {
let (name, code, path) = match add_by {
AddModuleBy::Path(path) => {
let name = self.module_name(dir, &path);
let code = fs::read_to_string(&path).map_err(|error| Error::FileIo {
path: path.clone(),
error,
})?;
(name, code, path)
}
AddModuleBy::Source { name, code } => (name, code, dir.to_path_buf()),
};
self.sources.push(Source { self.sources.push(Source {
name, name,

View File

@ -32,16 +32,9 @@ pub struct ParsedModule {
} }
impl ParsedModule { impl ParsedModule {
pub fn deps_for_graph(&self) -> (String, Vec<String>) { pub fn deps_for_graph(&self, env_modules: &[String]) -> (String, Vec<String>) {
let name = self.name.clone(); let name = self.name.clone();
let deps: Vec<_> = self.ast.dependencies(env_modules);
let deps: Vec<_> = self
.ast
.dependencies()
.into_iter()
.map(|(dep, _span)| dep)
.collect();
(name, deps) (name, deps)
} }
@ -51,6 +44,7 @@ impl ParsedModule {
id_gen: &IdGenerator, id_gen: &IdGenerator,
package: &str, package: &str,
tracing: Tracing, tracing: Tracing,
env: Option<&str>,
validate_module_name: bool, validate_module_name: bool,
module_sources: &mut HashMap<String, (String, LineNumbers)>, module_sources: &mut HashMap<String, (String, LineNumbers)>,
module_types: &mut HashMap<String, TypeInfo>, module_types: &mut HashMap<String, TypeInfo>,
@ -68,6 +62,7 @@ impl ParsedModule {
module_types, module_types,
tracing, tracing,
&mut warnings, &mut warnings,
env,
) )
.map_err(|error| Error::Type { .map_err(|error| Error::Type {
path: self.path.clone(), path: self.path.clone(),
@ -122,10 +117,19 @@ impl ParsedModules {
} }
pub fn sequence(&self, our_modules: &BTreeSet<String>) -> Result<Vec<String>, Error> { pub fn sequence(&self, our_modules: &BTreeSet<String>) -> Result<Vec<String>, Error> {
let env_modules = self
.0
.values()
.filter_map(|m| match m.kind {
ModuleKind::Env => Some(m.name.clone()),
ModuleKind::Lib | ModuleKind::Validator | ModuleKind::Config => None,
})
.collect::<Vec<String>>();
let inputs = self let inputs = self
.0 .0
.values() .values()
.map(|m| m.deps_for_graph()) .map(|m| m.deps_for_graph(&env_modules))
.collect::<Vec<(String, Vec<String>)>>(); .collect::<Vec<(String, Vec<String>)>>();
let capacity = inputs.len(); let capacity = inputs.len();

View File

@ -3,6 +3,7 @@ use aiken_lang::ast::Tracing;
pub struct Options { pub struct Options {
pub code_gen_mode: CodeGenMode, pub code_gen_mode: CodeGenMode,
pub tracing: Tracing, pub tracing: Tracing,
pub env: Option<String>,
} }
impl Default for Options { impl Default for Options {
@ -10,6 +11,7 @@ impl Default for Options {
Self { Self {
code_gen_mode: CodeGenMode::NoOp, code_gen_mode: CodeGenMode::NoOp,
tracing: Tracing::silent(), tracing: Tracing::silent(),
env: None,
} }
} }
} }

View File

@ -1314,6 +1314,7 @@ mod test {
&module_types, &module_types,
Tracing::All(TraceLevel::Verbose), Tracing::All(TraceLevel::Verbose),
&mut warnings, &mut warnings,
None,
) )
.expect("Failed to type-check module."); .expect("Failed to type-check module.");

View File

@ -99,6 +99,7 @@ impl TestProject {
&self.module_types, &self.module_types,
Tracing::All(TraceLevel::Verbose), Tracing::All(TraceLevel::Verbose),
&mut warnings, &mut warnings,
None,
) )
.expect("Failed to type-check module"); .expect("Failed to type-check module");

View File

@ -1,4 +1,3 @@
use aiken_lang::ast::Tracing;
use aiken_project::watch::with_project; use aiken_project::watch::with_project;
use std::path::PathBuf; use std::path::PathBuf;
@ -20,10 +19,6 @@ pub struct Args {
#[clap(long)] #[clap(long)]
delegated_to: Option<String>, delegated_to: Option<String>,
/// Force the project to be rebuilt, otherwise relies on existing artifacts (i.e. plutus.json)
#[clap(long)]
rebuild: bool,
/// Output the address for mainnet (this command defaults to testnet) /// Output the address for mainnet (this command defaults to testnet)
#[clap(long)] #[clap(long)]
mainnet: bool, mainnet: bool,
@ -35,15 +30,10 @@ pub fn exec(
module, module,
validator, validator,
delegated_to, delegated_to,
rebuild,
mainnet, mainnet,
}: Args, }: Args,
) -> miette::Result<()> { ) -> miette::Result<()> {
with_project(directory.as_deref(), false, |p| { with_project(directory.as_deref(), false, |p| {
if rebuild {
p.build(false, Tracing::silent())?;
}
let title = module.as_ref().map(|m| { let title = module.as_ref().map(|m| {
format!( format!(
"{m}{}", "{m}{}",

View File

@ -1,4 +1,3 @@
use aiken_lang::ast::Tracing;
use aiken_project::watch::with_project; use aiken_project::watch::with_project;
use std::path::PathBuf; use std::path::PathBuf;
@ -15,10 +14,6 @@ pub struct Args {
/// Name of the validator within the module. Optional if there's only one validator /// Name of the validator within the module. Optional if there's only one validator
#[clap(short, long)] #[clap(short, long)]
validator: Option<String>, validator: Option<String>,
/// Force the project to be rebuilt, otherwise relies on existing artifacts (i.e. plutus.json)
#[clap(long)]
rebuild: bool,
} }
pub fn exec( pub fn exec(
@ -26,14 +21,9 @@ pub fn exec(
directory, directory,
module, module,
validator, validator,
rebuild,
}: Args, }: Args,
) -> miette::Result<()> { ) -> miette::Result<()> {
with_project(directory.as_deref(), false, |p| { with_project(directory.as_deref(), false, |p| {
if rebuild {
p.build(false, Tracing::silent())?;
}
let title = module.as_ref().map(|m| { let title = module.as_ref().map(|m| {
format!( format!(
"{m}{}", "{m}{}",

View File

@ -1,4 +1,3 @@
use aiken_lang::ast::Tracing;
use aiken_project::watch::with_project; use aiken_project::watch::with_project;
use std::path::PathBuf; use std::path::PathBuf;
@ -15,10 +14,6 @@ pub struct Args {
/// Name of the validator within the module. Optional if there's only one validator /// Name of the validator within the module. Optional if there's only one validator
#[clap(short, long)] #[clap(short, long)]
validator: Option<String>, validator: Option<String>,
/// Force the project to be rebuilt, otherwise relies on existing artifacts (i.e. plutus.json)
#[clap(long)]
rebuild: bool,
} }
pub fn exec( pub fn exec(
@ -26,14 +21,9 @@ pub fn exec(
directory, directory,
module, module,
validator, validator,
rebuild,
}: Args, }: Args,
) -> miette::Result<()> { ) -> miette::Result<()> {
with_project(directory.as_deref(), false, |p| { with_project(directory.as_deref(), false, |p| {
if rebuild {
p.build(false, Tracing::silent())?;
}
let title = module.as_ref().map(|m| { let title = module.as_ref().map(|m| {
format!( format!(
"{m}{}", "{m}{}",

View File

@ -21,6 +21,10 @@ pub struct Args {
#[clap(short, long)] #[clap(short, long)]
uplc: bool, uplc: bool,
/// Environment to build against.
#[clap(long)]
env: Option<String>,
/// Filter traces to be included in the generated program(s). /// Filter traces to be included in the generated program(s).
/// ///
/// - user-defined: /// - user-defined:
@ -63,6 +67,7 @@ pub fn exec(
uplc, uplc,
filter_traces, filter_traces,
trace_level, trace_level,
env,
}: Args, }: Args,
) -> miette::Result<()> { ) -> miette::Result<()> {
let result = if watch { let result = if watch {
@ -73,6 +78,7 @@ pub fn exec(
Some(filter_traces) => filter_traces(trace_level), Some(filter_traces) => filter_traces(trace_level),
None => Tracing::All(trace_level), None => Tracing::All(trace_level),
}, },
env.clone(),
) )
}) })
} else { } else {
@ -83,6 +89,7 @@ pub fn exec(
Some(filter_traces) => filter_traces(trace_level), Some(filter_traces) => filter_traces(trace_level),
None => Tracing::All(trace_level), None => Tracing::All(trace_level),
}, },
env.clone(),
) )
}) })
}; };

View File

@ -48,6 +48,10 @@ pub struct Args {
#[clap(short, long)] #[clap(short, long)]
exact_match: bool, exact_match: bool,
/// Environment to build against.
#[clap(long)]
env: Option<String>,
/// Filter traces to be included in the generated program(s). /// Filter traces to be included in the generated program(s).
/// ///
/// - user-defined: /// - user-defined:
@ -95,6 +99,7 @@ pub fn exec(
trace_level, trace_level,
seed, seed,
max_success, max_success,
env,
}: Args, }: Args,
) -> miette::Result<()> { ) -> miette::Result<()> {
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
@ -114,6 +119,7 @@ pub fn exec(
Some(filter_traces) => filter_traces(trace_level), Some(filter_traces) => filter_traces(trace_level),
None => Tracing::All(trace_level), None => Tracing::All(trace_level),
}, },
env.clone(),
) )
}) })
} else { } else {
@ -129,6 +135,7 @@ pub fn exec(
Some(filter_traces) => filter_traces(trace_level), Some(filter_traces) => filter_traces(trace_level),
None => Tracing::All(trace_level), None => Tracing::All(trace_level),
}, },
env.clone(),
) )
}) })
}; };