This change heads off an issue that is soon to be made real by the
inclusion of ImageBundler. In general, the change allows Generators
(in the compiler, hosted mode, or in JUnit tests) to make AWT
dependent calls without hanging the application on OS X.
GWTCompiler and GWTShell have been changed to ensure that we always
exit through a call to System.exit in the static main. GWTCompiler had
to be refactored to change the threading for the case when the
-treeLogger flag is passed in. Specificaly, the compiler now forks a
thread and runs the main compile on the secondary thread to allow the
main thread to pump the SWT event loop associated with the tree logger
window. A system property has also been set in BootStrapPlatform to
disable CocoaComponent compatibility mode for OS X.
Review by: scottb
bruce
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@658 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src-dummy/com/google/gwt/dev/BootStrapPlatform.java b/dev/core/src-dummy/com/google/gwt/dev/BootStrapPlatform.java
index 14ecb32..d9c2bde 100644
--- a/dev/core/src-dummy/com/google/gwt/dev/BootStrapPlatform.java
+++ b/dev/core/src-dummy/com/google/gwt/dev/BootStrapPlatform.java
@@ -1,4 +1,18 @@
-// Copyright 2006 Google Inc. All Rights Reserved.
+/*
+ * Copyright 2007 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.gwt.dev;
/**
@@ -12,4 +26,11 @@
// nothing to do
}
+ public static void maybeInitializeAWT() {
+ // nothing to do
+ }
+
+ public static void setSystemProperties() {
+ // nothing to do.
+ }
}
diff --git a/dev/core/src/com/google/gwt/dev/GWTCompiler.java b/dev/core/src/com/google/gwt/dev/GWTCompiler.java
index e2764d5..7c63b9a 100644
--- a/dev/core/src/com/google/gwt/dev/GWTCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/GWTCompiler.java
@@ -43,8 +43,8 @@
import com.google.gwt.dev.util.arg.ArgHandlerScriptStyle;
import com.google.gwt.dev.util.arg.ArgHandlerTreeLoggerFlag;
import com.google.gwt.dev.util.log.AbstractTreeLogger;
+import com.google.gwt.dev.util.log.DetachedTreeLoggerWindow;
import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
-import com.google.gwt.dev.util.log.TreeLoggerWidget;
import com.google.gwt.dev.util.xml.ReflectiveParser;
import com.google.gwt.util.tools.ArgHandlerExtra;
import com.google.gwt.util.tools.ArgHandlerOutDir;
@@ -201,20 +201,21 @@
private static final String EXT_CACHE_XML = ".cache.xml";
public static void main(String[] args) {
+ /*
+ * NOTE: main always exits with a call to System.exit to terminate any
+ * non-daemon threads that were started in Generators. Typically, this is to
+ * shutdown AWT related threads, since the contract for their termination is
+ * still implementation-dependent.
+ */
GWTCompiler compiler = new GWTCompiler();
if (compiler.processArgs(args)) {
if (compiler.run()) {
- // The only successful return path.
- //
- return;
+ // Exit w/ success code.
+ System.exit(0);
}
}
- if (!compiler.getUseGuiLogger()) {
- System.exit(1);
- } else {
- // This thread returns and the process ends with the GUI logger window
- // is closed. Calling System.exit() doesn't give you a chance to view it.
- }
+ // Exit w/ non-success code.
+ System.exit(1);
}
private final CacheManager cacheManager;
@@ -695,15 +696,51 @@
return compilation.getStrongName();
}
+ /**
+ * Runs the compiler. If a gui-based TreeLogger is used, this method will not
+ * return until its window is closed by the user.
+ *
+ * @return success from the compiler, <code>true</code> if the compile
+ * completed without errors, <code>false</code> otherwise.
+ */
private boolean run() {
- AbstractTreeLogger logger;
+ // Set any platform specific system properties.
+ BootStrapPlatform.setSystemProperties();
+
if (useGuiLogger) {
- logger = TreeLoggerWidget.getAsDetachedWindow("Build Output for "
- + moduleName, 800, 600, true);
- } else {
- logger = new PrintWriterTreeLogger();
- }
+ // Initialize a tree logger window.
+ DetachedTreeLoggerWindow loggerWindow = DetachedTreeLoggerWindow.getInstance(
+ "Build Output for " + moduleName, 800, 600, true);
+ // Eager AWT initialization for OS X to ensure safe coexistence with SWT.
+ BootStrapPlatform.maybeInitializeAWT();
+
+ final AbstractTreeLogger logger = loggerWindow.getLogger();
+ final boolean[] success = new boolean[1];
+
+ // Compiler will be spawned onto a second thread, UI thread for tree
+ // logger will remain on the main.
+ Thread compilerThread = new Thread(new Runnable() {
+ public void run() {
+ success[0] = GWTCompiler.this.run(logger);
+ }
+ });
+
+ compilerThread.setName("GWTCompiler Thread");
+ compilerThread.start();
+ loggerWindow.run();
+
+ // Even if the tree logger window is closed, we wait for the compiler
+ // to finish.
+ waitForThreadToTerminate(compilerThread);
+
+ return success[0];
+ } else {
+ return run(new PrintWriterTreeLogger());
+ }
+ }
+
+ private boolean run(AbstractTreeLogger logger) {
try {
logger.setMaxDetail(logLevel);
@@ -714,12 +751,38 @@
} catch (UnableToCompleteException e) {
// We intentionally don't pass in the exception here since the real
// cause has been logged.
- //
logger.log(TreeLogger.ERROR, "Build failed", null);
return false;
}
}
+ /**
+ * Waits for a thread to terminate before it returns. This method is a
+ * non-cancellable task, in that it will defer thread interruption until it is
+ * done.
+ *
+ * @param godot the thread that is being waited on.
+ */
+ private void waitForThreadToTerminate(final Thread godot) {
+ // Goetz pattern for non-cancellable tasks.
+ // http://www-128.ibm.com/developerworks/java/library/j-jtp05236.html
+ boolean isInterrupted = false;
+ try {
+ while (true) {
+ try {
+ godot.join();
+ return;
+ } catch (InterruptedException e) {
+ isInterrupted = true;
+ }
+ }
+ } finally {
+ if (isInterrupted) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+
private void writeCacheFile(TreeLogger logger, Compilation compilation)
throws UnableToCompleteException {
// Create and write the cache file.
diff --git a/dev/core/src/com/google/gwt/dev/GWTShell.java b/dev/core/src/com/google/gwt/dev/GWTShell.java
index 7cc0238..438db98 100644
--- a/dev/core/src/com/google/gwt/dev/GWTShell.java
+++ b/dev/core/src/com/google/gwt/dev/GWTShell.java
@@ -299,11 +299,18 @@
}
public static void main(String[] args) {
+ /*
+ * NOTE: main always exits with a call to System.exit to terminate any
+ * non-daemon threads that were started in Generators. Typically, this is to
+ * shutdown AWT related threads, since the contract for their termination is
+ * still implementation-dependent.
+ */
BootStrapPlatform.go();
GWTShell shellMain = new GWTShell();
if (shellMain.processArgs(args)) {
shellMain.run();
}
+ System.exit(0);
}
/**
@@ -516,14 +523,18 @@
*/
public void run() {
try {
+ // Set any platform specific system properties.
+ BootStrapPlatform.setSystemProperties();
+
if (!startUp()) {
// Failed to initalize.
- //
return;
}
+ // Eager AWT initialization for OS X to ensure safe coexistence with SWT.
+ BootStrapPlatform.maybeInitializeAWT();
+
// Tomcat's running now, so launch browsers for startup urls now.
- //
launchStartupUrls(getTopLogger());
pumpEventLoop();
diff --git a/dev/core/src/com/google/gwt/dev/util/log/DetachedTreeLoggerWindow.java b/dev/core/src/com/google/gwt/dev/util/log/DetachedTreeLoggerWindow.java
new file mode 100644
index 0000000..91e7f13
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/util/log/DetachedTreeLoggerWindow.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2007 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.gwt.dev.util.log;
+
+import com.google.gwt.dev.shell.LowLevel;
+
+import org.eclipse.swt.layout.FillLayout;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+
+/**
+ * Useful for debugging, this class manages to standalone window
+ * and provides access to a logger you can use to write to it.
+ *
+ */
+public class DetachedTreeLoggerWindow implements Runnable {
+ private static DetachedTreeLoggerWindow singleton;
+
+ /**
+ * Provides a reference to a singleton <code>DetachedTreeLoggerWindow</code>.
+ *
+ * @param caption the text to appear in the windows title bar.
+ * @param width the widget of the window
+ * @param height the height of the window
+ * @param autoScroll whether or not the window should autoscroll as output is
+ * produced
+ * @return a proxy object providing limited control of the window.
+ */
+ public static synchronized DetachedTreeLoggerWindow getInstance(
+ final String caption, final int width, final int height,
+ final boolean autoScroll) {
+ if (singleton == null) {
+ singleton = new DetachedTreeLoggerWindow(caption, width, height,
+ autoScroll);
+ }
+ return singleton;
+ }
+
+ private final Shell shell;
+ private final AbstractTreeLogger logger;
+ private boolean isRunning = false;
+
+ private DetachedTreeLoggerWindow(final String caption, final int width,
+ final int height, final boolean autoScroll) {
+
+ shell = new Shell(Display.getCurrent());
+ shell.setText(caption);
+ FillLayout fillLayout = new FillLayout();
+ fillLayout.marginWidth = 0;
+ fillLayout.marginHeight = 0;
+ shell.setLayout(fillLayout);
+
+ final TreeLoggerWidget treeLoggerWidget = new TreeLoggerWidget(shell);
+ treeLoggerWidget.setAutoScroll(autoScroll);
+ logger = treeLoggerWidget.getLogger();
+
+ shell.setImage(LowLevel.loadImage("gwt.ico"));
+ shell.setSize(width, height);
+ shell.open();
+ }
+
+ public AbstractTreeLogger getLogger() {
+ return logger;
+ }
+
+ public synchronized boolean isRunning() {
+ return isRunning;
+ }
+
+ public void run() {
+ if (!maybeStart()) {
+ throw new IllegalStateException(
+ "DetachedTreeLogger window is already running.");
+ }
+
+ final Display display = shell.getDisplay();
+ while (!shell.isDisposed()) {
+ if (!display.readAndDispatch()) {
+ display.sleep();
+ }
+ }
+ }
+
+ private synchronized boolean maybeStart() {
+ if (isRunning) {
+ return false;
+ }
+ isRunning = true;
+ return true;
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/util/log/Loggers.java b/dev/core/src/com/google/gwt/dev/util/log/Loggers.java
index 8508017..b50a3ef 100644
--- a/dev/core/src/com/google/gwt/dev/util/log/Loggers.java
+++ b/dev/core/src/com/google/gwt/dev/util/log/Loggers.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006 Google Inc.
+ * Copyright 2007 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
@@ -32,8 +32,10 @@
*/
public static TreeLogger createOptionalGuiTreeLogger() {
if (System.getProperty("gwt.useGuiLogger") != null) {
- AbstractTreeLogger atl = TreeLoggerWidget.getAsDetachedWindow(
+ DetachedTreeLoggerWindow logWindow = DetachedTreeLoggerWindow.getInstance(
"CompilationServiceTest", 800, 600, true);
+ AbstractTreeLogger atl = logWindow.getLogger();
+ new Thread(logWindow).start();
return maybeSetDetailLevel(atl);
} else {
return TreeLogger.NULL;
diff --git a/dev/core/src/com/google/gwt/dev/util/log/TreeLoggerWidget.java b/dev/core/src/com/google/gwt/dev/util/log/TreeLoggerWidget.java
index 392a7d5..2f7908f 100644
--- a/dev/core/src/com/google/gwt/dev/util/log/TreeLoggerWidget.java
+++ b/dev/core/src/com/google/gwt/dev/util/log/TreeLoggerWidget.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006 Google Inc.
+ * Copyright 2007 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
@@ -16,7 +16,6 @@
package com.google.gwt.dev.util.log;
import com.google.gwt.core.ext.UnableToCompleteException;
-import com.google.gwt.dev.shell.LowLevel;
import com.google.gwt.dev.util.log.TreeItemLogger.LogEvent;
import org.eclipse.swt.SWT;
@@ -36,7 +35,6 @@
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
@@ -50,82 +48,6 @@
public class TreeLoggerWidget extends Composite implements TreeListener,
SelectionListener {
- private static AbstractTreeLogger singletonWindowLogger;
-
- /**
- * Useful for debugging, this method creates a standalone window in a
- * background thread and returns a logger you can use to write to it.
- */
- public static synchronized AbstractTreeLogger getAsDetachedWindow(
- final String caption, final int width, final int height,
- final boolean autoScroll) {
-
- if (singletonWindowLogger != null) {
- // Already set.
- //
- return singletonWindowLogger;
- }
-
- final AbstractTreeLogger[] loggerHolder = new AbstractTreeLogger[1];
- Thread logWindowThread = new Thread() {
-
- public void run() {
- // Create a shell.
- //
- Shell shell = new Shell(Display.getCurrent());
- shell.setText(caption);
- FillLayout fillLayout = new FillLayout();
- fillLayout.marginWidth = 0;
- fillLayout.marginHeight = 0;
- shell.setLayout(fillLayout);
-
- // Create a logger in it.
- //
- synchronized (loggerHolder) {
- final TreeLoggerWidget treeLoggerWidget = new TreeLoggerWidget(shell);
- treeLoggerWidget.setAutoScroll(autoScroll);
- loggerHolder[0] = treeLoggerWidget.getLogger();
- }
-
- // Set the shell's icon and size then show it.
- //
- shell.setImage(LowLevel.loadImage("gwt.ico"));
- shell.setSize(width, height);
- shell.open();
-
- // Pump the event loop until the shell is closed.
- //
- Display display = Display.getCurrent();
- while (!shell.isDisposed()) {
- try {
- if (!display.readAndDispatch()) {
- display.sleep();
- }
- } catch (Throwable e) {
- // Ignored -- this method is only intended for test harnesses.
- }
- }
- }
- };
-
- // Start the thread and wait until the loggerHolder has something.
- //
- logWindowThread.setName("TreeLogger Window");
- logWindowThread.start();
- while (singletonWindowLogger == null) {
- Thread.yield();
- if (logWindowThread.isAlive()) {
- synchronized (loggerHolder) {
- singletonWindowLogger = loggerHolder[0];
- }
- } else {
- throw new IllegalStateException("Log window thread died unexpectedly");
- }
- }
-
- return singletonWindowLogger;
- }
-
private boolean autoScroll;
private final Text details;
diff --git a/dev/core/test/com/google/gwt/dev/jdt/test/ByteCodeCompilerTest.java b/dev/core/test/com/google/gwt/dev/jdt/test/ByteCodeCompilerTest.java
index 5f3a6b2..c14a600 100644
--- a/dev/core/test/com/google/gwt/dev/jdt/test/ByteCodeCompilerTest.java
+++ b/dev/core/test/com/google/gwt/dev/jdt/test/ByteCodeCompilerTest.java
@@ -226,12 +226,6 @@
private final Set pkgNames;
}
- public static void main(String[] args) throws UnableToCompleteException {
- // Run this to try to put the hurtin' on the compiler.
- //
- scanAndCompile(Loggers.createOptionalGuiTreeLogger());
- }
-
private static void scanAndCompile(TreeLogger logger)
throws UnableToCompleteException {
FileOracleFactory fof = new FileOracleFactory();
diff --git a/dev/linux/src/com/google/gwt/dev/BootStrapPlatform.java b/dev/linux/src/com/google/gwt/dev/BootStrapPlatform.java
index bb52b54..c42f750 100644
--- a/dev/linux/src/com/google/gwt/dev/BootStrapPlatform.java
+++ b/dev/linux/src/com/google/gwt/dev/BootStrapPlatform.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006 Google Inc.
+ * Copyright 2007 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
@@ -24,4 +24,11 @@
// nothing to do
}
+ public static void maybeInitializeAWT() {
+ // nothing to do
+ }
+
+ public static void setSystemProperties() {
+ // nothing to do
+ }
}
diff --git a/dev/mac/src/com/google/gwt/dev/BootStrapPlatform.java b/dev/mac/src/com/google/gwt/dev/BootStrapPlatform.java
index 0199ea2..d180574 100644
--- a/dev/mac/src/com/google/gwt/dev/BootStrapPlatform.java
+++ b/dev/mac/src/com/google/gwt/dev/BootStrapPlatform.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006 Google Inc.
+ * Copyright 2007 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
@@ -17,6 +17,8 @@
import com.google.gwt.dev.shell.mac.LowLevelSaf;
+import java.awt.Toolkit;
+
/**
* Initializes the low level libraries for Mac.
*/
@@ -41,4 +43,54 @@
System.exit(-1);
}
+ /**
+ *
+ * This works around a complicated set of OS X SWT/AWT compatibilities.
+ * {@link #setSystemProperties()} will typically need to be called first to
+ * ensure that CocoaComponent compatability mode is disabled. The constraints
+ * of using SWT and AWT together are:
+ *
+ * <p>
+ * 1 - The SWT event dispatch needs to be running on the main application
+ * thread (only possible with -XstartOnFirstThread vm arg).
+ * </p>
+ * <p>
+ * 2 - The first call into AWT must be from the main thread after a SWT
+ * display has been initialized.
+ * </p>
+ *
+ * This method allows the compiler to have a tree logger in a SWT window and
+ * allow generators to use AWT for image generation.
+ *
+ * <p>
+ * NOTE: In GUI applications, {@link #setSystemProperties()} and
+ * {@link #maybeInitializeAWT()} will both be called during the bootstrap
+ * process. Command line applications (like
+ * @{link com.google.gwt.dev.GWTCompiler}) avoid eagerly initializing AWT
+ * and only call {@link #setSystemProperties()} allowing AWT to be
+ * initialized on demand.
+ * </p>
+ */
+ public static void maybeInitializeAWT() {
+ final Toolkit toolkit = Toolkit.getDefaultToolkit();
+ }
+
+ /**
+ * Sets platform specific system properties. Currently, this disables
+ * CocoaComponent CompatibilityMode.
+ *
+ * <p>
+ * NOTE: In GUI applications, {@link #setSystemProperties()} and
+ * {@link #maybeInitializeAWT()} will both be called during the bootstrap
+ * process. Command line applications (like
+ * @{link com.google.gwt.dev.GWTCompiler}) avoid eagerly initializing AWT
+ * and only call {@link #setSystemProperties()} allowing AWT to be
+ * initialized on demand.
+ * </p>
+ */
+ public static void setSystemProperties() {
+ // Disable CocoaComponent compatibility mode.
+ System.setProperty("com.apple.eawt.CocoaComponent.CompatibilityMode",
+ "false");
+ }
}
diff --git a/dev/windows/src/com/google/gwt/dev/BootStrapPlatform.java b/dev/windows/src/com/google/gwt/dev/BootStrapPlatform.java
index 05f9b78..fc19628 100644
--- a/dev/windows/src/com/google/gwt/dev/BootStrapPlatform.java
+++ b/dev/windows/src/com/google/gwt/dev/BootStrapPlatform.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006 Google Inc.
+ * Copyright 2007 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
@@ -23,4 +23,12 @@
public static void go() {
// nothing to do
}
+
+ public static void maybeInitializeAWT() {
+ // nothing to do
+ }
+
+ public static void setSystemProperties() {
+ // nothing to do
+ }
}