1 /* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil -*-
3 * Copyright (c) 2016 Apple Inc. All rights reserved.
5 * @APPLE_LICENSE_HEADER_START@
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
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.
22 * @APPLE_LICENSE_HEADER_END@
25 #include <sys/types.h>
28 #include <sys/resource.h>
29 #include <mach/mach.h>
30 #include <mach/mach_time.h>
42 #include <sys/param.h>
43 #include <sys/sysctl.h>
44 #include <sys/resource.h>
55 #include <unordered_set>
63 #include <Foundation/NSData.h>
64 #include <Foundation/NSDictionary.h>
65 #include <Foundation/NSPropertyList.h>
66 #include <Foundation/NSString.h>
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"
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
80 extern char** environ;
82 static dispatch_queue_t build_queue;
84 int runCommandAndWait(Diagnostics& diags, const char* args[])
88 int res = posix_spawn(&pid, args[0], nullptr, nullptr, (char**)args, environ);
90 diags.error("Failed to spawn %s: %s (%d)", args[0], strerror(res), res);
93 res = waitpid(pid, &status, 0);
94 } while (res == -1 && errno == EINTR);
96 if (WIFEXITED(status)) {
97 res = WEXITSTATUS(status);
106 void processRoots(Diagnostics& diags, std::list<std::string>& roots, const char *tempRootsDir)
108 std::list<std::string> processedRoots;
113 for (const auto& root : roots) {
114 res = stat(root.c_str(), &sb);
116 if (res == 0 && S_ISDIR(sb.st_mode)) {
117 processedRoots.push_back(root);
121 char tempRootDir[MAXPATHLEN];
122 strlcpy(tempRootDir, tempRootsDir, MAXPATHLEN);
123 strlcat(tempRootDir, "/XXXXXXXX", MAXPATHLEN);
124 mkdtemp(tempRootDir);
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;
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;
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;
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;
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;
162 diags.error("unknown archive type: %s", root.c_str());
166 if (res != runCommandAndWait(diags, args)) {
167 fprintf(stderr, "Could not expand archive %s: %s (%d)", root.c_str(), strerror(res), res);
170 for (auto& existingRoot : processedRoots) {
171 if (existingRoot == tempRootDir)
175 processedRoots.push_back(tempRootDir);
178 roots = processedRoots;
181 void writeRootList(const std::string& dstRoot, const std::list<std::string>& roots)
183 if (roots.size() == 0)
186 std::string rootFile = dstRoot + "/roots.txt";
187 FILE* froots = ::fopen(rootFile.c_str(), "w");
191 for (auto& root : roots) {
192 fprintf(froots, "%s\n", root.c_str());
198 struct FilteredCopyOptions {
199 Diagnostics* diags = nullptr;
200 std::set<std::string>* cachePaths = nullptr;
201 std::set<std::string>* dylibsFoundInRoots = nullptr;
204 BOMCopierCopyOperation filteredCopyIncludingPaths(BOMCopier copier, const char* path, BOMFSObjType type, off_t size)
206 std::string absolutePath = &path[1];
207 const FilteredCopyOptions *userData = (const FilteredCopyOptions*)BOMCopierUserData(copier);
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;
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;
221 if (userData->cachePaths->count(absolutePath)) {
222 userData->diags->verbose("Copying dylib from shared cache artifact: '%s'\n", absolutePath.c_str());
223 return BOMCopierContinue;
225 return BOMCopierSkipFile;
228 static Disposition stringToDisposition(Diagnostics& diags, const std::string& str) {
229 if (diags.hasError())
231 if (str == "Unknown")
233 if (str == "InternalDevelopment")
234 return InternalDevelopment;
235 if (str == "Customer")
237 if (str == "InternalMinDevelopment")
238 return InternalMinDevelopment;
242 static Platform stringToPlatform(Diagnostics& diags, const std::string& str) {
243 if (diags.hasError())
245 if (str == "unknown")
247 if ( (str == "macOS") || (str == "osx") )
253 if (str == "watchOS")
255 if (str == "bridgeOS")
259 if (str == "UIKitForMac")
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;
270 static FileFlags stringToFileFlags(Diagnostics& diags, const std::string& str) {
271 if (diags.hasError())
273 if (str == "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;
290 struct SharedCacheBuilderOptions {
292 std::list<std::string> roots;
293 std::string dylibCacheDir;
294 std::string artifactDir;
296 bool emitDevCaches = true;
297 bool emitCustomerCaches = true;
298 bool emitElidedDylibs = true;
299 bool listConfigs = false;
300 bool copyRoots = false;
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;
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) {
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);
325 struct stat stat_buf;
326 int fd = ::open(buildPath.c_str(), O_RDONLY, 0);
328 if (baselineCacheFiles.count(runtimePath)) {
329 diags.error("can't open file '%s', errno=%d\n", buildPath.c_str(), errno);
332 diags.verbose("can't open file '%s', errno=%d\n", buildPath.c_str(), errno);
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);
343 diags.verbose("can't stat open file '%s', errno=%d\n", buildPath.c_str(), errno);
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);
356 mappedFiles.emplace_back(buffer, (size_t)stat_buf.st_size);
358 addFile(sharedCacheBuilder, runtimePath.c_str(), (uint8_t*)buffer, (size_t)stat_buf.st_size, fileFlags);
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);
367 static ssize_t write64(int fildes, const void *buf, size_t nbyte)
369 unsigned char* uchars = (unsigned char*)buf;
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.
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
387 size_t limit = 0x7FFFFFFF;
388 size_t towrite = nbyte < limit ? nbyte : limit;
389 ssize_t wrote = write(fildes, uchars, towrite);
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);
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]);
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]);
435 cacheBuildSuccess = false;
440 if (!cacheBuildSuccess) {
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]);
452 switch (result.behavior) {
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);
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) {
479 fprintf(stderr, "ERROR: could not write file %s\n", pathTemplateSpace);
480 cacheBuildSuccess = false;
483 ::unlink(pathTemplateSpace);
486 fprintf(stderr, "ERROR: could not open file %s\n", pathTemplateSpace);
487 cacheBuildSuccess = false;
492 // Give up if we couldn't write the caches
493 if (!cacheBuildSuccess) {
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() )
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);
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) {
525 fprintf(stderr, "ERROR: could not write file %s\n", pathTemplateSpace);
526 cacheBuildSuccess = false;
529 ::unlink(pathTemplateSpace);
532 fprintf(stderr, "ERROR: could not open file %s\n", pathTemplateSpace);
533 cacheBuildSuccess = false;
538 // Give up if we couldn't write the cache maps
539 if (!cacheBuildSuccess) {
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())
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");
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())
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);
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");
576 const dyld3::json::Node& archsNode = dyld3::json::getRequiredValue(diags, buildOptionsNode, "archs");
577 if (diags.hasError())
579 if (archsNode.array.empty()) {
580 diags.error("Build options archs node is not an array\n");
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;
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() ) {
603 validArchs += jsonArch;
605 diags.error("Command line -arch '%s' is not valid for this device. Valid archs are (%s)\n", cmdLineArch.c_str(), validArchs.c_str());
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;
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"));
629 if (diags.hasError())
632 // Override the disposition if we don't want certaion caches.
633 switch (buildOptions.disposition) {
635 // Nothing we can do here as we can't assume what caches are built here.
637 case InternalDevelopment:
638 if (!options.emitDevCaches && !options.emitCustomerCaches) {
639 diags.error("both -no_customer_cache and -no_development_cache passed\n");
642 if (!options.emitDevCaches) {
643 // This builds both caches, but we don't want dev
644 buildOptions.disposition = Customer;
646 if (!options.emitCustomerCaches) {
647 // This builds both caches, but we don't want customer
648 buildOptions.disposition = InternalMinDevelopment;
652 if (!options.emitCustomerCaches) {
653 diags.error("Cannot request no customer cache for Customer as that is already only a customer cache\n");
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");
663 if (diags.hasError())
666 struct MRMSharedCacheBuilder* sharedCacheBuilder = createSharedCacheBuilder((const BuildOptions_v1*)&buildOptions);
669 if (filesNode.array.empty()) {
670 diags.error("Build options files node is not an array\n");
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")));
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");
688 if (!sourcePathNode->map.empty()) {
689 diags.error("sourcePath node cannot be a map\n");
692 sourcePath = sourcePathNode->value;
697 std::string buildPath = sourcePath;
699 // Check if one of the -root's has this path
700 bool foundInOverlay = false;
701 for (const std::string& overlay : options.roots) {
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);
716 // Build paths are relative to the build artifact root directory.
720 case ShouldBeExcludedFromCacheIfUnusedLeaf:
721 case RequiredClosure:
723 case DirtyDataOrderFile:
724 case ObjCOptimizationsFile:
725 buildPath = "." + buildPath;
728 inputFiles.push_back({ buildPath, path, fileFlags });
731 if (diags.hasError())
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())
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");
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())
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);
761 const dyld3::json::Node& imagesNode = dyld3::json::getRequiredValue(diags, mapNode, "images");
762 if (diags.hasError())
764 if (imagesNode.array.empty()) {
765 diags.error("Images node is not an array\n");
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");
775 unionBaselineDylibs.insert(pathNode.value);
779 std::vector<std::pair<const void*, size_t>> mappedFiles;
780 loadMRMFiles(diags, sharedCacheBuilder, inputFiles, mappedFiles, unionBaselineDylibs);
782 if (diags.hasError())
785 // Parse the symlinks if we have them
787 if (symlinksNode->array.empty()) {
788 diags.error("Build options symlinks node is not an array\n");
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());
798 if (diags.hasError())
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);
806 (void)mkpath_np((options.dstRoot + IPHONE_DYLD_SHARED_CACHE_DIR).c_str(), 0755);
810 // Actually build the cache.
811 bool cacheBuildSuccess = runSharedCacheBuilder(sharedCacheBuilder);
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]);
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");
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);
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");
850 BOMCopierCopy(copier, dylibCacheRootDir.c_str(), options.dstRoot.c_str());
851 BOMCopierFree(copier);
853 for (const std::string& dylibMissingFromNewCache : dylibsMissingFromNewCaches) {
854 diags.verbose("Dylib missing from new cache: '%s'\n", dylibMissingFromNewCache.c_str());
859 if (!options.baselineDifferenceResultPath.empty()) {
860 auto cppToObjStr = [](const std::string& str) {
861 return [NSString stringWithUTF8String:str.c_str()];
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) {
869 std::string filePath = root + "/" + dylibInstallName;
870 if (!stat(filePath.c_str(), &sb)) {
871 [dylibsFromRoots addObject:cppToObjStr(dylibInstallName)];
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)];
883 NSMutableDictionary* cacheDict = [[NSMutableDictionary alloc] init];
884 cacheDict[@"root-paths-in-cache"] = dylibsFromRoots;
885 cacheDict[@"device-paths-to-delete"] = dylibsMissingFromBaselineCache;
887 NSError* error = nil;
888 NSData* outData = [NSPropertyListSerialization dataWithPropertyList:cacheDict
889 format:NSPropertyListBinaryFormat_v1_0
892 (void)[outData writeToFile:cppToObjStr(options.baselineDifferenceResultPath) atomically:YES];
896 bool wroteCaches = writeMRMResults(cacheBuildSuccess, sharedCacheBuilder, options);
898 destroySharedCacheBuilder(sharedCacheBuilder);
900 unloadMRMFiles(mappedFiles);
907 int main(int argc, const char* argv[])
910 __block Diagnostics diags;
911 SharedCacheBuilderOptions options;
912 std::string jsonManifestPath;
913 char* tempRootsDir = strdup("/tmp/dyld_shared_cache_builder.XXXXXX");
915 mkdtemp(tempRootsDir);
917 for (int i = 1; i < argc; ++i) {
918 const char* arg = argv[i];
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]);
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);
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]);
974 options.baselineCacheMapPaths.push_back(path);
975 } else if (strcmp(arg, "-arch") == 0) {
977 options.cmdLineArchs.insert(argv[i]);
980 fprintf(stderr, "-arch missing architecture name");
985 fprintf(stderr, "unknown option: %s\n", arg);
989 fprintf(stderr, "unknown option: %s\n", arg);
993 (void)options.emitElidedDylibs; // not implemented yet
995 time_t mytime = time(0);
996 fprintf(stderr, "Started: %s", asctime(localtime(&mytime)));
997 processRoots(diags, options.roots, tempRootsDir);
999 struct rlimit rl = { OPEN_MAX, OPEN_MAX };
1000 (void)setrlimit(RLIMIT_NOFILE, &rl);
1002 if (options.dylibCacheDir.empty() && options.artifactDir.empty() && options.release.empty()) {
1003 fprintf(stderr, "you must specify either -dylib_cache, -artifact or -release\n");
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");
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");
1013 if (jsonManifestPath.empty() && options.buildAllPath.empty()) {
1014 fprintf(stderr, "Must specify a -json_manifest path OR a -build_all path\n");
1018 if (!options.buildAllPath.empty()) {
1019 if (!options.dstRoot.empty()) {
1020 fprintf(stderr, "Cannot combine -dst_root and -build_all\n");
1023 if (!jsonManifestPath.empty()) {
1024 fprintf(stderr, "Cannot combine -json_manifest and -build_all\n");
1027 if (!options.baselineDifferenceResultPath.empty()) {
1028 fprintf(stderr, "Cannot combine -baseline_diff_results and -build_all\n");
1031 if (options.baselineCopyRoots) {
1032 fprintf(stderr, "Cannot combine -baseline_copy_roots and -build_all\n");
1035 if (!options.baselineCacheMapPaths.empty()) {
1036 fprintf(stderr, "Cannot combine -baseline_cache_map and -build_all\n");
1039 } else if (!options.listConfigs) {
1040 if (options.dstRoot.empty()) {
1041 fprintf(stderr, "Must specify a valid -dst_root OR -list_configs\n");
1045 if (jsonManifestPath.empty()) {
1046 fprintf(stderr, "Must specify a -json_manifest path OR -list_configs\n");
1051 if (!options.baselineDifferenceResultPath.empty() && (options.roots.size() > 1)) {
1052 fprintf(stderr, "Cannot use -baseline_diff_results with more that one -root\n");
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");
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");
1066 if (options.baselineCopyRoots && options.baselineCacheMapPaths.empty()) {
1067 fprintf(stderr, "Must use -baseline_cache_map with -baseline_copy_roots when using -json_manifest\n");
1071 if (!options.baselineCacheMapPaths.empty()) {
1072 fprintf(stderr, "Cannot use -baseline_cache_map without -json_manifest\n");
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");
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());
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());
1098 auto processFile = ^(const std::string& path, const struct stat& statBuf) {
1099 if ( !endsWith(path, ".json") )
1102 jsonPaths.push_back(path);
1105 iterateDirectoryTree("", options.buildAllPath,
1106 ^(const std::string& dirPath) { return false; },
1107 processFile, true /* process files */, false /* recurse */);
1109 if (jsonPaths.empty()) {
1110 fprintf(stderr, "Didn't find any .json files inside -build_all path: %s\n", options.buildAllPath.c_str());
1114 if (options.listConfigs) {
1115 for (const std::string& path : jsonPaths) {
1116 fprintf(stderr, "Found config: %s\n", path.c_str());
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());
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());
1135 if (!options.release.empty()) {
1136 // Use the given release
1137 options.dylibCacheDir = dir + "/" + options.release + ".dlc";
1139 // Find a release directory
1140 __block std::vector<std::string> subDirectories;
1141 iterateDirectoryTree("", dir, ^(const std::string& dirPath) {
1142 subDirectories.push_back(dirPath);
1144 }, nullptr, false, false);
1146 if (subDirectories.empty()) {
1147 fprintf(stderr, "Could not find dlc subdirectories inside '%s'\n", dir.c_str());
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());
1156 options.dylibCacheDir = subDirectories.front();
1160 if (options.dylibCacheDir.empty()) {
1161 options.dylibCacheDir = std::string("/AppleInternal/Developer/DylibCaches/") + options.release + ".dlc";
1164 //Move into the dir so we can use relative path manifests
1165 chdir(options.dylibCacheDir.c_str());
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);
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); }
1186 const std::string& jsonPath = jsonPaths[index];
1187 buildCacheFromJSONManifest(diags, options, jsonPath);
1189 if (requiresConcurrencyLimit) { dispatch_semaphore_signal(concurrencyLimit); }
1192 buildCacheFromJSONManifest(diags, options, jsonManifestPath);
1195 const char* args[8];
1196 args[0] = (char*)"/bin/rm";
1197 args[1] = (char*)"-rf";
1198 args[2] = (char*)tempRootsDir;
1200 (void)runCommandAndWait(diags, args);
1202 if (diags.hasError()) {
1203 fprintf(stderr, "dyld_shared_cache_builder: error: %s", diags.errorMessage().c_str());
1207 for (const std::string& warn : diags.warnings()) {
1208 fprintf(stderr, "dyld_shared_cache_builder: warning: %s\n", warn.c_str());
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);