Runner.java revision 6e8cce623b6e4fe0c9e4af605d675dd9d0338c38
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