new: ignore vendored rust artifacts in the generated gitignore
This commit is contained in:
+62
-12
@@ -323,15 +323,25 @@ pub const ROOT_GITIGNORE_ENTRIES: [&str; 6] = [
|
|||||||
"target/",
|
"target/",
|
||||||
];
|
];
|
||||||
|
|
||||||
/// Merge the root `.gitignore` entries into `existing` (the current file
|
/// Comment heading a root `.gitignore` freshly created by pkh (the skeleton
|
||||||
/// contents, when there is one): missing entries are appended, an existing
|
/// build-artifact section).
|
||||||
/// file is never overwritten just to duplicate entries. Returns the new
|
pub const ROOT_GITIGNORE_HEADER: &str = "# pkh build artifacts";
|
||||||
/// contents, or `None` when nothing has to be written.
|
|
||||||
pub fn merge_root_gitignore(existing: Option<&str>) -> Option<String> {
|
/// Merge `entries` into the root `.gitignore` contents `existing` (the
|
||||||
|
/// current file contents, when there is one): missing entries are appended,
|
||||||
|
/// an existing file is never overwritten just to duplicate entries. A fresh
|
||||||
|
/// file is headed by the `header` comment when one is given; appending to a
|
||||||
|
/// user file adds bare entries. Returns the new contents, or `None` when
|
||||||
|
/// nothing has to be written.
|
||||||
|
pub fn merge_gitignore_entries(
|
||||||
|
existing: Option<&str>,
|
||||||
|
entries: &[&str],
|
||||||
|
header: Option<&str>,
|
||||||
|
) -> Option<String> {
|
||||||
let have: HashSet<&str> = existing
|
let have: HashSet<&str> = existing
|
||||||
.map(|content| content.lines().map(str::trim).collect())
|
.map(|content| content.lines().map(str::trim).collect())
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let missing: Vec<&str> = ROOT_GITIGNORE_ENTRIES
|
let missing: Vec<&str> = entries
|
||||||
.iter()
|
.iter()
|
||||||
.copied()
|
.copied()
|
||||||
.filter(|entry| !have.contains(entry))
|
.filter(|entry| !have.contains(entry))
|
||||||
@@ -346,8 +356,11 @@ pub fn merge_root_gitignore(existing: Option<&str>) -> Option<String> {
|
|||||||
}
|
}
|
||||||
// Section comment only for a fresh file; appending to a user file adds
|
// Section comment only for a fresh file; appending to a user file adds
|
||||||
// bare entries.
|
// bare entries.
|
||||||
if existing.is_none() {
|
if existing.is_none()
|
||||||
out.push_str("# pkh build artifacts\n");
|
&& let Some(header) = header
|
||||||
|
{
|
||||||
|
out.push_str(header);
|
||||||
|
out.push('\n');
|
||||||
}
|
}
|
||||||
for entry in missing {
|
for entry in missing {
|
||||||
out.push_str(entry);
|
out.push_str(entry);
|
||||||
@@ -746,17 +759,30 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn root_gitignore_merge() {
|
fn gitignore_merge() {
|
||||||
// Fresh file: header + all entries.
|
// Fresh file: header + all entries.
|
||||||
let fresh = merge_root_gitignore(None).unwrap();
|
let fresh =
|
||||||
|
merge_gitignore_entries(None, &ROOT_GITIGNORE_ENTRIES, Some(ROOT_GITIGNORE_HEADER))
|
||||||
|
.unwrap();
|
||||||
assert!(fresh.starts_with("# pkh build artifacts\n"));
|
assert!(fresh.starts_with("# pkh build artifacts\n"));
|
||||||
for entry in ROOT_GITIGNORE_ENTRIES {
|
for entry in ROOT_GITIGNORE_ENTRIES {
|
||||||
assert!(fresh.contains(entry), "{entry} missing");
|
assert!(fresh.contains(entry), "{entry} missing");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A fresh file without a header carries the bare entries.
|
||||||
|
assert_eq!(
|
||||||
|
merge_gitignore_entries(None, &["a/", "b"], None).unwrap(),
|
||||||
|
"a/\nb\n"
|
||||||
|
);
|
||||||
|
|
||||||
// Existing file: only the missing entries are appended, nothing lost.
|
// Existing file: only the missing entries are appended, nothing lost.
|
||||||
let existing = "*.deb\nnode_modules/\n";
|
let existing = "*.deb\nnode_modules/\n";
|
||||||
let merged = merge_root_gitignore(Some(existing)).unwrap();
|
let merged = merge_gitignore_entries(
|
||||||
|
Some(existing),
|
||||||
|
&ROOT_GITIGNORE_ENTRIES,
|
||||||
|
Some(ROOT_GITIGNORE_HEADER),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
assert!(merged.starts_with(existing));
|
assert!(merged.starts_with(existing));
|
||||||
assert!(merged.contains("*.dsc\n"));
|
assert!(merged.contains("*.dsc\n"));
|
||||||
assert!(!merged.contains("*.deb\n*.deb"));
|
assert!(!merged.contains("*.deb\n*.deb"));
|
||||||
@@ -766,7 +792,31 @@ mod tests {
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|e| format!("{e}\n"))
|
.map(|e| format!("{e}\n"))
|
||||||
.collect();
|
.collect();
|
||||||
assert!(merge_root_gitignore(Some(&full)).is_none());
|
assert!(
|
||||||
|
merge_gitignore_entries(
|
||||||
|
Some(&full),
|
||||||
|
&ROOT_GITIGNORE_ENTRIES,
|
||||||
|
Some(ROOT_GITIGNORE_HEADER),
|
||||||
|
)
|
||||||
|
.is_none()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The vendoring entries of the rust template merge into an existing
|
||||||
|
/// user `.gitignore` like any other entry set: appended after the
|
||||||
|
/// user's lines, no header comment, idempotent.
|
||||||
|
#[test]
|
||||||
|
fn gitignore_merge_appends_template_entries() {
|
||||||
|
let entries = ["vendor/", ".cargo/config.toml"];
|
||||||
|
let merged =
|
||||||
|
merge_gitignore_entries(Some("# my project\n*.log\n"), &entries, None).unwrap();
|
||||||
|
assert_eq!(merged, "# my project\n*.log\nvendor/\n.cargo/config.toml\n");
|
||||||
|
|
||||||
|
// Already ignored: nothing to write.
|
||||||
|
assert!(
|
||||||
|
merge_gitignore_entries(Some("vendor/\n.cargo/config.toml\n"), &entries, None)
|
||||||
|
.is_none()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
+59
-8
@@ -3,7 +3,9 @@
|
|||||||
//!
|
//!
|
||||||
//! This module orchestrates a scaffold run: target directory checks, project
|
//! This module orchestrates a scaffold run: target directory checks, project
|
||||||
//! detection, in-memory rendering of every file (all-or-nothing write), the
|
//! detection, in-memory rendering of every file (all-or-nothing write), the
|
||||||
//! template post-write hook (e.g. `cargo vendor`), orig tarball creation
|
//! root `.gitignore` merge (skeleton build artifacts plus the template's own
|
||||||
|
//! entries, e.g. the rust vendored layout), the template post-write hook
|
||||||
|
//! (e.g. `cargo vendor`), orig tarball creation
|
||||||
//! (from the origin the run decided on — see [`origin`] and [`orig`]), git
|
//! (from the origin the run decided on — see [`origin`] and [`orig`]), git
|
||||||
//! initialization, structural verification and the next-steps message.
|
//! initialization, structural verification and the next-steps message.
|
||||||
//! The interactive wizard ([`questions`]) fills a [`options::NewCli`] from
|
//! The interactive wizard ([`questions`]) fills a [`options::NewCli`] from
|
||||||
@@ -35,8 +37,10 @@ use templates::{OutputFile, ScaffoldOutcome};
|
|||||||
/// 1. resolve the template from the registry,
|
/// 1. resolve the template from the registry,
|
||||||
/// 2. check the target directory (refuse an existing `debian/control`),
|
/// 2. check the target directory (refuse an existing `debian/control`),
|
||||||
/// 3. render every file in memory and check for collisions,
|
/// 3. render every file in memory and check for collisions,
|
||||||
/// 4. write the files all-or-nothing (plus the root `.gitignore` in skeleton
|
/// 4. write the files all-or-nothing (plus the root `.gitignore` merge:
|
||||||
/// mode, appending to an existing one),
|
/// the skeleton build-artifact entries in skeleton mode and the
|
||||||
|
/// template's own entries — rust: the vendored layout — in every mode,
|
||||||
|
/// appending to an existing file),
|
||||||
/// 5. run the template post-write hook (e.g. `cargo vendor`, so the vendored
|
/// 5. run the template post-write hook (e.g. `cargo vendor`, so the vendored
|
||||||
/// sources land inside the orig tarball created next),
|
/// sources land inside the orig tarball created next),
|
||||||
/// 6. create the orig tarball (quilt only, refusing overwrites),
|
/// 6. create the orig tarball (quilt only, refusing overwrites),
|
||||||
@@ -171,13 +175,25 @@ fn scaffold_steps(opts: &NewOptions, pb: &ProgressBar) -> Result<ScaffoldOutcome
|
|||||||
pb.set_message("Writing files");
|
pb.set_message("Writing files");
|
||||||
debian::write_files(&target, &files)?;
|
debian::write_files(&target, &files)?;
|
||||||
|
|
||||||
// Root .gitignore: skeleton mode only, never overwriting an existing
|
// Root .gitignore: the skeleton build-artifact entries in skeleton
|
||||||
// file (append the missing entries instead).
|
// mode, plus the template's own entries (rust: the vendored layout) in
|
||||||
if skeleton
|
// every mode — missing ones appended, an existing file never
|
||||||
&& let Some(contents) = debian::merge_root_gitignore(
|
// overwritten.
|
||||||
|
let template_gitignore = template.gitignore_entries(opts);
|
||||||
|
let mut entries: Vec<&str> = Vec::new();
|
||||||
|
let mut header = None;
|
||||||
|
if skeleton {
|
||||||
|
entries.extend(debian::ROOT_GITIGNORE_ENTRIES);
|
||||||
|
header = Some(debian::ROOT_GITIGNORE_HEADER);
|
||||||
|
}
|
||||||
|
entries.extend(template_gitignore.iter().map(String::as_str));
|
||||||
|
if !entries.is_empty()
|
||||||
|
&& let Some(contents) = debian::merge_gitignore_entries(
|
||||||
std::fs::read_to_string(target.join(".gitignore"))
|
std::fs::read_to_string(target.join(".gitignore"))
|
||||||
.ok()
|
.ok()
|
||||||
.as_deref(),
|
.as_deref(),
|
||||||
|
&entries,
|
||||||
|
header,
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
std::fs::write(target.join(".gitignore"), contents)?;
|
std::fs::write(target.join(".gitignore"), contents)?;
|
||||||
@@ -502,7 +518,8 @@ mod tests {
|
|||||||
scaffold_in(&tree, opts(TemplateId::Shell, "runtool", SourceDir::Here)).unwrap();
|
scaffold_in(&tree, opts(TemplateId::Shell, "runtool", SourceDir::Here)).unwrap();
|
||||||
|
|
||||||
// debian/ lands directly in the directory; no skeleton file, no
|
// debian/ lands directly in the directory; no skeleton file, no
|
||||||
// root .gitignore (skeleton mode only), no debian/install (the
|
// root .gitignore (the shell template contributes none and the
|
||||||
|
// artifact entries are skeleton-only), no debian/install (the
|
||||||
// generated one would reference the non-existent skeleton script),
|
// generated one would reference the non-existent skeleton script),
|
||||||
// and the existing script is left alone.
|
// and the existing script is left alone.
|
||||||
assert!(tree.join("debian/control").exists());
|
assert!(tree.join("debian/control").exists());
|
||||||
@@ -662,6 +679,40 @@ mod tests {
|
|||||||
assert!(config.contains("[source.crates-io]"), "{config}");
|
assert!(config.contains("[source.crates-io]"), "{config}");
|
||||||
assert!(config.contains("[net]\noffline = true"), "{config}");
|
assert!(config.contains("[net]\noffline = true"), "{config}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Root .gitignore: the skeleton build-artifact entries plus the
|
||||||
|
// template's vendoring entries (contributed in every mode).
|
||||||
|
let gitignore = std::fs::read_to_string(tree.join(".gitignore")).unwrap();
|
||||||
|
assert!(gitignore.contains("# pkh build artifacts"), "{gitignore}");
|
||||||
|
assert!(gitignore.contains("*.deb"), "{gitignore}");
|
||||||
|
assert!(gitignore.contains("target/"), "{gitignore}");
|
||||||
|
assert!(gitignore.contains("vendor/\n"), "{gitignore}");
|
||||||
|
assert!(gitignore.contains(".cargo/config.toml\n"), "{gitignore}");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The rust template's vendoring entries land in the root `.gitignore`
|
||||||
|
/// in every mode: a Here-mode tree gets them appended to its existing
|
||||||
|
/// file (custom lines kept, no header comment), while the skeleton-only
|
||||||
|
/// build-artifact entries do not appear.
|
||||||
|
#[test]
|
||||||
|
#[serial]
|
||||||
|
fn scaffold_rust_here_merges_vendoring_gitignore_entries() {
|
||||||
|
let dir = tempdir().unwrap();
|
||||||
|
let tree = dir.path().join("packdir");
|
||||||
|
std::fs::create_dir_all(&tree).unwrap();
|
||||||
|
std::fs::write(tree.join(".gitignore"), "# my project\n*.log\n").unwrap();
|
||||||
|
scaffold_in(&tree, opts(TemplateId::Rust, "mytool", SourceDir::Here)).unwrap();
|
||||||
|
|
||||||
|
let gitignore = std::fs::read_to_string(tree.join(".gitignore")).unwrap();
|
||||||
|
assert!(
|
||||||
|
gitignore.starts_with("# my project\n*.log\n"),
|
||||||
|
"{gitignore}"
|
||||||
|
);
|
||||||
|
assert!(gitignore.contains("vendor/\n"), "{gitignore}");
|
||||||
|
assert!(gitignore.contains(".cargo/config.toml\n"), "{gitignore}");
|
||||||
|
// The build-artifact entries stay skeleton-only.
|
||||||
|
assert!(!gitignore.contains("*.deb"), "{gitignore}");
|
||||||
|
assert!(!gitignore.contains("# pkh build artifacts"), "{gitignore}");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// End-to-end vendored rust quilt package: the vendoring hook creates
|
/// End-to-end vendored rust quilt package: the vendoring hook creates
|
||||||
|
|||||||
@@ -137,6 +137,15 @@ pub trait Template: Sync {
|
|||||||
Vec::new()
|
Vec::new()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Root `.gitignore` entries the template contributes in every mode
|
||||||
|
/// (the skeleton-mode build-artifact entries of
|
||||||
|
/// [`crate::new::debian::ROOT_GITIGNORE_ENTRIES`] are separate), merged
|
||||||
|
/// into the root `.gitignore` after the files are written — missing
|
||||||
|
/// ones appended, an existing file never overwritten. Default: none.
|
||||||
|
fn gitignore_entries(&self, _opts: &NewOptions) -> Vec<String> {
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
|
||||||
/// Extra defaults derived from project metadata in `dir` (detect.rs);
|
/// Extra defaults derived from project metadata in `dir` (detect.rs);
|
||||||
/// `None` when the project carries nothing this template can read.
|
/// `None` when the project carries nothing this template can read.
|
||||||
fn probe(&self, _dir: &Path) -> Option<ProbeResult> {
|
fn probe(&self, _dir: &Path) -> Option<ProbeResult> {
|
||||||
@@ -248,6 +257,48 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Only the rust template contributes root `.gitignore` entries of its
|
||||||
|
/// own — exactly the vendoring pair, and unconditionally (its vendoring
|
||||||
|
/// hook runs in every mode); every other template contributes nothing.
|
||||||
|
#[test]
|
||||||
|
fn gitignore_entries_are_rust_only() {
|
||||||
|
let o = NewOptions {
|
||||||
|
name: "mytool".into(),
|
||||||
|
template: TemplateId::Rust,
|
||||||
|
source_dir: SourceDir::Here,
|
||||||
|
upstream_version: "0.1.0".into(),
|
||||||
|
revision: 1,
|
||||||
|
summary: "A tool".into(),
|
||||||
|
long_description: "A tool".into(),
|
||||||
|
homepage: None,
|
||||||
|
license: crate::new::options::License::Mit,
|
||||||
|
command: "mytool".into(),
|
||||||
|
maintainer: ("Jane".into(), "jane@example.com".into()),
|
||||||
|
dist: "ubuntu".into(),
|
||||||
|
series: "resolute".into(),
|
||||||
|
release: false,
|
||||||
|
depends: Vec::new(),
|
||||||
|
source_format: crate::new::options::SourceFormat::Quilt,
|
||||||
|
orig: None,
|
||||||
|
git: false,
|
||||||
|
autopkgtest: false,
|
||||||
|
pkg_config: false,
|
||||||
|
watch: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
for id in TemplateId::all() {
|
||||||
|
let entries = get(id).unwrap().gitignore_entries(&o);
|
||||||
|
if id == TemplateId::Rust {
|
||||||
|
assert_eq!(
|
||||||
|
entries,
|
||||||
|
vec!["vendor/".to_string(), ".cargo/config.toml".to_string()]
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
assert!(entries.is_empty(), "{id} contributes {entries:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The final `debian/rules` of every template must be valid-looking
|
/// The final `debian/rules` of every template must be valid-looking
|
||||||
/// make: `#!/usr/bin/make -f` shebang, exactly one `%:` target whose
|
/// make: `#!/usr/bin/make -f` shebang, exactly one `%:` target whose
|
||||||
/// recipe is the template's dh line, tab-indented recipes only, and no
|
/// recipe is the template's dh line, tab-indented recipes only, and no
|
||||||
|
|||||||
@@ -75,6 +75,17 @@ impl Template for Rust {
|
|||||||
vec!["cargo:native".to_string(), "rustc:native".to_string()]
|
vec!["cargo:native".to_string(), "rustc:native".to_string()]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Root `.gitignore` entries contributed in every mode: the vendoring
|
||||||
|
/// hook runs unconditionally for this template, so `vendor/` and
|
||||||
|
/// `.cargo/config.toml` exist (or will exist) in every scaffolded rust
|
||||||
|
/// tree and must stay out of the repository. `.cargo/config.toml` is
|
||||||
|
/// the subtle one: it points cargo at `vendor/`, so committing it would
|
||||||
|
/// break plain `cargo build` for anyone cloning the repository without
|
||||||
|
/// the vendored sources.
|
||||||
|
fn gitignore_entries(&self, _opts: &NewOptions) -> Vec<String> {
|
||||||
|
vec!["vendor/".to_string(), ".cargo/config.toml".to_string()]
|
||||||
|
}
|
||||||
|
|
||||||
fn architecture(&self, _opts: &NewOptions) -> &'static str {
|
fn architecture(&self, _opts: &NewOptions) -> &'static str {
|
||||||
"any"
|
"any"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user