Utility class for creating and executing system commands and shell scripts

This commit is contained in:
Roman Shevchenko
2011-08-08 19:38:13 +04:00
parent 99cf24d3ca
commit 55e2b430a9
6 changed files with 184 additions and 122 deletions
@@ -15,8 +15,6 @@
*/
package com.intellij.ide.actions;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationType;
import com.intellij.notification.Notifications;
@@ -32,6 +30,7 @@ import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.system.ExecUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.ui.AppUIUtil;
import com.intellij.util.PlatformUtils;
@@ -41,8 +40,9 @@ import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import static com.intellij.util.containers.CollectionFactory.hashMap;
import static java.util.Arrays.asList;
public class CreateDesktopEntryAction extends AnAction {
private static final Logger LOG = Logger.getInstance("#com.intellij.ide.actions.CreateDesktopEntryAction");
@@ -87,7 +87,8 @@ public class CreateDesktopEntryAction extends AnAction {
if (!StringUtil.isEmptyOrSpaces(message)) {
LOG.warn(e);
Notifications.Bus.notify(
new Notification(Notifications.SYSTEM_MESSAGES_GROUP_ID, "Unable to create desktop entry", message, NotificationType.ERROR)
new Notification(Notifications.SYSTEM_MESSAGES_GROUP_ID, "Failed to create desktop entry", message, NotificationType.ERROR),
event.getProject()
);
}
else {
@@ -98,19 +99,9 @@ public class CreateDesktopEntryAction extends AnAction {
});
}
private static void check() {
final GeneralCommandLine cmd = new GeneralCommandLine();
cmd.setExePath("which");
cmd.addParameter("xdg-desktop-menu");
try {
final Process process = cmd.createProcess();
final int result = process.waitFor();
if (result != 0) throw new Exception("'" + cmd.getCommandLineString() + "' : " + result);
}
catch (Exception e) {
throw new RuntimeException(ApplicationBundle.message("desktop.entry.xdg.missing"), e);
}
private static void check() throws IOException, InterruptedException {
final int result = ExecUtil.execAndGetResult("which", "xdg-desktop-menu");
if (result != 0) throw new RuntimeException(ApplicationBundle.message("desktop.entry.xdg.missing"));
}
private static File prepare() throws IOException {
@@ -134,19 +125,13 @@ public class CreateDesktopEntryAction extends AnAction {
final String wmClass = AppUIUtil.getFrameClass();
final InputStream stream = CreateDesktopEntryAction.class.getClassLoader().getResourceAsStream("entry.desktop");
assert stream != null : CreateDesktopEntryAction.class.getClassLoader();
@SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
String content = FileUtil.loadTextAndClose(new InputStreamReader(stream));
content = StringUtil.replace(content, "$NAME$", name);
content = StringUtil.replace(content, "$SCRIPT$", execPath);
content = StringUtil.replace(content, "$ICON$", iconPath);
content = StringUtil.replace(content, "$WM_CLASS$", wmClass);
final String content = ExecUtil.loadTemplate(CreateDesktopEntryAction.class.getClassLoader(), "entry.desktop",
hashMap(asList("$NAME$", "$SCRIPT$", "$ICON$", "$WM_CLASS$"),
asList(name, execPath, iconPath, wmClass)));
final String entryName = wmClass + ".desktop";
final File entryFile = new File(FileUtil.getTempDirectory(), entryName);
FileUtil.writeToFile(entryFile, content);
return entryFile;
}
@@ -193,14 +178,10 @@ public class CreateDesktopEntryAction extends AnAction {
return null;
}
private static void install(final File entryFile) throws ExecutionException, InterruptedException {
final GeneralCommandLine cmd = new GeneralCommandLine();
cmd.setExePath("xdg-desktop-menu");
cmd.addParameters("install", "--mode", "user", entryFile.getAbsolutePath());
private static void install(final File entryFile) throws IOException, InterruptedException {
try {
final Process process = cmd.createProcess();
final int result = process.waitFor();
if (result != 0) throw new RuntimeException("'" + cmd.getCommandLineString() + "' : " + result);
final int result = ExecUtil.execAndGetResult("xdg-desktop-menu", "install", "--mode", "user", entryFile.getAbsolutePath());
if (result != 0) throw new RuntimeException("'" + entryFile.getAbsolutePath() + "' : " + result);
}
finally {
if (!entryFile.delete()) LOG.error("Failed to delete temp file '" + entryFile + "'");
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2010 JetBrains s.r.o.
* Copyright 2000-2011 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,11 +15,12 @@
*/
package com.intellij.ide.actions;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationType;
import com.intellij.notification.Notifications;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.PlatformDataKeys;
import com.intellij.openapi.application.ApplicationBundle;
import com.intellij.openapi.application.ApplicationNamesInfo;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.diagnostic.Logger;
@@ -27,18 +28,15 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.system.ExecUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.CharsetToolkit;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.swing.*;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import static com.intellij.util.containers.CollectionFactory.hashMap;
import static java.util.Arrays.asList;
/**
* @author yole
@@ -48,8 +46,7 @@ public class CreateLauncherScriptAction extends AnAction {
@Override
public void actionPerformed(AnActionEvent e) {
Project project = e.getData(PlatformDataKeys.PROJECT);
showDialog(project);
showDialog(e.getProject());
}
public static void showDialog(Project project) {
@@ -67,7 +64,7 @@ public class CreateLauncherScriptAction extends AnAction {
final File scriptFile = createLauncherScriptFile();
final File scriptTarget = new File(pathName);
if (scriptTarget.exists()) {
int rc = Messages.showOkCancelDialog(project, "The file " + scriptTarget + " already exists. Would you like to overwrite it?",
int rc = Messages.showOkCancelDialog(project, ApplicationBundle.message("launcher.script.overwrite", scriptTarget),
"Create Launcher Script", Messages.getQuestionIcon());
if (rc != 0) {
return;
@@ -79,94 +76,47 @@ public class CreateLauncherScriptAction extends AnAction {
!scriptFile.renameTo(scriptTarget)) {
final String launcherScriptContainingDirPath = launcherScriptContainingDir.getCanonicalPath();
final String installationScriptSrc =
"#!/bin/sh\n" +
// create all intermediate folders
"mkdir -p " + launcherScriptContainingDirPath + "\n" +
// Copy file & change owner to root
// uid 0 = root
// gid 0 = root || wheel (MacOS)
"install -g 0 -o 0 " + scriptFile.getCanonicalPath() + " " + pathName;
final File installationScript = createTempExecutableScript("launcher_installer", installationScriptSrc);
sudo(installationScript.getCanonicalPath(),
installationScriptSrc,
"Please enter your password to create a launcher script in a " + launcherScriptContainingDirPath);
"mkdir -p \"" + launcherScriptContainingDirPath + "\"\n" +
// copy file and change ownership to root (UID 0 = root, GID 0 = root (wheel on Macs))
"install -g 0 -o 0 \"" + scriptFile.getCanonicalPath() + "\" \"" + pathName + "\"";
final File installationScript = ExecUtil.createTempExecutableScript("launcher_installer", ".sh", installationScriptSrc);
ExecUtil.sudo(installationScript.getAbsolutePath(),
ApplicationBundle.message("launcher.script.sudo.prompt", launcherScriptContainingDirPath));
}
}
catch (Exception e) {
LOG.info(e);
Messages.showErrorDialog(project, "Failed to create launcher script: " + e.getMessage(), "Create Launcher Script");
}
}
private static boolean sudo(final String installationScriptPath,
final String installScriptSrc,
final String prompt) throws IOException, ScriptException, ExecutionException {
if (SystemInfo.isMac) {
final ScriptEngine engine = new ScriptEngineManager(null).getEngineByName("AppleScript");
if (engine == null) {
throw new IOException("Could not find AppleScript engine");
}
engine.eval("do shell script \"" + installationScriptPath + "\" with administrator privileges");
}
else {
GeneralCommandLine cmdLine = new GeneralCommandLine();
if (SystemInfo.isGnome) {
cmdLine.setExePath("gksudo");
cmdLine.addParameters("--message", prompt);
}
else if (SystemInfo.isKDE) {
cmdLine.setExePath("kdesudo");
final String message = e.getMessage();
if (!StringUtil.isEmptyOrSpaces(message)) {
LOG.warn(e);
Notifications.Bus.notify(
new Notification(Notifications.SYSTEM_MESSAGES_GROUP_ID, "Failed to create launcher script", message, NotificationType.ERROR),
project
);
}
else {
Messages.showMessageDialog("Unsupported graphical environment. Please execute the following command from the shell:\n" + installScriptSrc,
"Create Launcher Script",
Messages.getInformationIcon());
return true;
LOG.error(e);
}
cmdLine.addParameter(installationScriptPath);
cmdLine.createProcess();
}
return false;
}
private static File createLauncherScriptFile() throws IOException {
final File tempFile = FileUtil.createTempFile("launcher", "");
final InputStream stream = CreateLauncherScriptAction.class.getClassLoader().getResourceAsStream("launcher.py");
@SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
String launcherContents = FileUtil.loadTextAndClose(new InputStreamReader(stream));
launcherContents = launcherContents.replace("$CONFIG_PATH$", PathManager.getConfigPath());
String homePath = PathManager.getHomePath();
if (SystemInfo.isMac) {
// Just use "*.app"
launcherContents = launcherContents.replace("$RUN_PATH$", homePath);
String runPath = PathManager.getHomePath();
if (!SystemInfo.isMac) {
// for Macs just use "*.app"
final String productName = ApplicationNamesInfo.getInstance().getProductName().toLowerCase();
runPath += "/bin/" + productName + ".sh";
}
else {
String productName = ApplicationNamesInfo.getInstance().getProductName().toLowerCase();
launcherContents = launcherContents.replace("$RUN_PATH$", homePath + "/bin/" + productName + ".sh");
}
FileUtil.writeToFile(tempFile, launcherContents.getBytes(CharsetToolkit.UTF8_CHARSET));
if (!tempFile.setExecutable(true)) {
throw new IOException("Failed to mark the launcher script as executable");
}
return tempFile;
}
private static File createTempExecutableScript(final String fileNamePrefix,
final String source) throws IOException {
final File tempFile = FileUtil.createTempFile(fileNamePrefix, "");
FileUtil.writeToFile(tempFile, source.getBytes(CharsetToolkit.UTF8_CHARSET));
if (!tempFile.setExecutable(true)) {
throw new IOException("Failed to mark the launcher installation script as executable: script path " + tempFile.getCanonicalPath());
}
return tempFile;
final String launcherContents = ExecUtil.loadTemplate(CreateLauncherScriptAction.class.getClassLoader(), "launcher.py",
hashMap(asList("$CONFIG_PATH$", "$RUN_PATH$"),
asList(PathManager.getConfigPath(), runPath)));
return ExecUtil.createTempExecutableScript("launcher", "", launcherContents);
}
public static String defaultScriptName() {
final String productName = ApplicationNamesInfo.getInstance().getScriptName();
return StringUtil.isEmptyOrSpaces(productName) ? "idea" : productName;
final String scriptName = ApplicationNamesInfo.getInstance().getScriptName();
return StringUtil.isEmptyOrSpaces(scriptName) ? "idea" : scriptName;
}
@Override
@@ -532,6 +532,8 @@ auto.import=Auto Import
checkbox.collapse.suppress.warnings=<html>@SuppressWarnings</html>
checkbox.collapse.end.of.line.comments=<html>End of line comments sequence</html>
title.other.languages=Other Languages
launcher.script.overwrite=The file ''{0}'' already exists. Would you like to overwrite it?
launcher.script.sudo.prompt=Please enter your password to create a launcher script in a ''{0}''
desktop.entry.success=You may now exit {0} and start it from the system menu.
desktop.entry.xdg.missing=Unfortunately, xdg-utils seem to be unavailable on your system.
desktop.entry.icon.missing=Unable to locate suitable application icon in ''{0}''.
@@ -262,6 +262,12 @@ public class FileUtil {
return buffer.toByteArray();
}
@NotNull
public static String loadTextAndClose(@NotNull InputStream stream) throws IOException {
//noinspection IOResourceOpenedButNotSafelyClosed
return loadTextAndClose(new InputStreamReader(stream));
}
@NotNull
public static String loadTextAndClose(@NotNull Reader reader) throws IOException {
try {
@@ -0,0 +1,110 @@
/*
* Copyright 2000-2011 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.openapi.util.system;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.io.FileUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
public class ExecUtil {
private ExecUtil() { }
public static int execAndGetResult(final String... command) throws IOException, InterruptedException {
assert command != null && command.length > 0;
return execAndGetResult(Arrays.asList(command));
}
public static int execAndGetResult(@NotNull final List<String> command) throws IOException, InterruptedException {
assert command.size() > 0;
final ProcessBuilder processBuilder = new ProcessBuilder(command);
final Process process = processBuilder.start();
return process.waitFor();
}
public static String loadTemplate(@NotNull final ClassLoader loader,
@NotNull final String templateName,
@Nullable final Map<String, String> variables) throws IOException {
final InputStream stream = loader.getResourceAsStream(templateName);
if (stream == null) {
throw new IOException("Template '" + templateName + "' not found by " + loader);
}
final StringBuilder template = new StringBuilder(FileUtil.loadTextAndClose(stream));
if (variables != null) {
for (Map.Entry<String, String> var : variables.entrySet()) {
final String name = var.getKey();
final int pos = template.indexOf(name);
if (pos >= 0) {
template.replace(pos, pos + name.length(), var.getValue());
}
}
}
return template.toString();
}
public static File createTempExecutableScript(@NotNull final String prefix,
@NotNull final String suffix,
@NotNull final String source) throws IOException {
final File tempFile = FileUtil.createTempFile(prefix, suffix);
FileUtil.writeToFile(tempFile, source);
if (!tempFile.setExecutable(true, true)) {
throw new IOException("Failed to make temp file executable: " + tempFile);
}
return tempFile;
}
public static int sudo(@NotNull final String scriptPath,
@NotNull final String prompt) throws IOException, ScriptException, InterruptedException {
if (SystemInfo.isMac) {
final ScriptEngine engine = new ScriptEngineManager(null).getEngineByName("AppleScript");
if (engine == null) {
throw new IOException("Could not find AppleScript engine");
}
engine.eval("do shell script \"" + scriptPath + "\" with administrator privileges");
return 0;
}
else if (SystemInfo.isKDE) {
return execAndGetResult("kdesudo", "--comment", prompt, scriptPath);
}
else if (SystemInfo.isGnome) {
return execAndGetResult("gksudo", "--message", prompt, scriptPath);
}
else if (SystemInfo.isUnix) {
final File sudo = createTempExecutableScript("sudo", ".sh",
"#!/bin/sh\n" +
"echo \"" + prompt + "\"\n" +
"echo\n" +
"sudo \"" + scriptPath + "\"\n" +
"echo\n" +
"read -p \"Press Enter to close this window...\" TEMP\n");
return execAndGetResult("xterm", "-T", "Install", "-e", sudo.getAbsolutePath());
}
else {
throw new UnsupportedOperationException("Unsupported OS/desktop: " + System.getProperty("os.name") + '/' + SystemInfo.SUN_DESKTOP);
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2009 JetBrains s.r.o.
* Copyright 2000-2011 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@ import gnu.trove.THashSet;
import org.jetbrains.annotations.NotNull;
import java.util.*;
import java.util.HashMap;
import java.util.Stack;
/**
@@ -91,6 +92,18 @@ public class CollectionFactory {
return Maps.newHashMap();
}
public static <T, V> Map<T, V> hashMap(@NotNull final List<T> keys, @NotNull final List<V> values) {
if (keys.size() != values.size()) {
throw new IllegalArgumentException(keys + " should have some length as " + values);
}
final HashMap<T,V> map = Maps.newHashMap();
for (int i = 0; i < keys.size(); ++i) {
map.put(keys.get(i), values.get(i));
}
return map;
}
public static <T, V> LinkedHashMap<T, V> linkedMap() {
return Maps.newLinkedHashMap();
}