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