tasks/rust: Add support for running examples as binary targets (#21412)

Closes #21044

Release Notes:

- Added support for running Rust examples as tasks.
This commit is contained in:
Piotr Osiewicz 2024-12-02 19:53:51 +01:00 committed by GitHub
parent dbe41823d9
commit 95a047c11b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -10,6 +10,7 @@ pub use language::*;
use lsp::{LanguageServerBinary, LanguageServerName}; use lsp::{LanguageServerBinary, LanguageServerName};
use regex::Regex; use regex::Regex;
use smol::fs::{self}; use smol::fs::{self};
use std::fmt::Display;
use std::{ use std::{
any::Any, any::Any,
borrow::Cow, borrow::Cow,
@ -444,6 +445,10 @@ const RUST_PACKAGE_TASK_VARIABLE: VariableName =
const RUST_BIN_NAME_TASK_VARIABLE: VariableName = const RUST_BIN_NAME_TASK_VARIABLE: VariableName =
VariableName::Custom(Cow::Borrowed("RUST_BIN_NAME")); VariableName::Custom(Cow::Borrowed("RUST_BIN_NAME"));
/// The bin kind (bin/example) corresponding to the current file in Cargo.toml
const RUST_BIN_KIND_TASK_VARIABLE: VariableName =
VariableName::Custom(Cow::Borrowed("RUST_BIN_KIND"));
const RUST_MAIN_FUNCTION_TASK_VARIABLE: VariableName = const RUST_MAIN_FUNCTION_TASK_VARIABLE: VariableName =
VariableName::Custom(Cow::Borrowed("_rust_main_function_end")); VariableName::Custom(Cow::Borrowed("_rust_main_function_end"));
@ -469,12 +474,16 @@ impl ContextProvider for RustContextProvider {
.is_some(); .is_some();
if is_main_function { if is_main_function {
if let Some((package_name, bin_name)) = local_abs_path.and_then(|path| { if let Some(target) = local_abs_path.and_then(|path| {
package_name_and_bin_name_from_abs_path(path, project_env.as_ref()) package_name_and_bin_name_from_abs_path(path, project_env.as_ref())
}) { }) {
return Task::ready(Ok(TaskVariables::from_iter([ return Task::ready(Ok(TaskVariables::from_iter([
(RUST_PACKAGE_TASK_VARIABLE.clone(), package_name), (RUST_PACKAGE_TASK_VARIABLE.clone(), target.package_name),
(RUST_BIN_NAME_TASK_VARIABLE.clone(), bin_name), (RUST_BIN_NAME_TASK_VARIABLE.clone(), target.target_name),
(
RUST_BIN_KIND_TASK_VARIABLE.clone(),
target.target_kind.to_string(),
),
]))); ])));
} }
} }
@ -568,8 +577,9 @@ impl ContextProvider for RustContextProvider {
}, },
TaskTemplate { TaskTemplate {
label: format!( label: format!(
"cargo run -p {} --bin {}", "cargo run -p {} --{} {}",
RUST_PACKAGE_TASK_VARIABLE.template_value(), RUST_PACKAGE_TASK_VARIABLE.template_value(),
RUST_BIN_KIND_TASK_VARIABLE.template_value(),
RUST_BIN_NAME_TASK_VARIABLE.template_value(), RUST_BIN_NAME_TASK_VARIABLE.template_value(),
), ),
command: "cargo".into(), command: "cargo".into(),
@ -577,7 +587,7 @@ impl ContextProvider for RustContextProvider {
"run".into(), "run".into(),
"-p".into(), "-p".into(),
RUST_PACKAGE_TASK_VARIABLE.template_value(), RUST_PACKAGE_TASK_VARIABLE.template_value(),
"--bin".into(), format!("--{}", RUST_BIN_KIND_TASK_VARIABLE.template_value()),
RUST_BIN_NAME_TASK_VARIABLE.template_value(), RUST_BIN_NAME_TASK_VARIABLE.template_value(),
], ],
cwd: Some("$ZED_DIRNAME".to_owned()), cwd: Some("$ZED_DIRNAME".to_owned()),
@ -635,10 +645,42 @@ struct CargoTarget {
src_path: String, src_path: String,
} }
#[derive(Debug, PartialEq)]
enum TargetKind {
Bin,
Example,
}
impl Display for TargetKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TargetKind::Bin => write!(f, "bin"),
TargetKind::Example => write!(f, "example"),
}
}
}
impl TryFrom<&str> for TargetKind {
type Error = ();
fn try_from(value: &str) -> Result<Self, ()> {
match value {
"bin" => Ok(Self::Bin),
"example" => Ok(Self::Example),
_ => Err(()),
}
}
}
/// Which package and binary target are we in?
struct TargetInfo {
package_name: String,
target_name: String,
target_kind: TargetKind,
}
fn package_name_and_bin_name_from_abs_path( fn package_name_and_bin_name_from_abs_path(
abs_path: &Path, abs_path: &Path,
project_env: Option<&HashMap<String, String>>, project_env: Option<&HashMap<String, String>>,
) -> Option<(String, String)> { ) -> Option<TargetInfo> {
let mut command = util::command::new_std_command("cargo"); let mut command = util::command::new_std_command("cargo");
if let Some(envs) = project_env { if let Some(envs) = project_env {
command.envs(envs); command.envs(envs);
@ -656,10 +698,14 @@ fn package_name_and_bin_name_from_abs_path(
let metadata: CargoMetadata = serde_json::from_slice(&output).log_err()?; let metadata: CargoMetadata = serde_json::from_slice(&output).log_err()?;
retrieve_package_id_and_bin_name_from_metadata(metadata, abs_path).and_then( retrieve_package_id_and_bin_name_from_metadata(metadata, abs_path).and_then(
|(package_id, bin_name)| { |(package_id, bin_name, target_kind)| {
let package_name = package_name_from_pkgid(&package_id); let package_name = package_name_from_pkgid(&package_id);
package_name.map(|package_name| (package_name.to_owned(), bin_name)) package_name.map(|package_name| TargetInfo {
package_name: package_name.to_owned(),
target_name: bin_name,
target_kind,
})
}, },
) )
} }
@ -667,13 +713,19 @@ fn package_name_and_bin_name_from_abs_path(
fn retrieve_package_id_and_bin_name_from_metadata( fn retrieve_package_id_and_bin_name_from_metadata(
metadata: CargoMetadata, metadata: CargoMetadata,
abs_path: &Path, abs_path: &Path,
) -> Option<(String, String)> { ) -> Option<(String, String, TargetKind)> {
for package in metadata.packages { for package in metadata.packages {
for target in package.targets { for target in package.targets {
let is_bin = target.kind.iter().any(|kind| kind == "bin"); let Some(bin_kind) = target
.kind
.iter()
.find_map(|kind| TargetKind::try_from(kind.as_ref()).ok())
else {
continue;
};
let target_path = PathBuf::from(target.src_path); let target_path = PathBuf::from(target.src_path);
if target_path == abs_path && is_bin { if target_path == abs_path {
return Some((package.id, target.name)); return Some((package.id, target.name, bin_kind));
} }
} }
} }
@ -1066,7 +1118,11 @@ mod tests {
( (
r#"{"packages":[{"id":"path+file:///path/to/zed/crates/zed#0.131.0","targets":[{"name":"zed","kind":["bin"],"src_path":"/path/to/zed/src/main.rs"}]}]}"#, r#"{"packages":[{"id":"path+file:///path/to/zed/crates/zed#0.131.0","targets":[{"name":"zed","kind":["bin"],"src_path":"/path/to/zed/src/main.rs"}]}]}"#,
"/path/to/zed/src/main.rs", "/path/to/zed/src/main.rs",
Some(("path+file:///path/to/zed/crates/zed#0.131.0", "zed")), Some((
"path+file:///path/to/zed/crates/zed#0.131.0",
"zed",
TargetKind::Bin,
)),
), ),
( (
r#"{"packages":[{"id":"path+file:///path/to/custom-package#my-custom-package@0.1.0","targets":[{"name":"my-custom-bin","kind":["bin"],"src_path":"/path/to/custom-package/src/main.rs"}]}]}"#, r#"{"packages":[{"id":"path+file:///path/to/custom-package#my-custom-package@0.1.0","targets":[{"name":"my-custom-bin","kind":["bin"],"src_path":"/path/to/custom-package/src/main.rs"}]}]}"#,
@ -1074,6 +1130,16 @@ mod tests {
Some(( Some((
"path+file:///path/to/custom-package#my-custom-package@0.1.0", "path+file:///path/to/custom-package#my-custom-package@0.1.0",
"my-custom-bin", "my-custom-bin",
TargetKind::Bin,
)),
),
(
r#"{"packages":[{"id":"path+file:///path/to/custom-package#my-custom-package@0.1.0","targets":[{"name":"my-custom-bin","kind":["example"],"src_path":"/path/to/custom-package/src/main.rs"}]}]}"#,
"/path/to/custom-package/src/main.rs",
Some((
"path+file:///path/to/custom-package#my-custom-package@0.1.0",
"my-custom-bin",
TargetKind::Example,
)), )),
), ),
( (
@ -1088,7 +1154,7 @@ mod tests {
assert_eq!( assert_eq!(
retrieve_package_id_and_bin_name_from_metadata(metadata, absolute_path), retrieve_package_id_and_bin_name_from_metadata(metadata, absolute_path),
expected.map(|(pkgid, bin)| (pkgid.to_owned(), bin.to_owned())) expected.map(|(pkgid, name, kind)| (pkgid.to_owned(), name.to_owned(), kind))
); );
} }
} }