From: Argyrios Kyrtzidis Date: Tue, 25 Feb 2014 03:59:23 +0000 (+0000) Subject: [libclang] Introduce libclang APIs for creating a buffer with a JSON virtual file... X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=57fc288553c0208ab59ac6c48083ee0059f94797;p=clang [libclang] Introduce libclang APIs for creating a buffer with a JSON virtual file overlay description. The current API only supports adding 'virtual file path' -> 'real file path' mappings. rdar://15986708 git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@202105 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/include/clang-c/BuildSystem.h b/include/clang-c/BuildSystem.h index f8eff4257d..ab581fdcce 100644 --- a/include/clang-c/BuildSystem.h +++ b/include/clang-c/BuildSystem.h @@ -15,6 +15,7 @@ #define CLANG_C_BUILD_SYSTEM_H #include "clang-c/Platform.h" +#include "clang-c/CXErrorCode.h" #include "clang-c/CXString.h" #ifdef __cplusplus @@ -32,6 +33,48 @@ extern "C" { */ CINDEX_LINKAGE unsigned long long clang_getBuildSessionTimestamp(void); +/** + * \brief Object encapsulating information about overlaying virtual + * file/directories over the real file system. + */ +typedef struct CXVirtualFileOverlayImpl *CXVirtualFileOverlay; + +/** + * \brief Create a \c CXVirtualFileOverlay object. + * Must be disposed with \c clang_VirtualFileOverlay_dispose(). + * + * \param options is reserved, always pass 0. + */ +CINDEX_LINKAGE CXVirtualFileOverlay +clang_VirtualFileOverlay_create(unsigned options); + +/** + * \brief Map an absolute virtual file path to an absolute real one. + * The virtual path must be canonicalized (not contain "."/".."). + * \returns 0 for success, non-zero to indicate an error. + */ +CINDEX_LINKAGE enum CXErrorCode +clang_VirtualFileOverlay_addFileMapping(CXVirtualFileOverlay, + const char *virtualPath, + const char *realPath); + +/** + * \brief Write out the \c CXVirtualFileOverlay object to a char buffer. + * + * \param options is reserved, always pass 0. + * \param out_buffer pointer to receive the CXString object, which should be + * disposed using \c clang_disposeString(). + * \returns 0 for success, non-zero to indicate an error. + */ +CINDEX_LINKAGE enum CXErrorCode +clang_VirtualFileOverlay_writeToBuffer(CXVirtualFileOverlay, unsigned options, + CXString *out_buffer); + +/** + * \brief Dispose a \c CXVirtualFileOverlay object. + */ +CINDEX_LINKAGE void clang_VirtualFileOverlay_dispose(CXVirtualFileOverlay); + /** * @} */ diff --git a/include/clang-c/CXErrorCode.h b/include/clang-c/CXErrorCode.h new file mode 100644 index 0000000000..a026c95a5b --- /dev/null +++ b/include/clang-c/CXErrorCode.h @@ -0,0 +1,64 @@ +/*===-- clang-c/CXErrorCode.h - C Index Error Codes --------------*- C -*-===*\ +|* *| +|* The LLVM Compiler Infrastructure *| +|* *| +|* This file is distributed under the University of Illinois Open Source *| +|* License. See LICENSE.TXT for details. *| +|* *| +|*===----------------------------------------------------------------------===*| +|* *| +|* This header provides the CXErrorCode enumerators. *| +|* *| +\*===----------------------------------------------------------------------===*/ + +#ifndef CLANG_C_CXERRORCODE_H +#define CLANG_C_CXERRORCODE_H + +#include "clang-c/Platform.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \brief Error codes returned by libclang routines. + * + * Zero (\c CXError_Success) is the only error code indicating success. Other + * error codes, including not yet assigned non-zero values, indicate errors. + */ +enum CXErrorCode { + /** + * \brief No error. + */ + CXError_Success = 0, + + /** + * \brief A generic error code, no further details are available. + * + * Errors of this kind can get their own specific error codes in future + * libclang versions. + */ + CXError_Failure = 1, + + /** + * \brief libclang crashed while performing the requested operation. + */ + CXError_Crashed = 2, + + /** + * \brief The function detected that the arguments violate the function + * contract. + */ + CXError_InvalidArguments = 3, + + /** + * \brief An AST deserialization error has occurred. + */ + CXError_ASTReadError = 4 +}; + +#ifdef __cplusplus +} +#endif +#endif + diff --git a/include/clang-c/Index.h b/include/clang-c/Index.h index eece5fb975..9c37ac745b 100644 --- a/include/clang-c/Index.h +++ b/include/clang-c/Index.h @@ -19,6 +19,7 @@ #include #include "clang-c/Platform.h" +#include "clang-c/CXErrorCode.h" #include "clang-c/CXString.h" #include "clang-c/BuildSystem.h" @@ -31,7 +32,7 @@ * compatible, thus CINDEX_VERSION_MAJOR is expected to remain stable. */ #define CINDEX_VERSION_MAJOR 0 -#define CINDEX_VERSION_MINOR 23 +#define CINDEX_VERSION_MINOR 24 #define CINDEX_VERSION_ENCODE(major, minor) ( \ ((major) * 10000) \ @@ -73,43 +74,6 @@ extern "C" { * @{ */ -/** - * \brief Error codes returned by libclang routines. - * - * Zero (\c CXError_Success) is the only error code indicating success. Other - * error codes, including not yet assigned non-zero values, indicate errors. - */ -enum CXErrorCode { - /** - * \brief No error. - */ - CXError_Success = 0, - - /** - * \brief A generic error code, no further details are available. - * - * Errors of this kind can get their own specific error codes in future - * libclang versions. - */ - CXError_Failure = 1, - - /** - * \brief libclang crashed while performing the requested operation. - */ - CXError_Crashed = 2, - - /** - * \brief The function detected that the arguments violate the function - * contract. - */ - CXError_InvalidArguments = 3, - - /** - * \brief An AST deserialization error has occurred. - */ - CXError_ASTReadError = 4 -}; - /** * \brief An "index" that consists of a set of translation units that would * typically be linked together into an executable or library. diff --git a/tools/libclang/BuildSystem.cpp b/tools/libclang/BuildSystem.cpp index caf8377174..311319ab96 100644 --- a/tools/libclang/BuildSystem.cpp +++ b/tools/libclang/BuildSystem.cpp @@ -12,11 +12,184 @@ //===----------------------------------------------------------------------===// #include "clang-c/BuildSystem.h" +#include "CXString.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/raw_ostream.h" #include "llvm/Support/TimeValue.h" -extern "C" { +using namespace clang; +using namespace llvm::sys; + unsigned long long clang_getBuildSessionTimestamp(void) { return llvm::sys::TimeValue::now().toEpochTime(); } -} // extern "C" +struct CXVirtualFileOverlayImpl { + std::vector > Mappings; +}; + +CXVirtualFileOverlay clang_VirtualFileOverlay_create(unsigned) { + return new CXVirtualFileOverlayImpl(); +} + +enum CXErrorCode +clang_VirtualFileOverlay_addFileMapping(CXVirtualFileOverlay VFO, + const char *virtualPath, + const char *realPath) { + if (!VFO || !virtualPath || !realPath) + return CXError_InvalidArguments; + if (!path::is_absolute(virtualPath)) + return CXError_InvalidArguments; + if (!path::is_absolute(realPath)) + return CXError_InvalidArguments; + + for (path::const_iterator + PI = path::begin(virtualPath), + PE = path::end(virtualPath); PI != PE; ++PI) { + StringRef Comp = *PI; + if (Comp == "." || Comp == "..") + return CXError_InvalidArguments; + } + + VFO->Mappings.push_back(std::make_pair(virtualPath, realPath)); + return CXError_Success; +} + +namespace { +struct EntryTy { + std::string VPath; + std::string RPath; + + friend bool operator < (const EntryTy &LHS, const EntryTy &RHS) { + return LHS.VPath < RHS.VPath; + } +}; + +class JSONVFSPrinter { + llvm::raw_ostream &OS; + +public: + JSONVFSPrinter(llvm::raw_ostream &OS) : OS(OS) {} + + /// Entries must be sorted. + void print(ArrayRef Entries) { + OS << "{\n" + " 'version': 0,\n" + " 'roots': [\n"; + printDirNodes(Entries, "", 4); + OS << " ]\n" + "}\n"; + } + +private: + ArrayRef printDirNodes(ArrayRef Entries, + StringRef ParentPath, + unsigned Indent) { + while (!Entries.empty()) { + const EntryTy &Entry = Entries.front(); + OS.indent(Indent) << "{\n"; + Indent += 2; + OS.indent(Indent) << "'type': 'directory',\n"; + OS.indent(Indent) << "'name': \""; + StringRef DirName = containedPart(ParentPath, + path::parent_path(Entry.VPath)); + OS.write_escaped(DirName) << "\",\n"; + OS.indent(Indent) << "'contents': [\n"; + Entries = printContents(Entries, Indent + 2); + OS.indent(Indent) << "]\n"; + Indent -= 2; + OS.indent(Indent) << '}'; + if (Entries.empty()) { + OS << '\n'; + break; + } + StringRef NextVPath = Entries.front().VPath; + if (!containedIn(ParentPath, NextVPath)) { + OS << '\n'; + break; + } + OS << ",\n"; + } + return Entries; + } + + ArrayRef printContents(ArrayRef Entries, + unsigned Indent) { + while (!Entries.empty()) { + const EntryTy &Entry = Entries.front(); + Entries = Entries.slice(1); + StringRef ParentPath = path::parent_path(Entry.VPath); + StringRef VName = path::filename(Entry.VPath); + OS.indent(Indent) << "{\n"; + Indent += 2; + OS.indent(Indent) << "'type': 'file',\n"; + OS.indent(Indent) << "'name': \""; + OS.write_escaped(VName) << "\",\n"; + OS.indent(Indent) << "'external-contents': \""; + OS.write_escaped(Entry.RPath) << "\"\n"; + Indent -= 2; + OS.indent(Indent) << '}'; + if (Entries.empty()) { + OS << '\n'; + break; + } + StringRef NextVPath = Entries.front().VPath; + if (!containedIn(ParentPath, NextVPath)) { + OS << '\n'; + break; + } + OS << ",\n"; + if (path::parent_path(NextVPath) != ParentPath) { + Entries = printDirNodes(Entries, ParentPath, Indent); + } + } + return Entries; + } + + bool containedIn(StringRef Parent, StringRef Path) { + return Path.startswith(Parent); + } + + StringRef containedPart(StringRef Parent, StringRef Path) { + assert(containedIn(Parent, Path)); + if (Parent.empty()) + return Path; + return Path.slice(Parent.size()+1, StringRef::npos); + } +}; +} + +enum CXErrorCode +clang_VirtualFileOverlay_writeToBuffer(CXVirtualFileOverlay VFO, + unsigned, CXString *out_buffer) { + if (!VFO || !out_buffer) + return CXError_InvalidArguments; + + llvm::SmallVector Entries; + for (unsigned i = 0, e = VFO->Mappings.size(); i != e; ++i) { + EntryTy Entry; + Entry.VPath = VFO->Mappings[i].first; + Entry.RPath = VFO->Mappings[i].second; + Entries.push_back(Entry); + } + + // FIXME: We should add options to determine if the paths are case sensitive + // or not. The following assumes that if paths are case-insensitive the caller + // did not mix cases in the virtual paths it provided. + + std::sort(Entries.begin(), Entries.end()); + + llvm::SmallString<256> Buf; + llvm::raw_svector_ostream OS(Buf); + JSONVFSPrinter Printer(OS); + Printer.print(Entries); + + *out_buffer = cxstring::createDup(OS.str()); + return CXError_Success; +} + +void clang_VirtualFileOverlay_dispose(CXVirtualFileOverlay VFO) { + delete VFO; +} diff --git a/tools/libclang/libclang.exports b/tools/libclang/libclang.exports index 0fd63d3113..5fbd9a2468 100644 --- a/tools/libclang/libclang.exports +++ b/tools/libclang/libclang.exports @@ -284,3 +284,7 @@ clang_CompileCommand_getNumArgs clang_CompileCommand_getArg clang_visitChildren clang_visitChildrenWithBlock +clang_VirtualFileOverlay_addFileMapping +clang_VirtualFileOverlay_create +clang_VirtualFileOverlay_dispose +clang_VirtualFileOverlay_writeToBuffer diff --git a/unittests/libclang/LibclangTest.cpp b/unittests/libclang/LibclangTest.cpp index 4278d10a1f..f855ecdd3c 100644 --- a/unittests/libclang/LibclangTest.cpp +++ b/unittests/libclang/LibclangTest.cpp @@ -28,3 +28,114 @@ TEST(libclang, clang_createTranslationUnit2_InvalidArgs) { clang_createTranslationUnit2(0, 0, &TU)); EXPECT_EQ(0, TU); } + +namespace { +struct TestVFO { + const char *Contents; + CXVirtualFileOverlay VFO; + + TestVFO(const char *Contents) : Contents(Contents) { + VFO = clang_VirtualFileOverlay_create(0); + } + + void map(const char *VPath, const char *RPath) { + CXErrorCode Err = clang_VirtualFileOverlay_addFileMapping(VFO, VPath, RPath); + EXPECT_EQ(Err, CXError_Success); + } + + void mapError(const char *VPath, const char *RPath, CXErrorCode ExpErr) { + CXErrorCode Err = clang_VirtualFileOverlay_addFileMapping(VFO, VPath, RPath); + EXPECT_EQ(Err, ExpErr); + } + + ~TestVFO() { + if (!Contents) + return; + CXString Buf; + clang_VirtualFileOverlay_writeToBuffer(VFO, 0, &Buf); + EXPECT_STREQ(Contents, clang_getCString(Buf)); + clang_disposeString(Buf); + clang_VirtualFileOverlay_dispose(VFO); + } +}; +} + +TEST(libclang, VirtualFileOverlay) { + { + const char *contents = + "{\n" + " 'version': 0,\n" + " 'roots': [\n" + " {\n" + " 'type': 'directory',\n" + " 'name': \"/path/virtual\",\n" + " 'contents': [\n" + " {\n" + " 'type': 'file',\n" + " 'name': \"foo.h\",\n" + " 'external-contents': \"/real/foo.h\"\n" + " }\n" + " ]\n" + " }\n" + " ]\n" + "}\n"; + TestVFO T(contents); + T.map("/path/virtual/foo.h", "/real/foo.h"); + } + { + TestVFO T(NULL); + T.mapError("/path/./virtual/../foo.h", "/real/foo.h", + CXError_InvalidArguments); + } + { + const char *contents = + "{\n" + " 'version': 0,\n" + " 'roots': [\n" + " {\n" + " 'type': 'directory',\n" + " 'name': \"/another/dir\",\n" + " 'contents': [\n" + " {\n" + " 'type': 'file',\n" + " 'name': \"foo2.h\",\n" + " 'external-contents': \"/real/foo2.h\"\n" + " }\n" + " ]\n" + " },\n" + " {\n" + " 'type': 'directory',\n" + " 'name': \"/path/virtual/dir\",\n" + " 'contents': [\n" + " {\n" + " 'type': 'file',\n" + " 'name': \"foo1.h\",\n" + " 'external-contents': \"/real/foo1.h\"\n" + " },\n" + " {\n" + " 'type': 'file',\n" + " 'name': \"foo3.h\",\n" + " 'external-contents': \"/real/foo3.h\"\n" + " },\n" + " {\n" + " 'type': 'directory',\n" + " 'name': \"in/subdir\",\n" + " 'contents': [\n" + " {\n" + " 'type': 'file',\n" + " 'name': \"foo4.h\",\n" + " 'external-contents': \"/real/foo4.h\"\n" + " }\n" + " ]\n" + " }\n" + " ]\n" + " }\n" + " ]\n" + "}\n"; + TestVFO T(contents); + T.map("/path/virtual/dir/foo1.h", "/real/foo1.h"); + T.map("/another/dir/foo2.h", "/real/foo2.h"); + T.map("/path/virtual/dir/foo3.h", "/real/foo3.h"); + T.map("/path/virtual/dir/in/subdir/foo4.h", "/real/foo4.h"); + } +}