Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,47 @@ public class EimLoader
{
private static final String URL_JSON = "https://dl.espressif.com/dl/eim/eim_unified_release.json"; //$NON-NLS-1$
private static final Path DOWNLOAD_DIR = Paths.get(System.getProperty("java.io.tmpdir"), "eim_gui"); //$NON-NLS-1$ //$NON-NLS-2$
private static final String MACOS_LAUNCH_AND_PID_APPLESCRIPT = """
set appPath to system attribute "APP_PATH"
set bundlePrefix to system attribute "BUNDLE_PREFIX"

-- Launch app
do shell script "open -a " & quoted form of appPath

-- Try System Events first (may require Automation permission)
try
tell application "System Events"
repeat 100 times
set matches to (processes whose bundle identifier starts with bundlePrefix)
if (count of matches) > 0 then
return unix id of (item 1 of matches)
end if
delay 0.1
end repeat
end tell
on error errMsg number errNum
-- fall through to pgrep fallback
end try

-- Fallback: pgrep (does not require System Events)
repeat 100 times
try
set pidStr to do shell script "pgrep -fn " & quoted form of bundlePrefix
if pidStr is not "" then return pidStr as number
end try
delay 0.1
end repeat

error "PID not found (app may not have launched)"
"""; //$NON-NLS-1$

private String os;
private String arch;
private DownloadListener listener;
private MessageConsoleStream standardConsoleStream;
private MessageConsoleStream errorConsoleStream;
private Display display;
private long windowsPid;
private long eimPid;

public EimLoader(DownloadListener listener, MessageConsoleStream standardConsoleStream, MessageConsoleStream errorConsoleStream, Display display)
{
Expand Down Expand Up @@ -103,76 +136,152 @@ private void logError(String message)
Logger.log(message);
}

public Process launchEim(String eimPath) throws IOException
public long launchEim(String eimPath) throws IOException
{
if (!Files.exists(Paths.get(eimPath)))
throw new FileNotFoundException("EIM path not found: " + eimPath); //$NON-NLS-1$

String os = Platform.getOS();
List<String> command;
String osLocal = Platform.getOS();

if (os.equals(Platform.OS_WIN32))
if (osLocal.equals(Platform.OS_MACOSX))
{
String escapedPathForPowershell = eimPath.replace("'", "''"); //$NON-NLS-1$ //$NON-NLS-2$
String powershellCmd = String.format(
"Start-Process -FilePath '%s' -PassThru | " //$NON-NLS-1$
+ "Select-Object -ExpandProperty Id", //$NON-NLS-1$
escapedPathForPowershell);

command = List.of("powershell.exe", //$NON-NLS-1$
"-Command", powershellCmd); //$NON-NLS-1$
eimPid = launchMacAndGetPid(eimPath);
logMessage("Launched EIM application: " + eimPath + " (pid=" + eimPid + ")\n"); //$NON-NLS-1$ //$NON-NLS-2$
return eimPid;
}
else if (os.equals(Platform.OS_MACOSX))

List<String> command;
if (osLocal.equals(Platform.OS_WIN32))
{
command = List.of("open", "-W", "-a", eimPath); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$
command = windowsLaunchCommand(eimPath);
}
else if (os.equals(Platform.OS_LINUX))
else if (osLocal.equals(Platform.OS_LINUX))
{
command = List.of("bash", "-c", "\"" + eimPath + "\""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
command = linuxLaunchCommand(eimPath);
}
else
{
throw new UnsupportedOperationException("Unsupported OS: " + os); //$NON-NLS-1$
throw new UnsupportedOperationException("Unsupported OS: " + osLocal); //$NON-NLS-1$
}

Process process = new ProcessBuilder(command).redirectErrorStream(true).start();
if (os.equals(Platform.OS_WIN32))

Process launcher = new ProcessBuilder(command).redirectErrorStream(true).start();
storePid(launcher);

logMessage("Launched EIM application: " + eimPath + " (pid=" + eimPid + ")\n"); //$NON-NLS-1$ //$NON-NLS-2$
return eimPid;
}

private long launchMacAndGetPid(String eimPath) throws IOException
{
String bundlePrefix = "com.espressif.eim"; //$NON-NLS-1$
String appPath = deriveAppBundlePath(eimPath);

ProcessBuilder pb = new ProcessBuilder("osascript", "-"); //$NON-NLS-1$ //$NON-NLS-2$
pb.redirectErrorStream(true);

pb.environment().put("APP_PATH", appPath); //$NON-NLS-1$
pb.environment().put("BUNDLE_PREFIX", bundlePrefix); //$NON-NLS-1$

Process p = pb.start();

// Send AppleScript via stdin
try (OutputStream stdin = p.getOutputStream())
{
// store the PID returned by powershell query to a variable
storePid(process);
stdin.write(MACOS_LAUNCH_AND_PID_APPLESCRIPT.getBytes(java.nio.charset.StandardCharsets.UTF_8));
}

logMessage("Launched EIM application: " + eimPath + "\n"); //$NON-NLS-1$ //$NON-NLS-2$
// Read PID from stdout
long pid = readPidFromProcessOutput(p);

Logger.log("APP_PATH=" + appPath); //$NON-NLS-1$
// Ensure osascript finished successfully (otherwise you might have read some partial output)
try
{
int exit = p.waitFor();
if (exit != 0)
{
throw new IOException("osascript failed (exit " + exit + ")"); //$NON-NLS-1$ //$NON-NLS-2$
}
}
catch (InterruptedException e)
{
Thread.currentThread().interrupt();
throw new IOException("Interrupted while waiting for osascript", e); //$NON-NLS-1$
}

return process;
return pid;
}
private void storePid(Process powershellProcess)

private long readPidFromProcessOutput(Process p) throws IOException
{
try (BufferedReader reader = new BufferedReader(new InputStreamReader(powershellProcess.getInputStream())))
StringBuilder out = new StringBuilder();
try (BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream(),
java.nio.charset.StandardCharsets.UTF_8)))
{
String line;
while ((line = reader.readLine()) != null)
while ((line = br.readLine()) != null)
{
line = line.trim();
if (!line.isEmpty())
{
try
{
windowsPid = Long.parseLong(line);

}
catch (NumberFormatException ignored)
{
// skipping invalid lines
}
}
out.append(line).append('\n');
String trimmed = line.trim();
if (trimmed.matches("\\d+")) //$NON-NLS-1$
return Long.parseLong(trimmed);
}
}
catch (IOException e)

String output = out.toString().trim();
Logger.log("Launcher output was:\n" + output); //$NON-NLS-1$

throw new IOException("No PID found in launcher output. Output was:\n" + output); //$NON-NLS-1$
}


private String deriveAppBundlePath(String eimPath)
{
Path p = Paths.get(eimPath).toAbsolutePath().normalize();

// Walk up until we find *.app
while (p != null)
{
Logger.log(e);
String name = p.getFileName() != null ? p.getFileName().toString() : ""; //$NON-NLS-1$
if (name.endsWith(".app")) //$NON-NLS-1$
{
return p.toString(); // ALWAYS absolute due to toAbsolutePath() above
}
Comment on lines +160 to 166

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Ensure listeners are unpaused even if the callback fails.
If callback.run() throws, unpauseListeners() is skipped, leaving listeners permanently paused. Wrap the callback in try/finally.

✅ Proposed fix
-				if (callback != null)
-				{
-					callback.run();
-				}
-
-				EimJsonWatchService.getInstance().unpauseListeners();
+				try
+				{
+					if (callback != null)
+						callback.run();
+				}
+				catch (Exception e)
+				{
+					Logger.log(e);
+				}
+				finally
+				{
+					EimJsonWatchService.getInstance().unpauseListeners();
+				}
🤖 Prompt for AI Agents
In
`@bundles/com.espressif.idf.core/src/com/espressif/idf/core/tools/EimLoader.java`
around lines 160 - 166, In the block inside EimLoader (where callback.run() is
invoked and EimJsonWatchService.getInstance().unpauseListeners() is called),
ensure listeners are always unpaused by wrapping the callback invocation in a
try/finally: check callback != null, call callback.run() inside the try, and
call EimJsonWatchService.getInstance().unpauseListeners() in the finally so
unpauseListeners() executes even if callback.run() throws.

p = p.getParent();
}

throw new IllegalArgumentException("Cannot derive .app bundle path from: " + eimPath); //$NON-NLS-1$
}


private List<String> windowsLaunchCommand(String eimPath)
{
String escapedPathForPowershell = eimPath.replace("'", "''"); //$NON-NLS-1$ //$NON-NLS-2$
String powershellCmd = String.format(
"Start-Process -FilePath '%s' -PassThru | " //$NON-NLS-1$
+ "Select-Object -ExpandProperty Id", //$NON-NLS-1$
escapedPathForPowershell);

return List.of("powershell.exe", //$NON-NLS-1$
"-Command", powershellCmd); //$NON-NLS-1$
}

private List<String> linuxLaunchCommand(String eimPath)
{
String quotedPath = bashSingleQuote(eimPath);
String bashCmd = "nohup " + quotedPath + " > /dev/null 2>&1 & echo $!"; //$NON-NLS-1$ //$NON-NLS-2$
return List.of("bash", "-lc", bashCmd); //$NON-NLS-1$ //$NON-NLS-2$
}

private String bashSingleQuote(String input)
{
return "'" + input.replace("'", "'\"'\"'") + "'"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
}

private void storePid(Process launcherProcess) throws IOException
{
// For Windows/Linux launchers we expect PID to be printed to stdout.
eimPid = readPidFromProcessOutput(launcherProcess);
}

public String installAndLaunchDmg(Path dmgPath) throws IOException, InterruptedException
Expand Down Expand Up @@ -304,7 +413,7 @@ else if (name.endsWith(".exe")) //$NON-NLS-1$

private IStatus waitForProcessWindows()
{
while (isWindowsProcessAlive(windowsPid))
while (isWindowsProcessAlive(eimPid))
{
try
{
Expand Down Expand Up @@ -333,7 +442,7 @@ private boolean isWindowsProcessAlive(long pid)
String line;
while ((line = reader.readLine()) != null)
{
if (line.contains(String.valueOf(windowsPid)))
if (line.contains(String.valueOf(eimPid)))
{
return true;
}
Expand All @@ -348,6 +457,31 @@ private boolean isWindowsProcessAlive(long pid)
return false;
}

private IStatus waitForProcessByPid(long pid)
{
if (pid <= 0)
return Status.error("Invalid PID: " + pid); //$NON-NLS-1$

try
{
while (ProcessHandle.of(pid).map(ProcessHandle::isAlive).orElse(false))
{
Thread.sleep(1000);
}
return Status.OK_STATUS;
}
catch (InterruptedException e)
{
Thread.currentThread().interrupt();
return Status.CANCEL_STATUS;
}
catch (Exception e)
{
Logger.log(e);
return Status.error(e.getMessage());
}
}

private IStatus waitForProcess(Process process)
{
try
Expand All @@ -366,14 +500,14 @@ private IStatus waitForProcess(Process process)
}
}

public void waitForEimClosure(Process process, Runnable callback)
{
public void waitForEimClosure(long pidToWait, Runnable callback)
{
Job waitJob = new Job("Wait for EIM Closure") //$NON-NLS-1$
{
@Override
protected IStatus run(IProgressMonitor monitor)
{
return os.equals(Platform.OS_WIN32) ? waitForProcessWindows() : waitForProcess(process);
return waitForProcessByPid(pidToWait);
}
};
waitJob.setSystem(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,8 @@ protected IStatus run(IProgressMonitor monitor)
{
try
{
Process process = eimLoader.launchEim(idfEnvironmentVariables.getEnvValue(IDFEnvironmentVariables.EIM_PATH));
eimLoader.waitForEimClosure(process, EimButtonLaunchListener.this::refreshAfterEimClose);
long eimPid = eimLoader.launchEim(idfEnvironmentVariables.getEnvValue(IDFEnvironmentVariables.EIM_PATH));
eimLoader.waitForEimClosure(eimPid, EimButtonLaunchListener.this::refreshAfterEimClose);
}
catch (IOException e)
{
Expand Down Expand Up @@ -194,12 +194,12 @@ public void onCompleted(String filePath)
}
}

Process process;
long pidEim = -1;
try
{
idfEnvironmentVariables.addEnvVariable(IDFEnvironmentVariables.EIM_PATH, appToLaunch);
process = eimLoader.launchEim(appToLaunch);
eimLoader.waitForEimClosure(process, EimButtonLaunchListener.this::refreshAfterEimClose);
pidEim = eimLoader.launchEim(appToLaunch);
eimLoader.waitForEimClosure(pidEim, EimButtonLaunchListener.this::refreshAfterEimClose);
}
catch (IOException e)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,7 @@ public void onCompleted(String filePath)
}
});

Process process = null;
long eimPid = -1;
String appToLaunch = filePath;
try
{
Expand All @@ -386,7 +386,7 @@ public void onCompleted(String filePath)
}

idfEnvironmentVariables.addEnvVariable(IDFEnvironmentVariables.EIM_PATH, appToLaunch);
process = eimLoader.launchEim(appToLaunch);
eimPid = eimLoader.launchEim(appToLaunch);
}
catch (
IOException
Expand All @@ -395,7 +395,7 @@ public void onCompleted(String filePath)
Logger.log(e);
}

eimLoader.waitForEimClosure(process, () -> {
eimLoader.waitForEimClosure(eimPid, () -> {
if (toolInitializer.isOldEspIdfConfigPresent() && !toolInitializer.isOldConfigExported())
{
Logger.log("Old configuration found and not converted");
Expand Down
Loading