Skip to content

Commit 2ca2b1f

Browse files
committed
Implement cargo new --proc-macro and cargo init --proc-macro
1 parent 04106b5 commit 2ca2b1f

File tree

19 files changed

+388
-98
lines changed

19 files changed

+388
-98
lines changed

src/cargo/ops/cargo_new.rs

Lines changed: 101 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use crate::util::errors::CargoResult;
33
use crate::util::important_paths::find_root_manifest_for_wd;
44
use crate::util::{FossilRepo, GitRepo, HgRepo, PijulRepo, existing_vcs_repo};
55
use crate::util::{GlobalContext, restricted_names};
6-
use anyhow::{Context as _, anyhow};
6+
use anyhow::{Context as _, anyhow, bail};
77
use cargo_util::paths::{self, write_atomic};
88
use cargo_util_schemas::manifest::PackageName;
99
use serde::Deserialize;
@@ -65,28 +65,32 @@ pub struct NewOptions {
6565
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
6666
pub enum NewProjectKind {
6767
Bin,
68-
Lib,
68+
Lib { proc_macro: bool },
6969
}
7070

7171
impl NewProjectKind {
7272
fn is_bin(self) -> bool {
7373
self == NewProjectKind::Bin
7474
}
75+
fn is_proc_macro(self) -> bool {
76+
self == NewProjectKind::Lib { proc_macro: true }
77+
}
7578
}
7679

7780
impl fmt::Display for NewProjectKind {
7881
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
7982
match *self {
8083
NewProjectKind::Bin => "binary (application)",
81-
NewProjectKind::Lib => "library",
84+
NewProjectKind::Lib { proc_macro: false } => "library",
85+
NewProjectKind::Lib { proc_macro: true } => "library (proc-macro)",
8286
}
8387
.fmt(f)
8488
}
8589
}
8690

8791
struct SourceFileInformation {
8892
relative_path: String,
89-
bin: bool,
93+
kind: NewProjectKind,
9094
}
9195

9296
struct MkOptions<'a> {
@@ -103,17 +107,24 @@ impl NewOptions {
103107
version_control: Option<VersionControl>,
104108
bin: bool,
105109
lib: bool,
110+
proc_macro: bool,
106111
path: PathBuf,
107112
name: Option<String>,
108113
edition: Option<String>,
109114
registry: Option<String>,
110115
) -> 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,
116+
let auto_detect_kind = !bin && !lib && !proc_macro;
117+
118+
let kind = match (bin, lib, proc_macro) {
119+
(true, false, false) | (false, false, false) => NewProjectKind::Bin,
120+
(false, true, false) => NewProjectKind::Lib { proc_macro: false },
121+
(false, false, true) => NewProjectKind::Lib { proc_macro: true },
122+
123+
// invalid
124+
(true, true, false) => bail!("can't specify both binary and library outputs"),
125+
(true, false, true) => bail!("can't specify both binary and proc-macro outputs"),
126+
(false, true, true) => bail!("can't specify both library and proc-macro outputs"),
127+
(true, true, true) => bail!("can't specify all binary, library and proc-macro outputs"),
117128
};
118129

119130
let opts = NewOptions {
@@ -343,18 +354,27 @@ fn detect_source_paths_and_types(
343354
let sfi = match i.handling {
344355
H::Bin => SourceFileInformation {
345356
relative_path: pp,
346-
bin: true,
357+
kind: NewProjectKind::Bin,
347358
},
348359
H::Lib => SourceFileInformation {
349-
relative_path: pp,
350-
bin: false,
360+
relative_path: pp.clone(),
361+
kind: NewProjectKind::Lib {
362+
proc_macro: paths::read(&path.join(pp.clone()))?
363+
.contains("proc_macro::TokenStream"),
364+
},
351365
},
352366
H::Detect => {
353367
let content = paths::read(&path.join(pp.clone()))?;
354-
let isbin = content.contains("fn main");
368+
let kind = if content.contains("fn main") {
369+
NewProjectKind::Bin
370+
} else if content.contains("proc_macro::TokenStream") {
371+
NewProjectKind::Lib { proc_macro: true }
372+
} else {
373+
NewProjectKind::Lib { proc_macro: false }
374+
};
355375
SourceFileInformation {
356376
relative_path: pp,
357-
bin: isbin,
377+
kind,
358378
}
359379
}
360380
};
@@ -367,7 +387,7 @@ fn detect_source_paths_and_types(
367387
let mut duplicates_checker: BTreeMap<&str, &SourceFileInformation> = BTreeMap::new();
368388

369389
for i in detected_files {
370-
if i.bin {
390+
if i.kind.is_bin() {
371391
if let Some(x) = BTreeMap::get::<str>(&duplicates_checker, &name) {
372392
anyhow::bail!(
373393
"\
@@ -397,17 +417,15 @@ cannot automatically generate Cargo.toml as the main target would be ambiguous",
397417
Ok(())
398418
}
399419

400-
fn plan_new_source_file(bin: bool) -> SourceFileInformation {
401-
if bin {
402-
SourceFileInformation {
403-
relative_path: "src/main.rs".to_string(),
404-
bin: true,
405-
}
420+
fn plan_new_source_file(kind: NewProjectKind) -> SourceFileInformation {
421+
let relative_path = if kind.is_bin() {
422+
"src/main.rs".to_string()
406423
} else {
407-
SourceFileInformation {
408-
relative_path: "src/lib.rs".to_string(),
409-
bin: false,
410-
}
424+
"src/lib.rs".to_string()
425+
};
426+
SourceFileInformation {
427+
relative_path,
428+
kind,
411429
}
412430
}
413431

@@ -416,10 +434,10 @@ fn calculate_new_project_kind(
416434
auto_detect_kind: bool,
417435
found_files: &Vec<SourceFileInformation>,
418436
) -> NewProjectKind {
419-
let bin_file = found_files.iter().find(|x| x.bin);
437+
let bin_file = found_files.iter().find(|x| x.kind.is_bin());
420438

421439
let kind_from_files = if !found_files.is_empty() && bin_file.is_none() {
422-
NewProjectKind::Lib
440+
NewProjectKind::Lib { proc_macro: false }
423441
} else {
424442
NewProjectKind::Bin
425443
};
@@ -454,7 +472,7 @@ pub fn new(opts: &NewOptions, gctx: &GlobalContext) -> CargoResult<()> {
454472
version_control: opts.version_control,
455473
path,
456474
name,
457-
source_files: vec![plan_new_source_file(opts.kind.is_bin())],
475+
source_files: vec![plan_new_source_file(opts.kind)],
458476
edition: opts.edition.as_deref(),
459477
registry: opts.registry.as_deref(),
460478
};
@@ -488,34 +506,31 @@ pub fn init(opts: &NewOptions, gctx: &GlobalContext) -> CargoResult<NewProjectKi
488506
}
489507
check_path(path, &mut gctx.shell())?;
490508

491-
let has_bin = kind.is_bin();
492-
493509
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) {
510+
src_paths_types.push(plan_new_source_file(kind));
511+
} else if let Ok(file) = itertools::Itertools::exactly_one(src_paths_types.iter_mut())
512+
&& file.kind != kind
513+
{
496514
// 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-
};
502515
gctx.shell().warn(format!(
503516
"file `{}` seems to be a {} file",
504-
src_paths_types[0].relative_path, file_type
517+
file.relative_path, file.kind
505518
))?;
506-
src_paths_types[0].bin = has_bin
507-
} else if src_paths_types.len() > 1 && !has_bin {
519+
file.kind = kind;
520+
} else if let [first, second, ..] = &src_paths_types[..]
521+
&& !kind.is_bin()
522+
{
508523
// We have found both lib and bin files and the user would like us to treat both as libs
509524
anyhow::bail!(
510525
"cannot have a package with \
511526
multiple libraries, \
512527
found both `{}` and `{}`",
513-
src_paths_types[0].relative_path,
514-
src_paths_types[1].relative_path
528+
first.relative_path,
529+
second.relative_path
515530
)
516531
}
517532

518-
check_name(name, opts.name.is_none(), has_bin, &mut gctx.shell())?;
533+
check_name(name, opts.name.is_none(), kind.is_bin(), &mut gctx.shell())?;
519534

520535
let mut version_control = opts.version_control;
521536

@@ -781,7 +796,7 @@ fn mk(gctx: &GlobalContext, opts: &MkOptions<'_>) -> CargoResult<()> {
781796

782797
// Calculate what `[lib]` and `[[bin]]`s we need to append to `Cargo.toml`.
783798
for i in &opts.source_files {
784-
if i.bin {
799+
if i.kind.is_bin() {
785800
if i.relative_path != "src/main.rs" {
786801
let mut bin = toml_edit::Table::new();
787802
bin["name"] = toml_edit::value(name);
@@ -794,10 +809,18 @@ fn mk(gctx: &GlobalContext, opts: &MkOptions<'_>) -> CargoResult<()> {
794809
.expect("bin is an array of tables")
795810
.push(bin);
796811
}
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);
812+
} else {
813+
if i.relative_path != "src/lib.rs" {
814+
let mut lib = toml_edit::Table::new();
815+
lib["path"] = toml_edit::value(i.relative_path.clone());
816+
manifest["lib"] = toml_edit::Item::Table(lib);
817+
}
818+
if i.kind.is_proc_macro() {
819+
if manifest.get("lib").is_none() {
820+
manifest["lib"] = toml_edit::Item::Table(toml_edit::Table::new());
821+
}
822+
manifest["lib"]["proc-macro"] = true.into();
823+
}
801824
}
802825
}
803826

@@ -867,14 +890,16 @@ fn mk(gctx: &GlobalContext, opts: &MkOptions<'_>) -> CargoResult<()> {
867890
paths::create_dir_all(src_dir)?;
868891
}
869892

870-
let default_file_content: &[u8] = if i.bin {
871-
b"\
893+
let default_file_content: &[u8] = match i.kind {
894+
NewProjectKind::Bin => {
895+
b"\
872896
fn main() {
873897
println!(\"Hello, world!\");
874898
}
875899
"
876-
} else {
877-
b"\
900+
}
901+
NewProjectKind::Lib { proc_macro: false } => {
902+
b"\
878903
pub fn add(left: u64, right: u64) -> u64 {
879904
left + right
880905
}
@@ -890,6 +915,29 @@ mod tests {
890915
}
891916
}
892917
"
918+
}
919+
NewProjectKind::Lib { proc_macro: true } => {
920+
b"\
921+
#[proc_macro]
922+
pub fn identity_proc_macro(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
923+
input
924+
}
925+
926+
#[proc_macro_derive(Foo, attributes(foo))]
927+
pub fn identity_derive_macro(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
928+
input
929+
}
930+
931+
#[proc_macro_attribute]
932+
pub fn identity_attribute_macro(
933+
args: proc_macro::TokenStream,
934+
input: proc_macro::TokenStream,
935+
) -> proc_macro::TokenStream {
936+
let _ = args;
937+
input
938+
}
939+
"
940+
}
893941
};
894942

895943
if !path_of_source_file.is_file() {

src/cargo/util/command_prelude.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,7 @@ pub trait CommandExt: Sized {
448448
)
449449
._arg(flag("bin", "Use a binary (application) template [default]"))
450450
._arg(flag("lib", "Use a library template"))
451+
._arg(flag("proc-macro", "Use a library (proc-macro) template"))
451452
._arg(
452453
opt("edition", "Edition to set for the crate generated")
453454
.value_parser(Edition::CLI_VALUES)
@@ -955,6 +956,7 @@ Run `{cmd}` to see possible targets."
955956
vcs,
956957
self.flag("bin"),
957958
self.flag("lib"),
959+
self.flag("proc-macro"),
958960
self.value_of_path("path", gctx).unwrap(),
959961
self._value_of("name").map(|s| s.to_string()),
960962
self._value_of("edition").map(|s| s.to_string()),

tests/testsuite/cargo_init/help/stdout.term.svg

Lines changed: 23 additions & 21 deletions
Loading

0 commit comments

Comments
 (0)