feat: basic ability to have many projects in one repo
This commit is contained in:
parent
e0732c2ecf
commit
5f9b5ac781
|
@ -144,6 +144,7 @@ dependencies = [
|
|||
"dirs",
|
||||
"fslock",
|
||||
"futures",
|
||||
"glob",
|
||||
"hex",
|
||||
"ignore",
|
||||
"indexmap 1.9.3",
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::server::Server;
|
||||
use aiken_project::{config::Config, paths};
|
||||
use aiken_project::{config::ProjectConfig, paths};
|
||||
use error::Error;
|
||||
use lsp_server::Connection;
|
||||
use std::env;
|
||||
|
@ -23,7 +23,7 @@ pub fn start() -> Result<(), Error> {
|
|||
let config = if paths::project_config().exists() {
|
||||
tracing::info!("Aiken project detected");
|
||||
|
||||
Some(Config::load(&root).expect("failed to load aiken.toml"))
|
||||
Some(ProjectConfig::load(&root).expect("failed to load aiken.toml"))
|
||||
} else {
|
||||
tracing::info!("Aiken project config not found");
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ use aiken_lang::{
|
|||
tipo::pretty::Printer,
|
||||
};
|
||||
use aiken_project::{
|
||||
config::{self, Config},
|
||||
config::{self, ProjectConfig},
|
||||
error::{Error as ProjectError, GetSource},
|
||||
module::CheckedModule,
|
||||
};
|
||||
|
@ -50,7 +50,7 @@ pub struct Server {
|
|||
// Project root directory
|
||||
root: PathBuf,
|
||||
|
||||
config: Option<config::Config>,
|
||||
config: Option<config::ProjectConfig>,
|
||||
|
||||
/// Files that have been edited in memory
|
||||
edited: HashMap<String, String>,
|
||||
|
@ -235,7 +235,7 @@ impl Server {
|
|||
}
|
||||
|
||||
DidChangeWatchedFiles::METHOD => {
|
||||
if let Ok(config) = Config::load(&self.root) {
|
||||
if let Ok(config) = ProjectConfig::load(&self.root) {
|
||||
self.config = Some(config);
|
||||
self.create_new_compiler();
|
||||
self.compile(connection)?;
|
||||
|
@ -603,7 +603,7 @@ impl Server {
|
|||
|
||||
pub fn new(
|
||||
initialize_params: InitializeParams,
|
||||
config: Option<config::Config>,
|
||||
config: Option<config::ProjectConfig>,
|
||||
root: PathBuf,
|
||||
) -> Self {
|
||||
let mut server = Server {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use aiken_lang::{ast::Tracing, line_numbers::LineNumbers, test_framework::PropertyTest};
|
||||
use aiken_project::{config::Config, error::Error as ProjectError, module::CheckedModule, Project};
|
||||
use aiken_project::{config::ProjectConfig, error::Error as ProjectError, module::CheckedModule, Project};
|
||||
use std::{collections::HashMap, path::PathBuf};
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -18,7 +18,7 @@ pub struct LspProject {
|
|||
}
|
||||
|
||||
impl LspProject {
|
||||
pub fn new(config: Config, root: PathBuf, telemetry: super::telemetry::Lsp) -> Self {
|
||||
pub fn new(config: ProjectConfig, root: PathBuf, telemetry: super::telemetry::Lsp) -> Self {
|
||||
Self {
|
||||
project: Project::new_with_config(config, root, telemetry),
|
||||
modules: HashMap::new(),
|
||||
|
|
|
@ -22,6 +22,7 @@ ciborium = "0.2.2"
|
|||
dirs = "4.0.0"
|
||||
fslock = "0.2.1"
|
||||
futures = "0.3.26"
|
||||
glob = "0.3.2"
|
||||
hex = "0.4.3"
|
||||
ignore = "0.4.20"
|
||||
indexmap = "1.9.2"
|
||||
|
|
|
@ -6,7 +6,7 @@ pub mod schema;
|
|||
pub mod validator;
|
||||
|
||||
use crate::{
|
||||
config::{self, Config, PlutusVersion},
|
||||
config::{self, ProjectConfig, PlutusVersion},
|
||||
module::CheckedModules,
|
||||
};
|
||||
use aiken_lang::gen_uplc::CodeGenerator;
|
||||
|
@ -58,7 +58,7 @@ pub enum LookupResult<'a, T> {
|
|||
|
||||
impl Blueprint {
|
||||
pub fn new(
|
||||
config: &Config,
|
||||
config: &ProjectConfig,
|
||||
modules: &CheckedModules,
|
||||
generator: &mut CodeGenerator,
|
||||
) -> Result<Self, Error> {
|
||||
|
@ -179,8 +179,8 @@ impl Blueprint {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<&Config> for Preamble {
|
||||
fn from(config: &Config) -> Self {
|
||||
impl From<&ProjectConfig> for Preamble {
|
||||
fn from(config: &ProjectConfig) -> Self {
|
||||
Preamble {
|
||||
title: config.name.to_string(),
|
||||
description: if config.description.is_empty() {
|
||||
|
|
|
@ -7,6 +7,7 @@ use aiken_lang::{
|
|||
parser::token::Base,
|
||||
};
|
||||
pub use aiken_lang::{plutus_version::PlutusVersion, version::compiler_version};
|
||||
use glob::glob;
|
||||
use miette::NamedSource;
|
||||
use semver::Version;
|
||||
use serde::{
|
||||
|
@ -14,30 +15,94 @@ use serde::{
|
|||
ser::{self, SerializeSeq, SerializeStruct},
|
||||
Deserialize, Serialize,
|
||||
};
|
||||
use std::{collections::BTreeMap, fmt::Display, fs, io, path::Path};
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
fmt::Display,
|
||||
fs, io,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone)]
|
||||
pub struct Config {
|
||||
pub struct ProjectConfig {
|
||||
pub name: PackageName,
|
||||
|
||||
pub version: String,
|
||||
|
||||
#[serde(
|
||||
deserialize_with = "deserialize_version",
|
||||
serialize_with = "serialize_version",
|
||||
default = "default_version"
|
||||
)]
|
||||
pub compiler: Version,
|
||||
|
||||
#[serde(default, deserialize_with = "validate_v3_only")]
|
||||
pub plutus: PlutusVersion,
|
||||
|
||||
pub license: Option<String>,
|
||||
|
||||
#[serde(default)]
|
||||
pub description: String,
|
||||
|
||||
pub repository: Option<Repository>,
|
||||
|
||||
#[serde(default)]
|
||||
pub dependencies: Vec<Dependency>,
|
||||
|
||||
#[serde(default)]
|
||||
pub config: BTreeMap<String, BTreeMap<String, SimpleExpr>>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone)]
|
||||
struct RawWorkspaceConfig {
|
||||
members: Vec<String>,
|
||||
}
|
||||
|
||||
impl RawWorkspaceConfig {
|
||||
pub fn expand_members(self, root: &Path) -> Vec<PathBuf> {
|
||||
let mut result = Vec::new();
|
||||
|
||||
for member in self.members {
|
||||
let pattern = root.join(member);
|
||||
|
||||
let glob_result: Vec<_> = pattern
|
||||
.to_str()
|
||||
.and_then(|s| glob(s).ok())
|
||||
.map_or(Vec::new(), |g| g.filter_map(Result::ok).collect());
|
||||
|
||||
if glob_result.is_empty() {
|
||||
// No matches (or glob failed), treat as literal path
|
||||
result.push(pattern);
|
||||
} else {
|
||||
// Glob worked, add all matches
|
||||
result.extend(glob_result);
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WorkspaceConfig {
|
||||
pub members: Vec<PathBuf>,
|
||||
}
|
||||
|
||||
impl WorkspaceConfig {
|
||||
pub fn load(dir: &Path) -> Result<WorkspaceConfig, Error> {
|
||||
let config_path = dir.join(paths::project_config());
|
||||
let raw_config = fs::read_to_string(&config_path).map_err(|_| Error::MissingManifest {
|
||||
path: dir.to_path_buf(),
|
||||
})?;
|
||||
|
||||
let raw: RawWorkspaceConfig = toml::from_str(&raw_config).map_err(|e| {
|
||||
from_toml_de_error(e, config_path, raw_config, TomlLoadingContext::Workspace)
|
||||
})?;
|
||||
|
||||
let members = raw.expand_members(dir);
|
||||
|
||||
Ok(WorkspaceConfig { members })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum SimpleExpr {
|
||||
Int(i64),
|
||||
|
@ -303,9 +368,9 @@ impl Display for Platform {
|
|||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
impl ProjectConfig {
|
||||
pub fn default(name: &PackageName) -> Self {
|
||||
Config {
|
||||
ProjectConfig {
|
||||
name: name.clone(),
|
||||
version: "0.0.0".to_string(),
|
||||
compiler: default_version(),
|
||||
|
@ -338,23 +403,14 @@ impl Config {
|
|||
fs::write(aiken_toml_path, aiken_toml)
|
||||
}
|
||||
|
||||
pub fn load(dir: &Path) -> Result<Config, Error> {
|
||||
pub fn load(dir: &Path) -> Result<ProjectConfig, Error> {
|
||||
let config_path = dir.join(paths::project_config());
|
||||
let raw_config = fs::read_to_string(&config_path).map_err(|_| Error::MissingManifest {
|
||||
path: dir.to_path_buf(),
|
||||
})?;
|
||||
|
||||
let result: Self = toml::from_str(&raw_config).map_err(|e| Error::TomlLoading {
|
||||
ctx: TomlLoadingContext::Project,
|
||||
path: config_path.clone(),
|
||||
src: raw_config.clone(),
|
||||
named: NamedSource::new(config_path.display().to_string(), raw_config).into(),
|
||||
// this isn't actually a legit way to get the span
|
||||
location: e.span().map(|range| Span {
|
||||
start: range.start,
|
||||
end: range.end,
|
||||
}),
|
||||
help: e.message().to_string(),
|
||||
let result: Self = toml::from_str(&raw_config).map_err(|e| {
|
||||
from_toml_de_error(e, config_path, raw_config, TomlLoadingContext::Project)
|
||||
})?;
|
||||
|
||||
Ok(result)
|
||||
|
@ -388,6 +444,26 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
fn from_toml_de_error(
|
||||
e: toml::de::Error,
|
||||
config_path: PathBuf,
|
||||
raw_config: String,
|
||||
ctx: TomlLoadingContext,
|
||||
) -> Error {
|
||||
Error::TomlLoading {
|
||||
ctx,
|
||||
path: config_path.clone(),
|
||||
src: raw_config.clone(),
|
||||
named: NamedSource::new(config_path.display().to_string(), raw_config).into(),
|
||||
// this isn't actually a legit way to get the span
|
||||
location: e.span().map(|range| Span {
|
||||
start: range.start,
|
||||
end: range.end,
|
||||
}),
|
||||
help: e.message().to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
mod built_info {
|
||||
include!(concat!(env!("OUT_DIR"), "/built.rs"));
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize};
|
|||
use tokio::time::Instant;
|
||||
|
||||
use crate::{
|
||||
config::{Config, Dependency},
|
||||
config::{ProjectConfig, Dependency},
|
||||
error::{Error, TomlLoadingContext},
|
||||
package_name::PackageName,
|
||||
paths,
|
||||
|
@ -133,7 +133,7 @@ impl From<&Manifest> for LocalPackages {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn download<T>(event_listener: &T, root_path: &Path, config: &Config) -> Result<Manifest, Error>
|
||||
pub fn download<T>(event_listener: &T, root_path: &Path, config: &ProjectConfig) -> Result<Manifest, Error>
|
||||
where
|
||||
T: EventListener,
|
||||
{
|
||||
|
|
|
@ -9,7 +9,7 @@ use std::{
|
|||
};
|
||||
|
||||
use crate::{
|
||||
config::{Config, Dependency, Platform},
|
||||
config::{ProjectConfig, Dependency, Platform},
|
||||
error::{Error, TomlLoadingContext},
|
||||
package_name::PackageName,
|
||||
paths,
|
||||
|
@ -27,7 +27,7 @@ pub struct Manifest {
|
|||
impl Manifest {
|
||||
pub fn load<T>(
|
||||
event_listener: &T,
|
||||
config: &Config,
|
||||
config: &ProjectConfig,
|
||||
root_path: &Path,
|
||||
) -> Result<(Self, bool), Error>
|
||||
where
|
||||
|
@ -121,7 +121,7 @@ pub struct Package {
|
|||
pub source: Platform,
|
||||
}
|
||||
|
||||
fn resolve_versions<T>(config: &Config, event_listener: &T) -> Result<Manifest, Error>
|
||||
fn resolve_versions<T>(config: &ProjectConfig, event_listener: &T) -> Result<Manifest, Error>
|
||||
where
|
||||
T: EventListener,
|
||||
{
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::{
|
||||
config::{Config, Repository},
|
||||
config::{ProjectConfig, Repository},
|
||||
module::CheckedModule,
|
||||
};
|
||||
use aiken_lang::{
|
||||
|
@ -104,7 +104,7 @@ impl DocLink {
|
|||
/// The documentation is built using template files located at the root of this crate.
|
||||
/// With the documentation, we also build a client-side search index to ease navigation
|
||||
/// across multiple modules.
|
||||
pub fn generate_all(root: &Path, config: &Config, modules: Vec<&CheckedModule>) -> Vec<DocFile> {
|
||||
pub fn generate_all(root: &Path, config: &ProjectConfig, modules: Vec<&CheckedModule>) -> Vec<DocFile> {
|
||||
let timestamp = new_timestamp();
|
||||
let modules_links = generate_modules_links(&modules);
|
||||
|
||||
|
@ -155,7 +155,7 @@ pub fn generate_all(root: &Path, config: &Config, modules: Vec<&CheckedModule>)
|
|||
|
||||
fn generate_module(
|
||||
root: &Path,
|
||||
config: &Config,
|
||||
config: &ProjectConfig,
|
||||
module: &CheckedModule,
|
||||
modules: &[DocLink],
|
||||
source: &DocLink,
|
||||
|
@ -376,7 +376,7 @@ fn generate_static_assets(search_indexes: Vec<SearchIndex>) -> Vec<DocFile> {
|
|||
|
||||
fn generate_readme(
|
||||
root: &Path,
|
||||
config: &Config,
|
||||
config: &ProjectConfig,
|
||||
modules: &[DocLink],
|
||||
source: &DocLink,
|
||||
timestamp: &Duration,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::{
|
||||
config::{Config, Platform},
|
||||
config::{ProjectConfig, Platform},
|
||||
CheckedModule,
|
||||
};
|
||||
use aiken_lang::{ast::Span, line_numbers::LineNumbers};
|
||||
|
@ -12,7 +12,7 @@ pub struct SourceLinker {
|
|||
}
|
||||
|
||||
impl SourceLinker {
|
||||
pub fn new(root: &Path, config: &Config, module: &CheckedModule) -> Self {
|
||||
pub fn new(root: &Path, config: &ProjectConfig, module: &CheckedModule) -> Self {
|
||||
let utf8_path = <&Utf8Path>::try_from(
|
||||
module
|
||||
.input_path
|
||||
|
|
|
@ -26,6 +26,7 @@ pub enum TomlLoadingContext {
|
|||
Project,
|
||||
Manifest,
|
||||
Package,
|
||||
Workspace,
|
||||
}
|
||||
|
||||
impl fmt::Display for TomlLoadingContext {
|
||||
|
@ -34,6 +35,7 @@ impl fmt::Display for TomlLoadingContext {
|
|||
TomlLoadingContext::Project => write!(f, "project"),
|
||||
TomlLoadingContext::Manifest => write!(f, "manifest"),
|
||||
TomlLoadingContext::Package => write!(f, "package"),
|
||||
TomlLoadingContext::Workspace => write!(f, "workspace"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ use crate::{
|
|||
schema::{Annotated, Schema},
|
||||
Blueprint,
|
||||
},
|
||||
config::Config,
|
||||
config::ProjectConfig,
|
||||
error::{Error, Warning},
|
||||
module::{CheckedModule, CheckedModules, ParsedModule, ParsedModules},
|
||||
telemetry::Event,
|
||||
|
@ -87,7 +87,7 @@ pub struct Project<T>
|
|||
where
|
||||
T: EventListener,
|
||||
{
|
||||
config: Config,
|
||||
config: ProjectConfig,
|
||||
defined_modules: HashMap<String, PathBuf>,
|
||||
checked_modules: CheckedModules,
|
||||
id_gen: IdGenerator,
|
||||
|
@ -108,7 +108,7 @@ where
|
|||
T: EventListener,
|
||||
{
|
||||
pub fn new(root: PathBuf, event_listener: T) -> Result<Project<T>, Error> {
|
||||
let config = Config::load(&root)?;
|
||||
let config = ProjectConfig::load(&root)?;
|
||||
|
||||
let demanded_compiler_version = format!("v{}", config.compiler);
|
||||
|
||||
|
@ -126,7 +126,7 @@ where
|
|||
Ok(project)
|
||||
}
|
||||
|
||||
pub fn new_with_config(config: Config, root: PathBuf, event_listener: T) -> Project<T> {
|
||||
pub fn new_with_config(config: ProjectConfig, root: PathBuf, event_listener: T) -> Project<T> {
|
||||
let id_gen = IdGenerator::new();
|
||||
|
||||
let mut module_types = HashMap::new();
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::{telemetry::EventTarget, Project};
|
||||
use crate::{config::WorkspaceConfig, telemetry::EventTarget, Project};
|
||||
use miette::{Diagnostic, IntoDiagnostic};
|
||||
use notify::{Event, RecursiveMode, Watcher};
|
||||
use owo_colors::{OwoColorize, Stream::Stderr};
|
||||
|
@ -108,17 +108,57 @@ where
|
|||
current_dir
|
||||
};
|
||||
|
||||
let mut project = match Project::new(project_path, EventTarget::default()) {
|
||||
Ok(p) => Ok(p),
|
||||
Err(e) => {
|
||||
e.report();
|
||||
Err(ExitFailure::into_report())
|
||||
let mut warnings = Vec::new();
|
||||
let mut errs: Vec<crate::error::Error> = Vec::new();
|
||||
let mut check_count = None;
|
||||
|
||||
if let Ok(workspace) = WorkspaceConfig::load(&project_path) {
|
||||
let res_projects = workspace
|
||||
.members
|
||||
.into_iter()
|
||||
.map(|member| Project::new(member, EventTarget::default()))
|
||||
.collect::<Result<Vec<Project<_>>, crate::error::Error>>();
|
||||
|
||||
let projects = match res_projects {
|
||||
Ok(p) => Ok(p),
|
||||
Err(e) => {
|
||||
e.report();
|
||||
Err(ExitFailure::into_report())
|
||||
}
|
||||
}?;
|
||||
|
||||
for mut project in projects {
|
||||
let build_result = action(&mut project);
|
||||
|
||||
warnings.extend(project.warnings());
|
||||
|
||||
let sum = check_count.unwrap_or(0) + project.checks_count.unwrap_or(0);
|
||||
check_count = if sum > 0 { Some(sum) } else { None };
|
||||
|
||||
if let Err(e) = build_result {
|
||||
errs.extend(e);
|
||||
}
|
||||
}
|
||||
}?;
|
||||
} else {
|
||||
let mut project = match Project::new(project_path, EventTarget::default()) {
|
||||
Ok(p) => Ok(p),
|
||||
Err(e) => {
|
||||
e.report();
|
||||
Err(ExitFailure::into_report())
|
||||
}
|
||||
}?;
|
||||
|
||||
let build_result = action(&mut project);
|
||||
let build_result = action(&mut project);
|
||||
|
||||
let warnings = project.warnings();
|
||||
warnings.extend(project.warnings());
|
||||
|
||||
let sum = check_count.unwrap_or(0) + project.checks_count.unwrap_or(0);
|
||||
check_count = if sum > 0 { Some(sum) } else { None };
|
||||
|
||||
if let Err(e) = build_result {
|
||||
errs.extend(e);
|
||||
}
|
||||
}
|
||||
|
||||
let warning_count = warnings.len();
|
||||
|
||||
|
@ -130,7 +170,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
if let Err(errs) = build_result {
|
||||
if !errs.is_empty() {
|
||||
for err in &errs {
|
||||
err.report()
|
||||
}
|
||||
|
@ -138,7 +178,7 @@ where
|
|||
eprintln!(
|
||||
"{}",
|
||||
Summary {
|
||||
check_count: project.checks_count,
|
||||
check_count,
|
||||
warning_count,
|
||||
error_count: errs.len(),
|
||||
}
|
||||
|
@ -147,16 +187,14 @@ where
|
|||
return Err(ExitFailure::into_report());
|
||||
}
|
||||
|
||||
if project.checks_count.unwrap_or_default() + warning_count > 0 {
|
||||
eprintln!(
|
||||
"{}",
|
||||
Summary {
|
||||
check_count: project.checks_count,
|
||||
error_count: 0,
|
||||
warning_count
|
||||
}
|
||||
);
|
||||
}
|
||||
eprintln!(
|
||||
"{}",
|
||||
Summary {
|
||||
check_count,
|
||||
error_count: 0,
|
||||
warning_count
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if warning_count > 0 && deny {
|
||||
|
@ -172,6 +210,7 @@ where
|
|||
/// // Note: doctest disabled, because aiken_project doesn't have an implementation of EventListener I can use
|
||||
/// use aiken_project::watch::{watch_project, default_filter};
|
||||
/// use aiken_project::{Project};
|
||||
///
|
||||
/// watch_project(None, default_filter, 500, |project| {
|
||||
/// println!("Project changed!");
|
||||
/// Ok(())
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use aiken_project::{
|
||||
config::{self, Config},
|
||||
config::{self, ProjectConfig},
|
||||
package_name::{self, PackageName},
|
||||
};
|
||||
use indoc::{formatdoc, indoc};
|
||||
|
@ -46,7 +46,7 @@ fn create_project(args: Args, package_name: &PackageName) -> miette::Result<()>
|
|||
|
||||
readme(&root, &package_name.repo)?;
|
||||
|
||||
Config::default(package_name)
|
||||
ProjectConfig::default(package_name)
|
||||
.save(&root)
|
||||
.into_diagnostic()?;
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use aiken_project::{
|
||||
config::{Config, Dependency, Platform},
|
||||
config::{ProjectConfig, Dependency, Platform},
|
||||
error::Warning,
|
||||
package_name::PackageName,
|
||||
pretty,
|
||||
|
@ -35,7 +35,7 @@ pub fn exec(args: Args) -> miette::Result<()> {
|
|||
source: Platform::Github,
|
||||
};
|
||||
|
||||
let config = match Config::load(&root) {
|
||||
let config = match ProjectConfig::load(&root) {
|
||||
Ok(config) => config,
|
||||
Err(e) => {
|
||||
e.report();
|
||||
|
|
Loading…
Reference in New Issue