#
cargo/ops/
cargo_new.rs

    1use crate::core::{Edition, Shell, Workspace};
    2use crate::util::errors::CargoResult;
    3use crate::util::important_paths::find_root_manifest_for_wd;
    4use crate::util::{FossilRepo, GitRepo, HgRepo, PijulRepo, existing_vcs_repo};
    5use crate::util::{GlobalContext, restricted_names};
    6use anyhow::{Context as _, anyhow};
    7use cargo_util::paths::{self, write_atomic};
    8use cargo_util_schemas::manifest::PackageName;
    9use serde::Deserialize;
    10use serde::de;
    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    /// Absolute path to the directory for the new package
    59    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
    167/// See also `util::toml::embedded::sanitize_name`
    168fn check_name(
    169    name: &str,
    170    show_name_help: bool,
    171    has_bin: bool,
    172    shell: &mut Shell,
    173) -> CargoResult<()> {
    174    // If --name is already used to override, no point in suggesting it
    175    // again as a fix.
    176    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
    276/// Checks if the path contains any invalid PATH env characters.
    277fn check_path(path: &Path, shell: &mut Shell) -> CargoResult<()> {
    278    // warn if the path contains characters that will break `env::join_paths`
    279    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        // path/pp does not exist or is not a file
    339        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    // Check for duplicate lib attempt
    365
    366    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    // This is here just as a random location to exercise the internal error handling.
    474    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        // we've found the only file and it's not the type user wants. Change the type and warn
    497        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        // We have found both lib and bin files and the user would like us to treat both as libs
    509        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 none exists, maybe create git, like in `cargo new`
    546
    547        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
    575/// `IgnoreList`
    576struct IgnoreList {
    577    /// git like formatted entries
    578    ignore: Vec<String>,
    579    /// mercurial formatted entries
    580    hg_ignore: Vec<String>,
    581    /// Fossil-formatted entries.
    582    fossil_ignore: Vec<String>,
    583}
    584
    585impl IgnoreList {
    586    /// constructor to build a new ignore file
    587    fn new() -> IgnoreList {
    588        IgnoreList {
    589            ignore: Vec::new(),
    590            hg_ignore: Vec::new(),
    591            fossil_ignore: Vec::new(),
    592        }
    593    }
    594
    595    /// Add a new entry to the ignore list. Requires three arguments with the
    596    /// entry in possibly three different formats. One for "git style" entries,
    597    /// one for "mercurial style" entries and one for "fossil style" entries.
    598    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    /// Return the correctly formatted content of the ignore file for the given
    605    /// version control system as `String`.
    606    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    /// `format_existing` is used to format the `IgnoreList` when the ignore file
    617    /// already exists. It reads the contents of the given `BufRead` and
    618    /// checks if the contents of the ignore list are already existing in the
    619    /// file.
    620    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        // Fossil does not support `#` comments.
    646        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                    // Just merge for Fossil.
    661                    continue;
    662                }
    663                out.push('#');
    664            }
    665            out.push_str(item);
    666            out.push('\n');
    667        }
    668
    669        Ok(out)
    670    }
    671}
    672
    673/// Writes the ignore file to the given directory. If the ignore file for the
    674/// given vcs system already exists, its content is read and duplicate ignore
    675/// file entries are filtered out.
    676fn write_ignore_file(base_path: &Path, list: &IgnoreList, vcs: VersionControl) -> CargoResult<()> {
    677    // Fossil only supports project-level settings in a dedicated subdirectory.
    678    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        // Fossil has a cleaning functionality configured in a separate file.
    687        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
    707/// Initializes the correct VCS system based on the provided config.
    708fn init_vcs(path: &Path, vcs: VersionControl, gctx: &GlobalContext) -> CargoResult<()> {
    709    match vcs {
    710        VersionControl::Git => {
    711            if !path.join(".git").exists() {
    712                // Temporary fix to work around bug in libgit2 when creating a
    713                // directory in the root of a posix filesystem.
    714                // See: https://github.com/libgit2/libgit2/issues/5130
    715                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    // Using the push method with multiple arguments ensures that the entries
    748    // for all mutually-incompatible VCS in terms of syntax are in sync.
    749    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    // Create `Cargo.toml` file with necessary `[lib]` and `[[bin]]` sections, if needed.
    765    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    // Calculate what `[lib]` and `[[bin]]`s we need to append to `Cargo.toml`.
    783    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        // Sometimes the root manifest is not a valid manifest, so we only try to parse it if it is.
    808        // This should not block the creation of the new project. It is only a best effort to
    809        // inherit the workspace package keys.
    810        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            // Only try to inherit the workspace stuff if the new package can be a member of the workspace.
    814            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                // Try to inherit the workspace lints key if it exists.
    827                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                // Try to add the new package to the workspace members.
    838                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    // Create all specified source files (with respective parent directories) if they don't exist.
    863    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            // Format the newly created source file
    899            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
    924// Update the manifest with the inherited workspace package keys.
    925// If the option is not set, the key is removed from the manifest.
    926// If the option is set, keep the value from the manifest.
    927fn 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    // Inherit keys from the workspace.
    948    // Only keep the value from the manifest if the option is set.
    949    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
    961/// Adds the new package member to the [workspace.members] array.
    962/// - It first checks if the name matches any element in [workspace.exclude],
    963///  and it ignores the name if there is a match.
    964/// - Then it check if the name matches any element already in [workspace.members],
    965/// and it ignores the name if there is a match.
    966/// - If [workspace.members] doesn't exist in the manifest, it will add a new section
    967/// with the new package in it.
    968fn 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 the members element already exist, check if one of the patterns
    978    // in the array already includes the new package's relative path.
    979    // - Add the relative path if the members don't match the new package's path.
    980    // - Create a new members array if there are no members element in the workspace yet.
    981    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    // Find the relative path for the package from the workspace root directory.
    1018    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
    1043// Check if the package can be a member of the workspace.
    1044fn 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}