@@ -13,6 +13,7 @@ import (
1313 "runtime"
1414 "strconv"
1515 "strings"
16+ "sync"
1617 "syscall"
1718 "time"
1819
@@ -176,45 +177,79 @@ func findTemporalCLI() (string, error) {
176177
177178 // If not found in PATH, try different possible locations for the temporal CLI
178179 log .Println ("Checking bundled/local locations for temporal CLI..." )
179- possiblePaths := []string {
180- "./temporal" , // Current directory
180+ currentPaths := []string {
181+ "./temporal" ,
182+ "./temporal.exe" ,
183+ }
184+ if path , err := getExistingTemporalCLIFrom (currentPaths ); err == nil {
185+ return path , nil
186+ } else {
187+ log .Printf ("Attempt to find in local directory failed: %s." , err )
181188 }
182189
183190 // Also try relative to the current executable (most important for bundled apps)
184- if exePath , err := os .Executable (); err == nil {
185- exeDir := filepath .Dir (exePath )
186- log .Printf ("Executable directory: %s" , exeDir )
187- additionalPaths := []string {
188- filepath .Join (exeDir , "temporal" ),
189- filepath .Join (exeDir , "temporal.exe" ), // Windows
190- // Also try one level up (for development)
191- filepath .Join (exeDir , ".." , "temporal" ),
192- filepath .Join (exeDir , ".." , "temporal.exe" ),
193- }
194- possiblePaths = append (possiblePaths , additionalPaths ... )
195- log .Printf ("Will check these additional paths: %v" , additionalPaths )
196- } else {
191+ exePath , err := os .Executable ()
192+ if err != nil {
197193 log .Printf ("Failed to get executable path: %v" , err )
198194 }
195+ exeDir := filepath .Dir (exePath )
196+ log .Printf ("Executable directory: %s" , exeDir )
197+ additionalPaths := []string {
198+ filepath .Join (exeDir , "temporal" ),
199+ filepath .Join (exeDir , "temporal.exe" ), // Windows
200+ // Also try one level up (for development)
201+ filepath .Join (exeDir , ".." , "temporal" ),
202+ filepath .Join (exeDir , ".." , "temporal.exe" ),
203+ }
204+ log .Printf ("Will check these additional paths: %v" , additionalPaths )
205+ return getExistingTemporalCLIFrom (additionalPaths )
206+ }
199207
208+ // getExistingTemporalCLIFrom gets a list of paths and returns one of those that is an existing and working Temporal CLI binary
209+ func getExistingTemporalCLIFrom (possiblePaths []string ) (string , error ) {
200210 log .Printf ("Checking %d possible paths for temporal CLI" , len (possiblePaths ))
201211
202- // Test each possible path
212+ // Check all possible paths in parallel, pick the first one that works.
213+ pathFound := make (chan string )
214+ var wg sync.WaitGroup
215+ // This allows us to cancel whatever remaining work is done when we find a valid path.
216+ psCtx , psCancel := context .WithCancel (context .Background ())
203217 for i , path := range possiblePaths {
204- log .Printf ("Checking path %d/%d: %s" , i + 1 , len (possiblePaths ), path )
205- if _ , err := os .Stat (path ); err == nil {
218+ wg .Add (1 )
219+ go func () {
220+ defer wg .Done ()
221+ log .Printf ("Checking path %d/%d: %s" , i + 1 , len (possiblePaths ), path )
222+ if _ , err := os .Stat (path ); err != nil {
223+ log .Printf ("File does not exist at %s: %v" , path , err )
224+ return
225+ }
206226 log .Printf ("File exists at: %s" , path )
207227 // File exists, test if it's executable and the right binary
208- cmd := exec .Command (path , "--version" )
209- if err := cmd .Run (); err == nil {
210- log .Printf ("Successfully verified temporal CLI at: %s" , path )
211- return path , nil
212- } else {
228+ cmd := exec .CommandContext (psCtx , path , "--version" )
229+ if err := cmd .Run (); err != nil {
213230 log .Printf ("Failed to verify temporal CLI at %s: %v" , path , err )
231+ return
214232 }
215- } else {
216- log .Printf ("File does not exist at %s: %v" , path , err )
217- }
233+ select {
234+ case pathFound <- path :
235+ log .Printf ("Successfully verified temporal CLI at: %s" , path )
236+ case <- psCtx .Done ():
237+ // No need to report the path not chosen.
238+ }
239+ }()
240+ }
241+ // We transform the workgroup wait into a channel so we can wait for either this or pathFound
242+ pathNotFound := make (chan bool )
243+ go func () {
244+ wg .Wait ()
245+ pathNotFound <- true
246+ }()
247+ select {
248+ case path := <- pathFound :
249+ psCancel () // Cancel the remaining search functions otherwise they'll just exist eternally.
250+ return path , nil
251+ case <- pathNotFound :
252+ // No need to do anything, this just says that none of the functions were able to do it and there's nothing left to cleanup
218253 }
219254
220255 return "" , fmt .Errorf ("temporal CLI not found in PATH or any of the expected locations: %v" , possiblePaths )
0 commit comments