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