/* * 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.cli; import com.facebook.buck.command.Build; import com.facebook.buck.event.ConsoleEvent; import com.facebook.buck.model.BuildTarget; import com.facebook.buck.parser.NoSuchBuildTargetException; import com.facebook.buck.rules.BinaryBuildRule; import com.facebook.buck.rules.BuildRule; import com.facebook.buck.rules.SourcePathResolver; import com.facebook.buck.rules.SourcePathRuleFinder; import com.facebook.buck.rules.Tool; import com.facebook.buck.util.ForwardingProcessListener; import com.facebook.buck.util.HumanReadableException; import com.facebook.buck.util.ListeningProcessExecutor; import com.facebook.buck.util.ProcessExecutorParams; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import java.io.IOException; import java.nio.channels.Channels; import java.util.ArrayList; import java.util.List; import org.kohsuke.args4j.Argument; import org.kohsuke.args4j.Option; public final class RunCommand extends AbstractCommand { /** * Expected usage: * * <pre> * buck run //java/com/facebook/tools/munge:munge --mungearg /tmp/input * </pre> */ @Argument private List<String> noDashArguments = new ArrayList<>(); @Option(name = "--", handler = ConsumeAllOptionsHandler.class) private List<String> withDashArguments = new ArrayList<>(); private final Supplier<ImmutableList<String>> arguments = Suppliers.memoize( () -> { ImmutableList.Builder<String> builder = new ImmutableList.Builder<>(); builder.addAll(noDashArguments); builder.addAll(withDashArguments); return builder.build(); }); @VisibleForTesting ImmutableList<String> getArguments() { return arguments.get(); } /** @return the arguments (if any) to be passed to the target command. */ private ImmutableList<String> getTargetArguments() { return arguments.get().subList(1, arguments.get().size()); } private boolean hasTargetSpecified() { return arguments.get().size() > 0; } /** @return the normalized target name for command to run. */ private String getTarget(BuckConfig buckConfig) { return Iterables.getOnlyElement( getCommandLineBuildTargetNormalizer(buckConfig).normalize(arguments.get().get(0))); } @Override public String getShortDescription() { return "runs a target as a command"; } @Override public int runWithoutHelp(CommandRunnerParams params) throws IOException, InterruptedException { if (!hasTargetSpecified()) { params.getBuckEventBus().post(ConsoleEvent.severe("No target given to run")); params.getBuckEventBus().post(ConsoleEvent.severe("buck run <target> <arg1> <arg2>...")); return 1; } // Make sure the target is built. BuildCommand buildCommand = new BuildCommand(ImmutableList.of(getTarget(params.getBuckConfig()))); int exitCode = buildCommand.runWithoutHelp(params); if (exitCode != 0) { return exitCode; } String targetName = getTarget(params.getBuckConfig()); BuildTarget target = Iterables.getOnlyElement( getBuildTargets(params.getCell().getCellPathResolver(), ImmutableSet.of(targetName))); Build build = buildCommand.getBuild(); BuildRule targetRule; try { targetRule = build.getRuleResolver().requireRule(target); } catch (NoSuchBuildTargetException e) { throw new HumanReadableException(e.getHumanReadableErrorMessage()); } BinaryBuildRule binaryBuildRule = null; if (targetRule instanceof BinaryBuildRule) { binaryBuildRule = (BinaryBuildRule) targetRule; } if (binaryBuildRule == null) { params .getBuckEventBus() .post( ConsoleEvent.severe( "target " + targetName + " is not a binary rule (only binary rules can be `run`)")); return 1; } // Ideally, we would take fullCommand, disconnect from NailGun, and run the command in the // user's shell. Currently, if you use `buck run` with buckd and ctrl-C to kill the command // being run, occasionally I get the following error when I try to run `buck run` again: // // Daemon is busy, please wait or run "buck kill" to terminate it. // // Clearly something bad has happened here. If you are using `buck run` to start up a server // or some other process that is meant to "run forever," then it's pretty common to do: // `buck run`, test server, hit ctrl-C, edit server code, repeat. This should not wedge buckd. SourcePathResolver resolver = new SourcePathResolver(new SourcePathRuleFinder(build.getRuleResolver())); Tool executable = binaryBuildRule.getExecutableCommand(); ListeningProcessExecutor processExecutor = new ListeningProcessExecutor(); ProcessExecutorParams processExecutorParams = ProcessExecutorParams.builder() .addAllCommand(executable.getCommandPrefix(resolver)) .addAllCommand(getTargetArguments()) .setEnvironment( ImmutableMap.<String, String>builder() .putAll(params.getEnvironment()) .putAll(executable.getEnvironment(resolver)) .build()) .setDirectory(params.getCell().getFilesystem().getRootPath()) .build(); ForwardingProcessListener processListener = new ForwardingProcessListener( Channels.newChannel(params.getConsole().getStdOut()), Channels.newChannel(params.getConsole().getStdErr())); ListeningProcessExecutor.LaunchedProcess process = processExecutor.launchProcess(processExecutorParams, processListener); try { return processExecutor.waitForProcess(process); } finally { processExecutor.destroyProcess(process, /* force */ false); processExecutor.waitForProcess(process); } } @Override public boolean isReadOnly() { return false; } }