Skip to main content

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(&current_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}