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