|
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