Skip to content

Commit dc1e322

Browse files
committed
Add GUI/CLI launch support for EIM path resolution
- Add CLI install path (~/.espressif/eim/) as final fallback in resolver - Add getDefaultCliEimPath() for platform-specific CLI binary locations - Add isEimGuiCapable() to detect GUI support via `eim gui --help` - Handle CLI-only EIM binaries on macOS by launching directly instead of attempting AppleScript open on non-.app paths - Add USER_EIM_CLI_DIR constant in EimConstants Aligns with vscode-esp-idf-extension PR #1822 approach of checking both GUI and CLI managed install locations and detecting GUI capability.
1 parent aa93952 commit dc1e322

3 files changed

Lines changed: 108 additions & 4 deletions

File tree

bundles/com.espressif.idf.core/src/com/espressif/idf/core/tools/EimConstants.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,7 @@ public interface EimConstants
2727

2828
String USER_EIM_DIR = Paths.get(System.getProperty("user.home"), ".espressif", "eim_gui").toString(); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
2929

30+
String USER_EIM_CLI_DIR = Paths.get(System.getProperty("user.home"), ".espressif", "eim").toString(); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
31+
3032
String EIM_JSON_VALID_VERSION = "1.0"; //$NON-NLS-1$
3133
}

bundles/com.espressif.idf.core/src/com/espressif/idf/core/tools/ToolInitializer.java

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import java.nio.file.Paths;
1212
import java.util.ArrayList;
1313
import java.util.List;
14+
import java.util.concurrent.TimeUnit;
1415

1516
import org.eclipse.core.resources.ResourcesPlugin;
1617
import org.eclipse.core.runtime.IPath;
@@ -65,9 +66,14 @@ private String findEimOnSystemPath()
6566
}
6667

6768
/**
68-
* Resolves the EIM executable path: <strong>system {@code PATH} first</strong>, then {@code eimPath} from
69-
* {@code eim_idf.json} when the path exists on disk, then {@code EIM_PATH} env variable, then
70-
* {@link #getDefaultEimPath()} (existence-checked).
69+
* Resolves the EIM executable path using priority-based resolution:
70+
* <ol>
71+
* <li>System {@code PATH}</li>
72+
* <li>{@code eimPath} from {@code eim_idf.json} (when the path exists on disk)</li>
73+
* <li>{@code EIM_PATH} env variable (existence-checked)</li>
74+
* <li>Default GUI install location (existence-checked)</li>
75+
* <li>Default CLI install location (existence-checked)</li>
76+
* </ol>
7177
*
7278
* @param eimJson parsed JSON or {@code null}
7379
* @return resolved absolute path string, or empty if nothing could be resolved
@@ -101,6 +107,12 @@ public String resolveEimExecutablePath(EimJson eimJson)
101107
return defaultEimPath.toString();
102108
}
103109

110+
Path cliEimPath = getDefaultCliEimPath();
111+
if (cliEimPath != null && Files.exists(cliEimPath))
112+
{
113+
return cliEimPath.toString();
114+
}
115+
104116
return StringUtil.EMPTY;
105117
}
106118

@@ -203,5 +215,55 @@ else if (os.equals(Platform.OS_MACOSX))
203215

204216
return defaultEimPath;
205217
}
206-
218+
219+
/**
220+
* Returns the default CLI EIM binary path per platform. Unlike the GUI path, this points to the CLI-only install
221+
* directory ({@code ~/.espressif/eim/}).
222+
*/
223+
public Path getDefaultCliEimPath()
224+
{
225+
String userHome = System.getProperty("user.home"); //$NON-NLS-1$
226+
String os = Platform.getOS();
227+
if (os.equals(Platform.OS_WIN32))
228+
{
229+
return Paths.get(userHome, ".espressif", "eim", "eim.exe"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
230+
}
231+
return Paths.get(userHome, ".espressif", "eim", "eim"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
232+
}
233+
234+
/**
235+
* Checks whether the EIM binary at the given path supports GUI mode by running {@code eim gui --help} and checking
236+
* for a successful exit code. This is used to determine whether to launch EIM as a GUI application or in CLI/wizard
237+
* mode.
238+
*
239+
* @param eimPath absolute path to the EIM executable
240+
* @return {@code true} if the binary supports the {@code gui} subcommand, {@code false} otherwise
241+
*/
242+
public boolean isEimGuiCapable(String eimPath)
243+
{
244+
if (StringUtil.isEmpty(eimPath))
245+
{
246+
return false;
247+
}
248+
249+
try
250+
{
251+
ProcessBuilder pb = new ProcessBuilder(eimPath, "gui", "--help"); //$NON-NLS-1$ //$NON-NLS-2$
252+
pb.redirectErrorStream(true);
253+
Process process = pb.start();
254+
boolean finished = process.waitFor(5, TimeUnit.SECONDS);
255+
if (!finished)
256+
{
257+
process.destroyForcibly();
258+
return false;
259+
}
260+
return process.exitValue() == 0;
261+
}
262+
catch (IOException | InterruptedException e)
263+
{
264+
Logger.log("EIM does not support the gui subcommand, falling back to CLI mode."); //$NON-NLS-1$
265+
return false;
266+
}
267+
}
268+
207269
}

bundles/com.espressif.idf.core/src/com/espressif/idf/core/tools/launch/strategies/MacOsEimLauncherStrategy.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ public MacOsEimLauncherStrategy(Display display, MessageConsoleStream standardCo
6262
@Override
6363
public LaunchResult launch(String eimPath) throws IOException
6464
{
65+
if (!isAppBundle(eimPath))
66+
{
67+
return launchCliDirect(eimPath);
68+
}
69+
6570
String appBundlePath = deriveAppBundlePath(eimPath);
6671
String execPath = deriveExecPath(eimPath, appBundlePath);
6772
String bundleId = readBundleId(appBundlePath);
@@ -108,6 +113,41 @@ public LaunchResult launch(String eimPath) throws IOException
108113
"osascript exit=" + exit + "\n" + out); //$NON-NLS-1$ //$NON-NLS-2$
109114
}
110115

116+
private LaunchResult launchCliDirect(String eimPath) throws IOException
117+
{
118+
String quotedPath = ProcessUtils.bashSingleQuote(eimPath);
119+
String bashCmd = "nohup " + quotedPath + " > /dev/null 2>&1 & echo $!"; //$NON-NLS-1$ //$NON-NLS-2$
120+
121+
Process launcher = new ProcessBuilder("bash", "-lc", bashCmd) //$NON-NLS-1$ //$NON-NLS-2$
122+
.redirectErrorStream(true).start();
123+
124+
String out = ProcessUtils.readAll(launcher.getInputStream());
125+
Long pid = ProcessUtils.parseFirstLongLine(out);
126+
127+
if (pid == null)
128+
{
129+
Logger.log("macOS CLI launcher output was:\n" + out); //$NON-NLS-1$
130+
throw new IOException("No PID found in launcher output. Output was:\n" + out); //$NON-NLS-1$
131+
}
132+
133+
return LaunchResult.ofPid(pid.longValue(), eimPath, out);
134+
}
135+
136+
private boolean isAppBundle(String eimPath)
137+
{
138+
Path p = Paths.get(eimPath).toAbsolutePath().normalize();
139+
while (p != null)
140+
{
141+
String name = p.getFileName() != null ? p.getFileName().toString() : ""; //$NON-NLS-1$
142+
if (name.endsWith(".app")) //$NON-NLS-1$
143+
{
144+
return true;
145+
}
146+
p = p.getParent();
147+
}
148+
return false;
149+
}
150+
111151
@Override
112152
public IStatus waitForExit(LaunchResult launchResult, IProgressMonitor monitor)
113153
{

0 commit comments

Comments
 (0)