]> git.saurik.com Git - apple/dyld.git/blob - dyld3/shared-cache/dyld_shared_cache_builder.mm
dyld-750.5.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 <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 #include <Foundation/NSData.h>
63 #include <Foundation/NSDictionary.h>
64 #include <Foundation/NSPropertyList.h>
65 #include <Foundation/NSString.h>
66
67 #include "Diagnostics.h"
68 #include "DyldSharedCache.h"
69 #include "FileUtils.h"
70 #include "JSONReader.h"
71 #include "JSONWriter.h"
72 #include "StringUtils.h"
73 #include "mrm_shared_cache_builder.h"
74
75 #if !__has_feature(objc_arc)
76 #error The use of libdispatch in this files requires it to be compiled with ARC in order to avoid leaks
77 #endif
78
79 extern char** environ;
80
81 static dispatch_queue_t build_queue;
82
83 int runCommandAndWait(Diagnostics& diags, const char* args[])
84 {
85 pid_t pid;
86 int status;
87 int res = posix_spawn(&pid, args[0], nullptr, nullptr, (char**)args, environ);
88 if (res != 0)
89 diags.error("Failed to spawn %s: %s (%d)", args[0], strerror(res), res);
90
91 do {
92 res = waitpid(pid, &status, 0);
93 } while (res == -1 && errno == EINTR);
94 if (res != -1) {
95 if (WIFEXITED(status)) {
96 res = WEXITSTATUS(status);
97 } else {
98 res = -1;
99 }
100 }
101
102 return res;
103 }
104
105 void processRoots(Diagnostics& diags, std::set<std::string>& roots, const char *tempRootsDir)
106 {
107 std::set<std::string> processedRoots;
108 struct stat sb;
109 int res = 0;
110 const char* args[8];
111
112 for (const auto& root : roots) {
113 res = stat(root.c_str(), &sb);
114
115 if (res == 0 && S_ISDIR(sb.st_mode)) {
116 processedRoots.insert(root);
117 continue;
118 }
119
120 char tempRootDir[MAXPATHLEN];
121 strlcpy(tempRootDir, tempRootsDir, MAXPATHLEN);
122 strlcat(tempRootDir, "/XXXXXXXX", MAXPATHLEN);
123 mkdtemp(tempRootDir);
124
125 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")) {
126 args[0] = (char*)"/usr/bin/ditto";
127 args[1] = (char*)"-x";
128 args[2] = (char*)root.c_str();
129 args[3] = tempRootDir;
130 args[4] = nullptr;
131 } else if (endsWith(root, ".tar")) {
132 args[0] = (char*)"/usr/bin/tar";
133 args[1] = (char*)"xf";
134 args[2] = (char*)root.c_str();
135 args[3] = (char*)"-C";
136 args[4] = tempRootDir;
137 args[5] = nullptr;
138 } else if (endsWith(root, ".tar.gz") || endsWith(root, ".tgz")) {
139 args[0] = (char*)"/usr/bin/tar";
140 args[1] = (char*)"xzf";
141 args[2] = (char*)root.c_str();
142 args[3] = (char*)"-C";
143 args[4] = tempRootDir;
144 args[5] = nullptr;
145 } else if (endsWith(root, ".tar.bz2")
146 || endsWith(root, ".tbz2")
147 || endsWith(root, ".tbz")) {
148 args[0] = (char*)"/usr/bin/tar";
149 args[1] = (char*)"xjf";
150 args[2] = (char*)root.c_str();
151 args[3] = (char*)"-C";
152 args[4] = tempRootDir;
153 args[5] = nullptr;
154 } else if (endsWith(root, ".zip")) {
155 args[0] = (char*)"/usr/bin/ditto";
156 args[1] = (char*)"-xk";
157 args[2] = (char*)root.c_str();
158 args[3] = tempRootDir;
159 args[4] = nullptr;
160 } else {
161 diags.error("unknown archive type: %s", root.c_str());
162 continue;
163 }
164
165 if (res != runCommandAndWait(diags, args)) {
166 fprintf(stderr, "Could not expand archive %s: %s (%d)", root.c_str(), strerror(res), res);
167 exit(-1);
168 }
169 for (auto& existingRoot : processedRoots) {
170 if (existingRoot == tempRootDir)
171 return;
172 }
173
174 processedRoots.insert(tempRootDir);
175 }
176
177 roots = processedRoots;
178 }
179
180 void writeRootList(const std::string& dstRoot, const std::set<std::string>& roots)
181 {
182 if (roots.size() == 0)
183 return;
184
185 std::string rootFile = dstRoot + "/roots.txt";
186 FILE* froots = ::fopen(rootFile.c_str(), "w");
187 if (froots == NULL)
188 return;
189
190 for (auto& root : roots) {
191 fprintf(froots, "%s\n", root.c_str());
192 }
193
194 ::fclose(froots);
195 }
196
197 BOMCopierCopyOperation filteredCopyIncludingPaths(BOMCopier copier, const char* path, BOMFSObjType type, off_t size)
198 {
199 std::string absolutePath = &path[1];
200 void *userData = BOMCopierUserData(copier);
201 std::set<std::string> *cachePaths = (std::set<std::string>*)userData;
202 for (const std::string& cachePath : *cachePaths) {
203 if (startsWith(cachePath, absolutePath))
204 return BOMCopierContinue;
205 }
206 if (cachePaths->count(absolutePath)) {
207 return BOMCopierContinue;
208 }
209 return BOMCopierSkipFile;
210 }
211
212 static Disposition stringToDisposition(Diagnostics& diags, const std::string& str) {
213 if (diags.hasError())
214 return Unknown;
215 if (str == "Unknown")
216 return Unknown;
217 if (str == "InternalDevelopment")
218 return InternalDevelopment;
219 if (str == "Customer")
220 return Customer;
221 if (str == "InternalMinDevelopment")
222 return InternalMinDevelopment;
223 return Unknown;
224 }
225
226 static Platform stringToPlatform(Diagnostics& diags, const std::string& str) {
227 if (diags.hasError())
228 return unknown;
229 if (str == "unknown")
230 return unknown;
231 if (str == "macOS")
232 return macOS;
233 if (str == "iOS")
234 return iOS;
235 if (str == "tvOS")
236 return tvOS;
237 if (str == "watchOS")
238 return watchOS;
239 if (str == "bridgeOS")
240 return bridgeOS;
241 if (str == "iOSMac")
242 return iOSMac;
243 if (str == "UIKitForMac")
244 return iOSMac;
245 if (str == "iOS_simulator")
246 return iOS_simulator;
247 if (str == "tvOS_simulator")
248 return tvOS_simulator;
249 if (str == "watchOS_simulator")
250 return watchOS_simulator;
251 return unknown;
252 }
253
254 static FileFlags stringToFileFlags(Diagnostics& diags, const std::string& str) {
255 if (diags.hasError())
256 return NoFlags;
257 if (str == "NoFlags")
258 return NoFlags;
259 if (str == "MustBeInCache")
260 return MustBeInCache;
261 if (str == "ShouldBeExcludedFromCacheIfUnusedLeaf")
262 return ShouldBeExcludedFromCacheIfUnusedLeaf;
263 if (str == "RequiredClosure")
264 return RequiredClosure;
265 if (str == "DylibOrderFile")
266 return DylibOrderFile;
267 if (str == "DirtyDataOrderFile")
268 return DirtyDataOrderFile;
269 return NoFlags;
270 }
271
272 struct SharedCacheBuilderOptions {
273 Diagnostics diags;
274 std::set<std::string> roots;
275 std::string dylibCacheDir;
276 std::string artifactDir;
277 std::string release;
278 bool emitDevCaches = true;
279 bool emitElidedDylibs = true;
280 bool listConfigs = false;
281 bool copyRoots = false;
282 bool debug = false;
283 bool useMRM = false;
284 std::string dstRoot;
285 std::string emitJSONPath;
286 std::string buildAllPath;
287 std::string resultPath;
288 std::string baselineDifferenceResultPath;
289 std::string baselineCacheMapPath;
290 bool baselineCopyRoots = false;
291 };
292
293 static void loadMRMFiles(Diagnostics& diags,
294 MRMSharedCacheBuilder* sharedCacheBuilder,
295 const std::vector<std::tuple<std::string, std::string, FileFlags>>& inputFiles,
296 std::vector<std::pair<const void*, size_t>>& mappedFiles,
297 const std::set<std::string>& baselineCacheFiles) {
298
299 for (const std::tuple<std::string, std::string, FileFlags>& inputFile : inputFiles) {
300 const std::string& buildPath = std::get<0>(inputFile);
301 const std::string& runtimePath = std::get<1>(inputFile);
302 FileFlags fileFlags = std::get<2>(inputFile);
303
304 struct stat stat_buf;
305 int fd = ::open(buildPath.c_str(), O_RDONLY, 0);
306 if (fd == -1) {
307 if (baselineCacheFiles.count(runtimePath)) {
308 diags.error("can't open file '%s', errno=%d\n", buildPath.c_str(), errno);
309 return;
310 } else {
311 diags.verbose("can't open file '%s', errno=%d\n", buildPath.c_str(), errno);
312 continue;
313 }
314 }
315
316 if (fstat(fd, &stat_buf) == -1) {
317 if (baselineCacheFiles.count(runtimePath)) {
318 diags.error("can't stat open file '%s', errno=%d\n", buildPath.c_str(), errno);
319 ::close(fd);
320 return;
321 } else {
322 diags.verbose("can't stat open file '%s', errno=%d\n", buildPath.c_str(), errno);
323 ::close(fd);
324 continue;
325 }
326 }
327
328 const void* buffer = mmap(NULL, (size_t)stat_buf.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
329 if (buffer == MAP_FAILED) {
330 diags.error("mmap() for file at %s failed, errno=%d\n", buildPath.c_str(), errno);
331 ::close(fd);
332 }
333 ::close(fd);
334
335 mappedFiles.emplace_back(buffer, (size_t)stat_buf.st_size);
336
337 addFile(sharedCacheBuilder, runtimePath.c_str(), (uint8_t*)buffer, (size_t)stat_buf.st_size, fileFlags);
338 }
339 }
340
341 static void unloadMRMFiles(std::vector<std::pair<const void*, size_t>>& mappedFiles) {
342 for (auto mappedFile : mappedFiles)
343 ::munmap((void*)mappedFile.first, mappedFile.second);
344 }
345
346 static void writeMRMResults(bool cacheBuildSuccess, MRMSharedCacheBuilder* sharedCacheBuilder, const SharedCacheBuilderOptions& options) {
347 if (!cacheBuildSuccess) {
348 uint64_t errorCount = 0;
349 if (const char* const* errors = getErrors(sharedCacheBuilder, &errorCount)) {
350 for (uint64_t i = 0, e = errorCount; i != e; ++i) {
351 const char* errorMessage = errors[i];
352 fprintf(stderr, "ERROR: %s\n", errorMessage);
353 }
354 }
355 }
356
357 // Now emit each cache we generated, or the errors for them.
358 uint64_t cacheResultCount = 0;
359 if (const CacheResult* const* cacheResults = getCacheResults(sharedCacheBuilder, &cacheResultCount)) {
360 for (uint64_t i = 0, e = cacheResultCount; i != e; ++i) {
361 const CacheResult& result = *(cacheResults[i]);
362 // Always print the warnings if we have roots, even if there are errors
363 if ( (result.numErrors == 0) || !options.roots.empty() ) {
364 for (uint64_t warningIndex = 0; warningIndex != result.numWarnings; ++warningIndex) {
365 fprintf(stderr, "[%s] WARNING: %s\n", result.loggingPrefix, result.warnings[warningIndex]);
366 }
367 }
368 if (result.numErrors) {
369 for (uint64_t errorIndex = 0; errorIndex != result.numErrors; ++errorIndex) {
370 fprintf(stderr, "[%s] ERROR: %s\n", result.loggingPrefix, result.errors[errorIndex]);
371 }
372 cacheBuildSuccess = false;
373 }
374 }
375 }
376
377 if (!cacheBuildSuccess) {
378 exit(-1);
379 }
380
381 // If we built caches, then write everything out.
382 // TODO: Decide if we should we write any good caches anyway?
383 if (cacheBuildSuccess && !options.dstRoot.empty()) {
384 uint64_t fileResultCount = 0;
385 if (const FileResult* const* fileResults = getFileResults(sharedCacheBuilder, &fileResultCount)) {
386 for (uint64_t i = 0, e = fileResultCount; i != e; ++i) {
387 const FileResult& result = *(fileResults[i]);
388
389 switch (result.behavior) {
390 case AddFile:
391 break;
392 case ChangeFile:
393 continue;
394 }
395
396 if (!result.data)
397 continue;
398
399 const std::string path = options.dstRoot + result.path;
400 std::string pathTemplate = path + "-XXXXXX";
401 size_t templateLen = strlen(pathTemplate.c_str())+2;
402 char pathTemplateSpace[templateLen];
403 strlcpy(pathTemplateSpace, pathTemplate.c_str(), templateLen);
404 int fd = mkstemp(pathTemplateSpace);
405 if ( fd != -1 ) {
406 ::ftruncate(fd, result.size);
407 uint64_t writtenSize = pwrite(fd, result.data, result.size, 0);
408 if ( writtenSize == result.size ) {
409 ::fchmod(fd, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); // mkstemp() makes file "rw-------", switch it to "rw-r--r--"
410 if ( ::rename(pathTemplateSpace, path.c_str()) == 0) {
411 ::close(fd);
412 continue; // success
413 }
414 }
415 else {
416 fprintf(stderr, "ERROR: could not write file %s\n", pathTemplateSpace);
417 cacheBuildSuccess = false;
418 }
419 ::close(fd);
420 ::unlink(pathTemplateSpace);
421 }
422 else {
423 fprintf(stderr, "ERROR: could not open file %s\n", pathTemplateSpace);
424 cacheBuildSuccess = false;
425 }
426 }
427 }
428 }
429 }
430
431 static void buildCacheFromJSONManifest(Diagnostics& diags, const SharedCacheBuilderOptions& options,
432 const std::string& jsonManifestPath) {
433 dyld3::json::Node manifestNode = dyld3::json::readJSON(diags, jsonManifestPath.c_str());
434 if (diags.hasError())
435 return;
436
437 // Top level node should be a map of the options, files, and symlinks.
438 if (manifestNode.map.empty()) {
439 diags.error("Expected map for JSON manifest node\n");
440 return;
441 }
442
443 // Parse the nodes in the top level manifest node
444 const dyld3::json::Node& versionNode = dyld3::json::getRequiredValue(diags, manifestNode, "version");
445 uint64_t manifestVersion = dyld3::json::parseRequiredInt(diags, versionNode);
446 if (diags.hasError())
447 return;
448
449 const uint64_t supportedManifestVersion = 1;
450 if (manifestVersion != supportedManifestVersion) {
451 diags.error("JSON manfiest version of %lld is unsupported. Supported version is %lld\n",
452 manifestVersion, supportedManifestVersion);
453 return;
454 }
455 const dyld3::json::Node& buildOptionsNode = dyld3::json::getRequiredValue(diags, manifestNode, "buildOptions");
456 const dyld3::json::Node& filesNode = dyld3::json::getRequiredValue(diags, manifestNode, "files");
457 const dyld3::json::Node* symlinksNode = dyld3::json::getOptionalValue(diags, manifestNode, "symlinks");
458
459 // Parse the archs
460 const dyld3::json::Node& archsNode = dyld3::json::getRequiredValue(diags, buildOptionsNode, "archs");
461 if (diags.hasError())
462 return;
463 if (archsNode.array.empty()) {
464 diags.error("Build options archs node is not an array\n");
465 return;
466 }
467 const char* archs[archsNode.array.size()];
468 uint64_t archIndex = 0;
469 for (const dyld3::json::Node& archNode : archsNode.array) {
470 archs[archIndex++] = dyld3::json::parseRequiredString(diags, archNode).c_str();
471 }
472
473 // Parse the rest of the options node.
474 BuildOptions_v1 buildOptions;
475 buildOptions.version = dyld3::json::parseRequiredInt(diags, dyld3::json::getRequiredValue(diags, buildOptionsNode, "version"));
476 buildOptions.updateName = dyld3::json::parseRequiredString(diags, dyld3::json::getRequiredValue(diags, buildOptionsNode, "updateName")).c_str();
477 buildOptions.deviceName = dyld3::json::parseRequiredString(diags, dyld3::json::getRequiredValue(diags, buildOptionsNode, "deviceName")).c_str();
478 buildOptions.disposition = stringToDisposition(diags, dyld3::json::parseRequiredString(diags, dyld3::json::getRequiredValue(diags, buildOptionsNode, "disposition")));
479 buildOptions.platform = stringToPlatform(diags, dyld3::json::parseRequiredString(diags, dyld3::json::getRequiredValue(diags, buildOptionsNode, "platform")));
480 buildOptions.archs = archs;
481 buildOptions.numArchs = archsNode.array.size();
482 buildOptions.verboseDiagnostics = options.debug;
483 buildOptions.isLocallyBuiltCache = true;
484
485 if (diags.hasError())
486 return;
487
488 // Override the disposition if we don't want certaion caches.
489 if (!options.emitDevCaches) {
490 switch (buildOptions.disposition) {
491 case Unknown:
492 // Nothing we can do here as we can't assume what caches are built here.
493 break;
494 case InternalDevelopment:
495 // This builds both caches, but we don't want dev
496 buildOptions.disposition = Customer;
497 break;
498 case Customer:
499 // This is already only the customer cache
500 break;
501 case InternalMinDevelopment:
502 diags.error("Cannot request no dev cache for InternalMinDevelopment as that is already only a dev cache\n");
503 break;
504 }
505 }
506
507 if (diags.hasError())
508 return;
509
510 struct MRMSharedCacheBuilder* sharedCacheBuilder = createSharedCacheBuilder(&buildOptions);
511
512 // Parse the files
513 if (filesNode.array.empty()) {
514 diags.error("Build options files node is not an array\n");
515 return;
516 }
517
518 std::vector<std::tuple<std::string, std::string, FileFlags>> inputFiles;
519 for (const dyld3::json::Node& fileNode : filesNode.array) {
520 const std::string& path = dyld3::json::parseRequiredString(diags, dyld3::json::getRequiredValue(diags, fileNode, "path")).c_str();
521 FileFlags fileFlags = stringToFileFlags(diags, dyld3::json::parseRequiredString(diags, dyld3::json::getRequiredValue(diags, fileNode, "flags")));
522
523 // We can optionally have a sourcePath entry which is the path to get the source content from instead of the install path
524 std::string sourcePath;
525 const dyld3::json::Node* sourcePathNode = dyld3::json::getOptionalValue(diags, fileNode, "sourcePath");
526 if ( sourcePathNode != nullptr ) {
527 if (!sourcePathNode->array.empty()) {
528 diags.error("sourcePath node cannot be an array\n");
529 return;
530 }
531 if (!sourcePathNode->map.empty()) {
532 diags.error("sourcePath node cannot be a map\n");
533 return;
534 }
535 sourcePath = sourcePathNode->value;
536 } else {
537 sourcePath = path;
538 }
539
540 // Check if one of the -root's has this path
541 bool foundInOverlay = false;
542 for (const std::string& overlay : options.roots) {
543 struct stat sb;
544 std::string filePath = overlay + path;
545 if (!stat(filePath.c_str(), &sb)) {
546 foundInOverlay = true;
547 diags.verbose("Taking '%s' from overlay instead of dylib cache\n", path.c_str());
548 inputFiles.push_back({ filePath, path, fileFlags });
549 break;
550 }
551 }
552
553 if (foundInOverlay)
554 continue;
555
556 // Build paths are relative to the build artifact root directory.
557 std::string buildPath;
558 switch (fileFlags) {
559 case NoFlags:
560 case MustBeInCache:
561 case ShouldBeExcludedFromCacheIfUnusedLeaf:
562 case RequiredClosure:
563 buildPath = "." + sourcePath;
564 break;
565 case DylibOrderFile:
566 case DirtyDataOrderFile:
567 buildPath = "." + sourcePath;
568 break;
569 }
570 inputFiles.push_back({ buildPath, path, fileFlags });
571 }
572
573 if (diags.hasError())
574 return;
575
576 // Parse the baseline from the map if we have it
577 std::set<std::string> baselineDylibs;
578 if ( !options.baselineCacheMapPath.empty() ) {
579 dyld3::json::Node mapNode = dyld3::json::readJSON(diags, options.baselineCacheMapPath.c_str());
580 if (diags.hasError())
581 return;
582
583 // Top level node should be a map of the version and files
584 if (mapNode.map.empty()) {
585 diags.error("Expected map for JSON cache map node\n");
586 return;
587 }
588
589 // Parse the nodes in the top level manifest node
590 const dyld3::json::Node& versionNode = dyld3::json::getRequiredValue(diags, mapNode, "version");
591 uint64_t mapVersion = dyld3::json::parseRequiredInt(diags, versionNode);
592 if (diags.hasError())
593 return;
594
595 const uint64_t supportedMapVersion = 1;
596 if (mapVersion != supportedMapVersion) {
597 diags.error("JSON map version of %lld is unsupported. Supported version is %lld\n",
598 mapVersion, supportedMapVersion);
599 return;
600 }
601
602 // Parse the images
603 const dyld3::json::Node& imagesNode = dyld3::json::getRequiredValue(diags, mapNode, "images");
604 if (diags.hasError())
605 return;
606 if (imagesNode.array.empty()) {
607 diags.error("Images node is not an array\n");
608 return;
609 }
610
611 for (const dyld3::json::Node& imageNode : imagesNode.array) {
612 const dyld3::json::Node& pathNode = dyld3::json::getRequiredValue(diags, imageNode, "path");
613 if (pathNode.value.empty()) {
614 diags.error("Image path node is not a string\n");
615 return;
616 }
617 baselineDylibs.insert(pathNode.value);
618 }
619 }
620
621 std::vector<std::pair<const void*, size_t>> mappedFiles;
622 loadMRMFiles(diags, sharedCacheBuilder, inputFiles, mappedFiles, baselineDylibs);
623
624 if (diags.hasError())
625 return;
626
627 // Parse the symlinks if we have them
628 if (symlinksNode) {
629 if (symlinksNode->array.empty()) {
630 diags.error("Build options symlinks node is not an array\n");
631 return;
632 }
633 for (const dyld3::json::Node& symlinkNode : symlinksNode->array) {
634 const std::string& fromPath = dyld3::json::parseRequiredString(diags, dyld3::json::getRequiredValue(diags, symlinkNode, "path")).c_str();
635 const std::string& toPath = dyld3::json::parseRequiredString(diags, dyld3::json::getRequiredValue(diags, symlinkNode, "target")).c_str();
636 addSymlink(sharedCacheBuilder, fromPath.c_str(), toPath.c_str());
637 }
638 }
639
640 if (diags.hasError())
641 return;
642
643 // Don't create a directory if we are skipping writes, which means we have no dstRoot set
644 if (!options.dstRoot.empty())
645 (void)mkpath_np((options.dstRoot + "/System/Library/Caches/com.apple.dyld/").c_str(), 0755);
646
647 // Actually build the cache.
648 bool cacheBuildSuccess = runSharedCacheBuilder(sharedCacheBuilder);
649
650 // Compare this cache to the baseline cache and see if we have any roots to copy over
651 if (!options.baselineDifferenceResultPath.empty() || options.baselineCopyRoots) {
652 std::set<std::string> newDylibs;
653 if (cacheBuildSuccess) {
654 uint64_t fileResultCount = 0;
655 if (const char* const* fileResults = getFilesToRemove(sharedCacheBuilder, &fileResultCount)) {
656 for (uint64_t i = 0; i != fileResultCount; ++i)
657 newDylibs.insert(fileResults[i]);
658 }
659 }
660
661 if (options.baselineCopyRoots) {
662 // Work out the set of dylibs in the old cache but not the new one
663 std::set<std::string> dylibsMissingFromNewCache;
664 for (const std::string& baselineDylib : baselineDylibs) {
665 if (!newDylibs.count(baselineDylib))
666 dylibsMissingFromNewCache.insert(baselineDylib);
667 }
668
669 if (!dylibsMissingFromNewCache.empty()) {
670 BOMCopier copier = BOMCopierNewWithSys(BomSys_default());
671 BOMCopierSetUserData(copier, (void*)&dylibsMissingFromNewCache);
672 BOMCopierSetCopyFileStartedHandler(copier, filteredCopyIncludingPaths);
673 std::string dylibCacheRootDir = realFilePath(options.dylibCacheDir);
674 if (dylibCacheRootDir == "") {
675 fprintf(stderr, "Could not find dylib Root directory to copy baseline roots from\n");
676 exit(1);
677 }
678 BOMCopierCopy(copier, dylibCacheRootDir.c_str(), options.dstRoot.c_str());
679 BOMCopierFree(copier);
680
681 for (const std::string& dylibMissingFromNewCache : dylibsMissingFromNewCache) {
682 diags.verbose("Dylib missing from new cache: '%s'\n", dylibMissingFromNewCache.c_str());
683 }
684 }
685 }
686
687 if (!options.baselineDifferenceResultPath.empty()) {
688 auto cppToObjStr = [](const std::string& str) {
689 return [NSString stringWithUTF8String:str.c_str()];
690 };
691
692 // Work out the set of dylibs in the cache and taken from the -root
693 NSMutableArray<NSString*>* dylibsFromRoots = [NSMutableArray array];
694 for (auto& root : options.roots) {
695 for (const std::string& dylibInstallName : newDylibs) {
696 struct stat sb;
697 std::string filePath = root + "/" + dylibInstallName;
698 if (!stat(filePath.c_str(), &sb)) {
699 [dylibsFromRoots addObject:cppToObjStr(dylibInstallName)];
700 }
701 }
702 }
703
704 // Work out the set of dylibs in the new cache but not in the baseline cache.
705 NSMutableArray<NSString*>* dylibsMissingFromBaselineCache = [NSMutableArray array];
706 for (const std::string& newDylib : newDylibs) {
707 if (!baselineDylibs.count(newDylib))
708 [dylibsMissingFromBaselineCache addObject:cppToObjStr(newDylib)];
709 }
710
711 NSMutableDictionary* cacheDict = [[NSMutableDictionary alloc] init];
712 cacheDict[@"root-paths-in-cache"] = dylibsFromRoots;
713 cacheDict[@"device-paths-to-delete"] = dylibsMissingFromBaselineCache;
714
715 NSError* error = nil;
716 NSData* outData = [NSPropertyListSerialization dataWithPropertyList:cacheDict
717 format:NSPropertyListBinaryFormat_v1_0
718 options:0
719 error:&error];
720 (void)[outData writeToFile:cppToObjStr(options.baselineDifferenceResultPath) atomically:YES];
721 }
722 }
723
724 writeMRMResults(cacheBuildSuccess, sharedCacheBuilder, options);
725
726 destroySharedCacheBuilder(sharedCacheBuilder);
727
728 unloadMRMFiles(mappedFiles);
729 }
730
731 int main(int argc, const char* argv[])
732 {
733 @autoreleasepool {
734 __block Diagnostics diags;
735 SharedCacheBuilderOptions options;
736 std::string jsonManifestPath;
737 char* tempRootsDir = strdup("/tmp/dyld_shared_cache_builder.XXXXXX");
738
739 mkdtemp(tempRootsDir);
740
741 for (int i = 1; i < argc; ++i) {
742 const char* arg = argv[i];
743 if (arg[0] == '-') {
744 if (strcmp(arg, "-debug") == 0) {
745 diags = Diagnostics(true);
746 options.debug = true;
747 } else if (strcmp(arg, "-list_configs") == 0) {
748 options.listConfigs = true;
749 } else if (strcmp(arg, "-root") == 0) {
750 std::string realpath = realPath(argv[++i]);
751 if ( realpath.empty() || !fileExists(realpath) ) {
752 fprintf(stderr, "-root path doesn't exist: %s\n", argv[i]);
753 exit(-1);
754 }
755 options.roots.insert(realpath);
756 } else if (strcmp(arg, "-copy_roots") == 0) {
757 options.copyRoots = true;
758 } else if (strcmp(arg, "-dylib_cache") == 0) {
759 options.dylibCacheDir = realPath(argv[++i]);
760 } else if (strcmp(arg, "-artifact") == 0) {
761 options.artifactDir = realPath(argv[++i]);
762 } else if (strcmp(arg, "-no_development_cache") == 0) {
763 options.emitDevCaches = false;
764 } else if (strcmp(arg, "-no_overflow_dylibs") == 0) {
765 options.emitElidedDylibs = false;
766 } else if (strcmp(arg, "-development_cache") == 0) {
767 options.emitDevCaches = true;
768 } else if (strcmp(arg, "-overflow_dylibs") == 0) {
769 options.emitElidedDylibs = true;
770 } else if (strcmp(arg, "-mrm") == 0) {
771 options.useMRM = true;
772 } else if (strcmp(arg, "-emit_json") == 0) {
773 options.emitJSONPath = realPath(argv[++i]);
774 } else if (strcmp(arg, "-json_manifest") == 0) {
775 jsonManifestPath = realPath(argv[++i]);
776 } else if (strcmp(arg, "-build_all") == 0) {
777 options.buildAllPath = realPath(argv[++i]);
778 } else if (strcmp(arg, "-dst_root") == 0) {
779 options.dstRoot = realPath(argv[++i]);
780 } else if (strcmp(arg, "-release") == 0) {
781 options.release = argv[++i];
782 } else if (strcmp(arg, "-results") == 0) {
783 options.resultPath = realPath(argv[++i]);
784 } else if (strcmp(arg, "-baseline_diff_results") == 0) {
785 options.baselineDifferenceResultPath = realPath(argv[++i]);
786 } else if (strcmp(arg, "-baseline_copy_roots") == 0) {
787 options.baselineCopyRoots = true;
788 } else if (strcmp(arg, "-baseline_cache_map") == 0) {
789 options.baselineCacheMapPath = realPath(argv[++i]);
790 } else {
791 //usage();
792 fprintf(stderr, "unknown option: %s\n", arg);
793 exit(-1);
794 }
795 } else {
796 fprintf(stderr, "unknown option: %s\n", arg);
797 exit(-1);
798 }
799 }
800 (void)options.emitElidedDylibs; // not implemented yet
801
802 time_t mytime = time(0);
803 fprintf(stderr, "Started: %s", asctime(localtime(&mytime)));
804 processRoots(diags, options.roots, tempRootsDir);
805
806 struct rlimit rl = { OPEN_MAX, OPEN_MAX };
807 (void)setrlimit(RLIMIT_NOFILE, &rl);
808
809 if (options.dylibCacheDir.empty() && options.artifactDir.empty() && options.release.empty()) {
810 fprintf(stderr, "you must specify either -dylib_cache, -artifact or -release\n");
811 exit(-1);
812 } else if (!options.dylibCacheDir.empty() && !options.release.empty()) {
813 fprintf(stderr, "you may not use -dylib_cache and -release at the same time\n");
814 exit(-1);
815 } else if (!options.dylibCacheDir.empty() && !options.artifactDir.empty()) {
816 fprintf(stderr, "you may not use -dylib_cache and -artifact at the same time\n");
817 exit(-1);
818 }
819
820 if (jsonManifestPath.empty() && options.buildAllPath.empty()) {
821 fprintf(stderr, "Must specify a -json_manifest path OR a -build_all path\n");
822 exit(-1);
823 }
824
825 if (!options.buildAllPath.empty()) {
826 if (!options.dstRoot.empty()) {
827 fprintf(stderr, "Cannot combine -dst_root and -build_all\n");
828 exit(-1);
829 }
830 if (!jsonManifestPath.empty()) {
831 fprintf(stderr, "Cannot combine -json_manifest and -build_all\n");
832 exit(-1);
833 }
834 if (!options.baselineDifferenceResultPath.empty()) {
835 fprintf(stderr, "Cannot combine -baseline_diff_results and -build_all\n");
836 exit(-1);
837 }
838 if (options.baselineCopyRoots) {
839 fprintf(stderr, "Cannot combine -baseline_copy_roots and -build_all\n");
840 exit(-1);
841 }
842 if (!options.baselineCacheMapPath.empty()) {
843 fprintf(stderr, "Cannot combine -baseline_cache_map and -build_all\n");
844 exit(-1);
845 }
846 } else if (!options.listConfigs) {
847 if (options.dstRoot.empty()) {
848 fprintf(stderr, "Must specify a valid -dst_root OR -list_configs\n");
849 exit(-1);
850 }
851
852 if (jsonManifestPath.empty()) {
853 fprintf(stderr, "Must specify a -json_manifest path OR -list_configs\n");
854 exit(-1);
855 }
856 }
857
858 if (!options.baselineDifferenceResultPath.empty() && (options.roots.size() > 1)) {
859 fprintf(stderr, "Cannot use -baseline_diff_results with more that one -root\n");
860 exit(-1);
861 }
862
863 // Some options don't work with a JSON manifest
864 if (!jsonManifestPath.empty()) {
865 if (!options.resultPath.empty()) {
866 fprintf(stderr, "Cannot use -results with -json_manifest\n");
867 exit(-1);
868 }
869 if (!options.baselineDifferenceResultPath.empty() && options.baselineCacheMapPath.empty()) {
870 fprintf(stderr, "Must use -baseline_cache_map with -baseline_diff_results when using -json_manifest\n");
871 exit(-1);
872 }
873 if (options.baselineCopyRoots && options.baselineCacheMapPath.empty()) {
874 fprintf(stderr, "Must use -baseline_cache_map with -baseline_copy_roots when using -json_manifest\n");
875 exit(-1);
876 }
877 } else {
878 if (!options.baselineCacheMapPath.empty()) {
879 fprintf(stderr, "Cannot use -baseline_cache_map without -json_manifest\n");
880 exit(-1);
881 }
882 }
883
884 if (!options.baselineCacheMapPath.empty()) {
885 if (options.baselineDifferenceResultPath.empty() && options.baselineCopyRoots) {
886 fprintf(stderr, "Must use -baseline_cache_map with -baseline_diff_results or -baseline_copy_roots\n");
887 exit(-1);
888 }
889 }
890
891 // Find all the JSON files if we use -build_all
892 __block std::vector<std::string> jsonPaths;
893 if (!options.buildAllPath.empty()) {
894 struct stat stat_buf;
895 if (stat(options.buildAllPath.c_str(), &stat_buf) != 0) {
896 fprintf(stderr, "Could not find -build_all path '%s'\n", options.buildAllPath.c_str());
897 exit(-1);
898 }
899
900 if ( (stat_buf.st_mode & S_IFMT) != S_IFDIR ) {
901 fprintf(stderr, "-build_all path is not a directory '%s'\n", options.buildAllPath.c_str());
902 exit(-1);
903 }
904
905 auto processFile = ^(const std::string& path, const struct stat& statBuf) {
906 if ( !endsWith(path, ".json") )
907 return;
908
909 jsonPaths.push_back(path);
910 };
911
912 iterateDirectoryTree("", options.buildAllPath,
913 ^(const std::string& dirPath) { return false; },
914 processFile, true /* process files */, false /* recurse */);
915
916 if (jsonPaths.empty()) {
917 fprintf(stderr, "Didn't find any .json files inside -build_all path: %s\n", options.buildAllPath.c_str());
918 exit(-1);
919 }
920
921 if (options.listConfigs) {
922 for (const std::string& path : jsonPaths) {
923 fprintf(stderr, "Found config: %s\n", path.c_str());
924 }
925 exit(-1);
926 }
927 }
928
929 if (!options.artifactDir.empty()) {
930 // Find the dylib cache dir from inside the artifact dir
931 struct stat stat_buf;
932 if (stat(options.artifactDir.c_str(), &stat_buf) != 0) {
933 fprintf(stderr, "Could not find artifact path '%s'\n", options.artifactDir.c_str());
934 exit(-1);
935 }
936 std::string dir = options.artifactDir + "/AppleInternal/Developer/DylibCaches";
937 if (stat(dir.c_str(), &stat_buf) != 0) {
938 fprintf(stderr, "Could not find artifact path '%s'\n", dir.c_str());
939 exit(-1);
940 }
941
942 if (!options.release.empty()) {
943 // Use the given release
944 options.dylibCacheDir = dir + "/" + options.release + ".dlc";
945 } else {
946 // Find a release directory
947 __block std::vector<std::string> subDirectories;
948 iterateDirectoryTree("", dir, ^(const std::string& dirPath) {
949 subDirectories.push_back(dirPath);
950 return false;
951 }, nullptr, false, false);
952
953 if (subDirectories.empty()) {
954 fprintf(stderr, "Could not find dlc subdirectories inside '%s'\n", dir.c_str());
955 exit(-1);
956 }
957
958 if (subDirectories.size() > 1) {
959 fprintf(stderr, "Found too many subdirectories inside artifact path '%s'. Use -release to select one\n", dir.c_str());
960 exit(-1);
961 }
962
963 options.dylibCacheDir = subDirectories.front();
964 }
965 }
966
967 if (options.dylibCacheDir.empty()) {
968 options.dylibCacheDir = std::string("/AppleInternal/Developer/DylibCaches/") + options.release + ".dlc";
969 }
970
971 //Move into the dir so we can use relative path manifests
972 chdir(options.dylibCacheDir.c_str());
973
974 dispatch_async(dispatch_get_main_queue(), ^{
975 if (!options.buildAllPath.empty()) {
976 bool requiresConcurrencyLimit = false;
977 dispatch_semaphore_t concurrencyLimit = NULL;
978 // Try build 1 cache per 8GB of RAM
979 uint64_t memSize = 0;
980 size_t sz = sizeof(memSize);
981 if ( sysctlbyname("hw.memsize", &memSize, &sz, NULL, 0) == 0 ) {
982 uint64_t maxThreads = std::max(memSize / 0x200000000ULL, 1ULL);
983 fprintf(stderr, "Detected %lldGb or less of memory, limiting concurrency to %lld threads\n",
984 memSize / (1 << 30), maxThreads);
985 requiresConcurrencyLimit = true;
986 concurrencyLimit = dispatch_semaphore_create(maxThreads);
987 }
988
989 dispatch_apply(jsonPaths.size(), DISPATCH_APPLY_AUTO, ^(size_t index) {
990 // Horrible hack to limit concurrency in low spec build machines.
991 if (requiresConcurrencyLimit) { dispatch_semaphore_wait(concurrencyLimit, DISPATCH_TIME_FOREVER); }
992
993 const std::string& jsonPath = jsonPaths[index];
994 buildCacheFromJSONManifest(diags, options, jsonPath);
995
996 if (requiresConcurrencyLimit) { dispatch_semaphore_signal(concurrencyLimit); }
997 });
998 } else {
999 buildCacheFromJSONManifest(diags, options, jsonManifestPath);
1000 }
1001
1002 const char* args[8];
1003 args[0] = (char*)"/bin/rm";
1004 args[1] = (char*)"-rf";
1005 args[2] = (char*)tempRootsDir;
1006 args[3] = nullptr;
1007 (void)runCommandAndWait(diags, args);
1008
1009 if (diags.hasError()) {
1010 fprintf(stderr, "dyld_shared_cache_builder: error: %s", diags.errorMessage().c_str());
1011 exit(-1);
1012 }
1013
1014 for (const std::string& warn : diags.warnings()) {
1015 fprintf(stderr, "dyld_shared_cache_builder: warning: %s\n", warn.c_str());
1016 }
1017
1018 // Finally, write the roots.txt to tell us which roots we pulled in
1019 if (!options.dstRoot.empty())
1020 writeRootList(options.dstRoot + "/System/Library/Caches/com.apple.dyld", options.roots);
1021 exit(0);
1022 });
1023 }
1024
1025 dispatch_main();
1026
1027 return 0;
1028 }