| //===-- 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/FileSystem.h" |
| #include "llbuild/Basic/LLVM.h" |
| #include "llbuild/BuildSystem/BuildExecutionQueue.h" |
| #include "llbuild/BuildSystem/BuildFile.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; |
| |
| class BuildSystemFrontendExecutionQueueDelegate |
| : public BuildExecutionQueueDelegate { |
| BuildSystemFrontendDelegateImpl &delegateImpl; |
| |
| bool showVerboseOutput() const; |
| |
| BuildSystem& getSystem() const; |
| |
| public: |
| BuildSystemFrontendExecutionQueueDelegate( |
| BuildSystemFrontendDelegateImpl& delegateImpl) |
| : delegateImpl(delegateImpl) { } |
| |
| virtual void commandStarted(Command* command) override { |
| static_cast<BuildSystemFrontendDelegate*>(&getSystem().getDelegate())-> |
| commandStarted(command); |
| } |
| |
| virtual void commandFinished(Command* command) override { |
| static_cast<BuildSystemFrontendDelegate*>(&getSystem().getDelegate())-> |
| commandFinished(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, |
| int exitStatus) override { |
| static_cast<BuildSystemFrontendDelegate*>(&getSystem().getDelegate())-> |
| commandProcessFinished( |
| command, BuildSystemFrontendDelegate::ProcessHandle { handle.id }, |
| exitStatus); |
| } |
| }; |
| |
| struct BuildSystemFrontendDelegateImpl { |
| 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) {} |
| }; |
| |
| 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)); |
| } |
| |
| // 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(impl->executionQueueDelegate, 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; |
| } |
| |
| void BuildSystemFrontendDelegate::commandStarted(Command*) { |
| } |
| |
| void BuildSystemFrontendDelegate::commandFinished(Command*) { |
| } |
| |
| void BuildSystemFrontendDelegate::commandProcessStarted(Command* command, |
| ProcessHandle handle) { |
| // 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:: |
| 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, |
| 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::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); |
| |
| // Register the system back pointer. |
| // |
| // FIXME: Eliminate this. |
| auto delegateImpl = |
| static_cast<BuildSystemFrontendDelegateImpl*>(delegate.impl); |
| delegateImpl->system = &system; |
| |
| // 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; |
| } |