find_crate/lib.rs
1// SPDX-License-Identifier: Apache-2.0 OR MIT
2
3/*!
4<!-- Note: Document from sync-markdown-to-rustdoc:start through sync-markdown-to-rustdoc:end
5 is synchronized from README.md. Any changes to that range are not preserved. -->
6<!-- tidy:sync-markdown-to-rustdoc:start -->
7
8Find the crate name from the current `Cargo.toml`.
9
10When writing declarative macros, `$crate` representing the current crate is
11very useful, but procedural macros do not have this. If you know the current
12name of the crate you want to use, you can do the same thing as `$crate`.
13This crate provides the features to make it easy.
14
15## Usage
16
17Add this to your `Cargo.toml`:
18
19```toml
20[dependencies]
21find-crate = "0.7"
22```
23
24## Examples
25
26[`find_crate`] function gets the crate name from the current `Cargo.toml`.
27
28```
29use find_crate::find_crate;
30use proc_macro2::{Ident, Span, TokenStream};
31use quote::quote;
32
33fn import() -> TokenStream {
34 let name = find_crate(|name| name == "foo").unwrap().name;
35 let name = Ident::new(&name, Span::call_site());
36 // If your proc-macro crate is 2018 edition, use `quote!(use #name as _foo;)` instead.
37 quote!(extern crate #name as _foo;)
38}
39```
40
41As in this example, it is easy to handle cases where proc-macro is exported
42from multiple crates.
43
44```
45use find_crate::find_crate;
46use proc_macro2::{Ident, Span, TokenStream};
47use quote::quote;
48
49fn import() -> TokenStream {
50 let name = find_crate(|name| name == "foo" || name == "foo-core").unwrap().name;
51 let name = Ident::new(&name, Span::call_site());
52 // If your proc-macro crate is 2018 edition, use `quote!(use #name as _foo;)` instead.
53 quote!(extern crate #name as _foo;)
54}
55```
56
57Using [`Manifest`] to search for multiple crates. It is much more efficient
58than using [`find_crate`] function for each crate.
59
60```
61use find_crate::Manifest;
62use proc_macro2::{Ident, Span, TokenStream};
63use quote::{format_ident, quote};
64
65const CRATE_NAMES: &[&[&str]] = &[
66 &["foo", "foo-core"],
67 &["bar", "bar-util", "bar-core"],
68 &["baz"],
69];
70
71fn imports() -> TokenStream {
72 let mut tokens = TokenStream::new();
73 let manifest = Manifest::new().unwrap();
74
75 for names in CRATE_NAMES {
76 let name = manifest.find(|name| names.contains(&name)).unwrap().name;
77 let name = Ident::new(&name, Span::call_site());
78 let import_name = format_ident!("_{}", names[0]);
79 // If your proc-macro crate is 2018 edition, use `quote!(use #name as #import_name;)` instead.
80 tokens.extend(quote!(extern crate #name as #import_name;));
81 }
82 tokens
83}
84```
85
86By default it will be searched from `dependencies` and `dev-dependencies`.
87This behavior can be adjusted by changing the [`Manifest::dependencies`] field.
88
89[`find_crate`] and [`Manifest::new`] functions read `Cargo.toml` in
90[`CARGO_MANIFEST_DIR`] as manifest.
91
92## Alternatives
93
94If you write function-like procedural macros, [you can combine it with
95declarative macros to support both crate renaming and macro
96re-exporting.][rust-lang/futures-rs#2124]
97
98This crate is intended to provide more powerful features such as support
99for multiple crate names and versions. For general purposes,
100[proc-macro-crate], which provides a simpler API, may be easier to use.
101
102[`CARGO_MANIFEST_DIR`]: https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-crates
103[rust-lang/futures-rs#2124]: https://github.com/rust-lang/futures-rs/pull/2124
104[proc-macro-crate]: https://github.com/bkchr/proc-macro-crate
105
106<!-- tidy:sync-markdown-to-rustdoc:end -->
107*/
108
109#![no_std]
110#![doc(test(
111 no_crate_inject,
112 attr(allow(
113 dead_code,
114 unused_variables,
115 clippy::undocumented_unsafe_blocks,
116 clippy::unused_trait_names,
117 ))
118))]
119#![forbid(unsafe_code)]
120#![warn(
121 // Lints that may help when writing public library.
122 missing_debug_implementations,
123 missing_docs,
124 clippy::alloc_instead_of_core,
125 clippy::exhaustive_enums,
126 clippy::exhaustive_structs,
127 clippy::impl_trait_in_params,
128 // clippy::missing_inline_in_public_items,
129 clippy::std_instead_of_alloc,
130 clippy::std_instead_of_core,
131)]
132
133extern crate alloc;
134extern crate std;
135
136#[cfg(test)]
137#[path = "gen/tests/assert_impl.rs"]
138mod assert_impl;
139#[cfg(test)]
140#[path = "gen/tests/track_size.rs"]
141mod track_size;
142
143mod error;
144
145use alloc::{borrow::ToOwned as _, string::String};
146use core::str::FromStr;
147use std::{
148 env, fs,
149 path::{Path, PathBuf},
150};
151
152use toml::value::{Table, Value};
153
154pub use self::error::{Error, TomlError};
155
156type Result<T, E = Error> = core::result::Result<T, E>;
157
158/// The [`CARGO_MANIFEST_DIR`] environment variable.
159///
160/// [`CARGO_MANIFEST_DIR`]: https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-crates
161const MANIFEST_DIR: &str = "CARGO_MANIFEST_DIR";
162
163/// Find the crate name from the current `Cargo.toml`.
164///
165/// This function reads `Cargo.toml` in [`CARGO_MANIFEST_DIR`] as manifest.
166///
167/// Note that this function needs to be used in the context of proc-macro.
168///
169/// # Examples
170///
171/// ```
172/// use find_crate::find_crate;
173/// use proc_macro2::{Ident, Span, TokenStream};
174/// use quote::quote;
175///
176/// fn import() -> TokenStream {
177/// let name = find_crate(|name| name == "foo" || name == "foo-core").unwrap().name;
178/// let name = Ident::new(&name, Span::call_site());
179/// // If your proc-macro crate is 2018 edition, use `quote!(use #name as _foo;)` instead.
180/// quote!(extern crate #name as _foo;)
181/// }
182/// ```
183///
184/// [`CARGO_MANIFEST_DIR`]: https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-crates
185pub fn find_crate<P>(predicate: P) -> Result<Package>
186where
187 P: FnMut(&str) -> bool,
188{
189 Manifest::new()?.find(predicate).ok_or(Error::NotFound)
190}
191
192/// The kind of dependencies to be searched.
193#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
194#[non_exhaustive]
195pub enum Dependencies {
196 /// Search from `dependencies` and `dev-dependencies`.
197 #[default]
198 Default,
199 /// Search from `dependencies`.
200 Release,
201 /// Search from `dev-dependencies`.
202 Dev,
203 /// Search from `build-dependencies`.
204 Build,
205 /// Search from `dependencies`, `dev-dependencies` and `build-dependencies`.
206 All,
207}
208
209impl Dependencies {
210 fn as_slice(self) -> &'static [&'static str] {
211 match self {
212 Dependencies::Default => &["dependencies", "dev-dependencies"],
213 Dependencies::Release => &["dependencies"],
214 Dependencies::Dev => &["dev-dependencies"],
215 Dependencies::Build => &["build-dependencies"],
216 Dependencies::All => &["dependencies", "dev-dependencies", "build-dependencies"],
217 }
218 }
219}
220
221/// The package information. This has information on the current package name,
222/// original package name, and specified version.
223#[derive(Debug, Clone, PartialEq, Eq)]
224pub struct Package {
225 /// The key of this dependency in the manifest.
226 key: String,
227
228 // The key or the value of 'package' key.
229 // If this is `None`, the value of `key` field is the original name.
230 package: Option<String>,
231
232 /// The current name of the package. This is always a valid rust identifier
233 /// (`-` is replaced with `_`).
234 pub name: String,
235
236 /// The version requirement of the package. Returns `*` if no version
237 /// requirement is specified.
238 pub version: String,
239}
240
241impl Package {
242 /// Returns the original package name.
243 #[must_use]
244 pub fn original_name(&self) -> &str {
245 self.package.as_ref().unwrap_or(&self.key)
246 }
247
248 /// Returns `true` if the value of the [`name`] field is the original package
249 /// name.
250 ///
251 /// [`name`]: Package::name
252 #[must_use]
253 pub fn is_original(&self) -> bool {
254 self.package.is_none()
255 }
256}
257
258/// The manifest of cargo.
259///
260/// Note that this function needs to be used in the context of proc-macro.
261#[derive(Debug, Clone)]
262pub struct Manifest {
263 manifest: Table,
264
265 /// The kind of dependencies to be searched.
266 pub dependencies: Dependencies,
267}
268
269impl Manifest {
270 /// Creates a new `Manifest` from the current `Cargo.toml`.
271 ///
272 /// This function reads `Cargo.toml` in [`CARGO_MANIFEST_DIR`] as manifest.
273 ///
274 /// [`CARGO_MANIFEST_DIR`]: https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-crates
275 pub fn new() -> Result<Self> {
276 Self::from_path(&manifest_path()?)
277 }
278
279 /// Creates a new `Manifest` from the specified toml file.
280 pub fn from_path(manifest_path: &Path) -> Result<Self> {
281 Self::from_str(&fs::read_to_string(manifest_path)?)
282 }
283
284 /// Finds the crate with crate name, and returns its package information.
285 ///
286 /// The argument of the closure is the original name of the package.
287 ///
288 /// # Examples
289 ///
290 /// ```
291 /// use find_crate::Manifest;
292 /// use proc_macro2::{Ident, Span, TokenStream};
293 /// use quote::quote;
294 ///
295 /// fn import() -> TokenStream {
296 /// let manifest = Manifest::new().unwrap();
297 /// let name = manifest.find(|name| name == "foo" || name == "foo-core").unwrap().name;
298 /// let name = Ident::new(&name, Span::call_site());
299 /// // If your proc-macro crate is 2018 edition, use `quote!(use #name as _foo;)` instead.
300 /// quote!(extern crate #name as _foo;)
301 /// }
302 /// ```
303 pub fn find<P>(&self, mut predicate: P) -> Option<Package>
304 where
305 P: FnMut(&str) -> bool,
306 {
307 self.find2(|s, _| predicate(s))
308 }
309
310 /// Finds the crate with crate name and version, and returns its package information.
311 ///
312 /// The first argument of the closure is the original name of the package
313 /// and the second argument is the version of the package.
314 ///
315 /// # Examples
316 ///
317 /// ```
318 /// use find_crate::Manifest;
319 /// use proc_macro2::{Ident, Span, TokenStream};
320 /// use quote::quote;
321 /// use semver::{Version, VersionReq};
322 ///
323 /// fn check_version(req: &str, version: &Version) -> bool {
324 /// VersionReq::parse(req).unwrap().matches(version)
325 /// }
326 ///
327 /// fn import() -> TokenStream {
328 /// let version = Version::parse("0.3.0").unwrap();
329 /// let manifest = Manifest::new().unwrap();
330 /// let name = manifest
331 /// .find2(|name, req| name == "foo" && (req == "*" || check_version(req, &version)))
332 /// .unwrap()
333 /// .name;
334 /// let name = Ident::new(&name, Span::call_site());
335 /// // If your proc-macro crate is 2018 edition, use `quote!(use #name as _foo;)` instead.
336 /// quote!(extern crate #name as _foo;)
337 /// }
338 /// ```
339 pub fn find2<P>(&self, predicate: P) -> Option<Package>
340 where
341 P: FnMut(&str, &str) -> bool,
342 {
343 find(&self.manifest, self.dependencies, predicate)
344 }
345
346 /// The package for the crate that this manifest represents.
347 ///
348 /// # Examples
349 ///
350 /// ```
351 /// use find_crate::Manifest;
352 /// use proc_macro2::{Ident, Span, TokenStream};
353 /// use quote::quote;
354 ///
355 /// fn current_crate_name() -> TokenStream {
356 /// let manifest = Manifest::new().unwrap();
357 /// let current_crate_package = manifest.crate_package().unwrap();
358 /// let name = Ident::new(¤t_crate_package.name, Span::call_site());
359 /// quote!(#name)
360 /// }
361 /// ```
362 pub fn crate_package(&self) -> Result<Package> {
363 let package_section = self
364 .manifest
365 .get("package")
366 .ok_or_else(|| Error::InvalidManifest("[package] section is missing".to_owned()))?;
367
368 let package_key_value = package_section.get("name").ok_or_else(|| {
369 Error::InvalidManifest("[package] section is missing `name`".to_owned())
370 })?;
371
372 let package_key = package_key_value.as_str().ok_or_else(|| {
373 Error::InvalidManifest("`name` in [package] section is not a string".to_owned())
374 })?;
375
376 let package_version = match package_section.get("version") {
377 Some(package_version_value) => package_version_value.as_str().ok_or_else(|| {
378 Error::InvalidManifest("`version` in [package] section is not a string".to_owned())
379 })?,
380 // Cargo supports version-less manifests: https://github.com/rust-lang/cargo/pull/12786
381 None => "0.0.0",
382 };
383
384 let package = Package {
385 key: package_key.to_owned(),
386 package: None,
387 name: package_key.replace('-', "_"),
388 version: package_version.to_owned(),
389 };
390
391 Ok(package)
392 }
393}
394
395impl FromStr for Manifest {
396 type Err = Error;
397
398 /// Creates a new `Manifest` from a string containing a TOML file.
399 fn from_str(manifest: &str) -> Result<Self, Self::Err> {
400 Ok(Self {
401 manifest: toml::from_str(manifest).map_err(|e| Error::Toml(TomlError { error: e }))?,
402 dependencies: Dependencies::default(),
403 })
404 }
405}
406
407fn manifest_path() -> Result<PathBuf> {
408 let mut path: PathBuf = env::var_os(MANIFEST_DIR).ok_or(Error::NotFoundManifestDir)?.into();
409 path.push("Cargo.toml");
410 Ok(path)
411}
412
413fn find<P>(manifest: &Table, dependencies: Dependencies, mut predicate: P) -> Option<Package>
414where
415 P: FnMut(&str, &str) -> bool,
416{
417 fn find_inner<P>(table: &Table, dependencies: &str, predicate: &mut P) -> Option<Package>
418 where
419 P: FnMut(&str, &str) -> bool,
420 {
421 find_from_dependencies(table.get(dependencies)?.as_table()?, predicate)
422 }
423 fn find_target<P>(table: &Table, dependencies: &str, predicate: &mut P) -> Option<Package>
424 where
425 P: FnMut(&str, &str) -> bool,
426 {
427 table.values().find_map(|table| {
428 let table = table.as_table()?;
429 find_inner(table, dependencies, predicate)
430 .or_else(|| find_target(table, dependencies, predicate))
431 })
432 }
433
434 dependencies
435 .as_slice()
436 .iter()
437 .find_map(|dependencies| find_inner(manifest, dependencies, &mut predicate))
438 .or_else(|| {
439 dependencies.as_slice().iter().find_map(|dependencies| {
440 find_target(manifest.get("target")?.as_table()?, dependencies, &mut predicate)
441 })
442 })
443}
444
445fn find_from_dependencies<P>(table: &Table, mut predicate: P) -> Option<Package>
446where
447 P: FnMut(&str, &str) -> bool,
448{
449 fn package<P>(value: &Value, version: &str, predicate: P) -> Option<String>
450 where
451 P: FnOnce(&str, &str) -> bool,
452 {
453 value
454 .as_table()?
455 .get("package")?
456 .as_str()
457 .and_then(|name| if predicate(name, version) { Some(name.to_owned()) } else { None })
458 }
459
460 fn version(value: &Value) -> Option<&str> {
461 value.as_str().or_else(|| value.as_table()?.get("version")?.as_str())
462 }
463
464 table.iter().find_map(|(key, value)| {
465 let version = version(value).unwrap_or("*");
466 let package = package(value, version, &mut predicate);
467 if package.is_some() || predicate(key, version) {
468 Some(Package {
469 key: key.clone(),
470 name: key.replace('-', "_"),
471 version: version.to_owned(),
472 package,
473 })
474 } else {
475 None
476 }
477 })
478}