diff --git a/crates/uv/src/commands/build_frontend.rs b/crates/uv/src/commands/build_frontend.rs index 028443a94e71..458cf3cce608 100644 --- a/crates/uv/src/commands/build_frontend.rs +++ b/crates/uv/src/commands/build_frontend.rs @@ -187,14 +187,19 @@ async fn build_impl( } }; - let project = workspace + let package = workspace .packages() .get(package) - .ok_or_else(|| anyhow::anyhow!("Package `{}` not found in workspace", package))? - .root(); + .ok_or_else(|| anyhow::anyhow!("Package `{package}` not found in workspace"))?; + + if !package.pyproject_toml().is_package() { + let name = &package.project().name; + let pyproject_toml = package.root().join("pyproject.toml"); + return Err(anyhow::anyhow!("Package `{}` is missing a `{}`. For example, to build with `{}`, add the following to `{}`:\n```toml\n[build-system]\nrequires = [\"setuptools\"]\nbuild-backend = \"setuptools.build_meta\"\n```", name.cyan(), "build-system".green(), "setuptools".cyan(), pyproject_toml.user_display().cyan())); + } vec![AnnotatedSource::from(Source::Directory(Cow::Borrowed( - project, + package.root(), )))] } else if all { if matches!(src, Source::File(_)) { @@ -211,6 +216,10 @@ async fn build_impl( } }; + if workspace.packages().is_empty() { + return Err(anyhow::anyhow!("No packages found in workspace")); + } + let packages: Vec<_> = workspace .packages() .values() @@ -222,7 +231,10 @@ async fn build_impl( .collect(); if packages.is_empty() { - return Err(anyhow::anyhow!("No packages found in workspace")); + let member = workspace.packages().values().next().unwrap(); + let name = &member.project().name; + let pyproject_toml = member.root().join("pyproject.toml"); + return Err(anyhow::anyhow!("Workspace does contain any buildable packages. For example, to build `{}` with `{}`, add a `{}` to `{}`:\n```toml\n[build-system]\nrequires = [\"setuptools\"]\nbuild-backend = \"setuptools.build_meta\"\n```", name.cyan(), "setuptools".cyan(), "build-system".green(), pyproject_toml.user_display().cyan())); } packages diff --git a/crates/uv/tests/it/build.rs b/crates/uv/tests/it/build.rs index 2c1a027d9e55..9c84e6446c84 100644 --- a/crates/uv/tests/it/build.rs +++ b/crates/uv/tests/it/build.rs @@ -1985,3 +1985,104 @@ fn git_boundary_in_dist_build() -> Result<()> { Ok(()) } + +#[test] +fn build_non_package() -> Result<()> { + let context = TestContext::new("3.12"); + let filters = context + .filters() + .into_iter() + .chain([ + (r"exit code: 1", "exit status: 1"), + (r"bdist\.[^/\\\s]+-[^/\\\s]+", "bdist.linux-x86_64"), + (r"\\\.", ""), + (r"\[project\]", "[PKG]"), + (r"\[member\]", "[PKG]"), + ]) + .collect::>(); + + let project = context.temp_dir.child("project"); + + let pyproject_toml = project.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["anyio==3.7.0"] + + [tool.uv.workspace] + members = ["packages/*"] + "#, + )?; + + project.child("src").child("__init__.py").touch()?; + project.child("README").touch()?; + + let member = project.child("packages").child("member"); + fs_err::create_dir_all(member.path())?; + + member.child("pyproject.toml").write_str( + r#" + [project] + name = "member" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["iniconfig"] + "#, + )?; + + member.child("src").child("__init__.py").touch()?; + member.child("README").touch()?; + + // Build the member. + uv_snapshot!(&filters, context.build().arg("--package").arg("member").current_dir(&project), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: Package `member` is missing a `build-system`. For example, to build with `setuptools`, add the following to `packages/member/pyproject.toml`: + ```toml + [build-system] + requires = ["setuptools"] + build-backend = "setuptools.build_meta" + ``` + "###); + + project + .child("dist") + .child("member-0.1.0.tar.gz") + .assert(predicate::path::missing()); + project + .child("dist") + .child("member-0.1.0-py3-none-any.whl") + .assert(predicate::path::missing()); + + // Build all packages. + uv_snapshot!(&filters, context.build().arg("--all").arg("--no-build-logs").current_dir(&project), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: Workspace does contain any buildable packages. For example, to build `member` with `setuptools`, add a `build-system` to `packages/member/pyproject.toml`: + ```toml + [build-system] + requires = ["setuptools"] + build-backend = "setuptools.build_meta" + ``` + "###); + + project + .child("dist") + .child("member-0.1.0.tar.gz") + .assert(predicate::path::missing()); + project + .child("dist") + .child("member-0.1.0-py3-none-any.whl") + .assert(predicate::path::missing()); + + Ok(()) +}