From ec9edc294fd5e7e29977fc7b0e6fb953422bc0e2 Mon Sep 17 00:00:00 2001 From: 魏曹先生 <1992414357@qq.com> Date: Wed, 19 Aug 2026 05:54:00 +0800 Subject: chore: reorganize dev tools into dev/ directory and consolidate configs Move CI, dev tools, and configs from root-level scattered locations into a unified `dev/` directory structure. Update all references across build scripts, documentation, and editor configurations. Also consolidate editor config generation into build.rs for automated synchronization of rust-analyzer settings across VS Code and Zed. --- build.rs | 153 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 151 insertions(+), 2 deletions(-) (limited to 'build.rs') diff --git a/build.rs b/build.rs index 951ecf8..17a6d02 100644 --- a/build.rs +++ b/build.rs @@ -1,13 +1,162 @@ -use std::{env::current_dir, fs}; +//! This module initializes the Mingling local repository workspace. +//! +//! It automatically generates necessary files for development. + +use serde_json::Value; +use std::env::current_dir; +use std::fs; fn main() { gen_fake_cargo_toml_in_temp_dir(); + gen_rust_analyzer_config_for_editors(); } +/// Generate a fake Cargo workspace to prevent temporary repositories under `.temp/` +/// from finding the root directory fn gen_fake_cargo_toml_in_temp_dir() { fs::write( - current_dir().unwrap().join(".temp").join("Cargo.toml"), + current_dir().unwrap().join(".temp/Cargo.toml"), "[workspace]", ) .unwrap(); } + +/// Generate Rust Analyzer configuration for editors. +/// +/// Copies the editor configuration at `dev/configs/rust-analyzer.json` to each +/// editor's configuration file. +/// +/// Supported editors: +/// - `Zed Editor` : ".zed/settings.json" +/// - `VS Code`: ".vscode/settings.json" +fn gen_rust_analyzer_config_for_editors() { + // Re-run this build script whenever the source config changes. + println!("cargo:rerun-if-changed=dev/configs/rust-analyzer.json"); + + let root = current_dir().unwrap(); + let source = root.join("dev/configs/rust-analyzer.json"); + let Ok(source_content) = fs::read_to_string(&source) else { + eprintln!( + "warning: `{}` not found, skip editor config generation", + source.display() + ); + return; + }; + + // VS Code uses the same flat `rust-analyzer.*` key format, so copy as-is. + let vscode_path = root.join(".vscode/settings.json"); + if ra_settings::write_if_changed(&vscode_path, &source_content) { + eprintln!("generated {}", vscode_path.display()); + } + + // Zed nests the same settings under `lsp.rust-analyzer.initialization_options`. + let Ok(config) = serde_json::from_str::(&source_content) else { + eprintln!( + "warning: failed to parse `{}` as JSON, skip Zed config", + source.display() + ); + return; + }; + let zed_path = root.join(".zed/settings.json"); + if ra_settings::write_if_changed( + &zed_path, + &ra_settings::to_pretty_json(&ra_settings::to_zed_settings(&config)), + ) { + eprintln!("generated {}", zed_path.display()); + } +} + +/// Internal helpers for generating editor configuration files. +/// +/// This module contains utilities for writing config files only when their +/// contents change, serializing settings in the repo's preferred JSON style, +/// and converting between the flat `rust-analyzer.*` key format used by VS Code +/// and the nested structure expected by the Zed editor. +mod ra_settings { + + use std::fs; + use std::path::Path; + + use serde::Serialize; + use serde_json::{Map, Value}; + + /// Write `content` to `path` only when it differs, so editors do not reload + /// unchanged settings files on every build. Returns whether a write happened. + pub fn write_if_changed(path: &Path, content: &str) -> bool { + if fs::read_to_string(path).is_ok_and(|existing| existing == content) { + return false; + } + if let Some(parent) = path.parent() { + fs::create_dir_all(parent).unwrap(); + } + fs::write(path, content).unwrap(); + true + } + + /// Serialize a value as 4-space-indented JSON (matching the repo's hand-written + /// config style), with a trailing newline. + pub fn to_pretty_json(value: &Value) -> String { + let mut buf = Vec::new(); + let mut ser = serde_json::Serializer::with_formatter( + &mut buf, + serde_json::ser::PrettyFormatter::with_indent(b" "), + ); + value.serialize(&mut ser).unwrap(); + String::from_utf8(buf).unwrap() + "\n" + } + + /// Convert VSCode-style flat `rust-analyzer.*` keys into the nested + /// `lsp.rust-analyzer.initialization_options` structure that Zed expects. + pub fn to_zed_settings(vscode: &Value) -> Value { + let mut init = Map::new(); + let Some(source) = vscode.as_object() else { + return Value::Object(init); + }; + + let unknown: Vec<&String> = source + .keys() + .filter(|k| !k.starts_with("rust-analyzer.")) + .collect(); + if !unknown.is_empty() { + eprintln!( + "warning: ignoring non-rust-analyzer keys when generating Zed config: {unknown:?}" + ); + } + + for (key, value) in source { + if let Some(rest) = key.strip_prefix("rust-analyzer.") { + insert_nested(&mut init, rest, value.clone()); + } + } + + let mut rust_analyzer = Map::new(); + rust_analyzer.insert("initialization_options".into(), Value::Object(init)); + let mut lsp = Map::new(); + lsp.insert("rust-analyzer".into(), Value::Object(rust_analyzer)); + let mut root = Map::new(); + root.insert("lsp".into(), Value::Object(lsp)); + Value::Object(root) + } + + /// Insert `value` at the dotted path `a.b.c` inside `map`, creating any + /// intermediate objects on the way. + fn insert_nested(map: &mut Map, path: &str, value: Value) { + let Some((head, tail)) = path.split_once('.') else { + map.insert(path.to_string(), value); + return; + }; + + let child = map + .entry(head.to_string()) + .or_insert_with(|| Value::Object(Map::new())); + match child { + Value::Object(child) => insert_nested(child, tail, value), + // A scalar already occupies this path; promote it to an object. + _ => { + let mut fresh = Map::new(); + insert_nested(&mut fresh, tail, value); + *child = Value::Object(fresh); + } + } + } +} -- cgit