From b25993e470e387cb9f1d81d22e18e4780fc89e1c Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 10 Nov 2026 22:06:07 +0100 Subject: [PATCH] replace rustlings-macros with build script This turns rustlings into a single crate, which is less complicated to publish releases for. A follow-up step might be to remove the workspace configuration. The motivation was this discussion on Zulip: https://rust-lang.zulipchat.com/#narrow/channel/334454-t-lang-docs.2Frustlings/topic/Trusted.20publishing/near/638396712 --- CONTRIBUTING.md | 2 +- Cargo.lock | 11 +-- Cargo.toml | 8 ++- build.rs | 93 +++++++++++++++++++++++++ rustlings-macros/info.toml => info.toml | 0 rustlings-macros/Cargo.toml | 24 ------- rustlings-macros/src/lib.rs | 77 -------------------- src/embedded.rs | 2 +- 8 files changed, 103 insertions(+), 114 deletions(-) rename rustlings-macros/info.toml => info.toml (100%) delete mode 100644 rustlings-macros/Cargo.toml delete mode 100644 rustlings-macros/src/lib.rs diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2670e6ef..f4e221d6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -52,7 +52,7 @@ When updating an exercise, check if its solution needs to be updated. - Make sure to put in some helpful links, and link to sections of The Book in `exercises/yourTopic/README.md`. - In the exercise, add a `// TODO: …` comment where user changes are required. - Add a solution at `solutions/yourTopic/yourTopicN.rs` with comments explaining it. -- Add the [metadata for your exercise](#exercise-metadata) in the `rustlings-macros/info.toml` file. +- Add the [metadata for your exercise](#exercise-metadata) in the `info.toml` file. - [Open a pull request](#pull-requests). ### Exercise Metadata diff --git a/Cargo.lock b/Cargo.lock index f4d0bd1f..8ce8e508 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -434,8 +434,8 @@ dependencies = [ "clap", "crossterm", "notify", + "quote", "rustix", - "rustlings-macros", "serde", "serde_json", "shlex", @@ -444,15 +444,6 @@ dependencies = [ "wait-timeout", ] -[[package]] -name = "rustlings-macros" -version = "6.5.0" -dependencies = [ - "quote", - "serde", - "toml", -] - [[package]] name = "same-file" version = "1.0.6" diff --git a/Cargo.toml b/Cargo.toml index b5e19c0a..f4d87a92 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,10 +36,12 @@ keywords = [ ] include = [ "/src/", + "/build.rs", "/exercises/", "/solutions/", # A symlink to be able to include `dev/Cargo.toml` although `dev` is excluded. "/dev-Cargo.toml", + "/info.toml", "/README.md", "/LICENSE", ] @@ -49,7 +51,6 @@ anyhow = "1" clap = { version = "4", features = ["derive"] } crossterm = { version = "0.29", default-features = false, features = ["windows", "events"] } notify = "8" -rustlings-macros = { path = "rustlings-macros", version = "=6.5.0" } serde_json = "1" serde.workspace = true shlex = "2" @@ -59,6 +60,11 @@ wait-timeout = "0.2" [target.'cfg(not(windows))'.dependencies] rustix = { version = "1.0", default-features = false, features = ["std", "stdio", "termios"] } +[build-dependencies] +quote = "1" +serde.workspace = true +toml.workspace = true + [dev-dependencies] tempfile = "3" diff --git a/build.rs b/build.rs index 56878641..8a1392a7 100644 --- a/build.rs +++ b/build.rs @@ -1,5 +1,98 @@ +use quote::quote; +use serde::Deserialize; + fn main() { // Fix building from source on Windows because it can't handle file links. #[cfg(windows)] let _ = std::fs::copy("dev/Cargo.toml", "dev-Cargo.toml"); + + process_info_toml(); +} + +#[derive(Deserialize)] +struct ExerciseInfo<'a> { + name: &'a str, + dir: &'a str, + #[serde(default)] + input_files: Vec<&'a str>, +} + +#[derive(Deserialize)] +struct InfoFile<'a> { + #[serde(borrow)] + exercises: Vec>, +} + +/// Translates the data in info.toml to Rust data structures to be embedded in +/// the final binary. +pub fn process_info_toml() { + let info_file = include_str!("info.toml"); + let info = toml::de::from_str::(info_file).expect("Failed to parse `info.toml`"); + let exercises = info.exercises; + let manifest_dir = env!("CARGO_MANIFEST_DIR"); + + let exercise_files = exercises.iter().map(|exercise| { + format!( + "{manifest_dir}/exercises/{}/{}.rs", + exercise.dir, exercise.name + ) + }); + let solution_files = exercises.iter().map(|exercise| { + format!( + "{manifest_dir}/solutions/{}/{}.rs", + exercise.dir, exercise.name + ) + }); + + let mut dirs = Vec::with_capacity(32); + let mut dir_inds = vec![0; exercises.len()]; + + for (exercise, dir_ind) in exercises.iter().zip(&mut dir_inds) { + // The directory is often the last one inserted. + if let Some(ind) = dirs.iter().rev().position(|dir| *dir == exercise.dir) { + *dir_ind = dirs.len() - 1 - ind; + continue; + } + + dirs.push(exercise.dir); + *dir_ind = dirs.len() - 1; + } + + let input_files = exercises.iter().map(|exercise| { + let names = exercise.input_files.iter(); + let paths = exercise + .input_files + .iter() + .map(|f| format!("{manifest_dir}/exercises/{}/{}", exercise.dir, f)); + quote! { + &[#(InputFile { + name: #names, + content: include_str!(#paths), + }),*] + } + }); + + let readmes = dirs + .iter() + .map(|dir| format!("{manifest_dir}/exercises/{dir}/README.md")); + + let embedded_files = quote! { + EmbeddedFiles { + info_file: #info_file, + exercise_files: &[#(ExerciseFiles { + exercise: include_bytes!(#exercise_files), + solution: include_bytes!(#solution_files), + dir_ind: #dir_inds, + input_files: #input_files, + }),*], + exercise_dirs: &[#(ExerciseDir { name: #dirs, readme: include_bytes!(#readmes) }),*], + } + }; + + let out_dir = std::env::var("OUT_DIR").unwrap(); + std::fs::write( + format!("{out_dir}/embedded_files.rs"), + embedded_files.to_string(), + ) + .unwrap(); } diff --git a/rustlings-macros/info.toml b/info.toml similarity index 100% rename from rustlings-macros/info.toml rename to info.toml diff --git a/rustlings-macros/Cargo.toml b/rustlings-macros/Cargo.toml deleted file mode 100644 index 5d123b51..00000000 --- a/rustlings-macros/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -name = "rustlings-macros" -description = "A macros crate intended to be used only by Rustlings" -version.workspace = true -authors.workspace = true -repository.workspace = true -license.workspace = true -edition.workspace = true -rust-version.workspace = true -include = [ - "/src/", - "/info.toml", -] - -[lib] -proc-macro = true - -[dependencies] -quote = "1" -serde.workspace = true -toml.workspace = true - -[lints] -workspace = true diff --git a/rustlings-macros/src/lib.rs b/rustlings-macros/src/lib.rs deleted file mode 100644 index b672b850..00000000 --- a/rustlings-macros/src/lib.rs +++ /dev/null @@ -1,77 +0,0 @@ -use proc_macro::TokenStream; -use quote::quote; -use serde::Deserialize; - -#[derive(Deserialize)] -struct ExerciseInfo<'a> { - name: &'a str, - dir: &'a str, - #[serde(default)] - input_files: Vec<&'a str>, -} - -#[derive(Deserialize)] -struct InfoFile<'a> { - #[serde(borrow)] - exercises: Vec>, -} - -#[proc_macro] -pub fn include_files(_: TokenStream) -> TokenStream { - let info_file = include_str!("../info.toml"); - let info = toml::de::from_str::(info_file).expect("Failed to parse `info.toml`"); - let exercises = info.exercises; - - let exercise_files = exercises - .iter() - .map(|exercise| format!("../exercises/{}/{}.rs", exercise.dir, exercise.name)); - let solution_files = exercises - .iter() - .map(|exercise| format!("../solutions/{}/{}.rs", exercise.dir, exercise.name)); - - let mut dirs = Vec::with_capacity(32); - let mut dir_inds = vec![0; exercises.len()]; - - for (exercise, dir_ind) in exercises.iter().zip(&mut dir_inds) { - // The directory is often the last one inserted. - if let Some(ind) = dirs.iter().rev().position(|dir| *dir == exercise.dir) { - *dir_ind = dirs.len() - 1 - ind; - continue; - } - - dirs.push(exercise.dir); - *dir_ind = dirs.len() - 1; - } - - let input_files = exercises.iter().map(|exercise| { - let names = exercise.input_files.iter(); - let paths = exercise - .input_files - .iter() - .map(|f| format!("../exercises/{}/{}", exercise.dir, f)); - quote! { - &[#(InputFile { - name: #names, - content: include_str!(#paths), - }),*] - } - }); - - let readmes = dirs - .iter() - .map(|dir| format!("../exercises/{dir}/README.md")); - - quote! { - EmbeddedFiles { - info_file: #info_file, - exercise_files: &[#(ExerciseFiles { - exercise: include_bytes!(#exercise_files), - solution: include_bytes!(#solution_files), - dir_ind: #dir_inds, - input_files: #input_files, - }),*], - exercise_dirs: &[#(ExerciseDir { name: #dirs, readme: include_bytes!(#readmes) }),*], - } - } - .into() -} diff --git a/src/embedded.rs b/src/embedded.rs index 9e2527d6..7aa48d10 100644 --- a/src/embedded.rs +++ b/src/embedded.rs @@ -7,7 +7,7 @@ use std::{ use crate::info_file::ExerciseInfo; /// Contains all embedded files. -pub static EMBEDDED_FILES: EmbeddedFiles = rustlings_macros::include_files!(); +pub static EMBEDDED_FILES: EmbeddedFiles = include!(concat!(env!("OUT_DIR"), "/embedded_files.rs")); // Files related to one exercise. pub struct ExerciseFiles {