Skip to content

Commit 95008f9

Browse files
committed
Try to better handle restricted crate names.
1 parent 62180bf commit 95008f9

File tree

11 files changed

+251
-76
lines changed

11 files changed

+251
-76
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ tar = { version = "0.4.26", default-features = false }
6464
tempfile = "3.0"
6565
termcolor = "1.0"
6666
toml = "0.5.3"
67+
unicode-xid = "0.2.0"
6768
url = "2.0"
6869
walkdir = "2.2"
6970
clap = "2.31.2"

src/cargo/core/compiler/layout.rs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -129,12 +129,6 @@ pub struct Layout {
129129
_lock: FileLock,
130130
}
131131

132-
pub fn is_bad_artifact_name(name: &str) -> bool {
133-
["deps", "examples", "build", "incremental"]
134-
.iter()
135-
.any(|&reserved| reserved == name)
136-
}
137-
138132
impl Layout {
139133
/// Calculate the paths for build output, lock the build directory, and return as a Layout.
140134
///

src/cargo/core/compiler/mod.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ pub use self::custom_build::{BuildOutput, BuildScriptOutputs, BuildScripts};
3737
pub use self::job::Freshness;
3838
use self::job::{Job, Work};
3939
use self::job_queue::{JobQueue, JobState};
40-
pub use self::layout::is_bad_artifact_name;
4140
use self::output_depinfo::output_depinfo;
4241
use self::unit_dependencies::UnitDep;
4342
pub use crate::core::compiler::unit::{Unit, UnitInterner};

src/cargo/ops/cargo_new.rs

Lines changed: 67 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
use crate::core::{compiler, Workspace};
1+
use crate::core::{Shell, Workspace};
22
use crate::util::errors::{CargoResult, CargoResultExt};
33
use crate::util::{existing_vcs_repo, FossilRepo, GitRepo, HgRepo, PijulRepo};
4-
use crate::util::{paths, validate_package_name, Config};
4+
use crate::util::{paths, restricted_names, Config};
55
use git2::Config as GitConfig;
66
use git2::Repository as GitRepository;
77
use serde::de;
@@ -155,41 +155,71 @@ fn get_name<'a>(path: &'a Path, opts: &'a NewOptions) -> CargoResult<&'a str> {
155155
})
156156
}
157157

158-
fn check_name(name: &str, opts: &NewOptions) -> CargoResult<()> {
159-
// If --name is already used to override, no point in suggesting it
160-
// again as a fix.
161-
let name_help = match opts.name {
162-
Some(_) => "",
163-
None => "\nuse --name to override crate name",
164-
};
158+
fn check_name(name: &str, name_help: &str, has_bin: bool, shell: &mut Shell) -> CargoResult<()> {
159+
restricted_names::validate_package_name(name, "crate name", name_help)?;
165160

166-
// Ban keywords + test list found at
167-
// https://doc.rust-lang.org/reference/keywords.html
168-
let blacklist = [
169-
"abstract", "alignof", "as", "become", "box", "break", "const", "continue", "crate", "do",
170-
"else", "enum", "extern", "false", "final", "fn", "for", "if", "impl", "in", "let", "loop",
171-
"macro", "match", "mod", "move", "mut", "offsetof", "override", "priv", "proc", "pub",
172-
"pure", "ref", "return", "self", "sizeof", "static", "struct", "super", "test", "trait",
173-
"true", "type", "typeof", "unsafe", "unsized", "use", "virtual", "where", "while", "yield",
174-
];
175-
if blacklist.contains(&name) || (opts.kind.is_bin() && compiler::is_bad_artifact_name(name)) {
161+
if restricted_names::is_keyword(name) {
176162
anyhow::bail!(
177-
"The name `{}` cannot be used as a crate name{}",
163+
"the name `{}` cannot be used as a crate name, it is a Rust keyword{}",
178164
name,
179165
name_help
180-
)
166+
);
181167
}
182-
183-
if let Some(ref c) = name.chars().next() {
184-
if c.is_digit(10) {
168+
if restricted_names::is_conflicting_artifact_name(name) {
169+
if has_bin {
185170
anyhow::bail!(
186-
"Package names starting with a digit cannot be used as a crate name{}",
171+
"the name `{}` cannot be used as a crate name, \
172+
it conflicts with cargo's build directory names{}",
173+
name,
187174
name_help
188-
)
175+
);
176+
} else {
177+
shell.warn(format!(
178+
"the name `{}` will not support binary \
179+
executables with that name, \
180+
it conflicts with cargo's build directory names",
181+
name
182+
))?;
189183
}
190184
}
185+
if name == "test" {
186+
anyhow::bail!(
187+
"the name `test` cannot be used as a crate name, \
188+
it conflicts with Rust's built-in test library{}",
189+
name_help
190+
);
191+
}
192+
if ["core", "std", "alloc", "proc_macro", "proc-macro"].contains(&name) {
193+
shell.warn(format!(
194+
"the name `{}` is part of Rust's standard library\n\
195+
It is recommended to use a different name to avoid problems.",
196+
name
197+
))?;
198+
}
199+
if restricted_names::is_windows_reserved(name) {
200+
if cfg!(windows) {
201+
anyhow::bail!(
202+
"cannot use name `{}`, it is a reserved Windows filename{}",
203+
name,
204+
name_help
205+
);
206+
} else {
207+
shell.warn(format!(
208+
"the name `{}` is a reserved Windows filename\n\
209+
This package will not work on Windows platforms.",
210+
name
211+
))?;
212+
}
213+
}
214+
if restricted_names::is_non_ascii_name(name) {
215+
shell.warn(format!(
216+
"the name `{}` contains non-ASCII characters\n\
217+
Support for non-ASCII crate names is experimental and only valid \
218+
on the nightly toolchain.",
219+
name
220+
))?;
221+
}
191222

192-
validate_package_name(name, "crate name", name_help)?;
193223
Ok(())
194224
}
195225

@@ -337,7 +367,7 @@ pub fn new(opts: &NewOptions, config: &Config) -> CargoResult<()> {
337367
}
338368

339369
let name = get_name(path, opts)?;
340-
check_name(name, opts)?;
370+
check_name(name, "", opts.kind.is_bin(), &mut config.shell())?;
341371

342372
let mkopts = MkOptions {
343373
version_control: opts.version_control,
@@ -372,7 +402,6 @@ pub fn init(opts: &NewOptions, config: &Config) -> CargoResult<()> {
372402
}
373403

374404
let name = get_name(path, opts)?;
375-
check_name(name, opts)?;
376405

377406
let mut src_paths_types = vec![];
378407

@@ -385,6 +414,14 @@ pub fn init(opts: &NewOptions, config: &Config) -> CargoResult<()> {
385414
// Maybe when doing `cargo init --bin` inside a library package stub,
386415
// user may mean "initialize for library, but also add binary target"
387416
}
417+
let has_bin = src_paths_types.iter().any(|x| x.bin);
418+
// If --name is already used to override, no point in suggesting it
419+
// again as a fix.
420+
let name_help = match opts.name {
421+
Some(_) => "",
422+
None => "\nuse --name to override crate name",
423+
};
424+
check_name(name, name_help, has_bin, &mut config.shell())?;
388425

389426
let mut version_control = opts.version_control;
390427

@@ -426,7 +463,7 @@ pub fn init(opts: &NewOptions, config: &Config) -> CargoResult<()> {
426463
version_control,
427464
path,
428465
name,
429-
bin: src_paths_types.iter().any(|x| x.bin),
466+
bin: has_bin,
430467
source_files: src_paths_types,
431468
edition: opts.edition.as_ref().map(|s| &**s),
432469
registry: opts.registry.as_ref().map(|s| &**s),

src/cargo/util/mod.rs

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ pub use self::paths::{dylib_path_envvar, normalize_path};
1919
pub use self::process_builder::{process, ProcessBuilder};
2020
pub use self::progress::{Progress, ProgressStyle};
2121
pub use self::read2::read2;
22+
pub use self::restricted_names::validate_package_name;
2223
pub use self::rustc::Rustc;
2324
pub use self::sha256::Sha256;
2425
pub use self::to_semver::ToSemver;
@@ -51,6 +52,7 @@ pub mod process_builder;
5152
pub mod profile;
5253
mod progress;
5354
mod read2;
55+
pub mod restricted_names;
5456
pub mod rustc;
5557
mod sha256;
5658
pub mod to_semver;
@@ -68,22 +70,6 @@ pub fn elapsed(duration: Duration) -> String {
6870
}
6971
}
7072

71-
/// Check the base requirements for a package name.
72-
///
73-
/// This can be used for other things than package names, to enforce some
74-
/// level of sanity. Note that package names have other restrictions
75-
/// elsewhere. `cargo new` has a few restrictions, such as checking for
76-
/// reserved names. crates.io has even more restrictions.
77-
pub fn validate_package_name(name: &str, what: &str, help: &str) -> CargoResult<()> {
78-
if let Some(ch) = name
79-
.chars()
80-
.find(|ch| !ch.is_alphanumeric() && *ch != '_' && *ch != '-')
81-
{
82-
anyhow::bail!("Invalid character `{}` in {}: `{}`{}", ch, what, name, help);
83-
}
84-
Ok(())
85-
}
86-
8773
/// Whether or not this running in a Continuous Integration environment.
8874
pub fn is_ci() -> bool {
8975
std::env::var("CI").is_ok() || std::env::var("TF_BUILD").is_ok()

src/cargo/util/restricted_names.rs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
//! Helpers for validating and checking names like package and crate names.
2+
3+
use crate::util::CargoResult;
4+
use anyhow::bail;
5+
6+
/// Returns `true` if the name contains non-ASCII characters.
7+
pub fn is_non_ascii_name(name: &str) -> bool {
8+
name.chars().any(|ch| ch > '\x7f')
9+
}
10+
11+
/// A Rust keyword.
12+
pub fn is_keyword(name: &str) -> bool {
13+
// See https://doc.rust-lang.org/reference/keywords.html
14+
[
15+
"Self", "abstract", "as", "async", "await", "become", "box", "break", "const", "continue",
16+
"crate", "do", "dyn", "else", "enum", "extern", "false", "final", "fn", "for", "if",
17+
"impl", "in", "let", "loop", "macro", "match", "mod", "move", "mut", "override", "priv",
18+
"pub", "ref", "return", "self", "static", "struct", "super", "trait", "true", "try",
19+
"type", "typeof", "unsafe", "unsized", "use", "virtual", "where", "while", "yield",
20+
]
21+
.contains(&name)
22+
}
23+
24+
/// These names cannot be used on Windows, even with an extension.
25+
pub fn is_windows_reserved(name: &str) -> bool {
26+
[
27+
"con", "prn", "aux", "nul", "com1", "com2", "com3", "com4", "com5", "com6", "com7", "com8",
28+
"com9", "lpt1", "lpt2", "lpt3", "lpt4", "lpt5", "lpt6", "lpt7", "lpt8", "lpt9",
29+
]
30+
.contains(&name.to_ascii_lowercase().as_str())
31+
}
32+
33+
/// An artifact with this name will conflict with one of Cargo's build directories.
34+
pub fn is_conflicting_artifact_name(name: &str) -> bool {
35+
["deps", "examples", "build", "incremental"].contains(&name)
36+
}
37+
38+
/// Check the base requirements for a package name.
39+
///
40+
/// This can be used for other things than package names, to enforce some
41+
/// level of sanity. Note that package names have other restrictions
42+
/// elsewhere. `cargo new` has a few restrictions, such as checking for
43+
/// reserved names. crates.io has even more restrictions.
44+
pub fn validate_package_name(name: &str, what: &str, help: &str) -> CargoResult<()> {
45+
let mut chars = name.chars();
46+
if let Some(ch) = chars.next() {
47+
if ch.is_digit(10) {
48+
// A specific error for a potentially common case.
49+
bail!(
50+
"the name `{}` cannot be used as a {}, \
51+
the name cannot start with a digit{}",
52+
name,
53+
what,
54+
help
55+
);
56+
}
57+
if !(unicode_xid::UnicodeXID::is_xid_start(ch) || ch == '_') {
58+
bail!(
59+
"invalid character `{}` in {}: `{}`, \
60+
the first character must be a Unicode XID start character \
61+
(most letters or `_`){}",
62+
ch,
63+
what,
64+
name,
65+
help
66+
);
67+
}
68+
}
69+
for ch in chars {
70+
if !(unicode_xid::UnicodeXID::is_xid_continue(ch) || ch == '-') {
71+
bail!(
72+
"invalid character `{}` in {}: `{}`, \
73+
characters must be Unicode XID characters \
74+
(numbers, `-`, `_`, or most letters){}",
75+
ch,
76+
what,
77+
name,
78+
help
79+
);
80+
}
81+
}
82+
Ok(())
83+
}

src/cargo/util/toml/targets.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,9 @@ use super::{
1818
LibKind, PathValue, StringOrBool, StringOrVec, TomlBenchTarget, TomlBinTarget,
1919
TomlExampleTarget, TomlLibTarget, TomlManifest, TomlTarget, TomlTestTarget,
2020
};
21-
use crate::core::{compiler, Edition, Feature, Features, Target};
21+
use crate::core::{Edition, Feature, Features, Target};
2222
use crate::util::errors::{CargoResult, CargoResultExt};
23+
use crate::util::restricted_names;
2324

2425
pub fn targets(
2526
features: &Features,
@@ -286,7 +287,7 @@ fn clean_bins(
286287
));
287288
}
288289

289-
if compiler::is_bad_artifact_name(&name) {
290+
if restricted_names::is_conflicting_artifact_name(&name) {
290291
anyhow::bail!("the binary target name `{}` is forbidden", name)
291292
}
292293
}

tests/testsuite/alt_registry.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -644,7 +644,7 @@ fn bad_registry_name() {
644644
[ERROR] failed to parse manifest at `[CWD]/Cargo.toml`
645645
646646
Caused by:
647-
Invalid character ` ` in registry name: `bad name`",
647+
invalid character ` ` in registry name: `bad name`, [..]",
648648
)
649649
.run();
650650

@@ -661,7 +661,7 @@ Caused by:
661661
.arg("--registry")
662662
.arg("bad name")
663663
.with_status(101)
664-
.with_stderr("[ERROR] Invalid character ` ` in registry name: `bad name`")
664+
.with_stderr("[ERROR] invalid character ` ` in registry name: `bad name`, [..]")
665665
.run();
666666
}
667667
}

tests/testsuite/build.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,7 @@ fn cargo_compile_with_invalid_package_name() {
299299
[ERROR] failed to parse manifest at `[..]`
300300
301301
Caused by:
302-
Invalid character `:` in package name: `foo::bar`
302+
invalid character `:` in package name: `foo::bar`, [..]
303303
",
304304
)
305305
.run();

tests/testsuite/init.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -342,9 +342,8 @@ fn invalid_dir_name() {
342342
.with_status(101)
343343
.with_stderr(
344344
"\
345-
[ERROR] Invalid character `.` in crate name: `foo.bar`
346-
use --name to override crate name
347-
",
345+
[ERROR] invalid character `.` in crate name: `foo.bar`, [..]
346+
use --name to override crate name",
348347
)
349348
.run();
350349

@@ -361,7 +360,7 @@ fn reserved_name() {
361360
.with_status(101)
362361
.with_stderr(
363362
"\
364-
[ERROR] The name `test` cannot be used as a crate name\n\
363+
[ERROR] the name `test` cannot be used as a crate name, it conflicts [..]\n\
365364
use --name to override crate name
366365
",
367366
)

0 commit comments

Comments
 (0)