mirror of
https://github.com/rust-lang/cargo
synced 2024-10-05 23:39:47 +00:00
Auto merge of #12399 - epage:normalize, r=weihanglo
fix(package): Recognize and normalize `cargo.toml` ### What does this PR try to resolve? This solution is a blend of conservative and easy - Normalizes `cargo.toml` to `Cargo.toml` on publish - Ensuring we publish the `prepare_for_publish` version and include `Cargo.toml.orig` - Avoids dealing with trying to push existing users to `Cargo.toml` - All other cases of `Cargo.toml` are warnings - We could either normalize or turn this into an error in the future - When helping users with case previously, we've only handle the `cargo.toml` case - We already should have a fallback in case a manifest isn't detected - I didn't want to put in too much effort to make the code more complicated to handle this As a side effect, if a Linux user has `cargo.toml` and `Cargo.toml`, we'll only put one of them in the `.crate` file. We can extend this out to also include a warning for portability for case insensitive filesystems but I left that for after rust-lang/cargo#12235. ### How should we test and review this PR? A PR at a time will show how the behavior changed as the source was edited This does add a direct dependency on `unicase` to help keep case-insensitive comparisons easy / clear and to avoid riskier areas for bugs like writing an appropriate `Hash` implementation. `unicase` is an existing transitive dependency of cargo. ### Additional information Fixes #12384 [Discussion on Zulip](https://rust-lang.zulipchat.com/#narrow/stream/246057-t-cargo/topic/.60cargo.2Etoml.60.20on.20case.20insensitive.20filesystems)
This commit is contained in:
commit
c86366091b
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -335,6 +335,7 @@ dependencies = [
|
|||
"time",
|
||||
"toml",
|
||||
"toml_edit",
|
||||
"unicase",
|
||||
"unicode-width",
|
||||
"unicode-xid",
|
||||
"url",
|
||||
|
|
|
@ -94,6 +94,7 @@ thiserror = "1.0.40"
|
|||
time = { version = "0.3", features = ["parsing", "formatting", "serde"] }
|
||||
toml = "0.7.0"
|
||||
toml_edit = "0.19.0"
|
||||
unicase = "2.6.0"
|
||||
unicode-width = "0.1.5"
|
||||
unicode-xid = "0.2.0"
|
||||
url = "2.2.2"
|
||||
|
@ -177,6 +178,7 @@ termcolor.workspace = true
|
|||
time.workspace = true
|
||||
toml.workspace = true
|
||||
toml_edit.workspace = true
|
||||
unicase.workspace = true
|
||||
unicode-width.workspace = true
|
||||
unicode-xid.workspace = true
|
||||
url.workspace = true
|
||||
|
|
|
@ -25,6 +25,7 @@ use flate2::{Compression, GzBuilder};
|
|||
use log::debug;
|
||||
use serde::Serialize;
|
||||
use tar::{Archive, Builder, EntryType, Header, HeaderMode};
|
||||
use unicase::Ascii as UncasedAscii;
|
||||
|
||||
pub struct PackageOpts<'cfg> {
|
||||
pub config: &'cfg Config,
|
||||
|
@ -227,58 +228,84 @@ fn build_ar_list(
|
|||
src_files: Vec<PathBuf>,
|
||||
vcs_info: Option<VcsInfo>,
|
||||
) -> CargoResult<Vec<ArchiveFile>> {
|
||||
let mut result = Vec::new();
|
||||
let mut result = HashMap::new();
|
||||
let root = pkg.root();
|
||||
for src_file in src_files {
|
||||
let rel_path = src_file.strip_prefix(&root)?.to_path_buf();
|
||||
check_filename(&rel_path, &mut ws.config().shell())?;
|
||||
let rel_str = rel_path
|
||||
.to_str()
|
||||
.ok_or_else(|| {
|
||||
anyhow::format_err!("non-utf8 path in source directory: {}", rel_path.display())
|
||||
})?
|
||||
.to_string();
|
||||
|
||||
for src_file in &src_files {
|
||||
let rel_path = src_file.strip_prefix(&root)?;
|
||||
check_filename(rel_path, &mut ws.config().shell())?;
|
||||
let rel_str = rel_path.to_str().ok_or_else(|| {
|
||||
anyhow::format_err!("non-utf8 path in source directory: {}", rel_path.display())
|
||||
})?;
|
||||
match rel_str.as_ref() {
|
||||
"Cargo.toml" => {
|
||||
result.push(ArchiveFile {
|
||||
rel_path: PathBuf::from(ORIGINAL_MANIFEST_FILE),
|
||||
rel_str: ORIGINAL_MANIFEST_FILE.to_string(),
|
||||
contents: FileContents::OnDisk(src_file),
|
||||
});
|
||||
result.push(ArchiveFile {
|
||||
rel_path,
|
||||
rel_str,
|
||||
contents: FileContents::Generated(GeneratedFile::Manifest),
|
||||
});
|
||||
}
|
||||
"Cargo.lock" => continue,
|
||||
VCS_INFO_FILE | ORIGINAL_MANIFEST_FILE => anyhow::bail!(
|
||||
"invalid inclusion of reserved file name {} in package source",
|
||||
rel_str
|
||||
),
|
||||
_ => {
|
||||
result.push(ArchiveFile {
|
||||
rel_path,
|
||||
rel_str,
|
||||
contents: FileContents::OnDisk(src_file),
|
||||
});
|
||||
result
|
||||
.entry(UncasedAscii::new(rel_str))
|
||||
.or_insert_with(Vec::new)
|
||||
.push(ArchiveFile {
|
||||
rel_path: rel_path.to_owned(),
|
||||
rel_str: rel_str.to_owned(),
|
||||
contents: FileContents::OnDisk(src_file.clone()),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure we normalize for case insensitive filesystems (like on Windows) by removing the
|
||||
// existing entry, regardless of case, and adding in with the correct case
|
||||
if result.remove(&UncasedAscii::new("Cargo.toml")).is_some() {
|
||||
result
|
||||
.entry(UncasedAscii::new(ORIGINAL_MANIFEST_FILE))
|
||||
.or_insert_with(Vec::new)
|
||||
.push(ArchiveFile {
|
||||
rel_path: PathBuf::from(ORIGINAL_MANIFEST_FILE),
|
||||
rel_str: ORIGINAL_MANIFEST_FILE.to_string(),
|
||||
contents: FileContents::OnDisk(pkg.manifest_path().to_owned()),
|
||||
});
|
||||
result
|
||||
.entry(UncasedAscii::new("Cargo.toml"))
|
||||
.or_insert_with(Vec::new)
|
||||
.push(ArchiveFile {
|
||||
rel_path: PathBuf::from("Cargo.toml"),
|
||||
rel_str: "Cargo.toml".to_string(),
|
||||
contents: FileContents::Generated(GeneratedFile::Manifest),
|
||||
});
|
||||
} else {
|
||||
ws.config().shell().warn(&format!(
|
||||
"no `Cargo.toml` file found when packaging `{}` (note the case of the file name).",
|
||||
pkg.name()
|
||||
))?;
|
||||
}
|
||||
|
||||
if pkg.include_lockfile() {
|
||||
result.push(ArchiveFile {
|
||||
rel_path: PathBuf::from("Cargo.lock"),
|
||||
rel_str: "Cargo.lock".to_string(),
|
||||
contents: FileContents::Generated(GeneratedFile::Lockfile),
|
||||
});
|
||||
let rel_str = "Cargo.lock";
|
||||
result
|
||||
.entry(UncasedAscii::new(rel_str))
|
||||
.or_insert_with(Vec::new)
|
||||
.push(ArchiveFile {
|
||||
rel_path: PathBuf::from(rel_str),
|
||||
rel_str: rel_str.to_string(),
|
||||
contents: FileContents::Generated(GeneratedFile::Lockfile),
|
||||
});
|
||||
}
|
||||
if let Some(vcs_info) = vcs_info {
|
||||
result.push(ArchiveFile {
|
||||
rel_path: PathBuf::from(VCS_INFO_FILE),
|
||||
rel_str: VCS_INFO_FILE.to_string(),
|
||||
contents: FileContents::Generated(GeneratedFile::VcsInfo(vcs_info)),
|
||||
});
|
||||
let rel_str = VCS_INFO_FILE;
|
||||
result
|
||||
.entry(UncasedAscii::new(rel_str))
|
||||
.or_insert_with(Vec::new)
|
||||
.push(ArchiveFile {
|
||||
rel_path: PathBuf::from(rel_str),
|
||||
rel_str: rel_str.to_string(),
|
||||
contents: FileContents::Generated(GeneratedFile::VcsInfo(vcs_info)),
|
||||
});
|
||||
}
|
||||
|
||||
let mut result = result.into_values().flatten().collect();
|
||||
if let Some(license_file) = &pkg.manifest().metadata().license_file {
|
||||
let license_path = Path::new(license_file);
|
||||
let abs_file_path = paths::normalize_path(&pkg.root().join(license_path));
|
||||
|
|
|
@ -2983,3 +2983,115 @@ src/main.rs.bak
|
|||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[cargo_test]
|
||||
#[cfg(windows)] // windows is the platform that is most consistently configured for case insensitive filesystems
|
||||
fn normalize_case() {
|
||||
let p = project()
|
||||
.file("src/main.rs", r#"fn main() { println!("hello"); }"#)
|
||||
.file("src/bar.txt", "") // should be ignored when packaging
|
||||
.build();
|
||||
// Workaround `project()` making a `Cargo.toml` on our behalf
|
||||
std::fs::remove_file(p.root().join("Cargo.toml")).unwrap();
|
||||
std::fs::write(
|
||||
p.root().join("cargo.toml"),
|
||||
r#"
|
||||
[package]
|
||||
name = "foo"
|
||||
version = "0.0.1"
|
||||
authors = []
|
||||
exclude = ["*.txt"]
|
||||
license = "MIT"
|
||||
description = "foo"
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
p.cargo("package")
|
||||
.with_stderr(
|
||||
"\
|
||||
[WARNING] manifest has no documentation[..]
|
||||
See [..]
|
||||
[PACKAGING] foo v0.0.1 ([CWD])
|
||||
[VERIFYING] foo v0.0.1 ([CWD])
|
||||
[COMPILING] foo v0.0.1 ([CWD][..])
|
||||
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
|
||||
[PACKAGED] 4 files, [..] ([..] compressed)
|
||||
",
|
||||
)
|
||||
.run();
|
||||
assert!(p.root().join("target/package/foo-0.0.1.crate").is_file());
|
||||
p.cargo("package -l")
|
||||
.with_stdout(
|
||||
"\
|
||||
Cargo.lock
|
||||
Cargo.toml
|
||||
Cargo.toml.orig
|
||||
src/main.rs
|
||||
",
|
||||
)
|
||||
.run();
|
||||
p.cargo("package").with_stdout("").run();
|
||||
|
||||
let f = File::open(&p.root().join("target/package/foo-0.0.1.crate")).unwrap();
|
||||
validate_crate_contents(
|
||||
f,
|
||||
"foo-0.0.1.crate",
|
||||
&["Cargo.lock", "Cargo.toml", "Cargo.toml.orig", "src/main.rs"],
|
||||
&[],
|
||||
);
|
||||
}
|
||||
|
||||
#[cargo_test]
|
||||
#[cfg(target_os = "linux")] // linux is generally configured to be case sensitive
|
||||
fn mixed_case() {
|
||||
let manifest = r#"
|
||||
[package]
|
||||
name = "foo"
|
||||
version = "0.0.1"
|
||||
authors = []
|
||||
exclude = ["*.txt"]
|
||||
license = "MIT"
|
||||
description = "foo"
|
||||
"#;
|
||||
let p = project()
|
||||
.file("Cargo.toml", manifest)
|
||||
.file("cargo.toml", manifest)
|
||||
.file("src/main.rs", r#"fn main() { println!("hello"); }"#)
|
||||
.file("src/bar.txt", "") // should be ignored when packaging
|
||||
.build();
|
||||
|
||||
p.cargo("package")
|
||||
.with_stderr(
|
||||
"\
|
||||
[WARNING] manifest has no documentation[..]
|
||||
See [..]
|
||||
[PACKAGING] foo v0.0.1 ([CWD])
|
||||
[VERIFYING] foo v0.0.1 ([CWD])
|
||||
[COMPILING] foo v0.0.1 ([CWD][..])
|
||||
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
|
||||
[PACKAGED] 4 files, [..] ([..] compressed)
|
||||
",
|
||||
)
|
||||
.run();
|
||||
assert!(p.root().join("target/package/foo-0.0.1.crate").is_file());
|
||||
p.cargo("package -l")
|
||||
.with_stdout(
|
||||
"\
|
||||
Cargo.lock
|
||||
Cargo.toml
|
||||
Cargo.toml.orig
|
||||
src/main.rs
|
||||
",
|
||||
)
|
||||
.run();
|
||||
p.cargo("package").with_stdout("").run();
|
||||
|
||||
let f = File::open(&p.root().join("target/package/foo-0.0.1.crate")).unwrap();
|
||||
validate_crate_contents(
|
||||
f,
|
||||
"foo-0.0.1.crate",
|
||||
&["Cargo.lock", "Cargo.toml", "Cargo.toml.orig", "src/main.rs"],
|
||||
&[],
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue