@@ -3,6 +3,13 @@ use std::path::Path;
33use std:: path:: PathBuf ;
44
55use codex_core:: CODEX_APPLY_PATCH_ARG1 ;
6+ #[ cfg( unix) ]
7+ use std:: os:: unix:: fs:: symlink;
8+ use tempfile:: TempDir ;
9+
10+ const LINUX_SANDBOX_ARG0 : & str = "codex-linux-sandbox" ;
11+ const APPLY_PATCH_ARG0 : & str = "apply_patch" ;
12+ const MISSPELLED_APPLY_PATCH_ARG0 : & str = "applypatch" ;
613
714/// While we want to deploy the Codex CLI as a single executable for simplicity,
815/// we also want to expose some of its functionality as distinct CLIs, so we use
3946 . and_then ( |s| s. to_str ( ) )
4047 . unwrap_or ( "" ) ;
4148
42- if exe_name == "codex-linux-sandbox" {
49+ if exe_name == LINUX_SANDBOX_ARG0 {
4350 // Safety: [`run_main`] never returns.
4451 codex_linux_sandbox:: run_main ( ) ;
52+ } else if exe_name == APPLY_PATCH_ARG0 || exe_name == MISSPELLED_APPLY_PATCH_ARG0 {
53+ codex_apply_patch:: main ( ) ;
4554 }
4655
4756 let argv1 = args. next ( ) . unwrap_or_default ( ) ;
6877 // before creating any threads/the Tokio runtime.
6978 load_dotenv ( ) ;
7079
80+ // Retain the TempDir so it exists for the lifetime of the invocation of
81+ // this executable. Admittedly, we could invoke `keep()` on it, but it
82+ // would be nice to avoid leaving temporary directories behind, if possible.
83+ let _path_entry = match prepend_path_entry_for_apply_patch ( ) {
84+ Ok ( path_entry) => path_entry,
85+ Err ( err) => {
86+ // While it is possible that Codex could likely proceed successfully
87+ // even if updating the PATH fails, let's be strict.
88+ eprintln ! ( "could not update PATH: {err}" ) ;
89+ std:: process:: exit ( 1 ) ;
90+ }
91+ } ;
92+
7193 // Regular invocation – create a Tokio runtime and execute the provided
7294 // async entry-point.
7395 let runtime = tokio:: runtime:: Runtime :: new ( ) ?;
@@ -113,3 +135,60 @@ where
113135 }
114136 }
115137}
138+
139+ /// Creates a temporary directory with either:
140+ ///
141+ /// - UNIX: `apply_patch` symlink to the current executable
142+ /// - WINDOWS: `apply_patch.bat` batch script to invoke the current executable
143+ /// with the "secret" --codex-run-as-apply-patch flag.
144+ ///
145+ /// This temporary directory is prepended to the PATH environment variable so
146+ /// that `apply_patch` can be on the PATH without requiring the user to
147+ /// install a separate `apply_patch` executable, simplifying the deployment of
148+ /// Codex CLI.
149+ ///
150+ /// IMPORTANT: This function modifies the PATH environment variable, so it MUST
151+ /// be called before multiple threads are spawned.
152+ fn prepend_path_entry_for_apply_patch ( ) -> std:: io:: Result < TempDir > {
153+ let temp_dir = TempDir :: new ( ) ?;
154+ let path = temp_dir. path ( ) ;
155+
156+ for filename in & [ APPLY_PATCH_ARG0 , MISSPELLED_APPLY_PATCH_ARG0 ] {
157+ let exe = std:: env:: current_exe ( ) ?;
158+
159+ #[ cfg( unix) ]
160+ {
161+ let link = path. join ( filename) ;
162+ symlink ( & exe, & link) ?;
163+ }
164+
165+ #[ cfg( windows) ]
166+ {
167+ let batch_script = path. join ( format ! ( "{filename}.bat" ) ) ;
168+ std:: fs:: write (
169+ & batch_script,
170+ format ! (
171+ r#"@echo off
172+ "{}" {CODEX_APPLY_PATCH_ARG1} %*
173+ "# ,
174+ exe. display( )
175+ ) ,
176+ ) ?;
177+ }
178+ }
179+
180+ let updated_path_env_var = match std:: env:: var ( "PATH" ) {
181+ Ok ( existing_path) => {
182+ format ! ( "{}:{}" , path. display( ) , existing_path)
183+ }
184+ Err ( _) => {
185+ format ! ( "{}" , path. display( ) )
186+ }
187+ } ;
188+
189+ unsafe {
190+ std:: env:: set_var ( "PATH" , updated_path_env_var) ;
191+ }
192+
193+ Ok ( temp_dir)
194+ }
0 commit comments