dyld-832.7.1.tar.gz
[apple/dyld.git] / dyld3 / shared-cache / dyld_shared_cache_builder.mm
1 /* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil -*-
2  *
3  * Copyright (c) 2016 Apple Inc. All rights reserved.
4  *
5  * @APPLE_LICENSE_HEADER_START@
6  *
7  * This file contains Original Code and/or Modifications of Original Code
8  * as defined in and that are subject to the Apple Public Source License
9  * Version 2.0 (the 'License'). You may not use this file except in
10  * compliance with the License. Please obtain a copy of the License at
11  * http://www.opensource.apple.com/apsl/ and read it before using this
12  * file.
13  *
14  * The Original Code and all software distributed under the License are
15  * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
16  * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
17  * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
19  * Please see the License for the specific language governing rights and
20  * limitations under the License.
21  *
22  * @APPLE_LICENSE_HEADER_END@
23  */
24
25 #include <sys/types.h>
26 #include <sys/stat.h>
27 #include <sys/mman.h>
28 #include <sys/resource.h>
29 #include <mach/mach.h>
30 #include <mach/mach_time.h>
31 #include <limits.h>
32 #include <stdarg.h>
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <math.h>
36 #include <fcntl.h>
37 #include <dlfcn.h>
38 #include <signal.h>
39 #include <errno.h>
40 #include <sys/uio.h>
41 #include <unistd.h>
42 #include <sys/param.h>
43 #include <sys/sysctl.h>
44 #include <sys/resource.h>
45 #include <dirent.h>
46 #include <libgen.h>
47 #include <pthread.h>
48 #include <fts.h>
49
50 #include <vector>
51 #include <array>
52 #include <list>
53 #include <set>
54 #include <map>
55 #include <unordered_set>
56 #include <algorithm>
57 #include <fstream>
58 #include <regex>
59
60 #include <spawn.h>
61
62 #include <Bom/Bom.h>
63 #include <Foundation/NSData.h>
64 #include <Foundation/NSDictionary.h>
65 #include <Foundation/NSPropertyList.h>
66 #include <Foundation/NSString.h>
67
68 #include "Diagnostics.h"
69 #include "DyldSharedCache.h"
70 #include "FileUtils.h"
71 #include "JSONReader.h"
72 #include "JSONWriter.h"
73 #include "StringUtils.h"
74 #include "mrm_shared_cache_builder.h"
75
76 #if !__has_feature(objc_arc)
77 #error The use of libdispatch in this files requires it to be compiled with ARC in order to avoid leaks
78 #endif
79
80 extern char** environ;
81
82 static dispatch_queue_t build_queue;
83
84 int runCommandAndWait(Diagnostics& diags, const char* args[])
85 {
86     pid_t pid;
87     int   status;
88     int   res = posix_spawn(&pid, args[0], nullptr, nullptr, (char**)args, environ);
89     if (res != 0)
90         diags.error("Failed to spawn %s: %s (%d)", args[0], strerror(res), res);
91
92     do {
93         res = waitpid(pid, &status, 0);
94     } while (res == -1 && errno == EINTR);
95     if (res != -1) {
96         if (WIFEXITED(status)) {
97             res = WEXITSTATUS(status);
98         } else {
99             res = -1;
100         }
101     }
102
103     return res;
104 }
105
106 void processRoots(Diagnostics& diags, std::list<std::string>& roots, const char *tempRootsDir)
107 {
108     std::list<std::string>  processedRoots;
109     struct stat             sb;
110     int                     res = 0;
111     const char*             args[8];
112
113     for (const auto& root : roots) {
114         res = stat(root.c_str(), &sb);
115
116         if (res == 0 && S_ISDIR(sb.st_mode)) {
117             processedRoots.push_back(root);
118             continue;
119         }
120
121         char tempRootDir[MAXPATHLEN];
122         strlcpy(tempRootDir, tempRootsDir, MAXPATHLEN);
123         strlcat(tempRootDir, "/XXXXXXXX", MAXPATHLEN);
124         mkdtemp(tempRootDir);
125
126         if (endsWith(root, ".cpio") || endsWith(root, ".cpio.gz") || endsWith(root, ".cpgz") || endsWith(root, ".cpio.bz2") || endsWith(root, ".cpbz2") || endsWith(root, ".pax") || endsWith(root, ".pax.gz") || endsWith(root, ".pgz") || endsWith(root, ".pax.bz2") || endsWith(root, ".pbz2")) {
127             args[0] = (char*)"/usr/bin/ditto";
128             args[1] = (char*)"-x";
129             args[2] = (char*)root.c_str();
130             args[3] = tempRootDir;
131             args[4] = nullptr;
132         } else if (endsWith(root, ".tar")) {
133             args[0] = (char*)"/usr/bin/tar";
134             args[1] = (char*)"xf";
135             args[2] = (char*)root.c_str();
136             args[3] = (char*)"-C";
137             args[4] = tempRootDir;
138             args[5] = nullptr;
139         } else if (endsWith(root, ".tar.gz") || endsWith(root, ".tgz")) {
140             args[0] = (char*)"/usr/bin/tar";
141             args[1] = (char*)"xzf";
142             args[2] = (char*)root.c_str();
143             args[3] = (char*)"-C";
144             args[4] = tempRootDir;
145             args[5] = nullptr;
146         } else if (endsWith(root, ".tar.bz2")
147             || endsWith(root, ".tbz2")
148             || endsWith(root, ".tbz")) {
149             args[0] = (char*)"/usr/bin/tar";
150             args[1] = (char*)"xjf";
151             args[2] = (char*)root.c_str();
152             args[3] = (char*)"-C";
153             args[4] = tempRootDir;
154             args[5] = nullptr;
155         } else if (endsWith(root, ".zip")) {
156             args[0] = (char*)"/usr/bin/ditto";
157             args[1] = (char*)"-xk";
158             args[2] = (char*)root.c_str();
159             args[3] = tempRootDir;
160             args[4] = nullptr;
161         } else {
162             diags.error("unknown archive type: %s", root.c_str());
163             continue;
164         }
165
166         if (res != runCommandAndWait(diags, args)) {
167             fprintf(stderr, "Could not expand archive %s: %s (%d)", root.c_str(), strerror(res), res);
168             exit(-1);
169         }
170         for (auto& existingRoot : processedRoots) {
171             if (existingRoot == tempRootDir)
172                 return;
173         }
174
175         processedRoots.push_back(tempRootDir);
176     }
177
178     roots = processedRoots;
179 }
180
181 void writeRootList(const std::string& dstRoot, const std::list<std::string>& roots)
182 {
183     if (roots.size() == 0)
184         return;
185
186     std::string rootFile = dstRoot + "/roots.txt";
187     FILE*       froots = ::fopen(rootFile.c_str(), "w");
188     if (froots == NULL)
189         return;
190
191     for (auto& root : roots) {
192         fprintf(froots, "%s\n", root.c_str());
193     }
194
195     ::fclose(froots);
196 }
197
198 struct FilteredCopyOptions {
199     Diagnostics*            diags               = nullptr;
200     std::set<std::string>*  cachePaths          = nullptr;
201     std::set<std::string>*  dylibsFoundInRoots  = nullptr;
202 };
203
204 BOMCopierCopyOperation filteredCopyIncludingPaths(BOMCopier copier, const char* path, BOMFSObjType type, off_t size)
205 {
206     std::string absolutePath = &path[1];
207     const FilteredCopyOptions *userData = (const FilteredCopyOptions*)BOMCopierUserData(copier);
208
209     // Don't copy from the artifact if the dylib is actally in a -root
210     if ( userData->dylibsFoundInRoots->count(absolutePath) != 0 ) {
211         userData->diags->verbose("Skipping copying dylib from shared cache artifact as it is in a -root: '%s'\n", absolutePath.c_str());
212         return BOMCopierSkipFile;
213     }
214
215     for (const std::string& cachePath : *userData->cachePaths) {
216         if (startsWith(cachePath, absolutePath)) {
217             userData->diags->verbose("Copying dylib from shared cache artifact: '%s'\n", absolutePath.c_str());
218             return BOMCopierContinue;
219         }
220     }
221     if (userData->cachePaths->count(absolutePath)) {
222         userData->diags->verbose("Copying dylib from shared cache artifact: '%s'\n", absolutePath.c_str());
223         return BOMCopierContinue;
224     }
225     return BOMCopierSkipFile;
226 }
227
228 static Disposition stringToDisposition(Diagnostics& diags, const std::string& str) {
229     if (diags.hasError())
230         return Unknown;
231     if (str == "Unknown")
232         return Unknown;
233     if (str == "InternalDevelopment")
234         return InternalDevelopment;
235     if (str == "Customer")
236         return Customer;
237     if (str == "InternalMinDevelopment")
238         return InternalMinDevelopment;
239     return Unknown;
240 }
241
242 static Platform stringToPlatform(Diagnostics& diags, const std::string& str) {
243     if (diags.hasError())
244         return unknown;
245     if (str == "unknown")
246         return unknown;
247     if ( (str == "macOS") || (str == "osx") )
248         return macOS;
249     if (str == "iOS")
250         return iOS;
251     if (str == "tvOS")
252         return tvOS;
253     if (str == "watchOS")
254         return watchOS;
255     if (str == "bridgeOS")
256         return bridgeOS;
257     if (str == "iOSMac")
258         return iOSMac;
259     if (str == "UIKitForMac")
260         return iOSMac;
261     if (str == "iOS_simulator")
262         return iOS_simulator;
263     if (str == "tvOS_simulator")
264         return tvOS_simulator;
265     if (str == "watchOS_simulator")
266         return watchOS_simulator;
267     return unknown;
268 }
269
270 static FileFlags stringToFileFlags(Diagnostics& diags, const std::string& str) {
271     if (diags.hasError())
272         return NoFlags;
273     if (str == "NoFlags")
274         return NoFlags;
275     if (str == "MustBeInCache")
276         return MustBeInCache;
277     if (str == "ShouldBeExcludedFromCacheIfUnusedLeaf")
278         return ShouldBeExcludedFromCacheIfUnusedLeaf;
279     if (str == "RequiredClosure")
280         return RequiredClosure;
281     if (str == "DylibOrderFile")
282         return DylibOrderFile;
283     if (str == "DirtyDataOrderFile")
284         return DirtyDataOrderFile;
285     if (str == "ObjCOptimizationsFile")
286         return ObjCOptimizationsFile;
287     return NoFlags;
288 }
289
290 struct SharedCacheBuilderOptions {
291     Diagnostics                 diags;
292     std::list<std::string>      roots;
293     std::string                 dylibCacheDir;
294     std::string                 artifactDir;
295     std::string                 release;
296     bool                        emitDevCaches = true;
297     bool                        emitCustomerCaches = true;
298     bool                        emitElidedDylibs = true;
299     bool                        listConfigs = false;
300     bool                        copyRoots = false;
301     bool                        debug = false;
302     bool                        useMRM = false;
303     std::string                 dstRoot;
304     std::string                 emitJSONPath;
305     std::string                 buildAllPath;
306     std::string                 resultPath;
307     std::string                 baselineDifferenceResultPath;
308     std::list<std::string>      baselineCacheMapPaths;
309     bool                        baselineCopyRoots = false;
310     bool                        emitMapFiles = false;
311     std::set<std::string>       cmdLineArchs;
312 };
313
314 static void loadMRMFiles(Diagnostics& diags,
315                          MRMSharedCacheBuilder* sharedCacheBuilder,
316                          const std::vector<std::tuple<std::string, std::string, FileFlags>>& inputFiles,
317                          std::vector<std::pair<const void*, size_t>>& mappedFiles,
318                          const std::set<std::string>& baselineCacheFiles) {
319
320     for (const std::tuple<std::string, std::string, FileFlags>& inputFile : inputFiles) {
321         const std::string& buildPath   = std::get<0>(inputFile);
322         const std::string& runtimePath = std::get<1>(inputFile);
323         FileFlags   fileFlags   = std::get<2>(inputFile);
324
325         struct stat stat_buf;
326         int fd = ::open(buildPath.c_str(), O_RDONLY, 0);
327         if (fd == -1) {
328             if (baselineCacheFiles.count(runtimePath)) {
329                 diags.error("can't open file '%s', errno=%d\n", buildPath.c_str(), errno);
330                 return;
331             } else {
332                 diags.verbose("can't open file '%s', errno=%d\n", buildPath.c_str(), errno);
333                 continue;
334             }
335         }
336
337         if (fstat(fd, &stat_buf) == -1) {
338             if (baselineCacheFiles.count(runtimePath)) {
339                 diags.error("can't stat open file '%s', errno=%d\n", buildPath.c_str(), errno);
340                 ::close(fd);
341                 return;
342             } else {
343                 diags.verbose("can't stat open file '%s', errno=%d\n", buildPath.c_str(), errno);
344                 ::close(fd);
345                 continue;
346             }
347         }
348
349         const void* buffer = mmap(NULL, (size_t)stat_buf.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
350         if (buffer == MAP_FAILED) {
351             diags.error("mmap() for file at %s failed, errno=%d\n", buildPath.c_str(), errno);
352             ::close(fd);
353         }
354         ::close(fd);
355
356         mappedFiles.emplace_back(buffer, (size_t)stat_buf.st_size);
357
358         addFile(sharedCacheBuilder, runtimePath.c_str(), (uint8_t*)buffer, (size_t)stat_buf.st_size, fileFlags);
359     }
360 }
361
362 static void unloadMRMFiles(std::vector<std::pair<const void*, size_t>>& mappedFiles) {
363     for (auto mappedFile : mappedFiles)
364         ::munmap((void*)mappedFile.first, mappedFile.second);
365 }
366
367 static ssize_t write64(int fildes, const void *buf, size_t nbyte)
368 {
369     unsigned char* uchars = (unsigned char*)buf;
370     ssize_t total = 0;
371
372     while (nbyte)
373     {
374         /*
375          * If we were writing socket- or stream-safe code we'd chuck the
376          * entire buf to write(2) and then gracefully re-request bytes that
377          * didn't get written. But write(2) will return EINVAL if you ask it to
378          * write more than 2^31-1 bytes. So instead we actually need to throttle
379          * the input to write.
380          *
381          * Historically code using write(2) to write to disk will assert that
382          * that all of the requested bytes were written. It seems harmless to
383          * re-request bytes as one does when writing to streams, with the
384          * compromise that we will return immediately when write(2) returns 0
385          * bytes written.
386          */
387         size_t limit = 0x7FFFFFFF;
388         size_t towrite = nbyte < limit ? nbyte : limit;
389         ssize_t wrote = write(fildes, uchars, towrite);
390         if (-1 == wrote)
391         {
392             return -1;
393         }
394         else if (0 == wrote)
395         {
396             break;
397         }
398         else
399         {
400             nbyte -= wrote;
401             uchars += wrote;
402             total += wrote;
403         }
404     }
405
406     return total;
407 }
408
409 static bool writeMRMResults(bool cacheBuildSuccess, MRMSharedCacheBuilder* sharedCacheBuilder, const SharedCacheBuilderOptions& options) {
410     if (!cacheBuildSuccess) {
411         uint64_t errorCount = 0;
412         if (const char* const* errors = getErrors(sharedCacheBuilder, &errorCount)) {
413             for (uint64_t i = 0, e = errorCount; i != e; ++i) {
414                 const char* errorMessage = errors[i];
415                 fprintf(stderr, "ERROR: %s\n", errorMessage);
416             }
417         }
418     }
419
420     // Now emit each cache we generated, or the errors for them.
421     uint64_t cacheResultCount = 0;
422     if (const CacheResult* const* cacheResults = getCacheResults(sharedCacheBuilder, &cacheResultCount)) {
423         for (uint64_t i = 0, e = cacheResultCount; i != e; ++i) {
424             const CacheResult& result = *(cacheResults[i]);
425             // Always print the warnings if we have roots, even if there are errors
426             if ( (result.numErrors == 0) || !options.roots.empty() ) {
427                 for (uint64_t warningIndex = 0; warningIndex != result.numWarnings; ++warningIndex) {
428                     fprintf(stderr, "[%s] WARNING: %s\n", result.loggingPrefix, result.warnings[warningIndex]);
429                 }
430             }
431             if (result.numErrors) {
432                 for (uint64_t errorIndex = 0; errorIndex != result.numErrors; ++errorIndex) {
433                     fprintf(stderr, "[%s] ERROR: %s\n", result.loggingPrefix, result.errors[errorIndex]);
434                 }
435                 cacheBuildSuccess = false;
436             }
437         }
438     }
439
440     if (!cacheBuildSuccess) {
441         return false;
442     }
443
444     // If we built caches, then write everything out.
445     // TODO: Decide if we should we write any good caches anyway?
446     if (cacheBuildSuccess && !options.dstRoot.empty()) {
447         uint64_t fileResultCount = 0;
448         if (const FileResult* const* fileResults = getFileResults(sharedCacheBuilder, &fileResultCount)) {
449             for (uint64_t i = 0, e = fileResultCount; i != e; ++i) {
450                 const FileResult& result = *(fileResults[i]);
451
452                 switch (result.behavior) {
453                     case AddFile:
454                         break;
455                     case ChangeFile:
456                         continue;
457                 }
458
459                 if (!result.data)
460                     continue;
461
462                 const std::string path = options.dstRoot + result.path;
463                 std::string pathTemplate = path + "-XXXXXX";
464                 size_t templateLen = strlen(pathTemplate.c_str())+2;
465                 char pathTemplateSpace[templateLen];
466                 strlcpy(pathTemplateSpace, pathTemplate.c_str(), templateLen);
467                 int fd = mkstemp(pathTemplateSpace);
468                 if ( fd != -1 ) {
469                     ::ftruncate(fd, result.size);
470                     uint64_t writtenSize = write64(fd, result.data, result.size);
471                     if ( writtenSize == result.size ) {
472                         ::fchmod(fd, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); // mkstemp() makes file "rw-------", switch it to "rw-r--r--"
473                         if ( ::rename(pathTemplateSpace, path.c_str()) == 0) {
474                             ::close(fd);
475                             continue; // success
476                         }
477                     }
478                     else {
479                         fprintf(stderr, "ERROR: could not write file %s\n", pathTemplateSpace);
480                         cacheBuildSuccess = false;
481                     }
482                     ::close(fd);
483                     ::unlink(pathTemplateSpace);
484                 }
485                 else {
486                     fprintf(stderr, "ERROR: could not open file %s\n", pathTemplateSpace);
487                     cacheBuildSuccess = false;
488                 }
489             }
490         }
491
492         // Give up if we couldn't write the caches
493         if (!cacheBuildSuccess) {
494             return false;
495         }
496     }
497
498     // Emit the map files
499     if ( options.emitMapFiles && !options.dstRoot.empty() ) {
500         uint64_t cacheResultCount = 0;
501         if (const CacheResult* const* cacheResults = getCacheResults(sharedCacheBuilder, &cacheResultCount)) {
502             for (uint64_t i = 0, e = cacheResultCount; i != e; ++i) {
503                 const CacheResult& result = *(cacheResults[i]);
504                 std::string_view jsonData = result.mapJSON;
505                 if ( jsonData.empty() )
506                     continue;
507
508                 const std::string path = options.dstRoot + "/System/Library/dyld/" + result.loggingPrefix + ".json";
509                 std::string pathTemplate = path + "-XXXXXX";
510                 size_t templateLen = strlen(pathTemplate.c_str())+2;
511                 char pathTemplateSpace[templateLen];
512                 strlcpy(pathTemplateSpace, pathTemplate.c_str(), templateLen);
513                 int fd = mkstemp(pathTemplateSpace);
514                 if ( fd != -1 ) {
515                     ::ftruncate(fd, jsonData.size());
516                     uint64_t writtenSize = write64(fd, jsonData.data(), jsonData.size());
517                     if ( writtenSize == jsonData.size() ) {
518                         ::fchmod(fd, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); // mkstemp() makes file "rw-------", switch it to "rw-r--r--"
519                         if ( ::rename(pathTemplateSpace, path.c_str()) == 0) {
520                             ::close(fd);
521                             continue; // success
522                         }
523                     }
524                     else {
525                         fprintf(stderr, "ERROR: could not write file %s\n", pathTemplateSpace);
526                         cacheBuildSuccess = false;
527                     }
528                     ::close(fd);
529                     ::unlink(pathTemplateSpace);
530                 }
531                 else {
532                     fprintf(stderr, "ERROR: could not open file %s\n", pathTemplateSpace);
533                     cacheBuildSuccess = false;
534                 }
535             }
536         }
537
538         // Give up if we couldn't write the cache maps
539         if (!cacheBuildSuccess) {
540             return false;
541         }
542     }
543
544     return true;
545 }
546
547 static void buildCacheFromJSONManifest(Diagnostics& diags, const SharedCacheBuilderOptions& options,
548                                        const std::string& jsonManifestPath) {
549     dyld3::json::Node manifestNode = dyld3::json::readJSON(diags, jsonManifestPath.c_str());
550     if (diags.hasError())
551         return;
552
553     // Top level node should be a map of the options, files, and symlinks.
554     if (manifestNode.map.empty()) {
555         diags.error("Expected map for JSON manifest node\n");
556         return;
557     }
558
559     // Parse the nodes in the top level manifest node
560     const dyld3::json::Node& versionNode          = dyld3::json::getRequiredValue(diags, manifestNode, "version");
561     uint64_t manifestVersion                      = dyld3::json::parseRequiredInt(diags, versionNode);
562     if (diags.hasError())
563         return;
564
565     const uint64_t supportedManifestVersion = 1;
566     if (manifestVersion != supportedManifestVersion) {
567         diags.error("JSON manfiest version of %lld is unsupported.  Supported version is %lld\n",
568                     manifestVersion, supportedManifestVersion);
569         return;
570     }
571     const dyld3::json::Node& buildOptionsNode     = dyld3::json::getRequiredValue(diags, manifestNode, "buildOptions");
572     const dyld3::json::Node& filesNode            = dyld3::json::getRequiredValue(diags, manifestNode, "files");
573     const dyld3::json::Node* symlinksNode         = dyld3::json::getOptionalValue(diags, manifestNode, "symlinks");
574
575     // Parse the archs
576     const dyld3::json::Node& archsNode = dyld3::json::getRequiredValue(diags, buildOptionsNode, "archs");
577     if (diags.hasError())
578         return;
579     if (archsNode.array.empty()) {
580         diags.error("Build options archs node is not an array\n");
581         return;
582     }
583     std::set<std::string> jsonArchs;
584     const char* archs[archsNode.array.size()];
585     uint64_t numArchs = 0;
586     for (const dyld3::json::Node& archNode : archsNode.array) {
587         const char* archName = dyld3::json::parseRequiredString(diags, archNode).c_str();
588         jsonArchs.insert(archName);
589         if ( options.cmdLineArchs.empty() || options.cmdLineArchs.count(archName) ) {
590             archs[numArchs++] = archName;
591         }
592     }
593
594     // Check that the command line archs are in the JSON list
595     if ( !options.cmdLineArchs.empty() ) {
596         for (const std::string& cmdLineArch : options.cmdLineArchs) {
597             if ( !jsonArchs.count(cmdLineArch) ) {
598                 std::string validArchs = "";
599                 for (const std::string& jsonArch : jsonArchs) {
600                     if ( !validArchs.empty() ) {
601                         validArchs += ", ";
602                     }
603                     validArchs += jsonArch;
604                 }
605                 diags.error("Command line -arch '%s' is not valid for this device.  Valid archs are (%s)\n", cmdLineArch.c_str(), validArchs.c_str());
606                 return;
607             }
608         }
609     }
610
611     // Parse the rest of the options node.
612     BuildOptions_v2 buildOptions;
613     buildOptions.version                            = dyld3::json::parseRequiredInt(diags, dyld3::json::getRequiredValue(diags, buildOptionsNode, "version"));
614     buildOptions.updateName                         = dyld3::json::parseRequiredString(diags, dyld3::json::getRequiredValue(diags, buildOptionsNode, "updateName")).c_str();
615     buildOptions.deviceName                         = dyld3::json::parseRequiredString(diags, dyld3::json::getRequiredValue(diags, buildOptionsNode, "deviceName")).c_str();
616     buildOptions.disposition                        = stringToDisposition(diags, dyld3::json::parseRequiredString(diags, dyld3::json::getRequiredValue(diags, buildOptionsNode, "disposition")));
617     buildOptions.platform                           = stringToPlatform(diags, dyld3::json::parseRequiredString(diags, dyld3::json::getRequiredValue(diags, buildOptionsNode, "platform")));
618     buildOptions.archs                              = archs;
619     buildOptions.numArchs                           = numArchs;
620     buildOptions.verboseDiagnostics                 = options.debug;
621     buildOptions.isLocallyBuiltCache                = true;
622
623     // optimizeForSize was added in version 2
624     buildOptions.optimizeForSize = false;
625     if ( buildOptions.version >= 2 ) {
626         buildOptions.optimizeForSize                = dyld3::json::parseRequiredBool(diags, dyld3::json::getRequiredValue(diags, buildOptionsNode, "optimizeForSize"));
627     }
628
629     if (diags.hasError())
630         return;
631
632     // Override the disposition if we don't want certaion caches.
633     switch (buildOptions.disposition) {
634         case Unknown:
635             // Nothing we can do here as we can't assume what caches are built here.
636             break;
637         case InternalDevelopment:
638             if (!options.emitDevCaches && !options.emitCustomerCaches) {
639                 diags.error("both -no_customer_cache and -no_development_cache passed\n");
640                 break;
641             }
642             if (!options.emitDevCaches) {
643                 // This builds both caches, but we don't want dev
644                 buildOptions.disposition = Customer;
645             }
646             if (!options.emitCustomerCaches) {
647                 // This builds both caches, but we don't want customer
648                 buildOptions.disposition = InternalMinDevelopment;
649             }
650             break;
651         case Customer:
652             if (!options.emitCustomerCaches) {
653                 diags.error("Cannot request no customer cache for Customer as that is already only a customer cache\n");
654             }
655             break;
656         case InternalMinDevelopment:
657             if (!options.emitDevCaches) {
658                 diags.error("Cannot request no dev cache for InternalMinDevelopment as that is already only a dev cache\n");
659             }
660             break;
661     }
662
663     if (diags.hasError())
664         return;
665
666     struct MRMSharedCacheBuilder* sharedCacheBuilder = createSharedCacheBuilder((const BuildOptions_v1*)&buildOptions);
667
668     // Parse the files
669     if (filesNode.array.empty()) {
670         diags.error("Build options files node is not an array\n");
671         return;
672     }
673
674     std::vector<std::tuple<std::string, std::string, FileFlags>> inputFiles;
675     std::set<std::string> dylibsFoundInRoots;
676     for (const dyld3::json::Node& fileNode : filesNode.array) {
677         std::string path = dyld3::json::parseRequiredString(diags, dyld3::json::getRequiredValue(diags, fileNode, "path")).c_str();
678         FileFlags fileFlags     = stringToFileFlags(diags, dyld3::json::parseRequiredString(diags, dyld3::json::getRequiredValue(diags, fileNode, "flags")));
679
680         // We can optionally have a sourcePath entry which is the path to get the source content from instead of the install path
681         std::string sourcePath;
682         const dyld3::json::Node* sourcePathNode = dyld3::json::getOptionalValue(diags, fileNode, "sourcePath");
683         if ( sourcePathNode != nullptr ) {
684             if (!sourcePathNode->array.empty()) {
685                 diags.error("sourcePath node cannot be an array\n");
686                 return;
687             }
688             if (!sourcePathNode->map.empty()) {
689                 diags.error("sourcePath node cannot be a map\n");
690                 return;
691             }
692             sourcePath = sourcePathNode->value;
693         } else {
694             sourcePath = path;
695         }
696
697         std::string buildPath = sourcePath;
698
699         // Check if one of the -root's has this path
700         bool foundInOverlay = false;
701         for (const std::string& overlay : options.roots) {
702             struct stat sb;
703             std::string filePath = overlay + path;
704             if (!stat(filePath.c_str(), &sb)) {
705                 foundInOverlay = true;
706                 diags.verbose("Taking '%s' from overlay '%s' instead of dylib cache\n", path.c_str(), overlay.c_str());
707                 inputFiles.push_back({ filePath, path, fileFlags });
708                 dylibsFoundInRoots.insert(path);
709                 break;
710             }
711         }
712
713         if (foundInOverlay)
714             continue;
715
716         // Build paths are relative to the build artifact root directory.
717         switch (fileFlags) {
718             case NoFlags:
719             case MustBeInCache:
720             case ShouldBeExcludedFromCacheIfUnusedLeaf:
721             case RequiredClosure:
722             case DylibOrderFile:
723             case DirtyDataOrderFile:
724             case ObjCOptimizationsFile:
725                 buildPath = "." + buildPath;
726                 break;
727         }
728         inputFiles.push_back({ buildPath, path, fileFlags });
729     }
730
731     if (diags.hasError())
732         return;
733
734     // Parse the baseline from the map(s) if we have it
735     std::set<std::string> unionBaselineDylibs;
736     for (const std::string& baselineCacheMapPath : options.baselineCacheMapPaths) {
737         dyld3::json::Node mapNode = dyld3::json::readJSON(diags, baselineCacheMapPath.c_str());
738         if (diags.hasError())
739             return;
740
741         // Top level node should be a map of the version and files
742         if (mapNode.map.empty()) {
743             diags.error("Expected map for JSON cache map node\n");
744             return;
745         }
746
747         // Parse the nodes in the top level manifest node
748         const dyld3::json::Node& versionNode     = dyld3::json::getRequiredValue(diags, mapNode, "version");
749         uint64_t mapVersion                      = dyld3::json::parseRequiredInt(diags, versionNode);
750         if (diags.hasError())
751             return;
752
753         const uint64_t supportedMapVersion = 1;
754         if (mapVersion != supportedMapVersion) {
755             diags.error("JSON map version of %lld is unsupported.  Supported version is %lld\n",
756                         mapVersion, supportedMapVersion);
757             return;
758         }
759
760         // Parse the images
761         const dyld3::json::Node& imagesNode = dyld3::json::getRequiredValue(diags, mapNode, "images");
762         if (diags.hasError())
763             return;
764         if (imagesNode.array.empty()) {
765             diags.error("Images node is not an array\n");
766             return;
767         }
768
769         for (const dyld3::json::Node& imageNode : imagesNode.array) {
770             const dyld3::json::Node& pathNode = dyld3::json::getRequiredValue(diags, imageNode, "path");
771             if (pathNode.value.empty()) {
772                 diags.error("Image path node is not a string\n");
773                 return;
774             }
775             unionBaselineDylibs.insert(pathNode.value);
776         }
777     }
778
779     std::vector<std::pair<const void*, size_t>> mappedFiles;
780     loadMRMFiles(diags, sharedCacheBuilder, inputFiles, mappedFiles, unionBaselineDylibs);
781
782     if (diags.hasError())
783         return;
784
785     // Parse the symlinks if we have them
786     if (symlinksNode) {
787         if (symlinksNode->array.empty()) {
788             diags.error("Build options symlinks node is not an array\n");
789             return;
790         }
791         for (const dyld3::json::Node& symlinkNode : symlinksNode->array) {
792             std::string fromPath = dyld3::json::parseRequiredString(diags, dyld3::json::getRequiredValue(diags, symlinkNode, "path")).c_str();
793             const std::string& toPath   = dyld3::json::parseRequiredString(diags, dyld3::json::getRequiredValue(diags, symlinkNode, "target")).c_str();
794             addSymlink(sharedCacheBuilder, fromPath.c_str(), toPath.c_str());
795         }
796     }
797
798     if (diags.hasError())
799         return;
800
801     // Don't create a directory if we are skipping writes, which means we have no dstRoot set
802     if (!options.dstRoot.empty()) {
803         if ( buildOptions.platform == macOS ) {
804             (void)mkpath_np((options.dstRoot + MACOSX_MRM_DYLD_SHARED_CACHE_DIR).c_str(), 0755);
805         } else {
806             (void)mkpath_np((options.dstRoot + IPHONE_DYLD_SHARED_CACHE_DIR).c_str(), 0755);
807         }
808     }
809
810     // Actually build the cache.
811     bool cacheBuildSuccess = runSharedCacheBuilder(sharedCacheBuilder);
812
813     // Compare this cache to the baseline cache and see if we have any roots to copy over
814     if (!options.baselineDifferenceResultPath.empty() || options.baselineCopyRoots) {
815         std::set<std::string> dylibsInNewCaches;
816         std::set<std::string> simulatorSupportDylibs;
817         if (cacheBuildSuccess) {
818             uint64_t fileResultCount = 0;
819             if (const char* const* fileResults = getFilesToRemove(sharedCacheBuilder, &fileResultCount)) {
820                 for (uint64_t i = 0; i != fileResultCount; ++i)
821                     dylibsInNewCaches.insert(fileResults[i]);
822             }
823             if ( buildOptions.platform == Platform::macOS ) {
824                 // macOS has to leave the simulator support binaries on disk
825                 // It won't put them in the result of getFilesToRemove() so we need to manually add them
826                 simulatorSupportDylibs.insert("/usr/lib/system/libsystem_kernel.dylib");
827                 simulatorSupportDylibs.insert("/usr/lib/system/libsystem_platform.dylib");
828                 simulatorSupportDylibs.insert("/usr/lib/system/libsystem_pthread.dylib");
829             }
830         }
831
832         if (options.baselineCopyRoots) {
833             // Work out the set of dylibs in the old caches but not the new ones
834             std::set<std::string> dylibsMissingFromNewCaches;
835             for (const std::string& baselineDylib : unionBaselineDylibs) {
836                 if ( !dylibsInNewCaches.count(baselineDylib) && !simulatorSupportDylibs.count(baselineDylib))
837                     dylibsMissingFromNewCaches.insert(baselineDylib);
838             }
839
840             if (!dylibsMissingFromNewCaches.empty()) {
841                 BOMCopier copier = BOMCopierNewWithSys(BomSys_default());
842                 FilteredCopyOptions userData = { &diags, &dylibsMissingFromNewCaches, &dylibsFoundInRoots };
843                 BOMCopierSetUserData(copier, (void*)&userData);
844                 BOMCopierSetCopyFileStartedHandler(copier, filteredCopyIncludingPaths);
845                 std::string dylibCacheRootDir = realFilePath(options.dylibCacheDir);
846                 if (dylibCacheRootDir == "") {
847                     fprintf(stderr, "Could not find dylib Root directory to copy baseline roots from\n");
848                     exit(1);
849                 }
850                 BOMCopierCopy(copier, dylibCacheRootDir.c_str(), options.dstRoot.c_str());
851                 BOMCopierFree(copier);
852
853                 for (const std::string& dylibMissingFromNewCache : dylibsMissingFromNewCaches) {
854                     diags.verbose("Dylib missing from new cache: '%s'\n", dylibMissingFromNewCache.c_str());
855                 }
856             }
857         }
858
859         if (!options.baselineDifferenceResultPath.empty()) {
860             auto cppToObjStr = [](const std::string& str) {
861                 return [NSString stringWithUTF8String:str.c_str()];
862             };
863
864             // Work out the set of dylibs in the cache and taken from the -root
865             NSMutableArray<NSString*>* dylibsFromRoots = [NSMutableArray array];
866             for (auto& root : options.roots) {
867                 for (const std::string& dylibInstallName : dylibsInNewCaches) {
868                     struct stat sb;
869                     std::string filePath = root + "/" + dylibInstallName;
870                     if (!stat(filePath.c_str(), &sb)) {
871                         [dylibsFromRoots addObject:cppToObjStr(dylibInstallName)];
872                     }
873                 }
874             }
875
876             // Work out the set of dylibs in the new cache but not in the baseline cache.
877             NSMutableArray<NSString*>* dylibsMissingFromBaselineCache = [NSMutableArray array];
878             for (const std::string& newDylib : dylibsInNewCaches) {
879                 if (!unionBaselineDylibs.count(newDylib))
880                     [dylibsMissingFromBaselineCache addObject:cppToObjStr(newDylib)];
881             }
882
883             NSMutableDictionary* cacheDict = [[NSMutableDictionary alloc] init];
884             cacheDict[@"root-paths-in-cache"] = dylibsFromRoots;
885             cacheDict[@"device-paths-to-delete"] = dylibsMissingFromBaselineCache;
886
887             NSError* error = nil;
888             NSData*  outData = [NSPropertyListSerialization dataWithPropertyList:cacheDict
889                                                                           format:NSPropertyListBinaryFormat_v1_0
890                                                                          options:0
891                                                                            error:&error];
892             (void)[outData writeToFile:cppToObjStr(options.baselineDifferenceResultPath) atomically:YES];
893         }
894     }
895
896     bool wroteCaches = writeMRMResults(cacheBuildSuccess, sharedCacheBuilder, options);
897
898     destroySharedCacheBuilder(sharedCacheBuilder);
899
900     unloadMRMFiles(mappedFiles);
901
902     if (!wroteCaches) {
903         exit(-1);
904     }
905 }
906
907 int main(int argc, const char* argv[])
908 {
909     @autoreleasepool {
910         __block Diagnostics diags;
911         SharedCacheBuilderOptions options;
912         std::string jsonManifestPath;
913         char* tempRootsDir = strdup("/tmp/dyld_shared_cache_builder.XXXXXX");
914
915         mkdtemp(tempRootsDir);
916
917         for (int i = 1; i < argc; ++i) {
918             const char* arg = argv[i];
919             if (arg[0] == '-') {
920                 if (strcmp(arg, "-debug") == 0) {
921                     diags = Diagnostics(true);
922                     options.debug = true;
923                 } else if (strcmp(arg, "-list_configs") == 0) {
924                     options.listConfigs = true;
925                 } else if (strcmp(arg, "-root") == 0) {
926                     std::string realpath = realPath(argv[++i]);
927                     if ( realpath.empty() || !fileExists(realpath) ) {
928                         fprintf(stderr, "-root path doesn't exist: %s\n", argv[i]);
929                         exit(-1);
930                     }
931                     if ( std::find(options.roots.begin(), options.roots.end(), realpath) == options.roots.end() ) {
932                         // Push roots on to the front so that each -root overrides previous entries
933                         options.roots.push_front(realpath);
934                     }
935                 } else if (strcmp(arg, "-copy_roots") == 0) {
936                     options.copyRoots = true;
937                 } else if (strcmp(arg, "-dylib_cache") == 0) {
938                     options.dylibCacheDir = realPath(argv[++i]);
939                 } else if (strcmp(arg, "-artifact") == 0) {
940                     options.artifactDir = realPath(argv[++i]);
941                 } else if (strcmp(arg, "-no_overflow_dylibs") == 0) {
942                     options.emitElidedDylibs = false;
943                 } else if (strcmp(arg, "-no_development_cache") == 0) {
944                     options.emitDevCaches = false;
945                 } else if (strcmp(arg, "-development_cache") == 0) {
946                     options.emitDevCaches = true;
947                 } else if (strcmp(arg, "-no_customer_cache") == 0) {
948                     options.emitCustomerCaches = false;
949                 } else if (strcmp(arg, "-customer_cache") == 0) {
950                     options.emitCustomerCaches = true;
951                 } else if (strcmp(arg, "-overflow_dylibs") == 0) {
952                     options.emitElidedDylibs = true;
953                 } else if (strcmp(arg, "-mrm") == 0) {
954                     options.useMRM = true;
955                 } else if (strcmp(arg, "-emit_json") == 0) {
956                     options.emitJSONPath = realPath(argv[++i]);
957                 } else if (strcmp(arg, "-json_manifest") == 0) {
958                     jsonManifestPath = realPath(argv[++i]);
959                 } else if (strcmp(arg, "-build_all") == 0) {
960                     options.buildAllPath = realPath(argv[++i]);
961                 } else if (strcmp(arg, "-dst_root") == 0) {
962                     options.dstRoot = realPath(argv[++i]);
963                 } else if (strcmp(arg, "-release") == 0) {
964                     options.release = argv[++i];
965                 } else if (strcmp(arg, "-results") == 0) {
966                     options.resultPath = realPath(argv[++i]);
967                 } else if (strcmp(arg, "-baseline_diff_results") == 0) {
968                     options.baselineDifferenceResultPath = realPath(argv[++i]);
969                 } else if (strcmp(arg, "-baseline_copy_roots") == 0) {
970                     options.baselineCopyRoots = true;
971                 } else if (strcmp(arg, "-baseline_cache_map") == 0) {
972                     std::string path = realPath(argv[++i]);
973                     if ( !path.empty() )
974                         options.baselineCacheMapPaths.push_back(path);
975                 } else if (strcmp(arg, "-arch") == 0) {
976                     if ( ++i < argc ) {
977                         options.cmdLineArchs.insert(argv[i]);
978                     }
979                     else {
980                         fprintf(stderr, "-arch missing architecture name");
981                         return 1;
982                     }
983                 } else {
984                     //usage();
985                     fprintf(stderr, "unknown option: %s\n", arg);
986                     exit(-1);
987                 }
988             } else {
989                 fprintf(stderr, "unknown option: %s\n", arg);
990                 exit(-1);
991             }
992         }
993         (void)options.emitElidedDylibs; // not implemented yet
994
995         time_t mytime = time(0);
996         fprintf(stderr, "Started: %s", asctime(localtime(&mytime)));
997         processRoots(diags, options.roots, tempRootsDir);
998
999         struct rlimit rl = { OPEN_MAX, OPEN_MAX };
1000         (void)setrlimit(RLIMIT_NOFILE, &rl);
1001
1002         if (options.dylibCacheDir.empty() && options.artifactDir.empty() && options.release.empty()) {
1003             fprintf(stderr, "you must specify either -dylib_cache, -artifact or -release\n");
1004             exit(-1);
1005         } else if (!options.dylibCacheDir.empty() && !options.release.empty()) {
1006             fprintf(stderr, "you may not use -dylib_cache and -release at the same time\n");
1007             exit(-1);
1008         } else if (!options.dylibCacheDir.empty() && !options.artifactDir.empty()) {
1009             fprintf(stderr, "you may not use -dylib_cache and -artifact at the same time\n");
1010             exit(-1);
1011         }
1012
1013         if (jsonManifestPath.empty() && options.buildAllPath.empty()) {
1014             fprintf(stderr, "Must specify a -json_manifest path OR a -build_all path\n");
1015             exit(-1);
1016         }
1017
1018         if (!options.buildAllPath.empty()) {
1019             if (!options.dstRoot.empty()) {
1020                 fprintf(stderr, "Cannot combine -dst_root and -build_all\n");
1021                 exit(-1);
1022             }
1023             if (!jsonManifestPath.empty()) {
1024                 fprintf(stderr, "Cannot combine -json_manifest and -build_all\n");
1025                 exit(-1);
1026             }
1027             if (!options.baselineDifferenceResultPath.empty()) {
1028                 fprintf(stderr, "Cannot combine -baseline_diff_results and -build_all\n");
1029                 exit(-1);
1030             }
1031             if (options.baselineCopyRoots) {
1032                 fprintf(stderr, "Cannot combine -baseline_copy_roots and -build_all\n");
1033                 exit(-1);
1034             }
1035             if (!options.baselineCacheMapPaths.empty()) {
1036                 fprintf(stderr, "Cannot combine -baseline_cache_map and -build_all\n");
1037                 exit(-1);
1038             }
1039         } else if (!options.listConfigs) {
1040             if (options.dstRoot.empty()) {
1041                 fprintf(stderr, "Must specify a valid -dst_root OR -list_configs\n");
1042                 exit(-1);
1043             }
1044
1045             if (jsonManifestPath.empty()) {
1046                 fprintf(stderr, "Must specify a -json_manifest path OR -list_configs\n");
1047                 exit(-1);
1048             }
1049         }
1050
1051         if (!options.baselineDifferenceResultPath.empty() && (options.roots.size() > 1)) {
1052             fprintf(stderr, "Cannot use -baseline_diff_results with more that one -root\n");
1053             exit(-1);
1054         }
1055
1056         // Some options don't work with a JSON manifest
1057         if (!jsonManifestPath.empty()) {
1058             if (!options.resultPath.empty()) {
1059                 fprintf(stderr, "Cannot use -results with -json_manifest\n");
1060                 exit(-1);
1061             }
1062             if (!options.baselineDifferenceResultPath.empty() && options.baselineCacheMapPaths.empty()) {
1063                 fprintf(stderr, "Must use -baseline_cache_map with -baseline_diff_results when using -json_manifest\n");
1064                 exit(-1);
1065             }
1066             if (options.baselineCopyRoots && options.baselineCacheMapPaths.empty()) {
1067                 fprintf(stderr, "Must use -baseline_cache_map with -baseline_copy_roots when using -json_manifest\n");
1068                 exit(-1);
1069             }
1070         } else {
1071             if (!options.baselineCacheMapPaths.empty()) {
1072                 fprintf(stderr, "Cannot use -baseline_cache_map without -json_manifest\n");
1073                 exit(-1);
1074             }
1075         }
1076
1077         if (!options.baselineCacheMapPaths.empty()) {
1078             if (options.baselineDifferenceResultPath.empty() && options.baselineCopyRoots) {
1079                 fprintf(stderr, "Must use -baseline_cache_map with -baseline_diff_results or -baseline_copy_roots\n");
1080                 exit(-1);
1081             }
1082         }
1083
1084         // Find all the JSON files if we use -build_all
1085         __block std::vector<std::string> jsonPaths;
1086         if (!options.buildAllPath.empty()) {
1087             struct stat stat_buf;
1088             if (stat(options.buildAllPath.c_str(), &stat_buf) != 0) {
1089                 fprintf(stderr, "Could not find -build_all path '%s'\n", options.buildAllPath.c_str());
1090                 exit(-1);
1091             }
1092
1093             if ( (stat_buf.st_mode & S_IFMT) != S_IFDIR ) {
1094                 fprintf(stderr, "-build_all path is not a directory '%s'\n", options.buildAllPath.c_str());
1095                 exit(-1);
1096             }
1097
1098             auto processFile = ^(const std::string& path, const struct stat& statBuf) {
1099                 if ( !endsWith(path, ".json") )
1100                     return;
1101
1102                 jsonPaths.push_back(path);
1103             };
1104
1105             iterateDirectoryTree("", options.buildAllPath,
1106                                  ^(const std::string& dirPath) { return false; },
1107                                  processFile, true /* process files */, false /* recurse */);
1108
1109             if (jsonPaths.empty()) {
1110                 fprintf(stderr, "Didn't find any .json files inside -build_all path: %s\n", options.buildAllPath.c_str());
1111                 exit(-1);
1112             }
1113
1114             if (options.listConfigs) {
1115                 for (const std::string& path : jsonPaths) {
1116                     fprintf(stderr, "Found config: %s\n", path.c_str());
1117                 }
1118                 exit(-1);
1119             }
1120         }
1121
1122         if (!options.artifactDir.empty()) {
1123             // Find the dylib cache dir from inside the artifact dir
1124             struct stat stat_buf;
1125             if (stat(options.artifactDir.c_str(), &stat_buf) != 0) {
1126                 fprintf(stderr, "Could not find artifact path '%s'\n", options.artifactDir.c_str());
1127                 exit(-1);
1128             }
1129             std::string dir = options.artifactDir + "/AppleInternal/Developer/DylibCaches";
1130             if (stat(dir.c_str(), &stat_buf) != 0) {
1131                 fprintf(stderr, "Could not find artifact path '%s'\n", dir.c_str());
1132                 exit(-1);
1133             }
1134
1135             if (!options.release.empty()) {
1136                 // Use the given release
1137                 options.dylibCacheDir = dir + "/" + options.release + ".dlc";
1138             } else {
1139                 // Find a release directory
1140                 __block std::vector<std::string> subDirectories;
1141                 iterateDirectoryTree("", dir, ^(const std::string& dirPath) {
1142                     subDirectories.push_back(dirPath);
1143                     return false;
1144                 }, nullptr, false, false);
1145
1146                 if (subDirectories.empty()) {
1147                     fprintf(stderr, "Could not find dlc subdirectories inside '%s'\n", dir.c_str());
1148                     exit(-1);
1149                 }
1150
1151                 if (subDirectories.size() > 1) {
1152                     fprintf(stderr, "Found too many subdirectories inside artifact path '%s'.  Use -release to select one\n", dir.c_str());
1153                     exit(-1);
1154                 }
1155
1156                 options.dylibCacheDir = subDirectories.front();
1157             }
1158         }
1159
1160         if (options.dylibCacheDir.empty()) {
1161             options.dylibCacheDir = std::string("/AppleInternal/Developer/DylibCaches/") + options.release + ".dlc";
1162         }
1163
1164         //Move into the dir so we can use relative path manifests
1165         chdir(options.dylibCacheDir.c_str());
1166
1167         dispatch_async(dispatch_get_main_queue(), ^{
1168             if (!options.buildAllPath.empty()) {
1169                 bool requiresConcurrencyLimit = false;
1170                 dispatch_semaphore_t concurrencyLimit = NULL;
1171                 // Try build 1 cache per 8GB of RAM
1172                 uint64_t memSize = 0;
1173                 size_t sz = sizeof(memSize);
1174                 if ( sysctlbyname("hw.memsize", &memSize, &sz, NULL, 0) == 0 ) {
1175                     uint64_t maxThreads = std::max(memSize / 0x200000000ULL, 1ULL);
1176                     fprintf(stderr, "Detected %lldGb or less of memory, limiting concurrency to %lld threads\n",
1177                             memSize / (1 << 30), maxThreads);
1178                     requiresConcurrencyLimit = true;
1179                     concurrencyLimit = dispatch_semaphore_create(maxThreads);
1180                 }
1181
1182                 dispatch_apply(jsonPaths.size(), DISPATCH_APPLY_AUTO, ^(size_t index) {
1183                     // Horrible hack to limit concurrency in low spec build machines.
1184                     if (requiresConcurrencyLimit) { dispatch_semaphore_wait(concurrencyLimit, DISPATCH_TIME_FOREVER); }
1185
1186                     const std::string& jsonPath = jsonPaths[index];
1187                     buildCacheFromJSONManifest(diags, options, jsonPath);
1188
1189                     if (requiresConcurrencyLimit) { dispatch_semaphore_signal(concurrencyLimit); }
1190                 });
1191             } else {
1192                 buildCacheFromJSONManifest(diags, options, jsonManifestPath);
1193             }
1194
1195             const char* args[8];
1196             args[0] = (char*)"/bin/rm";
1197             args[1] = (char*)"-rf";
1198             args[2] = (char*)tempRootsDir;
1199             args[3] = nullptr;
1200             (void)runCommandAndWait(diags, args);
1201
1202             if (diags.hasError()) {
1203                 fprintf(stderr, "dyld_shared_cache_builder: error: %s", diags.errorMessage().c_str());
1204                 exit(-1);
1205             }
1206
1207             for (const std::string& warn : diags.warnings()) {
1208                 fprintf(stderr, "dyld_shared_cache_builder: warning: %s\n", warn.c_str());
1209             }
1210
1211             // Finally, write the roots.txt to tell us which roots we pulled in
1212             if (!options.dstRoot.empty())
1213                 writeRootList(options.dstRoot + "/System/Library/Caches/com.apple.dyld", options.roots);
1214             exit(0);
1215         });
1216     }
1217
1218     dispatch_main();
1219
1220     return 0;
1221 }