|
1 | | -use glob::glob; |
2 | | -use serde::{Deserialize, Serialize}; |
| 1 | +use anyhow::{Context, Result}; |
| 2 | +use serde::Serialize; |
3 | 3 | use std::env; |
4 | | -use std::error::Error; |
5 | | -use std::path::{Path, PathBuf}; |
6 | | -use std::process::Command; |
| 4 | +use std::path::PathBuf; |
| 5 | +use std::process::{Command, Stdio}; |
| 6 | + |
| 7 | +use crate::exercise::Exercise; |
7 | 8 |
|
8 | 9 | /// Contains the structure of resulting rust-project.json file |
9 | 10 | /// and functions to build the data required to create the file |
10 | | -#[derive(Serialize, Deserialize)] |
11 | | -pub struct RustAnalyzerProject { |
12 | | - sysroot_src: String, |
13 | | - pub crates: Vec<Crate>, |
| 11 | +#[derive(Serialize)] |
| 12 | +struct RustAnalyzerProject { |
| 13 | + sysroot_src: PathBuf, |
| 14 | + crates: Vec<Crate>, |
14 | 15 | } |
15 | 16 |
|
16 | | -#[derive(Serialize, Deserialize)] |
17 | | -pub struct Crate { |
18 | | - root_module: String, |
19 | | - edition: String, |
20 | | - deps: Vec<String>, |
21 | | - cfg: Vec<String>, |
| 17 | +#[derive(Serialize)] |
| 18 | +struct Crate { |
| 19 | + root_module: PathBuf, |
| 20 | + edition: &'static str, |
| 21 | + // Not used, but required in the JSON file. |
| 22 | + deps: Vec<()>, |
| 23 | + // Only `test` is used for all crates. |
| 24 | + // Therefore, an array is used instead of a `Vec`. |
| 25 | + cfg: [&'static str; 1], |
22 | 26 | } |
23 | 27 |
|
24 | 28 | impl RustAnalyzerProject { |
25 | | - pub fn new() -> RustAnalyzerProject { |
26 | | - RustAnalyzerProject { |
27 | | - sysroot_src: String::new(), |
28 | | - crates: Vec::new(), |
29 | | - } |
30 | | - } |
31 | | - |
32 | | - /// Write rust-project.json to disk |
33 | | - pub fn write_to_disk(&self) -> Result<(), std::io::Error> { |
34 | | - std::fs::write( |
35 | | - "./rust-project.json", |
36 | | - serde_json::to_vec(&self).expect("Failed to serialize to JSON"), |
37 | | - )?; |
38 | | - Ok(()) |
39 | | - } |
| 29 | + fn build(exercises: Vec<Exercise>) -> Result<Self> { |
| 30 | + let crates = exercises |
| 31 | + .into_iter() |
| 32 | + .map(|exercise| Crate { |
| 33 | + root_module: exercise.path, |
| 34 | + edition: "2021", |
| 35 | + deps: Vec::new(), |
| 36 | + // This allows rust_analyzer to work inside `#[test]` blocks |
| 37 | + cfg: ["test"], |
| 38 | + }) |
| 39 | + .collect(); |
40 | 40 |
|
41 | | - /// If path contains .rs extension, add a crate to `rust-project.json` |
42 | | - fn path_to_json(&mut self, path: PathBuf) -> Result<(), Box<dyn Error>> { |
43 | | - if let Some(ext) = path.extension() { |
44 | | - if ext == "rs" { |
45 | | - self.crates.push(Crate { |
46 | | - root_module: path.display().to_string(), |
47 | | - edition: "2021".to_string(), |
48 | | - deps: Vec::new(), |
49 | | - // This allows rust_analyzer to work inside #[test] blocks |
50 | | - cfg: vec!["test".to_string()], |
51 | | - }) |
52 | | - } |
53 | | - } |
54 | | - |
55 | | - Ok(()) |
56 | | - } |
57 | | - |
58 | | - /// Parse the exercises folder for .rs files, any matches will create |
59 | | - /// a new `crate` in rust-project.json which allows rust-analyzer to |
60 | | - /// treat it like a normal binary |
61 | | - pub fn exercises_to_json(&mut self) -> Result<(), Box<dyn Error>> { |
62 | | - for path in glob("./exercises/**/*")? { |
63 | | - self.path_to_json(path?)?; |
64 | | - } |
65 | | - Ok(()) |
66 | | - } |
67 | | - |
68 | | - /// Use `rustc` to determine the default toolchain |
69 | | - pub fn get_sysroot_src(&mut self) -> Result<(), Box<dyn Error>> { |
70 | | - // check if RUST_SRC_PATH is set |
71 | | - if let Ok(path) = env::var("RUST_SRC_PATH") { |
72 | | - self.sysroot_src = path; |
73 | | - return Ok(()); |
| 41 | + if let Some(path) = env::var_os("RUST_SRC_PATH") { |
| 42 | + return Ok(Self { |
| 43 | + sysroot_src: PathBuf::from(path), |
| 44 | + crates, |
| 45 | + }); |
74 | 46 | } |
75 | 47 |
|
76 | 48 | let toolchain = Command::new("rustc") |
77 | 49 | .arg("--print") |
78 | 50 | .arg("sysroot") |
79 | | - .output()? |
| 51 | + .stderr(Stdio::inherit()) |
| 52 | + .output() |
| 53 | + .context("Failed to get the sysroot from `rustc`. Do you have `rustc` installed?")? |
80 | 54 | .stdout; |
81 | 55 |
|
82 | | - let toolchain = String::from_utf8(toolchain)?; |
| 56 | + let toolchain = |
| 57 | + String::from_utf8(toolchain).context("The toolchain path is invalid UTF8")?; |
83 | 58 | let toolchain = toolchain.trim_end(); |
84 | | - |
85 | 59 | println!("Determined toolchain: {toolchain}\n"); |
86 | 60 |
|
87 | | - let Ok(path) = Path::new(toolchain) |
88 | | - .join("lib") |
89 | | - .join("rustlib") |
90 | | - .join("src") |
91 | | - .join("rust") |
92 | | - .join("library") |
93 | | - .into_os_string() |
94 | | - .into_string() |
95 | | - else { |
96 | | - return Err("The sysroot path is invalid UTF8".into()); |
97 | | - }; |
98 | | - self.sysroot_src = path; |
| 61 | + let mut sysroot_src = PathBuf::with_capacity(256); |
| 62 | + sysroot_src.extend([toolchain, "lib", "rustlib", "src", "rust", "library"]); |
99 | 63 |
|
100 | | - Ok(()) |
| 64 | + Ok(Self { |
| 65 | + sysroot_src, |
| 66 | + crates, |
| 67 | + }) |
101 | 68 | } |
102 | 69 | } |
| 70 | + |
| 71 | +/// Write `rust-project.json` to disk. |
| 72 | +pub fn write_project_json(exercises: Vec<Exercise>) -> Result<()> { |
| 73 | + let content = RustAnalyzerProject::build(exercises)?; |
| 74 | + |
| 75 | + // Using the capacity 2^14 since the file length in bytes is higher than 2^13. |
| 76 | + // The final length is not known exactly because it depends on the user's sysroot path, |
| 77 | + // the current number of exercises etc. |
| 78 | + let mut buf = Vec::with_capacity(1 << 14); |
| 79 | + serde_json::to_writer(&mut buf, &content)?; |
| 80 | + std::fs::write("rust-project.json", buf)?; |
| 81 | + |
| 82 | + Ok(()) |
| 83 | +} |
0 commit comments