From 10f12f312b978665c29b94aa01044d73d01c4034 Mon Sep 17 00:00:00 2001 From: Ben Ibinson Date: Wed, 24 May 2023 13:06:37 +0100 Subject: [PATCH 1/3] First attempt at using cargo to build --- .gitignore | 2 ++ exercises/async/async1.rs | 17 +++++++++++++++++ info.toml | 21 +++++++++++++++++++++ src/exercise.rs | 32 +++++++++++++++++++++++++++++++- src/run.rs | 1 + src/verify.rs | 3 +++ 6 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 exercises/async/async1.rs diff --git a/.gitignore b/.gitignore index 88bf2b6c7b..da85be1025 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,8 @@ target/ *.pdb exercises/clippy/Cargo.toml exercises/clippy/Cargo.lock +exercises/async/Cargo.toml +exercises/async/Cargo.lock rust-project.json .idea .vscode/* diff --git a/exercises/async/async1.rs b/exercises/async/async1.rs new file mode 100644 index 0000000000..faded8c0ec --- /dev/null +++ b/exercises/async/async1.rs @@ -0,0 +1,17 @@ +// async1.rs +// Running a basic async function. Try to get the answer to print out. +// Note that `tokio` with full features have been added to `Cargo.toml` for you already. +// +// Execute `rustlings hint async1` or use the `hint` watch subcommand for a hint. + +// I AM NOT DONE + +fn main() { + let result = complex_calculation(); + println!("The answer is {result}"); +} + +// Don't change anything below this line. +async fn complex_calculation() -> i32 { + 2 + 2 +} diff --git a/info.toml b/info.toml index 2add5f0ccf..aa2ed60396 100644 --- a/info.toml +++ b/info.toml @@ -1171,3 +1171,24 @@ path = "exercises/conversions/as_ref_mut.rs" mode = "test" hint = """ Add AsRef as a trait bound to the functions.""" + +[[exercises]] +name = "async1" +path = "exercises/async/async1.rs" +mode = "async" +hint = """ +The function that we need to run is async. You might want to look at +https://rust-lang.github.io/async-book/01_getting_started/04_async_await_primer.html +for a primer on using async functions. + +The println! can't print out the result of the async function as it returns a Future. Is there a way to wait for the Future to complete? + +The result needs to be awaited before it can be printed. You can use the .await syntax to do this. + +Using await will cause the main function to become async. What happens if we add the async keyword to the main function? + +Remember that we have tokio imported. To start an async function, we need a runtime to run it. + +We can use the #[tokio::main] attribute macro to start the runtime. This will allow us to run async functions in the main function. +[[exercises]] +""" diff --git a/src/exercise.rs b/src/exercise.rs index 2cde4e1592..a06a1caf8b 100644 --- a/src/exercise.rs +++ b/src/exercise.rs @@ -12,6 +12,7 @@ const RUSTC_EDITION_ARGS: &[&str] = &["--edition", "2021"]; const I_AM_DONE_REGEX: &str = r"(?m)^\s*///?\s*I\s+AM\s+NOT\s+DONE"; const CONTEXT: usize = 2; const CLIPPY_CARGO_TOML_PATH: &str = "./exercises/clippy/Cargo.toml"; +const ASYNC_CARGO_TOML_PATH: &str = "./exercises/async/Cargo.toml"; // Get a temporary file name that is hopefully unique #[inline] @@ -34,6 +35,8 @@ pub enum Mode { Test, // Indicates that the exercise should be linted with clippy Clippy, + // Indicates that the exercise should be compiled as a binary with tokio + Async, } #[derive(Deserialize)] @@ -161,6 +164,27 @@ path = "{}.rs""#, .args(&["--", "-D", "warnings", "-D", "clippy::float_cmp"]) .output() } + + Mode::Async => { + let cargo_toml = format!( + r#"[package] +name = "{}" +version = "0.0.1" +edition = "2021" +[[bin]] +name = "{}" +path = "{}.rs" + +[dependencies] +tokio = {{ version = "1.28.1", features = ["full"] }}"#, + self.name, &temp_file()[2..], self.name + ); + fs::write(ASYNC_CARGO_TOML_PATH, cargo_toml).expect("Failed to write async Cargo.toml file."); + Command::new("cargo") + .args(&["build", "--manifest-path", ASYNC_CARGO_TOML_PATH]) + .args(RUSTC_COLOR_ARGS) + .output() + } } .expect("Failed to run 'compile' command."); @@ -183,7 +207,13 @@ path = "{}.rs""#, Mode::Test => "--show-output", _ => "", }; - let cmd = Command::new(&temp_file()) + + let bin_path = match self.mode { + Mode::Async => format!("./exercises/async/target/debug/{}", &temp_file()[2..]), + _ => temp_file() + }; + + let cmd = Command::new(bin_path) .arg(arg) .output() .expect("Failed to run 'run' command"); diff --git a/src/run.rs b/src/run.rs index 1e2e56cfad..8a81162ed6 100644 --- a/src/run.rs +++ b/src/run.rs @@ -13,6 +13,7 @@ pub fn run(exercise: &Exercise, verbose: bool) -> Result<(), ()> { Mode::Test => test(exercise, verbose)?, Mode::Compile => compile_and_run(exercise)?, Mode::Clippy => compile_and_run(exercise)?, + Mode::Async => compile_and_run(exercise)?, } Ok(()) } diff --git a/src/verify.rs b/src/verify.rs index f3f3b564a5..285c8ad5a4 100644 --- a/src/verify.rs +++ b/src/verify.rs @@ -29,6 +29,7 @@ pub fn verify<'a>( Mode::Test => compile_and_test(exercise, RunMode::Interactive, verbose, success_hints), Mode::Compile => compile_and_run_interactively(exercise, success_hints), Mode::Clippy => compile_only(exercise, success_hints), + Mode::Async => compile_only(exercise, success_hints), }; if !compile_result.unwrap_or(false) { return Err(exercise); @@ -152,6 +153,7 @@ fn prompt_for_completion(exercise: &Exercise, prompt_output: Option, suc Mode::Compile => success!("Successfully ran {}!", exercise), Mode::Test => success!("Successfully tested {}!", exercise), Mode::Clippy => success!("Successfully compiled {}!", exercise), + Mode::Async => success!("Successfully compiled {}!", exercise), } let no_emoji = env::var("NO_EMOJI").is_ok(); @@ -164,6 +166,7 @@ fn prompt_for_completion(exercise: &Exercise, prompt_output: Option, suc let success_msg = match exercise.mode { Mode::Compile => "The code is compiling!", + Mode::Async => "The code is compiling!", Mode::Test => "The code is compiling, and the tests pass!", Mode::Clippy => clippy_success_msg, }; From 9f377bfcd3714458d55e199b8221bef88f7fef88 Mon Sep 17 00:00:00 2001 From: Ben Ibinson Date: Fri, 26 May 2023 14:13:25 +0100 Subject: [PATCH 2/3] Hacked in rust analyser support --- src/main.rs | 2 ++ src/project.rs | 51 +++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/src/main.rs b/src/main.rs index 0a9af2ec09..e23e06eae7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -242,6 +242,8 @@ fn main() { project .get_sysroot_src() .expect("Couldn't find toolchain path, do you have `rustc` installed?"); + + project.get_cargo_tokio_path(); project .exercises_to_json() .expect("Couldn't parse rustlings exercises files"); diff --git a/src/project.rs b/src/project.rs index ebebe27d3a..dc746b2a69 100644 --- a/src/project.rs +++ b/src/project.rs @@ -10,6 +10,9 @@ use std::process::Command; #[derive(Serialize, Deserialize)] pub struct RustAnalyzerProject { sysroot_src: String, + + #[serde(skip)] + cargo_tokio: String, pub crates: Vec, } @@ -17,14 +20,22 @@ pub struct RustAnalyzerProject { pub struct Crate { root_module: String, edition: String, - deps: Vec, + deps: Vec, cfg: Vec, } +#[derive(Serialize, Deserialize)] +pub struct DepData { + #[serde(rename="crate")] + crate_index: i32, + name: String, +} + impl RustAnalyzerProject { pub fn new() -> RustAnalyzerProject { RustAnalyzerProject { sysroot_src: String::new(), + cargo_tokio: String::new(), crates: Vec::new(), } } @@ -42,23 +53,41 @@ impl RustAnalyzerProject { fn path_to_json(&mut self, path: PathBuf) -> Result<(), Box> { if let Some(ext) = path.extension() { if ext == "rs" { - self.crates.push(Crate { + let mut c = Crate { root_module: path.display().to_string(), edition: "2021".to_string(), deps: Vec::new(), // This allows rust_analyzer to work inside #[test] blocks cfg: vec!["test".to_string()], - }) + }; + if path.display().to_string().starts_with("exercises/async") { + c.deps = vec!(DepData{ crate_index: 0, name: "tokio".to_string()}) + } + self.crates.push(c); } } Ok(()) } + fn add_tokio_to_crates(&mut self) { + self.crates.push(Crate { + root_module: self.cargo_tokio.to_string(), + edition: "2021".to_string(), + deps: Vec::new(), + // This allows rust_analyzer to work inside #[test] blocks + cfg: vec![ + "feature=\"rt\"".to_string(), + "feature=\"rt-multi-thread\"".to_string() + ], + }); + } + /// Parse the exercises folder for .rs files, any matches will create /// a new `crate` in rust-project.json which allows rust-analyzer to /// treat it like a normal binary pub fn exercises_to_json(&mut self) -> Result<(), Box> { + self.add_tokio_to_crates(); for path in glob("./exercises/**/*")? { self.path_to_json(path?)?; } @@ -96,4 +125,20 @@ impl RustAnalyzerProject { .to_string(); Ok(()) } + + + pub fn get_cargo_tokio_path(&mut self) { + let home = env::var("HOME").unwrap_or_else(|_| String::from("~/")); + self.cargo_tokio = (std::path::Path::new(&home) + .join(".cargo") + .join("registry") + .join("src") + .join("github.com-1ecc6299db9ec823") + .join("tokio-1.28.1") + .join("src") + .join("lib.rs") + .to_string_lossy()) + .to_string(); + } + } From f849b030697911de0d2771616142e2f66844d780 Mon Sep 17 00:00:00 2001 From: Ben Ibinson Date: Fri, 26 May 2023 14:24:06 +0100 Subject: [PATCH 3/3] Add all features --- src/project.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/project.rs b/src/project.rs index dc746b2a69..6b92d20233 100644 --- a/src/project.rs +++ b/src/project.rs @@ -77,8 +77,18 @@ impl RustAnalyzerProject { deps: Vec::new(), // This allows rust_analyzer to work inside #[test] blocks cfg: vec![ + "feature=\"fs\"".to_string(), + "feature=\"io-util\"".to_string(), + "feature=\"io-std\"".to_string(), + "feature=\"macros\"".to_string(), + "feature=\"net\"".to_string(), + "feature=\"parking_lot\"".to_string(), + "feature=\"process\"".to_string(), "feature=\"rt\"".to_string(), - "feature=\"rt-multi-thread\"".to_string() + "feature=\"rt-multi-thread\"".to_string(), + "feature=\"signal\"".to_string(), + "feature=\"sync\"".to_string(), + "feature=\"time\"".to_string(), ], }); }