diff --git a/bin/mac/relaunch b/bin/mac/relaunch deleted file mode 100755 index 294f889f974b..000000000000 Binary files a/bin/mac/relaunch and /dev/null differ diff --git a/bin/mac/restarter b/bin/mac/restarter new file mode 100755 index 000000000000..6c093cdf119b Binary files /dev/null and b/bin/mac/restarter differ diff --git a/bin/win/restarter.exe b/bin/win/restarter.exe index a6e782440847..d642f78a8098 100644 Binary files a/bin/win/restarter.exe and b/bin/win/restarter.exe differ diff --git a/build/scripts/utils.gant b/build/scripts/utils.gant index f8b8f7fef301..cc8bb295e2de 100644 --- a/build/scripts/utils.gant +++ b/build/scripts/utils.gant @@ -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) diff --git a/build/update.sh b/build/update.sh index a4b171484146..47c4af6cf95b 100755 --- a/build/update.sh +++ b/build/update.sh @@ -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 diff --git a/native/macrestarter/relaunch.m b/native/macrestarter/relaunch.m deleted file mode 100644 index 5d6a7075ed8b..000000000000 --- a/native/macrestarter/relaunch.m +++ /dev/null @@ -1,21 +0,0 @@ -// gcc -Wall -arch i386 -arch ppc -mmacosx-version-min=10.4 -Os -framework AppKit -o relaunch relaunch.m - -#import - -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; -} \ No newline at end of file diff --git a/native/restarter/.gitignore b/native/restarter/.gitignore index 5f88d71148d0..4691cba0cd25 100644 --- a/native/restarter/.gitignore +++ b/native/restarter/.gitignore @@ -6,3 +6,4 @@ _UpgradeReport_Files UpgradeLog.xml ipch *.sdf +xuserdata \ No newline at end of file diff --git a/native/restarter/restarter.cpp b/native/restarter/restarter.cpp index aef7476ab70d..683f828ea371 100644 --- a/native/restarter/restarter.cpp +++ b/native/restarter/restarter.cpp @@ -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 +_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; } - diff --git a/native/restarter/restarter.m b/native/restarter/restarter.m new file mode 100644 index 000000000000..3b829e12ff03 --- /dev/null +++ b/native/restarter/restarter.m @@ -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 + +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; +} \ No newline at end of file diff --git a/native/restarter/restarter.sln b/native/restarter/restarter.sln index 359e70e11595..7b9e2583d41a 100644 --- a/native/restarter/restarter.sln +++ b/native/restarter/restarter.sln @@ -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 diff --git a/native/macrestarter/relaunch.xcodeproj/project.pbxproj b/native/restarter/restarter.xcodeproj/project.pbxproj similarity index 79% rename from native/macrestarter/relaunch.xcodeproj/project.pbxproj rename to native/restarter/restarter.xcodeproj/project.pbxproj index 8cc758f70181..23c4cc62bed6 100644 --- a/native/macrestarter/relaunch.xcodeproj/project.pbxproj +++ b/native/restarter/restarter.xcodeproj/project.pbxproj @@ -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 = ""; }; + 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 = ""; }; 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 = ""; @@ -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 */, diff --git a/platform/bootstrap/src/com/intellij/idea/Main.java b/platform/bootstrap/src/com/intellij/idea/Main.java index 4eb26720ade2..0cf4fde426bb 100644 --- a/platform/bootstrap/src/com/intellij/idea/Main.java +++ b/platform/bootstrap/src/com/intellij/idea/Main.java @@ -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 args = new ArrayList(); - 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 args = new ArrayList(); + 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()]))); } } diff --git a/platform/lang-impl/src/com/intellij/openapi/util/registry/RegistryUi.java b/platform/lang-impl/src/com/intellij/openapi/util/registry/RegistryUi.java index 6dfdf5c2b0c3..6c939197b441 100644 --- a/platform/lang-impl/src/com/intellij/openapi/util/registry/RegistryUi.java +++ b/platform/lang-impl/src/com/intellij/openapi/util/registry/RegistryUi.java @@ -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); } diff --git a/platform/platform-impl/src/com/intellij/ide/actions/ImportSettingsAction.java b/platform/platform-impl/src/com/intellij/ide/actions/ImportSettingsAction.java index d68996eb630b..4d2c6b56cf9e 100644 --- a/platform/platform-impl/src/com/intellij/ide/actions/ImportSettingsAction.java +++ b/platform/platform-impl/src/com/intellij/ide/actions/ImportSettingsAction.java @@ -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) { diff --git a/platform/platform-impl/src/com/intellij/ide/plugins/PluginManagerConfigurable.java b/platform/platform-impl/src/com/intellij/ide/plugins/PluginManagerConfigurable.java index e1b7b272dd7a..712e15e5a3cf 100644 --- a/platform/platform-impl/src/com/intellij/ide/plugins/PluginManagerConfigurable.java +++ b/platform/platform-impl/src/com/intellij/ide/plugins/PluginManagerConfigurable.java @@ -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() { diff --git a/platform/platform-impl/src/com/intellij/openapi/application/ex/ApplicationEx.java b/platform/platform-impl/src/com/intellij/openapi/application/ex/ApplicationEx.java index 40184ad941f7..927239124418 100644 --- a/platform/platform-impl/src/com/intellij/openapi/application/ex/ApplicationEx.java +++ b/platform/platform-impl/src/com/intellij/openapi/application/ex/ApplicationEx.java @@ -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} */ diff --git a/platform/platform-impl/src/com/intellij/openapi/application/impl/ApplicationImpl.java b/platform/platform-impl/src/com/intellij/openapi/application/impl/ApplicationImpl.java index 1c9aadff56b0..3e6dea3b2873 100644 --- a/platform/platform-impl/src/com/intellij/openapi/application/impl/ApplicationImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/application/impl/ApplicationImpl.java @@ -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() { diff --git a/platform/platform-impl/src/com/intellij/openapi/project/impl/ProjectManagerImpl.java b/platform/platform-impl/src/com/intellij/openapi/project/impl/ProjectManagerImpl.java index 492dfa503c7d..b68074ed3eb6 100644 --- a/platform/platform-impl/src/com/intellij/openapi/project/impl/ProjectManagerImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/project/impl/ProjectManagerImpl.java @@ -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); } } diff --git a/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/AbstractUpdateDialog.java b/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/AbstractUpdateDialog.java index 47c9b548c907..78c280dfaba9 100644 --- a/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/AbstractUpdateDialog.java +++ b/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/AbstractUpdateDialog.java @@ -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); } }); } diff --git a/platform/testFramework/src/com/intellij/mock/MockApplicationEx.java b/platform/testFramework/src/com/intellij/mock/MockApplicationEx.java index 5b078fbcec3f..4aaa1814380f 100644 --- a/platform/testFramework/src/com/intellij/mock/MockApplicationEx.java +++ b/platform/testFramework/src/com/intellij/mock/MockApplicationEx.java @@ -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() { } diff --git a/platform/util/src/com/intellij/util/Restarter.java b/platform/util/src/com/intellij/util/Restarter.java index 6b0b087a6f23..847511c04c11 100644 --- a/platform/util/src/com/intellij/util/Restarter.java +++ b/platform/util/src/com/intellij/util/Restarter.java @@ -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>() { + @Override + public void consume(List 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>() { + @Override + public void consume(List commands) { + Collections.addAll(commands, homePath); + Collections.addAll(commands, beforeRestart); + } + }); + } + + private static void doScheduleRestart(File restarterFile, Consumer> argumentsBuilder) throws IOException { + List commands = new ArrayList(); + 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) { + } } } -} +} \ No newline at end of file diff --git a/updater/src/com/intellij/updater/BaseUpdateAction.java b/updater/src/com/intellij/updater/BaseUpdateAction.java index 82611083e636..2ddfb685928d 100644 --- a/updater/src/com/intellij/updater/BaseUpdateAction.java +++ b/updater/src/com/intellij/updater/BaseUpdateAction.java @@ -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)) { diff --git a/updater/src/com/intellij/updater/Patch.java b/updater/src/com/intellij/updater/Patch.java index a0ae758a5a31..ac787afd50b0 100644 --- a/updater/src/com/intellij/updater/Patch.java +++ b/updater/src/com/intellij/updater/Patch.java @@ -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); } diff --git a/updater/src/com/intellij/updater/UpdateAction.java b/updater/src/com/intellij/updater/UpdateAction.java index dc93eb0dafa3..6c79c44cbc4b 100644 --- a/updater/src/com/intellij/updater/UpdateAction.java +++ b/updater/src/com/intellij/updater/UpdateAction.java @@ -42,7 +42,7 @@ public class UpdateAction extends BaseUpdateAction { out.close(); } - Utils.copy(temp, toFile); + replaceUpdated(temp, toFile); Utils.setExecutable(toFile, executable); } } diff --git a/updater/src/com/intellij/updater/UpdateZipAction.java b/updater/src/com/intellij/updater/UpdateZipAction.java index 548448205224..553e7d8da0a4 100644 --- a/updater/src/com/intellij/updater/UpdateZipAction.java +++ b/updater/src/com/intellij/updater/UpdateZipAction.java @@ -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 {