<rdar://problem/30961839> Implement support for stale file removal
- Implement support for recursive removal of directories
- Implement support for specifying root paths outside of which files
will not be removed
- Add test coverage for new features and a test for verifying that
symlink targets will not be removed
diff --git a/docs/buildsystem.rst b/docs/buildsystem.rst
index f5447fb..d2b3e82 100644
--- a/docs/buildsystem.rst
+++ b/docs/buildsystem.rst
@@ -711,3 +711,8 @@
* - expectedOutputs
- A string list of paths that are expected to be produced by the given
manifest.
+
+ * - roots
+ - A string lists of paths that are the only allowed root paths for files
+ to be deleted. Files outside of those paths will not be removed by
+ stale file removal.
diff --git a/include/llbuild/Basic/FileSystem.h b/include/llbuild/Basic/FileSystem.h
index 055de7d..7e61f71 100644
--- a/include/llbuild/Basic/FileSystem.h
+++ b/include/llbuild/Basic/FileSystem.h
@@ -62,7 +62,7 @@
/// Remove the file or directory at the given path.
///
- /// Directory removal is non-recursive, so only empty directories will be removed.
+ /// Directory removal is recursive.
///
/// \returns True if the item was removed, false otherwise.
virtual bool remove(const std::string& path) = 0;
diff --git a/include/llbuild/Basic/PlatformUtility.h b/include/llbuild/Basic/PlatformUtility.h
index 1fd4851..2415007 100644
--- a/include/llbuild/Basic/PlatformUtility.h
+++ b/include/llbuild/Basic/PlatformUtility.h
@@ -40,6 +40,7 @@
int read(int fileHandle, void *destinationBuffer, unsigned int maxCharCount);
int rmdir(const char *path);
int stat(const char *fileName, StatStruct *buf);
+int symlink(const char *source, const char *target);
int unlink(const char *fileName);
int write(int fileHandle, void *destinationBuffer, unsigned int maxCharCount);
}
diff --git a/lib/Basic/FileSystem.cpp b/lib/Basic/FileSystem.cpp
index 7ab3b17..42db497 100644
--- a/lib/Basic/FileSystem.cpp
+++ b/lib/Basic/FileSystem.cpp
@@ -14,12 +14,99 @@
#include "llbuild/Basic/PlatformUtility.h"
#include "llvm/ADT/STLExtras.h"
+#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/MemoryBuffer.h"
#include <cassert>
#include <cstring>
+// Cribbed from llvm, where it's been since removed.
+namespace {
+ using namespace std;
+ using namespace llvm;
+ using namespace llvm::sys::fs;
+
+ static std::error_code fillStatus(int StatRet, const struct stat &Status,
+ file_status &Result) {
+ if (StatRet != 0) {
+ std::error_code ec(errno, std::generic_category());
+ if (ec == errc::no_such_file_or_directory)
+ Result = file_status(file_type::file_not_found);
+ else
+ Result = file_status(file_type::status_error);
+ return ec;
+ }
+
+ file_type Type = file_type::type_unknown;
+
+ if (S_ISDIR(Status.st_mode))
+ Type = file_type::directory_file;
+ else if (S_ISREG(Status.st_mode))
+ Type = file_type::regular_file;
+ else if (S_ISBLK(Status.st_mode))
+ Type = file_type::block_file;
+ else if (S_ISCHR(Status.st_mode))
+ Type = file_type::character_file;
+ else if (S_ISFIFO(Status.st_mode))
+ Type = file_type::fifo_file;
+ else if (S_ISSOCK(Status.st_mode))
+ Type = file_type::socket_file;
+ else if (S_ISLNK(Status.st_mode))
+ Type = file_type::symlink_file;
+
+ perms Perms = static_cast<perms>(Status.st_mode);
+ Result =
+ file_status(Type, Perms, Status.st_dev, Status.st_ino, Status.st_mtime,
+ Status.st_uid, Status.st_gid, Status.st_size);
+
+ return std::error_code();
+ }
+
+ std::error_code link_status(const Twine &Path, file_status &Result) {
+ SmallString<128> PathStorage;
+ StringRef P = Path.toNullTerminatedStringRef(PathStorage);
+
+ struct stat Status;
+ int StatRet = ::lstat(P.begin(), &Status);
+ return fillStatus(StatRet, Status, Result);
+ }
+
+ error_code _remove_all_r(StringRef path, file_type ft, uint32_t &count) {
+ if (ft == file_type::directory_file) {
+ error_code ec;
+ directory_iterator i(path, ec);
+ if (ec)
+ return ec;
+
+ for (directory_iterator e; i != e; i.increment(ec)) {
+ if (ec)
+ return ec;
+
+ file_status st;
+
+ if (error_code ec = link_status(i->path(), st))
+ return ec;
+
+ if (error_code ec = _remove_all_r(i->path(), st.type(), count))
+ return ec;
+ }
+
+ if (error_code ec = remove(path, false))
+ return ec;
+
+ ++count; // Include the directory itself in the items removed.
+ } else {
+ if (error_code ec = remove(path, false))
+ return ec;
+
+ ++count;
+ }
+
+ return error_code();
+ }
+}
+
using namespace llbuild;
using namespace llbuild::basic;
@@ -46,7 +133,7 @@
virtual bool
createDirectory(const std::string& path) override {
- if (!sys::mkdir(path.c_str())) {
+ if (!llbuild::basic::sys::mkdir(path.c_str())) {
if (errno != EEXIST) {
return false;
}
@@ -63,25 +150,34 @@
return std::unique_ptr<llvm::MemoryBuffer>(result->release());
}
+ bool rm_tree(const char* path) {
+ uint32_t count = 0;
+ return !_remove_all_r(path, file_type::directory_file, count);
+ }
+
virtual bool remove(const std::string& path) override {
// Assume `path` is a regular file.
- if (sys::unlink(path.c_str()) == 0) {
+ if (llbuild::basic::sys::unlink(path.c_str()) == 0) {
return true;
}
- // Error can't be that `path` is actually a directory.
- if (errno != EPERM) {
+ // Error can't be that `path` is actually a directory (on Linux `EISDIR` will be returned since 2.1.132).
+ if (errno != EPERM && errno != EISDIR) {
return false;
}
// Check if `path` is a directory.
struct stat statbuf;
- if (sys::stat(path.c_str(), &statbuf) != 0) {
+ if (llbuild::basic::sys::lstat(path.c_str(), &statbuf) != 0) {
return false;
}
if (S_ISDIR(statbuf.st_mode)) {
- return sys::rmdir(path.c_str()) == 0;
+ if (llbuild::basic::sys::rmdir(path.c_str()) == 0) {
+ return true;
+ } else {
+ return rm_tree(path.c_str());
+ }
}
return false;
diff --git a/lib/Basic/PlatformUtility.cpp b/lib/Basic/PlatformUtility.cpp
index 60e4f40..fba346f 100644
--- a/lib/Basic/PlatformUtility.cpp
+++ b/lib/Basic/PlatformUtility.cpp
@@ -104,6 +104,14 @@
#endif
}
+int sys::symlink(const char *source, const char *target) {
+#if defined(_WIN32)
+ return ::_symlink(source, target);
+#else
+ return ::symlink(source, target);
+#endif
+}
+
int sys::unlink(const char *fileName) {
#if defined(_WIN32)
return ::_unlink(fileName);
diff --git a/lib/BuildSystem/BuildSystem.cpp b/lib/BuildSystem/BuildSystem.cpp
index 9b4b137..802ce18 100644
--- a/lib/BuildSystem/BuildSystem.cpp
+++ b/lib/BuildSystem/BuildSystem.cpp
@@ -2306,11 +2306,14 @@
std::vector<std::string> expectedOutputs;
std::vector<std::string> filesToDelete;
+ std::vector<std::string> roots;
bool computedFilesToDelete = false;
BuildValue priorValue;
bool hasPriorResult = false;
+ char path_separator = llvm::sys::path::get_separator()[0];
+
virtual void configureDescription(const ConfigureContext&, StringRef value) override {
description = value;
}
@@ -2353,6 +2356,12 @@
expectedOutputs.emplace_back(value.str());
}
return true;
+ } else if (name == "roots") {
+ roots.reserve(values.size());
+ for (auto value : values) {
+ roots.emplace_back(value.str());
+ }
+ return true;
}
ctx.error("unexpected attribute: '" + name + "'");
@@ -2430,6 +2439,28 @@
bsci.getDelegate().commandStarted(this);
for (auto fileToDelete : filesToDelete) {
+ // If no root paths are specified, any path is valid.
+ bool isLocatedUnderRootPath = roots.size() == 0 ? true : false;
+
+ // If root paths are defined, stale file paths should be absolute.
+ if (roots.size() > 0 && fileToDelete[0] != path_separator) {
+ bsci.getDelegate().error("", {}, (Twine("Stale file '") + fileToDelete + "' has a relative path. This is invalid in combination with the root path attribute."));
+ continue;
+ }
+
+ // Check if the file is located under one of the allowed root paths.
+ for (auto root : roots) {
+ auto res = std::mismatch(root.begin(), root.end(), fileToDelete.begin());
+ if (res.first == root.end() && ((*(res.first++) == '\0') || (*(res.first++) == path_separator))) {
+ isLocatedUnderRootPath = true;
+ }
+ }
+
+ if (!isLocatedUnderRootPath) {
+ bsci.getDelegate().error("", {}, (Twine("Stale file '") + fileToDelete + "' is located outside of the allowed root paths."));
+ continue;
+ }
+
if (!getBuildSystem(bsci.getBuildEngine()).getDelegate().getFileSystem().remove(fileToDelete)) {
bsci.getDelegate().error("", {}, (Twine("cannot remove stale file '") + fileToDelete + "': " + strerror(errno)));
}
diff --git a/llbuild.xcodeproj/project.pbxproj b/llbuild.xcodeproj/project.pbxproj
index 64c1cea..bb99339 100644
--- a/llbuild.xcodeproj/project.pbxproj
+++ b/llbuild.xcodeproj/project.pbxproj
@@ -66,6 +66,7 @@
/* Begin PBXBuildFile section */
9D0A6D811E1FFEA800BE636F /* TempDir.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9D0A6D7F1E1FFEA800BE636F /* TempDir.cpp */; };
9D2107C61DFADDFA00BE26FF /* libcurses.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = E15B6EC61B546A2C00643066 /* libcurses.dylib */; };
+ 9D5A5C311EC5FAE600DC84CC /* TempDir.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9D0A6D7F1E1FFEA800BE636F /* TempDir.cpp */; };
9DADBBAD1E256C73005B4869 /* PlatformUtility.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9DADBBAC1E256C52005B4869 /* PlatformUtility.cpp */; };
9DB047BA1DF9D4A4006CDF52 /* libgtest_main.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E1A224E619F99C580059043E /* libgtest_main.a */; };
9DB047BB1DF9D4A4006CDF52 /* libgtest.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E1A224DD19F99B0E0059043E /* libgtest.a */; };
@@ -2674,6 +2675,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ 9D5A5C311EC5FAE600DC84CC /* TempDir.cpp in Sources */,
E13812A21C53708E000092C0 /* FileSystemTest.cpp in Sources */,
E120B9EE1E4E65EB00B28469 /* ShellUtilityTest.cpp in Sources */,
E147DF1A1BA81D5A0032D08E /* SerialQueueTest.cpp in Sources */,
diff --git a/unittests/Basic/CMakeLists.txt b/unittests/Basic/CMakeLists.txt
index 6a65394..c1df379 100644
--- a/unittests/Basic/CMakeLists.txt
+++ b/unittests/Basic/CMakeLists.txt
@@ -3,6 +3,7 @@
FileSystemTest.cpp
SerialQueueTest.cpp
ShellUtilityTest.cpp
+ ../BuildSystem/TempDir.cpp
)
target_link_libraries(BasicTests llbuildBasic llvmSupport)
diff --git a/unittests/Basic/FileSystemTest.cpp b/unittests/Basic/FileSystemTest.cpp
index 66ef337..65182b6 100644
--- a/unittests/Basic/FileSystemTest.cpp
+++ b/unittests/Basic/FileSystemTest.cpp
@@ -10,11 +10,15 @@
//
//===----------------------------------------------------------------------===//
+#include "../BuildSystem/TempDir.h"
+
#include "llbuild/Basic/FileSystem.h"
#include "llbuild/Basic/LLVM.h"
+#include "llbuild/Basic/PlatformUtility.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/MemoryBuffer.h"
+#include "llvm/Support/Path.h"
#include "llvm/Support/raw_ostream.h"
#include "gtest/gtest.h"
@@ -56,4 +60,97 @@
EXPECT_FALSE(ec);
}
+TEST(FileSystemTest, testRecursiveRemoval) {
+ TmpDir rootTempDir{ __FUNCTION__ };
+
+ SmallString<256> tempDir { rootTempDir.str() };
+ llvm::sys::path::append(tempDir, "root");
+ sys::mkdir(tempDir.c_str());
+
+ SmallString<256> file{ tempDir.str() };
+ llvm::sys::path::append(file, "test.txt");
+ {
+ std::error_code ec;
+ llvm::raw_fd_ostream os(file.str(), ec, llvm::sys::fs::F_Text);
+ EXPECT_FALSE(ec);
+ os << "Hello, world!\n";
+ os.close();
+ }
+
+ SmallString<256> dir{ tempDir.str() };
+ llvm::sys::path::append(dir, "subdir");
+ sys::mkdir(dir.c_str());
+
+ llvm::sys::path::append(dir, "file_in_subdir.txt");
+ {
+ std::error_code ec;
+ llvm::raw_fd_ostream os(dir.str(), ec, llvm::sys::fs::F_Text);
+ EXPECT_FALSE(ec);
+ os << "Hello, world!\n";
+ os.close();
+ }
+
+ auto fs = createLocalFileSystem();
+ bool result = fs->remove(tempDir.c_str());
+ EXPECT_TRUE(result);
+
+ struct stat statbuf;
+ EXPECT_EQ(-1, sys::stat(tempDir.c_str(), &statbuf));
+ EXPECT_EQ(ENOENT, errno);
+}
+
+TEST(FileSystemTest, testRecursiveRemovalDoesNotFollowSymlinks) {
+ TmpDir rootTempDir{ __FUNCTION__ };
+
+ SmallString<256> file{ rootTempDir.str() };
+ llvm::sys::path::append(file, "test.txt");
+ {
+ std::error_code ec;
+ llvm::raw_fd_ostream os(file.str(), ec, llvm::sys::fs::F_Text);
+ EXPECT_FALSE(ec);
+ os << "Hello, world!\n";
+ os.close();
+ }
+
+ SmallString<256> otherDir{ rootTempDir.str() };
+ llvm::sys::path::append(otherDir, "other_dir");
+ sys::mkdir(otherDir.c_str());
+
+ SmallString<256> otherFile{ otherDir.str() };
+ llvm::sys::path::append(otherFile, "test.txt");
+ {
+ std::error_code ec;
+ llvm::raw_fd_ostream os(otherFile.str(), ec, llvm::sys::fs::F_Text);
+ EXPECT_FALSE(ec);
+ os << "Hello, world!\n";
+ os.close();
+ }
+
+ SmallString<256> tempDir { rootTempDir.str() };
+ llvm::sys::path::append(tempDir, "root");
+ sys::mkdir(tempDir.c_str());
+
+ SmallString<256> linkPath { tempDir.str() };
+ llvm::sys::path::append(linkPath, "link.txt");
+ int res = sys::symlink(file.c_str(), linkPath.c_str());
+ EXPECT_EQ(res, 0);
+
+ SmallString<256> directoryLinkPath { tempDir.str() };
+ llvm::sys::path::append(directoryLinkPath, "link_to_other_dir");
+ res = sys::symlink(otherDir.c_str(), directoryLinkPath.c_str());
+ EXPECT_EQ(res, 0);
+
+ auto fs = createLocalFileSystem();
+ bool result = fs->remove(tempDir.c_str());
+ EXPECT_TRUE(result);
+
+ struct stat statbuf;
+ EXPECT_EQ(-1, sys::stat(tempDir.c_str(), &statbuf));
+ EXPECT_EQ(ENOENT, errno);
+ // Verify that the symlink target still exists.
+ EXPECT_EQ(0, sys::stat(file.c_str(), &statbuf));
+ // Verify that we did not delete the symlinked directories contents.
+ EXPECT_EQ(0, sys::stat(otherFile.c_str(), &statbuf));
+}
+
}
diff --git a/unittests/BuildSystem/BuildSystemTaskTests.cpp b/unittests/BuildSystem/BuildSystemTaskTests.cpp
index ffee42c..f8fd3b6 100644
--- a/unittests/BuildSystem/BuildSystemTaskTests.cpp
+++ b/unittests/BuildSystem/BuildSystemTaskTests.cpp
@@ -410,4 +410,264 @@
}), delegate.getMessages());
}
+TEST(BuildSystemTaskTests, staleFileRemovalWithRoots) {
+ TmpDir tempDir{ __FUNCTION__ };
+
+ SmallString<256> manifest{ tempDir.str() };
+ sys::path::append(manifest, "manifest.llbuild");
+ {
+ std::error_code ec;
+ llvm::raw_fd_ostream os(manifest, ec, llvm::sys::fs::F_Text);
+ assert(!ec);
+
+ os << R"END(
+client:
+ name: mock
+
+commands:
+ C.1:
+ tool: stale-file-removal
+ description: STALE-FILE-REMOVAL
+ expectedOutputs: ["/bar/a.out"]
+)END";
+ }
+
+ auto keyToBuild = BuildKey::makeCommand("C.1");
+
+ SmallString<256> builddb{ tempDir.str() };
+ sys::path::append(builddb, "build.db");
+
+ {
+ MockBuildSystemDelegate delegate(/*trackAllMessages=*/true);
+ BuildSystem system(delegate);
+ system.attachDB(builddb.c_str(), nullptr);
+
+ bool loadingResult = system.loadDescription(manifest);
+ ASSERT_TRUE(loadingResult);
+
+ auto result = system.build(keyToBuild);
+
+ ASSERT_TRUE(result.getValue().isStaleFileRemoval());
+ ASSERT_EQ(result.getValue().getStaleFileList().size(), 1UL);
+ ASSERT_TRUE(strcmp(result.getValue().getStaleFileList()[0].str().c_str(), "/bar/a.out") == 0);
+
+ ASSERT_EQ(std::vector<std::string>({
+ "commandPreparing(C.1)",
+ "commandStarted(C.1)",
+ "commandFinished(C.1)",
+ }), delegate.getMessages());
+ }
+
+ {
+ std::error_code ec;
+ llvm::raw_fd_ostream os(manifest, ec, llvm::sys::fs::F_Text);
+ assert(!ec);
+
+ os << R"END(
+client:
+ name: mock
+
+commands:
+ C.1:
+ tool: stale-file-removal
+ description: STALE-FILE-REMOVAL
+ expectedOutputs: ["/bar/b.out", "/foo", "/foobar.txt"]
+ roots: ["/foo"]
+)END";
+ }
+
+ MockBuildSystemDelegate delegate(/*trackAllMessages=*/true);
+ BuildSystem system(delegate);
+ system.attachDB(builddb.c_str(), nullptr);
+ bool loadingResult = system.loadDescription(manifest);
+ ASSERT_TRUE(loadingResult);
+ auto result = system.build(keyToBuild);
+
+ ASSERT_TRUE(result.getValue().isStaleFileRemoval());
+ ASSERT_EQ(result.getValue().getStaleFileList().size(), 3UL);
+ ASSERT_TRUE(strcmp(result.getValue().getStaleFileList()[0].str().c_str(), "/bar/b.out") == 0);
+ ASSERT_TRUE(strcmp(result.getValue().getStaleFileList()[1].str().c_str(), "/foo") == 0);
+ ASSERT_TRUE(strcmp(result.getValue().getStaleFileList()[2].str().c_str(), "/foobar.txt") == 0);
+
+ ASSERT_EQ(std::vector<std::string>({
+ "commandPreparing(C.1)",
+ "commandStarted(C.1)",
+ "Stale file '/bar/a.out' is located outside of the allowed root paths.",
+ // FIXME: Enable once stale file removal issues are no longer errros.
+ //"Stale file '/foobar.txt' is located outside of the allowed root paths.",
+ "commandFinished(C.1)",
+ }), delegate.getMessages());
+}
+
+TEST(BuildSystemTaskTests, staleFileRemovalWithRootsEnforcesAbsolutePaths) {
+ TmpDir tempDir{ __FUNCTION__ };
+
+ SmallString<256> manifest{ tempDir.str() };
+ sys::path::append(manifest, "manifest.llbuild");
+ {
+ std::error_code ec;
+ llvm::raw_fd_ostream os(manifest, ec, llvm::sys::fs::F_Text);
+ assert(!ec);
+
+ os << R"END(
+client:
+ name: mock
+
+commands:
+ C.1:
+ tool: stale-file-removal
+ description: STALE-FILE-REMOVAL
+ expectedOutputs: ["a.out"]
+)END";
+ }
+
+ auto keyToBuild = BuildKey::makeCommand("C.1");
+
+ SmallString<256> builddb{ tempDir.str() };
+ sys::path::append(builddb, "build.db");
+
+ {
+ MockBuildSystemDelegate delegate(/*trackAllMessages=*/true);
+ BuildSystem system(delegate);
+ system.attachDB(builddb.c_str(), nullptr);
+
+ bool loadingResult = system.loadDescription(manifest);
+ ASSERT_TRUE(loadingResult);
+
+ auto result = system.build(keyToBuild);
+
+ ASSERT_TRUE(result.getValue().isStaleFileRemoval());
+ ASSERT_EQ(result.getValue().getStaleFileList().size(), 1UL);
+ ASSERT_TRUE(strcmp(result.getValue().getStaleFileList()[0].str().c_str(), "a.out") == 0);
+
+ ASSERT_EQ(std::vector<std::string>({
+ "commandPreparing(C.1)",
+ "commandStarted(C.1)",
+ "commandFinished(C.1)",
+ }), delegate.getMessages());
+ }
+
+ {
+ std::error_code ec;
+ llvm::raw_fd_ostream os(manifest, ec, llvm::sys::fs::F_Text);
+ assert(!ec);
+
+ os << R"END(
+client:
+ name: mock
+
+commands:
+ C.1:
+ tool: stale-file-removal
+ description: STALE-FILE-REMOVAL
+ expectedOutputs: ["/bar/b.out"]
+ roots: ["/foo"]
+)END";
+ }
+
+ MockBuildSystemDelegate delegate(/*trackAllMessages=*/true);
+ BuildSystem system(delegate);
+ system.attachDB(builddb.c_str(), nullptr);
+ bool loadingResult = system.loadDescription(manifest);
+ ASSERT_TRUE(loadingResult);
+ auto result = system.build(keyToBuild);
+
+ ASSERT_TRUE(result.getValue().isStaleFileRemoval());
+ ASSERT_EQ(result.getValue().getStaleFileList().size(), 1UL);
+ ASSERT_TRUE(strcmp(result.getValue().getStaleFileList()[0].str().c_str(), "/bar/b.out") == 0);
+
+ ASSERT_EQ(std::vector<std::string>({
+ "commandPreparing(C.1)",
+ "commandStarted(C.1)",
+ "Stale file 'a.out' has a relative path. This is invalid in combination with the root path attribute.",
+ "commandFinished(C.1)",
+ }), delegate.getMessages());
+}
+
+TEST(BuildSystemTaskTests, staleFileRemovalWithRootsIsAgnosticToTrailingPathSeparator) {
+ TmpDir tempDir{ __FUNCTION__ };
+
+ SmallString<256> manifest{ tempDir.str() };
+ sys::path::append(manifest, "manifest.llbuild");
+ {
+ std::error_code ec;
+ llvm::raw_fd_ostream os(manifest, ec, llvm::sys::fs::F_Text);
+ assert(!ec);
+
+ os << R"END(
+client:
+ name: mock
+
+commands:
+ C.1:
+ tool: stale-file-removal
+ description: STALE-FILE-REMOVAL
+ expectedOutputs: ["a.out", "/foo/"]
+)END";
+ }
+
+ auto keyToBuild = BuildKey::makeCommand("C.1");
+
+ SmallString<256> builddb{ tempDir.str() };
+ sys::path::append(builddb, "build.db");
+
+ {
+ MockBuildSystemDelegate delegate(/*trackAllMessages=*/true);
+ BuildSystem system(delegate);
+ system.attachDB(builddb.c_str(), nullptr);
+
+ bool loadingResult = system.loadDescription(manifest);
+ ASSERT_TRUE(loadingResult);
+
+ auto result = system.build(keyToBuild);
+
+ ASSERT_TRUE(result.getValue().isStaleFileRemoval());
+ ASSERT_EQ(result.getValue().getStaleFileList().size(), 2UL);
+ ASSERT_TRUE(strcmp(result.getValue().getStaleFileList()[0].str().c_str(), "a.out") == 0);
+
+ ASSERT_EQ(std::vector<std::string>({
+ "commandPreparing(C.1)",
+ "commandStarted(C.1)",
+ "commandFinished(C.1)",
+ }), delegate.getMessages());
+ }
+
+ {
+ std::error_code ec;
+ llvm::raw_fd_ostream os(manifest, ec, llvm::sys::fs::F_Text);
+ assert(!ec);
+
+ os << R"END(
+client:
+ name: mock
+
+commands:
+ C.1:
+ tool: stale-file-removal
+ description: STALE-FILE-REMOVAL
+ expectedOutputs: ["/bar/b.out"]
+ roots: ["/foo/"]
+)END";
+ }
+
+ MockBuildSystemDelegate delegate(/*trackAllMessages=*/true);
+ BuildSystem system(delegate);
+ system.attachDB(builddb.c_str(), nullptr);
+ bool loadingResult = system.loadDescription(manifest);
+ ASSERT_TRUE(loadingResult);
+ auto result = system.build(keyToBuild);
+
+ ASSERT_TRUE(result.getValue().isStaleFileRemoval());
+ ASSERT_EQ(result.getValue().getStaleFileList().size(), 1UL);
+ ASSERT_TRUE(strcmp(result.getValue().getStaleFileList()[0].str().c_str(), "/bar/b.out") == 0);
+
+ ASSERT_EQ(std::vector<std::string>({
+ "commandPreparing(C.1)",
+ "commandStarted(C.1)",
+ "cannot remove stale file '/foo/': No such file or directory",
+ "Stale file 'a.out' has a relative path. This is invalid in combination with the root path attribute.",
+ "commandFinished(C.1)",
+ }), delegate.getMessages());
+}
+
}
diff --git a/unittests/BuildSystem/TempDir.cpp b/unittests/BuildSystem/TempDir.cpp
index 10fa8da..ed01c56 100644
--- a/unittests/BuildSystem/TempDir.cpp
+++ b/unittests/BuildSystem/TempDir.cpp
@@ -12,70 +12,12 @@
#include "TempDir.h"
+#include "llbuild/Basic/FileSystem.h"
+
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/SourceMgr.h"
-// TODO Move this into some kind of libtestSupport?
-// Cribbed from llvm, where it's been since removed.
-namespace {
-
- using namespace std;
- using namespace llvm;
- using namespace llvm::sys::fs;
-
- error_code _remove_all_r(StringRef path, file_type ft, uint32_t &count) {
- if (ft == file_type::directory_file) {
- error_code ec;
- directory_iterator i(path, ec);
- if (ec)
- return ec;
-
- for (directory_iterator e; i != e; i.increment(ec)) {
- if (ec)
- return ec;
-
- file_status st;
-
- if (error_code ec = i->status(st))
- return ec;
-
- if (error_code ec = _remove_all_r(i->path(), st.type(), count))
- return ec;
- }
-
- if (error_code ec = remove(path, false))
- return ec;
-
- ++count; // Include the directory itself in the items removed.
- } else {
- if (error_code ec = remove(path, false))
- return ec;
-
- ++count;
- }
-
- return error_code();
- }
-
- error_code remove_all(const Twine &path, uint32_t &num_removed) {
- SmallString<128> path_storage;
- StringRef p = path.toStringRef(path_storage);
-
- file_status fs;
- if (error_code ec = status(path, fs))
- return ec;
- num_removed = 0;
- return _remove_all_r(p, fs.type(), num_removed);
- }
-
- error_code remove_all(const Twine &path) {
- uint32_t num_removed = 0;
- return remove_all(path, num_removed);
- }
-
-}
-
llbuild::TmpDir::TmpDir(llvm::StringRef namePrefix) {
llvm::SmallString<256> tempDirPrefix;
llvm::sys::path::system_temp_directory(true, tempDirPrefix);
@@ -88,9 +30,10 @@
}
llbuild::TmpDir::~TmpDir() {
- std::error_code ec = remove_all(llvm::Twine(tempDir));
- assert(!ec);
- (void)ec;
+ auto fs = basic::createLocalFileSystem();
+ bool result = fs->remove(tempDir.c_str());
+ assert(result);
+ (void)result;
}
const char *llbuild::TmpDir::c_str() { return tempDir.c_str(); }