blob: aebf39f5f2498653fab63e14eb472ad4a5851c26 [file] [log] [blame]
//===-- BuildSystemFrontend.cpp -------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2015 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/LLVM.h"
#include "llbuild/BuildSystem/BuildExecutionQueue.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 <unistd.h>
using namespace llbuild;
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" },
{ "-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 == "--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;
}
}
}
#pragma mark - BuildSystemFrontendDelegate implementation
namespace {
struct BuildSystemFrontendDelegateImpl {
llvm::SourceMgr& sourceMgr;
const BuildSystemInvocation& invocation;
StringRef bufferBeingParsed;
std::atomic<unsigned> numErrors{0};
std::atomic<unsigned> numFailedCommands{0};
BuildSystemFrontendDelegateImpl(llvm::SourceMgr& sourceMgr,
const BuildSystemInvocation& invocation)
: sourceMgr(sourceMgr), invocation(invocation) {}
};
}
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;
}
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 = llvm::MemoryBuffer::getFile(filename);
if (!buffer.getError()) {
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);
}
}
std::unique_ptr<BuildExecutionQueue>
BuildSystemFrontendDelegate::createExecutionQueue() {
auto impl = static_cast<BuildSystemFrontendDelegateImpl*>(this->impl);
if (impl->invocation.useSerialBuild) {
return std::unique_ptr<BuildExecutionQueue>(
createLaneBasedExecutionQueue(1));
}
// Get the number of CPUs to use.
long numCPUs = sysconf(_SC_NPROCESSORS_ONLN);
unsigned numLanes;
if (numCPUs < 0) {
error("<unknown>", {}, "unable to detect number of CPUs");
numLanes = 1;
} else {
numLanes = numCPUs + 2;
}
return std::unique_ptr<BuildExecutionQueue>(
createLaneBasedExecutionQueue(numLanes));
}
bool BuildSystemFrontendDelegate::isCancelled() {
// Stop the build after any command failures.
return getNumFailedCommands() > 0;
}
void BuildSystemFrontendDelegate::hadCommandFailure() {
auto impl = static_cast<BuildSystemFrontendDelegateImpl*>(this->impl);
// Increment the failed command count.
++impl->numFailedCommands;
}
bool BuildSystemFrontendDelegate::showVerboseStatus() {
auto impl = static_cast<BuildSystemFrontendDelegateImpl*>(this->impl);
return impl->invocation.showVerboseStatus;
}
#pragma mark - BuildSystemFrontend implementation
BuildSystemFrontend::
BuildSystemFrontend(BuildSystemFrontendDelegate& delegate,
const BuildSystemInvocation& invocation)
: delegate(delegate), invocation(invocation)
{
}
bool BuildSystemFrontend::build(StringRef targetToBuild) {
// Honor the --chdir option, if used.
if (!invocation.chdirPath.empty()) {
if (::chdir(invocation.chdirPath.c_str()) < 0) {
getDelegate().error(Twine("unable to honor --chdir: ") + strerror(errno));
return false;
}
}
// Create the build system.
BuildSystem system(delegate, invocation.buildFilePath);
// Enable tracing, if requested.
if (!invocation.traceFilePath.empty()) {
std::string error;
if (!system.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::has_relative_path(invocation.dbPath)) {
llvm::sys::path::append(
tmp, llvm::sys::path::parent_path(invocation.buildFilePath),
invocation.dbPath);
dbPath = tmp.str();
}
std::string error;
if (!system.attachDB(dbPath, &error)) {
getDelegate().error(Twine("unable to attach DB: ") + error);
return false;
}
}
// If something unspecified failed about the build, return an error.
if (!system.build(targetToBuild)) {
return false;
}
// If there were failed commands, report the count and return an error.
if (delegate.getNumFailedCommands()) {
getDelegate().error("build had " + Twine(delegate.getNumFailedCommands()) +
" command failures");
return false;
}
// Otherwise, return an error only if there were unspecified errors.
return delegate.getNumErrors() == 0;
}