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