@@ -421,33 +421,117 @@ public String checkEngineExist(@NonNull String engineName) throws CxException, I
421421 }
422422
423423 private String verifyEngineOnMAC (String engineName ,List <String >arguments ) throws CxException , IOException , InterruptedException {
424- Exception lastException = null ;
425- String enginePath ;
424+ this .logger .debug ("Verifying container engine '{}' on macOS" , engineName );
425+
426+ // First, try to find the engine via shell command (works when launched from terminal)
426427 try {
427- enginePath = Execution .executeCommand ((arguments ), logger , line ->line );
428- return enginePath ;
428+ String enginePath = Execution .executeCommand ((arguments ), logger , line ->line );
429+ if (enginePath != null && !enginePath .isEmpty ()) {
430+ this .logger .debug ("Found engine '{}' via shell command: {}" , engineName , enginePath );
431+ return enginePath ;
432+ }
429433 } catch (CxException | IOException e ) {
430- lastException = e ;
434+ this . logger . debug ( "Shell command lookup failed for '{}': {}" , engineName , e . getMessage ()) ;
431435 }
432- Path dockerPath = Paths .get (CxConstants .DOCKER_FALLBACK_PATH );
433- Path podmanPath = Paths .get (CxConstants .PODMAN_FALLBACK_PATH );
436+
437+ // Build list of fallback paths based on engine type
438+ // This handles the case when IntelliJ is launched via GUI (double-click) and doesn't inherit shell PATH
439+ List <String > fallbackPaths = new ArrayList <>();
434440 if (CxConstants .DOCKER .equalsIgnoreCase (engineName )) {
435- if (Files .isSymbolicLink (dockerPath )) {
436- return Files .readSymbolicLink (dockerPath ).toAbsolutePath ().toString ();
441+ fallbackPaths .add (CxConstants .DOCKER_FALLBACK_PATH ); // /usr/local/bin/docker
442+ fallbackPaths .add (CxConstants .DOCKER_HOMEBREW_PATH ); // /opt/homebrew/bin/docker (Apple Silicon)
443+ fallbackPaths .add (CxConstants .DOCKER_APP_PATH ); // /Applications/Docker.app/Contents/Resources/bin/docker
444+ // Add user home-based paths
445+ String userHome = System .getProperty ("user.home" );
446+ if (userHome != null ) {
447+ fallbackPaths .add (userHome + "/.docker/bin/docker" ); // Docker Desktop CLI
448+ fallbackPaths .add (userHome + "/.rd/bin/docker" ); // Rancher Desktop
449+ }
450+ } else if (CxConstants .PODMAN .equalsIgnoreCase (engineName )) {
451+ fallbackPaths .add (CxConstants .PODMAN_FALLBACK_PATH ); // /usr/local/bin/podman
452+ fallbackPaths .add (CxConstants .PODMAN_HOMEBREW_PATH ); // /opt/homebrew/bin/podman (Apple Silicon)
453+ // Add user home-based paths
454+ String userHome = System .getProperty ("user.home" );
455+ if (userHome != null ) {
456+ fallbackPaths .add (userHome + "/.local/bin/podman" );
437457 }
438- else { return dockerPath .toAbsolutePath ().toString (); }
439458 }
440- else if (CxConstants .PODMAN .equalsIgnoreCase (engineName )) {
441- if (Files .exists (podmanPath )) {
442- if (Files .isSymbolicLink (podmanPath )) {
443- return Files .readSymbolicLink (podmanPath ).toAbsolutePath ().toString ();
459+
460+ this .logger .debug ("Checking {} fallback paths for engine '{}'" , fallbackPaths .size (), engineName );
461+
462+ // Try each fallback path
463+ for (String pathStr : fallbackPaths ) {
464+ Path path = Paths .get (pathStr );
465+ this .logger .debug ("Checking fallback path: {}" , pathStr );
466+
467+ if (Files .exists (path )) {
468+ String resolvedPath ;
469+ try {
470+ if (Files .isSymbolicLink (path )) {
471+ resolvedPath = Files .readSymbolicLink (path ).toAbsolutePath ().toString ();
472+ this .logger .debug ("Resolved symlink {} -> {}" , pathStr , resolvedPath );
473+ } else {
474+ resolvedPath = path .toAbsolutePath ().toString ();
475+ }
476+
477+ // Verify the engine is executable and works
478+ if (verifyEngineExecutable (resolvedPath )) {
479+ this .logger .info ("Found working container engine '{}' at: {}" , engineName , resolvedPath );
480+ return resolvedPath ;
481+ }
482+ } catch (IOException e ) {
483+ this .logger .debug ("Failed to resolve path {}: {}" , pathStr , e .getMessage ());
444484 }
445- else {
446- return podmanPath .toAbsolutePath ().toString ();
485+ } else {
486+ this .logger .debug ("Path does not exist: {}" , pathStr );
487+ }
488+ }
489+
490+ String errorMsg = String .format (
491+ "Container engine '%s' not found. Checked paths: %s. " +
492+ "Please ensure Docker or Podman is installed and accessible. " +
493+ "If installed via Docker Desktop, try launching IntelliJ from terminal: open -a 'IntelliJ IDEA'" ,
494+ engineName , String .join (", " , fallbackPaths )
495+ );
496+ this .logger .error (errorMsg );
497+ throw new CxException (1 , errorMsg );
498+ }
499+
500+ /**
501+ * Verifies that the engine at the given path is executable and responds to --version.
502+ *
503+ * @param enginePath the absolute path to the container engine executable
504+ * @return true if the engine is working, false otherwise
505+ */
506+ private boolean verifyEngineExecutable (String enginePath ) {
507+ try {
508+ Path path = Paths .get (enginePath );
509+ if (!Files .exists (path ) || !Files .isExecutable (path )) {
510+ this .logger .debug ("Engine path '{}' is not executable" , enginePath );
511+ return false ;
512+ }
513+
514+ // Run a quick version check to verify the engine works
515+ ProcessBuilder pb = new ProcessBuilder (enginePath , "--version" );
516+ pb .redirectErrorStream (true );
517+ Process process = pb .start ();
518+ boolean completed = process .waitFor (5 , java .util .concurrent .TimeUnit .SECONDS );
519+
520+ if (completed && process .exitValue () == 0 ) {
521+ this .logger .debug ("Engine at '{}' verified successfully" , enginePath );
522+ return true ;
523+ } else {
524+ this .logger .debug ("Engine at '{}' failed verification (exit code: {}, completed: {})" ,
525+ enginePath , process .exitValue (), completed );
526+ if (!completed ) {
527+ process .destroyForcibly ();
447528 }
529+ return false ;
448530 }
531+ } catch (Exception e ) {
532+ this .logger .debug ("Engine verification failed for '{}': {}" , enginePath , e .getMessage ());
533+ return false ;
449534 }
450- throw new CxException ( 1 , "Engine '" + engineName + "' is not installed or not symlinked to /usr/local/bin." );
451535 }
452536
453537 private String checkEngine (String engineName , String osType ) throws CxException , IOException , InterruptedException {
0 commit comments