]> git.proxmox.com Git - cargo.git/blame - src/cargo/ops/cargo_new.rs
Auto merge of #9467 - ehuss:install-metadata, r=alexcrichton
[cargo.git] / src / cargo / ops / cargo_new.rs
CommitLineData
aa61976c 1use crate::core::{Edition, Shell, Workspace};
ebca5190 2use crate::util::errors::CargoResult;
44a31d67 3use crate::util::{existing_vcs_repo, FossilRepo, GitRepo, HgRepo, PijulRepo};
1dae5acb 4use crate::util::{restricted_names, Config};
ebca5190 5use anyhow::Context as _;
1dae5acb 6use cargo_util::paths;
44a31d67
AC
7use serde::de;
8use serde::Deserialize;
f8fb0a02 9use std::collections::BTreeMap;
9e5721c2 10use std::fmt;
db09895f 11use std::io::{BufRead, BufReader, ErrorKind};
71073f8b 12use std::path::{Path, PathBuf};
aa7fe99c 13use std::process::Command;
768b60ab 14use std::str::{from_utf8, FromStr};
7e66058a 15
abe56727 16#[derive(Clone, Copy, Debug, PartialEq)]
1e682848
AC
17pub enum VersionControl {
18 Git,
19 Hg,
20 Pijul,
21 Fossil,
22 NoVcs,
23}
16d3e72a 24
44a31d67 25impl FromStr for VersionControl {
3a18c89a 26 type Err = anyhow::Error;
44a31d67 27
3a18c89a 28 fn from_str(s: &str) -> Result<Self, anyhow::Error> {
44a31d67
AC
29 match s {
30 "git" => Ok(VersionControl::Git),
31 "hg" => Ok(VersionControl::Hg),
32 "pijul" => Ok(VersionControl::Pijul),
33 "fossil" => Ok(VersionControl::Fossil),
34 "none" => Ok(VersionControl::NoVcs),
3a18c89a 35 other => anyhow::bail!("unknown vcs specification: `{}`", other),
44a31d67
AC
36 }
37 }
38}
39
40impl<'de> de::Deserialize<'de> for VersionControl {
41 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
42 where
43 D: de::Deserializer<'de>,
44 {
45 let s = String::deserialize(deserializer)?;
46 FromStr::from_str(&s).map_err(de::Error::custom)
47 }
48}
49
7625cfaf 50#[derive(Debug)]
50284b11 51pub struct NewOptions {
16d3e72a 52 pub version_control: Option<VersionControl>,
9e5721c2 53 pub kind: NewProjectKind,
3492a390 54 /// Absolute path to the directory for the new package
71073f8b 55 pub path: PathBuf,
50284b11 56 pub name: Option<String>,
3d029039 57 pub edition: Option<String>,
edc7c887 58 pub registry: Option<String>,
a4272ef2
AC
59}
60
9e5721c2
AK
61#[derive(Clone, Copy, Debug, PartialEq, Eq)]
62pub enum NewProjectKind {
63 Bin,
64 Lib,
65}
66
67impl NewProjectKind {
385b54b3
E
68 fn is_bin(self) -> bool {
69 self == NewProjectKind::Bin
9e5721c2
AK
70 }
71}
72
73impl fmt::Display for NewProjectKind {
b8b7faee 74 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
9e5721c2
AK
75 match *self {
76 NewProjectKind::Bin => "binary (application)",
77 NewProjectKind::Lib => "library",
7cd4a94d
E
78 }
79 .fmt(f)
9e5721c2
AK
80 }
81}
82
800172fb
VS
83struct SourceFileInformation {
84 relative_path: String,
85 target_name: String,
86 bin: bool,
87}
88
89struct MkOptions<'a> {
90 version_control: Option<VersionControl>,
91 path: &'a Path,
92 name: &'a str,
7e66058a 93 source_files: Vec<SourceFileInformation>,
0e4fa582 94 bin: bool,
3d029039 95 edition: Option<&'a str>,
edc7c887 96 registry: Option<&'a str>,
800172fb
VS
97}
98
50284b11 99impl NewOptions {
1e682848
AC
100 pub fn new(
101 version_control: Option<VersionControl>,
102 bin: bool,
103 lib: bool,
71073f8b 104 path: PathBuf,
1e682848 105 name: Option<String>,
3d029039 106 edition: Option<String>,
edc7c887 107 registry: Option<String>,
1e682848 108 ) -> CargoResult<NewOptions> {
9e5721c2 109 let kind = match (bin, lib) {
3a18c89a 110 (true, true) => anyhow::bail!("can't specify both lib and binary outputs"),
9e5721c2 111 (false, true) => NewProjectKind::Lib,
9cb10e68 112 // default to bin
676edacf 113 (_, false) => NewProjectKind::Bin,
9e5721c2 114 };
a882abe7 115
1e682848
AC
116 let opts = NewOptions {
117 version_control,
118 kind,
119 path,
120 name,
3d029039 121 edition,
edc7c887 122 registry,
1e682848 123 };
9e5721c2 124 Ok(opts)
a882abe7
JT
125 }
126}
127
44a31d67 128#[derive(Deserialize)]
de0f6041 129struct CargoNewConfig {
b0998864
J
130 #[deprecated = "cargo-new no longer supports adding the authors field"]
131 #[allow(dead_code)]
132 name: Option<String>,
133
134 #[deprecated = "cargo-new no longer supports adding the authors field"]
135 #[allow(dead_code)]
136 email: Option<String>,
137
44a31d67 138 #[serde(rename = "vcs")]
16d3e72a 139 version_control: Option<VersionControl>,
de0f6041
AC
140}
141
3e7c75a7 142fn get_name<'a>(path: &'a Path, opts: &'a NewOptions) -> CargoResult<&'a str> {
50284b11 143 if let Some(ref name) = opts.name {
800172fb
VS
144 return Ok(name);
145 }
455f800c 146
3e7c75a7 147 let file_name = path.file_name().ok_or_else(|| {
3a18c89a 148 anyhow::format_err!(
3492a390 149 "cannot auto-detect package name from path {:?} ; use --name to override",
1e682848
AC
150 path.as_os_str()
151 )
82655b46 152 })?;
455f800c 153
3e7c75a7 154 file_name.to_str().ok_or_else(|| {
3a18c89a 155 anyhow::format_err!(
3492a390 156 "cannot create package with a non-unicode name: {:?}",
1e682848
AC
157 file_name
158 )
3e7c75a7 159 })
800172fb
VS
160}
161
3042ec68
WL
162fn check_name(
163 name: &str,
164 show_name_help: bool,
165 has_bin: bool,
166 shell: &mut Shell,
167) -> CargoResult<()> {
168 // If --name is already used to override, no point in suggesting it
169 // again as a fix.
170 let name_help = if show_name_help {
fdb8ea1e 171 "\nIf you need a package name to not match the directory name, consider using --name flag."
3042ec68
WL
172 } else {
173 ""
174 };
fdb8ea1e
EH
175 let bin_help = || {
176 let mut help = String::from(name_help);
177 if has_bin {
178 help.push_str(&format!(
179 "\n\
180 If you need a binary with the name \"{name}\", use a valid package \
181 name, and set the binary name to be different from the package. \
182 This can be done by setting the binary filename to `src/bin/{name}.rs` \
183 or change the name in Cargo.toml with:\n\
184 \n \
185 [bin]\n \
186 name = \"{name}\"\n \
187 path = \"src/main.rs\"\n\
188 ",
189 name = name
190 ));
191 }
192 help
193 };
194 restricted_names::validate_package_name(name, "package name", &bin_help())?;
466c74a9 195
95008f91 196 if restricted_names::is_keyword(name) {
3a18c89a 197 anyhow::bail!(
fdb8ea1e 198 "the name `{}` cannot be used as a package name, it is a Rust keyword{}",
7625cfaf 199 name,
fdb8ea1e 200 bin_help()
95008f91 201 );
d30e963c 202 }
95008f91
EH
203 if restricted_names::is_conflicting_artifact_name(name) {
204 if has_bin {
205 anyhow::bail!(
fdb8ea1e 206 "the name `{}` cannot be used as a package name, \
95008f91
EH
207 it conflicts with cargo's build directory names{}",
208 name,
209 name_help
210 );
211 } else {
212 shell.warn(format!(
213 "the name `{}` will not support binary \
214 executables with that name, \
215 it conflicts with cargo's build directory names",
216 name
217 ))?;
218 }
219 }
220 if name == "test" {
221 anyhow::bail!(
fdb8ea1e 222 "the name `test` cannot be used as a package name, \
95008f91 223 it conflicts with Rust's built-in test library{}",
fdb8ea1e 224 bin_help()
95008f91
EH
225 );
226 }
227 if ["core", "std", "alloc", "proc_macro", "proc-macro"].contains(&name) {
228 shell.warn(format!(
229 "the name `{}` is part of Rust's standard library\n\
fdb8ea1e
EH
230 It is recommended to use a different name to avoid problems.{}",
231 name,
232 bin_help()
95008f91
EH
233 ))?;
234 }
235 if restricted_names::is_windows_reserved(name) {
236 if cfg!(windows) {
3a18c89a 237 anyhow::bail!(
95008f91
EH
238 "cannot use name `{}`, it is a reserved Windows filename{}",
239 name,
1e682848 240 name_help
95008f91
EH
241 );
242 } else {
243 shell.warn(format!(
244 "the name `{}` is a reserved Windows filename\n\
245 This package will not work on Windows platforms.",
246 name
247 ))?;
73aa1174
RS
248 }
249 }
95008f91
EH
250 if restricted_names::is_non_ascii_name(name) {
251 shell.warn(format!(
252 "the name `{}` contains non-ASCII characters\n\
253 Support for non-ASCII crate names is experimental and only valid \
254 on the nightly toolchain.",
255 name
256 ))?;
257 }
73aa1174 258
800172fb
VS
259 Ok(())
260}
261
1e682848 262fn detect_source_paths_and_types(
3492a390
ZL
263 package_path: &Path,
264 package_name: &str,
1e682848
AC
265 detected_files: &mut Vec<SourceFileInformation>,
266) -> CargoResult<()> {
3492a390
ZL
267 let path = package_path;
268 let name = package_name;
455f800c 269
800172fb
VS
270 enum H {
271 Bin,
272 Lib,
273 Detect,
274 }
455f800c 275
800172fb
VS
276 struct Test {
277 proposed_path: String,
278 handling: H,
279 }
455f800c 280
800172fb 281 let tests = vec![
1e682848 282 Test {
385b54b3 283 proposed_path: "src/main.rs".to_string(),
1e682848
AC
284 handling: H::Bin,
285 },
286 Test {
385b54b3 287 proposed_path: "main.rs".to_string(),
1e682848
AC
288 handling: H::Bin,
289 },
290 Test {
291 proposed_path: format!("src/{}.rs", name),
292 handling: H::Detect,
293 },
294 Test {
295 proposed_path: format!("{}.rs", name),
296 handling: H::Detect,
297 },
298 Test {
385b54b3 299 proposed_path: "src/lib.rs".to_string(),
1e682848
AC
300 handling: H::Lib,
301 },
302 Test {
385b54b3 303 proposed_path: "lib.rs".to_string(),
1e682848
AC
304 handling: H::Lib,
305 },
800172fb 306 ];
455f800c 307
800172fb
VS
308 for i in tests {
309 let pp = i.proposed_path;
455f800c 310
800172fb 311 // path/pp does not exist or is not a file
4367ec4d 312 if !path.join(&pp).is_file() {
800172fb
VS
313 continue;
314 }
455f800c 315
800172fb 316 let sfi = match i.handling {
1e682848
AC
317 H::Bin => SourceFileInformation {
318 relative_path: pp,
3492a390 319 target_name: package_name.to_string(),
1e682848
AC
320 bin: true,
321 },
322 H::Lib => SourceFileInformation {
323 relative_path: pp,
3492a390 324 target_name: package_name.to_string(),
1e682848
AC
325 bin: false,
326 },
800172fb 327 H::Detect => {
82655b46 328 let content = paths::read(&path.join(pp.clone()))?;
800172fb 329 let isbin = content.contains("fn main");
455f800c
AC
330 SourceFileInformation {
331 relative_path: pp,
3492a390 332 target_name: package_name.to_string(),
1e682848 333 bin: isbin,
800172fb
VS
334 }
335 }
336 };
337 detected_files.push(sfi);
338 }
455f800c 339
800172fb 340 // Check for duplicate lib attempt
455f800c 341
1e682848
AC
342 let mut previous_lib_relpath: Option<&str> = None;
343 let mut duplicates_checker: BTreeMap<&str, &SourceFileInformation> = BTreeMap::new();
455f800c 344
800172fb
VS
345 for i in detected_files {
346 if i.bin {
347 if let Some(x) = BTreeMap::get::<str>(&duplicates_checker, i.target_name.as_ref()) {
3a18c89a 348 anyhow::bail!(
1e682848 349 "\
800172fb
VS
350multiple possible binary sources found:
351 {}
352 {}
353cannot automatically generate Cargo.toml as the main target would be ambiguous",
1e682848
AC
354 &x.relative_path,
355 &i.relative_path
356 );
800172fb
VS
357 }
358 duplicates_checker.insert(i.target_name.as_ref(), i);
359 } else {
360 if let Some(plp) = previous_lib_relpath {
3a18c89a 361 anyhow::bail!(
3492a390 362 "cannot have a package with \
1e682848
AC
363 multiple libraries, \
364 found both `{}` and `{}`",
365 plp,
366 i.relative_path
367 )
800172fb
VS
368 }
369 previous_lib_relpath = Some(&i.relative_path);
370 }
9a387087 371 }
455f800c 372
800172fb
VS
373 Ok(())
374}
375
3492a390 376fn plan_new_source_file(bin: bool, package_name: String) -> SourceFileInformation {
800172fb 377 if bin {
455f800c 378 SourceFileInformation {
1e682848 379 relative_path: "src/main.rs".to_string(),
3492a390 380 target_name: package_name,
1e682848 381 bin: true,
800172fb
VS
382 }
383 } else {
384 SourceFileInformation {
1e682848 385 relative_path: "src/lib.rs".to_string(),
3492a390 386 target_name: package_name,
1e682848 387 bin: false,
800172fb
VS
388 }
389 }
390}
391
23591fe5 392pub fn new(opts: &NewOptions, config: &Config) -> CargoResult<()> {
71073f8b 393 let path = &opts.path;
4367ec4d 394 if path.exists() {
3a18c89a 395 anyhow::bail!(
1e682848 396 "destination `{}` already exists\n\n\
71073f8b 397 Use `cargo init` to initialize the directory",
1e682848 398 path.display()
484a33af 399 )
800172fb 400 }
455f800c 401
71073f8b 402 let name = get_name(path, opts)?;
3042ec68
WL
403 check_name(
404 name,
405 opts.name.is_none(),
406 opts.kind.is_bin(),
407 &mut config.shell(),
408 )?;
800172fb
VS
409
410 let mkopts = MkOptions {
411 version_control: opts.version_control,
947e8902 412 path,
0247dc42 413 name,
9e5721c2
AK
414 source_files: vec![plan_new_source_file(opts.kind.is_bin(), name.to_string())],
415 bin: opts.kind.is_bin(),
d47a9545
EH
416 edition: opts.edition.as_deref(),
417 registry: opts.registry.as_deref(),
800172fb 418 };
455f800c 419
ebca5190 420 mk(config, &mkopts).with_context(|| {
9099b491 421 format!(
3492a390 422 "Failed to create package `{}` at `{}`",
1e682848
AC
423 name,
424 path.display()
425 )
37cffbe0
AC
426 })?;
427 Ok(())
800172fb
VS
428}
429
23591fe5 430pub fn init(opts: &NewOptions, config: &Config) -> CargoResult<()> {
0d44a826
EH
431 // This is here just as a random location to exercise the internal error handling.
432 if std::env::var_os("__CARGO_TEST_INTERNAL_ERROR").is_some() {
433 return Err(crate::util::internal("internal error test"));
434 }
435
71073f8b 436 let path = &opts.path;
455f800c 437
4367ec4d 438 if path.join("Cargo.toml").exists() {
3a18c89a 439 anyhow::bail!("`cargo init` cannot be run on existing Cargo packages")
800172fb 440 }
455f800c 441
947e8902 442 let name = get_name(path, opts)?;
455f800c 443
800172fb 444 let mut src_paths_types = vec![];
455f800c 445
947e8902 446 detect_source_paths_and_types(path, name, &mut src_paths_types)?;
455f800c 447
23591fe5 448 if src_paths_types.is_empty() {
9e5721c2 449 src_paths_types.push(plan_new_source_file(opts.kind.is_bin(), name.to_string()));
800172fb
VS
450 } else {
451 // --bin option may be ignored if lib.rs or src/lib.rs present
3492a390 452 // Maybe when doing `cargo init --bin` inside a library package stub,
800172fb
VS
453 // user may mean "initialize for library, but also add binary target"
454 }
95008f91 455 let has_bin = src_paths_types.iter().any(|x| x.bin);
3042ec68 456 check_name(name, opts.name.is_none(), has_bin, &mut config.shell())?;
455f800c 457
800172fb 458 let mut version_control = opts.version_control;
455f800c 459
800172fb
VS
460 if version_control == None {
461 let mut num_detected_vsces = 0;
455f800c 462
4367ec4d 463 if path.join(".git").exists() {
800172fb
VS
464 version_control = Some(VersionControl::Git);
465 num_detected_vsces += 1;
466 }
455f800c 467
4367ec4d 468 if path.join(".hg").exists() {
800172fb
VS
469 version_control = Some(VersionControl::Hg);
470 num_detected_vsces += 1;
471 }
455f800c 472
4367ec4d 473 if path.join(".pijul").exists() {
2b31978c
PW
474 version_control = Some(VersionControl::Pijul);
475 num_detected_vsces += 1;
476 }
477
4367ec4d 478 if path.join(".fossil").exists() {
7eca7f27
DK
479 version_control = Some(VersionControl::Fossil);
480 num_detected_vsces += 1;
481 }
482
800172fb 483 // if none exists, maybe create git, like in `cargo new`
455f800c 484
800172fb 485 if num_detected_vsces > 1 {
3a18c89a 486 anyhow::bail!(
1e682848
AC
487 "more than one of .hg, .git, .pijul, .fossil configurations \
488 found and the ignore file can't be filled in as \
489 a result. specify --vcs to override detection"
490 );
800172fb
VS
491 }
492 }
455f800c 493
800172fb 494 let mkopts = MkOptions {
9e5721c2 495 version_control,
71073f8b 496 path,
9e5721c2 497 name,
95008f91 498 bin: has_bin,
7e66058a 499 source_files: src_paths_types,
d47a9545
EH
500 edition: opts.edition.as_deref(),
501 registry: opts.registry.as_deref(),
800172fb 502 };
455f800c 503
ebca5190 504 mk(config, &mkopts).with_context(|| {
9099b491 505 format!(
3492a390 506 "Failed to create package `{}` at `{}`",
1e682848
AC
507 name,
508 path.display()
509 )
37cffbe0
AC
510 })?;
511 Ok(())
a4272ef2
AC
512}
513
99a23c08
JW
514/// IgnoreList
515struct IgnoreList {
516 /// git like formatted entries
517 ignore: Vec<String>,
518 /// mercurial formatted entries
519 hg_ignore: Vec<String>,
520}
521
522impl IgnoreList {
523 /// constructor to build a new ignore file
524 fn new() -> IgnoreList {
bce30836 525 IgnoreList {
526 ignore: Vec::new(),
99a23c08 527 hg_ignore: Vec::new(),
1e682848 528 }
99a23c08
JW
529 }
530
db09895f 531 /// add a new entry to the ignore list. Requires two arguments with the
99a23c08
JW
532 /// entry in two different formats. One for "git style" entries and one for
533 /// "mercurial like" entries.
534 fn push(&mut self, ignore: &str, hg_ignore: &str) {
535 self.ignore.push(ignore.to_string());
536 self.hg_ignore.push(hg_ignore.to_string());
537 }
538
539 /// Return the correctly formatted content of the ignore file for the given
540 /// version control system as `String`.
541 fn format_new(&self, vcs: VersionControl) -> String {
5a5d924c
AK
542 let ignore_items = match vcs {
543 VersionControl::Hg => &self.hg_ignore,
544 _ => &self.ignore,
545 };
546
547 ignore_items.join("\n") + "\n"
99a23c08
JW
548 }
549
550 /// format_existing is used to format the IgnoreList when the ignore file
551 /// already exists. It reads the contents of the given `BufRead` and
552 /// checks if the contents of the ignore list are already existing in the
553 /// file.
554 fn format_existing<T: BufRead>(&self, existing: T, vcs: VersionControl) -> String {
555 // TODO: is unwrap safe?
db09895f 556 let existing_items = existing.lines().collect::<Result<Vec<_>, _>>().unwrap();
99a23c08 557
db09895f 558 let ignore_items = match vcs {
559 VersionControl::Hg => &self.hg_ignore,
560 _ => &self.ignore,
99a23c08
JW
561 };
562
773ab74b 563 let mut out = "\n\n# Added by cargo\n".to_string();
3e8cdd7c
OD
564 if ignore_items
565 .iter()
566 .any(|item| existing_items.contains(item))
567 {
773ab74b 568 out.push_str("#\n# already existing elements were commented out\n");
3e8cdd7c
OD
569 }
570 out.push('\n');
99a23c08
JW
571
572 for item in ignore_items {
99a23c08
JW
573 if existing_items.contains(item) {
574 out.push('#');
db09895f 575 }
5a5d924c
AK
576 out.push_str(item);
577 out.push('\n');
99a23c08
JW
578 }
579
580 out
581 }
582}
583
f7c91ba6 584/// Writes the ignore file to the given directory. If the ignore file for the
99a23c08
JW
585/// given vcs system already exists, its content is read and duplicate ignore
586/// file entries are filtered out.
db09895f 587fn write_ignore_file(
588 base_path: &Path,
589 list: &IgnoreList,
590 vcs: VersionControl,
591) -> CargoResult<String> {
99a23c08
JW
592 let fp_ignore = match vcs {
593 VersionControl::Git => base_path.join(".gitignore"),
db09895f 594 VersionControl::Hg => base_path.join(".hgignore"),
595 VersionControl::Pijul => base_path.join(".ignore"),
99a23c08 596 VersionControl::Fossil => return Ok("".to_string()),
db09895f 597 VersionControl::NoVcs => return Ok("".to_string()),
99a23c08
JW
598 };
599
ce86e866
EH
600 let ignore: String = match paths::open(&fp_ignore) {
601 Err(err) => match err.downcast_ref::<std::io::Error>() {
602 Some(io_err) if io_err.kind() == ErrorKind::NotFound => list.format_new(vcs),
603 _ => return Err(err),
99a23c08 604 },
db09895f 605 Ok(file) => list.format_existing(BufReader::new(file), vcs),
99a23c08 606 };
0c26596a 607
99a23c08
JW
608 paths::append(&fp_ignore, ignore.as_bytes())?;
609
bce30836 610 Ok(ignore)
99a23c08
JW
611}
612
f7c91ba6 613/// Initializes the correct VCS system based on the provided config.
99a23c08 614fn init_vcs(path: &Path, vcs: VersionControl, config: &Config) -> CargoResult<()> {
16d3e72a
WW
615 match vcs {
616 VersionControl::Git => {
e8ea433c 617 if !path.join(".git").exists() {
2f12ab6f
EH
618 // Temporary fix to work around bug in libgit2 when creating a
619 // directory in the root of a posix filesystem.
620 // See: https://github.com/libgit2/libgit2/issues/5130
5102de2b 621 paths::create_dir_all(path)?;
82655b46 622 GitRepo::init(path, config.cwd())?;
800172fb 623 }
1e682848 624 }
16d3e72a 625 VersionControl::Hg => {
e8ea433c 626 if !path.join(".hg").exists() {
82655b46 627 HgRepo::init(path, config.cwd())?;
800172fb 628 }
1e682848 629 }
2b31978c 630 VersionControl::Pijul => {
e8ea433c 631 if !path.join(".pijul").exists() {
2b31978c
PW
632 PijulRepo::init(path, config.cwd())?;
633 }
1e682848 634 }
7eca7f27 635 VersionControl::Fossil => {
02fbf660 636 if !path.join(".fossil").exists() {
7eca7f27
DK
637 FossilRepo::init(path, config.cwd())?;
638 }
1e682848 639 }
16d3e72a 640 VersionControl::NoVcs => {
5102de2b 641 paths::create_dir_all(path)?;
1e682848 642 }
16d3e72a 643 };
a4272ef2 644
99a23c08
JW
645 Ok(())
646}
647
648fn mk(config: &Config, opts: &MkOptions<'_>) -> CargoResult<()> {
649 let path = opts.path;
650 let name = opts.name;
44a31d67 651 let cfg = config.get::<CargoNewConfig>("cargo-new")?;
99a23c08 652
f7c91ba6
AR
653 // Using the push method with two arguments ensures that the entries for
654 // both `ignore` and `hgignore` are in sync.
99a23c08
JW
655 let mut ignore = IgnoreList::new();
656 ignore.push("/target", "^target/");
99a23c08
JW
657 if !opts.bin {
658 ignore.push("Cargo.lock", "glob:Cargo.lock");
659 }
660
661 let vcs = opts.version_control.unwrap_or_else(|| {
662 let in_existing_vcs = existing_vcs_repo(path.parent().unwrap_or(path), config.cwd());
663 match (cfg.version_control, in_existing_vcs) {
664 (None, false) => VersionControl::Git,
665 (Some(opt), false) => opt,
666 (_, true) => VersionControl::NoVcs,
667 }
668 });
669
670 init_vcs(path, vcs, config)?;
671 write_ignore_file(path, &ignore, vcs)?;
672
7e66058a
EH
673 let mut cargotoml_path_specifier = String::new();
674
f7c91ba6 675 // Calculate what `[lib]` and `[[bin]]`s we need to append to `Cargo.toml`.
7e66058a
EH
676
677 for i in &opts.source_files {
678 if i.bin {
679 if i.relative_path != "src/main.rs" {
1e682848
AC
680 cargotoml_path_specifier.push_str(&format!(
681 r#"
7e66058a
EH
682[[bin]]
683name = "{}"
684path = {}
1e682848
AC
685"#,
686 i.target_name,
687 toml::Value::String(i.relative_path.clone())
688 ));
7e66058a 689 }
23591fe5 690 } else if i.relative_path != "src/lib.rs" {
1e682848
AC
691 cargotoml_path_specifier.push_str(&format!(
692 r#"
7e66058a
EH
693[lib]
694name = "{}"
695path = {}
1e682848
AC
696"#,
697 i.target_name,
698 toml::Value::String(i.relative_path.clone())
699 ));
7e66058a
EH
700 }
701 }
702
f7c91ba6 703 // Create `Cargo.toml` file with necessary `[lib]` and `[[bin]]` sections, if needed.
7e66058a 704
1e682848
AC
705 paths::write(
706 &path.join("Cargo.toml"),
707 format!(
708 r#"[package]
7e66058a
EH
709name = "{}"
710version = "0.1.0"
cf8d6a41 711edition = {}
1fd3f1b6 712{}
d7ffef77 713# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
714
7e66058a 715[dependencies]
1e682848
AC
716{}"#,
717 name,
3d029039 718 match opts.edition {
cf8d6a41 719 Some(edition) => toml::Value::String(edition.to_string()),
aa61976c 720 None => toml::Value::String(Edition::LATEST_STABLE.to_string()),
3d029039 721 },
edc7c887 722 match opts.registry {
7cd4a94d
E
723 Some(registry) => format!(
724 "publish = {}\n",
725 toml::Value::Array(vec!(toml::Value::String(registry.to_string())))
726 ),
1fd3f1b6 727 None => "".to_string(),
7cd4a94d 728 },
1e682848 729 cargotoml_path_specifier
7cd4a94d
E
730 )
731 .as_bytes(),
1e682848 732 )?;
7e66058a 733
f7c91ba6 734 // Create all specified source files (with respective parent directories) if they don't exist.
7e66058a
EH
735
736 for i in &opts.source_files {
737 let path_of_source_file = path.join(i.relative_path.clone());
738
739 if let Some(src_dir) = path_of_source_file.parent() {
5102de2b 740 paths::create_dir_all(src_dir)?;
800172fb 741 }
455f800c 742
1e682848 743 let default_file_content: &[u8] = if i.bin {
7e66058a
EH
744 b"\
745fn main() {
746 println!(\"Hello, world!\");
747}
748"
749 } else {
750 b"\
751#[cfg(test)]
752mod tests {
753 #[test]
7016687b
GB
754 fn it_works() {
755 assert_eq!(2 + 2, 4);
756 }
7e66058a
EH
757}
758"
759 };
760
4367ec4d 761 if !path_of_source_file.is_file() {
7e66058a 762 paths::write(&path_of_source_file, default_file_content)?;
aa7fe99c 763
63cb62df 764 // Format the newly created source file
768b60ab
K
765 match Command::new("rustfmt").arg(&path_of_source_file).output() {
766 Err(e) => log::warn!("failed to call rustfmt: {}", e),
767 Ok(output) => {
768 if !output.status.success() {
769 log::warn!("rustfmt failed: {:?}", from_utf8(&output.stdout));
770 }
771 }
772 };
7e66058a 773 }
a4272ef2
AC
774 }
775
58ddb28a 776 if let Err(e) = Workspace::new(&path.join("Cargo.toml"), config) {
b64d0f36 777 crate::display_warning_with_error(
fdb8ea1e 778 "compiling this new package may not work due to invalid \
b64d0f36
EH
779 workspace configuration",
780 &e,
781 &mut config.shell(),
1e682848 782 );
58ddb28a 783 }
875a8aba 784
7e66058a 785 Ok(())
875a8aba 786}