@@ -263,25 +263,77 @@ impl<Cmd: ConfigureCommand> ShellOptions<Cmd> {
263263 path
264264 }
265265
266- /// Adds paths to cargo binaries (including examples) to the `PATH` env variable
267- /// for the shell described by these options.
268- /// This allows to call them by the corresponding filename, without specifying a path
266+ #[ cfg_attr( feature = "tracing" , tracing:: instrument( level = "debug" , ret) ) ]
267+ fn legacy_cargo_path ( binary_name : & str ) -> Option < PathBuf > {
268+ let target_path = Self :: target_path ( ) ;
269+ let binary_path = target_path. join ( format ! ( "{binary_name}{}" , env:: consts:: EXE_SUFFIX ) ) ;
270+ let exists = binary_path. try_exists ( ) ;
271+
272+ #[ cfg( feature = "tracing" ) ]
273+ tracing:: debug!( ?binary_path, ?exists, "checked binary path" ) ;
274+ exists. ok ( ) ?. then_some ( binary_path)
275+ }
276+
277+ fn panic_on_missing_cargo_path ( binary_name : & str ) -> ! {
278+ let binaries: Vec < _ > = env:: vars_os ( )
279+ . filter_map ( |( name, _) | {
280+ let name = name. into_string ( ) . ok ( ) ?;
281+ Some ( name. strip_prefix ( "CARGO_BIN_EXE_" ) ?. to_owned ( ) )
282+ } )
283+ . collect ( ) ;
284+ if binaries. is_empty ( ) {
285+ panic ! (
286+ "`CARGO_BIN_EXE_{binary_name}` env variable is unset, and {binary_name} is not in the default cargo target dir.\n \
287+ help: If this is run in a unit test, move it to an integration test to gain access to `CARGO_BIN_EXE_` vars (requires Rust 1.94+)"
288+ ) ;
289+ } else {
290+ panic ! (
291+ "`{binary_name}` does not look like a valid cargo binary in the workspace.\n \
292+ help: Available binaries: {binaries:?}"
293+ ) ;
294+ }
295+ }
296+
297+ /// Adds paths to a cargo binary to the `PATH` env variable for the shell described by these options.
298+ /// This allows to call the binary by the corresponding filename, without specifying a path
269299 /// or doing complex preparations (e.g., calling `cargo install`).
270300 ///
271301 /// # Limitations
272302 ///
273303 /// - The caller must be a unit or integration test; the method will work improperly otherwise.
304+ /// - Does not work in Rust 1.91, 1.92, 1.93 with a non-default `build.build-dir`.
305+ #[ cfg_attr( feature = "tracing" , tracing:: instrument( level = "debug" , skip( self ) ) ) ]
274306 #[ must_use]
275- pub fn with_cargo_path ( mut self ) -> Self {
276- let target_path = Self :: target_path ( ) ;
277- self . path_additions . push ( target_path. join ( "examples" ) ) ;
278- self . path_additions . push ( target_path) ;
307+ #[ allow( clippy:: missing_panics_doc) ] // should never be triggered
308+ pub fn with_cargo_path_for ( mut self , binary_name : & str ) -> Self {
309+ let env_var_name = format ! ( "CARGO_BIN_EXE_{binary_name}" ) ;
310+ let binary_path = env:: var_os ( & env_var_name) . map ( PathBuf :: from) ;
311+
312+ #[ cfg( feature = "tracing" ) ]
313+ tracing:: debug!( ?binary_path, "got Rust 1.94+ path to binary" ) ;
314+
315+ let binary_path = binary_path
316+ . or_else ( || Self :: legacy_cargo_path ( binary_name) )
317+ . unwrap_or_else ( || Self :: panic_on_missing_cargo_path ( binary_name) ) ;
318+
319+ #[ cfg( feature = "tracing" ) ]
320+ tracing:: debug!( ?binary_path, "got path to binary" ) ;
321+
322+ let parent_path = binary_path
323+ . parent ( )
324+ . expect ( "invalid binary path" )
325+ . to_owned ( ) ;
326+ // The check is inefficient, but we shouldn't have many additional paths.
327+ if !self . path_additions . contains ( & parent_path) {
328+ self . path_additions . push ( parent_path) ;
329+ }
330+
279331 self
280332 }
281333
282334 /// Adds a specified path to the `PATH` env variable for the shell described by these options.
283335 /// This method can be called multiple times to add multiple paths and is composable
284- /// with [`Self::with_cargo_path ()`].
336+ /// with [`Self::with_cargo_path_for ()`].
285337 #[ must_use]
286338 pub fn with_additional_path ( mut self , path : impl Into < PathBuf > ) -> Self {
287339 let path = path. into ( ) ;
0 commit comments