mirror of
https://github.com/pesde-pkg/pesde.git
synced 2025-04-05 11:20:55 +01:00
feat(engines): print incompatibility warning for dependencies
Adds a warning message when a dependency depends on an incompatible engine.
This commit is contained in:
parent
4946a19f8b
commit
2d534a534d
4 changed files with 164 additions and 42 deletions
|
@ -368,6 +368,7 @@ pub async fn publish_package(
|
||||||
let new_entry = IndexFileEntry {
|
let new_entry = IndexFileEntry {
|
||||||
target: manifest.target.clone(),
|
target: manifest.target.clone(),
|
||||||
published_at: chrono::Utc::now(),
|
published_at: chrono::Utc::now(),
|
||||||
|
engines: manifest.engines.clone(),
|
||||||
description: manifest.description.clone(),
|
description: manifest.description.clone(),
|
||||||
license: manifest.license.clone(),
|
license: manifest.license.clone(),
|
||||||
authors: manifest.authors.clone(),
|
authors: manifest.authors.clone(),
|
||||||
|
|
|
@ -1,10 +1,3 @@
|
||||||
use std::{
|
|
||||||
collections::{BTreeMap, BTreeSet, HashMap},
|
|
||||||
num::NonZeroUsize,
|
|
||||||
sync::Arc,
|
|
||||||
time::Instant,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::files::make_executable;
|
use super::files::make_executable;
|
||||||
use crate::cli::{
|
use crate::cli::{
|
||||||
bin_dir,
|
bin_dir,
|
||||||
|
@ -16,10 +9,19 @@ use colored::Colorize;
|
||||||
use fs_err::tokio as fs;
|
use fs_err::tokio as fs;
|
||||||
use pesde::{
|
use pesde::{
|
||||||
download_and_link::{DownloadAndLinkHooks, DownloadAndLinkOptions},
|
download_and_link::{DownloadAndLinkHooks, DownloadAndLinkOptions},
|
||||||
|
engine::EngineKind,
|
||||||
graph::{DependencyGraph, DependencyGraphWithTarget},
|
graph::{DependencyGraph, DependencyGraphWithTarget},
|
||||||
lockfile::Lockfile,
|
lockfile::Lockfile,
|
||||||
manifest::{target::TargetKind, DependencyType},
|
manifest::{target::TargetKind, DependencyType, Manifest},
|
||||||
Project, RefreshedSources, LOCKFILE_FILE_NAME, MANIFEST_FILE_NAME,
|
names::PackageNames,
|
||||||
|
source::{pesde::PesdePackageSource, refs::PackageRefs},
|
||||||
|
version_matches, Project, RefreshedSources, LOCKFILE_FILE_NAME, MANIFEST_FILE_NAME,
|
||||||
|
};
|
||||||
|
use std::{
|
||||||
|
collections::{BTreeMap, BTreeSet, HashMap},
|
||||||
|
num::NonZeroUsize,
|
||||||
|
sync::Arc,
|
||||||
|
time::Instant,
|
||||||
};
|
};
|
||||||
use tokio::task::JoinSet;
|
use tokio::task::JoinSet;
|
||||||
|
|
||||||
|
@ -196,7 +198,8 @@ pub async fn install(
|
||||||
let overrides = resolve_overrides(&manifest)?;
|
let overrides = resolve_overrides(&manifest)?;
|
||||||
|
|
||||||
let (new_lockfile, old_graph) =
|
let (new_lockfile, old_graph) =
|
||||||
reporters::run_with_reporter(|_, root_progress, reporter| async {
|
reporters::run_with_reporter(|multi, root_progress, reporter| async {
|
||||||
|
let multi = multi;
|
||||||
let root_progress = root_progress;
|
let root_progress = root_progress;
|
||||||
|
|
||||||
root_progress.set_prefix(format!("{} {}: ", manifest.name, manifest.target));
|
root_progress.set_prefix(format!("{} {}: ", manifest.name, manifest.target));
|
||||||
|
@ -300,9 +303,104 @@ pub async fn install(
|
||||||
root_progress.set_message("patch");
|
root_progress.set_message("patch");
|
||||||
|
|
||||||
project
|
project
|
||||||
.apply_patches(&downloaded_graph.convert(), reporter)
|
.apply_patches(&downloaded_graph.clone().convert(), reporter)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "version-management")]
|
||||||
|
{
|
||||||
|
let mut tasks = manifest
|
||||||
|
.engines
|
||||||
|
.into_iter()
|
||||||
|
.map(|(engine, req)| async move {
|
||||||
|
Ok::<_, anyhow::Error>(
|
||||||
|
crate::cli::version::get_installed_versions(engine)
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.filter(|version| version_matches(version, &req))
|
||||||
|
.last()
|
||||||
|
.map(|version| (engine, version)),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<JoinSet<_>>();
|
||||||
|
|
||||||
|
let mut resolved_engine_versions = HashMap::new();
|
||||||
|
while let Some(task) = tasks.join_next().await {
|
||||||
|
let Some((engine, version)) = task.unwrap()? else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
resolved_engine_versions.insert(engine, version);
|
||||||
|
}
|
||||||
|
|
||||||
|
let manifest_target_kind = manifest.target.kind();
|
||||||
|
let mut tasks = downloaded_graph.iter()
|
||||||
|
.map(|(id, node)| {
|
||||||
|
let id = id.clone();
|
||||||
|
let node = node.clone();
|
||||||
|
let project = project.clone();
|
||||||
|
|
||||||
|
async move {
|
||||||
|
let engines = match &node.node.pkg_ref {
|
||||||
|
PackageRefs::Pesde(pkg_ref) => {
|
||||||
|
let source = PesdePackageSource::new(pkg_ref.index_url.clone());
|
||||||
|
#[allow(irrefutable_let_patterns)]
|
||||||
|
let PackageNames::Pesde(name) = id.name() else {
|
||||||
|
panic!("unexpected package name");
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut file = source.read_index_file(name, &project).await.context("failed to read package index file")?.context("package not found in index")?;
|
||||||
|
file
|
||||||
|
.entries
|
||||||
|
.remove(id.version_id())
|
||||||
|
.context("package version not found in index")?
|
||||||
|
.engines
|
||||||
|
}
|
||||||
|
#[cfg(feature = "wally-compat")]
|
||||||
|
PackageRefs::Wally(_) => Default::default(),
|
||||||
|
_ => {
|
||||||
|
let path = node.node.container_folder_from_project(
|
||||||
|
&id,
|
||||||
|
&project,
|
||||||
|
manifest_target_kind,
|
||||||
|
);
|
||||||
|
|
||||||
|
match fs::read_to_string(path.join(MANIFEST_FILE_NAME)).await {
|
||||||
|
Ok(manifest) => match toml::from_str::<Manifest>(&manifest) {
|
||||||
|
Ok(manifest) => manifest.engines,
|
||||||
|
Err(e) => return Err(e).context("failed to read package manifest"),
|
||||||
|
},
|
||||||
|
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Default::default(),
|
||||||
|
Err(e) => return Err(e).context("failed to read package manifest"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((id, engines))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<JoinSet<_>>();
|
||||||
|
|
||||||
|
while let Some(task) = tasks.join_next().await {
|
||||||
|
let (id, required_engines) = task.unwrap()?;
|
||||||
|
|
||||||
|
for (engine, req) in required_engines {
|
||||||
|
if engine == EngineKind::Pesde {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(version) = resolved_engine_versions.get(&engine) else {
|
||||||
|
tracing::debug!("package {id} requires {engine} {req}, but it is not installed");
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
if !version_matches(version, &req) {
|
||||||
|
multi.suspend(|| {
|
||||||
|
println!("{}: package {id} requires {engine} {req}, but {version} is installed", "warn".yellow().bold());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
root_progress.set_message("finish");
|
root_progress.set_message("finish");
|
||||||
|
@ -325,7 +423,7 @@ pub async fn install(
|
||||||
|
|
||||||
anyhow::Ok((new_lockfile, old_graph.unwrap_or_default()))
|
anyhow::Ok((new_lockfile, old_graph.unwrap_or_default()))
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let elapsed = start.elapsed();
|
let elapsed = start.elapsed();
|
||||||
|
|
||||||
|
|
|
@ -142,25 +142,20 @@ pub async fn check_for_updates(reqwest: &reqwest::Client) -> anyhow::Result<()>
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip(reqwest), level = "trace")]
|
const ENGINES_DIR: &str = "engines";
|
||||||
pub async fn get_or_download_engine(
|
|
||||||
reqwest: &reqwest::Client,
|
#[instrument(level = "trace")]
|
||||||
engine: EngineKind,
|
pub async fn get_installed_versions(engine: EngineKind) -> anyhow::Result<BTreeSet<Version>> {
|
||||||
req: VersionReq,
|
|
||||||
) -> anyhow::Result<PathBuf> {
|
|
||||||
let source = engine.source();
|
let source = engine.source();
|
||||||
|
let path = home_dir()?.join(ENGINES_DIR).join(source.directory());
|
||||||
let path = home_dir()?.join("engines").join(source.directory());
|
|
||||||
fs::create_dir_all(&path)
|
|
||||||
.await
|
|
||||||
.context("failed to create engines directory")?;
|
|
||||||
|
|
||||||
let mut read_dir = fs::read_dir(&path)
|
|
||||||
.await
|
|
||||||
.context("failed to read engines directory")?;
|
|
||||||
|
|
||||||
let mut installed_versions = BTreeSet::new();
|
let mut installed_versions = BTreeSet::new();
|
||||||
|
|
||||||
|
let mut read_dir = match fs::read_dir(&path).await {
|
||||||
|
Ok(read_dir) => read_dir,
|
||||||
|
Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(installed_versions),
|
||||||
|
Err(e) => return Err(e).context("failed to read engines directory"),
|
||||||
|
};
|
||||||
|
|
||||||
while let Some(entry) = read_dir.next_entry().await? {
|
while let Some(entry) = read_dir.next_entry().await? {
|
||||||
let path = entry.path();
|
let path = entry.path();
|
||||||
|
|
||||||
|
@ -173,6 +168,20 @@ pub async fn get_or_download_engine(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(installed_versions)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(skip(reqwest), level = "trace")]
|
||||||
|
pub async fn get_or_download_engine(
|
||||||
|
reqwest: &reqwest::Client,
|
||||||
|
engine: EngineKind,
|
||||||
|
req: VersionReq,
|
||||||
|
) -> anyhow::Result<PathBuf> {
|
||||||
|
let source = engine.source();
|
||||||
|
let path = home_dir()?.join(ENGINES_DIR).join(source.directory());
|
||||||
|
|
||||||
|
let installed_versions = get_installed_versions(engine).await?;
|
||||||
|
|
||||||
let max_matching = installed_versions
|
let max_matching = installed_versions
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|v| version_matches(v, &req))
|
.filter(|v| version_matches(v, &req))
|
||||||
|
|
|
@ -13,6 +13,7 @@ use pkg_ref::PesdePackageRef;
|
||||||
use specifier::PesdeDependencySpecifier;
|
use specifier::PesdeDependencySpecifier;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
engine::EngineKind,
|
||||||
manifest::{target::Target, DependencyType},
|
manifest::{target::Target, DependencyType},
|
||||||
names::{PackageName, PackageNames},
|
names::{PackageName, PackageNames},
|
||||||
reporters::{response_to_async_read, DownloadProgressReporter},
|
reporters::{response_to_async_read, DownloadProgressReporter},
|
||||||
|
@ -27,6 +28,7 @@ use crate::{
|
||||||
};
|
};
|
||||||
use fs_err::tokio as fs;
|
use fs_err::tokio as fs;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
|
use semver::VersionReq;
|
||||||
use tokio::{pin, task::spawn_blocking};
|
use tokio::{pin, task::spawn_blocking};
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
|
@ -94,23 +96,31 @@ impl PesdePackageSource {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_index_file(
|
/// Reads the index file of a package
|
||||||
|
pub async fn read_index_file(
|
||||||
&self,
|
&self,
|
||||||
name: &PackageName,
|
name: &PackageName,
|
||||||
project: &Project,
|
project: &Project,
|
||||||
) -> Result<Option<IndexFile>, errors::ReadIndexFileError> {
|
) -> Result<Option<IndexFile>, errors::ReadIndexFileError> {
|
||||||
let (scope, name) = name.as_str();
|
let path = self.path(project);
|
||||||
let repo = gix::open(self.path(project)).map_err(Box::new)?;
|
let name = name.clone();
|
||||||
let tree = root_tree(&repo).map_err(Box::new)?;
|
|
||||||
let string = match read_file(&tree, [scope, name]) {
|
|
||||||
Ok(Some(s)) => s,
|
|
||||||
Ok(None) => return Ok(None),
|
|
||||||
Err(e) => {
|
|
||||||
return Err(errors::ReadIndexFileError::ReadFile(e));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
toml::from_str(&string).map_err(Into::into)
|
spawn_blocking(move || {
|
||||||
|
let (scope, name) = name.as_str();
|
||||||
|
let repo = gix::open(&path).map_err(Box::new)?;
|
||||||
|
let tree = root_tree(&repo).map_err(Box::new)?;
|
||||||
|
let string = match read_file(&tree, [scope, name]) {
|
||||||
|
Ok(Some(s)) => s,
|
||||||
|
Ok(None) => return Ok(None),
|
||||||
|
Err(e) => {
|
||||||
|
return Err(errors::ReadIndexFileError::ReadFile(e));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
toml::from_str(&string).map_err(Into::into)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,7 +150,7 @@ impl PackageSource for PesdePackageSource {
|
||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
let Some(IndexFile { meta, entries, .. }) =
|
let Some(IndexFile { meta, entries, .. }) =
|
||||||
self.read_index_file(&specifier.name, project)?
|
self.read_index_file(&specifier.name, project).await?
|
||||||
else {
|
else {
|
||||||
return Err(errors::ResolveError::NotFound(specifier.name.to_string()));
|
return Err(errors::ResolveError::NotFound(specifier.name.to_string()));
|
||||||
};
|
};
|
||||||
|
@ -296,7 +306,8 @@ impl PackageSource for PesdePackageSource {
|
||||||
panic!("unexpected package name");
|
panic!("unexpected package name");
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some(IndexFile { mut entries, .. }) = self.read_index_file(name, &options.project)?
|
let Some(IndexFile { mut entries, .. }) =
|
||||||
|
self.read_index_file(name, &options.project).await?
|
||||||
else {
|
else {
|
||||||
return Err(errors::GetTargetError::NotFound(name.to_string()));
|
return Err(errors::GetTargetError::NotFound(name.to_string()));
|
||||||
};
|
};
|
||||||
|
@ -460,6 +471,9 @@ pub struct IndexFileEntry {
|
||||||
/// When this package was published
|
/// When this package was published
|
||||||
#[serde(default = "chrono::Utc::now")]
|
#[serde(default = "chrono::Utc::now")]
|
||||||
pub published_at: chrono::DateTime<chrono::Utc>,
|
pub published_at: chrono::DateTime<chrono::Utc>,
|
||||||
|
/// The engines this package supports
|
||||||
|
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
|
||||||
|
pub engines: BTreeMap<EngineKind, VersionReq>,
|
||||||
|
|
||||||
/// The description of this package
|
/// The description of this package
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
|
Loading…
Add table
Reference in a new issue