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