| /* |
| * 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.ArgHandlerFilterJsInteropExports; |
| 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.regexfilter.WhitelistRegexFilter; |
| 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 WhitelistRegexFilter jsInteropExportFilter = new WhitelistRegexFilter(); |
| 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; |
| } |
| |
| WhitelistRegexFilter getJsInteropExportFilter() { |
| return jsInteropExportFilter; |
| } |
| |
| 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; |
| } |
| })); |
| OptionGenerateJsInteropExports optionGenerateJsInteropExport = |
| new OptionGenerateJsInteropExports() { |
| @Override |
| public boolean shouldGenerateJsInteropExports() { |
| return Options.this.generateJsInteropExports; |
| } |
| @Override |
| public void setGenerateJsInteropExports(boolean generateExports) { |
| Options.this.generateJsInteropExports = generateExports; |
| } |
| @Override |
| public WhitelistRegexFilter getJsInteropExportFilter() { |
| return Options.this.jsInteropExportFilter; |
| } |
| }; |
| registerHandler(new ArgHandlerGenerateJsInteropExports(optionGenerateJsInteropExport)); |
| registerHandler(new ArgHandlerFilterJsInteropExports(optionGenerateJsInteropExport)); |
| 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; |
| } |
| } |