1 //===-- llvm-libtool-darwin.cpp - a tool for creating libraries -----------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 //
9 // A utility for creating static and dynamic libraries for Darwin.
10 //
11 //===----------------------------------------------------------------------===//
12 
13 #include "llvm/BinaryFormat/Magic.h"
14 #include "llvm/IR/LLVMContext.h"
15 #include "llvm/Object/ArchiveWriter.h"
16 #include "llvm/Object/IRObjectFile.h"
17 #include "llvm/Object/MachO.h"
18 #include "llvm/Object/MachOUniversal.h"
19 #include "llvm/Object/MachOUniversalWriter.h"
20 #include "llvm/Object/ObjectFile.h"
21 #include "llvm/Support/CommandLine.h"
22 #include "llvm/Support/InitLLVM.h"
23 #include "llvm/Support/LineIterator.h"
24 #include "llvm/Support/WithColor.h"
25 #include "llvm/TextAPI/MachO/Architecture.h"
26 #include <map>
27 
28 using namespace llvm;
29 using namespace llvm::object;
30 
31 static LLVMContext LLVMCtx;
32 
33 typedef std::map<uint64_t, std::vector<NewArchiveMember>>
34     MembersPerArchitectureMap;
35 
36 cl::OptionCategory LibtoolCategory("llvm-libtool-darwin Options");
37 
38 static cl::opt<std::string> OutputFile("o", cl::desc("Specify output filename"),
39                                        cl::value_desc("filename"),
40                                        cl::cat(LibtoolCategory));
41 
42 static cl::list<std::string> InputFiles(cl::Positional,
43                                         cl::desc("<input files>"),
44                                         cl::ZeroOrMore,
45                                         cl::cat(LibtoolCategory));
46 
47 static cl::opt<std::string> ArchType(
48     "arch_only", cl::desc("Specify architecture type for output library"),
49     cl::value_desc("arch_type"), cl::ZeroOrMore, cl::cat(LibtoolCategory));
50 
51 enum class Operation { None, Static };
52 
53 static cl::opt<Operation> LibraryOperation(
54     cl::desc("Library Type: "),
55     cl::values(
56         clEnumValN(Operation::Static, "static",
57                    "Produce a statically linked library from the input files")),
58     cl::init(Operation::None), cl::cat(LibtoolCategory));
59 
60 static cl::opt<bool> DeterministicOption(
61     "D", cl::desc("Use zero for timestamps and UIDs/GIDs (Default)"),
62     cl::init(false), cl::cat(LibtoolCategory));
63 
64 static cl::opt<bool>
65     NonDeterministicOption("U", cl::desc("Use actual timestamps and UIDs/GIDs"),
66                            cl::init(false), cl::cat(LibtoolCategory));
67 
68 static cl::opt<std::string>
69     FileList("filelist",
70              cl::desc("Pass in file containing a list of filenames"),
71              cl::value_desc("listfile[,dirname]"), cl::cat(LibtoolCategory));
72 
73 static cl::list<std::string> Libraries(
74     "l",
75     cl::desc(
76         "l<x> searches for the library libx.a in the library search path. If"
77         " the string 'x' ends with '.o', then the library 'x' is searched for"
78         " without prepending 'lib' or appending '.a'"),
79     cl::ZeroOrMore, cl::Prefix, cl::cat(LibtoolCategory));
80 
81 static cl::list<std::string> LibrarySearchDirs(
82     "L",
83     cl::desc(
84         "L<dir> adds <dir> to the list of directories in which to search for"
85         " libraries"),
86     cl::ZeroOrMore, cl::Prefix, cl::cat(LibtoolCategory));
87 
88 static cl::opt<bool>
89     VersionOption("V", cl::desc("Print the version number and exit"),
90                   cl::cat(LibtoolCategory));
91 
92 static const std::array<std::string, 3> StandardSearchDirs{
93     "/lib",
94     "/usr/lib",
95     "/usr/local/lib",
96 };
97 
98 struct Config {
99   bool Deterministic = true; // Updated by 'D' and 'U' modifiers.
100   uint32_t ArchCPUType;
101   uint32_t ArchCPUSubtype;
102 };
103 
searchForFile(const Twine & FileName)104 static Expected<std::string> searchForFile(const Twine &FileName) {
105 
106   auto FindLib =
107       [FileName](ArrayRef<std::string> SearchDirs) -> Optional<std::string> {
108     for (StringRef Dir : SearchDirs) {
109       SmallString<128> Path;
110       sys::path::append(Path, Dir, FileName);
111 
112       if (sys::fs::exists(Path))
113         return std::string(Path);
114     }
115     return None;
116   };
117 
118   Optional<std::string> Found = FindLib(LibrarySearchDirs);
119   if (!Found)
120     Found = FindLib(StandardSearchDirs);
121   if (Found)
122     return *Found;
123 
124   return createStringError(std::errc::invalid_argument,
125                            "cannot locate file '%s'", FileName.str().c_str());
126 }
127 
processCommandLineLibraries()128 static Error processCommandLineLibraries() {
129   for (StringRef BaseName : Libraries) {
130     Expected<std::string> FullPath = searchForFile(
131         BaseName.endswith(".o") ? BaseName.str() : "lib" + BaseName + ".a");
132     if (!FullPath)
133       return FullPath.takeError();
134     InputFiles.push_back(FullPath.get());
135   }
136 
137   return Error::success();
138 }
139 
processFileList()140 static Error processFileList() {
141   StringRef FileName, DirName;
142   std::tie(FileName, DirName) = StringRef(FileList).rsplit(",");
143 
144   ErrorOr<std::unique_ptr<MemoryBuffer>> FileOrErr =
145       MemoryBuffer::getFileOrSTDIN(FileName, /*FileSize=*/-1,
146                                    /*RequiresNullTerminator=*/false);
147   if (std::error_code EC = FileOrErr.getError())
148     return createFileError(FileName, errorCodeToError(EC));
149   const MemoryBuffer &Ref = *FileOrErr.get();
150 
151   line_iterator I(Ref, /*SkipBlanks=*/false);
152   if (I.is_at_eof())
153     return createStringError(std::errc::invalid_argument,
154                              "file list file: '%s' is empty",
155                              FileName.str().c_str());
156   for (; !I.is_at_eof(); ++I) {
157     StringRef Line = *I;
158     if (Line.empty())
159       return createStringError(std::errc::invalid_argument,
160                                "file list file: '%s': filename cannot be empty",
161                                FileName.str().c_str());
162 
163     SmallString<128> Path;
164     if (!DirName.empty())
165       sys::path::append(Path, DirName, Line);
166     else
167       sys::path::append(Path, Line);
168     InputFiles.push_back(static_cast<std::string>(Path));
169   }
170   return Error::success();
171 }
172 
validateArchitectureName(StringRef ArchitectureName)173 static Error validateArchitectureName(StringRef ArchitectureName) {
174   if (!MachOObjectFile::isValidArch(ArchitectureName)) {
175     std::string Buf;
176     raw_string_ostream OS(Buf);
177     for (StringRef Arch : MachOObjectFile::getValidArchs())
178       OS << Arch << " ";
179 
180     return createStringError(
181         std::errc::invalid_argument,
182         "invalid architecture '%s': valid architecture names are %s",
183         ArchitectureName.str().c_str(), OS.str().c_str());
184   }
185   return Error::success();
186 }
187 
getCPUID(uint32_t CPUType,uint32_t CPUSubtype)188 static uint64_t getCPUID(uint32_t CPUType, uint32_t CPUSubtype) {
189   switch (CPUType) {
190   case MachO::CPU_TYPE_ARM:
191   case MachO::CPU_TYPE_ARM64:
192   case MachO::CPU_TYPE_ARM64_32:
193   case MachO::CPU_TYPE_X86_64:
194     // We consider CPUSubtype only for the above 4 CPUTypes to match cctools'
195     // libtool behavior.
196     return static_cast<uint64_t>(CPUType) << 32 | CPUSubtype;
197   default:
198     return CPUType;
199   }
200 }
201 
202 // Check that a file's architecture [FileCPUType, FileCPUSubtype]
203 // matches the architecture specified under -arch_only flag.
acceptFileArch(uint32_t FileCPUType,uint32_t FileCPUSubtype,const Config & C)204 static bool acceptFileArch(uint32_t FileCPUType, uint32_t FileCPUSubtype,
205                            const Config &C) {
206   if (C.ArchCPUType != FileCPUType)
207     return false;
208 
209   switch (C.ArchCPUType) {
210   case MachO::CPU_TYPE_ARM:
211   case MachO::CPU_TYPE_ARM64_32:
212   case MachO::CPU_TYPE_X86_64:
213     return C.ArchCPUSubtype == FileCPUSubtype;
214 
215   case MachO::CPU_TYPE_ARM64:
216     if (C.ArchCPUSubtype == MachO::CPU_SUBTYPE_ARM64_ALL)
217       return FileCPUSubtype == MachO::CPU_SUBTYPE_ARM64_ALL ||
218              FileCPUSubtype == MachO::CPU_SUBTYPE_ARM64_V8;
219     else
220       return C.ArchCPUSubtype == FileCPUSubtype;
221 
222   default:
223     return true;
224   }
225 }
226 
verifyAndAddMachOObject(MembersPerArchitectureMap & Members,NewArchiveMember Member,const Config & C)227 static Error verifyAndAddMachOObject(MembersPerArchitectureMap &Members,
228                                      NewArchiveMember Member, const Config &C) {
229   auto MBRef = Member.Buf->getMemBufferRef();
230   Expected<std::unique_ptr<object::ObjectFile>> ObjOrErr =
231       object::ObjectFile::createObjectFile(MBRef);
232 
233   // Throw error if not a valid object file.
234   if (!ObjOrErr)
235     return createFileError(Member.MemberName, ObjOrErr.takeError());
236 
237   // Throw error if not in Mach-O format.
238   if (!isa<object::MachOObjectFile>(**ObjOrErr))
239     return createStringError(std::errc::invalid_argument,
240                              "'%s': format not supported",
241                              Member.MemberName.data());
242 
243   auto *O = dyn_cast<MachOObjectFile>(ObjOrErr->get());
244   uint32_t FileCPUType, FileCPUSubtype;
245   std::tie(FileCPUType, FileCPUSubtype) = MachO::getCPUTypeFromArchitecture(
246       MachO::getArchitectureFromName(O->getArchTriple().getArchName()));
247 
248   // If -arch_only is specified then skip this file if it doesn't match
249   // the architecture specified.
250   if (!ArchType.empty() && !acceptFileArch(FileCPUType, FileCPUSubtype, C)) {
251     return Error::success();
252   }
253 
254   uint64_t FileCPUID = getCPUID(FileCPUType, FileCPUSubtype);
255   Members[FileCPUID].push_back(std::move(Member));
256   return Error::success();
257 }
258 
verifyAndAddIRObject(MembersPerArchitectureMap & Members,NewArchiveMember Member,const Config & C)259 static Error verifyAndAddIRObject(MembersPerArchitectureMap &Members,
260                                   NewArchiveMember Member, const Config &C) {
261   auto MBRef = Member.Buf->getMemBufferRef();
262   Expected<std::unique_ptr<object::IRObjectFile>> IROrErr =
263       object::IRObjectFile::create(MBRef, LLVMCtx);
264 
265   // Throw error if not a valid IR object file.
266   if (!IROrErr)
267     return createFileError(Member.MemberName, IROrErr.takeError());
268 
269   Triple TT = Triple(IROrErr->get()->getTargetTriple());
270 
271   Expected<uint32_t> FileCPUTypeOrErr = MachO::getCPUType(TT);
272   if (!FileCPUTypeOrErr)
273     return FileCPUTypeOrErr.takeError();
274 
275   Expected<uint32_t> FileCPUSubTypeOrErr = MachO::getCPUSubType(TT);
276   if (!FileCPUSubTypeOrErr)
277     return FileCPUSubTypeOrErr.takeError();
278 
279   // If -arch_only is specified then skip this file if it doesn't match
280   // the architecture specified.
281   if (!ArchType.empty() &&
282       !acceptFileArch(*FileCPUTypeOrErr, *FileCPUSubTypeOrErr, C)) {
283     return Error::success();
284   }
285 
286   uint64_t FileCPUID = getCPUID(*FileCPUTypeOrErr, *FileCPUSubTypeOrErr);
287   Members[FileCPUID].push_back(std::move(Member));
288   return Error::success();
289 }
290 
addChildMember(MembersPerArchitectureMap & Members,const object::Archive::Child & M,const Config & C)291 static Error addChildMember(MembersPerArchitectureMap &Members,
292                             const object::Archive::Child &M, const Config &C) {
293   Expected<NewArchiveMember> NMOrErr =
294       NewArchiveMember::getOldMember(M, C.Deterministic);
295   if (!NMOrErr)
296     return NMOrErr.takeError();
297 
298   file_magic Magic = identify_magic(NMOrErr->Buf->getBuffer());
299 
300   if (Magic == file_magic::bitcode)
301     return verifyAndAddIRObject(Members, std::move(*NMOrErr), C);
302 
303   if (Error E = verifyAndAddMachOObject(Members, std::move(*NMOrErr), C))
304     return E;
305 
306   return Error::success();
307 }
308 
processArchive(MembersPerArchitectureMap & Members,object::Archive & Lib,StringRef FileName,const Config & C)309 static Error processArchive(MembersPerArchitectureMap &Members,
310                             object::Archive &Lib, StringRef FileName,
311                             const Config &C) {
312   Error Err = Error::success();
313   for (const object::Archive::Child &Child : Lib.children(Err))
314     if (Error E = addChildMember(Members, Child, C))
315       return createFileError(FileName, std::move(E));
316   if (Err)
317     return createFileError(FileName, std::move(Err));
318 
319   return Error::success();
320 }
321 
322 static Error
addArchiveMembers(MembersPerArchitectureMap & Members,std::vector<std::unique_ptr<MemoryBuffer>> & ArchiveBuffers,NewArchiveMember NM,StringRef FileName,const Config & C)323 addArchiveMembers(MembersPerArchitectureMap &Members,
324                   std::vector<std::unique_ptr<MemoryBuffer>> &ArchiveBuffers,
325                   NewArchiveMember NM, StringRef FileName, const Config &C) {
326   Expected<std::unique_ptr<Archive>> LibOrErr =
327       object::Archive::create(NM.Buf->getMemBufferRef());
328   if (!LibOrErr)
329     return createFileError(FileName, LibOrErr.takeError());
330 
331   if (Error E = processArchive(Members, **LibOrErr, FileName, C))
332     return E;
333 
334   // Update vector ArchiveBuffers with the MemoryBuffers to transfer
335   // ownership.
336   ArchiveBuffers.push_back(std::move(NM.Buf));
337   return Error::success();
338 }
339 
addUniversalMembers(MembersPerArchitectureMap & Members,std::vector<std::unique_ptr<MemoryBuffer>> & UniversalBuffers,NewArchiveMember NM,StringRef FileName,const Config & C)340 static Error addUniversalMembers(
341     MembersPerArchitectureMap &Members,
342     std::vector<std::unique_ptr<MemoryBuffer>> &UniversalBuffers,
343     NewArchiveMember NM, StringRef FileName, const Config &C) {
344   Expected<std::unique_ptr<MachOUniversalBinary>> BinaryOrErr =
345       MachOUniversalBinary::create(NM.Buf->getMemBufferRef());
346   if (!BinaryOrErr)
347     return createFileError(FileName, BinaryOrErr.takeError());
348 
349   auto *UO = BinaryOrErr->get();
350   for (const MachOUniversalBinary::ObjectForArch &O : UO->objects()) {
351 
352     Expected<std::unique_ptr<MachOObjectFile>> MachOObjOrErr =
353         O.getAsObjectFile();
354     if (MachOObjOrErr) {
355       NewArchiveMember NewMember =
356           NewArchiveMember(MachOObjOrErr->get()->getMemoryBufferRef());
357       NewMember.MemberName = sys::path::filename(NewMember.MemberName);
358 
359       if (Error E = verifyAndAddMachOObject(Members, std::move(NewMember), C))
360         return E;
361       continue;
362     }
363 
364     Expected<std::unique_ptr<IRObjectFile>> IRObjectOrError =
365         O.getAsIRObject(LLVMCtx);
366     if (IRObjectOrError) {
367       // A universal file member can be a MachOObjectFile, an IRObject or an
368       // Archive. In case we can successfully cast the member as an IRObject, it
369       // is safe to throw away the error generated due to casting the object as
370       // a MachOObjectFile.
371       consumeError(MachOObjOrErr.takeError());
372 
373       NewArchiveMember NewMember =
374           NewArchiveMember(IRObjectOrError->get()->getMemoryBufferRef());
375       NewMember.MemberName = sys::path::filename(NewMember.MemberName);
376 
377       if (Error E = verifyAndAddIRObject(Members, std::move(NewMember), C))
378         return E;
379       continue;
380     }
381 
382     Expected<std::unique_ptr<Archive>> ArchiveOrError = O.getAsArchive();
383     if (ArchiveOrError) {
384       // A universal file member can be a MachOObjectFile, an IRObject or an
385       // Archive. In case we can successfully cast the member as an Archive, it
386       // is safe to throw away the error generated due to casting the object as
387       // a MachOObjectFile.
388       consumeError(MachOObjOrErr.takeError());
389       consumeError(IRObjectOrError.takeError());
390 
391       if (Error E = processArchive(Members, **ArchiveOrError, FileName, C))
392         return E;
393       continue;
394     }
395 
396     Error CombinedError = joinErrors(
397         ArchiveOrError.takeError(),
398         joinErrors(IRObjectOrError.takeError(), MachOObjOrErr.takeError()));
399     return createFileError(FileName, std::move(CombinedError));
400   }
401 
402   // Update vector UniversalBuffers with the MemoryBuffers to transfer
403   // ownership.
404   UniversalBuffers.push_back(std::move(NM.Buf));
405   return Error::success();
406 }
407 
addMember(MembersPerArchitectureMap & Members,std::vector<std::unique_ptr<MemoryBuffer>> & FileBuffers,StringRef FileName,const Config & C)408 static Error addMember(MembersPerArchitectureMap &Members,
409                        std::vector<std::unique_ptr<MemoryBuffer>> &FileBuffers,
410                        StringRef FileName, const Config &C) {
411   Expected<NewArchiveMember> NMOrErr =
412       NewArchiveMember::getFile(FileName, C.Deterministic);
413   if (!NMOrErr)
414     return createFileError(FileName, NMOrErr.takeError());
415 
416   // For regular archives, use the basename of the object path for the member
417   // name.
418   NMOrErr->MemberName = sys::path::filename(NMOrErr->MemberName);
419   file_magic Magic = identify_magic(NMOrErr->Buf->getBuffer());
420 
421   // Flatten archives.
422   if (Magic == file_magic::archive)
423     return addArchiveMembers(Members, FileBuffers, std::move(*NMOrErr),
424                              FileName, C);
425 
426   // Flatten universal files.
427   if (Magic == file_magic::macho_universal_binary)
428     return addUniversalMembers(Members, FileBuffers, std::move(*NMOrErr),
429                                FileName, C);
430 
431   // Bitcode files.
432   if (Magic == file_magic::bitcode)
433     return verifyAndAddIRObject(Members, std::move(*NMOrErr), C);
434 
435   if (Error E = verifyAndAddMachOObject(Members, std::move(*NMOrErr), C))
436     return E;
437   return Error::success();
438 }
439 
440 static Expected<SmallVector<Slice, 2>>
buildSlices(ArrayRef<OwningBinary<Archive>> OutputBinaries)441 buildSlices(ArrayRef<OwningBinary<Archive>> OutputBinaries) {
442   SmallVector<Slice, 2> Slices;
443 
444   for (const auto &OB : OutputBinaries) {
445     const Archive &A = *OB.getBinary();
446     Expected<Slice> ArchiveSlice = Slice::create(A, &LLVMCtx);
447     if (!ArchiveSlice)
448       return ArchiveSlice.takeError();
449     Slices.push_back(*ArchiveSlice);
450   }
451   return Slices;
452 }
453 
createStaticLibrary(const Config & C)454 static Error createStaticLibrary(const Config &C) {
455   MembersPerArchitectureMap NewMembers;
456   std::vector<std::unique_ptr<MemoryBuffer>> FileBuffers;
457   for (StringRef FileName : InputFiles)
458     if (Error E = addMember(NewMembers, FileBuffers, FileName, C))
459       return E;
460 
461   if (!ArchType.empty()) {
462     uint64_t ArchCPUID = getCPUID(C.ArchCPUType, C.ArchCPUSubtype);
463     if (NewMembers.find(ArchCPUID) == NewMembers.end())
464       return createStringError(std::errc::invalid_argument,
465                                "no library created (no object files in input "
466                                "files matching -arch_only %s)",
467                                ArchType.c_str());
468   }
469 
470   if (NewMembers.size() == 1) {
471     if (Error E =
472             writeArchive(OutputFile, NewMembers.begin()->second,
473                          /*WriteSymtab=*/true,
474                          /*Kind=*/object::Archive::K_DARWIN, C.Deterministic,
475                          /*Thin=*/false))
476       return E;
477   } else {
478     SmallVector<OwningBinary<Archive>, 2> OutputBinaries;
479     for (const std::pair<const uint64_t, std::vector<NewArchiveMember>> &M :
480          NewMembers) {
481       Expected<std::unique_ptr<MemoryBuffer>> OutputBufferOrErr =
482           writeArchiveToBuffer(M.second,
483                                /*WriteSymtab=*/true,
484                                /*Kind=*/object::Archive::K_DARWIN,
485                                C.Deterministic,
486                                /*Thin=*/false);
487       if (!OutputBufferOrErr)
488         return OutputBufferOrErr.takeError();
489       std::unique_ptr<MemoryBuffer> &OutputBuffer = OutputBufferOrErr.get();
490 
491       Expected<std::unique_ptr<Archive>> ArchiveOrError =
492           Archive::create(OutputBuffer->getMemBufferRef());
493       if (!ArchiveOrError)
494         return ArchiveOrError.takeError();
495       std::unique_ptr<Archive> &A = ArchiveOrError.get();
496 
497       OutputBinaries.push_back(
498           OwningBinary<Archive>(std::move(A), std::move(OutputBuffer)));
499     }
500 
501     Expected<SmallVector<Slice, 2>> Slices = buildSlices(OutputBinaries);
502     if (!Slices)
503       return Slices.takeError();
504 
505     llvm::stable_sort(*Slices);
506     if (Error E = writeUniversalBinary(*Slices, OutputFile))
507       return E;
508   }
509   return Error::success();
510 }
511 
parseCommandLine(int Argc,char ** Argv)512 static Expected<Config> parseCommandLine(int Argc, char **Argv) {
513   Config C;
514   cl::ParseCommandLineOptions(Argc, Argv, "llvm-libtool-darwin\n");
515 
516   if (LibraryOperation == Operation::None) {
517     if (!VersionOption) {
518       std::string Error;
519       raw_string_ostream Stream(Error);
520       LibraryOperation.error("must be specified", "", Stream);
521       return createStringError(std::errc::invalid_argument, Error.c_str());
522     }
523     return C;
524   }
525 
526   if (OutputFile.empty()) {
527     std::string Error;
528     raw_string_ostream Stream(Error);
529     OutputFile.error("must be specified", "o", Stream);
530     return createStringError(std::errc::invalid_argument, Error.c_str());
531   }
532 
533   if (DeterministicOption && NonDeterministicOption)
534     return createStringError(std::errc::invalid_argument,
535                              "cannot specify both -D and -U flags");
536   else if (NonDeterministicOption)
537     C.Deterministic = false;
538 
539   if (!Libraries.empty())
540     if (Error E = processCommandLineLibraries())
541       return std::move(E);
542 
543   if (!FileList.empty())
544     if (Error E = processFileList())
545       return std::move(E);
546 
547   if (InputFiles.empty())
548     return createStringError(std::errc::invalid_argument,
549                              "no input files specified");
550 
551   if (ArchType.getNumOccurrences()) {
552     if (Error E = validateArchitectureName(ArchType))
553       return std::move(E);
554 
555     std::tie(C.ArchCPUType, C.ArchCPUSubtype) =
556         MachO::getCPUTypeFromArchitecture(
557             MachO::getArchitectureFromName(ArchType));
558   }
559 
560   return C;
561 }
562 
main(int Argc,char ** Argv)563 int main(int Argc, char **Argv) {
564   InitLLVM X(Argc, Argv);
565   cl::HideUnrelatedOptions({&LibtoolCategory, &ColorCategory});
566   Expected<Config> ConfigOrErr = parseCommandLine(Argc, Argv);
567   if (!ConfigOrErr) {
568     WithColor::defaultErrorHandler(ConfigOrErr.takeError());
569     return EXIT_FAILURE;
570   }
571 
572   if (VersionOption)
573     cl::PrintVersionMessage();
574 
575   Config C = *ConfigOrErr;
576   switch (LibraryOperation) {
577   case Operation::None:
578     break;
579   case Operation::Static:
580     if (Error E = createStaticLibrary(C)) {
581       WithColor::defaultErrorHandler(std::move(E));
582       return EXIT_FAILURE;
583     }
584     break;
585   }
586 }
587