1 //! Hugging Face token resolution for on-device model loading.
2 //!
3 //! On iOS there is no way to run `mistralrs login` or place a token file at
4 //! `~/.cache/huggingface/token`, so we embed the token at **build time**.
5 //!
6 //! ## How it works
7 //!
8 //! 1. The consuming app's `build.rs` sets the `HF_TOKEN` env var (e.g. by
9 //! loading a `.env` file with `dotenvy`, or via CI secrets).
10 //! 2. `option_env!("HF_TOKEN")` in this module picks it up at compile time
11 //! and bakes it into the binary.
12 //!
13 //! This works regardless of whether the build is triggered from a terminal
14 //! (where shell env vars are available) or from Xcode (which strips the
15 //! shell environment).
16 //!
17 //! ## Setup
18 //!
19 //! Set the `HF_TOKEN` environment variable before building, or add it to
20 //! a `.env` file that your app's `build.rs` loads:
21 //!
22 //! ```text
23 //! HF_TOKEN=hf_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
24 //! ```
25 //!
26 //! Then build normally — no extra env vars needed.
27 //!
28 //! ## Runtime behaviour
29 //!
30 //! [`hf_token_source`] returns:
31 //!
32 //! 1. `TokenSource::Literal(token)` — if `HF_TOKEN` was present at compile time.
33 //! 2. `TokenSource::CacheToken` — otherwise (reads `~/.cache/huggingface/token`;
34 //! works on macOS after `mistralrs login`).
35
36 #[cfg(any(
37 target_os = "macos",
38 target_os = "ios",
39 target_os = "tvos",
40 target_os = "visionos",
41 target_os = "watchos",
42 target_os = "windows",
43 target_os = "linux",
44 target_os = "android"
45 ))]
46 use mistralrs::TokenSource;
47
48 /// Token baked in at compile time (if the `HF_TOKEN` env var was set).
49 ///
50 /// `option_env!` is evaluated by `rustc` during compilation — the resulting
51 /// value is a `Option<&'static str>` embedded directly in the binary.
52 /// This means the token never needs to exist on the device's filesystem.
53 const BUILD_TIME_HF_TOKEN: Option<&str> = option_env!("HF_TOKEN");
54
55 /// Resolve the best available [`TokenSource`] for Hugging Face Hub requests.
56 ///
57 /// Prefer the compile-time literal when available (required on iOS), otherwise
58 /// fall back to the on-disk cache token (the default on macOS where
59 /// `mistralrs login` has been run).
60 #[cfg(any(
61 target_os = "macos",
62 target_os = "ios",
63 target_os = "tvos",
64 target_os = "visionos",
65 target_os = "watchos",
66 target_os = "windows",
67 target_os = "linux",
68 target_os = "android"
69 ))]
70 pub fn hf_token_source() -> TokenSource {
71 match BUILD_TIME_HF_TOKEN {
72 Some(token) if !token.is_empty() => {
73 log::debug!("Using build-time HF_TOKEN for Hugging Face authentication.");
74 TokenSource::Literal(token.to_string())
75 }
76 _ => {
77 log::debug!(
78 "No build-time HF_TOKEN found; falling back to cached token \
79 (~/.cache/huggingface/token)."
80 );
81 TokenSource::CacheToken
82 }
83 }
84 }
85
86 #[cfg(test)]
87 mod tests {
88 use super::*;
89
90 #[test]
91 fn build_time_token_is_option() {
92 // This just ensures the constant compiles and is the expected type.
93 let _: Option<&str> = BUILD_TIME_HF_TOKEN;
94 }
95
96 #[test]
97 #[cfg(any(
98 target_os = "macos",
99 target_os = "ios",
100 target_os = "tvos",
101 target_os = "visionos",
102 target_os = "watchos",
103 target_os = "windows",
104 target_os = "linux",
105 target_os = "android"
106 ))]
107 fn token_source_returns_valid_variant() {
108 let source = hf_token_source();
109 // We can't know at test time whether HF_TOKEN was set, but we can
110 // verify it returns *something* without panicking.
111 let display = format!("{}", source);
112 assert!(
113 display.starts_with("literal:") || display == "cache",
114 "Unexpected token source: {display}"
115 );
116 }
117 }