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