blob: b305233f3527438e52c237e2a870677912b6e1c0 [file] [log] [blame]
//===--- MakeStyleDependencies.cpp -- Emit make-style dependencies --------===//
//
// 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 https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
#include "Dependencies.h"
#include "swift/AST/DiagnosticEngine.h"
#include "swift/AST/DiagnosticsFrontend.h"
#include "swift/AST/ModuleLoader.h"
#include "swift/Frontend/FrontendOptions.h"
#include "swift/Frontend/InputFile.h"
#include "swift/FrontendTool/FrontendTool.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/StringRef.h"
using namespace swift;
StringRef
swift::frontend::utils::escapeForMake(StringRef raw,
llvm::SmallVectorImpl<char> &buffer) {
buffer.clear();
// The escaping rules for GNU make are complicated due to the various
// subsitutions and use of the tab in the leading position for recipes.
// Various symbols have significance in different contexts. It is not
// possible to correctly quote all characters in Make (as of 3.7). Match
// gcc and clang's behaviour for the escaping which covers only a subset of
// characters.
for (unsigned I = 0, E = raw.size(); I != E; ++I) {
switch (raw[I]) {
case '#': // Handle '#' the broken GCC way
buffer.push_back('\\');
break;
case ' ':
for (unsigned J = I; J && raw[J - 1] == '\\'; --J)
buffer.push_back('\\');
buffer.push_back('\\');
break;
case '$': // $ is escaped by $
buffer.push_back('$');
break;
}
buffer.push_back(raw[I]);
}
buffer.push_back('\0');
return buffer.data();
}
/// This sorting function is used to stabilize the order in which dependencies
/// are emitted into \c .d files that are consumed by external build systems.
/// This serves to eliminate order as a source of non-determinism in these
/// outputs.
///
/// The exact sorting predicate is not important. Currently, it is a
/// lexicographic comparison that reverses the provided strings before applying
/// the sorting predicate. This has the benefit of being somewhat
/// invariant with respect to the installation location of various system
/// components. e.g. on two systems, the same file identified by two different
/// paths differing only in their relative install location such as
///
/// /Applications/MyXcode.app/Path/To/A/Framework/In/The/SDK/Header.h
/// /Applications/Xcodes/AnotherXcode.app/Path/To/A/Framework/In/The/SDK/Header.h
///
/// should appear in roughly the same order relative to other paths. Ultimately,
/// this makes it easier to test the contents of the emitted files with tools
/// like FileCheck.
static std::vector<std::string>
reversePathSortedFilenames(const ArrayRef<std::string> elts) {
std::vector<std::string> tmp(elts.begin(), elts.end());
std::sort(tmp.begin(), tmp.end(),
[](const std::string &a, const std::string &b) -> bool {
return std::lexicographical_compare(a.rbegin(), a.rend(),
b.rbegin(), b.rend());
});
return tmp;
}
/// Emits a Make-style dependencies file.
bool swift::emitMakeDependenciesIfNeeded(DiagnosticEngine &diags,
DependencyTracker *depTracker,
const FrontendOptions &opts,
const InputFile &input) {
auto dependenciesFilePath = input.getDependenciesFilePath();
if (dependenciesFilePath.empty())
return false;
std::error_code EC;
llvm::raw_fd_ostream out(dependenciesFilePath, EC, llvm::sys::fs::F_None);
if (out.has_error() || EC) {
diags.diagnose(SourceLoc(), diag::error_opening_output,
dependenciesFilePath, EC.message());
out.clear_error();
return true;
}
llvm::SmallString<256> buffer;
// collect everything in memory to avoid redundant work
// when there are multiple targets
std::string dependencyString;
// First include all other files in the module. Make-style dependencies
// need to be conservative!
auto inputPaths =
reversePathSortedFilenames(opts.InputsAndOutputs.getInputFilenames());
for (auto const &path : inputPaths) {
dependencyString.push_back(' ');
dependencyString.append(frontend::utils::escapeForMake(path, buffer).str());
}
// Then print dependencies we've picked up during compilation.
auto dependencyPaths =
reversePathSortedFilenames(depTracker->getDependencies());
for (auto const &path : dependencyPaths) {
dependencyString.push_back(' ');
dependencyString.append(frontend::utils::escapeForMake(path, buffer).str());
}
auto incrementalDependencyPaths =
reversePathSortedFilenames(depTracker->getIncrementalDependencies());
for (auto const &path : incrementalDependencyPaths) {
dependencyString.push_back(' ');
dependencyString.append(frontend::utils::escapeForMake(path, buffer).str());
}
// FIXME: Xcode can't currently handle multiple targets in a single
// dependency line.
opts.forAllOutputPaths(input, [&](const StringRef targetName) {
auto targetNameEscaped = frontend::utils::escapeForMake(targetName, buffer);
out << targetNameEscaped << " :" << dependencyString << '\n';
});
return false;
}