11use std:: cmp:: Ordering ;
2+ use std:: env;
3+ use std:: sync:: Mutex ;
24
35use color_eyre:: { eyre, Result } ;
46use semver:: Version ;
@@ -14,9 +16,29 @@ pub fn check_nix_version() -> Result<()> {
1416 let version = util:: get_nix_version ( ) ?;
1517 let is_lix_binary = util:: is_lix ( ) ?;
1618
19+ // XXX: Both Nix and Lix follow semantic versioning (semver). Update the
20+ // versions below once latest stable for either of those packages change.
21+ // TODO: Set up a CI to automatically update those in the future.
22+ const MIN_LIX_VERSION : & str = "2.91.1" ;
23+ const MIN_NIX_VERSION : & str = "2.24.14" ;
24+
1725 // Minimum supported versions. Those should generally correspond to
18- // latest package versions in the stable branch
19- let min_version = if is_lix_binary { "2.91.1" } else { "2.26.1" } ;
26+ // latest package versions in the stable branch.
27+ //
28+ // Q: Why are you doing this?
29+ // A: First of all to make sure we do not make baseless assumptions
30+ // about the user's system; we should only work around APIs that we
31+ // are fully aware of, and not try to work around every edge case.
32+ // Also, nh should be responsible for nudging the user to use the
33+ // relevant versions of the software it wraps, so that we do not have
34+ // to try and support too many versions. NixOS stable and unstable
35+ // will ALWAYS be supported, but outdated versions will not. If your
36+ // Nix fork uses a different versioning scheme, please open an issue.
37+ let min_version = if is_lix_binary {
38+ MIN_LIX_VERSION
39+ } else {
40+ MIN_NIX_VERSION
41+ } ;
2042
2143 let current = Version :: parse ( & version) ?;
2244 let required = Version :: parse ( min_version) ?;
@@ -85,7 +107,9 @@ pub fn setup_environment() -> Result<bool> {
85107 Ok ( do_warn)
86108}
87109
88- /// Runs all necessary checks for Nix functionality
110+ /// Consolidate all necessary checks for Nix functionality into a single function. This
111+ /// will be executed in the main function, but can be executed before critical commands
112+ /// to double-check if necessary.
89113///
90114/// # Returns
91115///
@@ -95,3 +119,160 @@ pub fn verify_nix_environment() -> Result<()> {
95119 check_nix_features ( ) ?;
96120 Ok ( ( ) )
97121}
122+
123+ // Environment variables are global state, so tests need to be run sequentially.
124+ // Using a mutex to ensure that env var manipulation in one test doesn't affect others.
125+ // Alternatively, run tests with `cargo test -- --test-threads=1`
126+ #[ allow( dead_code) ] // suppress 'false' positives
127+ static ENV_LOCK : Mutex < ( ) > = Mutex :: new ( ( ) ) ;
128+
129+ // Clean up environment variables set during tests
130+ #[ allow( dead_code) ]
131+ fn cleanup_env_vars ( ) {
132+ env:: remove_var ( "FLAKE" ) ;
133+ env:: remove_var ( "NH_FLAKE" ) ;
134+ env:: remove_var ( "NH_OS_FLAKE" ) ;
135+ env:: remove_var ( "NH_HOME_FLAKE" ) ;
136+ env:: remove_var ( "NH_DARWIN_FLAKE" ) ;
137+ }
138+
139+ #[ test]
140+ fn test_setup_environment_no_flake_set ( ) -> Result < ( ) > {
141+ let _lock = ENV_LOCK . lock ( ) . unwrap ( ) ;
142+ cleanup_env_vars ( ) ;
143+
144+ let should_warn = setup_environment ( ) ?;
145+ assert ! ( !should_warn, "Should not warn when FLAKE is not set" ) ;
146+ assert ! ( env:: var( "NH_FLAKE" ) . is_err( ) , "NH_FLAKE should not be set" ) ;
147+
148+ cleanup_env_vars ( ) ;
149+ Ok ( ( ) )
150+ }
151+
152+ #[ test]
153+ fn test_setup_environment_flake_set_no_nh_flake_no_specifics ( ) -> Result < ( ) > {
154+ let _lock = ENV_LOCK . lock ( ) . unwrap ( ) ;
155+ cleanup_env_vars ( ) ;
156+
157+ env:: set_var ( "FLAKE" , "test_flake_path" ) ;
158+ let should_warn = setup_environment ( ) ?;
159+
160+ assert ! (
161+ should_warn,
162+ "Should warn when FLAKE is set, NH_FLAKE is not, and no specific NH_*_FLAKE vars are set"
163+ ) ;
164+ assert_eq ! (
165+ env:: var( "NH_FLAKE" ) . unwrap( ) ,
166+ "test_flake_path" ,
167+ "NH_FLAKE should be set from FLAKE"
168+ ) ;
169+
170+ cleanup_env_vars ( ) ;
171+ Ok ( ( ) )
172+ }
173+
174+ #[ test]
175+ fn test_setup_environment_flake_set_nh_flake_already_set ( ) -> Result < ( ) > {
176+ let _lock = ENV_LOCK . lock ( ) . unwrap ( ) ;
177+ cleanup_env_vars ( ) ;
178+
179+ env:: set_var ( "FLAKE" , "test_flake_path" ) ;
180+ env:: set_var ( "NH_FLAKE" , "existing_nh_flake_path" ) ;
181+ let should_warn = setup_environment ( ) ?;
182+
183+ assert ! ( !should_warn, "Should not warn when NH_FLAKE is already set" ) ;
184+ assert_eq ! (
185+ env:: var( "NH_FLAKE" ) . unwrap( ) ,
186+ "existing_nh_flake_path" ,
187+ "NH_FLAKE should retain its original value"
188+ ) ;
189+
190+ cleanup_env_vars ( ) ;
191+ Ok ( ( ) )
192+ }
193+
194+ #[ test]
195+ fn test_setup_environment_flake_set_no_nh_flake_nh_os_flake_set ( ) -> Result < ( ) > {
196+ let _lock = ENV_LOCK . lock ( ) . unwrap ( ) ;
197+ cleanup_env_vars ( ) ;
198+
199+ env:: set_var ( "FLAKE" , "test_flake_path" ) ;
200+ env:: set_var ( "NH_OS_FLAKE" , "os_specific_flake" ) ;
201+ let should_warn = setup_environment ( ) ?;
202+
203+ assert ! (
204+ !should_warn,
205+ "Should not warn when FLAKE is set, NH_FLAKE is not, but NH_OS_FLAKE is set"
206+ ) ;
207+ assert_eq ! (
208+ env:: var( "NH_FLAKE" ) . unwrap( ) ,
209+ "test_flake_path" ,
210+ "NH_FLAKE should be set from FLAKE"
211+ ) ;
212+ assert_eq ! (
213+ env:: var( "NH_OS_FLAKE" ) . unwrap( ) ,
214+ "os_specific_flake" ,
215+ "NH_OS_FLAKE should remain set"
216+ ) ;
217+
218+ cleanup_env_vars ( ) ;
219+ Ok ( ( ) )
220+ }
221+
222+ #[ test]
223+ fn test_setup_environment_flake_set_no_nh_flake_nh_home_flake_set ( ) -> Result < ( ) > {
224+ let _lock = ENV_LOCK . lock ( ) . unwrap ( ) ;
225+ cleanup_env_vars ( ) ;
226+
227+ env:: set_var ( "FLAKE" , "test_flake_path" ) ;
228+ env:: set_var ( "NH_HOME_FLAKE" , "home_specific_flake" ) ;
229+ let should_warn = setup_environment ( ) ?;
230+
231+ assert ! (
232+ !should_warn,
233+ "Should not warn when FLAKE is set, NH_FLAKE is not, but NH_HOME_FLAKE is set"
234+ ) ;
235+ assert_eq ! (
236+ env:: var( "NH_FLAKE" ) . unwrap( ) ,
237+ "test_flake_path" ,
238+ "NH_FLAKE should be set from FLAKE"
239+ ) ;
240+ assert_eq ! (
241+ env:: var( "NH_HOME_FLAKE" ) . unwrap( ) ,
242+ "home_specific_flake" ,
243+ "NH_HOME_FLAKE should remain set"
244+ ) ;
245+
246+ cleanup_env_vars ( ) ;
247+ Ok ( ( ) )
248+ }
249+
250+ #[ test]
251+ // Greatest function name ever.
252+ // testSetupEnvironmentFlakeSetNoNhFlakeNhDarwinFlakeSetAbstractFactoryBuilder
253+ fn test_setup_environment_flake_set_no_nh_flake_nh_darwin_flake_set ( ) -> Result < ( ) > {
254+ let _lock = ENV_LOCK . lock ( ) . unwrap ( ) ;
255+ cleanup_env_vars ( ) ;
256+
257+ env:: set_var ( "FLAKE" , "test_flake_path" ) ;
258+ env:: set_var ( "NH_DARWIN_FLAKE" , "darwin_specific_flake" ) ;
259+ let should_warn = setup_environment ( ) ?;
260+
261+ assert ! (
262+ !should_warn,
263+ "Should not warn when FLAKE is set, NH_FLAKE is not, but NH_DARWIN_FLAKE is set"
264+ ) ;
265+ assert_eq ! (
266+ env:: var( "NH_FLAKE" ) . unwrap( ) ,
267+ "test_flake_path" ,
268+ "NH_FLAKE should be set from FLAKE"
269+ ) ;
270+ assert_eq ! (
271+ env:: var( "NH_DARWIN_FLAKE" ) . unwrap( ) ,
272+ "darwin_specific_flake" ,
273+ "NH_DARWIN_FLAKE should remain set"
274+ ) ;
275+
276+ cleanup_env_vars ( ) ; // Clean up after test
277+ Ok ( ( ) )
278+ }
0 commit comments