From 95008f91e50a05be1f0eecd06643506197cd29b8 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Mon, 2 Mar 2020 14:26:21 -0800 Subject: [PATCH 1/3] Try to better handle restricted crate names. --- Cargo.toml | 1 + src/cargo/core/compiler/layout.rs | 6 -- src/cargo/core/compiler/mod.rs | 1 - src/cargo/ops/cargo_new.rs | 97 ++++++++++++++++++--------- src/cargo/util/mod.rs | 18 +---- src/cargo/util/restricted_names.rs | 83 +++++++++++++++++++++++ src/cargo/util/toml/targets.rs | 5 +- tests/testsuite/alt_registry.rs | 4 +- tests/testsuite/build.rs | 2 +- tests/testsuite/init.rs | 7 +- tests/testsuite/new.rs | 103 +++++++++++++++++++++++++---- 11 files changed, 251 insertions(+), 76 deletions(-) create mode 100644 src/cargo/util/restricted_names.rs diff --git a/Cargo.toml b/Cargo.toml index 3eb42a2c461..2a26ef6441b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,6 +64,7 @@ tar = { version = "0.4.26", default-features = false } tempfile = "3.0" termcolor = "1.0" toml = "0.5.3" +unicode-xid = "0.2.0" url = "2.0" walkdir = "2.2" clap = "2.31.2" diff --git a/src/cargo/core/compiler/layout.rs b/src/cargo/core/compiler/layout.rs index 2f248fcde44..a896d5be38b 100644 --- a/src/cargo/core/compiler/layout.rs +++ b/src/cargo/core/compiler/layout.rs @@ -129,12 +129,6 @@ pub struct Layout { _lock: FileLock, } -pub fn is_bad_artifact_name(name: &str) -> bool { - ["deps", "examples", "build", "incremental"] - .iter() - .any(|&reserved| reserved == name) -} - impl Layout { /// Calculate the paths for build output, lock the build directory, and return as a Layout. /// diff --git a/src/cargo/core/compiler/mod.rs b/src/cargo/core/compiler/mod.rs index 8c678aa0c7e..ae5800289cd 100644 --- a/src/cargo/core/compiler/mod.rs +++ b/src/cargo/core/compiler/mod.rs @@ -37,7 +37,6 @@ pub use self::custom_build::{BuildOutput, BuildScriptOutputs, BuildScripts}; pub use self::job::Freshness; use self::job::{Job, Work}; use self::job_queue::{JobQueue, JobState}; -pub use self::layout::is_bad_artifact_name; use self::output_depinfo::output_depinfo; use self::unit_dependencies::UnitDep; pub use crate::core::compiler::unit::{Unit, UnitInterner}; diff --git a/src/cargo/ops/cargo_new.rs b/src/cargo/ops/cargo_new.rs index 97e657c506b..438f576875b 100644 --- a/src/cargo/ops/cargo_new.rs +++ b/src/cargo/ops/cargo_new.rs @@ -1,7 +1,7 @@ -use crate::core::{compiler, Workspace}; +use crate::core::{Shell, Workspace}; use crate::util::errors::{CargoResult, CargoResultExt}; use crate::util::{existing_vcs_repo, FossilRepo, GitRepo, HgRepo, PijulRepo}; -use crate::util::{paths, validate_package_name, Config}; +use crate::util::{paths, restricted_names, Config}; use git2::Config as GitConfig; use git2::Repository as GitRepository; use serde::de; @@ -155,41 +155,71 @@ fn get_name<'a>(path: &'a Path, opts: &'a NewOptions) -> CargoResult<&'a str> { }) } -fn check_name(name: &str, opts: &NewOptions) -> CargoResult<()> { - // If --name is already used to override, no point in suggesting it - // again as a fix. - let name_help = match opts.name { - Some(_) => "", - None => "\nuse --name to override crate name", - }; +fn check_name(name: &str, name_help: &str, has_bin: bool, shell: &mut Shell) -> CargoResult<()> { + restricted_names::validate_package_name(name, "crate name", name_help)?; - // Ban keywords + test list found at - // https://doc.rust-lang.org/reference/keywords.html - let blacklist = [ - "abstract", "alignof", "as", "become", "box", "break", "const", "continue", "crate", "do", - "else", "enum", "extern", "false", "final", "fn", "for", "if", "impl", "in", "let", "loop", - "macro", "match", "mod", "move", "mut", "offsetof", "override", "priv", "proc", "pub", - "pure", "ref", "return", "self", "sizeof", "static", "struct", "super", "test", "trait", - "true", "type", "typeof", "unsafe", "unsized", "use", "virtual", "where", "while", "yield", - ]; - if blacklist.contains(&name) || (opts.kind.is_bin() && compiler::is_bad_artifact_name(name)) { + if restricted_names::is_keyword(name) { anyhow::bail!( - "The name `{}` cannot be used as a crate name{}", + "the name `{}` cannot be used as a crate name, it is a Rust keyword{}", name, name_help - ) + ); } - - if let Some(ref c) = name.chars().next() { - if c.is_digit(10) { + if restricted_names::is_conflicting_artifact_name(name) { + if has_bin { anyhow::bail!( - "Package names starting with a digit cannot be used as a crate name{}", + "the name `{}` cannot be used as a crate name, \ + it conflicts with cargo's build directory names{}", + name, name_help - ) + ); + } else { + shell.warn(format!( + "the name `{}` will not support binary \ + executables with that name, \ + it conflicts with cargo's build directory names", + name + ))?; } } + if name == "test" { + anyhow::bail!( + "the name `test` cannot be used as a crate name, \ + it conflicts with Rust's built-in test library{}", + name_help + ); + } + if ["core", "std", "alloc", "proc_macro", "proc-macro"].contains(&name) { + shell.warn(format!( + "the name `{}` is part of Rust's standard library\n\ + It is recommended to use a different name to avoid problems.", + name + ))?; + } + if restricted_names::is_windows_reserved(name) { + if cfg!(windows) { + anyhow::bail!( + "cannot use name `{}`, it is a reserved Windows filename{}", + name, + name_help + ); + } else { + shell.warn(format!( + "the name `{}` is a reserved Windows filename\n\ + This package will not work on Windows platforms.", + name + ))?; + } + } + if restricted_names::is_non_ascii_name(name) { + shell.warn(format!( + "the name `{}` contains non-ASCII characters\n\ + Support for non-ASCII crate names is experimental and only valid \ + on the nightly toolchain.", + name + ))?; + } - validate_package_name(name, "crate name", name_help)?; Ok(()) } @@ -337,7 +367,7 @@ pub fn new(opts: &NewOptions, config: &Config) -> CargoResult<()> { } let name = get_name(path, opts)?; - check_name(name, opts)?; + check_name(name, "", opts.kind.is_bin(), &mut config.shell())?; let mkopts = MkOptions { version_control: opts.version_control, @@ -372,7 +402,6 @@ pub fn init(opts: &NewOptions, config: &Config) -> CargoResult<()> { } let name = get_name(path, opts)?; - check_name(name, opts)?; let mut src_paths_types = vec![]; @@ -385,6 +414,14 @@ pub fn init(opts: &NewOptions, config: &Config) -> CargoResult<()> { // Maybe when doing `cargo init --bin` inside a library package stub, // user may mean "initialize for library, but also add binary target" } + let has_bin = src_paths_types.iter().any(|x| x.bin); + // If --name is already used to override, no point in suggesting it + // again as a fix. + let name_help = match opts.name { + Some(_) => "", + None => "\nuse --name to override crate name", + }; + check_name(name, name_help, has_bin, &mut config.shell())?; let mut version_control = opts.version_control; @@ -426,7 +463,7 @@ pub fn init(opts: &NewOptions, config: &Config) -> CargoResult<()> { version_control, path, name, - bin: src_paths_types.iter().any(|x| x.bin), + bin: has_bin, source_files: src_paths_types, edition: opts.edition.as_ref().map(|s| &**s), registry: opts.registry.as_ref().map(|s| &**s), diff --git a/src/cargo/util/mod.rs b/src/cargo/util/mod.rs index 122bb65f98b..b3605052321 100644 --- a/src/cargo/util/mod.rs +++ b/src/cargo/util/mod.rs @@ -19,6 +19,7 @@ pub use self::paths::{dylib_path_envvar, normalize_path}; pub use self::process_builder::{process, ProcessBuilder}; pub use self::progress::{Progress, ProgressStyle}; pub use self::read2::read2; +pub use self::restricted_names::validate_package_name; pub use self::rustc::Rustc; pub use self::sha256::Sha256; pub use self::to_semver::ToSemver; @@ -51,6 +52,7 @@ pub mod process_builder; pub mod profile; mod progress; mod read2; +pub mod restricted_names; pub mod rustc; mod sha256; pub mod to_semver; @@ -68,22 +70,6 @@ pub fn elapsed(duration: Duration) -> String { } } -/// Check the base requirements for a package name. -/// -/// This can be used for other things than package names, to enforce some -/// level of sanity. Note that package names have other restrictions -/// elsewhere. `cargo new` has a few restrictions, such as checking for -/// reserved names. crates.io has even more restrictions. -pub fn validate_package_name(name: &str, what: &str, help: &str) -> CargoResult<()> { - if let Some(ch) = name - .chars() - .find(|ch| !ch.is_alphanumeric() && *ch != '_' && *ch != '-') - { - anyhow::bail!("Invalid character `{}` in {}: `{}`{}", ch, what, name, help); - } - Ok(()) -} - /// Whether or not this running in a Continuous Integration environment. pub fn is_ci() -> bool { std::env::var("CI").is_ok() || std::env::var("TF_BUILD").is_ok() diff --git a/src/cargo/util/restricted_names.rs b/src/cargo/util/restricted_names.rs new file mode 100644 index 00000000000..ad9df7dc122 --- /dev/null +++ b/src/cargo/util/restricted_names.rs @@ -0,0 +1,83 @@ +//! Helpers for validating and checking names like package and crate names. + +use crate::util::CargoResult; +use anyhow::bail; + +/// Returns `true` if the name contains non-ASCII characters. +pub fn is_non_ascii_name(name: &str) -> bool { + name.chars().any(|ch| ch > '\x7f') +} + +/// A Rust keyword. +pub fn is_keyword(name: &str) -> bool { + // See https://doc.rust-lang.org/reference/keywords.html + [ + "Self", "abstract", "as", "async", "await", "become", "box", "break", "const", "continue", + "crate", "do", "dyn", "else", "enum", "extern", "false", "final", "fn", "for", "if", + "impl", "in", "let", "loop", "macro", "match", "mod", "move", "mut", "override", "priv", + "pub", "ref", "return", "self", "static", "struct", "super", "trait", "true", "try", + "type", "typeof", "unsafe", "unsized", "use", "virtual", "where", "while", "yield", + ] + .contains(&name) +} + +/// These names cannot be used on Windows, even with an extension. +pub fn is_windows_reserved(name: &str) -> bool { + [ + "con", "prn", "aux", "nul", "com1", "com2", "com3", "com4", "com5", "com6", "com7", "com8", + "com9", "lpt1", "lpt2", "lpt3", "lpt4", "lpt5", "lpt6", "lpt7", "lpt8", "lpt9", + ] + .contains(&name.to_ascii_lowercase().as_str()) +} + +/// An artifact with this name will conflict with one of Cargo's build directories. +pub fn is_conflicting_artifact_name(name: &str) -> bool { + ["deps", "examples", "build", "incremental"].contains(&name) +} + +/// Check the base requirements for a package name. +/// +/// This can be used for other things than package names, to enforce some +/// level of sanity. Note that package names have other restrictions +/// elsewhere. `cargo new` has a few restrictions, such as checking for +/// reserved names. crates.io has even more restrictions. +pub fn validate_package_name(name: &str, what: &str, help: &str) -> CargoResult<()> { + let mut chars = name.chars(); + if let Some(ch) = chars.next() { + if ch.is_digit(10) { + // A specific error for a potentially common case. + bail!( + "the name `{}` cannot be used as a {}, \ + the name cannot start with a digit{}", + name, + what, + help + ); + } + if !(unicode_xid::UnicodeXID::is_xid_start(ch) || ch == '_') { + bail!( + "invalid character `{}` in {}: `{}`, \ + the first character must be a Unicode XID start character \ + (most letters or `_`){}", + ch, + what, + name, + help + ); + } + } + for ch in chars { + if !(unicode_xid::UnicodeXID::is_xid_continue(ch) || ch == '-') { + bail!( + "invalid character `{}` in {}: `{}`, \ + characters must be Unicode XID characters \ + (numbers, `-`, `_`, or most letters){}", + ch, + what, + name, + help + ); + } + } + Ok(()) +} diff --git a/src/cargo/util/toml/targets.rs b/src/cargo/util/toml/targets.rs index fe83c3d424d..7431316b1eb 100644 --- a/src/cargo/util/toml/targets.rs +++ b/src/cargo/util/toml/targets.rs @@ -18,8 +18,9 @@ use super::{ LibKind, PathValue, StringOrBool, StringOrVec, TomlBenchTarget, TomlBinTarget, TomlExampleTarget, TomlLibTarget, TomlManifest, TomlTarget, TomlTestTarget, }; -use crate::core::{compiler, Edition, Feature, Features, Target}; +use crate::core::{Edition, Feature, Features, Target}; use crate::util::errors::{CargoResult, CargoResultExt}; +use crate::util::restricted_names; pub fn targets( features: &Features, @@ -286,7 +287,7 @@ fn clean_bins( )); } - if compiler::is_bad_artifact_name(&name) { + if restricted_names::is_conflicting_artifact_name(&name) { anyhow::bail!("the binary target name `{}` is forbidden", name) } } diff --git a/tests/testsuite/alt_registry.rs b/tests/testsuite/alt_registry.rs index 8343bd471fc..cf45400b21a 100644 --- a/tests/testsuite/alt_registry.rs +++ b/tests/testsuite/alt_registry.rs @@ -644,7 +644,7 @@ fn bad_registry_name() { [ERROR] failed to parse manifest at `[CWD]/Cargo.toml` Caused by: - Invalid character ` ` in registry name: `bad name`", + invalid character ` ` in registry name: `bad name`, [..]", ) .run(); @@ -661,7 +661,7 @@ Caused by: .arg("--registry") .arg("bad name") .with_status(101) - .with_stderr("[ERROR] Invalid character ` ` in registry name: `bad name`") + .with_stderr("[ERROR] invalid character ` ` in registry name: `bad name`, [..]") .run(); } } diff --git a/tests/testsuite/build.rs b/tests/testsuite/build.rs index 5558360b5e8..a5a80f45f59 100644 --- a/tests/testsuite/build.rs +++ b/tests/testsuite/build.rs @@ -299,7 +299,7 @@ fn cargo_compile_with_invalid_package_name() { [ERROR] failed to parse manifest at `[..]` Caused by: - Invalid character `:` in package name: `foo::bar` + invalid character `:` in package name: `foo::bar`, [..] ", ) .run(); diff --git a/tests/testsuite/init.rs b/tests/testsuite/init.rs index 0196863c5ec..bb554983e9c 100644 --- a/tests/testsuite/init.rs +++ b/tests/testsuite/init.rs @@ -342,9 +342,8 @@ fn invalid_dir_name() { .with_status(101) .with_stderr( "\ -[ERROR] Invalid character `.` in crate name: `foo.bar` -use --name to override crate name -", +[ERROR] invalid character `.` in crate name: `foo.bar`, [..] +use --name to override crate name", ) .run(); @@ -361,7 +360,7 @@ fn reserved_name() { .with_status(101) .with_stderr( "\ -[ERROR] The name `test` cannot be used as a crate name\n\ +[ERROR] the name `test` cannot be used as a crate name, it conflicts [..]\n\ use --name to override crate name ", ) diff --git a/tests/testsuite/new.rs b/tests/testsuite/new.rs index 1a7f344619a..7455094d688 100644 --- a/tests/testsuite/new.rs +++ b/tests/testsuite/new.rs @@ -126,11 +126,7 @@ fn existing() { fn invalid_characters() { cargo_process("new foo.rs") .with_status(101) - .with_stderr( - "\ -[ERROR] Invalid character `.` in crate name: `foo.rs` -use --name to override crate name", - ) + .with_stderr("[ERROR] invalid character `.` in crate name: `foo.rs`, [..]") .run(); } @@ -138,10 +134,7 @@ use --name to override crate name", fn reserved_name() { cargo_process("new test") .with_status(101) - .with_stderr( - "[ERROR] The name `test` cannot be used as a crate name\n\ - use --name to override crate name", - ) + .with_stderr("[ERROR] the name `test` cannot be used as a crate name, it conflicts [..]") .run(); } @@ -150,8 +143,18 @@ fn reserved_binary_name() { cargo_process("new --bin incremental") .with_status(101) .with_stderr( - "[ERROR] The name `incremental` cannot be used as a crate name\n\ - use --name to override crate name", + "[ERROR] the name `incremental` cannot be used as a crate name, it conflicts [..]", + ) + .run(); + + cargo_process("new --lib incremental") + .env("USER", "foo") + .with_stderr( + "\ +[WARNING] the name `incremental` will not support binary executables with that name, \ +it conflicts with cargo's build directory names +[CREATED] library `incremental` package +", ) .run(); } @@ -160,9 +163,20 @@ fn reserved_binary_name() { fn keyword_name() { cargo_process("new pub") .with_status(101) + .with_stderr("[ERROR] the name `pub` cannot be used as a crate name, it is a Rust keyword") + .run(); +} + +#[cargo_test] +fn std_name() { + cargo_process("new core") + .env("USER", "foo") .with_stderr( - "[ERROR] The name `pub` cannot be used as a crate name\n\ - use --name to override crate name", + "\ +[WARNING] the name `core` is part of Rust's standard library +It is recommended to use a different name to avoid problems. +[CREATED] binary (application) `core` package +", ) .run(); } @@ -483,7 +497,10 @@ fn unknown_flags() { fn explicit_invalid_name_not_suggested() { cargo_process("new --name 10-invalid a") .with_status(101) - .with_stderr("[ERROR] Package names starting with a digit cannot be used as a crate name") + .with_stderr( + "[ERROR] the name `10-invalid` cannot be used as a crate name, \ + the name cannot start with a digit", + ) .run(); } @@ -558,3 +575,61 @@ fn lockfile_constant_during_new() { let after = fs::read_to_string(paths::root().join("foo/Cargo.lock")).unwrap(); assert_eq!(before, after); } + +#[cargo_test] +fn restricted_windows_name() { + if cfg!(windows) { + cargo_process("new nul") + .env("USER", "foo") + .with_status(101) + .with_stderr("[ERROR] cannot use name `nul`, it is a reserved Windows filename") + .run(); + } else { + cargo_process("new nul") + .env("USER", "foo") + .with_stderr( + "\ +[WARNING] the name `nul` is a reserved Windows filename +This package will not work on Windows platforms. +[CREATED] binary (application) `nul` package +", + ) + .run(); + } +} + +#[cargo_test] +fn non_ascii_name() { + cargo_process("new Привет") + .env("USER", "foo") + .with_stderr( + "\ +[WARNING] the name `Привет` contains non-ASCII characters +Support for non-ASCII crate names is experimental and only valid on the nightly toolchain. +[CREATED] binary (application) `Привет` package +", + ) + .run(); +} + +#[cargo_test] +fn non_ascii_name_invalid() { + // These are alphanumeric characters, but not Unicode XID. + cargo_process("new ⒶⒷⒸ") + .env("USER", "foo") + .with_status(101) + .with_stderr( + "[ERROR] invalid character `Ⓐ` in crate name: `ⒶⒷⒸ`, \ + the first character must be a Unicode XID start character (most letters or `_`)", + ) + .run(); + + cargo_process("new a¼") + .env("USER", "foo") + .with_status(101) + .with_stderr( + "[ERROR] invalid character `¼` in crate name: `a¼`, \ + characters must be Unicode XID characters (numbers, `-`, `_`, or most letters)", + ) + .run(); +} From 15ac82b677a7055c3e67fa06fd32e8446b2b377f Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Mon, 2 Mar 2020 14:47:38 -0800 Subject: [PATCH 2/3] Warn on Windows about reserved target names. --- src/cargo/util/toml/targets.rs | 18 +++++++++++---- tests/testsuite/cargo_targets.rs | 39 ++++++++++++++++++++++++++++++++ tests/testsuite/main.rs | 1 + 3 files changed, 54 insertions(+), 4 deletions(-) create mode 100644 tests/testsuite/cargo_targets.rs diff --git a/src/cargo/util/toml/targets.rs b/src/cargo/util/toml/targets.rs index 7431316b1eb..4eee8a192fd 100644 --- a/src/cargo/util/toml/targets.rs +++ b/src/cargo/util/toml/targets.rs @@ -172,7 +172,7 @@ fn clean_lib( None => return Ok(None), }; - validate_has_name(lib, "library", "lib")?; + validate_target_name(lib, "library", "lib", warnings)?; let path = match (lib.path.as_ref(), inferred) { (Some(path), _) => package_root.join(&path.0), @@ -264,7 +264,7 @@ fn clean_bins( ); for bin in &bins { - validate_has_name(bin, "binary", "bin")?; + validate_target_name(bin, "binary", "bin", warnings)?; let name = bin.name(); @@ -529,7 +529,7 @@ fn clean_targets_with_legacy_path( ); for target in &toml_targets { - validate_has_name(target, target_kind_human, target_kind)?; + validate_target_name(target, target_kind_human, target_kind, warnings)?; } validate_unique_names(&toml_targets, target_kind)?; @@ -720,16 +720,26 @@ fn inferred_to_toml_targets(inferred: &[(String, PathBuf)]) -> Vec { .collect() } -fn validate_has_name( +fn validate_target_name( target: &TomlTarget, target_kind_human: &str, target_kind: &str, + warnings: &mut Vec, ) -> CargoResult<()> { match target.name { Some(ref name) => { if name.trim().is_empty() { anyhow::bail!("{} target names cannot be empty", target_kind_human) } + if cfg!(windows) { + if restricted_names::is_windows_reserved(name) { + warnings.push(format!( + "{} target `{}` is a reserved Windows filename, \ + this target will not work on Windows platforms", + target_kind_human, name + )); + } + } } None => anyhow::bail!( "{} target {}.name is required", diff --git a/tests/testsuite/cargo_targets.rs b/tests/testsuite/cargo_targets.rs new file mode 100644 index 00000000000..452c95694c1 --- /dev/null +++ b/tests/testsuite/cargo_targets.rs @@ -0,0 +1,39 @@ +//! Tests specifically related to target handling (lib, bins, examples, tests, benches). + +use cargo_test_support::project; + +#[cargo_test] +fn reserved_windows_target_name() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + + [[bin]] + name = "con" + path = "src/main.rs" + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + if cfg!(windows) { + p.cargo("check") + .with_stderr( + "\ +[WARNING] binary target `con` is a reserved Windows filename, \ +this target will not work on Windows platforms +[CHECKING] foo[..] +[FINISHED][..] +", + ) + .run(); + } else { + p.cargo("check") + .with_stderr("[CHECKING] foo[..]\n[FINISHED][..]") + .run(); + } +} diff --git a/tests/testsuite/main.rs b/tests/testsuite/main.rs index 00bafadc6cb..b6cd7f643fb 100644 --- a/tests/testsuite/main.rs +++ b/tests/testsuite/main.rs @@ -24,6 +24,7 @@ mod cache_messages; mod cargo_alias_config; mod cargo_command; mod cargo_features; +mod cargo_targets; mod cfg; mod check; mod clean; From 2a874aa522f228735da87353646c39981ff10fab Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Mon, 2 Mar 2020 16:14:36 -0800 Subject: [PATCH 3/3] Warn when packaging files with Windows special names. --- src/cargo/ops/cargo_package.rs | 28 ++++++++++++++++++++++++---- tests/testsuite/package.rs | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 4 deletions(-) diff --git a/src/cargo/ops/cargo_package.rs b/src/cargo/ops/cargo_package.rs index 1d7af7bf813..c4c2a0f9c66 100644 --- a/src/cargo/ops/cargo_package.rs +++ b/src/cargo/ops/cargo_package.rs @@ -13,14 +13,14 @@ use log::debug; use tar::{Archive, Builder, EntryType, Header}; use crate::core::compiler::{BuildConfig, CompileMode, DefaultExecutor, Executor}; -use crate::core::{Feature, Verbosity, Workspace}; +use crate::core::{Feature, Shell, Verbosity, Workspace}; use crate::core::{Package, PackageId, PackageSet, Resolve, Source, SourceId}; use crate::ops; use crate::sources::PathSource; use crate::util::errors::{CargoResult, CargoResultExt}; use crate::util::paths; use crate::util::toml::TomlManifest; -use crate::util::{self, Config, FileLock}; +use crate::util::{self, restricted_names, Config, FileLock}; pub struct PackageOpts<'cfg> { pub config: &'cfg Config, @@ -142,7 +142,7 @@ fn build_ar_list( 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)?; + check_filename(&rel_path, &mut ws.config().shell())?; let rel_str = rel_path .to_str() .ok_or_else(|| { @@ -804,7 +804,7 @@ fn report_hash_difference(orig: &HashMap, after: &HashMap CargoResult<()> { +fn check_filename(file: &Path, shell: &mut Shell) -> CargoResult<()> { let name = match file.file_name() { Some(name) => name, None => return Ok(()), @@ -825,5 +825,25 @@ fn check_filename(file: &Path) -> CargoResult<()> { file.display() ) } + let mut check_windows = |name| -> CargoResult<()> { + if restricted_names::is_windows_reserved(name) { + shell.warn(format!( + "file {} is a reserved Windows filename, \ + it will not work on Windows platforms", + file.display() + ))?; + } + Ok(()) + }; + for component in file.iter() { + if let Some(component) = component.to_str() { + check_windows(component)?; + } + } + if file.extension().is_some() { + if let Some(stem) = file.file_stem().and_then(|s| s.to_str()) { + check_windows(stem)?; + } + } Ok(()) } diff --git a/tests/testsuite/package.rs b/tests/testsuite/package.rs index 668d2024237..408b9b94b9d 100644 --- a/tests/testsuite/package.rs +++ b/tests/testsuite/package.rs @@ -1663,3 +1663,37 @@ src/lib.rs let orig = read_to_string(p.root().join("target/package/foo-1.0.0/Cargo.toml.orig")).unwrap(); assert!(orig.contains("license-file = \"../LICENSE\"")); } + +#[cargo_test] +#[cfg(not(windows))] // Don't want to create invalid files on Windows. +fn package_restricted_windows() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + license = "MIT" + description = "foo" + homepage = "foo" + "#, + ) + .file("src/lib.rs", "pub mod con;\npub mod aux;") + .file("src/con.rs", "pub fn f() {}") + .file("src/aux/mod.rs", "pub fn f() {}") + .build(); + + p.cargo("package") + .with_stderr( + "\ +[WARNING] file src/aux/mod.rs is a reserved Windows filename, it will not work on Windows platforms +[WARNING] file src/con.rs is a reserved Windows filename, it will not work on Windows platforms +[PACKAGING] foo [..] +[VERIFYING] foo [..] +[COMPILING] foo [..] +[FINISHED] [..] +", + ) + .run(); +}