1use crate::core::{Edition, Shell, Workspace};
2use crate::util::errors::CargoResult;
3use crate::util::important_paths::find_root_manifest_for_wd;
4use crate::util::{existing_vcs_repo, FossilRepo, GitRepo, HgRepo, PijulRepo};
5use crate::util::{restricted_names, GlobalContext};
6use anyhow::{anyhow, Context as _};
7use cargo_util::paths::{self, write_atomic};
8use cargo_util_schemas::manifest::PackageName;
9use serde::de;
10use serde::Deserialize;
11use std::collections::BTreeMap;
12use std::ffi::OsStr;
13use std::io::{BufRead, BufReader, ErrorKind};
14use std::path::{Path, PathBuf};
15use std::str::FromStr;
16use std::{fmt, slice};
17use toml_edit::{Array, Value};
18
19#[derive(Clone, Copy, Debug, PartialEq)]
20pub enum VersionControl {
21 Git,
22 Hg,
23 Pijul,
24 Fossil,
25 NoVcs,
26}
27
28impl FromStr for VersionControl {
29 type Err = anyhow::Error;
30
31 fn from_str(s: &str) -> Result<Self, anyhow::Error> {
32 match s {
33 "git" => Ok(VersionControl::Git),
34 "hg" => Ok(VersionControl::Hg),
35 "pijul" => Ok(VersionControl::Pijul),
36 "fossil" => Ok(VersionControl::Fossil),
37 "none" => Ok(VersionControl::NoVcs),
38 other => anyhow::bail!("unknown vcs specification: `{}`", other),
39 }
40 }
41}
42
43impl<'de> de::Deserialize<'de> for VersionControl {
44 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
45 where
46 D: de::Deserializer<'de>,
47 {
48 let s = String::deserialize(deserializer)?;
49 FromStr::from_str(&s).map_err(de::Error::custom)
50 }
51}
52
53#[derive(Debug)]
54pub struct NewOptions {
55 pub version_control: Option<VersionControl>,
56 pub kind: NewProjectKind,
57 pub auto_detect_kind: bool,
58 pub path: PathBuf,
60 pub name: Option<String>,
61 pub edition: Option<String>,
62 pub registry: Option<String>,
63}
64
65#[derive(Clone, Copy, Debug, PartialEq, Eq)]
66pub enum NewProjectKind {
67 Bin,
68 Lib,
69}
70
71impl NewProjectKind {
72 fn is_bin(self) -> bool {
73 self == NewProjectKind::Bin
74 }
75}
76
77impl fmt::Display for NewProjectKind {
78 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79 match *self {
80 NewProjectKind::Bin => "binary (application)",
81 NewProjectKind::Lib => "library",
82 }
83 .fmt(f)
84 }
85}
86
87struct SourceFileInformation {
88 relative_path: String,
89 bin: bool,
90}
91
92struct MkOptions<'a> {
93 version_control: Option<VersionControl>,
94 path: &'a Path,
95 name: &'a str,
96 source_files: Vec<SourceFileInformation>,
97 edition: Option<&'a str>,
98 registry: Option<&'a str>,
99}
100
101impl NewOptions {
102 pub fn new(
103 version_control: Option<VersionControl>,
104 bin: bool,
105 lib: bool,
106 path: PathBuf,
107 name: Option<String>,
108 edition: Option<String>,
109 registry: Option<String>,
110 ) -> CargoResult<NewOptions> {
111 let auto_detect_kind = !bin && !lib;
112
113 let kind = match (bin, lib) {
114 (true, true) => anyhow::bail!("can't specify both lib and binary outputs"),
115 (false, true) => NewProjectKind::Lib,
116 (_, false) => NewProjectKind::Bin,
117 };
118
119 let opts = NewOptions {
120 version_control,
121 kind,
122 auto_detect_kind,
123 path,
124 name,
125 edition,
126 registry,
127 };
128 Ok(opts)
129 }
130}
131
132#[derive(Deserialize)]
133#[serde(rename_all = "kebab-case")]
134struct CargoNewConfig {
135 #[deprecated = "cargo-new no longer supports adding the authors field"]
136 #[allow(dead_code)]
137 name: Option<String>,
138
139 #[deprecated = "cargo-new no longer supports adding the authors field"]
140 #[allow(dead_code)]
141 email: Option<String>,
142
143 #[serde(rename = "vcs")]
144 version_control: Option<VersionControl>,
145}
146
147fn get_name<'a>(path: &'a Path, opts: &'a NewOptions) -> CargoResult<&'a str> {
148 if let Some(ref name) = opts.name {
149 return Ok(name);
150 }
151
152 let file_name = path.file_name().ok_or_else(|| {
153 anyhow::format_err!(
154 "cannot auto-detect package name from path {:?} ; use --name to override",
155 path.as_os_str()
156 )
157 })?;
158
159 file_name.to_str().ok_or_else(|| {
160 anyhow::format_err!(
161 "cannot create package with a non-unicode name: {:?}",
162 file_name
163 )
164 })
165}
166
167fn check_name(
169 name: &str,
170 show_name_help: bool,
171 has_bin: bool,
172 shell: &mut Shell,
173) -> CargoResult<()> {
174 let name_help = if show_name_help {
177 "\nIf you need a package name to not match the directory name, consider using --name flag."
178 } else {
179 ""
180 };
181 let bin_help = || {
182 let mut help = String::from(name_help);
183 if has_bin && !name.is_empty() {
184 help.push_str(&format!(
185 "\n\
186 If you need a binary with the name \"{name}\", use a valid package \
187 name, and set the binary name to be different from the package. \
188 This can be done by setting the binary filename to `src/bin/{name}.rs` \
189 or change the name in Cargo.toml with:\n\
190 \n \
191 [[bin]]\n \
192 name = \"{name}\"\n \
193 path = \"src/main.rs\"\n\
194 ",
195 name = name
196 ));
197 }
198 help
199 };
200 PackageName::new(name).map_err(|err| {
201 let help = bin_help();
202 anyhow::anyhow!("{err}{help}")
203 })?;
204
205 if restricted_names::is_keyword(name) {
206 anyhow::bail!(
207 "the name `{}` cannot be used as a package name, it is a Rust keyword{}",
208 name,
209 bin_help()
210 );
211 }
212 if restricted_names::is_conflicting_artifact_name(name) {
213 if has_bin {
214 anyhow::bail!(
215 "the name `{}` cannot be used as a package name, \
216 it conflicts with cargo's build directory names{}",
217 name,
218 name_help
219 );
220 } else {
221 shell.warn(format!(
222 "the name `{}` will not support binary \
223 executables with that name, \
224 it conflicts with cargo's build directory names",
225 name
226 ))?;
227 }
228 }
229 if name == "test" {
230 anyhow::bail!(
231 "the name `test` cannot be used as a package name, \
232 it conflicts with Rust's built-in test library{}",
233 bin_help()
234 );
235 }
236 if ["core", "std", "alloc", "proc_macro", "proc-macro"].contains(&name) {
237 shell.warn(format!(
238 "the name `{}` is part of Rust's standard library\n\
239 It is recommended to use a different name to avoid problems.{}",
240 name,
241 bin_help()
242 ))?;
243 }
244 if restricted_names::is_windows_reserved(name) {
245 if cfg!(windows) {
246 anyhow::bail!(
247 "cannot use name `{}`, it is a reserved Windows filename{}",
248 name,
249 name_help
250 );
251 } else {
252 shell.warn(format!(
253 "the name `{}` is a reserved Windows filename\n\
254 This package will not work on Windows platforms.",
255 name
256 ))?;
257 }
258 }
259 if restricted_names::is_non_ascii_name(name) {
260 shell.warn(format!(
261 "the name `{}` contains non-ASCII characters\n\
262 Non-ASCII crate names are not supported by Rust.",
263 name
264 ))?;
265 }
266 let name_in_lowercase = name.to_lowercase();
267 if name != name_in_lowercase {
268 shell.warn(format!(
269 "the name `{name}` is not snake_case or kebab-case which is recommended for package names, consider `{name_in_lowercase}`"
270 ))?;
271 }
272
273 Ok(())
274}
275
276fn check_path(path: &Path, shell: &mut Shell) -> CargoResult<()> {
278 if let Err(_) = paths::join_paths(slice::from_ref(&OsStr::new(path)), "") {
280 let path = path.to_string_lossy();
281 shell.warn(format!(
282 "the path `{path}` contains invalid PATH characters (usually `:`, `;`, or `\"`)\n\
283 It is recommended to use a different name to avoid problems."
284 ))?;
285 }
286 Ok(())
287}
288
289fn detect_source_paths_and_types(
290 package_path: &Path,
291 package_name: &str,
292 detected_files: &mut Vec<SourceFileInformation>,
293) -> CargoResult<()> {
294 let path = package_path;
295 let name = package_name;
296
297 enum H {
298 Bin,
299 Lib,
300 Detect,
301 }
302
303 struct Test {
304 proposed_path: String,
305 handling: H,
306 }
307
308 let tests = vec![
309 Test {
310 proposed_path: "src/main.rs".to_string(),
311 handling: H::Bin,
312 },
313 Test {
314 proposed_path: "main.rs".to_string(),
315 handling: H::Bin,
316 },
317 Test {
318 proposed_path: format!("src/{}.rs", name),
319 handling: H::Detect,
320 },
321 Test {
322 proposed_path: format!("{}.rs", name),
323 handling: H::Detect,
324 },
325 Test {
326 proposed_path: "src/lib.rs".to_string(),
327 handling: H::Lib,
328 },
329 Test {
330 proposed_path: "lib.rs".to_string(),
331 handling: H::Lib,
332 },
333 ];
334
335 for i in tests {
336 let pp = i.proposed_path;
337
338 if !path.join(&pp).is_file() {
340 continue;
341 }
342
343 let sfi = match i.handling {
344 H::Bin => SourceFileInformation {
345 relative_path: pp,
346 bin: true,
347 },
348 H::Lib => SourceFileInformation {
349 relative_path: pp,
350 bin: false,
351 },
352 H::Detect => {
353 let content = paths::read(&path.join(pp.clone()))?;
354 let isbin = content.contains("fn main");
355 SourceFileInformation {
356 relative_path: pp,
357 bin: isbin,
358 }
359 }
360 };
361 detected_files.push(sfi);
362 }
363
364 let mut previous_lib_relpath: Option<&str> = None;
367 let mut duplicates_checker: BTreeMap<&str, &SourceFileInformation> = BTreeMap::new();
368
369 for i in detected_files {
370 if i.bin {
371 if let Some(x) = BTreeMap::get::<str>(&duplicates_checker, &name) {
372 anyhow::bail!(
373 "\
374multiple possible binary sources found:
375 {}
376 {}
377cannot automatically generate Cargo.toml as the main target would be ambiguous",
378 &x.relative_path,
379 &i.relative_path
380 );
381 }
382 duplicates_checker.insert(name, i);
383 } else {
384 if let Some(plp) = previous_lib_relpath {
385 anyhow::bail!(
386 "cannot have a package with \
387 multiple libraries, \
388 found both `{}` and `{}`",
389 plp,
390 i.relative_path
391 )
392 }
393 previous_lib_relpath = Some(&i.relative_path);
394 }
395 }
396
397 Ok(())
398}
399
400fn plan_new_source_file(bin: bool) -> SourceFileInformation {
401 if bin {
402 SourceFileInformation {
403 relative_path: "src/main.rs".to_string(),
404 bin: true,
405 }
406 } else {
407 SourceFileInformation {
408 relative_path: "src/lib.rs".to_string(),
409 bin: false,
410 }
411 }
412}
413
414fn calculate_new_project_kind(
415 requested_kind: NewProjectKind,
416 auto_detect_kind: bool,
417 found_files: &Vec<SourceFileInformation>,
418) -> NewProjectKind {
419 let bin_file = found_files.iter().find(|x| x.bin);
420
421 let kind_from_files = if !found_files.is_empty() && bin_file.is_none() {
422 NewProjectKind::Lib
423 } else {
424 NewProjectKind::Bin
425 };
426
427 if auto_detect_kind {
428 return kind_from_files;
429 }
430
431 requested_kind
432}
433
434pub fn new(opts: &NewOptions, gctx: &GlobalContext) -> CargoResult<()> {
435 let path = &opts.path;
436 let name = get_name(path, opts)?;
437 gctx.shell()
438 .status("Creating", format!("{} `{}` package", opts.kind, name))?;
439
440 if path.exists() {
441 anyhow::bail!(
442 "destination `{}` already exists\n\n\
443 Use `cargo init` to initialize the directory",
444 path.display()
445 )
446 }
447 check_path(path, &mut gctx.shell())?;
448
449 let is_bin = opts.kind.is_bin();
450
451 check_name(name, opts.name.is_none(), is_bin, &mut gctx.shell())?;
452
453 let mkopts = MkOptions {
454 version_control: opts.version_control,
455 path,
456 name,
457 source_files: vec![plan_new_source_file(opts.kind.is_bin())],
458 edition: opts.edition.as_deref(),
459 registry: opts.registry.as_deref(),
460 };
461
462 mk(gctx, &mkopts).with_context(|| {
463 format!(
464 "Failed to create package `{}` at `{}`",
465 name,
466 path.display()
467 )
468 })?;
469 Ok(())
470}
471
472pub fn init(opts: &NewOptions, gctx: &GlobalContext) -> CargoResult<NewProjectKind> {
473 if gctx.get_env_os("__CARGO_TEST_INTERNAL_ERROR").is_some() {
475 return Err(crate::util::internal("internal error test"));
476 }
477
478 let path = &opts.path;
479 let name = get_name(path, opts)?;
480 let mut src_paths_types = vec![];
481 detect_source_paths_and_types(path, name, &mut src_paths_types)?;
482 let kind = calculate_new_project_kind(opts.kind, opts.auto_detect_kind, &src_paths_types);
483 gctx.shell()
484 .status("Creating", format!("{} package", opts.kind))?;
485
486 if path.join("Cargo.toml").exists() {
487 anyhow::bail!("`cargo init` cannot be run on existing Cargo packages")
488 }
489 check_path(path, &mut gctx.shell())?;
490
491 let has_bin = kind.is_bin();
492
493 if src_paths_types.is_empty() {
494 src_paths_types.push(plan_new_source_file(has_bin));
495 } else if src_paths_types.len() == 1 && !src_paths_types.iter().any(|x| x.bin == has_bin) {
496 let file_type = if src_paths_types[0].bin {
498 NewProjectKind::Bin
499 } else {
500 NewProjectKind::Lib
501 };
502 gctx.shell().warn(format!(
503 "file `{}` seems to be a {} file",
504 src_paths_types[0].relative_path, file_type
505 ))?;
506 src_paths_types[0].bin = has_bin
507 } else if src_paths_types.len() > 1 && !has_bin {
508 anyhow::bail!(
510 "cannot have a package with \
511 multiple libraries, \
512 found both `{}` and `{}`",
513 src_paths_types[0].relative_path,
514 src_paths_types[1].relative_path
515 )
516 }
517
518 check_name(name, opts.name.is_none(), has_bin, &mut gctx.shell())?;
519
520 let mut version_control = opts.version_control;
521
522 if version_control == None {
523 let mut num_detected_vcses = 0;
524
525 if path.join(".git").exists() {
526 version_control = Some(VersionControl::Git);
527 num_detected_vcses += 1;
528 }
529
530 if path.join(".hg").exists() {
531 version_control = Some(VersionControl::Hg);
532 num_detected_vcses += 1;
533 }
534
535 if path.join(".pijul").exists() {
536 version_control = Some(VersionControl::Pijul);
537 num_detected_vcses += 1;
538 }
539
540 if path.join(".fossil").exists() {
541 version_control = Some(VersionControl::Fossil);
542 num_detected_vcses += 1;
543 }
544
545 if num_detected_vcses > 1 {
548 anyhow::bail!(
549 "more than one of .hg, .git, .pijul, .fossil configurations \
550 found and the ignore file can't be filled in as \
551 a result. specify --vcs to override detection"
552 );
553 }
554 }
555
556 let mkopts = MkOptions {
557 version_control,
558 path,
559 name,
560 source_files: src_paths_types,
561 edition: opts.edition.as_deref(),
562 registry: opts.registry.as_deref(),
563 };
564
565 mk(gctx, &mkopts).with_context(|| {
566 format!(
567 "Failed to create package `{}` at `{}`",
568 name,
569 path.display()
570 )
571 })?;
572 Ok(kind)
573}
574
575struct IgnoreList {
577 ignore: Vec<String>,
579 hg_ignore: Vec<String>,
581 fossil_ignore: Vec<String>,
583}
584
585impl IgnoreList {
586 fn new() -> IgnoreList {
588 IgnoreList {
589 ignore: Vec::new(),
590 hg_ignore: Vec::new(),
591 fossil_ignore: Vec::new(),
592 }
593 }
594
595 fn push(&mut self, ignore: &str, hg_ignore: &str, fossil_ignore: &str) {
599 self.ignore.push(ignore.to_string());
600 self.hg_ignore.push(hg_ignore.to_string());
601 self.fossil_ignore.push(fossil_ignore.to_string());
602 }
603
604 fn format_new(&self, vcs: VersionControl) -> String {
607 let ignore_items = match vcs {
608 VersionControl::Hg => &self.hg_ignore,
609 VersionControl::Fossil => &self.fossil_ignore,
610 _ => &self.ignore,
611 };
612
613 ignore_items.join("\n") + "\n"
614 }
615
616 fn format_existing<T: BufRead>(&self, existing: T, vcs: VersionControl) -> CargoResult<String> {
621 let mut existing_items = Vec::new();
622 for (i, item) in existing.lines().enumerate() {
623 match item {
624 Ok(s) => existing_items.push(s),
625 Err(err) => match err.kind() {
626 ErrorKind::InvalidData => {
627 return Err(anyhow!(
628 "Character at line {} is invalid. Cargo only supports UTF-8.",
629 i
630 ))
631 }
632 _ => return Err(anyhow!(err)),
633 },
634 }
635 }
636
637 let ignore_items = match vcs {
638 VersionControl::Hg => &self.hg_ignore,
639 VersionControl::Fossil => &self.fossil_ignore,
640 _ => &self.ignore,
641 };
642
643 let mut out = String::new();
644
645 if vcs != VersionControl::Fossil {
647 out.push_str("\n\n# Added by cargo\n");
648 if ignore_items
649 .iter()
650 .any(|item| existing_items.contains(item))
651 {
652 out.push_str("#\n# already existing elements were commented out\n");
653 }
654 out.push('\n');
655 }
656
657 for item in ignore_items {
658 if existing_items.contains(item) {
659 if vcs == VersionControl::Fossil {
660 continue;
662 }
663 out.push('#');
664 }
665 out.push_str(item);
666 out.push('\n');
667 }
668
669 Ok(out)
670 }
671}
672
673fn write_ignore_file(base_path: &Path, list: &IgnoreList, vcs: VersionControl) -> CargoResult<()> {
677 if vcs == VersionControl::Fossil {
679 paths::create_dir_all(base_path.join(".fossil-settings"))?;
680 }
681
682 for fp_ignore in match vcs {
683 VersionControl::Git => vec![base_path.join(".gitignore")],
684 VersionControl::Hg => vec![base_path.join(".hgignore")],
685 VersionControl::Pijul => vec![base_path.join(".ignore")],
686 VersionControl::Fossil => vec![
688 base_path.join(".fossil-settings/ignore-glob"),
689 base_path.join(".fossil-settings/clean-glob"),
690 ],
691 VersionControl::NoVcs => return Ok(()),
692 } {
693 let ignore: String = match paths::open(&fp_ignore) {
694 Err(err) => match err.downcast_ref::<std::io::Error>() {
695 Some(io_err) if io_err.kind() == ErrorKind::NotFound => list.format_new(vcs),
696 _ => return Err(err),
697 },
698 Ok(file) => list.format_existing(BufReader::new(file), vcs)?,
699 };
700
701 paths::append(&fp_ignore, ignore.as_bytes())?;
702 }
703
704 Ok(())
705}
706
707fn init_vcs(path: &Path, vcs: VersionControl, gctx: &GlobalContext) -> CargoResult<()> {
709 match vcs {
710 VersionControl::Git => {
711 if !path.join(".git").exists() {
712 paths::create_dir_all(path)?;
716 GitRepo::init(path, gctx.cwd())?;
717 }
718 }
719 VersionControl::Hg => {
720 if !path.join(".hg").exists() {
721 HgRepo::init(path, gctx.cwd())?;
722 }
723 }
724 VersionControl::Pijul => {
725 if !path.join(".pijul").exists() {
726 PijulRepo::init(path, gctx.cwd())?;
727 }
728 }
729 VersionControl::Fossil => {
730 if !path.join(".fossil").exists() {
731 FossilRepo::init(path, gctx.cwd())?;
732 }
733 }
734 VersionControl::NoVcs => {
735 paths::create_dir_all(path)?;
736 }
737 };
738
739 Ok(())
740}
741
742fn mk(gctx: &GlobalContext, opts: &MkOptions<'_>) -> CargoResult<()> {
743 let path = opts.path;
744 let name = opts.name;
745 let cfg = gctx.get::<CargoNewConfig>("cargo-new")?;
746
747 let mut ignore = IgnoreList::new();
750 ignore.push("/target", "^target$", "target");
751
752 let vcs = opts.version_control.unwrap_or_else(|| {
753 let in_existing_vcs = existing_vcs_repo(path.parent().unwrap_or(path), gctx.cwd());
754 match (cfg.version_control, in_existing_vcs) {
755 (None, false) => VersionControl::Git,
756 (Some(opt), false) => opt,
757 (_, true) => VersionControl::NoVcs,
758 }
759 });
760
761 init_vcs(path, vcs, gctx)?;
762 write_ignore_file(path, &ignore, vcs)?;
763
764 let mut manifest = toml_edit::DocumentMut::new();
766 manifest["package"] = toml_edit::Item::Table(toml_edit::Table::new());
767 manifest["package"]["name"] = toml_edit::value(name);
768 manifest["package"]["version"] = toml_edit::value("0.1.0");
769 let edition = match opts.edition {
770 Some(edition) => edition.to_string(),
771 None => Edition::LATEST_STABLE.to_string(),
772 };
773 manifest["package"]["edition"] = toml_edit::value(edition);
774 if let Some(registry) = opts.registry {
775 let mut array = toml_edit::Array::default();
776 array.push(registry);
777 manifest["package"]["publish"] = toml_edit::value(array);
778 }
779 let dep_table = toml_edit::Table::default();
780 manifest["dependencies"] = toml_edit::Item::Table(dep_table);
781
782 for i in &opts.source_files {
784 if i.bin {
785 if i.relative_path != "src/main.rs" {
786 let mut bin = toml_edit::Table::new();
787 bin["name"] = toml_edit::value(name);
788 bin["path"] = toml_edit::value(i.relative_path.clone());
789 manifest["bin"]
790 .or_insert(toml_edit::Item::ArrayOfTables(
791 toml_edit::ArrayOfTables::new(),
792 ))
793 .as_array_of_tables_mut()
794 .expect("bin is an array of tables")
795 .push(bin);
796 }
797 } else if i.relative_path != "src/lib.rs" {
798 let mut lib = toml_edit::Table::new();
799 lib["path"] = toml_edit::value(i.relative_path.clone());
800 manifest["lib"] = toml_edit::Item::Table(lib);
801 }
802 }
803
804 let manifest_path = paths::normalize_path(&path.join("Cargo.toml"));
805 if let Ok(root_manifest_path) = find_root_manifest_for_wd(&manifest_path) {
806 let root_manifest = paths::read(&root_manifest_path)?;
807 if let Ok(mut workspace_document) = root_manifest.parse::<toml_edit::DocumentMut>() {
811 let display_path = get_display_path(&root_manifest_path, &path)?;
812 let can_be_a_member = can_be_workspace_member(&display_path, &workspace_document)?;
813 if can_be_a_member {
815 if let Some(workspace_package_keys) = workspace_document
816 .get("workspace")
817 .and_then(|workspace| workspace.get("package"))
818 .and_then(|package| package.as_table())
819 {
820 update_manifest_with_inherited_workspace_package_keys(
821 opts,
822 &mut manifest,
823 workspace_package_keys,
824 )
825 }
826 if workspace_document
828 .get("workspace")
829 .and_then(|workspace| workspace.get("lints"))
830 .is_some()
831 {
832 let mut table = toml_edit::Table::new();
833 table["workspace"] = toml_edit::value(true);
834 manifest["lints"] = toml_edit::Item::Table(table);
835 }
836
837 if update_manifest_with_new_member(
839 &root_manifest_path,
840 &mut workspace_document,
841 &display_path,
842 )? {
843 gctx.shell().status(
844 "Adding",
845 format!(
846 "`{}` as member of workspace at `{}`",
847 PathBuf::from(&display_path)
848 .file_name()
849 .unwrap()
850 .to_str()
851 .unwrap(),
852 root_manifest_path.parent().unwrap().display()
853 ),
854 )?
855 }
856 }
857 }
858 }
859
860 paths::write(&manifest_path, manifest.to_string())?;
861
862 for i in &opts.source_files {
864 let path_of_source_file = path.join(i.relative_path.clone());
865
866 if let Some(src_dir) = path_of_source_file.parent() {
867 paths::create_dir_all(src_dir)?;
868 }
869
870 let default_file_content: &[u8] = if i.bin {
871 b"\
872fn main() {
873 println!(\"Hello, world!\");
874}
875"
876 } else {
877 b"\
878pub fn add(left: u64, right: u64) -> u64 {
879 left + right
880}
881
882#[cfg(test)]
883mod tests {
884 use super::*;
885
886 #[test]
887 fn it_works() {
888 let result = add(2, 2);
889 assert_eq!(result, 4);
890 }
891}
892"
893 };
894
895 if !path_of_source_file.is_file() {
896 paths::write(&path_of_source_file, default_file_content)?;
897
898 if let Err(e) = cargo_util::ProcessBuilder::new("rustfmt")
900 .arg(&path_of_source_file)
901 .exec_with_output()
902 {
903 tracing::warn!("failed to call rustfmt: {:#}", e);
904 }
905 }
906 }
907
908 if let Err(e) = Workspace::new(&manifest_path, gctx) {
909 crate::display_warning_with_error(
910 "compiling this new package may not work due to invalid \
911 workspace configuration",
912 &e,
913 &mut gctx.shell(),
914 );
915 }
916
917 gctx.shell().note(
918 "see more `Cargo.toml` keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html",
919 )?;
920
921 Ok(())
922}
923
924fn update_manifest_with_inherited_workspace_package_keys(
928 opts: &MkOptions<'_>,
929 manifest: &mut toml_edit::DocumentMut,
930 workspace_package_keys: &toml_edit::Table,
931) {
932 if workspace_package_keys.is_empty() {
933 return;
934 }
935
936 let try_remove_and_inherit_package_key = |key: &str, manifest: &mut toml_edit::DocumentMut| {
937 let package = manifest["package"]
938 .as_table_mut()
939 .expect("package is a table");
940 package.remove(key);
941 let mut table = toml_edit::Table::new();
942 table.set_dotted(true);
943 table["workspace"] = toml_edit::value(true);
944 package.insert(key, toml_edit::Item::Table(table));
945 };
946
947 for (key, _) in workspace_package_keys {
950 if key == "edition" && opts.edition.is_some() {
951 continue;
952 }
953 if key == "publish" && opts.registry.is_some() {
954 continue;
955 }
956
957 try_remove_and_inherit_package_key(key, manifest);
958 }
959}
960
961fn update_manifest_with_new_member(
969 root_manifest_path: &Path,
970 workspace_document: &mut toml_edit::DocumentMut,
971 display_path: &str,
972) -> CargoResult<bool> {
973 let Some(workspace) = workspace_document.get_mut("workspace") else {
974 return Ok(false);
975 };
976
977 if let Some(members) = workspace
982 .get_mut("members")
983 .and_then(|members| members.as_array_mut())
984 {
985 for member in members.iter() {
986 let pat = member
987 .as_str()
988 .with_context(|| format!("invalid non-string member `{}`", member))?;
989 let pattern = glob::Pattern::new(pat)
990 .with_context(|| format!("cannot build glob pattern from `{}`", pat))?;
991
992 if pattern.matches(&display_path) {
993 return Ok(false);
994 }
995 }
996
997 let was_sorted = members.iter().map(Value::as_str).is_sorted();
998 members.push(display_path);
999 if was_sorted {
1000 members.sort_by(|lhs, rhs| lhs.as_str().cmp(&rhs.as_str()));
1001 }
1002 } else {
1003 let mut array = Array::new();
1004 array.push(display_path);
1005
1006 workspace["members"] = toml_edit::value(array);
1007 }
1008
1009 write_atomic(
1010 &root_manifest_path,
1011 workspace_document.to_string().to_string().as_bytes(),
1012 )?;
1013 Ok(true)
1014}
1015
1016fn get_display_path(root_manifest_path: &Path, package_path: &Path) -> CargoResult<String> {
1017 let workspace_root = root_manifest_path.parent().with_context(|| {
1019 format!(
1020 "workspace root manifest doesn't have a parent directory `{}`",
1021 root_manifest_path.display()
1022 )
1023 })?;
1024 let relpath = pathdiff::diff_paths(package_path, workspace_root).with_context(|| {
1025 format!(
1026 "path comparison requires two absolute paths; package_path: `{}`, workspace_path: `{}`",
1027 package_path.display(),
1028 workspace_root.display()
1029 )
1030 })?;
1031
1032 let mut components = Vec::new();
1033 for comp in relpath.iter() {
1034 let comp = comp.to_str().with_context(|| {
1035 format!("invalid unicode component in path `{}`", relpath.display())
1036 })?;
1037 components.push(comp);
1038 }
1039 let display_path = components.join("/");
1040 Ok(display_path)
1041}
1042
1043fn can_be_workspace_member(
1045 display_path: &str,
1046 workspace_document: &toml_edit::DocumentMut,
1047) -> CargoResult<bool> {
1048 if let Some(exclude) = workspace_document
1049 .get("workspace")
1050 .and_then(|workspace| workspace.get("exclude"))
1051 .and_then(|exclude| exclude.as_array())
1052 {
1053 for member in exclude {
1054 let pat = member
1055 .as_str()
1056 .with_context(|| format!("invalid non-string exclude path `{}`", member))?;
1057 if pat == display_path {
1058 return Ok(false);
1059 }
1060 }
1061 }
1062 Ok(true)
1063}