Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 30 additions & 5 deletions crates/pixi_build_frontend/src/protocols/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,23 @@ mod error;
pub(super) mod stderr;

#[derive(Debug, Error, Diagnostic)]
pub enum InitializeError {
#[error("failed to setup communication with the build backend, an unexpected io error occurred while communicating with the pixi build backend")]
#[diagnostic(help("Ensure that the project manifest contains a valid [build] section."))]
pub enum BuildBackendSetupError {
#[error("an unexpected io error occurred while communicating with the pixi build backend")]
Io(#[from] std::io::Error),

#[error("the build backend executable '{0}' appears to be missing")]
MissingExecutable(String),
}

#[derive(Debug, Error, Diagnostic)]
pub enum InitializeError {
#[error("failed to setup communication with the build-backend")]
#[diagnostic(help("This is often caused by a broken build-backend. Try upgrading or downgrading the build backend."))]
Setup(
#[diagnostic_source]
#[from]
BuildBackendSetupError,
),
#[error(transparent)]
#[diagnostic(transparent)]
Protocol(#[from] ProtocolError),
Expand Down Expand Up @@ -177,11 +190,22 @@ impl JsonRPCBuildProtocol {
tool: Tool,
) -> Result<Self, InitializeError> {
// Spawn the tool and capture stdin/stdout.
let mut process = tokio::process::Command::from(tool.command())
let command = tool.command();
let program_name = command.get_program().to_string_lossy().into_owned();
let mut process = match tokio::process::Command::from(command)
.stdout(std::process::Stdio::piped())
.stdin(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.spawn()?;
.spawn()
{
Ok(process) => process,
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
return Err(BuildBackendSetupError::MissingExecutable(program_name).into());
}
Err(err) => {
return Err(BuildBackendSetupError::Io(err).into());
}
};

let backend_identifier = tool.executable().clone();

Expand Down Expand Up @@ -261,6 +285,7 @@ impl JsonRPCBuildProtocol {
.transpose()
.map_err(ProtocolError::from)?
.map(Into::into);

// Invoke the initialize method on the backend to establish the connection.
let _result: InitializeResult = client
.request(
Expand Down
73 changes: 69 additions & 4 deletions crates/pixi_build_frontend/tests/diagnostics.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
use std::path::Path;
use std::path::{Path, PathBuf};

use bytes::Bytes;
use futures::{SinkExt, StreamExt};
use miette::{Diagnostic, GraphicalReportHandler, GraphicalTheme};
use pixi_build_frontend::{BuildFrontend, InProcessBackend, SetupRequest};
use pixi_manifest::toml::ExternalWorkspaceProperties;
use pixi_manifest::toml::{FromTomlStr, TomlManifest};
use pixi_build_frontend::{BuildFrontend, InProcessBackend, SetupRequest, ToolContext};
use pixi_manifest::{
toml::{ExternalWorkspaceProperties, FromTomlStr, TomlManifest},
BuildBackend, KnownPreviewFeature, Package, PackageBuild, PackageManifest, Preview, Workspace,
WorkspaceManifest,
};
use pixi_spec::BinarySpec;
use rattler_conda_types::{NamedChannelOrUrl, PackageName, Platform};
use tokio::io::{AsyncRead, AsyncWrite};
use tokio_stream::wrappers::ReceiverStream;
use tokio_util::{
Expand All @@ -23,6 +28,25 @@ fn error_to_snapshot(diag: &impl Diagnostic) -> String {
report_str
}

fn test_data_dir() -> PathBuf {
let root_dir = Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.and_then(Path::parent)
.unwrap();
root_dir.join("tests")
}

fn build_backends_dir() -> PathBuf {
test_data_dir().join("build-backends")
}

/// Returns the channel to use when using the build backends in the tests
/// directory.
fn build_backends_channel() -> NamedChannelOrUrl {
let backends_dir = build_backends_dir();
NamedChannelOrUrl::Path(backends_dir.display().to_string().into())
}

#[tokio::test]
async fn test_non_existing_discovery() {
let err = BuildFrontend::default()
Expand Down Expand Up @@ -120,6 +144,47 @@ async fn test_not_a_package() {
insta::assert_snapshot!(snapshot);
}

/// This test checks the diagnostic message that is emitted if a backend is used
/// that does not contain the expected executable.
#[tokio::test]
async fn missing_backend_executable() {
// Construct an in memory manifest for a workspace and a package.
let workspace = WorkspaceManifest {
workspace: Workspace {
platforms: [Platform::current()].into(),
preview: Preview::from_iter([KnownPreviewFeature::PixiBuild]),
..Workspace::default()
},
..WorkspaceManifest::default()
};

let package = PackageManifest {
package: Package::new("project".into(), "0.1.0".parse().unwrap()),
build: PackageBuild::new(
BuildBackend {
name: PackageName::new_unchecked("empty-backend"),
spec: BinarySpec::any(),
},
vec![build_backends_channel()],
),
targets: Default::default(),
};

// Construct a protocol for the workspace and package.
let err = pixi_build_frontend::pixi_protocol::ProtocolBuilder::new(
PathBuf::from("."),
PathBuf::from(pixi_consts::consts::WORKSPACE_MANIFEST),
workspace,
package,
)
.finish(ToolContext::default().into(), 0)
.await
.unwrap_err();

// Check that the error message contains the expected text.
insta::assert_snapshot!(error_to_snapshot(&err));
}

#[tokio::test]
async fn test_invalid_backend() {
// Setup a temporary project
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
source: crates/pixi_build_frontend/tests/diagnostics.rs
expression: error_to_snapshot(&err)
---
× failed to setup communication with the build-backend
╰─▶ × the build backend executable 'empty-backend' appears to be missing

help: This is often caused by a broken build-backend. Try upgrading or downgrading the build backend.
12 changes: 12 additions & 0 deletions crates/pixi_manifest/src/build_system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,18 @@ pub struct PackageBuild {
pub configuration: Option<serde_value::Value>,
}

impl PackageBuild {
/// Constructs a new instance from just a backend and channels.
pub fn new(backend: BuildBackend, channels: Vec<NamedChannelOrUrl>) -> Self {
Self {
backend,
channels: Some(channels),
additional_dependencies: IndexMap::default(),
configuration: None,
}
}
}

#[derive(Debug, Clone)]
pub struct BuildBackend {
/// The name of the build backend to install
Expand Down
2 changes: 2 additions & 0 deletions crates/pixi_manifest/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ mod warning;
mod workspace;

pub use activation::Activation;
pub use build_system::BuildBackend;
pub use build_system::PackageBuild;
pub use channel::PrioritizedChannel;
pub use dependencies::{CondaDependencies, Dependencies, PyPiDependencies};
Expand All @@ -46,6 +47,7 @@ pub use manifests::{
ProvenanceError, WithProvenance, WorkspaceManifest, WorkspaceManifestMut,
};
use miette::Diagnostic;
pub use package::Package;
pub use preview::{KnownPreviewFeature, Preview};
pub use pypi::pypi_requirement::PyPiRequirement;
use rattler_conda_types::Platform;
Expand Down
2 changes: 1 addition & 1 deletion crates/pixi_manifest/src/manifests/workspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ use crate::{

/// Holds the parsed content of the workspace part of a pixi manifest. This
/// describes the part related to the workspace only.
#[derive(Debug, Clone)]
#[derive(Debug, Default, Clone)]
pub struct WorkspaceManifest {
/// Information about the project
pub workspace: Workspace,
Expand Down
18 changes: 18 additions & 0 deletions crates/pixi_manifest/src/package.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,21 @@ pub struct Package {
/// URL of the project documentation
pub documentation: Option<Url>,
}

impl Package {
/// Creates a new package with the given name and version.
pub fn new(name: String, version: Version) -> Self {
Self {
name,
version,
description: None,
authors: None,
license: None,
license_file: None,
readme: None,
homepage: None,
repository: None,
documentation: None,
}
}
}
6 changes: 6 additions & 0 deletions crates/pixi_manifest/src/preview.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ impl Default for Preview {
}
}

impl FromIterator<KnownPreviewFeature> for Preview {
fn from_iter<T: IntoIterator<Item = KnownPreviewFeature>>(iter: T) -> Self {
Self::Features(iter.into_iter().collect())
}
}

impl Preview {
/// Returns true if all preview features are enabled
pub fn all_enabled(&self) -> bool {
Expand Down
16 changes: 8 additions & 8 deletions crates/pixi_manifest/src/toml/workspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,21 @@ use std::{
path::{Path, PathBuf},
};

use crate::toml::manifest::ExternalWorkspaceProperties;
use indexmap::{IndexMap, IndexSet};
use pixi_spec::TomlVersionSpecStr;
use pixi_toml::{TomlFromStr, TomlHashMap, TomlIndexMap, TomlIndexSet, TomlWith};
use rattler_conda_types::{NamedChannelOrUrl, Platform, Version, VersionSpec};
use toml_span::{de_helpers::TableHelper, DeserError, Span, Spanned, Value};
use url::Url;

use crate::{
error::GenericError,
pypi::pypi_options::PypiOptions,
toml::{platform::TomlPlatform, preview::TomlPreview},
toml::{manifest::ExternalWorkspaceProperties, platform::TomlPlatform, preview::TomlPreview},
utils::PixiSpanned,
workspace::ChannelPriority,
PrioritizedChannel, S3Options, TargetSelector, Targets, TomlError, WithWarnings, Workspace,
};
use indexmap::{IndexMap, IndexSet};
use pixi_spec::TomlVersionSpecStr;
use pixi_toml::{TomlFromStr, TomlHashMap, TomlIndexMap, TomlIndexSet, TomlWith};
use rattler_conda_types::{NamedChannelOrUrl, Platform, Version, VersionSpec};
use toml_span::{de_helpers::TableHelper, DeserError, Span, Spanned, Value};
use url::Url;

#[derive(Debug, Clone)]
pub struct TomlWorkspaceTarget {
Expand Down
5 changes: 5 additions & 0 deletions crates/pixi_spec/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,11 @@ pub enum BinarySpec {
}

impl BinarySpec {
/// Constructs a new instance that matches anything.
pub const fn any() -> Self {
Self::Version(VersionSpec::Any)
}

/// Convert this instance into a binary spec.
///
/// A binary spec always refers to a binary package.
Expand Down
Loading