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