diff --git a/plugins/com.google.cloud.tools.eclipse.appengine.localserver/plugin.xml b/plugins/com.google.cloud.tools.eclipse.appengine.localserver/plugin.xml
index 626e02d190..e9fbcb3450 100644
--- a/plugins/com.google.cloud.tools.eclipse.appengine.localserver/plugin.xml
+++ b/plugins/com.google.cloud.tools.eclipse.appengine.localserver/plugin.xml
@@ -42,7 +42,8 @@
runtime="true"
runtimeTypeId="com.google.cloud.tools.eclipse.appengine.standard.runtime"
startBeforePublish="false"
- supportsRemoteHosts="true">
+ supportsRemoteHosts="true"
+ synchronousStart="true">
@@ -322,4 +323,13 @@
id="com.google.cloud.tools.eclipse.appengine.localserver.cloudSdkDebugTarget">
+
+
+
+
diff --git a/plugins/com.google.cloud.tools.eclipse.appengine.localserver/src/com/google/cloud/tools/eclipse/appengine/localserver/messages.properties b/plugins/com.google.cloud.tools.eclipse.appengine.localserver/src/com/google/cloud/tools/eclipse/appengine/localserver/messages.properties
index a455800bc2..628bf64821 100644
--- a/plugins/com.google.cloud.tools.eclipse.appengine.localserver/src/com/google/cloud/tools/eclipse/appengine/localserver/messages.properties
+++ b/plugins/com.google.cloud.tools.eclipse.appengine.localserver/src/com/google/cloud/tools/eclipse/appengine/localserver/messages.properties
@@ -19,6 +19,8 @@ CANNOT_DETERMINE_EXECUTION_CONTEXT=Cannot determine server execution context
SERVER_ALREADY_RUNNING=Server is already running
SERVER_ALREADY_RUNNING_IN_MODE=Server is already running in "{0}" mode
UNABLE_TO_LAUNCH=Unable to launch App Engine server
+STALE_RESOURCES_DETECTED=Stale resources detected
+STALE_RESOURCES_LAUNCH_CONFIRMATION=Some recently changed resources are not yet published. Continue with launch?
target.terminated={0}
cloudsdk.server.description=Google Cloud SDK Dev App Server
diff --git a/plugins/com.google.cloud.tools.eclipse.appengine.localserver/src/com/google/cloud/tools/eclipse/appengine/localserver/server/LocalAppEngineServerBehaviour.java b/plugins/com.google.cloud.tools.eclipse.appengine.localserver/src/com/google/cloud/tools/eclipse/appengine/localserver/server/LocalAppEngineServerBehaviour.java
index 058b414fdc..d7d2a460c3 100644
--- a/plugins/com.google.cloud.tools.eclipse.appengine.localserver/src/com/google/cloud/tools/eclipse/appengine/localserver/server/LocalAppEngineServerBehaviour.java
+++ b/plugins/com.google.cloud.tools.eclipse.appengine.localserver/src/com/google/cloud/tools/eclipse/appengine/localserver/server/LocalAppEngineServerBehaviour.java
@@ -199,6 +199,21 @@ public final void setModulePublishState2(IModule[] module, int state) {
setModulePublishState(module, state);
}
+ @Override
+ protected void publishFinish(IProgressMonitor monitor) throws CoreException {
+ boolean allPublished = true;
+ IServer server = getServer();
+ IModule[] modules = server.getModules();
+ for (IModule module : modules) {
+ if (server.getModulePublishState(new IModule[] {module}) != IServer.PUBLISH_STATE_NONE) {
+ allPublished = false;
+ }
+ }
+ if (allPublished) {
+ setServerPublishState(IServer.PUBLISH_STATE_NONE);
+ }
+ }
+
private static IStatus newErrorStatus(String message) {
return new Status(IStatus.ERROR, Activator.PLUGIN_ID, message);
}
diff --git a/plugins/com.google.cloud.tools.eclipse.appengine.localserver/src/com/google/cloud/tools/eclipse/appengine/localserver/server/LocalAppEngineServerLaunchConfigurationDelegate.java b/plugins/com.google.cloud.tools.eclipse.appengine.localserver/src/com/google/cloud/tools/eclipse/appengine/localserver/server/LocalAppEngineServerLaunchConfigurationDelegate.java
index 90c958045f..e491d283a6 100644
--- a/plugins/com.google.cloud.tools.eclipse.appengine.localserver/src/com/google/cloud/tools/eclipse/appengine/localserver/server/LocalAppEngineServerLaunchConfigurationDelegate.java
+++ b/plugins/com.google.cloud.tools.eclipse.appengine.localserver/src/com/google/cloud/tools/eclipse/appengine/localserver/server/LocalAppEngineServerLaunchConfigurationDelegate.java
@@ -25,6 +25,7 @@
import com.google.cloud.tools.eclipse.appengine.localserver.Messages;
import com.google.cloud.tools.eclipse.appengine.localserver.PreferencesInitializer;
import com.google.cloud.tools.eclipse.appengine.localserver.ui.LocalAppEngineConsole;
+import com.google.cloud.tools.eclipse.appengine.localserver.ui.StaleResourcesStatusHandler;
import com.google.cloud.tools.eclipse.ui.util.MessageConsoleUtilities;
import com.google.cloud.tools.eclipse.ui.util.WorkbenchUtil;
import com.google.cloud.tools.eclipse.usagetracker.AnalyticsEvents;
@@ -50,6 +51,7 @@
import java.util.logging.Logger;
import org.eclipse.core.resources.IMarkerDelta;
import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
@@ -57,6 +59,8 @@
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.SubMonitor;
+import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.debug.core.DebugEvent;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.DebugPlugin;
@@ -65,6 +69,7 @@
import org.eclipse.debug.core.ILaunchConfigurationType;
import org.eclipse.debug.core.ILaunchManager;
import org.eclipse.debug.core.ILaunchesListener2;
+import org.eclipse.debug.core.IStatusHandler;
import org.eclipse.debug.core.model.DebugElement;
import org.eclipse.debug.core.model.IBreakpoint;
import org.eclipse.debug.core.model.IDebugTarget;
@@ -82,6 +87,7 @@
import org.eclipse.wst.server.core.IModule;
import org.eclipse.wst.server.core.IServer;
import org.eclipse.wst.server.core.IServerListener;
+import org.eclipse.wst.server.core.ServerCore;
import org.eclipse.wst.server.core.ServerEvent;
import org.eclipse.wst.server.core.ServerUtil;
@@ -128,6 +134,65 @@ public ILaunch getLaunch(ILaunchConfiguration configuration, String mode) throws
return super.getLaunch(configuration, mode);
}
+ @Override
+ public boolean finalLaunchCheck(ILaunchConfiguration configuration, String mode,
+ IProgressMonitor monitor) throws CoreException {
+ SubMonitor progress = SubMonitor.convert(monitor, 40);
+ if (!super.finalLaunchCheck(configuration, mode, progress.newChild(20))) {
+ return false;
+ }
+
+ // If we're auto-publishing before launch, check if there may be stale
+ // resources not yet published. See
+ // https://github.com/GoogleCloudPlatform/google-cloud-eclipse/issues/1832
+ if (ServerCore.isAutoPublishing() && ResourcesPlugin.getWorkspace().isAutoBuilding()) {
+ // Must wait for any current autobuild to complete so resource changes are triggered
+ // and WTP will kick off ResourceChangeJobs. Note that there may be builds
+ // pending that are unrelated to our resource changes, so simply checking
+ // JobManager.find(FAMILY_AUTO_BUILD).length > 0 produces too many
+ // false positives.
+ try {
+ Job.getJobManager().join(ResourcesPlugin.FAMILY_AUTO_BUILD, progress.newChild(20));
+ } catch (InterruptedException ex) {
+ /* ignore */
+ }
+ IServer server = ServerUtil.getServer(configuration);
+ if (server.shouldPublish() || hasPendingChangesToPublish()) {
+ IStatusHandler prompter = DebugPlugin.getDefault().getStatusHandler(promptStatus);
+ if (prompter != null) {
+ Object continueLaunch = prompter
+ .handleStatus(StaleResourcesStatusHandler.CONTINUE_LAUNCH_REQUEST, configuration);
+ if (!(Boolean) continueLaunch) {
+ // cancel the launch so Server.StartJob won't raise an error dialog, since the
+ // server won't have been started
+ monitor.setCanceled(true);
+ return false;
+ }
+ return true;
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Check if there are pending changes to be published: this is a nasty condition that can occur if
+ * the user saves changes as part of launching the server.
+ */
+ private boolean hasPendingChangesToPublish() {
+ Job[] serverJobs = Job.getJobManager().find(ServerUtil.SERVER_JOB_FAMILY);
+ Job currentJob = Job.getJobManager().currentJob();
+ for (Job job : serverJobs) {
+ // Launching from Server#start() means this will be running within a
+ // Server.StartJob. All other jobs should be ResourceChangeJob or
+ // PublishJob, both of which indicate unpublished changes.
+ if (job != currentJob) {
+ return true;
+ }
+ }
+ return false;
+ }
+
@VisibleForTesting
void checkConflictingLaunches(ILaunchConfigurationType launchConfigType,
DefaultRunConfiguration runConfig, ILaunch[] launches) throws CoreException {
diff --git a/plugins/com.google.cloud.tools.eclipse.appengine.localserver/src/com/google/cloud/tools/eclipse/appengine/localserver/ui/StaleResourcesStatusHandler.java b/plugins/com.google.cloud.tools.eclipse.appengine.localserver/src/com/google/cloud/tools/eclipse/appengine/localserver/ui/StaleResourcesStatusHandler.java
new file mode 100644
index 0000000000..96d8980c74
--- /dev/null
+++ b/plugins/com.google.cloud.tools.eclipse.appengine.localserver/src/com/google/cloud/tools/eclipse/appengine/localserver/ui/StaleResourcesStatusHandler.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * 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.google.cloud.tools.eclipse.appengine.localserver.ui;
+
+import com.google.cloud.tools.eclipse.appengine.localserver.Messages;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.debug.core.ILaunchConfiguration;
+import org.eclipse.debug.core.IStatusHandler;
+import org.eclipse.debug.internal.ui.DebugUIPlugin;
+import org.eclipse.debug.ui.DebugUITools;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.swt.widgets.Shell;
+
+/**
+ * A simple prompter for the Platform/Debug framework to prompt the user to confirm whether the
+ * launch should continue when possibly stale resources have been found.
+ */
+public class StaleResourcesStatusHandler implements IStatusHandler {
+ /**
+ * The error code indicating that there may be stale resources. Used with the plugin ID to
+ * uniquely identify this prompter.
+ */
+ static final int CONFIRM_LAUNCH_CODE = 255;
+
+ /**
+ * A specially crafted status message that is pass into the Debug Prompter class to obtain our
+ * confirmation prompter.
+ */
+ public static final IStatus CONTINUE_LAUNCH_REQUEST = new Status(IStatus.INFO,
+ "com.google.cloud.tools.eclipse.appengine.localserver", CONFIRM_LAUNCH_CODE, "", null);
+
+ @Override
+ public Object handleStatus(IStatus status, Object source) throws CoreException {
+ if (source instanceof ILaunchConfiguration) {
+ ILaunchConfiguration config = (ILaunchConfiguration) source;
+ if (DebugUITools.isPrivate(config)) {
+ return Boolean.FALSE;
+ }
+ }
+
+ Shell activeShell = DebugUIPlugin.getShell();
+ // should consider using MessageDialogWithToggle?
+ return MessageDialog.openQuestion(activeShell, Messages.getString("STALE_RESOURCES_DETECTED"),
+ Messages.getString("STALE_RESOURCES_LAUNCH_CONFIRMATION"));
+ }
+}
diff --git a/plugins/com.google.cloud.tools.eclipse.integration.appengine/src/com/google/cloud/tools/eclipse/integration/appengine/BaseProjectTest.java b/plugins/com.google.cloud.tools.eclipse.integration.appengine/src/com/google/cloud/tools/eclipse/integration/appengine/BaseProjectTest.java
index f36e234edf..db6036af80 100644
--- a/plugins/com.google.cloud.tools.eclipse.integration.appengine/src/com/google/cloud/tools/eclipse/integration/appengine/BaseProjectTest.java
+++ b/plugins/com.google.cloud.tools.eclipse.integration.appengine/src/com/google/cloud/tools/eclipse/integration/appengine/BaseProjectTest.java
@@ -51,6 +51,9 @@ public static void setUp() throws Exception {
@After
public void tearDown() {
if (project != null) {
+ // close editors, so no property changes are dispatched on delete
+ bot.closeAllEditors();
+
// ensure there are no jobs
SwtBotWorkbenchActions.waitForProjects(bot, project);
try {
diff --git a/plugins/com.google.cloud.tools.eclipse.integration.appengine/src/com/google/cloud/tools/eclipse/integration/appengine/DebugNativeAppEngineStandardProjectTest.java b/plugins/com.google.cloud.tools.eclipse.integration.appengine/src/com/google/cloud/tools/eclipse/integration/appengine/DebugNativeAppEngineStandardProjectTest.java
index 1b57b89345..1f3aed344b 100644
--- a/plugins/com.google.cloud.tools.eclipse.integration.appengine/src/com/google/cloud/tools/eclipse/integration/appengine/DebugNativeAppEngineStandardProjectTest.java
+++ b/plugins/com.google.cloud.tools.eclipse.integration.appengine/src/com/google/cloud/tools/eclipse/integration/appengine/DebugNativeAppEngineStandardProjectTest.java
@@ -20,6 +20,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -27,7 +28,12 @@
import com.google.cloud.tools.eclipse.swtbot.SwtBotTestingUtilities;
import com.google.cloud.tools.eclipse.swtbot.SwtBotTreeUtilities;
import com.google.cloud.tools.eclipse.test.util.ThreadDumpingWatchdog;
-
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.net.URLConnection;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.TimeUnit;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.widgets.Tree;
@@ -38,18 +44,12 @@
import org.eclipse.swtbot.swt.finder.widgets.SWTBotToolbarButton;
import org.eclipse.swtbot.swt.finder.widgets.SWTBotTree;
import org.eclipse.swtbot.swt.finder.widgets.SWTBotTreeItem;
+import org.hamcrest.Matchers;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.osgi.service.prefs.Preferences;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.net.URL;
-import java.net.URLConnection;
-import java.nio.charset.StandardCharsets;
-import java.util.concurrent.TimeUnit;
-
/**
* Create a native App Engine Standard project, launch in debug mode, verify working, and then
* terminate.
@@ -101,16 +101,21 @@ public void run() {
SWTBotTree launchTree =
new SWTBotTree(bot.widget(widgetOfType(Tree.class), debugView.getWidget()));
+
+ // avoid any stray processes that may be lying around
+ launchTree.contextMenu("Remove All Terminated").click();
+
SwtBotTreeUtilities.waitUntilTreeHasItems(bot, launchTree);
SWTBotTreeItem[] allItems = launchTree.getAllItems();
SwtBotTreeUtilities.waitUntilTreeHasText(bot, allItems[0]);
- assertTrue("No App Engine launch found",
- allItems[0].getText().contains("App Engine Standard at localhost"));
+ assertThat("No App Engine launch found", allItems[0].getText(),
+ Matchers.containsString("App Engine Standard at localhost"));
SWTBotView consoleView = bot.viewById("org.eclipse.ui.console.ConsoleView"); // IConsoleConstants.ID_CONSOLE_VIEW
consoleView.show();
- assertTrue("App Engine console not active", consoleView.getViewReference()
- .getContentDescription().contains("App Engine Standard at localhost"));
+ assertThat("App Engine console not active",
+ consoleView.getViewReference().getContentDescription(),
+ Matchers.containsString("App Engine Standard at localhost"));
final SWTBotStyledText consoleContents =
new SWTBotStyledText(bot.widget(widgetOfType(StyledText.class), consoleView.getWidget()));
SwtBotTestingUtilities.waitUntilStyledTextContains(bot,