| //===-- llvm-libtool-darwin.cpp - a tool for creating libraries -----------===// |
| // |
| // 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 |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // A utility for creating static and dynamic libraries for Darwin. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "llvm/BinaryFormat/Magic.h" |
| #include "llvm/IR/LLVMContext.h" |
| #include "llvm/Object/ArchiveWriter.h" |
| #include "llvm/Object/IRObjectFile.h" |
| #include "llvm/Object/MachO.h" |
| #include "llvm/Object/MachOUniversal.h" |
| #include "llvm/Object/MachOUniversalWriter.h" |
| #include "llvm/Object/ObjectFile.h" |
| #include "llvm/Support/CommandLine.h" |
| #include "llvm/Support/InitLLVM.h" |
| #include "llvm/Support/LineIterator.h" |
| #include "llvm/Support/VirtualFileSystem.h" |
| #include "llvm/Support/WithColor.h" |
| #include "llvm/TextAPI/Architecture.h" |
| #include <map> |
| |
| using namespace llvm; |
| using namespace llvm::object; |
| |
| static LLVMContext LLVMCtx; |
| |
| typedef std::map<uint64_t, std::vector<NewArchiveMember>> |
| MembersPerArchitectureMap; |
| |
| cl::OptionCategory LibtoolCategory("llvm-libtool-darwin Options"); |
| |
| static cl::opt<std::string> OutputFile("o", cl::desc("Specify output filename"), |
| cl::value_desc("filename"), |
| cl::cat(LibtoolCategory)); |
| |
| static cl::list<std::string> InputFiles(cl::Positional, |
| cl::desc("<input files>"), |
| cl::ZeroOrMore, |
| cl::cat(LibtoolCategory)); |
| |
| static cl::opt<std::string> ArchType( |
| "arch_only", cl::desc("Specify architecture type for output library"), |
| cl::value_desc("arch_type"), cl::ZeroOrMore, cl::cat(LibtoolCategory)); |
| |
| enum class Operation { None, Static }; |
| |
| static cl::opt<Operation> LibraryOperation( |
| cl::desc("Library Type: "), |
| cl::values( |
| clEnumValN(Operation::Static, "static", |
| "Produce a statically linked library from the input files")), |
| cl::init(Operation::None), cl::cat(LibtoolCategory)); |
| |
| static cl::opt<bool> DeterministicOption( |
| "D", cl::desc("Use zero for timestamps and UIDs/GIDs (Default)"), |
| cl::init(false), cl::cat(LibtoolCategory)); |
| |
| static cl::opt<bool> |
| NonDeterministicOption("U", cl::desc("Use actual timestamps and UIDs/GIDs"), |
| cl::init(false), cl::cat(LibtoolCategory)); |
| |
| static cl::opt<std::string> |
| FileList("filelist", |
| cl::desc("Pass in file containing a list of filenames"), |
| cl::value_desc("listfile[,dirname]"), cl::cat(LibtoolCategory)); |
| |
| static cl::list<std::string> Libraries( |
| "l", |
| cl::desc( |
| "l<x> searches for the library libx.a in the library search path. If" |
| " the string 'x' ends with '.o', then the library 'x' is searched for" |
| " without prepending 'lib' or appending '.a'"), |
| cl::ZeroOrMore, cl::Prefix, cl::cat(LibtoolCategory)); |
| |
| static cl::list<std::string> LibrarySearchDirs( |
| "L", |
| cl::desc( |
| "L<dir> adds <dir> to the list of directories in which to search for" |
| " libraries"), |
| cl::ZeroOrMore, cl::Prefix, cl::cat(LibtoolCategory)); |
| |
| static cl::opt<bool> |
| VersionOption("V", cl::desc("Print the version number and exit"), |
| cl::cat(LibtoolCategory)); |
| |
| static cl::opt<bool> NoWarningForNoSymbols( |
| "no_warning_for_no_symbols", |
| cl::desc("Do not warn about files that have no symbols"), |
| cl::cat(LibtoolCategory), cl::init(false)); |
| |
| static const std::array<std::string, 3> StandardSearchDirs{ |
| "/lib", |
| "/usr/lib", |
| "/usr/local/lib", |
| }; |
| |
| struct Config { |
| bool Deterministic = true; // Updated by 'D' and 'U' modifiers. |
| uint32_t ArchCPUType; |
| uint32_t ArchCPUSubtype; |
| }; |
| |
| static Expected<std::string> searchForFile(const Twine &FileName) { |
| |
| auto FindLib = |
| [FileName](ArrayRef<std::string> SearchDirs) -> Optional<std::string> { |
| for (StringRef Dir : SearchDirs) { |
| SmallString<128> Path; |
| sys::path::append(Path, Dir, FileName); |
| |
| if (sys::fs::exists(Path)) |
| return std::string(Path); |
| } |
| return None; |
| }; |
| |
| Optional<std::string> Found = FindLib(LibrarySearchDirs); |
| if (!Found) |
| Found = FindLib(StandardSearchDirs); |
| if (Found) |
| return *Found; |
| |
| return createStringError(std::errc::invalid_argument, |
| "cannot locate file '%s'", FileName.str().c_str()); |
| } |
| |
| static Error processCommandLineLibraries() { |
| for (StringRef BaseName : Libraries) { |
| Expected<std::string> FullPath = searchForFile( |
| BaseName.endswith(".o") ? BaseName.str() : "lib" + BaseName + ".a"); |
| if (!FullPath) |
| return FullPath.takeError(); |
| InputFiles.push_back(FullPath.get()); |
| } |
| |
| return Error::success(); |
| } |
| |
| static Error processFileList() { |
| StringRef FileName, DirName; |
| std::tie(FileName, DirName) = StringRef(FileList).rsplit(","); |
| |
| ErrorOr<std::unique_ptr<MemoryBuffer>> FileOrErr = |
| MemoryBuffer::getFileOrSTDIN(FileName, /*IsText=*/false, |
| /*RequiresNullTerminator=*/false); |
| if (std::error_code EC = FileOrErr.getError()) |
| return createFileError(FileName, errorCodeToError(EC)); |
| const MemoryBuffer &Ref = *FileOrErr.get(); |
| |
| line_iterator I(Ref, /*SkipBlanks=*/false); |
| if (I.is_at_eof()) |
| return createStringError(std::errc::invalid_argument, |
| "file list file: '%s' is empty", |
| FileName.str().c_str()); |
| for (; !I.is_at_eof(); ++I) { |
| StringRef Line = *I; |
| if (Line.empty()) |
| return createStringError(std::errc::invalid_argument, |
| "file list file: '%s': filename cannot be empty", |
| FileName.str().c_str()); |
| |
| SmallString<128> Path; |
| if (!DirName.empty()) |
| sys::path::append(Path, DirName, Line); |
| else |
| sys::path::append(Path, Line); |
| InputFiles.push_back(static_cast<std::string>(Path)); |
| } |
| return Error::success(); |
| } |
| |
| static Error validateArchitectureName(StringRef ArchitectureName) { |
| if (!MachOObjectFile::isValidArch(ArchitectureName)) { |
| std::string Buf; |
| raw_string_ostream OS(Buf); |
| for (StringRef Arch : MachOObjectFile::getValidArchs()) |
| OS << Arch << " "; |
| |
| return createStringError( |
| std::errc::invalid_argument, |
| "invalid architecture '%s': valid architecture names are %s", |
| ArchitectureName.str().c_str(), OS.str().c_str()); |
| } |
| return Error::success(); |
| } |
| |
| static uint64_t getCPUID(uint32_t CPUType, uint32_t CPUSubtype) { |
| switch (CPUType) { |
| case MachO::CPU_TYPE_ARM: |
| case MachO::CPU_TYPE_ARM64: |
| case MachO::CPU_TYPE_ARM64_32: |
| case MachO::CPU_TYPE_X86_64: |
| // We consider CPUSubtype only for the above 4 CPUTypes to match cctools' |
| // libtool behavior. |
| return static_cast<uint64_t>(CPUType) << 32 | CPUSubtype; |
| default: |
| return CPUType; |
| } |
| } |
| |
| // Check that a file's architecture [FileCPUType, FileCPUSubtype] |
| // matches the architecture specified under -arch_only flag. |
| static bool acceptFileArch(uint32_t FileCPUType, uint32_t FileCPUSubtype, |
| const Config &C) { |
| if (C.ArchCPUType != FileCPUType) |
| return false; |
| |
| switch (C.ArchCPUType) { |
| case MachO::CPU_TYPE_ARM: |
| case MachO::CPU_TYPE_ARM64_32: |
| case MachO::CPU_TYPE_X86_64: |
| return C.ArchCPUSubtype == FileCPUSubtype; |
| |
| case MachO::CPU_TYPE_ARM64: |
| if (C.ArchCPUSubtype == MachO::CPU_SUBTYPE_ARM64_ALL) |
| return FileCPUSubtype == MachO::CPU_SUBTYPE_ARM64_ALL || |
| FileCPUSubtype == MachO::CPU_SUBTYPE_ARM64_V8; |
| else |
| return C.ArchCPUSubtype == FileCPUSubtype; |
| |
| default: |
| return true; |
| } |
| } |
| |
| static Error verifyAndAddMachOObject(MembersPerArchitectureMap &Members, |
| NewArchiveMember Member, const Config &C) { |
| auto MBRef = Member.Buf->getMemBufferRef(); |
| Expected<std::unique_ptr<object::ObjectFile>> ObjOrErr = |
| object::ObjectFile::createObjectFile(MBRef); |
| |
| // Throw error if not a valid object file. |
| if (!ObjOrErr) |
| return createFileError(Member.MemberName, ObjOrErr.takeError()); |
| |
| // Throw error if not in Mach-O format. |
| if (!isa<object::MachOObjectFile>(**ObjOrErr)) |
| return createStringError(std::errc::invalid_argument, |
| "'%s': format not supported", |
| Member.MemberName.data()); |
| |
| auto *O = dyn_cast<MachOObjectFile>(ObjOrErr->get()); |
| uint32_t FileCPUType, FileCPUSubtype; |
| std::tie(FileCPUType, FileCPUSubtype) = MachO::getCPUTypeFromArchitecture( |
| MachO::getArchitectureFromName(O->getArchTriple().getArchName())); |
| |
| // If -arch_only is specified then skip this file if it doesn't match |
| // the architecture specified. |
| if (!ArchType.empty() && !acceptFileArch(FileCPUType, FileCPUSubtype, C)) { |
| return Error::success(); |
| } |
| |
| if (!NoWarningForNoSymbols && O->symbols().empty()) |
| WithColor::warning() << Member.MemberName + " has no symbols\n"; |
| |
| uint64_t FileCPUID = getCPUID(FileCPUType, FileCPUSubtype); |
| Members[FileCPUID].push_back(std::move(Member)); |
| return Error::success(); |
| } |
| |
| static Error verifyAndAddIRObject(MembersPerArchitectureMap &Members, |
| NewArchiveMember Member, const Config &C) { |
| auto MBRef = Member.Buf->getMemBufferRef(); |
| Expected<std::unique_ptr<object::IRObjectFile>> IROrErr = |
| object::IRObjectFile::create(MBRef, LLVMCtx); |
| |
| // Throw error if not a valid IR object file. |
| if (!IROrErr) |
| return createFileError(Member.MemberName, IROrErr.takeError()); |
| |
| Triple TT = Triple(IROrErr->get()->getTargetTriple()); |
| |
| Expected<uint32_t> FileCPUTypeOrErr = MachO::getCPUType(TT); |
| if (!FileCPUTypeOrErr) |
| return FileCPUTypeOrErr.takeError(); |
| |
| Expected<uint32_t> FileCPUSubTypeOrErr = MachO::getCPUSubType(TT); |
| if (!FileCPUSubTypeOrErr) |
| return FileCPUSubTypeOrErr.takeError(); |
| |
| // If -arch_only is specified then skip this file if it doesn't match |
| // the architecture specified. |
| if (!ArchType.empty() && |
| !acceptFileArch(*FileCPUTypeOrErr, *FileCPUSubTypeOrErr, C)) { |
| return Error::success(); |
| } |
| |
| uint64_t FileCPUID = getCPUID(*FileCPUTypeOrErr, *FileCPUSubTypeOrErr); |
| Members[FileCPUID].push_back(std::move(Member)); |
| return Error::success(); |
| } |
| |
| static Error addChildMember(MembersPerArchitectureMap &Members, |
| const object::Archive::Child &M, const Config &C) { |
| Expected<NewArchiveMember> NMOrErr = |
| NewArchiveMember::getOldMember(M, C.Deterministic); |
| if (!NMOrErr) |
| return NMOrErr.takeError(); |
| |
| file_magic Magic = identify_magic(NMOrErr->Buf->getBuffer()); |
| |
| if (Magic == file_magic::bitcode) |
| return verifyAndAddIRObject(Members, std::move(*NMOrErr), C); |
| |
| if (Error E = verifyAndAddMachOObject(Members, std::move(*NMOrErr), C)) |
| return E; |
| |
| return Error::success(); |
| } |
| |
| static Error processArchive(MembersPerArchitectureMap &Members, |
| object::Archive &Lib, StringRef FileName, |
| const Config &C) { |
| Error Err = Error::success(); |
| for (const object::Archive::Child &Child : Lib.children(Err)) |
| if (Error E = addChildMember(Members, Child, C)) |
| return createFileError(FileName, std::move(E)); |
| if (Err) |
| return createFileError(FileName, std::move(Err)); |
| |
| return Error::success(); |
| } |
| |
| static Error |
| addArchiveMembers(MembersPerArchitectureMap &Members, |
| std::vector<std::unique_ptr<MemoryBuffer>> &ArchiveBuffers, |
| NewArchiveMember NM, StringRef FileName, const Config &C) { |
| Expected<std::unique_ptr<Archive>> LibOrErr = |
| object::Archive::create(NM.Buf->getMemBufferRef()); |
| if (!LibOrErr) |
| return createFileError(FileName, LibOrErr.takeError()); |
| |
| if (Error E = processArchive(Members, **LibOrErr, FileName, C)) |
| return E; |
| |
| // Update vector ArchiveBuffers with the MemoryBuffers to transfer |
| // ownership. |
| ArchiveBuffers.push_back(std::move(NM.Buf)); |
| return Error::success(); |
| } |
| |
| static Error addUniversalMembers( |
| MembersPerArchitectureMap &Members, |
| std::vector<std::unique_ptr<MemoryBuffer>> &UniversalBuffers, |
| NewArchiveMember NM, StringRef FileName, const Config &C) { |
| Expected<std::unique_ptr<MachOUniversalBinary>> BinaryOrErr = |
| MachOUniversalBinary::create(NM.Buf->getMemBufferRef()); |
| if (!BinaryOrErr) |
| return createFileError(FileName, BinaryOrErr.takeError()); |
| |
| auto *UO = BinaryOrErr->get(); |
| for (const MachOUniversalBinary::ObjectForArch &O : UO->objects()) { |
| |
| Expected<std::unique_ptr<MachOObjectFile>> MachOObjOrErr = |
| O.getAsObjectFile(); |
| if (MachOObjOrErr) { |
| NewArchiveMember NewMember = |
| NewArchiveMember(MachOObjOrErr->get()->getMemoryBufferRef()); |
| NewMember.MemberName = sys::path::filename(NewMember.MemberName); |
| |
| if (Error E = verifyAndAddMachOObject(Members, std::move(NewMember), C)) |
| return E; |
| continue; |
| } |
| |
| Expected<std::unique_ptr<IRObjectFile>> IRObjectOrError = |
| O.getAsIRObject(LLVMCtx); |
| if (IRObjectOrError) { |
| // A universal file member can be a MachOObjectFile, an IRObject or an |
| // Archive. In case we can successfully cast the member as an IRObject, it |
| // is safe to throw away the error generated due to casting the object as |
| // a MachOObjectFile. |
| consumeError(MachOObjOrErr.takeError()); |
| |
| NewArchiveMember NewMember = |
| NewArchiveMember(IRObjectOrError->get()->getMemoryBufferRef()); |
| NewMember.MemberName = sys::path::filename(NewMember.MemberName); |
| |
| if (Error E = verifyAndAddIRObject(Members, std::move(NewMember), C)) |
| return E; |
| continue; |
| } |
| |
| Expected<std::unique_ptr<Archive>> ArchiveOrError = O.getAsArchive(); |
| if (ArchiveOrError) { |
| // A universal file member can be a MachOObjectFile, an IRObject or an |
| // Archive. In case we can successfully cast the member as an Archive, it |
| // is safe to throw away the error generated due to casting the object as |
| // a MachOObjectFile. |
| consumeError(MachOObjOrErr.takeError()); |
| consumeError(IRObjectOrError.takeError()); |
| |
| if (Error E = processArchive(Members, **ArchiveOrError, FileName, C)) |
| return E; |
| continue; |
| } |
| |
| Error CombinedError = joinErrors( |
| ArchiveOrError.takeError(), |
| joinErrors(IRObjectOrError.takeError(), MachOObjOrErr.takeError())); |
| return createFileError(FileName, std::move(CombinedError)); |
| } |
| |
| // Update vector UniversalBuffers with the MemoryBuffers to transfer |
| // ownership. |
| UniversalBuffers.push_back(std::move(NM.Buf)); |
| return Error::success(); |
| } |
| |
| static Error addMember(MembersPerArchitectureMap &Members, |
| std::vector<std::unique_ptr<MemoryBuffer>> &FileBuffers, |
| StringRef FileName, const Config &C) { |
| Expected<NewArchiveMember> NMOrErr = |
| NewArchiveMember::getFile(FileName, C.Deterministic); |
| if (!NMOrErr) |
| return createFileError(FileName, NMOrErr.takeError()); |
| |
| // For regular archives, use the basename of the object path for the member |
| // name. |
| NMOrErr->MemberName = sys::path::filename(NMOrErr->MemberName); |
| file_magic Magic = identify_magic(NMOrErr->Buf->getBuffer()); |
| |
| // Flatten archives. |
| if (Magic == file_magic::archive) |
| return addArchiveMembers(Members, FileBuffers, std::move(*NMOrErr), |
| FileName, C); |
| |
| // Flatten universal files. |
| if (Magic == file_magic::macho_universal_binary) |
| return addUniversalMembers(Members, FileBuffers, std::move(*NMOrErr), |
| FileName, C); |
| |
| // Bitcode files. |
| if (Magic == file_magic::bitcode) |
| return verifyAndAddIRObject(Members, std::move(*NMOrErr), C); |
| |
| if (Error E = verifyAndAddMachOObject(Members, std::move(*NMOrErr), C)) |
| return E; |
| return Error::success(); |
| } |
| |
| static Expected<SmallVector<Slice, 2>> |
| buildSlices(ArrayRef<OwningBinary<Archive>> OutputBinaries) { |
| SmallVector<Slice, 2> Slices; |
| |
| for (const auto &OB : OutputBinaries) { |
| const Archive &A = *OB.getBinary(); |
| Expected<Slice> ArchiveSlice = Slice::create(A, &LLVMCtx); |
| if (!ArchiveSlice) |
| return ArchiveSlice.takeError(); |
| Slices.push_back(*ArchiveSlice); |
| } |
| return Slices; |
| } |
| |
| static Error createStaticLibrary(const Config &C) { |
| MembersPerArchitectureMap NewMembers; |
| std::vector<std::unique_ptr<MemoryBuffer>> FileBuffers; |
| for (StringRef FileName : InputFiles) |
| if (Error E = addMember(NewMembers, FileBuffers, FileName, C)) |
| return E; |
| |
| if (!ArchType.empty()) { |
| uint64_t ArchCPUID = getCPUID(C.ArchCPUType, C.ArchCPUSubtype); |
| if (NewMembers.find(ArchCPUID) == NewMembers.end()) |
| return createStringError(std::errc::invalid_argument, |
| "no library created (no object files in input " |
| "files matching -arch_only %s)", |
| ArchType.c_str()); |
| } |
| |
| if (NewMembers.size() == 1) { |
| if (Error E = |
| writeArchive(OutputFile, NewMembers.begin()->second, |
| /*WriteSymtab=*/true, |
| /*Kind=*/object::Archive::K_DARWIN, C.Deterministic, |
| /*Thin=*/false)) |
| return E; |
| } else { |
| SmallVector<OwningBinary<Archive>, 2> OutputBinaries; |
| for (const std::pair<const uint64_t, std::vector<NewArchiveMember>> &M : |
| NewMembers) { |
| Expected<std::unique_ptr<MemoryBuffer>> OutputBufferOrErr = |
| writeArchiveToBuffer(M.second, |
| /*WriteSymtab=*/true, |
| /*Kind=*/object::Archive::K_DARWIN, |
| C.Deterministic, |
| /*Thin=*/false); |
| if (!OutputBufferOrErr) |
| return OutputBufferOrErr.takeError(); |
| std::unique_ptr<MemoryBuffer> &OutputBuffer = OutputBufferOrErr.get(); |
| |
| Expected<std::unique_ptr<Archive>> ArchiveOrError = |
| Archive::create(OutputBuffer->getMemBufferRef()); |
| if (!ArchiveOrError) |
| return ArchiveOrError.takeError(); |
| std::unique_ptr<Archive> &A = ArchiveOrError.get(); |
| |
| OutputBinaries.push_back( |
| OwningBinary<Archive>(std::move(A), std::move(OutputBuffer))); |
| } |
| |
| Expected<SmallVector<Slice, 2>> Slices = buildSlices(OutputBinaries); |
| if (!Slices) |
| return Slices.takeError(); |
| |
| llvm::stable_sort(*Slices); |
| if (Error E = writeUniversalBinary(*Slices, OutputFile)) |
| return E; |
| } |
| return Error::success(); |
| } |
| |
| static Expected<Config> parseCommandLine(int Argc, char **Argv) { |
| Config C; |
| cl::ParseCommandLineOptions(Argc, Argv, "llvm-libtool-darwin\n"); |
| |
| if (LibraryOperation == Operation::None) { |
| if (!VersionOption) { |
| std::string Error; |
| raw_string_ostream Stream(Error); |
| LibraryOperation.error("must be specified", "", Stream); |
| return createStringError(std::errc::invalid_argument, Error.c_str()); |
| } |
| return C; |
| } |
| |
| if (OutputFile.empty()) { |
| std::string Error; |
| raw_string_ostream Stream(Error); |
| OutputFile.error("must be specified", "o", Stream); |
| return createStringError(std::errc::invalid_argument, Error.c_str()); |
| } |
| |
| if (DeterministicOption && NonDeterministicOption) |
| return createStringError(std::errc::invalid_argument, |
| "cannot specify both -D and -U flags"); |
| else if (NonDeterministicOption) |
| C.Deterministic = false; |
| |
| if (!Libraries.empty()) |
| if (Error E = processCommandLineLibraries()) |
| return std::move(E); |
| |
| if (!FileList.empty()) |
| if (Error E = processFileList()) |
| return std::move(E); |
| |
| if (InputFiles.empty()) |
| return createStringError(std::errc::invalid_argument, |
| "no input files specified"); |
| |
| if (ArchType.getNumOccurrences()) { |
| if (Error E = validateArchitectureName(ArchType)) |
| return std::move(E); |
| |
| std::tie(C.ArchCPUType, C.ArchCPUSubtype) = |
| MachO::getCPUTypeFromArchitecture( |
| MachO::getArchitectureFromName(ArchType)); |
| } |
| |
| return C; |
| } |
| |
| int main(int Argc, char **Argv) { |
| InitLLVM X(Argc, Argv); |
| cl::HideUnrelatedOptions({&LibtoolCategory, &getColorCategory()}); |
| Expected<Config> ConfigOrErr = parseCommandLine(Argc, Argv); |
| if (!ConfigOrErr) { |
| WithColor::defaultErrorHandler(ConfigOrErr.takeError()); |
| return EXIT_FAILURE; |
| } |
| |
| if (VersionOption) |
| cl::PrintVersionMessage(); |
| |
| Config C = *ConfigOrErr; |
| switch (LibraryOperation) { |
| case Operation::None: |
| break; |
| case Operation::Static: |
| if (Error E = createStaticLibrary(C)) { |
| WithColor::defaultErrorHandler(std::move(E)); |
| return EXIT_FAILURE; |
| } |
| break; |
| } |
| } |