Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "rmkit"
version = "0.0.21"
version = "0.1.0"
edition = "2021"
homepage = "https://github.com/rmk-rs/rmkit"
repository = "https://github.com/rmk-rs/rmkit"
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ Now rmkit can be used to generate RMK project directly from `keyboard.toml` and
rmkit create --keyboard-toml-path keyboard.toml --vial-json-path vial.json
```

A `Cargo.toml` or `memory.x` next to `keyboard.toml` replaces the template's copy as-is — use it to add Cargo features, pin dependencies, or change the flash layout. When you provide `Cargo.toml`, rmkit no longer adjusts the `rmk` features for you.

3. Or, you can create RMK project from project template

```
Expand Down
3 changes: 2 additions & 1 deletion src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ pub struct Args {
pub enum Commands {
/// Create a new RMK project from keyboard.toml and vial.json
Create {
/// Path to keyboard.toml file
/// Path to keyboard.toml file. A Cargo.toml or memory.x next to it
/// replaces the template's copy verbatim.
#[arg(long)]
keyboard_toml_path: Option<String>,

Expand Down
2 changes: 2 additions & 0 deletions src/chip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ pub(crate) fn get_chip_options(split: bool) -> Vec<&'static str> {
"Pi Pico W",
"esp32c3",
"esp32c6",
"esp32h2",
"esp32s3",
]
} else {
Expand All @@ -41,6 +42,7 @@ pub(crate) fn get_chip_options(split: bool) -> Vec<&'static str> {
"esp32c3",
"esp32s3",
"esp32c6",
"esp32h2",
"nice!nano_v2",
"XIAO BLE",
"nice!nano",
Expand Down
74 changes: 71 additions & 3 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,14 +106,42 @@ async fn create_project(
)?;
fs::copy(&vial_json_path, project_info.target_dir.join("vial.json"))?;

// A Cargo.toml / memory.x next to keyboard.toml is the user's, verbatim
let user_dir = Path::new(&keyboard_toml_path)
.parent()
.unwrap_or(Path::new(""));
let cargo_toml_user_owned = copy_user_owned_files(user_dir, &project_info.target_dir)?;

// Post-process
post_process(project_info)?;
post_process(project_info, cargo_toml_user_owned)?;

Ok(())
}

/// Files that replace the template's copy when they sit next to keyboard.toml
const USER_OWNED_FILES: [&str; 2] = ["Cargo.toml", "memory.x"];

/// Copy the user's own project files over the generated project. Returns whether
/// Cargo.toml was among them — the user then owns the feature list too.
fn copy_user_owned_files(user_dir: &Path, target_dir: &Path) -> Result<bool, Box<dyn Error>> {
let mut cargo_toml_user_owned = false;
for name in USER_OWNED_FILES {
let src = user_dir.join(name);
if !src.is_file() {
continue;
}
fs::copy(&src, target_dir.join(name))?;
println!("📄 Using {} (replaces the template's)", src.display());
cargo_toml_user_owned |= name == "Cargo.toml";
}
Ok(cargo_toml_user_owned)
}

/// Postprocessing after generating project
fn post_process(project_info: ProjectInfo) -> Result<(), Box<dyn Error>> {
fn post_process(
project_info: ProjectInfo,
cargo_toml_user_owned: bool,
) -> Result<(), Box<dyn Error>> {
// Replace {{ project_name }} in toml/json files
replace_in_folder(
&project_info,
Expand All @@ -139,6 +167,13 @@ fn post_process(project_info: ProjectInfo) -> Result<(), Box<dyn Error>> {
&project_info.uf2_key,
)?;

// The user's Cargo.toml is used as-is; keyboard.toml/feature mismatches are
// reported by rmk-macro at build time.
if cargo_toml_user_owned {
println!("Skipping rmk feature adjustments: Cargo.toml is user-provided");
return Ok(());
}

// Disable some default features
if !project_info.disabled_default_feature.is_empty() {
let metadata = MetadataCommand::new()
Expand Down Expand Up @@ -279,7 +314,7 @@ async fn init_project(
}

// Post-process
post_process(project_info)?;
post_process(project_info, false)?;

Ok(())
}
Expand Down Expand Up @@ -596,3 +631,36 @@ fn enable_rmk_features(target_dir: &PathBuf, features: Vec<String>) -> Result<()

Ok(())
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn user_owned_files_replace_template_copies() {
let root = std::env::temp_dir().join(format!("rmkit_user_owned_{}", std::process::id()));
let (user_dir, target_dir) = (root.join("user"), root.join("target"));
fs::create_dir_all(&user_dir).unwrap();
fs::create_dir_all(&target_dir).unwrap();
fs::write(target_dir.join("Cargo.toml"), "template").unwrap();
fs::write(target_dir.join("memory.x"), "template").unwrap();
let generated = |name: &str| fs::read_to_string(target_dir.join(name)).unwrap();

// Nothing next to keyboard.toml: template files stay, Cargo.toml is not user-owned
assert!(!copy_user_owned_files(&user_dir, &target_dir).unwrap());
assert_eq!(generated("Cargo.toml"), "template");

// Only memory.x provided: it replaces the template's, Cargo.toml is still rmkit's
fs::write(user_dir.join("memory.x"), "user").unwrap();
assert!(!copy_user_owned_files(&user_dir, &target_dir).unwrap());
assert_eq!(generated("memory.x"), "user");
assert_eq!(generated("Cargo.toml"), "template");

// Cargo.toml provided: replaced verbatim and reported as user-owned
fs::write(user_dir.join("Cargo.toml"), "user").unwrap();
assert!(copy_user_owned_files(&user_dir, &target_dir).unwrap());
assert_eq!(generated("Cargo.toml"), "user");

fs::remove_dir_all(&root).unwrap();
}
}
Loading