blob: 5aeb3f33956e41bde913f29cbe7128a2aa2c75ce [file] [log] [blame]
/*
* Copyright 2013-present Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package com.facebook.buck.plugin.intellij.commands;
import com.facebook.buck.plugin.intellij.commands.SocketClient.BuckPluginEventListener;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class BuckRunner {
private static final Logger LOG = Logger.getInstance(BuckRunner.class);
private static final String BUCK_BIN = "buck";
private static final String BUCKD_BIN = "buckd";
private static final String BUCK_EXTRA_JAVA_ARGS = "BUCK_EXTRA_JAVA_ARGS";
private static final String BUCKD_HTTPSERVER_PORT = "-Dbuck.httpserver.port";
private final File workingDirectory;
private final ExecutorService executorService;
private String buckPath;
private Optional<String> buckdPath;
private Optional<SocketClient> socket;
private BuckPluginEventListener listener;
private String stdout;
private String stderr;
public BuckRunner(Project project,
Optional<String> buckDirectory,
BuckPluginEventListener listener) throws BuckNotFound {
Preconditions.checkNotNull(project);
Preconditions.checkNotNull(buckDirectory);
this.listener = Preconditions.checkNotNull(listener);
buckdPath = Optional.absent();
socket = Optional.absent();
stdout = "";
stderr = "";
workingDirectory = new File(project.getBasePath());
Preconditions.checkState(workingDirectory.isDirectory(),
String.format("%s is not a valid working directory.", workingDirectory));
executorService = Executors.newFixedThreadPool(2); // For stdout and stderr stream readers
if (!detectBuck(buckDirectory)) {
throw new BuckNotFound();
}
}
public int execute(String... args) {
ImmutableList<String> command = ImmutableList.<String>builder()
.add(buckPath)
.addAll(ImmutableList.copyOf(args))
.build();
return execute(command, ImmutableMap.<String, String>of());
}
public int executeAndListenToWebsocket(String... args) {
if (socket.isPresent()) {
socket.get().start();
}
int exitCode = execute(args);
if (socket.isPresent()) {
socket.get().stop();
}
return exitCode;
}
public String getStdout() {
return stdout;
}
public String getStderr() {
return stderr;
}
private int execute(ImmutableList<String> command, ImmutableMap<String, String> environment) {
Preconditions.checkNotNull(command);
ProcessBuilder processBuilder = new ProcessBuilder(command);
processBuilder.directory(workingDirectory);
for (ImmutableMap.Entry<String, String> entry : environment.entrySet()) {
processBuilder.environment().put(entry.getKey(), entry.getValue());
}
try {
Process process = processBuilder.start();
int exitCode;
Future<String> stdoutFuture = readStream(process.getInputStream());
Future<String> stderrFuture = readStream(process.getErrorStream());
exitCode = process.waitFor();
stdout = stdoutFuture.get();
stderr = stderrFuture.get();
return exitCode;
} catch (IOException e) {
LOG.error(e);
} catch (ExecutionException e) {
LOG.error(e);
} catch (InterruptedException e) {
LOG.error(e);
}
return -1;
}
private boolean detectBuck(Optional<String> buckDirectory) {
if (buckDirectory.isPresent()) {
String binDirectory = buckDirectory.get() + "/bin";
if (detectBuckUnderDirectory(binDirectory)) {
return true;
}
}
// Find buck under system path
String value = Strings.nullToEmpty(System.getenv("PATH"));
for (String binDirectory : value.split(File.pathSeparator)) {
if (detectBuckUnderDirectory(binDirectory)) {
return true;
}
}
return false;
}
private boolean detectBuckUnderDirectory(String binDirectory) {
boolean success = false;
File buck = new File(binDirectory, BUCK_BIN);
if (buck.canExecute()) {
buckPath = buck.getAbsolutePath();
success = true;
}
File buckd = new File(binDirectory, BUCKD_BIN);
if (buckd.canExecute()) {
buckdPath = Optional.of(buckd.getAbsolutePath());
}
return success;
}
public void launchBuckd() {
if (buckdPath.isPresent()) {
try {
int port = findAvailablePortForBuckdHttpServer();
int exitCode = execute(ImmutableList.of(buckdPath.get()),
ImmutableMap.<String, String>of(BUCK_EXTRA_JAVA_ARGS,
String.format("%s=%d", BUCKD_HTTPSERVER_PORT, port)));
if (exitCode != 0) {
throw new Exception(getStderr());
}
socket = Optional.of(new SocketClient(port, listener));
} catch (Exception e) {
LOG.warn(String.format("Can not launch buckd: %s", e.getMessage()));
}
} else {
LOG.warn("Can not find path to buckd, buck will be used barely.");
}
}
private int findAvailablePortForBuckdHttpServer() throws IOException {
ServerSocket server = new ServerSocket(0);
int port = server.getLocalPort();
server.close();
return port;
}
private Future<String> readStream(final InputStream stream) {
return executorService.submit(new Callable<String>() {
@Override
public String call() throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
try {
StringBuilder output = new StringBuilder();
char[] buffer = new char[64];
int bytesRead;
while ((bytesRead = reader.read(buffer)) != -1) {
output.append(buffer, 0, bytesRead);
}
String outputString = output.toString();
// Unify line separators
return outputString.replace("\r\n", "\n");
} finally {
reader.close();
}
}
});
}
public class BuckNotFound extends Exception {
}
}