blob: 984c5bb51278c9252f258669bb65ac498f522e0a [file] [log] [blame]
/*
* Copyright 2011 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.codeserver;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.dev.ArgProcessorBase;
import com.google.gwt.dev.cfg.ModuleDef;
import com.google.gwt.dev.jjs.JsOutputOption;
import com.google.gwt.dev.util.arg.ArgHandlerBindAddress;
import com.google.gwt.dev.util.arg.ArgHandlerClosureFormattedOutput;
import com.google.gwt.dev.util.arg.ArgHandlerGenerateJsInteropExports;
import com.google.gwt.dev.util.arg.ArgHandlerIncrementalCompile;
import com.google.gwt.dev.util.arg.ArgHandlerLogLevel;
import com.google.gwt.dev.util.arg.ArgHandlerMethodNameDisplayMode;
import com.google.gwt.dev.util.arg.ArgHandlerScriptStyle;
import com.google.gwt.dev.util.arg.ArgHandlerSetProperties;
import com.google.gwt.dev.util.arg.ArgHandlerSourceLevel;
import com.google.gwt.dev.util.arg.OptionBindAddress;
import com.google.gwt.dev.util.arg.OptionClosureFormattedOutput;
import com.google.gwt.dev.util.arg.OptionGenerateJsInteropExports;
import com.google.gwt.dev.util.arg.OptionIncrementalCompile;
import com.google.gwt.dev.util.arg.OptionLogLevel;
import com.google.gwt.dev.util.arg.OptionMethodNameDisplayMode;
import com.google.gwt.dev.util.arg.OptionScriptStyle;
import com.google.gwt.dev.util.arg.OptionSetProperties;
import com.google.gwt.dev.util.arg.OptionSourceLevel;
import com.google.gwt.dev.util.arg.SourceLevel;
import com.google.gwt.thirdparty.guava.common.collect.ImmutableList;
import com.google.gwt.thirdparty.guava.common.collect.ImmutableSet;
import com.google.gwt.thirdparty.guava.common.collect.LinkedListMultimap;
import com.google.gwt.thirdparty.guava.common.collect.ListMultimap;
import com.google.gwt.util.tools.ArgHandler;
import com.google.gwt.util.tools.ArgHandlerDir;
import com.google.gwt.util.tools.ArgHandlerExtra;
import com.google.gwt.util.tools.ArgHandlerFlag;
import com.google.gwt.util.tools.ArgHandlerInt;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
/**
* Defines the command-line options for the {@link CodeServer CodeServer's} main() method.
*
* <p>These flags are EXPERIMENTAL and subject to change.</p>
*/
public class Options {
private ImmutableList<String> args;
private Set<String> tags = new LinkedHashSet<String>();
private boolean incremental = true;
private boolean noPrecompile = false;
private boolean isCompileTest = false;
private File workDir;
private File launcherDir;
private final List<String> moduleNames = new ArrayList<String>();
private boolean allowMissingSourceDir = false;
private final List<File> sourcePath = new ArrayList<File>();
private String bindAddress = ArgHandlerBindAddress.DEFAULT_BIND_ADDRESS;
private String preferredHost = ArgHandlerBindAddress.DEFAULT_BIND_ADDRESS;
private int port = 9876;
private RecompileListener recompileListener = RecompileListener.NONE;
private JobChangeListener jobChangeListener = JobChangeListener.NONE;
private TreeLogger.Type logLevel = TreeLogger.Type.INFO;
// Use the same default as the GWT compiler.
private SourceLevel sourceLevel = SourceLevel.DEFAULT_SOURCE_LEVEL;
private boolean failOnError = false;
private int compileTestRecompiles = 0;
private boolean generateJsInteropExports = false;
private OptionMethodNameDisplayMode.Mode methodNameDisplayMode =
OptionMethodNameDisplayMode.Mode.NONE;
private boolean closureFormattedOutput = false;
// Incremental superdevmod has different defaults than devmode and regular superdevmode; we use
// null here means not set by the user (and the right default is computed by getOutput().
private JsOutputOption output = null;
private final ListMultimap<String, String> properties = LinkedListMultimap.create();
/**
* Sets each option to the appropriate value, based on command-line arguments.
* If there is an error, prints error messages and/or usage to System.err.
* @return true if the arguments were parsed successfully.
*/
public boolean parseArgs(String[] args) {
if (this.args != null) {
throw new IllegalStateException("parseArgs may only be called once");
}
this.args = ImmutableList.copyOf(Arrays.asList(args));
boolean ok = new ArgProcessor().processArgs(args);
if (!ok) {
return false;
}
if (isCompileTest && noPrecompile) {
System.err.println("Usage: -noprecompile and -compiletest are incompatible");
return false;
}
if (moduleNames.isEmpty()) {
System.err.println("Usage: at least one module must be supplied");
return false;
}
if (incremental && !noPrecompile) {
System.out.println("Turning off precompile in incremental mode.");
noPrecompile = true;
}
// Set some tags automatically for migration tracking.
if (isIncrementalCompileEnabled()) {
addTags("incremental_on");
} else {
addTags("incremental_off");
}
if (getNoPrecompile()) {
addTags("precompile_off");
} else {
addTags("precompile_on");
}
return true;
}
/**
* Adds some user-defined tags that will be passed through to {@link JobEvent#getTags}.
*
* <p>A tag may not be null, contain whitespace, or be more than 100 characters.
* If a tag was already added, it won't be added again.
*
* <p>This method may be called more than once, but compile jobs that are already running
* will not have the new tags.
*/
public synchronized void addTags(String... tags) {
this.tags.addAll(JobEvent.checkTags(Arrays.asList(tags)));
}
/**
* Returns the arguments passed to {@link #parseArgs}.
*/
ImmutableList<String> getArgs() {
return args;
}
/**
* Returns the tags passed to {@link #addTags}.
*/
synchronized Set<String> getTags() {
return ImmutableSet.copyOf(tags);
}
/**
* A Java application that embeds Super Dev Mode can use this hook to find out
* when compiles start and end.
*
* @deprecated replaced by {@link #setJobChangeListener}
*/
@Deprecated
public void setRecompileListener(RecompileListener listener) {
this.recompileListener = listener == null ? RecompileListener.NONE : listener;
}
RecompileListener getRecompileListener() {
return recompileListener;
}
/**
* A Java application that embeds Super Dev Mode can use this hook to find out
* when compile jobs change state.
*
* <p>Replaces {@link #setRecompileListener}
*/
public void setJobChangeListener(JobChangeListener listener) {
this.jobChangeListener = listener == null ? JobChangeListener.NONE : listener;
}
JobChangeListener getJobChangeListener() {
return jobChangeListener;
}
/**
* The top level of the directory tree where the code server keeps compiler output.
*/
File getWorkDir() {
return workDir;
}
/**
* A directory where each module's files for launching Super Dev Mode should be written,
* or null if not supplied.
* (For example, nocache.js and public resource files will go here.)
*/
File getLauncherDir() {
return launcherDir;
}
/**
* The names of the module that will be compiled (along with all its dependencies).
*/
List<String> getModuleNames() {
return moduleNames;
}
/**
* Whether the codeServer should allow missing source directories.
*/
boolean shouldAllowMissingSourceDir() {
return allowMissingSourceDir;
}
/**
* Compiles faster by creating a JavaScript file per class. Can't be turned on at the same time as
* shouldCompileIncremental().
*/
boolean isIncrementalCompileEnabled() {
return incremental;
}
/**
* Whether the codeServer should start without precompiling modules.
*/
boolean getNoPrecompile() {
return noPrecompile;
}
/**
* The tree logger level.
*/
TreeLogger.Type getLogLevel() {
return logLevel;
}
/**
* Java source level compatibility,
*/
SourceLevel getSourceLevel() {
return sourceLevel;
}
/**
* If true, just compile the modules, then exit.
*/
boolean isCompileTest() {
return isCompileTest;
}
/**
* The IP address where the code server should listen.
*/
String getBindAddress() {
return bindAddress;
}
int getCompileTestRecompiles() {
return compileTestRecompiles;
}
/**
* The hostname to put in a URL pointing to the code server.
*/
String getPreferredHost() {
return preferredHost;
}
/**
* The port where the code server will listen for HTTP requests.
*/
int getPort() {
return port;
}
List<File> getSourcePath() {
return sourcePath;
}
/**
* If true, run the compiler in "strict" mode, which fails the compile if any Java file
* cannot be compiled, whether or not it is used.
*/
boolean isFailOnError() {
return failOnError;
}
boolean shouldGenerateJsInteropExports() {
return generateJsInteropExports;
}
JsOutputOption getOutput() {
if (output == null) {
return isIncrementalCompileEnabled() ? JsOutputOption.OBFUSCATED : JsOutputOption.PRETTY;
}
return output;
}
ListMultimap<String, String> getProperties() {
return properties;
}
public boolean isClosureFormattedOutput() {
return closureFormattedOutput;
}
private class ArgProcessor extends ArgProcessorBase {
public ArgProcessor() {
registerHandler(new AllowMissingSourceDirFlag());
registerHandler(new CompileTestFlag());
registerHandler(new CompileTestRecompilesFlag());
registerHandler(new FailOnErrorFlag());
registerHandler(new ModuleNameArgument());
registerHandler(new NoPrecompileFlag());
registerHandler(new PortFlag());
registerHandler(new SourceFlag());
registerHandler(new WorkDirFlag());
registerHandler(new LauncherDir());
registerHandler(new ArgHandlerBindAddress(new OptionBindAddress() {
@Override
public String getBindAddress() {
return Options.this.bindAddress;
}
@Override
public String getConnectAddress() {
return Options.this.preferredHost;
}
@Override
public void setBindAddress(String bindAddress) {
Options.this.bindAddress = bindAddress;
}
@Override
public void setConnectAddress(String connectAddress) {
Options.this.preferredHost = connectAddress;
}
}));
registerHandler(new ArgHandlerScriptStyle(new OptionScriptStyle() {
@Override
public JsOutputOption getOutput() {
return Options.this.output;
}
@Override
public void setOutput(JsOutputOption output) {
Options.this.output = output;
}
}));
registerHandler(new ArgHandlerSetProperties(new OptionSetProperties() {
@Override
public void setPropertyValues(String name, Iterable<String> values) {
properties.replaceValues(name, values);
}
@Override
public ListMultimap<String, String> getProperties() {
return properties;
}
}));
registerHandler(new ArgHandlerIncrementalCompile(new OptionIncrementalCompile() {
@Override
public boolean isIncrementalCompileEnabled() {
return incremental;
}
@Override
public void setIncrementalCompileEnabled(boolean enabled) {
incremental = enabled;
}
}));
registerHandler(new ArgHandlerSourceLevel(new OptionSourceLevel() {
@Override
public SourceLevel getSourceLevel() {
return sourceLevel;
}
@Override
public void setSourceLevel(SourceLevel sourceLevel) {
Options.this.sourceLevel = sourceLevel;
}
}));
registerHandler(new ArgHandlerLogLevel(new OptionLogLevel() {
@Override
public TreeLogger.Type getLogLevel() {
return logLevel;
}
@Override
public void setLogLevel(TreeLogger.Type logLevel) {
Options.this.logLevel = logLevel;
}
}));
registerHandler(new ArgHandlerGenerateJsInteropExports(new OptionGenerateJsInteropExports() {
@Override
public boolean shouldGenerateJsInteropExports() {
return Options.this.generateJsInteropExports;
}
@Override
public void setGenerateJsInteropExports(boolean generateExports) {
Options.this.generateJsInteropExports = generateExports;
}
}));
registerHandler(new ArgHandlerMethodNameDisplayMode(new OptionMethodNameDisplayMode() {
@Override
public OptionMethodNameDisplayMode.Mode getMethodNameDisplayMode() {
return Options.this.methodNameDisplayMode;
}
@Override
public void setMethodNameDisplayMode(Mode mode) {
Options.this.methodNameDisplayMode = mode;
}
}));
registerHandler(new ArgHandlerClosureFormattedOutput(new OptionClosureFormattedOutput() {
@Override
public boolean isClosureCompilerFormatEnabled() {
return Options.this.closureFormattedOutput;
}
@Override
public void setClosureCompilerFormatEnabled(boolean enabled) {
Options.this.closureFormattedOutput = enabled;
}
}));
}
@Override
protected String getName() {
return CodeServer.class.getName();
}
}
private class NoPrecompileFlag extends ArgHandlerFlag {
@Override
public String getLabel() {
return "precompile";
}
@Override
public String getPurposeSnippet() {
return "Precompile modules.";
}
@Override
public boolean setFlag(boolean value) {
noPrecompile = !value;
return true;
}
@Override
public boolean getDefaultValue() {
return !noPrecompile;
}
}
private class CompileTestFlag extends ArgHandlerFlag {
@Override
public String getLabel() {
return "compileTest";
}
@Override
public String getPurposeSnippet() {
return "Exits after compiling the modules. The exit code will be 0 if the compile succeeded.";
}
@Override
public boolean setFlag(boolean value) {
isCompileTest = value;
return true;
}
@Override
public boolean getDefaultValue() {
return isCompileTest;
}
}
private class CompileTestRecompilesFlag extends ArgHandlerInt {
@Override
public String getTag() {
return "-compileTestRecompiles";
}
@Override
public String[] getTagArgs() {
return new String[] { "count" };
}
@Override
public String getPurpose() {
return "The number of times to recompile (after the first one) during a compile test.";
}
@Override
public void setInt(int value) {
compileTestRecompiles = value;
}
}
private class PortFlag extends ArgHandlerInt {
@Override
public String getTag() {
return "-port";
}
@Override
public String[] getTagArgs() {
return new String[] {"port"};
}
@Override
public String getPurpose() {
return "The port where the code server will run.";
}
@Override
public void setInt(int newValue) {
port = newValue;
}
}
private class WorkDirFlag extends ArgHandlerDir {
@Override
public String getTag() {
return "-workDir";
}
@Override
public String getPurpose() {
return "The root of the directory tree where the code server will"
+ "write compiler output. If not supplied, a temporary directory"
+ "will be used.";
}
@Override
public void setDir(File newValue) {
workDir = newValue;
}
}
private class FailOnErrorFlag extends ArgHandlerFlag {
FailOnErrorFlag() {
// Backward compatibility with -strict in the regular compiler.
addTagValue("-strict", true);
}
@Override
public String getLabel() {
return "failOnError";
}
@Override
public boolean getDefaultValue() {
return false;
}
@Override
public String getPurposeSnippet() {
return "Stop compiling if a module has a Java file with a compile error, even if unused.";
}
@Override
public boolean setFlag(boolean value) {
failOnError = value;
return true;
}
}
private class AllowMissingSourceDirFlag extends ArgHandlerFlag {
@Override
public String getLabel() {
return "allowMissingSrc";
}
@Override
public String getPurposeSnippet() {
return "Allows -src flags to reference missing directories.";
}
@Override
public boolean setFlag(boolean value) {
allowMissingSourceDir = value;
return true;
}
@Override
public boolean getDefaultValue() {
return allowMissingSourceDir;
}
}
private class SourceFlag extends ArgHandler {
@Override
public String getTag() {
return "-src";
}
@Override
public String[] getTagArgs() {
return new String[]{"dir"};
}
@Override
public String getPurpose() {
return "A directory containing GWT source to be prepended to the classpath for compiling.";
}
@Override
public int handle(String[] args, int startIndex) {
if (startIndex + 1 >= args.length) {
System.err.println(getTag() + " should be followed by the name of a directory");
return -1;
}
File candidate = new File(args[startIndex + 1]);
if (!allowMissingSourceDir && !candidate.isDirectory()) {
System.err.println("not a directory: " + candidate);
return -1;
}
sourcePath.add(candidate);
return 1;
}
}
private class LauncherDir extends ArgHandler {
@Override
public String getTag() {
return "-launcherDir";
}
@Override
public String[] getTags() {
// add an alias since in DevMode this was "-war"
return new String[] {getTag(), "-war"};
}
@Override
public String[] getTagArgs() {
return new String[0];
}
@Override
public String getPurpose() {
return "An output directory where files for launching Super Dev Mode will be written. "
+ "(Optional.)";
}
@Override
public int handle(String[] args, int startIndex) {
if (startIndex + 1 >= args.length) {
System.err.println(getTag() + " should be followed by the name of a directory");
return -1;
}
File candidate = new File(args[startIndex + 1]);
if (candidate.exists() && !candidate.isDirectory()) {
System.err.println("not a directory: " + candidate);
return -1;
}
launcherDir = candidate;
return 1;
}
}
private class ModuleNameArgument extends ArgHandlerExtra {
@Override
public String[] getTagArgs() {
return new String[] {"module"};
}
@Override
public String getPurpose() {
return "The GWT modules that the code server should compile. (Example: com.example.MyApp)";
}
@Override
public boolean addExtraArg(String arg) {
if (!ModuleDef.isValidModuleName(arg)) {
System.err.println("Invalid module name: '" + arg + "'");
return false;
}
moduleNames.add(arg);
return true;
}
}
public OptionMethodNameDisplayMode.Mode getMethodNameDisplayMode() {
return methodNameDisplayMode;
}
}