]> git.saurik.com Git - apple/dyld.git/blob - dyld3/shared-cache/dyld_shared_cache_builder.mm
4841dc7ad9aa0395729a5697ddfe6048cdaeed37
[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 <set>
53 #include <map>
54 #include <unordered_set>
55 #include <algorithm>
56 #include <fstream>
57 #include <regex>
58
59 #include <spawn.h>
60
61 #include <Bom/Bom.h>
62
63 #include "Manifest.h"
64 #include "Diagnostics.h"
65 #include "DyldSharedCache.h"
66 #include "BuilderUtils.h"
67 #include "FileUtils.h"
68 #include "JSONReader.h"
69 #include "JSONWriter.h"
70 #include "StringUtils.h"
71 #include "mrm_shared_cache_builder.h"
72
73 #if !__has_feature(objc_arc)
74 #error The use of libdispatch in this files requires it to be compiled with ARC in order to avoid leaks
75 #endif
76
77 extern char** environ;
78
79 static dispatch_queue_t build_queue;
80
81 int runCommandAndWait(Diagnostics& diags, const char* args[])
82 {
83 pid_t pid;
84 int status;
85 int res = posix_spawn(&pid, args[0], nullptr, nullptr, (char**)args, environ);
86 if (res != 0)
87 diags.error("Failed to spawn %s: %s (%d)", args[0], strerror(res), res);
88
89 do {
90 res = waitpid(pid, &status, 0);
91 } while (res == -1 && errno == EINTR);
92 if (res != -1) {
93 if (WIFEXITED(status)) {
94 res = WEXITSTATUS(status);
95 } else {
96 res = -1;
97 }
98 }
99
100 return res;
101 }
102
103 void processRoots(Diagnostics& diags, std::set<std::string>& roots, const char *tempRootsDir)
104 {
105 std::set<std::string> processedRoots;
106 struct stat sb;
107 int res = 0;
108 const char* args[8];
109
110 for (const auto& root : roots) {
111 res = stat(root.c_str(), &sb);
112
113 if (res == 0 && S_ISDIR(sb.st_mode)) {
114 processedRoots.insert(root);
115 continue;
116 }
117
118 char tempRootDir[MAXPATHLEN];
119 strlcpy(tempRootDir, tempRootsDir, MAXPATHLEN);
120 strlcat(tempRootDir, "/XXXXXXXX", MAXPATHLEN);
121 mkdtemp(tempRootDir);
122
123 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")) {
124 args[0] = (char*)"/usr/bin/ditto";
125 args[1] = (char*)"-x";
126 args[2] = (char*)root.c_str();
127 args[3] = tempRootDir;
128 args[4] = nullptr;
129 } else if (endsWith(root, ".tar")) {
130 args[0] = (char*)"/usr/bin/tar";
131 args[1] = (char*)"xf";
132 args[2] = (char*)root.c_str();
133 args[3] = (char*)"-C";
134 args[4] = tempRootDir;
135 args[5] = nullptr;
136 } else if (endsWith(root, ".tar.gz") || endsWith(root, ".tgz")) {
137 args[0] = (char*)"/usr/bin/tar";
138 args[1] = (char*)"xzf";
139 args[2] = (char*)root.c_str();
140 args[3] = (char*)"-C";
141 args[4] = tempRootDir;
142 args[5] = nullptr;
143 } else if (endsWith(root, ".tar.bz2")
144 || endsWith(root, ".tbz2")
145 || endsWith(root, ".tbz")) {
146 args[0] = (char*)"/usr/bin/tar";
147 args[1] = (char*)"xjf";
148 args[2] = (char*)root.c_str();
149 args[3] = (char*)"-C";
150 args[4] = tempRootDir;
151 args[5] = nullptr;
152 } else if (endsWith(root, ".xar")) {
153 args[0] = (char*)"/usr/bin/xar";
154 args[1] = (char*)"-xf";
155 args[2] = (char*)root.c_str();
156 args[3] = (char*)"-C";
157 args[4] = tempRootDir;
158 args[5] = nullptr;
159 } else if (endsWith(root, ".zip")) {
160 args[0] = (char*)"/usr/bin/ditto";
161 args[1] = (char*)"-xk";
162 args[2] = (char*)root.c_str();
163 args[3] = tempRootDir;
164 args[4] = nullptr;
165 } else {
166 diags.error("unknown archive type: %s", root.c_str());
167 continue;
168 }
169
170 if (res != runCommandAndWait(diags, args)) {
171 fprintf(stderr, "Could not expand archive %s: %s (%d)", root.c_str(), strerror(res), res);
172 exit(-1);
173 }
174 for (auto& existingRoot : processedRoots) {
175 if (existingRoot == tempRootDir)
176 return;
177 }
178
179 processedRoots.insert(tempRootDir);
180 }
181
182 roots = processedRoots;
183 }
184
185 void writeRootList(const std::string& dstRoot, const std::set<std::string>& roots)
186 {
187 if (roots.size() == 0)
188 return;
189
190 std::string rootFile = dstRoot + "/roots.txt";
191 FILE* froots = ::fopen(rootFile.c_str(), "w");
192 if (froots == NULL)
193 return;
194
195 for (auto& root : roots) {
196 fprintf(froots, "%s\n", root.c_str());
197 }
198
199 ::fclose(froots);
200 }
201
202 BOMCopierCopyOperation filteredCopyExcludingPaths(BOMCopier copier, const char* path, BOMFSObjType type, off_t size)
203 {
204 std::string absolutePath = &path[1];
205 void *userData = BOMCopierUserData(copier);
206 std::set<std::string> *cachePaths = (std::set<std::string>*)userData;
207 if (cachePaths->count(absolutePath)) {
208 return BOMCopierSkipFile;
209 }
210 return BOMCopierContinue;
211 }
212
213 BOMCopierCopyOperation filteredCopyIncludingPaths(BOMCopier copier, const char* path, BOMFSObjType type, off_t size)
214 {
215 std::string absolutePath = &path[1];
216 void *userData = BOMCopierUserData(copier);
217 std::set<std::string> *cachePaths = (std::set<std::string>*)userData;
218 for (const std::string& cachePath : *cachePaths) {
219 if (startsWith(cachePath, absolutePath))
220 return BOMCopierContinue;
221 }
222 if (cachePaths->count(absolutePath)) {
223 return BOMCopierContinue;
224 }
225 return BOMCopierSkipFile;
226 }
227
228 static std::string dispositionToString(Disposition disposition) {
229 switch (disposition) {
230 case Unknown:
231 return "Unknown";
232 case InternalDevelopment:
233 return "InternalDevelopment";
234 case Customer:
235 return "Customer";
236 case InternalMinDevelopment:
237 return "InternalMinDevelopment";
238 }
239 }
240
241 static Disposition stringToDisposition(Diagnostics& diags, const std::string& str) {
242 if (diags.hasError())
243 return Unknown;
244 if (str == "Unknown")
245 return Unknown;
246 if (str == "InternalDevelopment")
247 return InternalDevelopment;
248 if (str == "Customer")
249 return Customer;
250 if (str == "InternalMinDevelopment")
251 return InternalMinDevelopment;
252 return Unknown;
253 }
254
255 static std::string platformToString(Platform platform) {
256 switch (platform) {
257 case unknown:
258 return "unknown";
259 case macOS:
260 return "macOS";
261 case iOS:
262 return "iOS";
263 case tvOS:
264 return "tvOS";
265 case watchOS:
266 return "watchOS";
267 case bridgeOS:
268 return "bridgeOS";
269 case iOSMac:
270 return "UIKitForMac";
271 case iOS_simulator:
272 return "iOS_simulator";
273 case tvOS_simulator:
274 return "tvOS_simulator";
275 case watchOS_simulator:
276 return "watchOS_simulator";
277 }
278 }
279
280 static Platform stringToPlatform(Diagnostics& diags, const std::string& str) {
281 if (diags.hasError())
282 return unknown;
283 if (str == "unknown")
284 return unknown;
285 if (str == "macOS")
286 return macOS;
287 if (str == "iOS")
288 return iOS;
289 if (str == "tvOS")
290 return tvOS;
291 if (str == "watchOS")
292 return watchOS;
293 if (str == "bridgeOS")
294 return bridgeOS;
295 if (str == "iOSMac")
296 return iOSMac;
297 if (str == "UIKitForMac")
298 return iOSMac;
299 if (str == "iOS_simulator")
300 return iOS_simulator;
301 if (str == "tvOS_simulator")
302 return tvOS_simulator;
303 if (str == "watchOS_simulator")
304 return watchOS_simulator;
305 return unknown;
306 }
307
308 static std::string fileFlagsToString(FileFlags fileFlags) {
309 switch (fileFlags) {
310 case NoFlags:
311 return "NoFlags";
312 case MustBeInCache:
313 return "MustBeInCache";
314 case ShouldBeExcludedFromCacheIfUnusedLeaf:
315 return "ShouldBeExcludedFromCacheIfUnusedLeaf";
316 case RequiredClosure:
317 return "RequiredClosure";
318 case DylibOrderFile:
319 return "DylibOrderFile";
320 case DirtyDataOrderFile:
321 return "DirtyDataOrderFile";
322 }
323 }
324
325 static FileFlags stringToFileFlags(Diagnostics& diags, const std::string& str) {
326 if (diags.hasError())
327 return NoFlags;
328 if (str == "NoFlags")
329 return NoFlags;
330 if (str == "MustBeInCache")
331 return MustBeInCache;
332 if (str == "ShouldBeExcludedFromCacheIfUnusedLeaf")
333 return ShouldBeExcludedFromCacheIfUnusedLeaf;
334 if (str == "RequiredClosure")
335 return RequiredClosure;
336 if (str == "DylibOrderFile")
337 return DylibOrderFile;
338 if (str == "DirtyDataOrderFile")
339 return DirtyDataOrderFile;
340 return NoFlags;
341 }
342
343 static dyld3::json::Node getBuildOptionsNode(BuildOptions_v1 buildOptions) {
344 dyld3::json::Node buildOptionsNode;
345 buildOptionsNode.map["version"].value = dyld3::json::decimal(buildOptions.version);
346 buildOptionsNode.map["updateName"].value = buildOptions.updateName;
347 buildOptionsNode.map["deviceName"].value = buildOptions.deviceName;
348 buildOptionsNode.map["disposition"].value = dispositionToString(buildOptions.disposition);
349 buildOptionsNode.map["platform"].value = platformToString(buildOptions.platform);
350 for (unsigned i = 0; i != buildOptions.numArchs; ++i) {
351 dyld3::json::Node archNode;
352 archNode.value = buildOptions.archs[i];
353 buildOptionsNode.map["archs"].array.push_back(archNode);
354 }
355 return buildOptionsNode;
356 }
357
358 struct SharedCacheBuilderOptions {
359 Diagnostics diags;
360 std::set<std::string> roots;
361 std::string dylibCacheDir;
362 std::string artifactDir;
363 std::string release;
364 bool emitDevCaches = true;
365 bool emitElidedDylibs = true;
366 bool listConfigs = false;
367 bool copyRoots = false;
368 bool debug = false;
369 bool useMRM = false;
370 std::string dstRoot;
371 std::string emitJSONPath;
372 std::string buildAllPath;
373 std::string configuration;
374 std::string resultPath;
375 std::string baselineDifferenceResultPath;
376 std::string baselineCacheMapPath;
377 bool baselineCopyRoots = false;
378 };
379
380 static void loadMRMFiles(Diagnostics& diags,
381 SharedCacheBuilder* sharedCacheBuilder,
382 const std::vector<std::tuple<std::string, std::string, FileFlags>>& inputFiles,
383 std::vector<std::pair<const void*, size_t>>& mappedFiles,
384 const std::set<std::string>& baselineCacheFiles) {
385
386 for (const std::tuple<std::string, std::string, FileFlags>& inputFile : inputFiles) {
387 const std::string& buildPath = std::get<0>(inputFile);
388 const std::string& runtimePath = std::get<1>(inputFile);
389 FileFlags fileFlags = std::get<2>(inputFile);
390
391 struct stat stat_buf;
392 int fd = ::open(buildPath.c_str(), O_RDONLY, 0);
393 if (fd == -1) {
394 if (baselineCacheFiles.count(runtimePath)) {
395 diags.error("can't open file '%s', errno=%d\n", buildPath.c_str(), errno);
396 return;
397 } else {
398 diags.verbose("can't open file '%s', errno=%d\n", buildPath.c_str(), errno);
399 continue;
400 }
401 }
402
403 if (fstat(fd, &stat_buf) == -1) {
404 if (baselineCacheFiles.count(runtimePath)) {
405 diags.error("can't stat open file '%s', errno=%d\n", buildPath.c_str(), errno);
406 ::close(fd);
407 return;
408 } else {
409 diags.verbose("can't stat open file '%s', errno=%d\n", buildPath.c_str(), errno);
410 ::close(fd);
411 continue;
412 }
413 }
414
415 const void* buffer = mmap(NULL, (size_t)stat_buf.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
416 if (buffer == MAP_FAILED) {
417 diags.error("mmap() for file at %s failed, errno=%d\n", buildPath.c_str(), errno);
418 ::close(fd);
419 }
420 ::close(fd);
421
422 mappedFiles.emplace_back(buffer, (size_t)stat_buf.st_size);
423
424 addFile(sharedCacheBuilder, runtimePath.c_str(), (uint8_t*)buffer, (size_t)stat_buf.st_size, fileFlags);
425 }
426 }
427
428 static void unloadMRMFiles(std::vector<std::pair<const void*, size_t>>& mappedFiles) {
429 for (auto mappedFile : mappedFiles)
430 ::munmap((void*)mappedFile.first, mappedFile.second);
431 }
432
433 static void writeMRMResults(bool cacheBuildSuccess, SharedCacheBuilder* sharedCacheBuilder, const SharedCacheBuilderOptions& options) {
434 if (!cacheBuildSuccess) {
435 uint64_t errorCount = 0;
436 if (const char* const* errors = getErrors(sharedCacheBuilder, &errorCount)) {
437 for (uint64 i = 0, e = errorCount; i != e; ++i) {
438 const char* errorMessage = errors[i];
439 fprintf(stderr, "ERROR: %s\n", errorMessage);
440 }
441 }
442 }
443
444 // Now emit each cache we generated, or the errors for them.
445 uint64_t cacheResultCount = 0;
446 if (const CacheResult* const* cacheResults = getCacheResults(sharedCacheBuilder, &cacheResultCount)) {
447 for (uint64 i = 0, e = cacheResultCount; i != e; ++i) {
448 const CacheResult& result = *(cacheResults[i]);
449 // Always print the warnings if we have roots, even if there are errors
450 if ( (result.numErrors == 0) || !options.roots.empty() ) {
451 for (uint64_t warningIndex = 0; warningIndex != result.numWarnings; ++warningIndex) {
452 fprintf(stderr, "[%s] WARNING: %s\n", result.loggingPrefix, result.warnings[warningIndex]);
453 }
454 }
455 if (result.numErrors) {
456 for (uint64_t errorIndex = 0; errorIndex != result.numErrors; ++errorIndex) {
457 fprintf(stderr, "[%s] ERROR: %s\n", result.loggingPrefix, result.errors[errorIndex]);
458 }
459 cacheBuildSuccess = false;
460 }
461 }
462 }
463
464 if (!cacheBuildSuccess) {
465 exit(-1);
466 }
467
468 // If we built caches, then write everything out.
469 // TODO: Decide if we should we write any good caches anyway?
470 if (cacheBuildSuccess && !options.dstRoot.empty()) {
471 uint64_t fileResultCount = 0;
472 if (const FileResult* const* fileResults = getFileResults(sharedCacheBuilder, &fileResultCount)) {
473 for (uint64 i = 0, e = fileResultCount; i != e; ++i) {
474 const FileResult& result = *(fileResults[i]);
475
476 switch (result.behavior) {
477 case AddFile:
478 break;
479 case ChangeFile:
480 continue;
481 }
482
483 if (!result.data)
484 continue;
485
486 const std::string path = options.dstRoot + result.path;
487 std::string pathTemplate = path + "-XXXXXX";
488 size_t templateLen = strlen(pathTemplate.c_str())+2;
489 char pathTemplateSpace[templateLen];
490 strlcpy(pathTemplateSpace, pathTemplate.c_str(), templateLen);
491 int fd = mkstemp(pathTemplateSpace);
492 if ( fd != -1 ) {
493 ::ftruncate(fd, result.size);
494 uint64_t writtenSize = pwrite(fd, result.data, result.size, 0);
495 if ( writtenSize == result.size ) {
496 ::fchmod(fd, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); // mkstemp() makes file "rw-------", switch it to "rw-r--r--"
497 if ( ::rename(pathTemplateSpace, path.c_str()) == 0) {
498 ::close(fd);
499 continue; // success
500 }
501 }
502 else {
503 fprintf(stderr, "ERROR: could not write file %s\n", pathTemplateSpace);
504 cacheBuildSuccess = false;
505 }
506 ::close(fd);
507 ::unlink(pathTemplateSpace);
508 }
509 else {
510 fprintf(stderr, "ERROR: could not open file %s\n", pathTemplateSpace);
511 cacheBuildSuccess = false;
512 }
513 }
514 }
515 }
516 }
517
518 static void reportUnknownConfiguration(const std::string& configuration, dyld3::Manifest& manifest) {
519 fprintf(stderr, "** Unknown config '%s' for build %s.\n",
520 configuration.c_str(), manifest.build().c_str());
521
522 // Look for available configurations that the user might have meant.
523 // Substring match: print configs that contain the user's string.
524 // Regex match: if user wants "N61OS" then match .*N.*6.*1.*O.*S.*
525
526 std::string patternString = ".*";
527 for (auto c : configuration) {
528 if (isalnum(c)) { // filter regex special characters
529 patternString += c;
530 patternString += ".*";
531 }
532 }
533 std::regex pattern(patternString);
534
535 std::vector<std::string> likelyConfigs{};
536 std::vector<std::string> allConfigs{};
537 manifest.forEachConfiguration([&](const std::string& configName) {
538 allConfigs.push_back(configName);
539 if (!configuration.empty()) {
540 if (configName.find(configuration) != std::string::npos || std::regex_match(configName, pattern)) {
541 likelyConfigs.push_back(configName);
542 }
543 }
544 });
545
546 if (!likelyConfigs.empty()) {
547 fprintf(stderr, "\nDid you mean:\n");
548 for (auto configName: likelyConfigs) {
549 fprintf(stderr, "%s\n", configName.c_str());
550 }
551 }
552
553 fprintf(stderr, "\nAvailable configurations:\n");
554 for (auto configName : allConfigs) {
555 fprintf(stderr, "%s\n", configName.c_str());
556 }
557 }
558
559 static void buildCacheFromPListManifest(Diagnostics& diags, const SharedCacheBuilderOptions& options) {
560 // Get the list of configurations, without fetching all of the files.
561 auto manifest = dyld3::Manifest(diags, options.dylibCacheDir + "/Manifest.plist", false);
562
563 if (manifest.build().empty()) {
564 fprintf(stderr, "No manifest found at '%s/Manifest.plist'\n", options.dylibCacheDir.c_str());
565 exit(-1);
566 }
567
568 // List configurations if requested.
569 if (options.listConfigs) {
570 manifest.forEachConfiguration([](const std::string& configName) {
571 printf("%s\n", configName.c_str());
572 });
573 // If we weren't passed a configuration then exit
574 if (options.configuration.empty())
575 exit(0);
576 }
577
578 // Stop if the requested configuration is unavailable.
579 if (!manifest.filterForConfig(options.configuration)) {
580 reportUnknownConfiguration(options.configuration, manifest);
581 exit(-1);
582 }
583
584 // Now finish initializing the manifest.
585 // This step is slow so we defer it until after the checks above.
586 fprintf(stderr, "Building Caches for %s\n", manifest.build().c_str());
587 manifest.populate(options.roots);
588
589 (void)mkpath_np((options.dstRoot + "/System/Library/Caches/com.apple.dyld/").c_str(), 0755);
590 bool cacheBuildSuccess = false;
591 if (options.useMRM) {
592
593 std::ofstream jsonFile;
594 if (!options.emitJSONPath.empty()) {
595 jsonFile.open(options.emitJSONPath, std::ofstream::out);
596 if (!jsonFile.is_open()) {
597 diags.verbose("can't open file '%s'\n", options.emitJSONPath.c_str());
598 return;
599 }
600 }
601 dyld3::json::Node buildInvocationNode;
602
603 buildInvocationNode.map["version"].value = "1";
604
605 // Find the archs for the configuration we want.
606 __block std::set<std::string> validArchs;
607 manifest.configuration(options.configuration).forEachArchitecture(^(const std::string& path) {
608 validArchs.insert(path);
609 });
610
611 if (validArchs.size() != 1) {
612 fprintf(stderr, "MRM doesn't support more than one arch per configuration: %s\n",
613 options.configuration.c_str());
614 exit(-1);
615 }
616
617 const char* archs[validArchs.size()];
618 uint64_t archIndex = 0;
619 for (const std::string& arch : validArchs) {
620 archs[archIndex++] = arch.c_str();
621 }
622
623 BuildOptions_v1 buildOptions;
624 buildOptions.version = 1;
625 buildOptions.updateName = manifest.build().c_str();
626 buildOptions.deviceName = options.configuration.c_str();
627 buildOptions.disposition = Disposition::Unknown;
628 buildOptions.platform = (Platform)manifest.platform();
629 buildOptions.archs = archs;
630 buildOptions.numArchs = validArchs.size();
631 buildOptions.verboseDiagnostics = options.debug;
632 buildOptions.isLocallyBuiltCache = true;
633
634 __block struct SharedCacheBuilder* sharedCacheBuilder = createSharedCacheBuilder(&buildOptions);
635 buildInvocationNode.map["buildOptions"] = getBuildOptionsNode(buildOptions);
636
637 std::set<std::string> requiredBinaries = {
638 "/usr/lib/libSystem.B.dylib"
639 };
640
641 // Get the file data for every MachO in the BOM.
642 __block dyld3::json::Node filesNode;
643 __block std::vector<std::pair<const void*, size_t>> mappedFiles;
644 manifest.forEachMachO(options.configuration, ^(const std::string &buildPath, const std::string &runtimePath, const std::string &arch, bool shouldBeExcludedIfLeaf) {
645
646 // Filter based on arch as the Manifest adds the file once for each UUID.
647 if (!validArchs.count(arch))
648 return;
649
650 struct stat stat_buf;
651 int fd = ::open(buildPath.c_str(), O_RDONLY, 0);
652 if (fd == -1) {
653 diags.verbose("can't open file '%s', errno=%d\n", buildPath.c_str(), errno);
654 return;
655 }
656
657 if (fstat(fd, &stat_buf) == -1) {
658 diags.verbose("can't stat open file '%s', errno=%d\n", buildPath.c_str(), errno);
659 ::close(fd);
660 return;
661 }
662
663 const void* buffer = mmap(NULL, (size_t)stat_buf.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
664 if (buffer == MAP_FAILED) {
665 diags.verbose("mmap() for file at %s failed, errno=%d\n", buildPath.c_str(), errno);
666 ::close(fd);
667 }
668 ::close(fd);
669
670 mappedFiles.emplace_back(buffer, (size_t)stat_buf.st_size);
671 FileFlags fileFlags = FileFlags::NoFlags;
672 if (requiredBinaries.count(runtimePath))
673 fileFlags = FileFlags::RequiredClosure;
674 addFile(sharedCacheBuilder, runtimePath.c_str(), (uint8_t*)buffer, (size_t)stat_buf.st_size, fileFlags);
675
676 dyld3::json::Node fileNode;
677 fileNode.map["path"].value = runtimePath;
678 fileNode.map["flags"].value = fileFlagsToString(fileFlags);
679 filesNode.array.push_back(fileNode);
680 });
681
682 __block dyld3::json::Node symlinksNode;
683 manifest.forEachSymlink(options.configuration, ^(const std::string &fromPath, const std::string &toPath) {
684 addSymlink(sharedCacheBuilder, fromPath.c_str(), toPath.c_str());
685
686 dyld3::json::Node symlinkNode;
687 symlinkNode.map["from-path"].value = fromPath;
688 symlinkNode.map["to-path"].value = toPath;
689 symlinksNode.array.push_back(symlinkNode);
690 });
691
692 buildInvocationNode.map["symlinks"] = symlinksNode;
693
694 std::string orderFileData;
695 if (!manifest.dylibOrderFile().empty()) {
696 orderFileData = loadOrderFile(manifest.dylibOrderFile());
697 if (!orderFileData.empty()) {
698 addFile(sharedCacheBuilder, "*order file data*", (uint8_t*)orderFileData.data(), orderFileData.size(), FileFlags::DylibOrderFile);
699 dyld3::json::Node fileNode;
700 fileNode.map["path"].value = manifest.dylibOrderFile();
701 fileNode.map["flags"].value = fileFlagsToString(FileFlags::DylibOrderFile);
702 filesNode.array.push_back(fileNode);
703 }
704 }
705
706 std::string dirtyDataOrderFileData;
707 if (!manifest.dirtyDataOrderFile().empty()) {
708 dirtyDataOrderFileData = loadOrderFile(manifest.dirtyDataOrderFile());
709 if (!dirtyDataOrderFileData.empty()) {
710 addFile(sharedCacheBuilder, "*dirty data order file data*", (uint8_t*)dirtyDataOrderFileData.data(), dirtyDataOrderFileData.size(), FileFlags::DirtyDataOrderFile);
711 dyld3::json::Node fileNode;
712 fileNode.map["path"].value = manifest.dirtyDataOrderFile();
713 fileNode.map["flags"].value = fileFlagsToString(FileFlags::DirtyDataOrderFile);
714 filesNode.array.push_back(fileNode);
715 }
716 }
717
718 buildInvocationNode.map["files"] = filesNode;
719
720 if (jsonFile.is_open()) {
721 dyld3::json::printJSON(buildInvocationNode, 0, jsonFile);
722 jsonFile.close();
723 }
724
725 cacheBuildSuccess = runSharedCacheBuilder(sharedCacheBuilder);
726
727 writeMRMResults(cacheBuildSuccess, sharedCacheBuilder, options);
728
729 destroySharedCacheBuilder(sharedCacheBuilder);
730
731 for (auto mappedFile : mappedFiles)
732 ::munmap((void*)mappedFile.first, mappedFile.second);
733 } else {
734 manifest.calculateClosure();
735
736 cacheBuildSuccess = build(diags, manifest, options.dstRoot, false, options.debug, false, false, options.emitDevCaches, true);
737 }
738
739 if (!cacheBuildSuccess) {
740 exit(-1);
741 }
742
743 // Compare this cache to the baseline cache and see if we have any roots to copy over
744 if (!options.baselineDifferenceResultPath.empty() || options.baselineCopyRoots) {
745 std::set<std::string> baselineDylibs = manifest.resultsForConfiguration(options.configuration);
746
747 std::set<std::string> newDylibs;
748 std::map<std::string, std::string> missingDylibReasons;
749 manifest.forEachConfiguration([&manifest, &newDylibs, &missingDylibReasons](const std::string& configName) {
750 for (auto& arch : manifest.configuration(configName).architectures) {
751 for (auto& dylib : arch.second.results.dylibs) {
752 if (dylib.second.included) {
753 newDylibs.insert(manifest.installNameForUUID(dylib.first));
754 } else {
755 missingDylibReasons[manifest.installNameForUUID(dylib.first)] = dylib.second.exclusionInfo;
756 }
757 }
758 }
759 });
760
761 // Work out the set of dylibs in the old cache but not the new one
762 std::map<std::string, std::string> dylibsMissingFromNewCache;
763 if (options.baselineCopyRoots || !options.baselineDifferenceResultPath.empty()) {
764 for (const std::string& baselineDylib : baselineDylibs) {
765 if (!newDylibs.count(baselineDylib)) {
766 auto reasonIt = missingDylibReasons.find(baselineDylib);
767 if (reasonIt != missingDylibReasons.end())
768 dylibsMissingFromNewCache[baselineDylib] = reasonIt->second;
769 else
770 dylibsMissingFromNewCache[baselineDylib] = "";
771 }
772 }
773
774 if (!dylibsMissingFromNewCache.empty()) {
775 // Work out which dylibs are missing from the new cache, but are not
776 // coming from the -root which already has them on disk
777 std::set<std::string> pathsNotInRoots;
778 for (std::pair<std::string, std::string> dylibMissingFromNewCache : dylibsMissingFromNewCache) {
779 const std::string& dylibInstallName = dylibMissingFromNewCache.first;
780 bool foundInRoot = false;
781 for (auto& root : options.roots) {
782 struct stat sb;
783 std::string filePath = root + "/" + dylibInstallName;
784 if (!stat(filePath.c_str(), &sb)) {
785 foundInRoot = true;
786 }
787 }
788 if (!foundInRoot)
789 pathsNotInRoots.insert(dylibInstallName);
790 }
791
792 BOMCopier copier = BOMCopierNewWithSys(BomSys_default());
793 BOMCopierSetUserData(copier, (void*)&pathsNotInRoots);
794 BOMCopierSetCopyFileStartedHandler(copier, filteredCopyIncludingPaths);
795 std::string dylibCacheRootDir = realFilePath(options.dylibCacheDir + "/Root");
796 if (dylibCacheRootDir == "") {
797 fprintf(stderr, "Could not find dylib Root directory to copy baseline roots from\n");
798 exit(1);
799 }
800 BOMCopierCopy(copier, dylibCacheRootDir.c_str(), options.dstRoot.c_str());
801 BOMCopierFree(copier);
802
803 for (std::pair<std::string, std::string> dylibMissingFromNewCache : dylibsMissingFromNewCache) {
804 if (dylibMissingFromNewCache.second.empty())
805 diags.verbose("Dylib missing from new cache: '%s'\n", dylibMissingFromNewCache.first.c_str());
806 else
807 diags.verbose("Dylib missing from new cache: '%s' because '%s'\n",
808 dylibMissingFromNewCache.first.c_str(), dylibMissingFromNewCache.second.c_str());
809 }
810 }
811 }
812
813 if (!options.baselineDifferenceResultPath.empty()) {
814 auto cppToObjStr = [](const std::string& str) {
815 return [NSString stringWithUTF8String:str.c_str()];
816 };
817
818 // Work out the set of dylibs in the cache and taken from the -root
819 NSMutableArray<NSString*>* dylibsFromRoots = [NSMutableArray array];
820 for (auto& root : options.roots) {
821 for (const std::string& dylibInstallName : newDylibs) {
822 struct stat sb;
823 std::string filePath = root + "/" + dylibInstallName;
824 if (!stat(filePath.c_str(), &sb)) {
825 [dylibsFromRoots addObject:cppToObjStr(dylibInstallName)];
826 }
827 }
828 }
829
830 // Work out the set of dylibs in the new cache but not in the baseline cache.
831 NSMutableArray<NSString*>* dylibsMissingFromBaselineCache = [NSMutableArray array];
832 for (const std::string& newDylib : newDylibs) {
833 if (!baselineDylibs.count(newDylib))
834 [dylibsMissingFromBaselineCache addObject:cppToObjStr(newDylib)];
835 }
836
837 // If a dylib which was cached is no longer eligible, say why
838 NSMutableArray<NSDictionary*>* dylibsReasonsMissingFromNewCache = [NSMutableArray array];
839 for (std::pair<std::string, std::string> dylibMissingFromNewCache : dylibsMissingFromNewCache) {
840 NSMutableDictionary* reasonDict = [[NSMutableDictionary alloc] init];
841 reasonDict[@"path"] = cppToObjStr(dylibMissingFromNewCache.first);
842 reasonDict[@"reason"] = cppToObjStr(dylibMissingFromNewCache.second);
843 [dylibsReasonsMissingFromNewCache addObject:reasonDict];
844 }
845
846 NSMutableDictionary* cacheDict = [[NSMutableDictionary alloc] init];
847 cacheDict[@"root-paths-in-cache"] = dylibsFromRoots;
848 cacheDict[@"device-paths-to-delete"] = dylibsMissingFromBaselineCache;
849 cacheDict[@"baseline-paths-evicted-from-cache"] = dylibsReasonsMissingFromNewCache;
850
851 NSError* error = nil;
852 NSData* outData = [NSPropertyListSerialization dataWithPropertyList:cacheDict
853 format:NSPropertyListBinaryFormat_v1_0
854 options:0
855 error:&error];
856 (void)[outData writeToFile:cppToObjStr(options.baselineDifferenceResultPath) atomically:YES];
857 }
858 }
859
860 if (options.copyRoots) {
861 std::set<std::string> cachePaths;
862 manifest.forEachConfiguration([&manifest, &cachePaths](const std::string& configName) {
863 for (auto& arch : manifest.configuration(configName).architectures) {
864 for (auto& dylib : arch.second.results.dylibs) {
865 if (dylib.second.included) {
866 cachePaths.insert(manifest.installNameForUUID(dylib.first));
867 }
868 }
869 }
870 });
871
872 BOMCopier copier = BOMCopierNewWithSys(BomSys_default());
873 BOMCopierSetUserData(copier, (void*)&cachePaths);
874 BOMCopierSetCopyFileStartedHandler(copier, filteredCopyExcludingPaths);
875 for (auto& root : options.roots) {
876 BOMCopierCopy(copier, root.c_str(), options.dstRoot.c_str());
877 }
878 BOMCopierFree(copier);
879 }
880
881 int err = sync_volume_np(options.dstRoot.c_str(), SYNC_VOLUME_FULLSYNC | SYNC_VOLUME_WAIT);
882 if (err) {
883 fprintf(stderr, "Volume sync failed errnor=%d (%s)\n", err, strerror(err));
884 }
885
886 // Now that all the build commands have been issued lets put a barrier in after then which can tear down the app after
887 // everything is written.
888
889 if (!options.resultPath.empty()) {
890 manifest.write(options.resultPath);
891 }
892 }
893
894 static void buildCacheFromJSONManifest(Diagnostics& diags, const SharedCacheBuilderOptions& options,
895 const std::string& jsonManifestPath) {
896 dyld3::json::Node manifestNode = dyld3::json::readJSON(diags, jsonManifestPath.c_str());
897 if (diags.hasError())
898 return;
899
900 // Top level node should be a map of the options, files, and symlinks.
901 if (manifestNode.map.empty()) {
902 diags.error("Expected map for JSON manifest node\n");
903 return;
904 }
905
906 // Parse the nodes in the top level manifest node
907 const dyld3::json::Node& versionNode = dyld3::json::getRequiredValue(diags, manifestNode, "version");
908 uint64_t manifestVersion = dyld3::json::parseRequiredInt(diags, versionNode);
909 if (diags.hasError())
910 return;
911
912 const uint64_t supportedManifestVersion = 1;
913 if (manifestVersion != supportedManifestVersion) {
914 diags.error("JSON manfiest version of %lld is unsupported. Supported version is %lld\n",
915 manifestVersion, supportedManifestVersion);
916 return;
917 }
918 const dyld3::json::Node& buildOptionsNode = dyld3::json::getRequiredValue(diags, manifestNode, "buildOptions");
919 const dyld3::json::Node& filesNode = dyld3::json::getRequiredValue(diags, manifestNode, "files");
920 const dyld3::json::Node* symlinksNode = dyld3::json::getOptionalValue(diags, manifestNode, "symlinks");
921
922 // Parse the archs
923 const dyld3::json::Node& archsNode = dyld3::json::getRequiredValue(diags, buildOptionsNode, "archs");
924 if (diags.hasError())
925 return;
926 if (archsNode.array.empty()) {
927 diags.error("Build options archs node is not an array\n");
928 return;
929 }
930 const char* archs[archsNode.array.size()];
931 uint64_t archIndex = 0;
932 for (const dyld3::json::Node& archNode : archsNode.array) {
933 archs[archIndex++] = dyld3::json::parseRequiredString(diags, archNode).c_str();
934 }
935
936 // Parse the rest of the options node.
937 BuildOptions_v1 buildOptions;
938 buildOptions.version = dyld3::json::parseRequiredInt(diags, dyld3::json::getRequiredValue(diags, buildOptionsNode, "version"));
939 buildOptions.updateName = dyld3::json::parseRequiredString(diags, dyld3::json::getRequiredValue(diags, buildOptionsNode, "updateName")).c_str();
940 buildOptions.deviceName = dyld3::json::parseRequiredString(diags, dyld3::json::getRequiredValue(diags, buildOptionsNode, "deviceName")).c_str();
941 buildOptions.disposition = stringToDisposition(diags, dyld3::json::parseRequiredString(diags, dyld3::json::getRequiredValue(diags, buildOptionsNode, "disposition")));
942 buildOptions.platform = stringToPlatform(diags, dyld3::json::parseRequiredString(diags, dyld3::json::getRequiredValue(diags, buildOptionsNode, "platform")));
943 buildOptions.archs = archs;
944 buildOptions.numArchs = archsNode.array.size();
945 buildOptions.verboseDiagnostics = options.debug;
946 buildOptions.isLocallyBuiltCache = true;
947
948 if (diags.hasError())
949 return;
950
951 // Override the disposition if we don't want certaion caches.
952 if (!options.emitDevCaches) {
953 switch (buildOptions.disposition) {
954 case Unknown:
955 // Nothing we can do here as we can't assume what caches are built here.
956 break;
957 case InternalDevelopment:
958 // This builds both caches, but we don't want dev
959 buildOptions.disposition = Customer;
960 break;
961 case Customer:
962 // This is already only the customer cache
963 break;
964 case InternalMinDevelopment:
965 diags.error("Cannot request no dev cache for InternalMinDevelopment as that is already only a dev cache\n");
966 break;
967 }
968 }
969
970 if (diags.hasError())
971 return;
972
973 struct SharedCacheBuilder* sharedCacheBuilder = createSharedCacheBuilder(&buildOptions);
974
975 // Parse the files
976 if (filesNode.array.empty()) {
977 diags.error("Build options files node is not an array\n");
978 return;
979 }
980
981 std::vector<std::tuple<std::string, std::string, FileFlags>> inputFiles;
982 for (const dyld3::json::Node& fileNode : filesNode.array) {
983 const std::string& path = dyld3::json::parseRequiredString(diags, dyld3::json::getRequiredValue(diags, fileNode, "path")).c_str();
984 FileFlags fileFlags = stringToFileFlags(diags, dyld3::json::parseRequiredString(diags, dyld3::json::getRequiredValue(diags, fileNode, "flags")));
985
986 // We can optionally have a sourcePath entry which is the path to get the source content from instead of the install path
987 std::string sourcePath;
988 const dyld3::json::Node* sourcePathNode = dyld3::json::getOptionalValue(diags, fileNode, "sourcePath");
989 if ( sourcePathNode != nullptr ) {
990 if (!sourcePathNode->array.empty()) {
991 diags.error("sourcePath node cannot be an array\n");
992 return;
993 }
994 if (!sourcePathNode->map.empty()) {
995 diags.error("sourcePath node cannot be a map\n");
996 return;
997 }
998 sourcePath = sourcePathNode->value;
999 } else {
1000 sourcePath = path;
1001 }
1002
1003 // Check if one of the -root's has this path
1004 bool foundInOverlay = false;
1005 for (const std::string& overlay : options.roots) {
1006 struct stat sb;
1007 std::string filePath = overlay + path;
1008 if (!stat(filePath.c_str(), &sb)) {
1009 foundInOverlay = true;
1010 diags.verbose("Taking '%s' from overlay instead of dylib cache\n", path.c_str());
1011 inputFiles.push_back({ filePath, path, fileFlags });
1012 break;
1013 }
1014 }
1015
1016 if (foundInOverlay)
1017 continue;
1018
1019 // Build paths are relative to the build artifact root directory.
1020 std::string buildPath;
1021 switch (fileFlags) {
1022 case NoFlags:
1023 case MustBeInCache:
1024 case ShouldBeExcludedFromCacheIfUnusedLeaf:
1025 case RequiredClosure:
1026 buildPath = "." + sourcePath;
1027 break;
1028 case DylibOrderFile:
1029 case DirtyDataOrderFile:
1030 buildPath = "." + sourcePath;
1031 break;
1032 }
1033 inputFiles.push_back({ buildPath, path, fileFlags });
1034 }
1035
1036 if (diags.hasError())
1037 return;
1038
1039 // Parse the baseline from the map if we have it
1040 std::set<std::string> baselineDylibs;
1041 if ( !options.baselineCacheMapPath.empty() ) {
1042 dyld3::json::Node mapNode = dyld3::json::readJSON(diags, options.baselineCacheMapPath.c_str());
1043 if (diags.hasError())
1044 return;
1045
1046 // Top level node should be a map of the version and files
1047 if (mapNode.map.empty()) {
1048 diags.error("Expected map for JSON cache map node\n");
1049 return;
1050 }
1051
1052 // Parse the nodes in the top level manifest node
1053 const dyld3::json::Node& versionNode = dyld3::json::getRequiredValue(diags, mapNode, "version");
1054 uint64_t mapVersion = dyld3::json::parseRequiredInt(diags, versionNode);
1055 if (diags.hasError())
1056 return;
1057
1058 const uint64_t supportedMapVersion = 1;
1059 if (mapVersion != supportedMapVersion) {
1060 diags.error("JSON map version of %lld is unsupported. Supported version is %lld\n",
1061 mapVersion, supportedMapVersion);
1062 return;
1063 }
1064
1065 // Parse the images
1066 const dyld3::json::Node& imagesNode = dyld3::json::getRequiredValue(diags, mapNode, "images");
1067 if (diags.hasError())
1068 return;
1069 if (imagesNode.array.empty()) {
1070 diags.error("Images node is not an array\n");
1071 return;
1072 }
1073
1074 for (const dyld3::json::Node& imageNode : imagesNode.array) {
1075 const dyld3::json::Node& pathNode = dyld3::json::getRequiredValue(diags, imageNode, "path");
1076 if (pathNode.value.empty()) {
1077 diags.error("Image path node is not a string\n");
1078 return;
1079 }
1080 baselineDylibs.insert(pathNode.value);
1081 }
1082 }
1083
1084 std::vector<std::pair<const void*, size_t>> mappedFiles;
1085 loadMRMFiles(diags, sharedCacheBuilder, inputFiles, mappedFiles, baselineDylibs);
1086
1087 if (diags.hasError())
1088 return;
1089
1090 // Parse the symlinks if we have them
1091 if (symlinksNode) {
1092 if (symlinksNode->array.empty()) {
1093 diags.error("Build options symlinks node is not an array\n");
1094 return;
1095 }
1096 for (const dyld3::json::Node& symlinkNode : symlinksNode->array) {
1097 const std::string& fromPath = dyld3::json::parseRequiredString(diags, dyld3::json::getRequiredValue(diags, symlinkNode, "path")).c_str();
1098 const std::string& toPath = dyld3::json::parseRequiredString(diags, dyld3::json::getRequiredValue(diags, symlinkNode, "target")).c_str();
1099 addSymlink(sharedCacheBuilder, fromPath.c_str(), toPath.c_str());
1100 }
1101 }
1102
1103 if (diags.hasError())
1104 return;
1105
1106 // Don't create a directory if we are skipping writes, which means we have no dstRoot set
1107 if (!options.dstRoot.empty())
1108 (void)mkpath_np((options.dstRoot + "/System/Library/Caches/com.apple.dyld/").c_str(), 0755);
1109
1110 // Actually build the cache.
1111 bool cacheBuildSuccess = runSharedCacheBuilder(sharedCacheBuilder);
1112
1113 // Compare this cache to the baseline cache and see if we have any roots to copy over
1114 if (!options.baselineDifferenceResultPath.empty() || options.baselineCopyRoots) {
1115 std::set<std::string> newDylibs;
1116 if (cacheBuildSuccess) {
1117 uint64_t fileResultCount = 0;
1118 if (const char* const* fileResults = getFilesToRemove(sharedCacheBuilder, &fileResultCount)) {
1119 for (uint64_t i = 0; i != fileResultCount; ++i)
1120 newDylibs.insert(fileResults[i]);
1121 }
1122 }
1123
1124 if (options.baselineCopyRoots) {
1125 // Work out the set of dylibs in the old cache but not the new one
1126 std::set<std::string> dylibsMissingFromNewCache;
1127 for (const std::string& baselineDylib : baselineDylibs) {
1128 if (!newDylibs.count(baselineDylib))
1129 dylibsMissingFromNewCache.insert(baselineDylib);
1130 }
1131
1132 if (!dylibsMissingFromNewCache.empty()) {
1133 BOMCopier copier = BOMCopierNewWithSys(BomSys_default());
1134 BOMCopierSetUserData(copier, (void*)&dylibsMissingFromNewCache);
1135 BOMCopierSetCopyFileStartedHandler(copier, filteredCopyIncludingPaths);
1136 std::string dylibCacheRootDir = realFilePath(options.dylibCacheDir);
1137 if (dylibCacheRootDir == "") {
1138 fprintf(stderr, "Could not find dylib Root directory to copy baseline roots from\n");
1139 exit(1);
1140 }
1141 BOMCopierCopy(copier, dylibCacheRootDir.c_str(), options.dstRoot.c_str());
1142 BOMCopierFree(copier);
1143
1144 for (const std::string& dylibMissingFromNewCache : dylibsMissingFromNewCache) {
1145 diags.verbose("Dylib missing from new cache: '%s'\n", dylibMissingFromNewCache.c_str());
1146 }
1147 }
1148 }
1149
1150 if (!options.baselineDifferenceResultPath.empty()) {
1151 auto cppToObjStr = [](const std::string& str) {
1152 return [NSString stringWithUTF8String:str.c_str()];
1153 };
1154
1155 // Work out the set of dylibs in the cache and taken from the -root
1156 NSMutableArray<NSString*>* dylibsFromRoots = [NSMutableArray array];
1157 for (auto& root : options.roots) {
1158 for (const std::string& dylibInstallName : newDylibs) {
1159 struct stat sb;
1160 std::string filePath = root + "/" + dylibInstallName;
1161 if (!stat(filePath.c_str(), &sb)) {
1162 [dylibsFromRoots addObject:cppToObjStr(dylibInstallName)];
1163 }
1164 }
1165 }
1166
1167 // Work out the set of dylibs in the new cache but not in the baseline cache.
1168 NSMutableArray<NSString*>* dylibsMissingFromBaselineCache = [NSMutableArray array];
1169 for (const std::string& newDylib : newDylibs) {
1170 if (!baselineDylibs.count(newDylib))
1171 [dylibsMissingFromBaselineCache addObject:cppToObjStr(newDylib)];
1172 }
1173
1174 NSMutableDictionary* cacheDict = [[NSMutableDictionary alloc] init];
1175 cacheDict[@"root-paths-in-cache"] = dylibsFromRoots;
1176 cacheDict[@"device-paths-to-delete"] = dylibsMissingFromBaselineCache;
1177
1178 NSError* error = nil;
1179 NSData* outData = [NSPropertyListSerialization dataWithPropertyList:cacheDict
1180 format:NSPropertyListBinaryFormat_v1_0
1181 options:0
1182 error:&error];
1183 (void)[outData writeToFile:cppToObjStr(options.baselineDifferenceResultPath) atomically:YES];
1184 }
1185 }
1186
1187 writeMRMResults(cacheBuildSuccess, sharedCacheBuilder, options);
1188
1189 destroySharedCacheBuilder(sharedCacheBuilder);
1190
1191 unloadMRMFiles(mappedFiles);
1192 }
1193
1194 int main(int argc, const char* argv[])
1195 {
1196 @autoreleasepool {
1197 __block Diagnostics diags;
1198 SharedCacheBuilderOptions options;
1199 std::string jsonManifestPath;
1200 char* tempRootsDir = strdup("/tmp/dyld_shared_cache_builder.XXXXXX");
1201
1202 mkdtemp(tempRootsDir);
1203
1204 for (int i = 1; i < argc; ++i) {
1205 const char* arg = argv[i];
1206 if (arg[0] == '-') {
1207 if (strcmp(arg, "-debug") == 0) {
1208 diags = Diagnostics(true);
1209 options.debug = true;
1210 } else if (strcmp(arg, "-list_configs") == 0) {
1211 options.listConfigs = true;
1212 } else if (strcmp(arg, "-root") == 0) {
1213 std::string realpath = realPath(argv[++i]);
1214 if ( realpath.empty() || !fileExists(realpath) ) {
1215 fprintf(stderr, "-root path doesn't exist: %s\n", argv[i]);
1216 exit(-1);
1217 }
1218 options.roots.insert(realpath);
1219 } else if (strcmp(arg, "-copy_roots") == 0) {
1220 options.copyRoots = true;
1221 } else if (strcmp(arg, "-dylib_cache") == 0) {
1222 options.dylibCacheDir = realPath(argv[++i]);
1223 } else if (strcmp(arg, "-artifact") == 0) {
1224 options.artifactDir = realPath(argv[++i]);
1225 } else if (strcmp(arg, "-no_development_cache") == 0) {
1226 options.emitDevCaches = false;
1227 } else if (strcmp(arg, "-no_overflow_dylibs") == 0) {
1228 options.emitElidedDylibs = false;
1229 } else if (strcmp(arg, "-development_cache") == 0) {
1230 options.emitDevCaches = true;
1231 } else if (strcmp(arg, "-overflow_dylibs") == 0) {
1232 options.emitElidedDylibs = true;
1233 } else if (strcmp(arg, "-mrm") == 0) {
1234 options.useMRM = true;
1235 } else if (strcmp(arg, "-emit_json") == 0) {
1236 options.emitJSONPath = realPath(argv[++i]);
1237 } else if (strcmp(arg, "-json_manifest") == 0) {
1238 jsonManifestPath = realPath(argv[++i]);
1239 } else if (strcmp(arg, "-build_all") == 0) {
1240 options.buildAllPath = realPath(argv[++i]);
1241 } else if (strcmp(arg, "-dst_root") == 0) {
1242 options.dstRoot = realPath(argv[++i]);
1243 } else if (strcmp(arg, "-release") == 0) {
1244 options.release = argv[++i];
1245 } else if (strcmp(arg, "-results") == 0) {
1246 options.resultPath = realPath(argv[++i]);
1247 } else if (strcmp(arg, "-baseline_diff_results") == 0) {
1248 options.baselineDifferenceResultPath = realPath(argv[++i]);
1249 } else if (strcmp(arg, "-baseline_copy_roots") == 0) {
1250 options.baselineCopyRoots = true;
1251 } else if (strcmp(arg, "-baseline_cache_map") == 0) {
1252 options.baselineCacheMapPath = realPath(argv[++i]);
1253 } else {
1254 //usage();
1255 fprintf(stderr, "unknown option: %s\n", arg);
1256 exit(-1);
1257 }
1258 } else {
1259 if (!options.configuration.empty()) {
1260 fprintf(stderr, "You may only specify one configuration\n");
1261 exit(-1);
1262 }
1263 options.configuration = argv[i];
1264 }
1265 }
1266 (void)options.emitElidedDylibs; // not implemented yet
1267
1268 time_t mytime = time(0);
1269 fprintf(stderr, "Started: %s", asctime(localtime(&mytime)));
1270 processRoots(diags, options.roots, tempRootsDir);
1271
1272 struct rlimit rl = { OPEN_MAX, OPEN_MAX };
1273 (void)setrlimit(RLIMIT_NOFILE, &rl);
1274
1275 if (options.dylibCacheDir.empty() && options.artifactDir.empty() && options.release.empty()) {
1276 fprintf(stderr, "you must specify either -dylib_cache, -artifact or -release\n");
1277 exit(-1);
1278 } else if (!options.dylibCacheDir.empty() && !options.release.empty()) {
1279 fprintf(stderr, "you may not use -dylib_cache and -release at the same time\n");
1280 exit(-1);
1281 } else if (!options.dylibCacheDir.empty() && !options.artifactDir.empty()) {
1282 fprintf(stderr, "you may not use -dylib_cache and -artifact at the same time\n");
1283 exit(-1);
1284 }
1285
1286 if (options.configuration.empty() && jsonManifestPath.empty() && options.buildAllPath.empty()) {
1287 fprintf(stderr, "Must specify a configuration OR a -json_manifest path OR a -build_all path\n");
1288 exit(-1);
1289 }
1290
1291 if (!options.buildAllPath.empty()) {
1292 if (!options.dstRoot.empty()) {
1293 fprintf(stderr, "Cannot combine -dst_root and -build_all\n");
1294 exit(-1);
1295 }
1296 if (!options.configuration.empty()) {
1297 fprintf(stderr, "Cannot combine configuration and -build_all\n");
1298 exit(-1);
1299 }
1300 if (!jsonManifestPath.empty()) {
1301 fprintf(stderr, "Cannot combine -json_manifest and -build_all\n");
1302 exit(-1);
1303 }
1304 if (!options.baselineDifferenceResultPath.empty()) {
1305 fprintf(stderr, "Cannot combine -baseline_diff_results and -build_all\n");
1306 exit(-1);
1307 }
1308 if (options.baselineCopyRoots) {
1309 fprintf(stderr, "Cannot combine -baseline_copy_roots and -build_all\n");
1310 exit(-1);
1311 }
1312 if (!options.baselineCacheMapPath.empty()) {
1313 fprintf(stderr, "Cannot combine -baseline_cache_map and -build_all\n");
1314 exit(-1);
1315 }
1316 } else if (!options.listConfigs) {
1317 if (options.dstRoot.empty()) {
1318 fprintf(stderr, "Must specify a valid -dst_root OR -list_configs\n");
1319 exit(-1);
1320 }
1321
1322 if (options.configuration.empty() && jsonManifestPath.empty()) {
1323 fprintf(stderr, "Must specify a configuration OR -json_manifest path OR -list_configs\n");
1324 exit(-1);
1325 }
1326 }
1327
1328 if (!options.baselineDifferenceResultPath.empty() && (options.roots.size() > 1)) {
1329 fprintf(stderr, "Cannot use -baseline_diff_results with more that one -root\n");
1330 exit(-1);
1331 }
1332
1333 // Some options don't work with a JSON manifest
1334 if (!jsonManifestPath.empty()) {
1335 if (!options.resultPath.empty()) {
1336 fprintf(stderr, "Cannot use -results with -json_manifest\n");
1337 exit(-1);
1338 }
1339 if (!options.baselineDifferenceResultPath.empty() && options.baselineCacheMapPath.empty()) {
1340 fprintf(stderr, "Must use -baseline_cache_map with -baseline_diff_results when using -json_manifest\n");
1341 exit(-1);
1342 }
1343 if (options.baselineCopyRoots && options.baselineCacheMapPath.empty()) {
1344 fprintf(stderr, "Must use -baseline_cache_map with -baseline_copy_roots when using -json_manifest\n");
1345 exit(-1);
1346 }
1347 } else {
1348 if (!options.baselineCacheMapPath.empty()) {
1349 fprintf(stderr, "Cannot use -baseline_cache_map without -json_manifest\n");
1350 exit(-1);
1351 }
1352 }
1353
1354 if (!options.baselineCacheMapPath.empty()) {
1355 if (options.baselineDifferenceResultPath.empty() && options.baselineCopyRoots) {
1356 fprintf(stderr, "Must use -baseline_cache_map with -baseline_diff_results or -baseline_copy_roots\n");
1357 exit(-1);
1358 }
1359 }
1360
1361 // Find all the JSON files if we use -build_all
1362 __block std::vector<std::string> jsonPaths;
1363 if (!options.buildAllPath.empty()) {
1364 struct stat stat_buf;
1365 if (stat(options.buildAllPath.c_str(), &stat_buf) != 0) {
1366 fprintf(stderr, "Could not find -build_all path '%s'\n", options.buildAllPath.c_str());
1367 exit(-1);
1368 }
1369
1370 if ( (stat_buf.st_mode & S_IFMT) != S_IFDIR ) {
1371 fprintf(stderr, "-build_all path is not a directory '%s'\n", options.buildAllPath.c_str());
1372 exit(-1);
1373 }
1374
1375 auto processFile = ^(const std::string& path, const struct stat& statBuf) {
1376 if ( !endsWith(path, ".json") )
1377 return;
1378
1379 jsonPaths.push_back(path);
1380 };
1381
1382 iterateDirectoryTree("", options.buildAllPath,
1383 ^(const std::string& dirPath) { return false; },
1384 processFile, true /* process files */, false /* recurse */);
1385
1386 if (jsonPaths.empty()) {
1387 fprintf(stderr, "Didn't find any .json files inside -build_all path: %s\n", options.buildAllPath.c_str());
1388 exit(-1);
1389 }
1390
1391 if (options.listConfigs) {
1392 for (const std::string& path : jsonPaths) {
1393 fprintf(stderr, "Found config: %s\n", path.c_str());
1394 }
1395 exit(-1);
1396 }
1397 }
1398
1399 if (!options.artifactDir.empty()) {
1400 // Find the dylib cache dir from inside the artifact dir
1401 struct stat stat_buf;
1402 if (stat(options.artifactDir.c_str(), &stat_buf) != 0) {
1403 fprintf(stderr, "Could not find artifact path '%s'\n", options.artifactDir.c_str());
1404 exit(-1);
1405 }
1406 std::string dir = options.artifactDir + "/AppleInternal/Developer/DylibCaches";
1407 if (stat(dir.c_str(), &stat_buf) != 0) {
1408 fprintf(stderr, "Could not find artifact path '%s'\n", dir.c_str());
1409 exit(-1);
1410 }
1411
1412 if (!options.release.empty()) {
1413 // Use the given release
1414 options.dylibCacheDir = dir + "/" + options.release + ".dlc";
1415 } else {
1416 // Find a release directory
1417 __block std::vector<std::string> subDirectories;
1418 iterateDirectoryTree("", dir, ^(const std::string& dirPath) {
1419 subDirectories.push_back(dirPath);
1420 return false;
1421 }, nullptr, false, false);
1422
1423 if (subDirectories.empty()) {
1424 fprintf(stderr, "Could not find dlc subdirectories inside '%s'\n", dir.c_str());
1425 exit(-1);
1426 }
1427
1428 if (subDirectories.size() > 1) {
1429 fprintf(stderr, "Found too many subdirectories inside artifact path '%s'. Use -release to select one\n", dir.c_str());
1430 exit(-1);
1431 }
1432
1433 options.dylibCacheDir = subDirectories.front();
1434 }
1435 }
1436
1437 if (options.dylibCacheDir.empty()) {
1438 options.dylibCacheDir = std::string("/AppleInternal/Developer/DylibCaches/") + options.release + ".dlc";
1439 }
1440
1441 //Move into the dir so we can use relative path manifests
1442 chdir(options.dylibCacheDir.c_str());
1443
1444 dispatch_async(dispatch_get_main_queue(), ^{
1445 if (!options.buildAllPath.empty()) {
1446 bool requiresConcurrencyLimit = false;
1447 dispatch_semaphore_t concurrencyLimit = NULL;
1448 // Try build 1 cache per 8GB of RAM
1449 uint64_t memSize = 0;
1450 size_t sz = sizeof(memSize);
1451 if ( sysctlbyname("hw.memsize", &memSize, &sz, NULL, 0) == 0 ) {
1452 uint64_t maxThreads = std::max(memSize / 0x200000000ULL, 1ULL);
1453 fprintf(stderr, "Detected %lldGb or less of memory, limiting concurrency to %lld threads\n",
1454 memSize / (1 << 30), maxThreads);
1455 requiresConcurrencyLimit = true;
1456 concurrencyLimit = dispatch_semaphore_create(maxThreads);
1457 }
1458
1459 dispatch_apply(jsonPaths.size(), DISPATCH_APPLY_AUTO, ^(size_t index) {
1460 // Horrible hack to limit concurrency in low spec build machines.
1461 if (requiresConcurrencyLimit) { dispatch_semaphore_wait(concurrencyLimit, DISPATCH_TIME_FOREVER); }
1462
1463 const std::string& jsonPath = jsonPaths[index];
1464 buildCacheFromJSONManifest(diags, options, jsonPath);
1465
1466 if (requiresConcurrencyLimit) { dispatch_semaphore_signal(concurrencyLimit); }
1467 });
1468 } else if (!jsonManifestPath.empty()) {
1469 buildCacheFromJSONManifest(diags, options, jsonManifestPath);
1470 } else {
1471 buildCacheFromPListManifest(diags, options);
1472 }
1473
1474 const char* args[8];
1475 args[0] = (char*)"/bin/rm";
1476 args[1] = (char*)"-rf";
1477 args[2] = (char*)tempRootsDir;
1478 args[3] = nullptr;
1479 (void)runCommandAndWait(diags, args);
1480
1481 if (diags.hasError()) {
1482 fprintf(stderr, "dyld_shared_cache_builder: error: %s", diags.errorMessage().c_str());
1483 exit(-1);
1484 }
1485
1486 for (const std::string& warn : diags.warnings()) {
1487 fprintf(stderr, "dyld_shared_cache_builder: warning: %s\n", warn.c_str());
1488 }
1489
1490 // Finally, write the roots.txt to tell us which roots we pulled in
1491 if (!options.dstRoot.empty())
1492 writeRootList(options.dstRoot + "/System/Library/Caches/com.apple.dyld", options.roots);
1493 exit(0);
1494 });
1495 }
1496
1497 dispatch_main();
1498
1499 return 0;
1500 }