From: Eric Beckmann Date: Thu, 20 Jul 2017 21:42:04 +0000 (+0000) Subject: Implement parsing and writing of a single xml manifest file. X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=c1527448edefc4fe937efe430a8eb17bb7ea726c;p=llvm Implement parsing and writing of a single xml manifest file. Summary: Implement parsing and writing of a single xml manifest file. Subscribers: mgorny, llvm-commits, hiraditya Differential Revision: https://reviews.llvm.org/D35425 git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@308679 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/CMakeLists.txt b/CMakeLists.txt index b0572f9adfa..115f39993db 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -363,6 +363,8 @@ set(LLVM_TARGET_ARCH "host" option(LLVM_ENABLE_TERMINFO "Use terminfo database if available." ON) +option(LLVM_ENABLE_LIBXML2 "Use libxml2 if available." ON) + option(LLVM_ENABLE_LIBEDIT "Use libedit if available." ON) option(LLVM_ENABLE_THREADS "Use threads if available." ON) diff --git a/cmake/config-ix.cmake b/cmake/config-ix.cmake index de8e9bf9a49..dc9bd153881 100644 --- a/cmake/config-ix.cmake +++ b/cmake/config-ix.cmake @@ -155,6 +155,17 @@ if( NOT PURE_WINDOWS AND NOT LLVM_USE_SANITIZER MATCHES "Memory.*") else() set(HAVE_TERMINFO 0) endif() + + set(LLVM_LIBXML2_ENABLED 0) + set(LIBXML2_FOUND 0) + if(LLVM_ENABLE_LIBXML2) + find_package(LibXml2) + if (LIBXML2_FOUND) + set(LLVM_LIBXML2_ENABLED 1) + include_directories(${LIBXML2_INCLUDE_DIR}) + set(LIBXML2_LIBS "xml2") + endif() + endif() endif() check_library_exists(xar xar_open "" HAVE_LIBXAR) diff --git a/include/llvm/Config/config.h.cmake b/include/llvm/Config/config.h.cmake index 1289551f073..4163d7d9c44 100644 --- a/include/llvm/Config/config.h.cmake +++ b/include/llvm/Config/config.h.cmake @@ -383,6 +383,9 @@ /* LLVM version string */ #define LLVM_VERSION_STRING "${PACKAGE_VERSION}" +/* Define if libxml2 is supported on this platform. */ +#cmakedefine LLVM_LIBXML2_ENABLED ${LLVM_LIBXML2_ENABLED} + /* Define to the extension used for shared libraries, say, ".so". */ #cmakedefine LTDL_SHLIB_EXT "${LTDL_SHLIB_EXT}" diff --git a/include/llvm/Support/WindowsManifestMerger.h b/include/llvm/Support/WindowsManifestMerger.h new file mode 100644 index 00000000000..a5d7b1182e3 --- /dev/null +++ b/include/llvm/Support/WindowsManifestMerger.h @@ -0,0 +1,75 @@ +//===-- WindowsManifestMerger.h ---------------------------------*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===---------------------------------------------------------------------===// +// +// This file provides a utility for merging Microsoft .manifest files. These +// files are xml documents which contain meta-information about applications, +// such as whether or not admin access is required, system compatibility, +// versions, etc. Part of the linking process of an executable may require +// merging several of these .manifest files using a tree-merge following +// specific rules. Unfortunately, these rules are not documented well +// anywhere. However, a careful investigation of the behavior of the original +// Microsoft Manifest Tool (mt.exe) revealed the rules of this merge. As the +// saying goes, code is the best documentation, so please look below if you are +// interested in the exact merging requirements. +// +// Ref: +// https://msdn.microsoft.com/en-us/library/windows/desktop/aa374191(v=vs.85).aspx +// +//===---------------------------------------------------------------------===// + +#ifndef LLVM_INCLUDE_LLVM_SUPPORT_WINDOWS_MANIFEST_MERGER_H +#define LLVM_INCLUDE_LLVM_SUPPORT_WINDOWS_MANIFEST_MERGER_H + +#include "llvm/Config/config.h" +#include "llvm/Support/Error.h" + +#if LLVM_LIBXML2_ENABLED +#include +#endif + +namespace llvm { + +class MemoryBuffer; + +#if LLVM_LIBXML2_ENABLED +typedef xmlDocPtr XMLDocumentImpl; +typedef xmlNodePtr XMLNodeImpl; +#else +typedef void *XMLDocumentImpl; +typedef void *XMLNodeImpl; +#endif + +class WindowsManifestError : public ErrorInfo { +public: + static char ID; + WindowsManifestError(const Twine &Msg); + void log(raw_ostream &OS) const override; + +private: + std::string Msg; +}; + +class WindowsManifestMerger { +public: + Error merge(const MemoryBuffer &Manifest); + + // Returns vector containing merged xml manifest, or uninitialized vector for + // empty manifest. + std::unique_ptr getMergedManifest(); + +private: + static void errorCallback(void *Ctx, const char *Format, ...); + Error getParseError(); + + XMLNodeImpl CombinedRoot = nullptr; + bool ParseErrorOccurred = false; +}; + +} // namespace llvm +#endif diff --git a/lib/Support/CMakeLists.txt b/lib/Support/CMakeLists.txt index 0a8e3897cce..e258e4df799 100644 --- a/lib/Support/CMakeLists.txt +++ b/lib/Support/CMakeLists.txt @@ -27,6 +27,9 @@ elseif( CMAKE_HOST_UNIX ) if( UNIX AND NOT (BEOS OR HAIKU) ) set(system_libs ${system_libs} m) endif() + if( LLVM_LIBXML2_ENABLED ) + set(system_libs ${system_libs} ${LIBXML2_LIBS}) + endif() endif( MSVC OR MINGW ) add_llvm_library(LLVMSupport @@ -110,6 +113,7 @@ add_llvm_library(LLVMSupport Triple.cpp Twine.cpp Unicode.cpp + WindowsManifestMerger.cpp YAMLParser.cpp YAMLTraits.cpp raw_os_ostream.cpp diff --git a/lib/Support/WindowsManifestMerger.cpp b/lib/Support/WindowsManifestMerger.cpp new file mode 100644 index 00000000000..60cddd10596 --- /dev/null +++ b/lib/Support/WindowsManifestMerger.cpp @@ -0,0 +1,70 @@ +//===-- WindowsManifestMerger.cpp ------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===---------------------------------------------------------------------===// +// +// This file implements the .manifest merger class. +// +//===---------------------------------------------------------------------===// + +#include "llvm/Support/WindowsManifestMerger.h" +#include "llvm/Support/MemoryBuffer.h" + +#include + +namespace llvm { + +char WindowsManifestError::ID = 0; + +WindowsManifestError::WindowsManifestError(const Twine &Msg) : Msg(Msg.str()) {} + +void WindowsManifestError::log(raw_ostream &OS) const { OS << Msg; } + +Error WindowsManifestMerger::merge(const MemoryBuffer &Manifest) { +#if LLVM_LIBXML2_ENABLED + xmlSetGenericErrorFunc((void *)this, WindowsManifestMerger::errorCallback); + XMLDocumentImpl ManifestXML = + xmlReadMemory(Manifest.getBufferStart(), Manifest.getBufferSize(), + "manifest.xml", nullptr, 0); + xmlSetGenericErrorFunc(nullptr, nullptr); + if (auto E = getParseError()) + return E; + CombinedRoot = xmlDocGetRootElement(ManifestXML); +#endif + return Error::success(); +} + +std::unique_ptr WindowsManifestMerger::getMergedManifest() { +#if LLVM_LIBXML2_ENABLED + unsigned char *XmlBuff; + int BufferSize = 0; + if (CombinedRoot) { + std::unique_ptr OutputDoc(xmlNewDoc((const unsigned char *)"1.0")); + xmlDocSetRootElement(OutputDoc.get(), CombinedRoot); + xmlDocDumpMemory(OutputDoc.get(), &XmlBuff, &BufferSize); + } + if (BufferSize == 0) + return nullptr; + return MemoryBuffer::getMemBuffer( + StringRef(reinterpret_cast(XmlBuff), (size_t)BufferSize)); +#else + return nullptr; +#endif +} + +void WindowsManifestMerger::errorCallback(void *Ctx, const char *Format, ...) { + auto *Merger = (WindowsManifestMerger *)Ctx; + Merger->ParseErrorOccurred = true; +} + +Error WindowsManifestMerger::getParseError() { + if (!ParseErrorOccurred) + return Error::success(); + return make_error("invalid xml document"); +} + +} // namespace llvm diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 124f0c72fd7..906b6a594bc 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -59,7 +59,6 @@ set(LLVM_TEST_DEPENDS llvm-mc llvm-mcmarkup llvm-modextract - llvm-mt llvm-nm llvm-objdump llvm-opt-report @@ -140,6 +139,12 @@ if(TARGET ocaml_llvm) ) endif() +if (LLVM_LIBXML2_ENABLED) + set(LLVM_TEST_DEPENDS ${LLVM_TEST_DEPENDS} + llvm-mt + ) +endif() + add_custom_target(llvm-test-depends DEPENDS ${LLVM_TEST_DEPENDS}) set_target_properties(llvm-test-depends PROPERTIES FOLDER "Tests") diff --git a/test/lit.cfg b/test/lit.cfg index 8ed9187aea7..891d8189c77 100644 --- a/test/lit.cfg +++ b/test/lit.cfg @@ -288,6 +288,7 @@ for pattern in [r"\bbugpoint\b(?!-)", r"\bllvm-config\b", r"\bllvm-cov\b", r"\bllvm-cxxdump\b", + r"\bllvm-cvtres\b", r"\bllvm-diff\b", r"\bllvm-dis\b", r"\bllvm-dsymutil\b", @@ -337,6 +338,7 @@ for pattern in [r"\bbugpoint\b(?!-)", # For tools that are optional depending on the config, we won't warn # if they're missing. for pattern in [r"\bllvm-go\b", + r"\bllvm-mt\b", r"\bKaleidoscope-Ch3\b", r"\bKaleidoscope-Ch4\b", r"\bKaleidoscope-Ch5\b", @@ -550,3 +552,7 @@ if config.have_libxar: if config.enable_abi_breaking_checks == "1": config.available_features.add('abi-breaking-checks') + +if config.llvm_disable_libxml2 == "OFF" and config.have_libxml2 == "TRUE": + config.available_features.add('libxml2') + \ No newline at end of file diff --git a/test/lit.site.cfg.in b/test/lit.site.cfg.in index f95f6d8ec9a..67683d793aa 100644 --- a/test/lit.site.cfg.in +++ b/test/lit.site.cfg.in @@ -40,6 +40,8 @@ config.have_libxar = @HAVE_LIBXAR@ config.have_dia_sdk = @LLVM_ENABLE_DIA_SDK@ config.enable_ffi = @LLVM_ENABLE_FFI@ config.build_shared_libs = @BUILD_SHARED_LIBS@ +config.llvm_disable_libxml2 = "@LLVM_DISABLE_LIBXML2@" +config.have_libxml2 = "@LIBXML2_FOUND@" # Support substitution of the tools_dir with user parameters. This is # used when we can't determine the tool dir at configuration time. diff --git a/test/tools/llvm-mt/Inputs/bad.manifest b/test/tools/llvm-mt/Inputs/bad.manifest new file mode 100644 index 00000000000..f76dd238ade Binary files /dev/null and b/test/tools/llvm-mt/Inputs/bad.manifest differ diff --git a/test/tools/llvm-mt/Inputs/test_manifest.manifest b/test/tools/llvm-mt/Inputs/test_manifest.manifest new file mode 100644 index 00000000000..f3dbc7a47e6 --- /dev/null +++ b/test/tools/llvm-mt/Inputs/test_manifest.manifest @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/test/tools/llvm-mt/help.test b/test/tools/llvm-mt/help.test index 29e3667ec2c..190e90a1fdc 100644 --- a/test/tools/llvm-mt/help.test +++ b/test/tools/llvm-mt/help.test @@ -1,7 +1,3 @@ RUN: llvm-mt /h | FileCheck %s -check-prefix=HELP -RUN: llvm-mt /inputresource:foo.res /manifest foo.manifest | FileCheck %s -check-prefix=NOT_SUPPORTED - HELP: OVERVIEW: Manifest Tool - -NOT_SUPPORTED: llvm-mt: ignoring unsupported 'inputresource:' option diff --git a/test/tools/llvm-mt/single_file.test b/test/tools/llvm-mt/single_file.test new file mode 100644 index 00000000000..5fd806c2643 --- /dev/null +++ b/test/tools/llvm-mt/single_file.test @@ -0,0 +1,5 @@ +REQUIRES: libxml2 +UNSUPPORTED: windows + +RUN: llvm-mt /manifest %p/Inputs/test_manifest.manifest /out:%t +RUN: diff %p/Inputs/test_manifest.manifest %t diff --git a/test/tools/llvm-mt/xml_error.test b/test/tools/llvm-mt/xml_error.test new file mode 100644 index 00000000000..50246c962a1 --- /dev/null +++ b/test/tools/llvm-mt/xml_error.test @@ -0,0 +1,11 @@ +REQUIRES: libxml2 +UNSUPPORTED: windows + +RUN: not llvm-mt /manifest %p/Inputs/bad.manifest 2>&1 >/dev/null | FileCheck %s + +CHECK: llvm-mt error: invalid xml document + +RUN: llvm-mt /inputresource:foo.res /manifest \ +RUN: %p/Inputs/test_manifest.manifest | FileCheck %s -check-prefix=NOT_SUPPORTED + +NOT_SUPPORTED: llvm-mt: ignoring unsupported 'inputresource:' option diff --git a/tools/LLVMBuild.txt b/tools/LLVMBuild.txt index bcf58842eac..be637a43096 100644 --- a/tools/LLVMBuild.txt +++ b/tools/LLVMBuild.txt @@ -38,6 +38,7 @@ subdirectories = llvm-mc llvm-mcmarkup llvm-modextract + llvm-mt llvm-nm llvm-objdump llvm-pdbutil diff --git a/tools/llvm-mt/llvm-mt.cpp b/tools/llvm-mt/llvm-mt.cpp index 05c9238c7c6..412883eb651 100644 --- a/tools/llvm-mt/llvm-mt.cpp +++ b/tools/llvm-mt/llvm-mt.cpp @@ -16,11 +16,13 @@ #include "llvm/Option/ArgList.h" #include "llvm/Option/Option.h" #include "llvm/Support/Error.h" +#include "llvm/Support/FileOutputBuffer.h" #include "llvm/Support/ManagedStatic.h" #include "llvm/Support/Path.h" #include "llvm/Support/PrettyStackTrace.h" #include "llvm/Support/Process.h" #include "llvm/Support/Signals.h" +#include "llvm/Support/WindowsManifestMerger.h" #include "llvm/Support/raw_ostream.h" #include @@ -67,6 +69,22 @@ LLVM_ATTRIBUTE_NORETURN void reportError(Twine Msg) { exit(1); } +static void reportError(StringRef Input, std::error_code EC) { + reportError(Twine(Input) + ": " + EC.message()); +} + +void error(std::error_code EC) { + if (EC) + reportError(EC.message()); +} + +void error(Error EC) { + if (EC) + handleAllErrors(std::move(EC), [&](const ErrorInfoBase &EI) { + reportError(EI.message()); + }); +} + int main(int argc, const char **argv) { sys::PrintStackTraceOnErrorSignal(argv[0]); PrettyStackTraceProgram X(argc, argv); @@ -104,7 +122,6 @@ int main(int argc, const char **argv) { } StringRef OutputFile; - if (InputArgs.hasArg(OPT_out)) { OutputFile = InputArgs.getLastArgValue(OPT_out); } else if (InputFiles.size() == 1) { @@ -113,5 +130,27 @@ int main(int argc, const char **argv) { reportError("no output file specified"); } + WindowsManifestMerger Merger; + + for (const auto &File : InputFiles) { + ErrorOr> ManifestOrErr = + MemoryBuffer::getFile(File); + if (!ManifestOrErr) + reportError(File, ManifestOrErr.getError()); + MemoryBuffer &Manifest = *ManifestOrErr.get(); + error(Merger.merge(Manifest)); + } + + std::unique_ptr OutputBuffer = Merger.getMergedManifest(); + if (!OutputBuffer) + reportError("empty manifest not written"); + ErrorOr> FileOrErr = + FileOutputBuffer::create(OutputFile, OutputBuffer->getBufferSize()); + if (!FileOrErr) + reportError(OutputFile, FileOrErr.getError()); + std::unique_ptr FileBuffer = std::move(*FileOrErr); + std::copy(OutputBuffer->getBufferStart(), OutputBuffer->getBufferEnd(), + FileBuffer->getBufferStart()); + error(FileBuffer->commit()); return 0; }