@@ -6,11 +6,13 @@ use crate::{CmdResult, FunResult};
6
6
use faccess:: { AccessMode , PathExt } ;
7
7
use lazy_static:: lazy_static;
8
8
use os_pipe:: { self , PipeReader , PipeWriter } ;
9
+ use std:: cell:: Cell ;
9
10
use std:: collections:: HashMap ;
10
11
use std:: ffi:: { OsStr , OsString } ;
11
12
use std:: fmt;
12
13
use std:: fs:: { File , OpenOptions } ;
13
14
use std:: io:: { Error , ErrorKind , Result } ;
15
+ use std:: marker:: PhantomData ;
14
16
use std:: mem:: take;
15
17
use std:: path:: { Path , PathBuf } ;
16
18
use std:: process:: Command ;
@@ -91,15 +93,19 @@ pub fn register_cmd(cmd: &'static str, func: FnFun) {
91
93
CMD_MAP . lock ( ) . unwrap ( ) . insert ( OsString :: from ( cmd) , func) ;
92
94
}
93
95
96
+ /// Whether debug mode is enabled globally.
97
+ /// Can be overridden by the thread-local setting in [`DEBUG_OVERRIDE`].
94
98
static DEBUG_ENABLED : LazyLock < AtomicBool > =
95
99
LazyLock :: new ( || AtomicBool :: new ( std:: env:: var ( "CMD_LIB_DEBUG" ) == Ok ( "1" . into ( ) ) ) ) ;
96
100
101
+ /// Whether debug mode is enabled globally.
102
+ /// Can be overridden by the thread-local setting in [`PIPEFAIL_OVERRIDE`].
97
103
static PIPEFAIL_ENABLED : LazyLock < AtomicBool > =
98
104
LazyLock :: new ( || AtomicBool :: new ( std:: env:: var ( "CMD_LIB_PIPEFAIL" ) != Ok ( "0" . into ( ) ) ) ) ;
99
105
100
106
/// Set debug mode or not, false by default.
101
107
///
102
- /// This is **global**, and affects all threads.
108
+ /// This is **global**, and affects all threads. To set it for the current thread only, use [`ScopedDebug`].
103
109
///
104
110
/// Setting environment variable CMD_LIB_DEBUG=0|1 has the same effect, but the environment variable is only
105
111
/// checked once at an unspecified time, so the only reliable way to do that is when the program is first started.
@@ -109,7 +115,7 @@ pub fn set_debug(enable: bool) {
109
115
110
116
/// Set pipefail or not, true by default.
111
117
///
112
- /// This is **global**, and affects all threads.
118
+ /// This is **global**, and affects all threads. To set it for the current thread only, use [`ScopedPipefail`].
113
119
///
114
120
/// Setting environment variable CMD_LIB_DEBUG=0|1 has the same effect, but the environment variable is only
115
121
/// checked once at an unspecified time, so the only reliable way to do that is when the program is first started.
@@ -118,11 +124,99 @@ pub fn set_pipefail(enable: bool) {
118
124
}
119
125
120
126
pub ( crate ) fn debug_enabled ( ) -> bool {
121
- DEBUG_ENABLED . load ( SeqCst )
127
+ DEBUG_OVERRIDE
128
+ . get ( )
129
+ . unwrap_or_else ( || DEBUG_ENABLED . load ( SeqCst ) )
122
130
}
123
131
124
132
pub ( crate ) fn pipefail_enabled ( ) -> bool {
125
- PIPEFAIL_ENABLED . load ( SeqCst )
133
+ PIPEFAIL_OVERRIDE
134
+ . get ( )
135
+ . unwrap_or_else ( || PIPEFAIL_ENABLED . load ( SeqCst ) )
136
+ }
137
+
138
+ thread_local ! {
139
+ /// Whether debug mode is enabled in the current thread.
140
+ /// None means to use the global setting in [`DEBUG_ENABLED`].
141
+ static DEBUG_OVERRIDE : Cell <Option <bool >> = Cell :: new( None ) ;
142
+
143
+ /// Whether pipefail mode is enabled in the current thread.
144
+ /// None means to use the global setting in [`PIPEFAIL_ENABLED`].
145
+ static PIPEFAIL_OVERRIDE : Cell <Option <bool >> = Cell :: new( None ) ;
146
+ }
147
+
148
+ /// Overrides the debug mode in the current thread, while the value is in scope.
149
+ ///
150
+ /// Each override restores the previous value when dropped, so they can be nested.
151
+ /// Since overrides are thread-local, these values can’t be sent across threads.
152
+ ///
153
+ /// ```
154
+ /// # use cmd_lib::{ScopedDebug, run_cmd};
155
+ /// // Must give the variable a name, not just `_`
156
+ /// let _debug = ScopedDebug::set(true);
157
+ /// run_cmd!(echo hello world)?; // Will have debug on
158
+ /// # Ok::<(), std::io::Error>(())
159
+ /// ```
160
+ // PhantomData field is equivalent to `impl !Send for Self {}`
161
+ pub struct ScopedDebug ( Option < bool > , PhantomData < * const ( ) > ) ;
162
+
163
+ /// Overrides the pipefail mode in the current thread, while the value is in scope.
164
+ ///
165
+ /// Each override restores the previous value when dropped, so they can be nested.
166
+ /// Since overrides are thread-local, these values can’t be sent across threads.
167
+ // PhantomData field is equivalent to `impl !Send for Self {}`
168
+ ///
169
+ /// ```
170
+ /// # use cmd_lib::{ScopedPipefail, run_cmd};
171
+ /// // Must give the variable a name, not just `_`
172
+ /// let _debug = ScopedPipefail::set(false);
173
+ /// run_cmd!(false | true)?; // Will have pipefail off
174
+ /// # Ok::<(), std::io::Error>(())
175
+ /// ```
176
+ pub struct ScopedPipefail ( Option < bool > , PhantomData < * const ( ) > ) ;
177
+
178
+ impl ScopedDebug {
179
+ /// ```compile_fail
180
+ /// let _: Box<dyn Send> = Box::new(cmd_lib::ScopedDebug::set(true));
181
+ /// ```
182
+ /// ```compile_fail
183
+ /// let _: Box<dyn Sync> = Box::new(cmd_lib::ScopedDebug::set(true));
184
+ /// ```
185
+ #[ doc( hidden) ]
186
+ pub fn test_not_send_not_sync ( ) { }
187
+
188
+ pub fn set ( enabled : bool ) -> Self {
189
+ let result = Self ( DEBUG_OVERRIDE . get ( ) , PhantomData ) ;
190
+ DEBUG_OVERRIDE . set ( Some ( enabled) ) ;
191
+ result
192
+ }
193
+ }
194
+ impl Drop for ScopedDebug {
195
+ fn drop ( & mut self ) {
196
+ DEBUG_OVERRIDE . set ( self . 0 )
197
+ }
198
+ }
199
+
200
+ impl ScopedPipefail {
201
+ /// ```compile_fail
202
+ /// let _: Box<dyn Send> = Box::new(cmd_lib::ScopedPipefail::set(true));
203
+ /// ```
204
+ /// ```compile_fail
205
+ /// let _: Box<dyn Sync> = Box::new(cmd_lib::ScopedPipefail::set(true));
206
+ /// ```
207
+ #[ doc( hidden) ]
208
+ pub fn test_not_send_not_sync ( ) { }
209
+
210
+ pub fn set ( enabled : bool ) -> Self {
211
+ let result = Self ( PIPEFAIL_OVERRIDE . get ( ) , PhantomData ) ;
212
+ PIPEFAIL_OVERRIDE . set ( Some ( enabled) ) ;
213
+ result
214
+ }
215
+ }
216
+ impl Drop for ScopedPipefail {
217
+ fn drop ( & mut self ) {
218
+ PIPEFAIL_OVERRIDE . set ( self . 0 )
219
+ }
126
220
}
127
221
128
222
#[ doc( hidden) ]
0 commit comments