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