blob: 33b7e2a23144a23e45fc96cc5042d002bee3dbc7 [file] [log] [blame]
//===-- BuildSystemFrontend.cpp -------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
#include "llbuild/BuildSystem/BuildSystemFrontend.h"
#include "llbuild/Basic/FileSystem.h"
#include "llbuild/Basic/LLVM.h"
#include "llbuild/Basic/PlatformUtility.h"
#include "llbuild/BuildSystem/BuildDescription.h"
#include "llbuild/BuildSystem/BuildExecutionQueue.h"
#include "llbuild/BuildSystem/BuildFile.h"
#include "llbuild/BuildSystem/BuildKey.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/Support/Format.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/SourceMgr.h"
#include "llvm/Support/raw_ostream.h"
#include <atomic>
#include <memory>
#include <thread>
using namespace llbuild;
using namespace llbuild::basic;
using namespace llbuild::buildsystem;
#pragma mark - BuildSystemInvocation implementation
void BuildSystemInvocation::getUsage(int optionWidth, raw_ostream& os) {
const struct Options {
llvm::StringRef option, helpText;
} options[] = {
{ "--help", "show this help message and exit" },
{ "--version", "show the tool version" },
{ "-C <PATH>, --chdir <PATH>", "change directory to PATH before building" },
{ "--no-db", "disable use of a build database" },
{ "--db <PATH>", "enable building against the database at PATH" },
{ "-f <PATH>", "load the build task file at PATH" },
{ "--serial", "do not build in parallel" },
{ "-v, --verbose", "show verbose status information" },
{ "--trace <PATH>", "trace build engine operation to PATH" },
};
for (const auto& entry: options) {
os << " " << llvm::format("%-*s", optionWidth, entry.option) << " "
<< entry.helpText << "\n";
}
}
void BuildSystemInvocation::parse(llvm::ArrayRef<std::string> args,
llvm::SourceMgr& sourceMgr) {
auto error = [&](const Twine &message) {
sourceMgr.PrintMessage(llvm::SMLoc{}, llvm::SourceMgr::DK_Error, message);
hadErrors = true;
};
while (!args.empty()) {
const auto& option = args.front();
args = args.slice(1);
if (option == "-") {
for (const auto& arg: args) {
positionalArgs.push_back(arg);
}
break;
}
if (!option.empty() && option[0] != '-') {
positionalArgs.push_back(option);
continue;
}
if (option == "--help") {
showUsage = true;
break;
} else if (option == "--version") {
showVersion = true;
break;
} else if (option == "--no-db") {
dbPath = "";
} else if (option == "--db") {
if (args.empty()) {
error("missing argument to '" + option + "'");
break;
}
dbPath = args[0];
args = args.slice(1);
} else if (option == "-C" || option == "--chdir") {
if (args.empty()) {
error("missing argument to '" + option + "'");
break;
}
chdirPath = args[0];
args = args.slice(1);
} else if (option == "-f") {
if (args.empty()) {
error("missing argument to '" + option + "'");
break;
}
buildFilePath = args[0];
args = args.slice(1);
} else if (option == "--serial") {
useSerialBuild = true;
} else if (option == "-v" || option == "--verbose") {
showVerboseStatus = true;
} else if (option == "--trace") {
if (args.empty()) {
error("missing argument to '" + option + "'");
break;
}
traceFilePath = args[0];
args = args.slice(1);
} else {
error("invalid option '" + option + "'");
break;
}
}
}
std::string BuildSystemInvocation::formatDetectedCycle(const std::vector<core::Rule*>& cycle) {
// Compute a description of the cycle path.
SmallString<256> message;
llvm::raw_svector_ostream os(message);
os << "cycle detected while building: ";
bool first = true;
for (const auto* rule: cycle) {
if (!first)
os << " -> ";
// Convert to a build key.
auto key = BuildKey::fromData(rule->key);
switch (key.getKind()) {
case BuildKey::Kind::Unknown:
os << "((unknown))";
break;
case BuildKey::Kind::Command:
os << "command '" << key.getCommandName() << "'";
break;
case BuildKey::Kind::CustomTask:
os << "custom task '" << key.getCustomTaskName() << "'";
break;
case BuildKey::Kind::DirectoryContents:
os << "directory-contents '" << key.getDirectoryContentsPath() << "'";
break;
case BuildKey::Kind::DirectoryTreeSignature:
os << "directory-tree-signature '"
<< key.getDirectoryTreeSignaturePath() << "'";
break;
case BuildKey::Kind::Node:
os << "node '" << key.getNodeName() << "'";
break;
case BuildKey::Kind::Target:
os << "target '" << key.getTargetName() << "'";
break;
}
first = false;
}
return os.str().str();
}
#pragma mark - BuildSystemFrontendDelegate implementation
namespace {
struct BuildSystemFrontendDelegateImpl;
class BuildSystemFrontendExecutionQueueDelegate
: public BuildExecutionQueueDelegate {
BuildSystemFrontendDelegateImpl &delegateImpl;
bool showVerboseOutput() const;
BuildSystem& getSystem() const;
public:
BuildSystemFrontendExecutionQueueDelegate(
BuildSystemFrontendDelegateImpl& delegateImpl)
: delegateImpl(delegateImpl) { }
virtual void commandJobStarted(Command* command) override {
static_cast<BuildSystemFrontendDelegate*>(&getSystem().getDelegate())->
commandJobStarted(command);
}
virtual void commandJobFinished(Command* command) override {
static_cast<BuildSystemFrontendDelegate*>(&getSystem().getDelegate())->
commandJobFinished(command);
}
virtual void commandProcessStarted(Command* command,
ProcessHandle handle) override {
static_cast<BuildSystemFrontendDelegate*>(&getSystem().getDelegate())->
commandProcessStarted(
command, BuildSystemFrontendDelegate::ProcessHandle { handle.id });
}
virtual void commandProcessHadError(Command* command, ProcessHandle handle,
const Twine& message) override {
static_cast<BuildSystemFrontendDelegate*>(&getSystem().getDelegate())->
commandProcessHadError(
command, BuildSystemFrontendDelegate::ProcessHandle { handle.id },
message);
}
virtual void commandProcessHadOutput(Command* command, ProcessHandle handle,
StringRef data) override {
static_cast<BuildSystemFrontendDelegate*>(&getSystem().getDelegate())->
commandProcessHadOutput(
command, BuildSystemFrontendDelegate::ProcessHandle { handle.id },
data);
}
virtual void commandProcessFinished(Command* command, ProcessHandle handle,
CommandResult result,
int exitStatus) override {
static_cast<BuildSystemFrontendDelegate*>(&getSystem().getDelegate())->
commandProcessFinished(
command, BuildSystemFrontendDelegate::ProcessHandle { handle.id },
result, exitStatus);
}
};
struct BuildSystemFrontendDelegateImpl {
/// The status of delegate.
enum class Status {
Uninitialized = 0,
Initialized,
InitializationFailure,
Cancelled,
};
llvm::SourceMgr& sourceMgr;
const BuildSystemInvocation& invocation;
StringRef bufferBeingParsed;
std::atomic<unsigned> numErrors{0};
std::atomic<unsigned> numFailedCommands{0};
BuildSystemFrontendExecutionQueueDelegate executionQueueDelegate;
BuildSystemFrontend* frontend = nullptr;
BuildSystem* system = nullptr;
BuildSystemFrontendDelegateImpl(llvm::SourceMgr& sourceMgr,
const BuildSystemInvocation& invocation)
: sourceMgr(sourceMgr), invocation(invocation),
executionQueueDelegate(*this) {}
private:
/// The status of the delegate.
std::atomic<Status> status{Status::Uninitialized};
public:
/// Set the status of delegate to the given value.
///
/// It is not possible to update the status once status is set to initialization failure.
void setStatus(Status newStatus) {
// Disallow changing status if there was an initialization failure.
if (status == Status::InitializationFailure) {
return;
}
status = newStatus;
}
/// Returns the current status.
Status getStatus() {
return status;
}
};
bool BuildSystemFrontendExecutionQueueDelegate::showVerboseOutput() const {
return delegateImpl.invocation.showVerboseStatus;
}
BuildSystem& BuildSystemFrontendExecutionQueueDelegate::getSystem() const {
assert(delegateImpl.system);
return *delegateImpl.system;
}
}
BuildSystemFrontendDelegate::
BuildSystemFrontendDelegate(llvm::SourceMgr& sourceMgr,
const BuildSystemInvocation& invocation,
StringRef name,
uint32_t version)
: BuildSystemDelegate(name, version),
impl(new BuildSystemFrontendDelegateImpl(sourceMgr, invocation))
{
}
BuildSystemFrontendDelegate::~BuildSystemFrontendDelegate() {
delete static_cast<BuildSystemFrontendDelegateImpl*>(this->impl);
}
void
BuildSystemFrontendDelegate::setFileContentsBeingParsed(StringRef buffer) {
auto impl = static_cast<BuildSystemFrontendDelegateImpl*>(this->impl);
impl->bufferBeingParsed = buffer;
}
BuildSystemFrontend& BuildSystemFrontendDelegate::getFrontend() {
auto impl = static_cast<BuildSystemFrontendDelegateImpl*>(this->impl);
return *impl->frontend;
}
llvm::SourceMgr& BuildSystemFrontendDelegate::getSourceMgr() {
auto impl = static_cast<BuildSystemFrontendDelegateImpl*>(this->impl);
return impl->sourceMgr;
}
unsigned BuildSystemFrontendDelegate::getNumErrors() {
auto impl = static_cast<BuildSystemFrontendDelegateImpl*>(this->impl);
return impl->numErrors;
}
unsigned BuildSystemFrontendDelegate::getNumFailedCommands() {
auto impl = static_cast<BuildSystemFrontendDelegateImpl*>(this->impl);
return impl->numFailedCommands;
}
void
BuildSystemFrontendDelegate::error(const Twine& message) {
error("", {}, message.str());
}
void
BuildSystemFrontendDelegate::error(StringRef filename,
const Token& at,
const Twine& message) {
auto impl = static_cast<BuildSystemFrontendDelegateImpl*>(this->impl);
++impl->numErrors;
// If we have a file and token, resolve the location and range to one
// accessible by the source manager.
//
// FIXME: We shouldn't need to do this, but should switch llbuild to using
// SourceMgr natively.
llvm::SMLoc loc{};
llvm::SMRange range{};
if (!filename.empty() && at.start) {
// FIXME: We ignore errors here, for now, this will be resolved when we move
// to SourceMgr completely.
auto buffer = getFileSystem().getFileContents(filename);
if (buffer) {
unsigned offset = at.start - impl->bufferBeingParsed.data();
if (offset + at.length < buffer->getBufferSize()) {
range.Start = loc = llvm::SMLoc::getFromPointer(
buffer->getBufferStart() + offset);
range.End = llvm::SMLoc::getFromPointer(
buffer->getBufferStart() + (offset + at.length));
getSourceMgr().AddNewSourceBuffer(std::move(buffer), llvm::SMLoc{});
}
}
}
if (range.Start.isValid()) {
getSourceMgr().PrintMessage(loc, llvm::SourceMgr::DK_Error, message, range);
} else {
getSourceMgr().PrintMessage(loc, llvm::SourceMgr::DK_Error, message);
}
fflush(stderr);
}
std::unique_ptr<BuildExecutionQueue>
BuildSystemFrontendDelegate::createExecutionQueue() {
auto impl = static_cast<BuildSystemFrontendDelegateImpl*>(this->impl);
if (impl->invocation.useSerialBuild) {
return std::unique_ptr<BuildExecutionQueue>(
createLaneBasedExecutionQueue(impl->executionQueueDelegate, 1,
impl->invocation.environment));
}
// Get the number of CPUs to use.
unsigned numCPUs = std::thread::hardware_concurrency();
unsigned numLanes;
if (numCPUs == 0) {
error("<unknown>", {}, "unable to detect number of CPUs");
numLanes = 1;
} else {
numLanes = numCPUs;
}
return std::unique_ptr<BuildExecutionQueue>(
createLaneBasedExecutionQueue(impl->executionQueueDelegate, numLanes,
impl->invocation.environment));
}
void BuildSystemFrontendDelegate::cancel() {
auto delegateImpl = static_cast<BuildSystemFrontendDelegateImpl*>(impl);
assert(delegateImpl->getStatus() != BuildSystemFrontendDelegateImpl::Status::Uninitialized);
// Update the status to cancelled.
delegateImpl->setStatus(BuildSystemFrontendDelegateImpl::Status::Cancelled);
auto system = delegateImpl->system;
if (system) {
system->cancel();
}
}
void BuildSystemFrontendDelegate::resetForBuild() {
auto impl = static_cast<BuildSystemFrontendDelegateImpl*>(this->impl);
impl->numFailedCommands = 0;
impl->numErrors = 0;
// Update status back to initialized on reset.
if (impl->getStatus() == BuildSystemFrontendDelegateImpl::Status::Cancelled) {
impl->setStatus(BuildSystemFrontendDelegateImpl::Status::Initialized);
}
// Reset the build system.
auto system = impl->system;
if (system) {
system->resetForBuild();
}
}
void BuildSystemFrontendDelegate::hadCommandFailure() {
auto impl = static_cast<BuildSystemFrontendDelegateImpl*>(this->impl);
// Increment the failed command count.
++impl->numFailedCommands;
}
void BuildSystemFrontendDelegate::commandStatusChanged(Command*,
CommandStatusKind) {
}
void BuildSystemFrontendDelegate::commandPreparing(Command*) {
}
bool BuildSystemFrontendDelegate::shouldCommandStart(Command*) {
return true;
}
void BuildSystemFrontendDelegate::commandStarted(Command* command) {
// Don't report status if opted out by the command.
if (!command->shouldShowStatus()) {
return;
}
// Log the command.
//
// FIXME: Design the logging and status output APIs.
SmallString<64> description;
if (getFrontend().getInvocation().showVerboseStatus) {
command->getVerboseDescription(description);
} else {
command->getShortDescription(description);
// If the short description is empty, always show the verbose one.
if (description.empty()) {
command->getVerboseDescription(description);
}
}
fprintf(stdout, "%s\n", description.c_str());
fflush(stdout);
}
void BuildSystemFrontendDelegate::commandFinished(Command*) {
}
void BuildSystemFrontendDelegate::commandJobStarted(Command*) {
}
void BuildSystemFrontendDelegate::commandJobFinished(Command*) {
}
void BuildSystemFrontendDelegate::commandProcessStarted(Command*,
ProcessHandle) {
}
void BuildSystemFrontendDelegate::
commandProcessHadError(Command* command, ProcessHandle handle,
const Twine& message) {
SmallString<256> buffer;
auto str = message.toStringRef(buffer);
// FIXME: Design the logging and status output APIs.
fwrite(str.data(), str.size(), 1, stderr);
fputc('\n', stderr);
fflush(stderr);
}
void BuildSystemFrontendDelegate::
commandProcessHadOutput(Command* command, ProcessHandle handle,
StringRef data) {
// FIXME: Design the logging and status output APIs.
fwrite(data.data(), data.size(), 1, stdout);
fflush(stdout);
}
void BuildSystemFrontendDelegate::
commandProcessFinished(Command*, ProcessHandle handle,
CommandResult result,
int exitStatus) {
}
#pragma mark - BuildSystemFrontend implementation
BuildSystemFrontend::
BuildSystemFrontend(BuildSystemFrontendDelegate& delegate,
const BuildSystemInvocation& invocation)
: delegate(delegate), invocation(invocation)
{
auto delegateImpl =
static_cast<BuildSystemFrontendDelegateImpl*>(delegate.impl);
delegateImpl->frontend = this;
}
bool BuildSystemFrontend::initialize() {
if (!invocation.chdirPath.empty()) {
if (!sys::chdir(invocation.chdirPath.c_str())) {
getDelegate().error(Twine("unable to honor --chdir: ") + strerror(errno));
return false;
}
}
// Create the build system.
buildSystem.emplace(delegate);
// Load the build file.
if (!buildSystem->loadDescription(invocation.buildFilePath))
return false;
// Register the system back pointer.
//
// FIXME: Eliminate this.
auto delegateImpl =
static_cast<BuildSystemFrontendDelegateImpl*>(delegate.impl);
delegateImpl->system = buildSystem.getPointer();
// Enable tracing, if requested.
if (!invocation.traceFilePath.empty()) {
std::string error;
if (!buildSystem->enableTracing(invocation.traceFilePath, &error)) {
getDelegate().error(Twine("unable to enable tracing: ") + error);
return false;
}
}
// Attach the database.
if (!invocation.dbPath.empty()) {
// If the database path is relative, always make it relative to the input
// file.
SmallString<256> tmp;
StringRef dbPath = invocation.dbPath;
if (llvm::sys::path::is_relative(invocation.dbPath) &&
dbPath.find("://") == StringRef::npos && !dbPath.startswith(":")) {
llvm::sys::path::append(
tmp, llvm::sys::path::parent_path(invocation.buildFilePath),
invocation.dbPath);
dbPath = tmp.str();
}
std::string error;
if (!buildSystem->attachDB(dbPath, &error)) {
getDelegate().error(Twine("unable to attach DB: ") + error);
return false;
}
}
return true;
}
bool BuildSystemFrontend::build(StringRef targetToBuild) {
auto delegateImpl =
static_cast<BuildSystemFrontendDelegateImpl*>(delegate.impl);
// We expect build to be called in these states only.
assert(delegateImpl->getStatus() == BuildSystemFrontendDelegateImpl::Status::Uninitialized
|| delegateImpl->getStatus() == BuildSystemFrontendDelegateImpl::Status::Initialized);
// Set the delegate status to initialized.
delegateImpl->setStatus(BuildSystemFrontendDelegateImpl::Status::Initialized);
// Initialize the build system, if necessary
if (!buildSystem.hasValue()) {
if (!initialize()) {
// Set status to initialization failure. It is not possible to recover from this state.
delegateImpl->setStatus(BuildSystemFrontendDelegateImpl::Status::InitializationFailure);
return false;
}
}
// If delegate was told to cancel while we were initializing, abort now.
if (delegateImpl->getStatus() == BuildSystemFrontendDelegateImpl::Status::Cancelled) {
return false;
}
// Build the target; if something unspecified failed about the build, return
// an error.
if (!buildSystem->build(targetToBuild))
return false;
// The build was successful if there were no failed commands or unspecified
// errors.
//
// It is the job of the client to report a summary, if desired.
return delegate.getNumFailedCommands() == 0 && delegate.getNumErrors() == 0;
}