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
This commit is contained in:
Remo Senekowitsch
2026-11-10 22:29:42 +01:00
parent f7b9b3c588
commit b25993e470
8 changed files with 103 additions and 114 deletions
+1 -1
View File
@@ -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`. - 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. - 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 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). - [Open a pull request](#pull-requests).
### Exercise Metadata ### Exercise Metadata
Generated
+1 -10
View File
@@ -434,8 +434,8 @@ dependencies = [
"clap", "clap",
"crossterm", "crossterm",
"notify", "notify",
"quote",
"rustix", "rustix",
"rustlings-macros",
"serde", "serde",
"serde_json", "serde_json",
"shlex", "shlex",
@@ -444,15 +444,6 @@ dependencies = [
"wait-timeout", "wait-timeout",
] ]
[[package]]
name = "rustlings-macros"
version = "6.5.0"
dependencies = [
"quote",
"serde",
"toml",
]
[[package]] [[package]]
name = "same-file" name = "same-file"
version = "1.0.6" version = "1.0.6"
+7 -1
View File
@@ -36,10 +36,12 @@ keywords = [
] ]
include = [ include = [
"/src/", "/src/",
"/build.rs",
"/exercises/", "/exercises/",
"/solutions/", "/solutions/",
# A symlink to be able to include `dev/Cargo.toml` although `dev` is excluded. # A symlink to be able to include `dev/Cargo.toml` although `dev` is excluded.
"/dev-Cargo.toml", "/dev-Cargo.toml",
"/info.toml",
"/README.md", "/README.md",
"/LICENSE", "/LICENSE",
] ]
@@ -49,7 +51,6 @@ anyhow = "1"
clap = { version = "4", features = ["derive"] } clap = { version = "4", features = ["derive"] }
crossterm = { version = "0.29", default-features = false, features = ["windows", "events"] } crossterm = { version = "0.29", default-features = false, features = ["windows", "events"] }
notify = "8" notify = "8"
rustlings-macros = { path = "rustlings-macros", version = "=6.5.0" }
serde_json = "1" serde_json = "1"
serde.workspace = true serde.workspace = true
shlex = "2" shlex = "2"
@@ -59,6 +60,11 @@ wait-timeout = "0.2"
[target.'cfg(not(windows))'.dependencies] [target.'cfg(not(windows))'.dependencies]
rustix = { version = "1.0", default-features = false, features = ["std", "stdio", "termios"] } rustix = { version = "1.0", default-features = false, features = ["std", "stdio", "termios"] }
[build-dependencies]
quote = "1"
serde.workspace = true
toml.workspace = true
[dev-dependencies] [dev-dependencies]
tempfile = "3" tempfile = "3"
+93
View File
@@ -1,5 +1,98 @@
use quote::quote;
use serde::Deserialize;
fn main() { fn main() {
// Fix building from source on Windows because it can't handle file links. // Fix building from source on Windows because it can't handle file links.
#[cfg(windows)] #[cfg(windows)]
let _ = std::fs::copy("dev/Cargo.toml", "dev-Cargo.toml"); 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<ExerciseInfo<'a>>,
}
/// 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::<InfoFile>(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();
} }
-24
View File
@@ -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
-77
View File
@@ -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<ExerciseInfo<'a>>,
}
#[proc_macro]
pub fn include_files(_: TokenStream) -> TokenStream {
let info_file = include_str!("../info.toml");
let info = toml::de::from_str::<InfoFile>(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()
}
+1 -1
View File
@@ -7,7 +7,7 @@ use std::{
use crate::info_file::ExerciseInfo; use crate::info_file::ExerciseInfo;
/// Contains all embedded files. /// 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. // Files related to one exercise.
pub struct ExerciseFiles { pub struct ExerciseFiles {