diff --git a/include/mrdocs/Config.hpp b/include/mrdocs/Config.hpp index 221c0b45f7..c383c3ad71 100644 --- a/include/mrdocs/Config.hpp +++ b/include/mrdocs/Config.hpp @@ -111,8 +111,9 @@ class MRDOCS_DECL /** Full path to the config file directory - The working directory is the directory - of the mrdocs.yml file. + The reference directory for most MrDocs + options is the directory of the + mrdocs.yml file. It is used to calculate full paths from relative paths. @@ -123,6 +124,29 @@ class MRDOCS_DECL std::string configDir() const; + /** Full path to the output directory + + The reference directory for MrDocs + output and temporary files is the + output directory. + + This is either the output option + (if already a directory) or + the parent directory of the output + option (if it is a file). + + When the output option is a path + that does not exist, we determine + if it's a file or directory by + checking if the filename contains + a period. + + This string will always be native style + and have a trailing directory separator. + */ + std::string + outputDir() const; + /** Full path to the mrdocs root directory This is the directory containing the diff --git a/include/mrdocs/Support/Algorithm.hpp b/include/mrdocs/Support/Algorithm.hpp index 8ea4a15beb..137547f95a 100644 --- a/include/mrdocs/Support/Algorithm.hpp +++ b/include/mrdocs/Support/Algorithm.hpp @@ -11,8 +11,9 @@ #ifndef MRDOCS_API_SUPPORT_ALGORITHM_HPP #define MRDOCS_API_SUPPORT_ALGORITHM_HPP -#include #include +#include +#include namespace clang::mrdocs { diff --git a/include/mrdocs/Support/Path.hpp b/include/mrdocs/Support/Path.hpp index 6bcbeea8d6..529a4938c2 100644 --- a/include/mrdocs/Support/Path.hpp +++ b/include/mrdocs/Support/Path.hpp @@ -307,6 +307,21 @@ bool isDirectory( std::string_view pathName); +/** Determine lexically if a path is a directory. + + This function determines if a path is a directory. + + If the path does not exist, the function + determines lexically if the path represents + a directory. In this case, the function + returns true if the last path segment + contains a period, otherwise false. +*/ +MRDOCS_DECL +bool +isLexicalDirectory( + std::string_view pathName); + /** Determine if a path is a directory. */ MRDOCS_DECL diff --git a/src/lib/Lib/Config.cpp b/src/lib/Lib/Config.cpp index 48f24ce4cb..b48f3f91a8 100644 --- a/src/lib/Lib/Config.cpp +++ b/src/lib/Lib/Config.cpp @@ -9,12 +9,13 @@ // Official repository: https://github.com/cppalliance/mrdocs // -#include "mrdocs/Config.hpp" -#include -#include -#include +#include +#include #include #include +#include +#include +#include namespace clang { namespace mrdocs { @@ -534,5 +535,36 @@ configDir() const return files::getParentDir(config); } +/** Full path to the output directory + + The reference directory for MrDocs + output and temporary files is the + output directory. + + This is either the output option + (if already a directory) or + the parent directory of the output + option (if it is a file). + + When the output option is a path + that does not exist, we determine + if it's a file or directory by + checking if the filename contains + a period. + + This string will always be native style + and have a trailing directory separator. +*/ +std::string +Config::Settings:: +outputDir() const +{ + if (files::isLexicalDirectory(output)) + { + return output; + } + return files::getParentDir(output); +} + } // mrdocs } // clang diff --git a/src/lib/Support/Path.cpp b/src/lib/Support/Path.cpp index 617bb2e35e..9ca6230c5c 100644 --- a/src/lib/Support/Path.cpp +++ b/src/lib/Support/Path.cpp @@ -10,10 +10,11 @@ // #include "Path.hpp" +#include #include #include -#include #include +#include #include namespace clang { @@ -378,11 +379,27 @@ isDirectory( { namespace fs = llvm::sys::fs; fs::file_status fileStatus; - if(auto ec = fs::status(pathName, fileStatus)) - return false; - if(fileStatus.type() != fs::file_type::directory_file) + if (auto ec = fs::status(pathName, fileStatus)) + { return false; - return true; + } + return fileStatus.type() == fs::file_type::directory_file; +} + +bool +isLexicalDirectory( + std::string_view pathName) +{ + namespace fs = llvm::sys::fs; + fs::file_status fileStatus; + if (auto const ec = fs::status(pathName, fileStatus); + ec || + fileStatus.type() == fs::file_type::file_not_found) + { + auto const filename = getFileName(pathName); + return !contains(filename, '.'); + } + return fileStatus.type() == fs::file_type::directory_file; } bool @@ -487,7 +504,7 @@ ScopedTempFile( ScopedTempDirectory:: ~ScopedTempDirectory() { - if (ok_) + if (*this) { llvm::sys::fs::remove_directories(path_); } @@ -498,11 +515,12 @@ ScopedTempDirectory( llvm::StringRef prefix) { llvm::SmallString<128> tempPath; - ok_ = !llvm::sys::fs::createUniqueDirectory(prefix, tempPath); - if (ok_) + if (llvm::sys::fs::createUniqueDirectory(prefix, tempPath)) { - path_ = tempPath; + status_ = ErrorStatus::CannotCreateDirectories; + return; } + path_ = tempPath; } ScopedTempDirectory:: @@ -513,19 +531,33 @@ ScopedTempDirectory( llvm::SmallString<128> tempPath(root); llvm::sys::path::append(tempPath, dir); bool const exists = llvm::sys::fs::exists(tempPath); - if (exists) + if (exists && + llvm::sys::fs::remove_directories(tempPath)) { - ok_ = !llvm::sys::fs::remove_directories(tempPath); - if (!ok_) - { - return; - } + status_ = ErrorStatus::CannotDeleteExisting; + return; } - ok_ = !llvm::sys::fs::create_directory(tempPath); - if (ok_) + if (llvm::sys::fs::create_directories(tempPath)) { - path_ = tempPath; + status_ = ErrorStatus::CannotCreateDirectories; + return; + } + path_ = tempPath; +} + +Error +ScopedTempDirectory:: +error() const +{ + if (status_ == ErrorStatus::CannotDeleteExisting) + { + return Error("Failed to delete existing directory"); + } + if (status_ == ErrorStatus::CannotCreateDirectories) + { + return Error("Failed to create directories"); } + return Error(); } } // mrdocs diff --git a/src/lib/Support/Path.hpp b/src/lib/Support/Path.hpp index 9187c3b0dd..9e17725ebf 100644 --- a/src/lib/Support/Path.hpp +++ b/src/lib/Support/Path.hpp @@ -85,8 +85,16 @@ class ScopedTempFile */ class ScopedTempDirectory { + // Status of the directory + enum class ErrorStatus + { + None, + CannotDeleteExisting, + CannotCreateDirectories + }; + clang::mrdocs::SmallPathString path_; - bool ok_ = false; + ErrorStatus status_ = ErrorStatus::None; public: /** Destructor @@ -129,12 +137,27 @@ class ScopedTempDirectory /** Returns `true` if the directory was created successfully. */ - operator bool() const { return ok_; } + operator bool() const + { + return status_ == ErrorStatus::None; + } + + /** Returns `true` if the directory was not created successfully. + */ + bool + failed() const + { + return status_ != ErrorStatus::None; + } /** Returns the path to the temporary directory. */ std::string_view path() const { return static_cast(path_); } + /** Returns the error status of the directory. + */ + Error error() const; + /** Convert temp directory to a std::string_view */ operator std::string_view() const { return path(); } diff --git a/src/tool/GenerateAction.cpp b/src/tool/GenerateAction.cpp index 3f3ff42a14..776329c1f0 100644 --- a/src/tool/GenerateAction.cpp +++ b/src/tool/GenerateAction.cpp @@ -137,7 +137,14 @@ DoGenerateAction( MRDOCS_CHECK( compilationDatabasePath, "The compilation database path argument is missing"); - ScopedTempDirectory tempDir(config->settings().output, ".temp"); + ScopedTempDirectory tempDir( + config->settings().outputDir(), + ".temp"); + if (tempDir.failed()) + { + report::error("Failed to create temporary directory: {}", tempDir.error()); + return Unexpected(tempDir.error()); + } std::string buildPath = files::appendPath(tempDir, "build"); Expected const compileCommandsPathExp = generateCompileCommandsFile(