unexpected errors logging
[idea/community.git] / jps / jps-builders / src / org / jetbrains / jps / cmdline / BuildMain.java
1 /*
2  * Copyright 2000-2012 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package org.jetbrains.jps.cmdline;
17
18 import com.intellij.openapi.diagnostic.Logger;
19 import com.intellij.openapi.util.io.FileSystemUtil;
20 import com.intellij.openapi.util.io.FileUtil;
21 import io.netty.bootstrap.Bootstrap;
22 import io.netty.channel.*;
23 import io.netty.channel.nio.NioEventLoopGroup;
24 import io.netty.channel.socket.nio.NioSocketChannel;
25 import io.netty.handler.codec.protobuf.ProtobufDecoder;
26 import io.netty.handler.codec.protobuf.ProtobufEncoder;
27 import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder;
28 import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender;
29 import org.jetbrains.annotations.Nullable;
30 import org.jetbrains.jps.api.CmdlineProtoUtil;
31 import org.jetbrains.jps.api.CmdlineRemoteProto;
32 import org.jetbrains.jps.builders.BuildTarget;
33 import org.jetbrains.jps.incremental.BuilderRegistry;
34 import org.jetbrains.jps.incremental.MessageHandler;
35 import org.jetbrains.jps.incremental.Utils;
36 import org.jetbrains.jps.incremental.fs.BuildFSState;
37 import org.jetbrains.jps.incremental.messages.BuildMessage;
38 import org.jetbrains.jps.incremental.storage.BuildTargetsState;
39 import org.jetbrains.jps.service.SharedThreadPool;
40
41 import java.io.*;
42 import java.net.InetSocketAddress;
43 import java.util.UUID;
44 import java.util.concurrent.TimeUnit;
45
46 /**
47  * @author Eugene Zhuravlev
48  *         Date: 4/16/12
49  */
50 @SuppressWarnings("UseOfSystemOutOrSystemErr")
51 public class BuildMain {
52   private static final String PRELOAD_PROJECT_PATH = "preload.project.path";
53   private static final String PRELOAD_CONFIG_PATH = "preload.config.path";
54
55   private static final Logger LOG;
56   static {
57     LogSetup.initLoggers();
58     LOG = Logger.getInstance("#org.jetbrains.jps.cmdline.BuildMain");
59   }
60
61   private static final int HOST_ARG = 0;
62   private static final int PORT_ARG = HOST_ARG + 1;
63   private static final int SESSION_ID_ARG = PORT_ARG + 1;
64   private static final int SYSTEM_DIR_ARG = SESSION_ID_ARG + 1;
65
66   private static NioEventLoopGroup ourEventLoopGroup;
67   @Nullable 
68   private static PreloadedData ourPreloadedData;
69
70   public static void main(String[] args) throws Throwable{
71     try {
72       final long processStart = System.currentTimeMillis();
73       final String startMessage = "Build process started. Classpath: " + System.getProperty("java.class.path");
74       System.out.println(startMessage);
75       LOG.info(startMessage);
76
77       final String host = args[HOST_ARG];
78       final int port = Integer.parseInt(args[PORT_ARG]);
79       final UUID sessionId = UUID.fromString(args[SESSION_ID_ARG]);
80       @SuppressWarnings("ConstantConditions")
81       final File systemDir = new File(FileUtil.toCanonicalPath(args[SYSTEM_DIR_ARG]));
82       Utils.setSystemRoot(systemDir);
83
84       final long connectStart = System.currentTimeMillis();
85       // IDEA-123132, let's try again
86       for (int attempt = 0; attempt < 3; attempt++) {
87         try {
88           ourEventLoopGroup = new NioEventLoopGroup(1, SharedThreadPool.getInstance());
89           break;
90         }
91         catch (IllegalStateException e) {
92           if (attempt == 2) {
93             printErrorAndExit(host, port, e);
94             return;
95           }
96           else {
97             LOG.warn("Cannot create event loop, attempt #" + attempt, e);
98             try {
99               //noinspection BusyWait
100               Thread.sleep(10 * (attempt + 1));
101             }
102             catch (InterruptedException ignored) {
103             }
104           }
105         }
106       }
107
108       final Bootstrap bootstrap = new Bootstrap().group(ourEventLoopGroup).channel(NioSocketChannel.class).handler(new ChannelInitializer() {
109         @Override
110         protected void initChannel(Channel channel) throws Exception {
111           channel.pipeline().addLast(new ProtobufVarint32FrameDecoder(),
112                                      new ProtobufDecoder(CmdlineRemoteProto.Message.getDefaultInstance()),
113                                      new ProtobufVarint32LengthFieldPrepender(),
114                                      new ProtobufEncoder(),
115                                      new MyMessageHandler(sessionId));
116         }
117       }).option(ChannelOption.TCP_NODELAY, true).option(ChannelOption.SO_KEEPALIVE, true);
118
119       final ChannelFuture future = bootstrap.connect(new InetSocketAddress(host, port)).awaitUninterruptibly();
120
121
122       final boolean success = future.isSuccess();
123       if (success) {
124         LOG.info("Connection to IDE established in " + (System.currentTimeMillis() - connectStart) + " ms");
125
126         final String projectPathToPreload = System.getProperty(PRELOAD_PROJECT_PATH, null);
127         final String globalsPathToPreload = System.getProperty(PRELOAD_CONFIG_PATH, null);
128         if (projectPathToPreload != null && globalsPathToPreload != null) {
129           final PreloadedData data = new PreloadedData();
130           ourPreloadedData = data;
131           try {
132             FileSystemUtil.getAttributes(projectPathToPreload); // this will pre-load all FS optimizations
133
134             final BuildRunner runner = new BuildRunner(new JpsModelLoaderImpl(projectPathToPreload, globalsPathToPreload, null));
135             data.setRunner(runner);
136
137             final File dataStorageRoot = Utils.getDataStorageRoot(projectPathToPreload);
138             final BuildFSState fsState = new BuildFSState(false);
139             final ProjectDescriptor pd = runner.load(new MessageHandler() {
140               @Override
141               public void processMessage(BuildMessage msg) {
142                 data.addMessage(msg);
143               }
144             }, dataStorageRoot, fsState);
145             data.setProjectDescriptor(pd);
146
147             try {
148               final File fsStateFile = new File(dataStorageRoot, BuildSession.FS_STATE_FILE);
149               final DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream(fsStateFile)));
150               try {
151                 final int version = in.readInt();
152                 if (version == BuildFSState.VERSION) {
153                   final long savedOrdinal = in.readLong();
154                   final boolean hasWorkToDo = in.readBoolean();// must skip "has-work-to-do" flag
155                   fsState.load(in, pd.getModel(), pd.getBuildRootIndex());
156                   data.setFsEventOrdinal(savedOrdinal);
157                   data.setHasHasWorkToDo(hasWorkToDo);
158                 }
159               }
160               finally {
161                 in.close();
162               }
163             }
164             catch (FileNotFoundException ignored) {
165             }
166             catch (IOException e) {
167               LOG.info("Error pre-loading FS state", e);
168               fsState.clearAll();
169             }
170
171             // preloading target configurations
172             final BuildTargetsState targetsState = pd.getTargetsState();
173             for (BuildTarget<?> target : pd.getBuildTargetIndex().getAllTargets()) {
174               targetsState.getTargetConfiguration(target);
175             }
176
177             BuilderRegistry.getInstance();
178
179             LOG.info("Pre-loaded process ready in " + (System.currentTimeMillis() - processStart) + " ms");
180           }
181           catch (Throwable e) {
182             LOG.info("Failed to pre-load project " + projectPathToPreload, e);
183             // just failed to preload the project, the situation will be handled later, when real build starts
184           }
185         }
186         else if (projectPathToPreload != null || globalsPathToPreload != null){
187           LOG.info("Skipping project pre-loading step: both paths to project configuration files and path to global settings must be specified");
188         }
189         future.channel().writeAndFlush(CmdlineProtoUtil.toMessage(sessionId, CmdlineProtoUtil.createParamRequest()));
190       }
191       else {
192         printErrorAndExit(host, port, future.cause());
193       }
194     }
195     catch (Throwable e) {
196       LOG.error(e);
197       throw e;
198     }
199   }
200
201   private static void printErrorAndExit(String host, int port, Throwable reason) {
202     System.err.println("Error connecting to " + host + ":" + port + "; reason: " + (reason != null ? reason.getMessage() : "unknown"));
203     if (reason != null) {
204       reason.printStackTrace(System.err);
205     }
206     System.err.println("Exiting.");
207     System.exit(-1);
208   }
209
210   private static class MyMessageHandler extends SimpleChannelInboundHandler<CmdlineRemoteProto.Message> {
211     private final UUID mySessionId;
212     private volatile BuildSession mySession;
213
214     private MyMessageHandler(UUID sessionId) {
215       mySessionId = sessionId;
216     }
217
218     @Override
219     public void channelRead0(final ChannelHandlerContext context, CmdlineRemoteProto.Message message) throws Exception {
220       final CmdlineRemoteProto.Message.Type type = message.getType();
221       final Channel channel = context.channel();
222
223       if (type == CmdlineRemoteProto.Message.Type.CONTROLLER_MESSAGE) {
224         final CmdlineRemoteProto.Message.ControllerMessage controllerMessage = message.getControllerMessage();
225         switch (controllerMessage.getType()) {
226
227           case BUILD_PARAMETERS: {
228             if (mySession == null) {
229               final CmdlineRemoteProto.Message.ControllerMessage.FSEvent delta = controllerMessage.hasFsEvent()? controllerMessage.getFsEvent() : null;
230               final BuildSession session = new BuildSession(mySessionId, channel, controllerMessage.getParamsMessage(), delta, ourPreloadedData);
231               mySession = session;
232               SharedThreadPool.getInstance().executeOnPooledThread(new Runnable() {
233                 @Override
234                 public void run() {
235                   //noinspection finally
236                   try {
237                     try {
238                       session.run();
239                     }
240                     finally {
241                       channel.close();
242                     }
243                   }
244                   finally {
245                     System.exit(0);
246                   }
247                 }
248               });
249             }
250             else {
251               LOG.info("Cannot start another build session because one is already running");
252             }
253             return;
254           }
255
256           case FS_EVENT: {
257             final BuildSession session = mySession;
258             if (session != null) {
259               session.processFSEvent(controllerMessage.getFsEvent());
260             }
261             return;
262           }
263
264           case CONSTANT_SEARCH_RESULT: {
265             final BuildSession session = mySession;
266             if (session != null) {
267               session.processConstantSearchResult(controllerMessage.getConstantSearchResult());
268             }
269             return;
270           }
271
272           case CANCEL_BUILD_COMMAND: {
273             final BuildSession session = mySession;
274             if (session != null) {
275               session.cancel();
276             }
277             else {
278               LOG.info("Build canceled, but no build session is running. Exiting.");
279               try {
280                 final CmdlineRemoteProto.Message.BuilderMessage canceledEvent = CmdlineProtoUtil
281                   .createBuildCompletedEvent("build completed", CmdlineRemoteProto.Message.BuilderMessage.BuildEvent.Status.CANCELED);
282                 channel.writeAndFlush(CmdlineProtoUtil.toMessage(mySessionId, canceledEvent)).await();
283                 channel.close();
284               }
285               catch (Throwable e) {
286                 LOG.info(e);
287               }
288               Thread.interrupted(); // to clear 'interrupted' flag
289               final PreloadedData preloaded = ourPreloadedData;
290               final ProjectDescriptor pd = preloaded != null? preloaded.getProjectDescriptor() : null;
291               if (pd != null) {
292                 pd.release();
293               }
294               System.exit(0);
295             }
296             return;
297           }
298         }
299       }
300
301       channel.writeAndFlush(
302         CmdlineProtoUtil.toMessage(mySessionId, CmdlineProtoUtil.createFailure("Unsupported message type: " + type.name(), null)));
303     }
304
305     @Override
306     public void channelInactive(ChannelHandlerContext context) throws Exception {
307       try {
308         super.channelInactive(context);
309       }
310       finally {
311         new Thread("Shutdown thread") {
312           @Override
313           public void run() {
314             //noinspection finally
315             try {
316               ourEventLoopGroup.shutdownGracefully(0, 15, TimeUnit.SECONDS);
317             }
318             finally {
319               System.exit(0);
320             }
321           }
322         }.start();
323       }
324     }
325   }
326
327 }