55//! Cargo, as compared to the classic "rustc-like" CLI.
66
77use std:: {
8- convert:: Infallible , env, ffi:: OsString , io:: Write , path:: PathBuf , process , str :: FromStr ,
9- sync:: Arc ,
8+ convert:: Infallible , env, ffi:: OsString , fs , io:: Write , path:: Path , path :: PathBuf , process ,
9+ str :: FromStr , sync:: Arc ,
1010} ;
1111use structopt:: { clap:: AppSettings , StructOpt } ;
1212use tectonic:: {
@@ -22,6 +22,7 @@ use tectonic::{
2222use tectonic_bridge_core:: { SecuritySettings , SecurityStance } ;
2323use tectonic_bundles:: Bundle ;
2424use tectonic_docmodel:: workspace:: { Workspace , WorkspaceCreator } ;
25+ use tectonic_errors:: prelude:: anyhow;
2526use tectonic_status_base:: plain:: PlainStatusBackend ;
2627use tokio:: runtime;
2728use watchexec:: event:: ProcessEnd ;
@@ -171,8 +172,8 @@ enum Commands {
171172 /// Create a new document project
172173 New ( NewCommand ) ,
173174
174- /// Initializes a new document in the current directory
175175 #[ structopt( name = "init" ) ]
176+ /// Initializes a new document in the current directory
176177 Init ( InitCommand ) ,
177178
178179 #[ structopt( name = "show" ) ]
@@ -182,6 +183,10 @@ enum Commands {
182183 #[ structopt( name = "watch" ) ]
183184 /// Watch input files and execute commands on change
184185 Watch ( WatchCommand ) ,
186+
187+ #[ structopt( external_subcommand) ]
188+ /// Runs the external command `tectonic-[command]` if one exists.
189+ External ( Vec < String > ) ,
185190}
186191
187192impl Commands {
@@ -195,6 +200,7 @@ impl Commands {
195200 Commands :: Init ( o) => o. customize ( cc) ,
196201 Commands :: Show ( o) => o. customize ( cc) ,
197202 Commands :: Watch ( o) => o. customize ( cc) ,
203+ Commands :: External ( _) => { }
198204 }
199205 }
200206
@@ -208,6 +214,7 @@ impl Commands {
208214 Commands :: Init ( o) => o. execute ( config, status) ,
209215 Commands :: Show ( o) => o. execute ( config, status) ,
210216 Commands :: Watch ( o) => o. execute ( config, status) ,
217+ Commands :: External ( args) => do_external ( args) ,
211218 }
212219 }
213220}
@@ -757,3 +764,69 @@ impl ShowUserCacheDirCommand {
757764 Ok ( 0 )
758765 }
759766}
767+
768+ #[ cfg( unix) ]
769+ /// On Unix, exec() to replace ourselves with the child process. This function
770+ /// *should* never return.
771+ fn exec_or_spawn ( cmd : & mut process:: Command ) -> Result < i32 > {
772+ use std:: os:: unix:: process:: CommandExt ;
773+
774+ // exec() only returns an io::Error directly, since on success it never
775+ // returns; the following tomfoolery transforms it into our Result
776+ // machinery as desired.
777+ Err ( cmd. exec ( ) . into ( ) )
778+ }
779+
780+ #[ cfg( not( unix) ) ]
781+ /// On other platforms, just run the process and wait for it.
782+ fn exec_or_spawn ( cmd : & mut process:: Command ) -> Result < i32 > {
783+ // code() can only return None on Unix when the subprocess was killed by a
784+ // signal. This function only runs if we're not on Unix, so we'll always
785+ // get Some.
786+ Ok ( cmd. status ( ) ?. code ( ) . unwrap ( ) )
787+ }
788+
789+ #[ cfg( unix) ]
790+ fn is_executable < P : AsRef < Path > > ( path : P ) -> bool {
791+ use std:: os:: unix:: prelude:: * ;
792+ fs:: metadata ( path)
793+ . map ( |metadata| metadata. is_file ( ) && metadata. permissions ( ) . mode ( ) & 0o111 != 0 )
794+ . unwrap_or ( false )
795+ }
796+
797+ #[ cfg( windows) ]
798+ fn is_executable < P : AsRef < Path > > ( path : P ) -> bool {
799+ fs:: metadata ( path)
800+ . map ( |metadata| metadata. is_file ( ) )
801+ . unwrap_or ( false )
802+ }
803+
804+ fn search_directories ( ) -> Vec < PathBuf > {
805+ let mut dirs = Vec :: new ( ) ;
806+
807+ if let Some ( val) = env:: var_os ( "PATH" ) {
808+ dirs. extend ( env:: split_paths ( & val) ) ;
809+ }
810+ dirs
811+ }
812+
813+ #[ allow( clippy:: redundant_closure) ]
814+ /// Run an external command by executing a subprocess.
815+ fn do_external ( all_args : Vec < String > ) -> Result < i32 > {
816+ let ( cmd, args) = all_args. split_first ( ) . unwrap ( ) ;
817+
818+ let command_exe = format ! ( "tectonic-{}{}" , cmd, env:: consts:: EXE_SUFFIX ) ;
819+ let path = search_directories ( )
820+ . iter ( )
821+ . map ( |dir| dir. join ( & command_exe) )
822+ . find ( |file| is_executable ( file) ) ;
823+
824+ let command = path. ok_or_else ( || {
825+ anyhow ! (
826+ "no internal or external subcommand `{0}` is available (install `tectonic-{0}`?)" ,
827+ cmd
828+ )
829+ } ) ?;
830+
831+ exec_or_spawn ( process:: Command :: new ( command) . args ( args) )
832+ }
0 commit comments