new: port the rust template bodies to manifests
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
[package]
|
||||
name = "{crate_name}"
|
||||
version = "{upstream_version}"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
@@ -0,0 +1,4 @@
|
||||
// Placeholder for {name}, generated by `pkh new`.
|
||||
fn main() {
|
||||
println!("Hello from {command}!");
|
||||
}
|
||||
@@ -1,8 +1,11 @@
|
||||
## The `rust` template: a vendored Cargo build (see the module docs of
|
||||
## src/new/templates/rust.rs for the vendoring strategy). The logic half —
|
||||
## the cargo vendor post-write hook, the Cargo.toml/src skeletons with
|
||||
## their crate-name sanitizing, and the `{locked}` / `{artifact}` values of
|
||||
## rules.extra.tpl — lives in that module.
|
||||
## src/new/templates/rust.rs for the vendoring strategy). The skeleton
|
||||
## bodies and the vendored-build rules overrides below are static data; the
|
||||
## logic half — the cargo vendor post-write hook, the project probe, and
|
||||
## the `{crate_name}` / `{locked}` / `{artifact}` values of the bodies —
|
||||
## lives in that module (dpkg package names may carry `+`/`.`, which cargo
|
||||
## rejects in crate names, so the skeleton crate name is a derived
|
||||
## placeholder, not the raw `{name}`).
|
||||
##
|
||||
## Schema: see src/new/templates/mod.rs.
|
||||
|
||||
@@ -23,4 +26,8 @@ rules_extra_file: rules.extra.tpl
|
||||
gitignore_entries:
|
||||
- vendor/
|
||||
- .cargo/config.toml
|
||||
files: []
|
||||
files:
|
||||
- path: Cargo.toml
|
||||
template: Cargo.toml.tpl
|
||||
- path: src/main.rs
|
||||
template: main.rs.tpl
|
||||
|
||||
+59
-12
@@ -141,10 +141,11 @@ pub struct ProbeResult {
|
||||
/// The manifest-driven files are the base of both file lists; a hook's
|
||||
/// [`skeleton_files`](TemplateHooks::skeleton_files) /
|
||||
/// [`debian_files`](TemplateHooks::debian_files) entries are merged over
|
||||
/// them by [`merge_files`]: an entry whose path a manifest file already
|
||||
/// uses replaces it, anything else is appended. That is how a body needing
|
||||
/// conditionals stays in Rust without giving up the data half — the hook
|
||||
/// file shadows the manifest body it supersedes.
|
||||
/// them by [`merge_files`]. The convention is *manifest unless it needs
|
||||
/// branching*: a conditional body lives in the hook alone, and the
|
||||
/// registry tests reject a hook file whose path a manifest entry also
|
||||
/// carries — a path with two homes would be one dead body silently
|
||||
/// shadowing the other.
|
||||
pub trait TemplateHooks: Sync {
|
||||
/// Extra defaults derived from the project metadata in `dir`
|
||||
/// (detect.rs pre-fills the wizard answers); `None` when the project
|
||||
@@ -336,10 +337,20 @@ static TEMPLATE_SOURCES: &[TemplateSources] = &[
|
||||
TemplateSources {
|
||||
id: TemplateId::RUST,
|
||||
manifest: include_str!("../../../data/templates/rust/manifest.yml"),
|
||||
tpls: &[(
|
||||
"rules.extra.tpl",
|
||||
include_str!("../../../data/templates/rust/rules.extra.tpl"),
|
||||
)],
|
||||
tpls: &[
|
||||
(
|
||||
"rules.extra.tpl",
|
||||
include_str!("../../../data/templates/rust/rules.extra.tpl"),
|
||||
),
|
||||
(
|
||||
"Cargo.toml.tpl",
|
||||
include_str!("../../../data/templates/rust/Cargo.toml.tpl"),
|
||||
),
|
||||
(
|
||||
"main.rs.tpl",
|
||||
include_str!("../../../data/templates/rust/main.rs.tpl"),
|
||||
),
|
||||
],
|
||||
hooks: Some(&rust::HOOKS),
|
||||
},
|
||||
TemplateSources {
|
||||
@@ -727,10 +738,11 @@ impl Template {
|
||||
}
|
||||
|
||||
/// Merge `overrides` into `files`: an entry whose path is already there
|
||||
/// replaces the earlier entry in place, anything else is appended. Hook
|
||||
/// files thereby both shadow manifest bodies needing logic and add extra
|
||||
/// ones — and the merged list never carries a path twice (the scaffold's
|
||||
/// collision check would reject that as an internal error).
|
||||
/// replaces the earlier entry in place, anything else is appended, and
|
||||
/// the merged list never carries a path twice (the scaffold's collision
|
||||
/// check would reject that as an internal error). The registry tests pin
|
||||
/// the built-in templates to appending only — a hook file shadowing a
|
||||
/// manifest body would leave one of the two dead (the no-drift invariant).
|
||||
fn merge_files(files: &mut Vec<OutputFile>, overrides: Vec<OutputFile>) {
|
||||
for file in overrides {
|
||||
match files.iter().position(|existing| existing.path == file.path) {
|
||||
@@ -1022,6 +1034,41 @@ mod tests {
|
||||
assert_eq!(files[2].contents, "extra");
|
||||
}
|
||||
|
||||
/// The no-drift invariant of the "manifest unless it needs branching"
|
||||
/// convention: no hook renders a file whose path a manifest entry also
|
||||
/// carries. A path with two homes would be one dead body silently
|
||||
/// shadowing the other — a body needing conditionals belongs to the
|
||||
/// hook alone. (Since every template's bodies are manifest data, the
|
||||
/// hooks render no files at all; this test locks that in.)
|
||||
#[test]
|
||||
fn hook_files_never_shadow_manifest_bodies() {
|
||||
let o = opts(TemplateId::EMPTY);
|
||||
for template in all() {
|
||||
let Some(hooks) = template.hooks else {
|
||||
continue;
|
||||
};
|
||||
let manifest_paths: Vec<&str> = template
|
||||
.manifest
|
||||
.files
|
||||
.iter()
|
||||
.map(|file| file.path.as_str())
|
||||
.collect();
|
||||
let hook_files = hooks
|
||||
.skeleton_files(&o)
|
||||
.into_iter()
|
||||
.chain(hooks.debian_files(&o));
|
||||
for file in hook_files {
|
||||
assert!(
|
||||
!manifest_paths.contains(&file.path.as_str()),
|
||||
"{}: hook body '{}' is also a manifest path — a body has \
|
||||
one home (manifest unless it needs branching)",
|
||||
template.id(),
|
||||
file.path
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The empty template is pure data: a stub `README` skeleton, no extra
|
||||
/// debian/ files (the metapackage `Depends` payload travels in the
|
||||
/// options, not in the template).
|
||||
|
||||
+49
-48
@@ -10,16 +10,17 @@
|
||||
//! [`ScaffoldOutcome::vendoring_failed`] to the flow — the package will not
|
||||
//! build until the user vendors manually.
|
||||
//!
|
||||
//! The metadata is manifest data (`data/templates/rust/manifest.yml`),
|
||||
//! including the `debian/rules` overrides of `rules.extra.tpl`; the logic
|
||||
//! half here fills that template's `{locked}`/`{artifact}` placeholders,
|
||||
//! renders the crate-name-sanitized skeleton and runs the vendoring.
|
||||
//! The metadata and bodies are manifest data
|
||||
//! (`data/templates/rust/manifest.yml`: the skeleton `Cargo.toml` /
|
||||
//! `src/main.rs` and the `debian/rules` vendored-build overrides), rendered
|
||||
//! through the placeholders this module supplies; the logic half here is
|
||||
//! the project probe and the vendoring hook.
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use serde_json::Value;
|
||||
|
||||
use super::{OutputFile, ProbeResult, ScaffoldOutcome, TemplateHooks, find_on_path, source_dir_of};
|
||||
use super::{ProbeResult, ScaffoldOutcome, TemplateHooks, find_on_path, source_dir_of};
|
||||
use crate::new::options::{NewOptions, SourceDir};
|
||||
|
||||
/// The logic half of the rust template.
|
||||
@@ -39,42 +40,14 @@ fn crate_name(opts: &NewOptions) -> String {
|
||||
}
|
||||
|
||||
impl TemplateHooks for Hooks {
|
||||
/// A zero-dependency `Cargo.toml` and the matching `src/main.rs`.
|
||||
fn skeleton_files(&self, opts: &NewOptions) -> Vec<OutputFile> {
|
||||
vec![
|
||||
OutputFile::new(
|
||||
"Cargo.toml",
|
||||
format!(
|
||||
"[package]\n\
|
||||
name = \"{name}\"\n\
|
||||
version = \"{version}\"\n\
|
||||
edition = \"2021\"\n\
|
||||
\n\
|
||||
[dependencies]\n",
|
||||
name = crate_name(opts),
|
||||
version = opts.upstream_version,
|
||||
),
|
||||
),
|
||||
OutputFile::new(
|
||||
"src/main.rs",
|
||||
format!(
|
||||
"// Placeholder for {name}, generated by `pkh new`.\n\
|
||||
fn main() {{\n\
|
||||
\tprintln!(\"Hello from {command}!\");\n\
|
||||
}}\n",
|
||||
name = opts.name,
|
||||
command = opts.command,
|
||||
),
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
/// The `{locked}`/`{artifact}` values of `rules.extra.tpl`: `--locked`
|
||||
/// is used only when the packaged tree already carries a `Cargo.lock`
|
||||
/// (fresh skeletons have none yet — the vendoring hook patches the flag
|
||||
/// in once `cargo vendor` created it); the built artifact of a skeleton
|
||||
/// is named after its crate (a sanitized package name), an existing
|
||||
/// project's under the (probed or answered) command.
|
||||
/// The placeholder values of the manifest bodies: `{crate_name}` names
|
||||
/// the skeleton crate (a sanitized package name — see [`crate_name`]);
|
||||
/// `{locked}` is ` --locked` only when the packaged tree already
|
||||
/// carries a `Cargo.lock` (fresh skeletons have none yet — the
|
||||
/// vendoring hook patches the flag in once `cargo vendor` created it);
|
||||
/// `{artifact}` is the built binary of the rules install override —
|
||||
/// a skeleton's is named after its crate, an existing project's under
|
||||
/// the (probed or answered) command.
|
||||
fn context(&self, opts: &NewOptions) -> Vec<(String, String)> {
|
||||
let locked = if lockfile_present(opts) {
|
||||
" --locked"
|
||||
@@ -86,6 +59,7 @@ impl TemplateHooks for Hooks {
|
||||
_ => opts.command.clone(),
|
||||
};
|
||||
vec![
|
||||
("crate_name".to_string(), crate_name(opts)),
|
||||
("locked".to_string(), locked.to_string()),
|
||||
("artifact".to_string(), artifact),
|
||||
]
|
||||
@@ -492,14 +466,33 @@ mod tests {
|
||||
assert_eq!(template.rules_dh_line(), "dh $@");
|
||||
assert!(template.debian(&o).is_empty());
|
||||
|
||||
// Skeleton: Cargo.toml + src/main.rs.
|
||||
// Skeleton (manifest data): Cargo.toml + src/main.rs, byte-exact.
|
||||
let skeleton = template.skeleton(&o);
|
||||
assert!(
|
||||
skeleton
|
||||
.iter()
|
||||
.any(|f| f.path == "Cargo.toml" && f.contents.contains("name = \"mytool\""))
|
||||
assert_eq!(skeleton.len(), 2);
|
||||
let cargo_toml = skeleton
|
||||
.iter()
|
||||
.find(|f| f.path == "Cargo.toml")
|
||||
.expect("Cargo.toml skeleton");
|
||||
assert_eq!(
|
||||
cargo_toml.contents,
|
||||
"[package]\n\
|
||||
name = \"mytool\"\n\
|
||||
version = \"0.1.0\"\n\
|
||||
edition = \"2021\"\n\
|
||||
\n\
|
||||
[dependencies]\n"
|
||||
);
|
||||
let main_rs = skeleton
|
||||
.iter()
|
||||
.find(|f| f.path == "src/main.rs")
|
||||
.expect("src/main.rs skeleton");
|
||||
assert_eq!(
|
||||
main_rs.contents,
|
||||
"// Placeholder for mytool, generated by `pkh new`.\n\
|
||||
fn main() {\n\
|
||||
\tprintln!(\"Hello from mytool!\");\n\
|
||||
}\n"
|
||||
);
|
||||
assert!(skeleton.iter().any(|f| f.path == "src/main.rs"));
|
||||
|
||||
// Fresh skeleton: no Cargo.lock, so no --locked flag anywhere.
|
||||
let extra = template.rules_extra(&o);
|
||||
@@ -553,7 +546,15 @@ mod tests {
|
||||
.iter()
|
||||
.find(|f| f.path == "Cargo.toml")
|
||||
.expect("Cargo.toml skeleton");
|
||||
assert!(cargo_toml.contents.contains("name = \"my_tool_\""));
|
||||
assert_eq!(
|
||||
cargo_toml.contents,
|
||||
"[package]\n\
|
||||
name = \"my_tool_\"\n\
|
||||
version = \"0.1.0\"\n\
|
||||
edition = \"2021\"\n\
|
||||
\n\
|
||||
[dependencies]\n"
|
||||
);
|
||||
|
||||
let extra = template.rules_extra(&o);
|
||||
assert!(
|
||||
|
||||
Reference in New Issue
Block a user