Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#9: background process #200

Merged
merged 46 commits into from
Mar 1, 2024
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
3f6ddbe
First implementation of ToolCommandlet
MustaphaOuchen Feb 1, 2024
86c8eb9
jmc implementation
MustaphaOuchen Feb 2, 2024
201b01b
Add tag
MustaphaOuchen Feb 2, 2024
bde7dc4
Add parameter for background process
MustaphaOuchen Feb 2, 2024
6da4cc6
Fix typo
MustaphaOuchen Feb 2, 2024
9617b16
Add test
MustaphaOuchen Feb 5, 2024
d4169ad
Refactor test and commandlet for mac os
MustaphaOuchen Feb 5, 2024
efe4ff5
Add "mock file" for jmc on MAC OS
MustaphaOuchen Feb 5, 2024
86c153b
Add ascii doc stuff
MustaphaOuchen Feb 6, 2024
f9f202c
Improve javadoc suggestions
MustaphaOuchen Feb 6, 2024
417fe6d
Link issue id to todo comments
MustaphaOuchen Feb 6, 2024
25b6e51
Refactor jmcTest
MustaphaOuchen Feb 7, 2024
9955032
Fix LocalToolCommandLet logic
MustaphaOuchen Feb 7, 2024
2334b86
Add java requirement installation for JMC
MustaphaOuchen Feb 7, 2024
ca60b42
Make test more clean and neat and consider Java requirement for JMC
MustaphaOuchen Feb 7, 2024
21f997d
Add first implementation of background processes
MustaphaOuchen Feb 9, 2024
4761dc4
remove unnecessary comments
MustaphaOuchen Feb 9, 2024
e9e285e
Fix test
MustaphaOuchen Feb 9, 2024
8bb848f
Add experimental implementation of background processes
MustaphaOuchen Feb 13, 2024
3620202
Merge remote-tracking branch 'upstream/main' into feature/9-Backgroun…
MustaphaOuchen Feb 13, 2024
5cb2cd6
Merge remote-tracking branch 'upstream/main' into feature/9-Backgroun…
MustaphaOuchen Feb 13, 2024
560e4bb
Implement workaround idea for windows background process starting wit…
MustaphaOuchen Feb 13, 2024
c1da390
Merge remote-tracking branch 'upstream/main' into feature/9-Backgroun…
MustaphaOuchen Feb 13, 2024
2b1722e
Improve JavaDoc for ProcessContext and minor changes
MustaphaOuchen Feb 14, 2024
e80e21e
Fix typo
MustaphaOuchen Feb 14, 2024
dec1e0b
Fix test
MustaphaOuchen Feb 14, 2024
e336ac8
Fix typo
MustaphaOuchen Feb 15, 2024
94aef77
Merge branch 'main' into feature/9-BackgroundProcess
hohwille Feb 19, 2024
c55b761
Fix test and force inherit streams on unix systems
MustaphaOuchen Feb 20, 2024
6fdd2a0
minor refactor of variable place
MustaphaOuchen Feb 20, 2024
ae167cd
Merge branch 'main' into feature/9-BackgroundProcess
hohwille Feb 20, 2024
c865840
Add first refactoring after review WORK IN PROGRESS
MustaphaOuchen Feb 20, 2024
a11dbfa
Merge branch 'feature/9-BackgroundProcess' of https://github.com/Must…
MustaphaOuchen Feb 20, 2024
68c3b1b
Refactoring after review, using enums, added JavaDoc with concrete ex…
MustaphaOuchen Feb 21, 2024
8313cf7
Fix mismatch of ProcessMode in eclipse commandlet
MustaphaOuchen Feb 21, 2024
f288cf0
Add issue id to todo comment
MustaphaOuchen Feb 21, 2024
c303a48
Merge branch 'main' into feature/9-BackgroundProcess
hohwille Feb 23, 2024
fdcdece
Remove todo
MustaphaOuchen Feb 23, 2024
c70c70a
Add curly brackets
MustaphaOuchen Feb 23, 2024
2bcfa03
Add isBackgound() method in enum und use new logic
MustaphaOuchen Feb 23, 2024
3e35af8
Add javadoc comment
MustaphaOuchen Feb 26, 2024
38951e7
Refactor building of unix arguments by considuering possible passed p…
MustaphaOuchen Feb 26, 2024
a1ca068
Refactor eclipse commandlet to use processMode
MustaphaOuchen Feb 27, 2024
4e4f787
Merge remote-tracking branch 'origin/main' into feature/9-BackgroundP…
MustaphaOuchen Feb 27, 2024
8c3797c
Make git context to use processMode
MustaphaOuchen Feb 27, 2024
63b3c70
Minor renaming in test
MustaphaOuchen Feb 27, 2024
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 @@ -158,8 +158,11 @@ default ProcessResult run(boolean capture) {
* @param capture - {@code true} to capture standard {@link ProcessResult#getOut() out} and
* {@link ProcessResult#getErr() err} in the {@link ProcessResult}, {@code false} otherwise (to redirect out
* and err).
* @param runInBackground {@code true}, the process of the command will be run as background process, {@code false}
* otherwise (it will be run as foreground process).
* @param runInBackground {@code true}, the process of the command will be run like a background process,
* {@code false} otherwise (it will be run as foreground process). Technically the parent process will simply
* not await its child process. In Unix systems the equivalent of appending an '&' is used. On Windows cmd is
* used to start the process as externally and to capture the child process output. It is recommended to set
* this option to {@code true} when a UI-Application is started, which may act dependent of its parent process.
* @return the {@link ProcessResult}.
*/
ProcessResult run(boolean capture, boolean runInBackground);
Expand Down
168 changes: 115 additions & 53 deletions cli/src/main/java/com/devonfw/tools/ide/process/ProcessContextImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

import com.devonfw.tools.ide.cli.CliException;
import com.devonfw.tools.ide.context.IdeContext;
Expand Down Expand Up @@ -98,9 +99,9 @@ public ProcessContext withEnvVar(String key, String value) {
@Override
public ProcessResult run(boolean capture, boolean isBackgroundProcess) {

if (isBackgroundProcess) {
this.context
.warning("TODO https://github.com/devonfw/IDEasy/issues/9 Implement background process functionality");
if (capture && isBackgroundProcess) {
throw new IllegalStateException(
"It is not possible for the main process to capture the streams of the subprocess (in a background process) !");
}

if (this.executable == null) {
Expand All @@ -109,72 +110,47 @@ public ProcessResult run(boolean capture, boolean isBackgroundProcess) {
String executableName = this.executable.toString();
// pragmatic solution to avoid copying lists/arrays
this.arguments.add(0, executableName);
String fileExtension = FilenameUtil.getExtension(executableName);
boolean isBashScript = "sh".equals(fileExtension) || hasSheBang(this.executable);
if (isBashScript) {
String bash = "bash";
if (this.context.getSystemInfo().isWindows()) {
String findBashOnWindowsResult = findBashOnWindows();
if (findBashOnWindowsResult != null) {
bash = findBashOnWindowsResult;
}
}
this.arguments.add(0, bash);
}
this.processBuilder.command(this.arguments);

boolean isBashScript = checkAndHandlePossibleBashScript(executableName);

if (this.context.debug().isEnabled()) {
String message = createCommandMessage(" ...");
this.context.debug(message);
}

try {

if (capture) {
this.processBuilder.redirectOutput(Redirect.PIPE).redirectError(Redirect.PIPE);
} else if (isBackgroundProcess) {
modifyArgumentsOnBackgroundProcess(isBashScript);
}

this.processBuilder.command(this.arguments);

Process process = this.processBuilder.start();

List<String> out = null;
List<String> err = null;
Process process = this.processBuilder.start();
if (capture) {
out = new ArrayList<>();
err = new ArrayList<>();
try (BufferedReader outReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
BufferedReader errReader = new BufferedReader(new InputStreamReader(process.getErrorStream()))) {
String outLine = "";
String errLine = "";
while ((outLine != null) || (errLine != null)) {
if (outLine != null) {
outLine = outReader.readLine();
if (outLine != null) {
out.add(outLine);
}
}
if (errLine != null) {
errLine = errReader.readLine();
if (errLine != null) {
err.add(errLine);
}
}
}
}
handleCapture(process, out, err);
}
int exitCode = process.waitFor();
ProcessResult result = new ProcessResultImpl(exitCode, out, err);
if (!result.isSuccessful() && (this.errorHandling != ProcessErrorHandling.NONE)) {
String message = createCommandMessage(" failed with exit code " + exitCode + "!");
if (this.errorHandling == ProcessErrorHandling.THROW) {
throw new CliException(message, exitCode);
}
IdeSubLogger level;
if (this.errorHandling == ProcessErrorHandling.ERROR) {
level = this.context.error();
} else if (this.errorHandling == ProcessErrorHandling.WARNING) {
level = this.context.warning();
} else {
level = this.context.error();
level.log("Internal error: Undefined error handling {}", this.errorHandling);
}
level.log(message);

int exitCode;
if (isBackgroundProcess) {
// TODO: Ask if a background process shall get its own process result or if we should assume success?
exitCode = ProcessResult.SUCCESS;
} else {
exitCode = process.waitFor();
}

ProcessResult result = new ProcessResultImpl(exitCode, out, err);
performLogOnError(result, exitCode);

return result;

} catch (Exception e) {
String msg = e.getMessage();
if ((msg == null) || msg.isEmpty()) {
Expand Down Expand Up @@ -272,4 +248,90 @@ private String findBashOnWindows() {
throw new IllegalStateException("Could not find Bash. Please install Git for Windows and rerun.");
}

private void handleCapture(Process process, List<String> out, List<String> err) throws IOException {

try (BufferedReader outReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
BufferedReader errReader = new BufferedReader(new InputStreamReader(process.getErrorStream()))) {
String outLine = "";
String errLine = "";
while ((outLine != null) || (errLine != null)) {
if (outLine != null) {
outLine = outReader.readLine();
if (outLine != null) {
out.add(outLine);
}
}
if (errLine != null) {
errLine = errReader.readLine();
if (errLine != null) {
err.add(errLine);
}
}
}
}
}

private boolean checkAndHandlePossibleBashScript(String executableName) {

String fileExtension = FilenameUtil.getExtension(executableName);
boolean isBashScript = "sh".equals(fileExtension) || hasSheBang(this.executable);
if (isBashScript) {
String bash = "bash";
if (this.context.getSystemInfo().isWindows()) {
String findBashOnWindowsResult = findBashOnWindows();
if (findBashOnWindowsResult != null) {
bash = findBashOnWindowsResult;
}
}
this.arguments.add(0, bash);
}
return isBashScript;
}

private void performLogOnError(ProcessResult result, int exitCode) {

if (!result.isSuccessful() && (this.errorHandling != ProcessErrorHandling.NONE)) {
String message = createCommandMessage(" failed with exit code " + exitCode + "!");
if (this.errorHandling == ProcessErrorHandling.THROW) {
throw new CliException(message, exitCode);
}
IdeSubLogger level;
if (this.errorHandling == ProcessErrorHandling.ERROR) {
level = this.context.error();
} else if (this.errorHandling == ProcessErrorHandling.WARNING) {
level = this.context.warning();
} else {
level = this.context.error();
level.log("Internal error: Undefined error handling {}", this.errorHandling);
}
level.log(message);
}
}

private void modifyArgumentsOnBackgroundProcess(boolean isBashScript) {

if (context.getSystemInfo().isWindows() && !isBashScript) {

this.context.warning(
"Currently starting the process as background process in windows will use 'more' command to redirect output to a new cmd window!");

this.processBuilder.redirectOutput(Redirect.PIPE).redirectError(Redirect.PIPE);
List<String> windowsArgs = List.of("cmd.exe", "/c", "start", "cmd.exe", "/k");

String currentCommandToRunInCmd = this.arguments.stream().map(Object::toString).collect(Collectors.joining(" "));
currentCommandToRunInCmd += " | more";

this.arguments.clear();

List<String> newArgs = new ArrayList<>(windowsArgs);
newArgs.add(currentCommandToRunInCmd);

this.arguments.addAll(newArgs);

} else {
// just assume unix system for now
this.arguments.add("&");
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ protected ProcessResult runEclipse(boolean log, String... args) {
Path javaPath = getCommandlet(Java.class).getToolBinPath();
pc.addArg("-vm").addArg(javaPath);
pc.addArgs(args);
return pc.run(log, false);
return pc.run(log, true);
}

@Override
Expand Down
Loading
Loading