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