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