1package org.chromium.devtools.compiler; 2 3import com.google.common.collect.Lists; 4import com.google.javascript.jscomp.CommandLineRunner; 5import com.google.javascript.jscomp.CompilerOptions; 6 7import org.kohsuke.args4j.CmdLineException; 8import org.kohsuke.args4j.CmdLineParser; 9import org.kohsuke.args4j.Option; 10 11import java.io.BufferedReader; 12import java.io.ByteArrayOutputStream; 13import java.io.FileInputStream; 14import java.io.IOException; 15import java.io.InputStreamReader; 16import java.io.PrintStream; 17import java.util.ArrayList; 18import java.util.Collections; 19import java.util.List; 20import java.util.concurrent.Callable; 21import java.util.concurrent.ExecutorService; 22import java.util.concurrent.Executors; 23import java.util.concurrent.Future; 24import java.util.regex.Matcher; 25import java.util.regex.Pattern; 26 27/** 28 * Prepares and executes several instances of the closure compiler. 29 */ 30public class Runner { 31 protected final Flags flags = new Flags(); 32 private final PrintStream err; 33 private boolean isConfigValid; 34 35 public Runner(String[] args, PrintStream err) { 36 this.err = err; 37 List<String> argList = processArgs(args); 38 CmdLineParser parser = new CmdLineParser(flags); 39 isConfigValid = true; 40 try { 41 parser.parseArgument(argList.toArray(new String[] {})); 42 if (flags.compilerArgsFile == null) { 43 isConfigValid = false; 44 } 45 } catch (CmdLineException e) { 46 err.println(e.getMessage()); 47 isConfigValid = false; 48 } 49 50 if (!isConfigValid) { 51 parser.printUsage(err); 52 } 53 } 54 55 private List<String> processArgs(String[] args) { 56 Pattern argPattern = Pattern.compile("(--[a-zA-Z_]+)=(.*)"); 57 Pattern quotesPattern = Pattern.compile("^['\"](.*)['\"]$"); 58 List<String> processedArgs = Lists.newArrayList(); 59 60 for (String arg : args) { 61 Matcher matcher = argPattern.matcher(arg); 62 if (matcher.matches()) { 63 processedArgs.add(matcher.group(1)); 64 65 String value = matcher.group(2); 66 Matcher quotesMatcher = quotesPattern.matcher(value); 67 if (quotesMatcher.matches()) { 68 processedArgs.add(quotesMatcher.group(1)); 69 } else { 70 processedArgs.add(value); 71 } 72 } else { 73 processedArgs.add(arg); 74 } 75 } 76 77 return processedArgs; 78 } 79 80 private boolean shouldRunCompiler() { 81 return isConfigValid; 82 } 83 84 protected void logError(String message, Exception e) { 85 err.println("ERROR: " + message); 86 if (e != null) { 87 e.printStackTrace(err); 88 } 89 } 90 91 private void run() { 92 List<CompilerInstanceDescriptor> descriptors = getDescriptors(); 93 if (descriptors == null) { 94 return; 95 } 96 ExecutorService executor = Executors.newFixedThreadPool( 97 Math.min(descriptors.size(), Runtime.getRuntime().availableProcessors() / 2 + 1)); 98 try { 99 runWithExecutor(descriptors, executor); 100 } finally { 101 executor.shutdown(); 102 } 103 } 104 105 private void runWithExecutor( 106 List<CompilerInstanceDescriptor> descriptors, ExecutorService executor) { 107 List<Future<CompilerRunner>> futures = new ArrayList<>(descriptors.size()); 108 for (CompilerInstanceDescriptor descriptor : descriptors) { 109 CompilerRunner task = new CompilerRunner(descriptor, new ByteArrayOutputStream(512)); 110 futures.add(executor.submit(task)); 111 } 112 113 for (Future<CompilerRunner> future : futures) { 114 try { 115 CompilerRunner task = future.get(); 116 int result = task.result; 117 if (result != 0) { 118 System.err.println("ERROR: Compiler returned " + result); 119 } 120 task.errStream.flush(); 121 System.err.println("@@ START_MODULE:" + task.descriptor.moduleName + " @@"); 122 System.err.println(task.errStream.toString("UTF-8")); 123 System.err.println("@@ END_MODULE @@"); 124 } catch (Exception e) { 125 System.err.println("ERROR - " + e.getMessage()); 126 } 127 } 128 System.exit(0); 129 } 130 131 private List<CompilerInstanceDescriptor> getDescriptors() { 132 List<CompilerInstanceDescriptor> result = new ArrayList<>(); 133 try (BufferedReader reader = new BufferedReader( 134 new InputStreamReader( 135 new FileInputStream(flags.compilerArgsFile), "UTF-8"))) { 136 int lineIndex = 0; 137 while (true) { 138 ++lineIndex; 139 String line = reader.readLine(); 140 if (line == null) { 141 break; 142 } 143 if (line.length() == 0) { 144 continue; 145 } 146 String[] moduleAndArgs = line.split(" +", 2); 147 if (moduleAndArgs.length != 2) { 148 logError(String.format( 149 "Line %d does not contain module name and compiler arguments", 150 lineIndex), null); 151 continue; 152 } 153 result.add(new CompilerInstanceDescriptor(moduleAndArgs[0], moduleAndArgs[1])); 154 } 155 } catch (IOException e) { 156 logError("Failed to read compiler arguments file", e); 157 return null; 158 } 159 160 return result; 161 } 162 163 public static void main(String[] args) { 164 Runner runner = new Runner(args, System.err); 165 if (runner.shouldRunCompiler()) { 166 runner.run(); 167 } else { 168 System.exit(-1); 169 } 170 } 171 172 private static class LocalCommandLineRunner extends CommandLineRunner { 173 protected LocalCommandLineRunner(String[] args, PrintStream out, PrintStream err) { 174 super(args, out, err); 175 } 176 177 @Override 178 protected CompilerOptions createOptions() { 179 CompilerOptions options = super.createOptions(); 180 options.setIdeMode(true); 181 options.setExtraAnnotationNames(Collections.singletonList("suppressReceiverCheck")); 182 return options; 183 } 184 185 @Override 186 protected void setRunOptions(CompilerOptions options) 187 throws FlagUsageException, IOException { 188 super.setRunOptions(options); 189 options.setCodingConvention(new DevToolsCodingConvention()); 190 } 191 192 int execute() { 193 int result = 0; 194 int runs = 1; 195 try { 196 for (int i = 0; i < runs && result == 0; i++) { 197 result = doRun(); 198 } 199 } catch (Throwable t) { 200 t.printStackTrace(); 201 result = -2; 202 } 203 return result; 204 } 205 } 206 207 private static class CompilerRunner implements Callable<CompilerRunner> { 208 private final CompilerInstanceDescriptor descriptor; 209 private final ByteArrayOutputStream errStream; 210 private int result; 211 212 public CompilerRunner( 213 CompilerInstanceDescriptor descriptor, ByteArrayOutputStream errStream) { 214 this.descriptor = descriptor; 215 this.errStream = errStream; 216 } 217 218 @Override 219 public CompilerRunner call() throws Exception { 220 PrintStream errPrintStream = new PrintStream(errStream, false, "UTF-8"); 221 LocalCommandLineRunner runner = 222 new LocalCommandLineRunner(prepareArgs(), System.out, errPrintStream); 223 if (!runner.shouldRunCompiler()) { 224 this.result = -1; 225 } 226 this.result = runner.execute(); 227 return this; 228 } 229 230 private String[] prepareArgs() { 231 // FIXME: This does not support quoted arguments. 232 return descriptor.commandLine.split(" +"); 233 } 234 } 235 236 private static class Flags { 237 @Option(name = "--compiler-args-file", 238 usage = "Full path to file containing compiler arguments (one line per instance)") 239 private String compilerArgsFile = null; 240 } 241 242 private static class CompilerInstanceDescriptor { 243 private final String moduleName; 244 private final String commandLine; 245 246 public CompilerInstanceDescriptor(String moduleName, String commandLine) { 247 this.moduleName = moduleName; 248 this.commandLine = commandLine; 249 } 250 } 251} 252