[llvm-libtool-darwin] Add support for -dependency_info
authorKeith Smiley <keithbsmiley@gmail.com>
Tue, 20 Sep 2022 22:37:49 +0000 (15:37 -0700)
committerKeith Smiley <keithbsmiley@gmail.com>
Mon, 3 Oct 2022 16:53:02 +0000 (09:53 -0700)
When using llvm-libtool-darwin as a drop in replacement for cctools
libtool, Xcode expects you to create a dependency info file. This file
is a very simple format describing the input files, the output files,
and the version of the tool. This logic is mirrored from that of
ld64.lld, which supports creating this file as well. Ideally we could
extract it, but I don't think we want to throw this into one of the
grab-bag libraries given how small the logic is.

Differential Revision: https://reviews.llvm.org/D134322

llvm/test/tools/llvm-libtool-darwin/Inputs/DependencyDump.py [new file with mode: 0755]
llvm/test/tools/llvm-libtool-darwin/dependency-info.test [new file with mode: 0644]
llvm/tools/llvm-libtool-darwin/DependencyInfo.h [new file with mode: 0644]
llvm/tools/llvm-libtool-darwin/llvm-libtool-darwin.cpp

diff --git a/llvm/test/tools/llvm-libtool-darwin/Inputs/DependencyDump.py b/llvm/test/tools/llvm-libtool-darwin/Inputs/DependencyDump.py
new file mode 100755 (executable)
index 0000000..ddf9873
--- /dev/null
@@ -0,0 +1,26 @@
+#
+# Dump the dependency file (produced with -dependency_info) to text
+# format for testing purposes.
+#
+
+import sys
+
+f = open(sys.argv[1], "rb")
+byte = f.read(1)
+while byte != b'':
+    if byte == b'\x00':
+        sys.stdout.write("version: ")
+    elif byte == b'\x10':
+        sys.stdout.write("input-file: ")
+    elif byte == b'\x11':
+        sys.stdout.write("not-found: ")
+    elif byte == b'\x40':
+        sys.stdout.write("output-file: ")
+    byte = f.read(1)
+    while byte != b'\x00':
+        sys.stdout.write(byte.decode("ascii"))
+        byte = f.read(1)
+    sys.stdout.write("\n")
+    byte = f.read(1)
+
+f.close()
diff --git a/llvm/test/tools/llvm-libtool-darwin/dependency-info.test b/llvm/test/tools/llvm-libtool-darwin/dependency-info.test
new file mode 100644 (file)
index 0000000..72e63d5
--- /dev/null
@@ -0,0 +1,19 @@
+## This test validates the format of the dependency info file
+# RUN: rm -rf %t; mkdir -p %t
+
+# RUN: yaml2obj %S/Inputs/input1.yaml -o %t/foo.o
+# RUN: llvm-libtool-darwin -static -o %t/libfirst.a %t/foo.o -dependency_info %t/simple.dat
+# RUN: %python %S/Inputs/DependencyDump.py %t/simple.dat | FileCheck --check-prefix=SIMPLE %s
+
+# RUN: llvm-libtool-darwin -static -o %t/second.lib %t/foo.o -lfirst -L/missing/directory/without/lib -L%t -dependency_info %t/lib.dat
+# RUN: %python %S/Inputs/DependencyDump.py %t/lib.dat | FileCheck --check-prefix=LIB %s
+
+# SIMPLE: version: llvm-libtool-darwin
+# SIMPLE: input-file: {{.+}}foo.o
+# SIMPLE: output-file: {{.+}}libfirst.a
+
+# LIB: version: llvm-libtool-darwin
+# LIB: input-file: {{.+}}foo.o
+# LIB: input-file: {{.+}}libfirst.a
+# LIB: not-found: {{.+}}missing{{.+}}directory{{.+}}without{{.+}}lib{{.+}}libfirst.a
+# LIB: output-file: {{.+}}second.lib
diff --git a/llvm/tools/llvm-libtool-darwin/DependencyInfo.h b/llvm/tools/llvm-libtool-darwin/DependencyInfo.h
new file mode 100644 (file)
index 0000000..7b2f94b
--- /dev/null
@@ -0,0 +1,85 @@
+//===-- DependencyInfo.h --------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/WithColor.h"
+#include "llvm/Support/raw_ostream.h"
+
+#include <set>
+
+class DependencyInfo {
+public:
+  explicit DependencyInfo(std::string DependencyInfoPath)
+      : DependencyInfoPath(DependencyInfoPath) {}
+
+  virtual ~DependencyInfo(){};
+
+  virtual void addMissingInput(llvm::StringRef Path) {
+    NotFounds.insert(Path.str());
+  }
+
+  // Writes the dependencies to specified path. The content is first sorted by
+  // OpCode and then by the filename (in alphabetical order).
+  virtual void write(llvm::Twine Version,
+                     const std::vector<std::string> &Inputs,
+                     std::string Output) {
+    std::error_code EC;
+    llvm::raw_fd_ostream OS(DependencyInfoPath, EC, llvm::sys::fs::OF_None);
+    if (EC) {
+      llvm::WithColor::defaultErrorHandler(llvm::createStringError(
+          EC,
+          "failed to write to " + DependencyInfoPath + ": " + EC.message()));
+      return;
+    }
+
+    auto AddDep = [&OS](DependencyInfoOpcode Opcode,
+                        const llvm::StringRef &Path) {
+      OS << static_cast<uint8_t>(Opcode);
+      OS << Path;
+      OS << '\0';
+    };
+
+    AddDep(DependencyInfoOpcode::Tool, Version.str());
+
+    // Sort the input by its names.
+    std::vector<llvm::StringRef> InputNames;
+    InputNames.reserve(Inputs.size());
+    for (const auto &F : Inputs)
+      InputNames.push_back(F);
+    llvm::sort(InputNames);
+
+    for (const auto &In : InputNames)
+      AddDep(DependencyInfoOpcode::InputFound, In);
+
+    for (const std::string &F : NotFounds)
+      AddDep(DependencyInfoOpcode::InputMissing, F);
+
+    AddDep(DependencyInfoOpcode::Output, Output);
+  }
+
+private:
+  enum DependencyInfoOpcode : uint8_t {
+    Tool = 0x00,
+    InputFound = 0x10,
+    InputMissing = 0x11,
+    Output = 0x40,
+  };
+
+  const std::string DependencyInfoPath;
+  std::set<std::string> NotFounds;
+};
+
+// Subclass to avoid any overhead when not using this feature
+class DummyDependencyInfo : public DependencyInfo {
+public:
+  DummyDependencyInfo() : DependencyInfo("") {}
+  void addMissingInput(llvm::StringRef Path) override {}
+  void write(llvm::Twine Version, const std::vector<std::string> &Inputs,
+             std::string Output) override {}
+};
index 8d4529c..5fec847 100644 (file)
@@ -10,6 +10,7 @@
 //
 //===----------------------------------------------------------------------===//
 
+#include "DependencyInfo.h"
 #include "llvm/BinaryFormat/Magic.h"
 #include "llvm/IR/LLVMContext.h"
 #include "llvm/Object/ArchiveWriter.h"
@@ -87,6 +88,12 @@ static cl::list<std::string> LibrarySearchDirs(
         " libraries"),
     cl::Prefix, cl::cat(LibtoolCategory));
 
+static cl::opt<std::string> DependencyInfoPath(
+    "dependency_info",
+    cl::desc("Write an Xcode dependency info file describing the dependencies "
+             "of the created library"),
+    cl::cat(LibtoolCategory));
+
 static cl::opt<bool>
     VersionOption("V", cl::desc("Print the version number and exit"),
                   cl::cat(LibtoolCategory));
@@ -109,6 +116,8 @@ static const std::array<std::string, 3> StandardSearchDirs{
     "/usr/local/lib",
 };
 
+std::unique_ptr<DependencyInfo> GlobalDependencyInfo;
+
 struct Config {
   bool Deterministic = true; // Updated by 'D' and 'U' modifiers.
   uint32_t ArchCPUType;
@@ -116,7 +125,6 @@ struct Config {
 };
 
 static Expected<std::string> searchForFile(const Twine &FileName) {
-
   auto FindLib =
       [FileName](ArrayRef<std::string> SearchDirs) -> Optional<std::string> {
     for (StringRef Dir : SearchDirs) {
@@ -125,6 +133,8 @@ static Expected<std::string> searchForFile(const Twine &FileName) {
 
       if (sys::fs::exists(Path))
         return std::string(Path);
+
+      GlobalDependencyInfo->addMissingInput(Path);
     }
     return None;
   };
@@ -652,6 +662,11 @@ static Expected<Config> parseCommandLine(int Argc, char **Argv) {
     return C;
   }
 
+  GlobalDependencyInfo =
+      DependencyInfoPath.empty()
+          ? std::make_unique<DummyDependencyInfo>()
+          : std::make_unique<DependencyInfo>(DependencyInfoPath);
+
   if (OutputFile.empty()) {
     std::string Error;
     raw_string_ostream Stream(Error);
@@ -686,6 +701,9 @@ static Expected<Config> parseCommandLine(int Argc, char **Argv) {
             MachO::getArchitectureFromName(ArchType));
   }
 
+  GlobalDependencyInfo->write("llvm-libtool-darwin " LLVM_VERSION_STRING,
+                              InputFiles, OutputFile);
+
   return C;
 }