Patches: out-of-process patches

This commit is contained in:
Anton Makeev
2013-01-18 09:30:31 +01:00
parent 74e96aebef
commit 96462d0b09
25 changed files with 355 additions and 324 deletions
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
+2 -2
View File
@@ -635,7 +635,7 @@ binding.setVariable("buildMacZip", { String zipRoot, String zipPath, List paths,
zipfileset(dir: it, prefix: zipRoot) {
exclude(name: "bin/*.sh")
exclude(name: "bin/fsnotifier")
exclude(name: "bin/relaunch")
exclude(name: "bin/restarter")
exclude(name: "Contents/MacOS/*")
extraBins.each {
exclude(name: it)
@@ -648,7 +648,7 @@ binding.setVariable("buildMacZip", { String zipRoot, String zipPath, List paths,
zipfileset(dir: it, filemode: "755", prefix: zipRoot) {
include(name: "bin/*.sh")
include(name: "bin/fsnotifier")
include(name: "bin/relaunch")
include(name: "bin/restarter")
include(name: "Contents/MacOS/*")
extraBins.each {
include(name: it)
+1 -1
View File
@@ -37,5 +37,5 @@ if [ "$OS_TYPE" = "Linux" ]; then
elif [ "$OS_TYPE" = "Darwin" ]; then
cp -a $DEV_IDEA_HOME/bin/mac/*.jnilib $WORK_IDEA_HOME/bin
cp -a $DEV_IDEA_HOME/bin/mac/fsnotifier $WORK_IDEA_HOME/bin
cp -a $DEV_IDEA_HOME/bin/mac/relaunch $WORK_IDEA_HOME/bin
cp -a $DEV_IDEA_HOME/bin/mac/restarter $WORK_IDEA_HOME/bin
fi
-21
View File
@@ -1,21 +0,0 @@
// gcc -Wall -arch i386 -arch ppc -mmacosx-version-min=10.4 -Os -framework AppKit -o relaunch relaunch.m
#import <AppKit/AppKit.h>
int main(int argc, const char *argv[]) {
if (argc != 2) return EXIT_FAILURE;
unsigned int interval = 500; // check every 0.5 second
unsigned int slept = 0;
while (getppid() != 1) {
usleep(interval * 1000);
slept += interval;
// if (slept > 10 * 1000 /* wait for maximum 10 seconds */) return EXIT_FAILURE;
}
char const *pathToRelaunch = argv[1];
[[NSWorkspace sharedWorkspace] launchApplication:[NSString stringWithUTF8String:pathToRelaunch]];
return EXIT_SUCCESS;
}
+1
View File
@@ -6,3 +6,4 @@ _UpgradeReport_Files
UpgradeLog.xml
ipch
*.sdf
xuserdata
+58 -25
View File
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2009 JetBrains s.r.o.
* Copyright 2000-2013 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,12 +15,38 @@
*/
#include "stdafx.h"
#include <process.h>
_TCHAR **convert_args(int argc, _TCHAR **argv) {
_TCHAR** result = new _TCHAR* [argc+1];
for(int i = 0; i < argc; i++)
{
if (_tcschr(argv[i], ' '))
{
int len = _tcslen(argv[i]) + 3;
TCHAR *arg = new TCHAR[len];
arg[0] = '\"';
_tcscpy_s(arg+1, len, argv[i]);
_tcscat_s(arg, len, _T("\""));
result[i] = arg;
}
else
{
result[i] = argv[i];
}
}
result[argc] = '\0';
return result;
}
// usage "pid_to_wait (commands_num commands...)*
int _tmain(int argc, _TCHAR* argv[])
{
if (argc < 3) return 0;
int ppid = _ttoi(argv [1]);
int arg_index = 1;
int ppid = _ttoi(argv [arg_index++]);
HANDLE parent_process = OpenProcess(SYNCHRONIZE, FALSE, ppid);
if (parent_process)
{
@@ -28,32 +54,39 @@ int _tmain(int argc, _TCHAR* argv[])
CloseHandle(parent_process);
}
int child_argc = argc-2;
_TCHAR** child_argv = new _TCHAR* [child_argc+1];
for(int i = 0; i < child_argc; i++)
{
if (_tcschr(argv[i+2], ' '))
{
int len = _tcslen(argv[i+2]) + 3;
TCHAR *arg = new TCHAR[len];
arg[0] = '\"';
_tcscpy_s(arg+1, len, argv[i+2]);
_tcscat_s(arg, len, _T("\""));
child_argv[i] = arg;
}
else
{
child_argv[i] = argv[i+2];
FILE *file = 0;
#ifdef _DEBUG
file = fopen("c:/restarter_debug.txt", "w");
#endif
while (arg_index < argc - 1) {
int argc_to_perform = _ttoi(argv[arg_index++]);
if (argc_to_perform > 0) {
_TCHAR *command_to_perform = argv[arg_index];
_TCHAR **argv_to_perform = convert_args(argc_to_perform, &argv[arg_index]);
arg_index += argc_to_perform;
if (file) {
_fwprintf_p(file, L"=================\n");
_fwprintf_p(file, L"argc:%d\n", argc_to_perform);
for(int ii = 0; ii < argc_to_perform; ii++) {
_fwprintf_p(file, L" %s\n", argv_to_perform[ii]);
}
fflush(file);
}
int last = arg_index >= argc - 1;
int rc = _tspawnv(last ? _P_NOWAIT : _P_WAIT, command_to_perform, argv_to_perform);
if (file && rc == -1) {
_fwprintf_p(file, L"Error restarting process: errno is %d\n", errno);
fflush(file);
}
}
}
child_argv[child_argc] = '\0';
int rc = _texecv(argv [2], child_argv);
if (rc == -1)
{
_tprintf(_T("Error restarting process: errno is %d"), errno);
if (file) {
_fwprintf_p(file, L"Finished\n");
fclose(file);
}
return 0;
}
+44
View File
@@ -0,0 +1,44 @@
/*
* Copyright 2000-2013 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.
*/
#import <AppKit/AppKit.h>
int main(int argc, const char *argv[]) {
if (argc < 2) return EXIT_FAILURE;
unsigned int interval = 500; // check every 0.5 second
unsigned int slept = 0;
while (getppid() != 1) {
usleep(interval * 1000);
slept += interval;
}
if (argc > 2) {
NSString *launchPath = [NSString stringWithUTF8String:argv[2]];
NSMutableArray *arguments = [NSMutableArray array];
for (int i = 3; i < argc; i++) {
[arguments addObject:[NSString stringWithUTF8String:argv[i]]];
}
NSTask *task = [NSTask launchedTaskWithLaunchPath:launchPath arguments:arguments];
[task waitUntilExit];
}
NSString *pathToRelaunch = [NSString stringWithUTF8String:argv[1]];
[[NSWorkspace sharedWorkspace] launchApplication:pathToRelaunch];
return EXIT_SUCCESS;
}
+3 -3
View File
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 10.00
# Visual Studio 2008
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "restarter", "restarter.vcproj", "{1B2485B2-CFED-4B2F-8FFD-00F1C9E1BD0C}"
Microsoft Visual Studio Solution File, Format Version 11.00
# Visual C++ Express 2010
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "restarter", "restarter.vcxproj", "{1B2485B2-CFED-4B2F-8FFD-00F1C9E1BD0C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -7,25 +7,27 @@
objects = {
/* Begin PBXBuildFile section */
3A6C10B4148122790074811C /* relaunch.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A6C10B3148122790074811C /* relaunch.m */; };
3A6C10B4148122790074811C /* restarter.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A6C10B3148122790074811C /* restarter.m */; };
3A6C10B7148122940074811C /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3A6C10B6148122940074811C /* AppKit.framework */; };
3ABC975E16A5A3CD0088619F /* restarter in CopyFiles */ = {isa = PBXBuildFile; fileRef = 3A6C10AE148122660074811C /* restarter */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
CF2F99674C3F98D5739C1B44 /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = /usr/share/man/man1/;
buildActionMask = 12;
dstPath = "$(PROJECT_DIR)/../../bin/mac";
dstSubfolderSpec = 0;
files = (
3ABC975E16A5A3CD0088619F /* restarter in CopyFiles */,
);
runOnlyForDeploymentPostprocessing = 1;
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
3A6C10AE148122660074811C /* relaunch */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = relaunch; sourceTree = BUILT_PRODUCTS_DIR; };
3A6C10B3148122790074811C /* relaunch.m */ = {isa = PBXFileReference; fileEncoding = 11; lastKnownFileType = sourcecode.c.objc; path = relaunch.m; sourceTree = "<group>"; };
3A6C10AE148122660074811C /* restarter */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = restarter; sourceTree = BUILT_PRODUCTS_DIR; };
3A6C10B3148122790074811C /* restarter.m */ = {isa = PBXFileReference; fileEncoding = 11; lastKnownFileType = sourcecode.c.objc; path = restarter.m; sourceTree = "<group>"; };
3A6C10B6148122940074811C /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; };
/* End PBXFileReference section */
@@ -44,7 +46,7 @@
CF2F99674C3F98D5739C1B39 = {
isa = PBXGroup;
children = (
3A6C10B3148122790074811C /* relaunch.m */,
3A6C10B3148122790074811C /* restarter.m */,
CF2F99674C3F98D5739C1B3A /* Products */,
CF2F99674C3F98D5739C1B45 /* Frameworks */,
3A6C10B6148122940074811C /* AppKit.framework */,
@@ -54,7 +56,7 @@
CF2F99674C3F98D5739C1B3A /* Products */ = {
isa = PBXGroup;
children = (
3A6C10AE148122660074811C /* relaunch */,
3A6C10AE148122660074811C /* restarter */,
);
name = Products;
sourceTree = "<group>";
@@ -69,9 +71,9 @@
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
CF2F99674C3F98D5739C1B3E /* relaunch */ = {
CF2F99674C3F98D5739C1B3E /* restarter */ = {
isa = PBXNativeTarget;
buildConfigurationList = CF2F99674C3F98D5739C1B3F /* Build configuration list for PBXNativeTarget "relaunch" */;
buildConfigurationList = CF2F99674C3F98D5739C1B3F /* Build configuration list for PBXNativeTarget "restarter" */;
buildPhases = (
CF2F99674C3F98D5739C1B42 /* Sources */,
CF2F99674C3F98D5739C1B43 /* Frameworks */,
@@ -81,9 +83,9 @@
);
dependencies = (
);
name = relaunch;
productName = relaunch;
productReference = 3A6C10AE148122660074811C /* relaunch */;
name = restarter;
productName = restarter;
productReference = 3A6C10AE148122660074811C /* restarter */;
productType = "com.apple.product-type.tool";
};
/* End PBXNativeTarget section */
@@ -91,7 +93,7 @@
/* Begin PBXProject section */
CF2F99674C3F98D5739C1B37 /* Project object */ = {
isa = PBXProject;
buildConfigurationList = CF2F99674C3F98D5739C1B38 /* Build configuration list for PBXProject "relaunch" */;
buildConfigurationList = CF2F99674C3F98D5739C1B38 /* Build configuration list for PBXProject "restarter" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
@@ -103,7 +105,7 @@
projectDirPath = "";
projectRoot = "";
targets = (
CF2F99674C3F98D5739C1B3E /* relaunch */,
CF2F99674C3F98D5739C1B3E /* restarter */,
);
};
/* End PBXProject section */
@@ -113,7 +115,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
3A6C10B4148122790074811C /* relaunch.m in Sources */,
3A6C10B4148122790074811C /* restarter.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -129,14 +131,14 @@
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_ENABLE_OBJC_EXCEPTIONS = YES;
GCC_VERSION = 4.0;
GCC_VERSION = "";
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.4;
ONLY_ACTIVE_ARCH = NO;
SDKROOT = macosx10.4;
SDKROOT = macosx;
};
name = Release;
};
@@ -155,14 +157,14 @@
"$(inherited)",
);
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
GCC_VERSION = 4.0;
GCC_VERSION = "";
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.4;
ONLY_ACTIVE_ARCH = NO;
SDKROOT = macosx10.4;
SDKROOT = macosx;
};
name = Debug;
};
@@ -185,7 +187,7 @@
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
CF2F99674C3F98D5739C1B38 /* Build configuration list for PBXProject "relaunch" */ = {
CF2F99674C3F98D5739C1B38 /* Build configuration list for PBXProject "restarter" */ = {
isa = XCConfigurationList;
buildConfigurations = (
CF2F99674C3F98D5739C1B3B /* Release */,
@@ -194,7 +196,7 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
CF2F99674C3F98D5739C1B3F /* Build configuration list for PBXNativeTarget "relaunch" */ = {
CF2F99674C3F98D5739C1B3F /* Build configuration list for PBXNativeTarget "restarter" */ = {
isa = XCConfigurationList;
buildConfigurations = (
CF2F99674C3F98D5739C1B40 /* Release */,
@@ -17,9 +17,10 @@
package com.intellij.idea;
import com.intellij.ide.Bootstrap;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.io.StreamUtil;
import com.intellij.util.Restarter;
import org.jetbrains.annotations.NonNls;
@@ -27,11 +28,7 @@ import javax.swing.*;
import java.awt.*;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -39,37 +36,11 @@ import java.util.List;
public class Main {
private static boolean isHeadless;
private Main() { }
private Main() {
}
@SuppressWarnings("MethodNamesDifferingOnlyByCase")
public static void main(final String[] args) {
final int[] restartCode = {Restarter.getRestartCode()};
Runnable restart = new Runnable() {
@Override
public void run() {
if (restartCode[0] == 0) {
try {
if (Restarter.restart()) restartCode[0] = 1;
}
catch (Throwable ignore) {
}
}
}
};
if (installPatch(restart)) {
if (restartCode[0] == 0) {
try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (Throwable ignore) { }
String msg = "Patch has been applied successfully, please restart application.";
JOptionPane.showMessageDialog(null, msg, "Update", JOptionPane.INFORMATION_MESSAGE);
}
System.exit(restartCode[0]);
return;
}
isHeadless = isHeadless(args);
if (isHeadless) {
System.setProperty("java.awt.headless", Boolean.TRUE.toString());
@@ -78,6 +49,40 @@ public class Main {
throw new HeadlessException("Unable to detect graphics environment");
}
if (!isHeadless) {
try {
installPatch();
}
catch (IOException e) {
e.printStackTrace();
File log = null;
try {
log = FileUtil.createTempFile("patch", ".log", false);
PrintWriter writer = new PrintWriter(log);
try {
e.printStackTrace(writer);
}
finally {
writer.close();
}
}
catch (IOException ignore) {
ignore.printStackTrace();
}
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
}
catch (Throwable ignore) {
}
String message = e.getMessage()
+ "\n" + (log == null ? "Log cannot be saved" : "Log is saved in " + log)
+ "\n\nPlease download and install update manually" ;
JOptionPane.showMessageDialog(null, message, "Cannot Apply Patch", JOptionPane.ERROR_MESSAGE);
}
}
Bootstrap.main(args, Main.class.getName() + "Impl", "start");
}
@@ -112,100 +117,34 @@ public class Main {
return isHeadless;
}
private static boolean installPatch(Runnable restart) {
try {
File ideaHomeDir = getIdeaHomeDir();
if (ideaHomeDir == null) return false;
private static void installPatch() throws IOException {
String platform = System.getProperty("idea.platform.prefix", "idea");
String patchFileName = ("jetbrains.patch.jar." + platform).toLowerCase();
File originalPatchFile = new File(System.getProperty("java.io.tmpdir"), patchFileName);
File copyPatchFile = new File(System.getProperty("java.io.tmpdir"), patchFileName + "_copy");
String platform = System.getProperty("idea.platform.prefix", "idea");
String patchFileName = ("jetbrains.patch.jar." + platform).toLowerCase();
File patchFile = new File(System.getProperty("java.io.tmpdir"), patchFileName);
// always delete previous patch copy
if (!FileUtil.delete(copyPatchFile)) throw new IOException("Cannot create temporary patch file");
if (!patchFile.exists()) return false;
if (!originalPatchFile.exists()) return;
try {
List<String> args = new ArrayList<String>();
if (System.getProperty("os.name").toLowerCase().startsWith("windows")) {
File launcherFile = new File(ideaHomeDir, "bin/vistalauncher.exe");
File launcherCopy = FileUtil.createTempFile("vistalauncher", ".exe");
launcherCopy.deleteOnExit();
FileUtil.copy(launcherFile, launcherCopy);
args.add(launcherCopy.getPath());
}
restart.run();
Collections.addAll(args,
System.getProperty("java.home") + "/bin/java",
"-classpath",
patchFile.getPath(),
"com.intellij.updater.Runner",
"install",
ideaHomeDir.getPath());
Process process = Runtime.getRuntime().exec(args.toArray(new String[args.size()]));
Thread outThread = new Thread(new StreamRedirector(process.getInputStream(), System.out));
Thread errThread = new Thread(new StreamRedirector(process.getErrorStream(), System.err));
outThread.start();
errThread.start();
try {
process.waitFor();
}
finally {
outThread.join();
errThread.join();
}
return true;
}
finally {
patchFile.delete();
}
}
catch (Exception e) {
e.printStackTrace();
}
return false;
}
private static class StreamRedirector implements Runnable {
private final InputStream myIn;
private final OutputStream myOut;
private StreamRedirector(InputStream in, OutputStream out) {
myIn = in;
myOut = out;
if (!originalPatchFile.renameTo(copyPatchFile) || !FileUtil.delete(originalPatchFile)) {
throw new IOException("Cannot create temporary patch file");
}
public void run() {
try {
StreamUtil.copyStreamContent(myIn, myOut);
}
catch (IOException e) {
e.printStackTrace();
}
List<String> args = new ArrayList<String>();
if (SystemInfo.isWindows) {
args.add(Restarter.createTempExecutable(new File(PathManager.getBinPath(), "vistalauncher.exe")).getPath());
}
}
private static File getIdeaHomeDir() throws IOException {
URL url = Bootstrap.class.getResource("");
if (url == null || !"jar".equals(url.getProtocol())) return null;
Collections.addAll(args,
System.getProperty("java.home") + "/bin/java",
"-classpath",
copyPatchFile.getPath(),
"com.intellij.updater.Runner",
"install",
PathManager.getHomePath());
String path = url.getPath();
int start = path.indexOf("file:/");
int end = path.indexOf("!/");
if (start == -1 || end == -1) return null;
String jarFileUrl = path.substring(start, end);
try {
File bootstrapJar = new File(new URI(jarFileUrl));
return bootstrapJar.getParentFile().getParentFile();
}
catch (URISyntaxException e) {
return null;
}
System.exit(Restarter.scheduleRestart(args.toArray(new String[args.size()])));
}
}
@@ -316,11 +316,7 @@ public class RegistryUi implements Disposable {
LaterInvocator.invokeLater(new Runnable() {
@Override
public void run() {
if (app.isRestartCapable()) {
app.restart();
} else {
app.exit(true);
}
app.restart(true);
}
}, ModalityState.NON_MODAL);
}
@@ -111,12 +111,7 @@ public class ImportSettingsAction extends AnAction implements DumbAware {
ApplicationNamesInfo.getInstance().getFullProductName()),
IdeBundle.message("title.restart.needed"), Messages.getQuestionIcon());
if (ret == 0) {
if (ApplicationManager.getApplication().isRestartCapable()) {
ApplicationManager.getApplication().restart();
}
else {
ApplicationManager.getApplication().exit();
}
ApplicationManager.getApplication().restart();
}
}
catch (ZipException e1) {
@@ -135,21 +135,13 @@ public class PluginManagerConfigurable extends BaseConfigurable implements Searc
if (myPluginManagerMain.isRequireShutdown()) {
final ApplicationEx app = ApplicationManagerEx.getApplicationEx();
if (app.isRestartCapable()) {
if (showRestartIDEADialog() == 0) {
app.restart();
}
else {
myPluginManagerMain.ignoreChanges();
}
int response = app.isRestartCapable() ? showRestartIDEADialog() : showShutDownIDEADialog();
if (response == 0) {
app.restart(true);
}
else {
if (showShutDownIDEADialog() == 0) {
app.exit(true);
}
else {
myPluginManagerMain.ignoreChanges();
}
myPluginManagerMain.ignoreChanges();
}
}
}
@@ -181,16 +173,8 @@ public class PluginManagerConfigurable extends BaseConfigurable implements Searc
public static void shutdownOrRestartApp(String title) {
final ApplicationEx app = ApplicationManagerEx.getApplicationEx();
if (app.isRestartCapable()) {
if (showRestartIDEADialog(title) == 0) {
app.restart();
}
}
else {
if (showShutDownIDEADialog(title) == 0) {
app.exit(true);
}
}
int response = app.isRestartCapable() ? showRestartIDEADialog(title) : showShutDownIDEADialog(title);
if (response == 0) app.restart(true);
}
public boolean isModified() {
@@ -51,6 +51,8 @@ public interface ApplicationEx extends Application {
//force exit
void exit(boolean force);
void restart(boolean force);
/**
* Runs modal process. For internal use only, see {@link Task}
*/
@@ -116,8 +116,6 @@ public class ApplicationImpl extends ComponentManagerImpl implements Application
private boolean myDoNotSave;
private volatile boolean myDisposeInProgress = false;
private int myRestartCode = 0;
private volatile int myExitCode = 0;
private final Disposable myLastDisposable = Disposer.newDisposable(); // will be disposed last
private boolean myHandlingInitComponentError;
@@ -268,8 +266,6 @@ public class ApplicationImpl extends ComponentManagerImpl implements Application
});
}
myRestartCode = Restarter.getRestartCode();
registerFont("/fonts/Inconsolata.ttf");
registerFont("/fonts/SourceCodePro-Regular.ttf");
registerFont("/fonts/SourceCodePro-Bold.ttf");
@@ -807,10 +803,20 @@ public class ApplicationImpl extends ComponentManagerImpl implements Application
@Override
public void exit(final boolean force) {
exit(force, true);
exit(force, true, false);
}
public void exit(final boolean force, final boolean allowListenersToCancel) {
@Override
public void restart() {
restart(false);
}
@Override
public void restart(boolean force) {
exit(force, true, true);
}
public void exit(final boolean force, final boolean allowListenersToCancel, final boolean restart) {
if (!force && getDefaultModalityState() != ModalityState.NON_MODAL) {
return;
}
@@ -820,15 +826,13 @@ public class ApplicationImpl extends ComponentManagerImpl implements Application
public void run() {
if (!force && !showConfirmation()) {
saveAll();
myExitCode = 0;
return;
}
getMessageBus().syncPublisher(AppLifecycleListener.TOPIC).appClosing();
myDisposeInProgress = true;
if (!doExit(allowListenersToCancel)) {
if (!doExit(allowListenersToCancel, restart)) {
myDisposeInProgress = false;
myExitCode = 0;
}
}
};
@@ -841,7 +845,7 @@ public class ApplicationImpl extends ComponentManagerImpl implements Application
}
}
private boolean doExit(boolean allowListenersToCancel) {
private boolean doExit(boolean allowListenersToCancel, boolean restart) {
saveSettings();
if (allowListenersToCancel && !canExit()) {
@@ -853,7 +857,16 @@ public class ApplicationImpl extends ComponentManagerImpl implements Application
return false;
}
System.exit(myExitCode);
int exitCode = 0;
if (restart) {
try {
exitCode = Restarter.scheduleRestart();
}
catch (IOException e) {
LOG.warn("Cannot restart", e);
}
}
System.exit(exitCode);
return true;
}
@@ -1468,24 +1481,7 @@ public class ApplicationImpl extends ComponentManagerImpl implements Application
@Override
public boolean isRestartCapable() {
return Restarter.isSupported() || myRestartCode > 0;
}
@Override
public void restart() {
boolean restarted = false;
try {
restarted = Restarter.restart();
}
catch (Restarter.CannotRestartException e) {
LOG.warn(e);
}
if (!restarted) {
myExitCode = myRestartCode;
}
exit(true);
return Restarter.isSupported();
}
public boolean isSaving() {
@@ -662,12 +662,7 @@ public class ProjectManagerImpl extends ProjectManagerEx implements NamedJDOMExt
((XmlElementStorage)stateStorage).disableSaving();
}
}
if (canRestart) {
ApplicationManagerEx.getApplicationEx().restart();
}
else {
ApplicationManagerEx.getApplicationEx().exit(true);
}
ApplicationManagerEx.getApplicationEx().restart(true);
}
}
@@ -107,12 +107,7 @@ public abstract class AbstractUpdateDialog extends DialogWrapper {
app.invokeLater(new Runnable() {
@Override
public void run() {
if (app.isRestartCapable()) {
app.restart();
}
else {
app.exit(true);
}
app.restart(true);
}
});
}
@@ -57,6 +57,10 @@ public class MockApplicationEx extends MockApplication implements ApplicationEx
public void exit(boolean force) {
}
@Override
public void restart(boolean force) {
}
@Override
public void doNotSave() {
}
@@ -18,18 +18,29 @@ package com.intellij.util;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.io.StreamUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.WString;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.win32.StdCallLibrary;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@SuppressWarnings({"UseOfSystemOutOrSystemErr", "CallToPrintStackTrace"})
public class Restarter {
private Restarter() {
}
public static int getRestartCode() {
private static int getRestartCode() {
String s = System.getProperty("jb.restart.code");
if (s != null) {
try {
@@ -42,45 +53,63 @@ public class Restarter {
}
public static boolean isSupported() {
return SystemInfo.isWindows || SystemInfo.isMac;
return getRestartCode() != 0 || SystemInfo.isWindows || SystemInfo.isMac;
}
public static boolean restart() throws CannotRestartException {
try {
if (SystemInfo.isWindows) {
return restartOnWindows();
public static int scheduleRestart(@NotNull String... beforeRestart) throws IOException {
int restartCode = getRestartCode();
if (restartCode != 0) {
try {
Process process = Runtime.getRuntime().exec(beforeRestart);
Thread outThread = new Thread(new StreamRedirector(process.getInputStream(), System.out));
Thread errThread = new Thread(new StreamRedirector(process.getErrorStream(), System.err));
outThread.start();
errThread.start();
try {
process.waitFor();
}
finally {
outThread.join();
errThread.join();
}
}
else if (SystemInfo.isMac) {
return restartOnMac();
catch (InterruptedException ignore) {
}
return restartCode;
}
catch (CannotRestartException e) {
throw e;
else if (SystemInfo.isWindows) {
restartOnWindows(beforeRestart);
return 0;
}
catch (Throwable e) {
throw new CannotRestartException(e);
else if (SystemInfo.isMac) {
restartOnMac(beforeRestart);
return 0;
}
return false;
throw new IOException("Cannot restart application: not supported.");
}
private static boolean restartOnWindows() throws CannotRestartException {
private static void restartOnWindows(@NotNull final String... beforeRestart) throws IOException {
Kernel32 kernel32 = (Kernel32)Native.loadLibrary("kernel32", Kernel32.class);
WString cline = kernel32.GetCommandLineW();
int pid = kernel32.GetCurrentProcessId();
Shell32 shell32 = (Shell32)Native.loadLibrary("shell32", Shell32.class);
try {
// to prevent blocking exe file during update we should copy it
File restarterFile = new File(PathManager.getBinPath(), "restarter.exe");
File restarterCopy = FileUtil.createTempFile("restarter", ".exe");
final int pid = kernel32.GetCurrentProcessId();
final IntByReference argc = new IntByReference();
Pointer argv_ptr = shell32.CommandLineToArgvW(kernel32.GetCommandLineW(), argc);
final String[] argv = argv_ptr.getStringArray(0, argc.getValue(), true);
kernel32.LocalFree(argv_ptr);
FileUtil.copy(restarterFile, restarterCopy);
String command = "\"" + restarterCopy + "\" " + Integer.toString(pid) + " " + cline;
Runtime.getRuntime().exec(command, null, new File(PathManager.getBinPath()));
}
catch (IOException ex) {
throw new CannotRestartException(ex);
}
doScheduleRestart(new File(PathManager.getBinPath(), "restarter.exe"), new Consumer<List<String>>() {
@Override
public void consume(List<String> commands) {
Collections.addAll(commands, String.valueOf(pid), String.valueOf(beforeRestart.length));
Collections.addAll(commands, beforeRestart);
Collections.addAll(commands, String.valueOf(argc.getValue()));
Collections.addAll(commands, argv);
}
});
// Since the process ID is passed through the command line, we want to make sure that we don't exit before the "restarter"
// process has a chance to open the handle to our process, and that it doesn't wait for the termination of an unrelated
@@ -88,39 +117,67 @@ public class Restarter {
try {
Thread.sleep(500);
}
catch (InterruptedException e1) {
// ignore
catch (InterruptedException ignore) {
}
return true;
}
private static void restartOnMac(@NotNull final String... beforeRestart) throws IOException {
final String homePath = PathManager.getHomePath();
if (!StringUtil.endsWithIgnoreCase(homePath, ".app")) throw new IOException("Application bundle not found: " + homePath);
doScheduleRestart(new File(PathManager.getBinPath(), "restarter"), new Consumer<List<String>>() {
@Override
public void consume(List<String> commands) {
Collections.addAll(commands, homePath);
Collections.addAll(commands, beforeRestart);
}
});
}
private static void doScheduleRestart(File restarterFile, Consumer<List<String>> argumentsBuilder) throws IOException {
List<String> commands = new ArrayList<String>();
commands.add(createTempExecutable(restarterFile).getPath());
argumentsBuilder.consume(commands);
Runtime.getRuntime().exec(commands.toArray(new String[commands.size()]));
}
public static File createTempExecutable(File executable) throws IOException {
String ext = FileUtil.getExtension(executable.getName());
File copy = FileUtil.createTempFile(FileUtil.getNameWithoutExtension(executable),
StringUtil.isEmptyOrSpaces(ext) ? ".tmp" : ("." + ext),
false);
FileUtil.copy(executable, copy);
if (!copy.setExecutable(executable.canExecute())) throw new IOException("Cannot make file executable: " + copy);
return copy;
}
private interface Kernel32 extends StdCallLibrary {
int GetCurrentProcessId();
WString GetCommandLineW();
int GetCurrentProcessId();
Pointer LocalFree(Pointer pointer);
}
private static boolean restartOnMac() throws CannotRestartException {
String binPath = PathManager.getBinPath();
if (!binPath.contains(".app")) return false;
int appIndex = binPath.indexOf(".app");
String appPath = binPath.substring(0, appIndex + 4);
try {
Runtime.getRuntime().exec(new String[]{new File(PathManager.getBinPath(), "relaunch").getPath(), appPath});
}
catch (IOException e) {
throw new CannotRestartException(e);
}
return true;
private interface Shell32 extends StdCallLibrary {
Pointer CommandLineToArgvW(WString command_line, IntByReference argc);
}
public static class CannotRestartException extends Exception {
public CannotRestartException(Throwable cause) {
super(cause);
private static class StreamRedirector implements Runnable {
private final InputStream myIn;
private final OutputStream myOut;
private StreamRedirector(InputStream in, OutputStream out) {
myIn = in;
myOut = out;
}
public void run() {
try {
StreamUtil.copyStreamContent(myIn, myOut);
}
catch (IOException ignore) {
}
}
}
}
}
@@ -33,6 +33,12 @@ public abstract class BaseUpdateAction extends PatchAction {
Utils.copy(toFile, backupFile);
}
protected void replaceUpdated(File from, File dest) throws IOException {
// on OS X code signing caches seem to be associated with specific file ids, so we need to remove the original file.
if (!dest.delete()) throw new IOException("Cannot delete file " + dest);
Utils.copy(from, dest);
}
@Override
protected void doRevert(File toFile, File backupFile) throws IOException {
if (!toFile.exists() || isModified(toFile)) {
@@ -212,6 +212,9 @@ public class Patch {
if (cancelled) throw new OperationCancelledException();
}
// on OS X we need to update bundle timestamp to reset Info.plist caches.
toDir.setLastModified(System.currentTimeMillis());
return new ApplicationResult(appliedActions);
}
@@ -42,7 +42,7 @@ public class UpdateAction extends BaseUpdateAction {
out.close();
}
Utils.copy(temp, toFile);
replaceUpdated(temp, toFile);
Utils.setExecutable(toFile, executable);
}
}
@@ -188,7 +188,7 @@ public class UpdateZipAction extends BaseUpdateAction {
out.close();
}
Utils.copy(temp, toFile);
replaceUpdated(temp, toFile);
}
private static void processZipFile(File file, Processor processor) throws IOException {