2 * Copyright 2000-2012 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package org.jetbrains.jps.cmdline;
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;
42 import java.net.InetSocketAddress;
43 import java.util.UUID;
44 import java.util.concurrent.TimeUnit;
47 * @author Eugene Zhuravlev
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";
55 private static final Logger LOG;
57 LogSetup.initLoggers();
58 LOG = Logger.getInstance("#org.jetbrains.jps.cmdline.BuildMain");
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;
66 private static NioEventLoopGroup ourEventLoopGroup;
68 private static PreloadedData ourPreloadedData;
70 public static void main(String[] args) throws Throwable{
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);
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);
84 final long connectStart = System.currentTimeMillis();
85 // IDEA-123132, let's try again
86 for (int attempt = 0; attempt < 3; attempt++) {
88 ourEventLoopGroup = new NioEventLoopGroup(1, SharedThreadPool.getInstance());
91 catch (IllegalStateException e) {
93 printErrorAndExit(host, port, e);
97 LOG.warn("Cannot create event loop, attempt #" + attempt, e);
99 //noinspection BusyWait
100 Thread.sleep(10 * (attempt + 1));
102 catch (InterruptedException ignored) {
108 final Bootstrap bootstrap = new Bootstrap().group(ourEventLoopGroup).channel(NioSocketChannel.class).handler(new ChannelInitializer() {
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));
117 }).option(ChannelOption.TCP_NODELAY, true).option(ChannelOption.SO_KEEPALIVE, true);
119 final ChannelFuture future = bootstrap.connect(new InetSocketAddress(host, port)).awaitUninterruptibly();
122 final boolean success = future.isSuccess();
124 LOG.info("Connection to IDE established in " + (System.currentTimeMillis() - connectStart) + " ms");
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;
132 FileSystemUtil.getAttributes(projectPathToPreload); // this will pre-load all FS optimizations
134 final BuildRunner runner = new BuildRunner(new JpsModelLoaderImpl(projectPathToPreload, globalsPathToPreload, null));
135 data.setRunner(runner);
137 final File dataStorageRoot = Utils.getDataStorageRoot(projectPathToPreload);
138 final BuildFSState fsState = new BuildFSState(false);
139 final ProjectDescriptor pd = runner.load(new MessageHandler() {
141 public void processMessage(BuildMessage msg) {
142 data.addMessage(msg);
144 }, dataStorageRoot, fsState);
145 data.setProjectDescriptor(pd);
148 final File fsStateFile = new File(dataStorageRoot, BuildSession.FS_STATE_FILE);
149 final DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream(fsStateFile)));
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);
164 catch (FileNotFoundException ignored) {
166 catch (IOException e) {
167 LOG.info("Error pre-loading FS state", e);
171 // preloading target configurations
172 final BuildTargetsState targetsState = pd.getTargetsState();
173 for (BuildTarget<?> target : pd.getBuildTargetIndex().getAllTargets()) {
174 targetsState.getTargetConfiguration(target);
177 BuilderRegistry.getInstance();
179 LOG.info("Pre-loaded process ready in " + (System.currentTimeMillis() - processStart) + " ms");
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
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");
189 future.channel().writeAndFlush(CmdlineProtoUtil.toMessage(sessionId, CmdlineProtoUtil.createParamRequest()));
192 printErrorAndExit(host, port, future.cause());
195 catch (Throwable e) {
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);
206 System.err.println("Exiting.");
210 private static class MyMessageHandler extends SimpleChannelInboundHandler<CmdlineRemoteProto.Message> {
211 private final UUID mySessionId;
212 private volatile BuildSession mySession;
214 private MyMessageHandler(UUID sessionId) {
215 mySessionId = sessionId;
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();
223 if (type == CmdlineRemoteProto.Message.Type.CONTROLLER_MESSAGE) {
224 final CmdlineRemoteProto.Message.ControllerMessage controllerMessage = message.getControllerMessage();
225 switch (controllerMessage.getType()) {
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);
232 SharedThreadPool.getInstance().executeOnPooledThread(new Runnable() {
235 //noinspection finally
251 LOG.info("Cannot start another build session because one is already running");
257 final BuildSession session = mySession;
258 if (session != null) {
259 session.processFSEvent(controllerMessage.getFsEvent());
264 case CONSTANT_SEARCH_RESULT: {
265 final BuildSession session = mySession;
266 if (session != null) {
267 session.processConstantSearchResult(controllerMessage.getConstantSearchResult());
272 case CANCEL_BUILD_COMMAND: {
273 final BuildSession session = mySession;
274 if (session != null) {
278 LOG.info("Build canceled, but no build session is running. Exiting.");
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();
285 catch (Throwable e) {
288 Thread.interrupted(); // to clear 'interrupted' flag
289 final PreloadedData preloaded = ourPreloadedData;
290 final ProjectDescriptor pd = preloaded != null? preloaded.getProjectDescriptor() : null;
301 channel.writeAndFlush(
302 CmdlineProtoUtil.toMessage(mySessionId, CmdlineProtoUtil.createFailure("Unsupported message type: " + type.name(), null)));
306 public void channelInactive(ChannelHandlerContext context) throws Exception {
308 super.channelInactive(context);
311 new Thread("Shutdown thread") {
314 //noinspection finally
316 ourEventLoopGroup.shutdownGracefully(0, 15, TimeUnit.SECONDS);