Loading...
--- /dev/null
+++ dyld/dyld-1323.3/cache_builder/dyld_shared_cache_builder.mm
@@ -0,0 +1,1412 @@
+/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil -*-
+ *
+ * Copyright (c) 2016 Apple Inc. All rights reserved.
+ *
+ * @APPLE_LICENSE_HEADER_START@
+ *
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this
+ * file.
+ *
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ *
+ * @APPLE_LICENSE_HEADER_END@
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <sys/resource.h>
+#include <mach/mach.h>
+#include <mach/mach_time.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <fcntl.h>
+#include <dlfcn.h>
+#include <signal.h>
+#include <errno.h>
+#include <sysexits.h>
+#include <sys/uio.h>
+#include <unistd.h>
+#include <sys/param.h>
+#include <sys/sysctl.h>
+#include <sys/resource.h>
+#include <dirent.h>
+#include <libgen.h>
+#include <pthread.h>
+#include <fts.h>
+
+#include <vector>
+#include <array>
+#include <list>
+#include <set>
+#include <map>
+#include <unordered_set>
+#include <algorithm>
+#include <fstream>
+#include <regex>
+
+#include <spawn.h>
+
+#include <Bom/Bom.h>
+#include <Foundation/NSData.h>
+#include <Foundation/NSDictionary.h>
+#include <Foundation/NSPropertyList.h>
+#include <Foundation/NSString.h>
+
+#include "Defines.h"
+#include "Diagnostics.h"
+#include "DyldSharedCache.h"
+#include "FileUtils.h"
+#include "JSONReader.h"
+#include "JSONWriter.h"
+#include "Platform.h"
+#include "StringUtils.h"
+#include "mrm_shared_cache_builder.h"
+
+#if !__has_feature(objc_arc)
+#error The use of libdispatch in this files requires it to be compiled with ARC in order to avoid leaks
+#endif
+
+extern char** environ;
+
+static dispatch_queue_t build_queue;
+
+static int runCommandAndWait(Diagnostics& diags, const char* args[])
+{
+ pid_t pid;
+ int status;
+ int res = posix_spawn(&pid, args[0], nullptr, nullptr, (char**)args, environ);
+ if (res != 0)
+ diags.error("Failed to spawn %s: %s (%d)", args[0], strerror(res), res);
+
+ do {
+ res = waitpid(pid, &status, 0);
+ } while (res == -1 && errno == EINTR);
+ if (res != -1) {
+ if (WIFEXITED(status)) {
+ res = WEXITSTATUS(status);
+ } else {
+ res = -1;
+ }
+ }
+
+ return res;
+}
+
+static void processRoots(std::list<std::string>& roots, const char *tempRootsDir)
+{
+ std::list<std::string> processedRoots;
+ struct stat sb;
+ int res = 0;
+ const char* args[8];
+
+ for (const auto& root : roots) {
+ res = stat(root.c_str(), &sb);
+
+ if (res == 0 && S_ISDIR(sb.st_mode)) {
+ processedRoots.push_back(root);
+ continue;
+ }
+
+ char tempRootDir[MAXPATHLEN];
+ strlcpy(tempRootDir, tempRootsDir, MAXPATHLEN);
+ strlcat(tempRootDir, "/XXXXXXXX", MAXPATHLEN);
+ mkdtemp(tempRootDir);
+
+ 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")) {
+ args[0] = (char*)"/usr/bin/ditto";
+ args[1] = (char*)"-x";
+ args[2] = (char*)root.c_str();
+ args[3] = tempRootDir;
+ args[4] = nullptr;
+ } else if (endsWith(root, ".tar")) {
+ args[0] = (char*)"/usr/bin/tar";
+ args[1] = (char*)"xf";
+ args[2] = (char*)root.c_str();
+ args[3] = (char*)"-C";
+ args[4] = tempRootDir;
+ args[5] = nullptr;
+ } else if (endsWith(root, ".tar.gz") || endsWith(root, ".tgz")) {
+ args[0] = (char*)"/usr/bin/tar";
+ args[1] = (char*)"xzf";
+ args[2] = (char*)root.c_str();
+ args[3] = (char*)"-C";
+ args[4] = tempRootDir;
+ args[5] = nullptr;
+ } else if (endsWith(root, ".tar.bz2")
+ || endsWith(root, ".tbz2")
+ || endsWith(root, ".tbz")) {
+ args[0] = (char*)"/usr/bin/tar";
+ args[1] = (char*)"xjf";
+ args[2] = (char*)root.c_str();
+ args[3] = (char*)"-C";
+ args[4] = tempRootDir;
+ args[5] = nullptr;
+ } else if (endsWith(root, ".zip")) {
+ args[0] = (char*)"/usr/bin/ditto";
+ args[1] = (char*)"-xk";
+ args[2] = (char*)root.c_str();
+ args[3] = tempRootDir;
+ args[4] = nullptr;
+ } else {
+ fprintf(stderr, "unknown archive type: %s\n", root.c_str());
+ exit(EX_DATAERR);
+ }
+
+ Diagnostics diags;
+ if (res != runCommandAndWait(diags, args)) {
+ fprintf(stderr, "could not expand archive %s: %s (%d) because '%s'\n",
+ root.c_str(), strerror(res), res,
+ diags.hasError() ? diags.errorMessageCStr() : "unknown error");
+ exit(EX_DATAERR);
+ }
+ for (auto& existingRoot : processedRoots) {
+ if (existingRoot == tempRootDir)
+ continue;
+ }
+
+ processedRoots.push_back(tempRootDir);
+ }
+
+ roots = processedRoots;
+}
+
+static void writeRootList(const std::string& dstRoot, const std::list<std::string>& roots)
+{
+ if (roots.size() == 0)
+ return;
+
+ std::string rootFile = dstRoot + "/roots.txt";
+ FILE* froots = ::fopen(rootFile.c_str(), "w");
+ if (froots == NULL)
+ return;
+
+ for (auto& root : roots) {
+ fprintf(froots, "%s\n", root.c_str());
+ }
+
+ ::fclose(froots);
+}
+
+struct FilteredCopyOptions {
+ Diagnostics* diags = nullptr;
+ std::set<std::string>* cachePaths = nullptr;
+ std::set<std::string>* dylibsFoundInRoots = nullptr;
+};
+
+static BOMCopierCopyOperation filteredCopyIncludingPaths(BOMCopier copier, const char* path, BOMFSObjType type, off_t size)
+{
+ std::string absolutePath = &path[1];
+ const FilteredCopyOptions *userData = (const FilteredCopyOptions*)BOMCopierUserData(copier);
+
+ // Don't copy from the artifact if the dylib is actally in a -root
+ if ( userData->dylibsFoundInRoots->count(absolutePath) != 0 ) {
+ userData->diags->verbose("Skipping copying dylib from shared cache artifact as it is in a -root: '%s'\n", absolutePath.c_str());
+ return BOMCopierSkipFile;
+ }
+
+ for (const std::string& cachePath : *userData->cachePaths) {
+ if (startsWith(cachePath, absolutePath)) {
+ userData->diags->verbose("Copying dylib from shared cache artifact: '%s'\n", absolutePath.c_str());
+ return BOMCopierContinue;
+ }
+ }
+ if (userData->cachePaths->count(absolutePath)) {
+ userData->diags->verbose("Copying dylib from shared cache artifact: '%s'\n", absolutePath.c_str());
+ return BOMCopierContinue;
+ }
+ return BOMCopierSkipFile;
+}
+
+static Disposition stringToDisposition(Diagnostics& diags, const std::string& str) {
+ if (diags.hasError())
+ return Unknown;
+ if (str == "Unknown")
+ return Unknown;
+ if (str == "InternalDevelopment")
+ return InternalDevelopment;
+ if (str == "Customer")
+ return Customer;
+ if (str == "InternalMinDevelopment")
+ return InternalMinDevelopment;
+ if (str == "SymbolsCache")
+ return SymbolsCache;
+ return Unknown;
+}
+
+static Platform stringToPlatform(Diagnostics& diags, const std::string& str) {
+ if (diags.hasError())
+ return unknown;
+ if (str == "unknown")
+ return unknown;
+ if ( (str == "macOS") || (str == "osx") )
+ return macOS;
+ if (str == "iOS")
+ return iOS;
+ if (str == "tvOS")
+ return tvOS;
+ if (str == "watchOS")
+ return watchOS;
+ if (str == "bridgeOS")
+ return bridgeOS;
+ if (str == "iOSMac")
+ return iOSMac;
+ if (str == "UIKitForMac")
+ return iOSMac;
+ if (str == "iOS_simulator")
+ return iOS_simulator;
+ if (str == "tvOS_simulator")
+ return tvOS_simulator;
+ if (str == "watchOS_simulator")
+ return watchOS_simulator;
+ if (str == "driverKit")
+ return driverKit;
+ if (str == "macOSExclaveKit")
+ return macOSExclaveKit;
+ if (str == "iOSExclaveKit")
+ return iOSExclaveKit;
+ if ( std::isdigit(str.front()) ) {
+ // Also allow platforms to be specified as an integer
+ return (Platform)atoi(str.c_str());
+ }
+ if ( startsWith(str, "platform") ) {
+ std::string_view strView = str;
+ strView.remove_prefix(8);
+ if ( std::isdigit(strView.front()) ) {
+ // Also allow platforms to be specified as an integer
+ return (Platform)atoi(strView.data());
+ }
+ }
+ return unknown;
+}
+
+static FileFlags stringToFileFlags(Diagnostics& diags, const std::string& str) {
+ if (diags.hasError())
+ return NoFlags;
+ if (str == "NoFlags")
+ return NoFlags;
+ if (str == "MustBeInCache")
+ return MustBeInCache;
+ if (str == "ShouldBeExcludedFromCacheIfUnusedLeaf")
+ return ShouldBeExcludedFromCacheIfUnusedLeaf;
+ if (str == "RequiredClosure")
+ return RequiredClosure;
+ if (str == "DylibOrderFile")
+ return DylibOrderFile;
+ if (str == "DirtyDataOrderFile")
+ return DirtyDataOrderFile;
+ if (str == "ObjCOptimizationsFile")
+ return ObjCOptimizationsFile;
+ if (str == "SwiftGenericMetadataFile")
+ return SwiftGenericMetadataFile;
+ if (str == "OptimizationFile")
+ return OptimizationFile;
+ return NoFlags;
+}
+
+struct SharedCacheBuilderOptions {
+ Diagnostics diags;
+ std::list<std::string> roots;
+ std::string dylibCacheDir;
+ std::string artifactDir;
+ std::string release;
+ bool emitDevCaches = true;
+ bool emitCustomerCaches = true;
+ bool emitElidedDylibs = true;
+ bool listConfigs = false;
+ bool copyRoots = false;
+ bool debug = false;
+ bool debugIMPCaches = false;
+ bool debugCacheLayout = false;
+ bool useMRM = false;
+ bool timePasses = false;
+ bool printStats = false;
+ bool printRemovedFiles = false;
+ bool emitJSONMap = false;
+ std::string dstRoot;
+ std::string buildAllPath;
+ std::string resultPath;
+ std::string baselineDifferenceResultPath;
+ std::list<std::string> baselineCacheMapPaths;
+ std::string baselineCacheMapDirPath;
+ bool baselineCopyRoots = false;
+ bool printCDHashes = false;
+ std::set<std::string> cmdLineArchs;
+};
+
+typedef std::tuple<std::string, std::string, FileFlags, std::string> InputFile;
+
+static void loadMRMFiles(Diagnostics& diags,
+ MRMSharedCacheBuilder* sharedCacheBuilder,
+ const std::vector<InputFile>& inputFiles,
+ std::vector<std::pair<const void*, size_t>>& mappedFiles,
+ const std::set<std::string>& baselineCacheFiles) {
+
+ for (const InputFile& inputFile : inputFiles) {
+ const std::string& buildPath = std::get<0>(inputFile);
+ const std::string& runtimePath = std::get<1>(inputFile);
+ FileFlags fileFlags = std::get<2>(inputFile);
+ const std::string& projectName = std::get<3>(inputFile);
+
+ struct stat stat_buf;
+ int fd = ::open(buildPath.c_str(), O_RDONLY, 0);
+ if (fd == -1) {
+ if (baselineCacheFiles.count(runtimePath)) {
+ diags.error("can't open file '%s', errno=%d\n", buildPath.c_str(), errno);
+ return;
+ } else {
+ // Don't spam with paths we know will be missing
+ if ( buildPath.starts_with("./System/Library/Templates/Data/") )
+ continue;
+ diags.verbose("can't open file '%s', errno=%d\n", buildPath.c_str(), errno);
+ continue;
+ }
+ }
+
+ if (fstat(fd, &stat_buf) == -1) {
+ if (baselineCacheFiles.count(runtimePath)) {
+ diags.error("can't stat open file '%s', errno=%d\n", buildPath.c_str(), errno);
+ ::close(fd);
+ return;
+ } else {
+ diags.verbose("can't stat open file '%s', errno=%d\n", buildPath.c_str(), errno);
+ ::close(fd);
+ continue;
+ }
+ }
+
+ const void* buffer = mmap(NULL, (size_t)stat_buf.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
+ if (buffer == MAP_FAILED) {
+ diags.error("mmap() for file at %s failed, errno=%d\n", buildPath.c_str(), errno);
+ ::close(fd);
+ }
+ ::close(fd);
+
+ mappedFiles.emplace_back(buffer, (size_t)stat_buf.st_size);
+
+ addFile_v2(sharedCacheBuilder, runtimePath.c_str(), (uint8_t*)buffer, (size_t)stat_buf.st_size, fileFlags, projectName.c_str());
+ }
+}
+
+static void unloadMRMFiles(std::vector<std::pair<const void*, size_t>>& mappedFiles) {
+ for (auto mappedFile : mappedFiles)
+ ::munmap((void*)mappedFile.first, mappedFile.second);
+}
+
+static ssize_t write64(int fildes, const void *buf, size_t nbyte)
+{
+ unsigned char* uchars = (unsigned char*)buf;
+ ssize_t total = 0;
+
+ while (nbyte)
+ {
+ /*
+ * If we were writing socket- or stream-safe code we'd chuck the
+ * entire buf to write(2) and then gracefully re-request bytes that
+ * didn't get written. But write(2) will return EINVAL if you ask it to
+ * write more than 2^31-1 bytes. So instead we actually need to throttle
+ * the input to write.
+ *
+ * Historically code using write(2) to write to disk will assert that
+ * that all of the requested bytes were written. It seems harmless to
+ * re-request bytes as one does when writing to streams, with the
+ * compromise that we will return immediately when write(2) returns 0
+ * bytes written.
+ */
+ size_t limit = 0x7FFFFFFF;
+ size_t towrite = nbyte < limit ? nbyte : limit;
+ ssize_t wrote = write(fildes, uchars, towrite);
+ if (-1 == wrote)
+ {
+ return -1;
+ }
+ else if (0 == wrote)
+ {
+ break;
+ }
+ else
+ {
+ nbyte -= wrote;
+ uchars += wrote;
+ total += wrote;
+ }
+ }
+
+ return total;
+}
+
+static void printRemovedFiles(bool cacheBuildSuccess, MRMSharedCacheBuilder* sharedCacheBuilder,
+ const SharedCacheBuilderOptions& options)
+{
+ if ( !cacheBuildSuccess || !options.printRemovedFiles )
+ return;
+
+ uint64_t fileResultCount = 0;
+ if (const char* const* fileResults = getFilesToRemove(sharedCacheBuilder, &fileResultCount)) {
+ for (uint64_t i = 0; i != fileResultCount; ++i)
+ printf("Removed: %s\n", fileResults[i]);
+ }
+}
+
+static void writeMRMResults(bool cacheBuildSuccess, MRMSharedCacheBuilder* sharedCacheBuilder,
+ const SharedCacheBuilderOptions& options)
+{
+ if (!cacheBuildSuccess) {
+ uint64_t errorCount = 0;
+ if (const char* const* errors = getErrors(sharedCacheBuilder, &errorCount)) {
+ for (uint64_t i = 0, e = errorCount; i != e; ++i) {
+ const char* errorMessage = errors[i];
+ fprintf(stderr, "ERROR: %s\n", errorMessage);
+ }
+ }
+ }
+
+ // Now emit each cache we generated, or the errors for them.
+ uint64_t cacheResultCount = 0;
+ if (const CacheResult* const* cacheResults = getCacheResults(sharedCacheBuilder, &cacheResultCount)) {
+ for (uint64_t i = 0, e = cacheResultCount; i != e; ++i) {
+ const CacheResult& result = *(cacheResults[i]);
+ // Always print the warnings if we have roots, even if there are errors
+ // But not if we have -build_all, as its too noisy
+ bool emitWarnings = (result.numErrors == 0) || !options.roots.empty() || options.debug;
+ if ( options.dstRoot.empty() )
+ emitWarnings = false;
+ if ( emitWarnings ) {
+ for (uint64_t warningIndex = 0; warningIndex != result.numWarnings; ++warningIndex) {
+ fprintf(stderr, "[%s] WARNING: %s\n", result.loggingPrefix, result.warnings[warningIndex]);
+ }
+ }
+ if (result.numErrors) {
+ for (uint64_t errorIndex = 0; errorIndex != result.numErrors; ++errorIndex) {
+ fprintf(stderr, "[%s] ERROR: %s\n", result.loggingPrefix, result.errors[errorIndex]);
+ }
+ cacheBuildSuccess = false;
+ }
+ }
+ }
+
+ if (!cacheBuildSuccess) {
+ return;
+ }
+
+ // If we built caches, then write everything out.
+ // TODO: Decide if we should we write any good caches anyway?
+ if ( cacheBuildSuccess ) {
+ uint64_t fileResultCount = 0;
+ if (const FileResult* const* fileResults = getFileResults(sharedCacheBuilder, &fileResultCount)) {
+ for (uint64_t i = 0, e = fileResultCount; i != e; ++i) {
+ const FileResult* fileResultPtr = fileResults[i];
+
+ assert(fileResultPtr->version == 1);
+ const FileResult_v1& fileResult = *(const FileResult_v1*)fileResultPtr;
+
+ switch (fileResult.behavior) {
+ case AddFile:
+ break;
+ case ChangeFile:
+ continue;
+ }
+
+ if ( options.printCDHashes ) {
+ fprintf(stderr, "[%s/%s] %s cdhash: %s\n", fileResult.hashArch, fileResult.hashType, fileResult.path, fileResult.hash);
+ }
+
+ if ( (fileResult.data != nullptr) && !options.dstRoot.empty() ) {
+ const std::string path = options.dstRoot + fileResult.path;
+ std::string pathTemplate = path + "-XXXXXX";
+ size_t templateLen = strlen(pathTemplate.c_str())+2;
+ char pathTemplateSpace[templateLen];
+ strlcpy(pathTemplateSpace, pathTemplate.c_str(), templateLen);
+ int fd = mkstemp(pathTemplateSpace);
+ if ( fd != -1 ) {
+ ::ftruncate(fd, fileResult.size);
+ uint64_t writtenSize = write64(fd, fileResult.data, fileResult.size);
+ if ( writtenSize == fileResult.size ) {
+ ::fchmod(fd, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); // mkstemp() makes file "rw-------", switch it to "rw-r--r--"
+ if ( ::rename(pathTemplateSpace, path.c_str()) == 0) {
+ ::close(fd);
+ continue; // success
+ }
+ }
+ else {
+ fprintf(stderr, "ERROR: could not write file %s\n", pathTemplateSpace);
+ cacheBuildSuccess = false;
+ }
+ ::close(fd);
+ ::unlink(pathTemplateSpace);
+ }
+ else {
+ fprintf(stderr, "ERROR: could not open file %s\n", pathTemplateSpace);
+ cacheBuildSuccess = false;
+ }
+ }
+ }
+ }
+
+ // Give up if we couldn't write the caches
+ if (!cacheBuildSuccess) {
+ return;
+ }
+ }
+
+ if ( options.emitJSONMap && !options.dstRoot.empty() ) {
+ if (const CacheResult* const* cacheResults = getCacheResults(sharedCacheBuilder, &cacheResultCount)) {
+ for (uint64_t i = 0, e = cacheResultCount; i != e; ++i) {
+ const CacheResult& result = *(cacheResults[i]);
+
+ const std::string path = options.dstRoot + "/" + result.loggingPrefix + ".json";
+ std::string pathTemplate = path + "-XXXXXX";
+ size_t templateLen = strlen(pathTemplate.c_str())+2;
+ char pathTemplateSpace[templateLen];
+ strlcpy(pathTemplateSpace, pathTemplate.c_str(), templateLen);
+ int fd = mkstemp(pathTemplateSpace);
+ if ( fd != -1 ) {
+ size_t jsonLength = strlen(result.mapJSON) + 1;
+ ::ftruncate(fd, jsonLength);
+ uint64_t writtenSize = write64(fd, result.mapJSON, jsonLength);
+ if ( writtenSize == jsonLength ) {
+ ::fchmod(fd, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); // mkstemp() makes file "rw-------", switch it to "rw-r--r--"
+ if ( ::rename(pathTemplateSpace, path.c_str()) == 0) {
+ ::close(fd);
+ continue; // success
+ }
+ }
+ else {
+ fprintf(stderr, "ERROR: could not write map file %s\n", pathTemplateSpace);
+ cacheBuildSuccess = false;
+ }
+ ::close(fd);
+ ::unlink(pathTemplateSpace);
+ }
+ else {
+ fprintf(stderr, "ERROR: could not open map file %s\n", pathTemplateSpace);
+ cacheBuildSuccess = false;
+ }
+ }
+ }
+ }
+
+ // If we ask for -stats, then also emit the stat files
+ if ( options.printStats && !options.dstRoot.empty() ) {
+ uint64_t statResultCount = 0;
+ if ( const char* const* statResults = getCacheStats(sharedCacheBuilder, &statResultCount) ) {
+ for (uint64_t i = 0; i != statResultCount; ++i) {
+ std::string_view statString = statResults[i];
+
+ const std::string path = options.dstRoot + "/" + "stats." + std::to_string(i) + ".json";
+ std::string pathTemplate = path + "-XXXXXX";
+ size_t templateLen = strlen(pathTemplate.c_str()) + 2;
+ char pathTemplateSpace[templateLen];
+ strlcpy(pathTemplateSpace, pathTemplate.c_str(), templateLen);
+ int fd = mkstemp(pathTemplateSpace);
+ if ( fd != -1 ) {
+ size_t jsonLength = statString.size();
+ ::ftruncate(fd, jsonLength);
+ uint64_t writtenSize = write64(fd, statString.data(), jsonLength);
+ if ( writtenSize == jsonLength ) {
+ ::fchmod(fd, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); // mkstemp() makes file "rw-------", switch it to "rw-r--r--"
+ if ( ::rename(pathTemplateSpace, path.c_str()) == 0) {
+ ::close(fd);
+ continue; // success
+ }
+ }
+ else {
+ fprintf(stderr, "ERROR: could not write map file %s\n", pathTemplateSpace);
+ cacheBuildSuccess = false;
+ }
+ ::close(fd);
+ ::unlink(pathTemplateSpace);
+ }
+ else {
+ fprintf(stderr, "ERROR: could not open map file %s\n", pathTemplateSpace);
+ cacheBuildSuccess = false;
+ }
+ }
+ }
+ }
+}
+
+static void buildCacheFromJSONManifest(Diagnostics& diags, const SharedCacheBuilderOptions& options,
+ const std::string& jsonManifestPath) {
+ json::Node manifestNode = json::readJSON(diags, jsonManifestPath.c_str(), false /* useJSON5 */);
+ if (diags.hasError())
+ return;
+
+ // Top level node should be a map of the options, files, and symlinks.
+ if (manifestNode.map.empty()) {
+ diags.error("Expected map for JSON manifest node\n");
+ return;
+ }
+
+ // Parse the nodes in the top level manifest node
+ const json::Node& versionNode = json::getRequiredValue(diags, manifestNode, "version");
+ uint64_t manifestVersion = json::parseRequiredInt(diags, versionNode);
+ if (diags.hasError())
+ return;
+
+ const uint64_t supportedManifestVersion = 1;
+ if (manifestVersion != supportedManifestVersion) {
+ diags.error("JSON manfiest version of %lld is unsupported. Supported version is %lld\n",
+ manifestVersion, supportedManifestVersion);
+ return;
+ }
+ const json::Node& buildOptionsNode = json::getRequiredValue(diags, manifestNode, "buildOptions");
+ const json::Node& filesNode = json::getRequiredValue(diags, manifestNode, "files");
+ const json::Node* symlinksNode = json::getOptionalValue(diags, manifestNode, "symlinks");
+
+ // Parse the archs
+ const json::Node& archsNode = json::getRequiredValue(diags, buildOptionsNode, "archs");
+ if (diags.hasError())
+ return;
+ if (archsNode.array.empty()) {
+ diags.error("Build options archs node is not an array\n");
+ return;
+ }
+ std::set<std::string> jsonArchs;
+ const char* archs[archsNode.array.size()];
+ uint64_t numArchs = 0;
+ for (const json::Node& archNode : archsNode.array) {
+ const char* archName = json::parseRequiredString(diags, archNode).c_str();
+ jsonArchs.insert(archName);
+ if ( options.cmdLineArchs.empty() || options.cmdLineArchs.count(archName) ) {
+ archs[numArchs++] = archName;
+ }
+ }
+
+ // Check that the command line archs are in the JSON list
+ if ( !options.cmdLineArchs.empty() ) {
+ for (const std::string& cmdLineArch : options.cmdLineArchs) {
+ if ( !jsonArchs.count(cmdLineArch) ) {
+ std::string validArchs = "";
+ for (const std::string& jsonArch : jsonArchs) {
+ if ( !validArchs.empty() ) {
+ validArchs += ", ";
+ }
+ validArchs += jsonArch;
+ }
+ diags.error("Command line -arch '%s' is not valid for this device. Valid archs are (%s)\n", cmdLineArch.c_str(), validArchs.c_str());
+ return;
+ }
+ }
+ }
+
+ // Parse the rest of the options node.
+ BuildOptions_v3 buildOptions;
+ buildOptions.version = json::parseRequiredInt(diags, json::getRequiredValue(diags, buildOptionsNode, "version"));
+ buildOptions.updateName = json::parseRequiredString(diags, json::getRequiredValue(diags, buildOptionsNode, "updateName")).c_str();
+ buildOptions.deviceName = json::parseRequiredString(diags, json::getRequiredValue(diags, buildOptionsNode, "deviceName")).c_str();
+ buildOptions.disposition = stringToDisposition(diags, json::parseRequiredString(diags, json::getRequiredValue(diags, buildOptionsNode, "disposition")));
+ buildOptions.platform = stringToPlatform(diags, json::parseRequiredString(diags, json::getRequiredValue(diags, buildOptionsNode, "platform")));
+ buildOptions.archs = archs;
+ buildOptions.numArchs = numArchs;
+ buildOptions.verboseDiagnostics = options.debug;
+ buildOptions.verboseIMPCaches = options.debugIMPCaches;
+ buildOptions.verboseCacheLayout = options.debugCacheLayout;
+ buildOptions.isLocallyBuiltCache = true;
+
+ // optimizeForSize was added in version 2
+ buildOptions.optimizeForSize = false;
+ if ( buildOptions.version >= 2 ) {
+ buildOptions.optimizeForSize = json::parseRequiredBool(diags, json::getRequiredValue(diags, buildOptionsNode, "optimizeForSize"));
+ }
+
+ // timePasses was added in version 3
+ buildOptions.filesRemovedFromDisk = true;
+ buildOptions.timePasses = false;
+ buildOptions.printStats = false;
+ if ( buildOptions.version == 2 ) {
+ // HACK:! Bump to version 3 so that timePasses/printStats are picked up.
+ buildOptions.version = 3;
+ buildOptions.timePasses = options.timePasses;
+ buildOptions.printStats = options.printStats;
+ } else if ( buildOptions.version >= 3 ) {
+ const json::Node* filesRemovedNode = json::getOptionalValue(diags, buildOptionsNode, "filesRemovedFromDisk");
+ const json::Node* timePassesNode = json::getOptionalValue(diags, buildOptionsNode, "timePasses");
+ const json::Node* printStatsNode = json::getOptionalValue(diags, buildOptionsNode, "printStats");
+ if ( filesRemovedNode != nullptr )
+ buildOptions.filesRemovedFromDisk = json::parseRequiredBool(diags, *filesRemovedNode);
+ if ( timePassesNode != nullptr )
+ buildOptions.timePasses = json::parseRequiredBool(diags, *timePassesNode);
+ if ( printStatsNode != nullptr )
+ buildOptions.printStats = json::parseRequiredBool(diags, *printStatsNode);
+ }
+
+ if (diags.hasError())
+ return;
+
+ // Override the disposition if we don't want certain caches.
+ switch (buildOptions.disposition) {
+ case Unknown:
+ // Nothing we can do here as we can't assume what caches are built here.
+ break;
+ case InternalDevelopment:
+ if (!options.emitDevCaches && !options.emitCustomerCaches) {
+ diags.error("both -no_customer_cache and -no_development_cache passed\n");
+ break;
+ }
+ if (!options.emitDevCaches) {
+ // This builds both caches, but we don't want dev
+ buildOptions.disposition = Customer;
+ }
+ if (!options.emitCustomerCaches) {
+ // This builds both caches, but we don't want customer
+ buildOptions.disposition = InternalMinDevelopment;
+ }
+ break;
+ case Customer:
+ if (!options.emitCustomerCaches) {
+ diags.error("Cannot request no customer cache for Customer as that is already only a customer cache\n");
+ }
+ break;
+ case InternalMinDevelopment:
+ if (!options.emitDevCaches) {
+ diags.error("Cannot request no dev cache for InternalMinDevelopment as that is already only a dev cache\n");
+ }
+ break;
+ case SymbolsCache:
+ break;
+ }
+
+ if (diags.hasError())
+ return;
+
+ struct MRMSharedCacheBuilder* sharedCacheBuilder = createSharedCacheBuilder((const BuildOptions_v1*)&buildOptions);
+
+ // Parse the files
+ if (filesNode.array.empty()) {
+ diags.error("Build options files node is not an array\n");
+ return;
+ }
+
+ std::vector<InputFile> inputFiles;
+ std::set<std::string> dylibsFoundInRoots;
+ for (const json::Node& fileNode : filesNode.array) {
+ std::string path = json::parseRequiredString(diags, json::getRequiredValue(diags, fileNode, "path")).c_str();
+ FileFlags fileFlags = stringToFileFlags(diags, json::parseRequiredString(diags, json::getRequiredValue(diags, fileNode, "flags")));
+
+ std::string_view projectName;
+ if ( const json::Node* projectNode = json::getOptionalValue(diags, fileNode, "project") )
+ projectName = projectNode->value;
+
+ // We can optionally have a sourcePath entry which is the path to get the source content from instead of the install path
+ std::string sourcePath;
+ const json::Node* sourcePathNode = json::getOptionalValue(diags, fileNode, "sourcePath");
+ if ( sourcePathNode != nullptr ) {
+ if (!sourcePathNode->array.empty()) {
+ diags.error("sourcePath node cannot be an array\n");
+ return;
+ }
+ if (!sourcePathNode->map.empty()) {
+ diags.error("sourcePath node cannot be a map\n");
+ return;
+ }
+ sourcePath = sourcePathNode->value;
+ } else {
+ sourcePath = path;
+ }
+
+ std::string buildPath = sourcePath;
+
+ // Check if one of the -root's has this path
+ bool foundInOverlay = false;
+ for (const std::string& overlay : options.roots) {
+ struct stat sb;
+ std::string filePath = overlay + path;
+ if (!stat(filePath.c_str(), &sb)) {
+ foundInOverlay = true;
+ diags.verbose("Taking '%s' from overlay '%s' instead of dylib cache\n", path.c_str(), overlay.c_str());
+ inputFiles.push_back({ filePath, path, fileFlags, std::string(projectName) });
+ dylibsFoundInRoots.insert(path);
+ break;
+ }
+ }
+
+ if (foundInOverlay)
+ continue;
+
+ // Build paths are relative to the build artifact root directory.
+ switch (fileFlags) {
+ case NoFlags:
+ case MustBeInCache:
+ case ShouldBeExcludedFromCacheIfUnusedLeaf:
+ case RequiredClosure:
+ case DylibOrderFile:
+ case DirtyDataOrderFile:
+ case ObjCOptimizationsFile:
+ case SwiftGenericMetadataFile:
+ case OptimizationFile:
+ buildPath = "." + buildPath;
+ break;
+ }
+ inputFiles.push_back({ buildPath, path, fileFlags, std::string(projectName) });
+ }
+
+ if (diags.hasError())
+ return;
+
+ // Parse the baseline from the map(s) if we have it
+ BaselineCachesChecker baselineCaches({ &archs[0], &archs[numArchs] }, mach_o::Platform(buildOptions.platform));
+
+ // If we have a maps directory, use it
+ if ( !options.baselineCacheMapDirPath.empty() ) {
+ if ( mach_o::Error err = baselineCaches.addBaselineMaps(options.baselineCacheMapDirPath) ) {
+ diags.error("%s", err.message());
+ return;
+ }
+ } else {
+ for ( const std::string& baselineCacheMapPath : options.baselineCacheMapPaths ) {
+ if ( mach_o::Error err = baselineCaches.addBaselineMap(baselineCacheMapPath) ) {
+ diags.error("%s", err.message());
+ return;
+ }
+ }
+ }
+
+ std::vector<std::pair<const void*, size_t>> mappedFiles;
+ {
+ uint64_t startTimeNanos = clock_gettime_nsec_np(CLOCK_UPTIME_RAW);
+ loadMRMFiles(diags, sharedCacheBuilder, inputFiles, mappedFiles, baselineCaches.unionBaselineDylibs());
+ uint64_t endTimeNanos = clock_gettime_nsec_np(CLOCK_UPTIME_RAW);
+
+ if ( options.timePasses ) {
+ uint64_t timeMillis = (endTimeNanos - startTimeNanos) / 1000000;
+ fprintf(stderr, "loadMRMFiles: time = %lldms\n", timeMillis);
+ }
+ }
+
+ if (diags.hasError())
+ return;
+
+ // Parse the symlinks if we have them
+ if (symlinksNode) {
+ if (symlinksNode->array.empty()) {
+ diags.error("Build options symlinks node is not an array\n");
+ return;
+ }
+ for (const json::Node& symlinkNode : symlinksNode->array) {
+ std::string fromPath = json::parseRequiredString(diags, json::getRequiredValue(diags, symlinkNode, "path")).c_str();
+ const std::string& toPath = json::parseRequiredString(diags, json::getRequiredValue(diags, symlinkNode, "target")).c_str();
+ addSymlink(sharedCacheBuilder, fromPath.c_str(), toPath.c_str());
+ }
+ }
+
+ if (diags.hasError())
+ return;
+
+ // Don't create a directory if we are skipping writes, which means we have no dstRoot set
+ if (!options.dstRoot.empty()) {
+ if ( buildOptions.platform == macOS ) {
+ (void)mkpath_np((options.dstRoot + MACOSX_MRM_DYLD_SHARED_CACHE_DIR).c_str(), 0755);
+ } else if (buildOptions.platform == driverKit ) {
+ (void)mkpath_np((options.dstRoot + DRIVERKIT_DYLD_SHARED_CACHE_DIR).c_str(), 0755);
+ } else if ( mach_o::Platform(buildOptions.platform).isExclaveKit() ) {
+ (void)mkpath_np((options.dstRoot + EXCLAVEKIT_DYLD_SHARED_CACHE_DIR).c_str(), 0755);
+ } else if ( buildOptions.disposition == SymbolsCache ) {
+ // symbols cache always uses /System/Library/dyld, even on iOS
+ (void)mkpath_np((options.dstRoot + MACOSX_MRM_DYLD_SHARED_CACHE_DIR).c_str(), 0755);
+ } else {
+ (void)mkpath_np((options.dstRoot + IPHONE_DYLD_SHARED_CACHE_DIR).c_str(), 0755);
+ }
+ }
+
+ // Actually build the cache.
+ bool cacheBuildSuccess = runSharedCacheBuilder(sharedCacheBuilder);
+
+ // Compare this cache to the baseline cache and see if we have any roots to copy over
+ if (!options.baselineDifferenceResultPath.empty() || options.baselineCopyRoots) {
+ std::set<std::string> dylibsInNewCaches;
+ if (cacheBuildSuccess) {
+ uint64_t fileResultCount = 0;
+ if (const char* const* fileResults = getFilesToRemove(sharedCacheBuilder, &fileResultCount)) {
+ for (uint64_t i = 0; i != fileResultCount; ++i)
+ dylibsInNewCaches.insert(fileResults[i]);
+ }
+ }
+
+ if ( options.baselineCopyRoots && cacheBuildSuccess ) {
+ uint64_t cacheResultCount = 0;
+ if ( const CacheResult* const* cacheResults = getCacheResults(sharedCacheBuilder, &cacheResultCount) ) {
+ for ( uint64_t i = 0; i != cacheResultCount; ++i ) {
+ const CacheResult& result = *(cacheResults[i]);
+ if ( result.mapJSON == nullptr )
+ continue;
+ std::string_view mapString = result.mapJSON;
+ if ( mapString.empty() )
+ continue;
+
+ if ( mach_o::Error err = baselineCaches.addNewMap(mapString) ) {
+ diags.error("%s", err.message());
+ return;
+ }
+ }
+ }
+
+ uint64_t fileResultCount = 0;
+ if ( const char* const* fileResults = getFilesToRemove(sharedCacheBuilder, &fileResultCount) )
+ baselineCaches.setFilesFromNewCaches({ fileResults, fileResultCount });
+
+ // Work out the set of dylibs in the old caches but not the new ones
+ std::set<std::string> dylibsMissingFromNewCaches = baselineCaches.dylibsMissingFromNewCaches();
+ if ( !dylibsMissingFromNewCaches.empty() ) {
+ BOMCopier copier = BOMCopierNewWithSys(BomSys_default());
+ FilteredCopyOptions userData = { &diags, &dylibsMissingFromNewCaches, &dylibsFoundInRoots };
+ BOMCopierSetUserData(copier, (void*)&userData);
+ BOMCopierSetCopyFileStartedHandler(copier, filteredCopyIncludingPaths);
+ std::string dylibCacheRootDir = realFilePath(options.dylibCacheDir);
+ if (dylibCacheRootDir == "") {
+ fprintf(stderr, "Could not find dylib Root directory to copy baseline roots from\n");
+ exit(EX_NOINPUT);
+ }
+ BOMCopierCopy(copier, dylibCacheRootDir.c_str(), options.dstRoot.c_str());
+ BOMCopierFree(copier);
+
+ for (const std::string& dylibMissingFromNewCache : dylibsMissingFromNewCaches) {
+ diags.verbose("Dylib missing from new cache: '%s'\n", dylibMissingFromNewCache.c_str());
+ }
+ }
+ }
+
+ if (!options.baselineDifferenceResultPath.empty()) {
+ auto cppToObjStr = [](const std::string& str) {
+ return [NSString stringWithUTF8String:str.c_str()];
+ };
+
+ // Work out the set of dylibs in the cache and taken from any -roots
+ NSMutableArray<NSString*>* dylibsFromRoots = [NSMutableArray array];
+ for (auto& root : options.roots) {
+ for (const std::string& dylibInstallName : dylibsInNewCaches) {
+ struct stat sb;
+ std::string filePath = root + "/" + dylibInstallName;
+ if (!stat(filePath.c_str(), &sb)) {
+ [dylibsFromRoots addObject:cppToObjStr(dylibInstallName)];
+ }
+ }
+ }
+
+ // Work out the set of dylibs in the new cache but not in the baseline cache.
+ NSMutableArray<NSString*>* dylibsMissingFromBaselineCache = [NSMutableArray array];
+ for (const std::string& newDylib : dylibsInNewCaches) {
+ if ( !baselineCaches.unionBaselineDylibs().count(newDylib) )
+ [dylibsMissingFromBaselineCache addObject:cppToObjStr(newDylib)];
+ }
+
+ NSMutableDictionary* cacheDict = [[NSMutableDictionary alloc] init];
+ cacheDict[@"root-paths-in-cache"] = dylibsFromRoots;
+ cacheDict[@"device-paths-to-delete"] = dylibsMissingFromBaselineCache;
+
+ NSError* error = nil;
+ NSData* outData = [NSPropertyListSerialization dataWithPropertyList:cacheDict
+ format:NSPropertyListBinaryFormat_v1_0
+ options:0
+ error:&error];
+ (void)[outData writeToFile:cppToObjStr(options.baselineDifferenceResultPath) atomically:YES];
+ }
+ }
+
+ printRemovedFiles(cacheBuildSuccess, sharedCacheBuilder, options);
+
+ writeMRMResults(cacheBuildSuccess, sharedCacheBuilder, options);
+
+ destroySharedCacheBuilder(sharedCacheBuilder);
+
+ unloadMRMFiles(mappedFiles);
+
+ // On failure, add an error to the diagnostic so that the caller can see that the build failed
+ if ( !cacheBuildSuccess ) {
+ diags.error("see other errors");
+ }
+}
+
+static std::string realPathOrExit(const char* argName, const char* argValue)
+{
+ std::string realpath = realPath(argValue);
+ if ( realpath.empty() || !fileExists(realpath) ) {
+ fprintf(stderr, "%s path doesn't exist: %s\n", argName, argValue);
+ exit(EX_NOINPUT);
+ }
+ return realpath;
+}
+
+static const char* leafName(std::string_view str)
+{
+ const char* start = strrchr(str.data(), '/');
+ if ( start != nullptr )
+ return &start[1];
+ else
+ return str.data();
+}
+
+int main(int argc, const char* argv[])
+{
+ __block SharedCacheBuilderOptions options;
+ std::string jsonManifestPath;
+ char* tempRootsDir = strdup("/tmp/dyld_shared_cache_builder.XXXXXX");
+
+ mkdtemp(tempRootsDir);
+
+ for (int i = 1; i < argc; ++i) {
+ const char* arg = argv[i];
+ if (arg[0] == '-') {
+ if (strcmp(arg, "-debug") == 0) {
+ options.debug = true;
+ } else if (strcmp(arg, "-debug-imp-caches") == 0) {
+ options.debugIMPCaches = true;
+ } else if (strcmp(arg, "-debug-cache-layout") == 0) {
+ options.debugCacheLayout = true;
+ } else if (strcmp(arg, "-list_configs") == 0) {
+ options.listConfigs = true;
+ } else if (strcmp(arg, "-root") == 0) {
+ std::string realpath = realPathOrExit("-root", argv[++i]);
+ if ( std::find(options.roots.begin(), options.roots.end(), realpath) == options.roots.end() ) {
+ // Push roots on to the front so that each -root overrides previous entries
+ options.roots.push_front(realpath);
+ }
+ } else if (strcmp(arg, "-copy_roots") == 0) {
+ options.copyRoots = true;
+ } else if (strcmp(arg, "-dylib_cache") == 0) {
+ options.dylibCacheDir = realPathOrExit("-dylib_cache", argv[++i]);
+ } else if (strcmp(arg, "-artifact") == 0) {
+ options.artifactDir = realPathOrExit("-artifact", argv[++i]);
+ } else if (strcmp(arg, "-no_overflow_dylibs") == 0) {
+ options.emitElidedDylibs = false;
+ } else if (strcmp(arg, "-no_development_cache") == 0) {
+ options.emitDevCaches = false;
+ } else if (strcmp(arg, "-development_cache") == 0) {
+ options.emitDevCaches = true;
+ } else if (strcmp(arg, "-no_customer_cache") == 0) {
+ options.emitCustomerCaches = false;
+ } else if (strcmp(arg, "-customer_cache") == 0) {
+ options.emitCustomerCaches = true;
+ } else if (strcmp(arg, "-overflow_dylibs") == 0) {
+ options.emitElidedDylibs = true;
+ } else if (strcmp(arg, "-mrm") == 0) {
+ options.useMRM = true;
+ } else if (strcmp(arg, "-time-passes") == 0) {
+ options.timePasses = true;
+ } else if (strcmp(arg, "-stats") == 0) {
+ options.printStats = true;
+ } else if (strcmp(arg, "-removed_files") == 0) {
+ options.printRemovedFiles = true;
+ } else if (strcmp(arg, "-emit_json") == 0) {
+ // unused
+ } else if (strcmp(arg, "-emit_json_map") == 0) {
+ options.emitJSONMap = true;
+ } else if (strcmp(arg, "-json_manifest") == 0) {
+ jsonManifestPath = realPathOrExit("-json_manifest", argv[++i]);
+ } else if (strcmp(arg, "-build_all") == 0) {
+ options.buildAllPath = realPathOrExit("-build_all", argv[++i]);
+ } else if (strcmp(arg, "-dst_root") == 0) {
+ options.dstRoot = realPath(argv[++i]);
+ } else if (strcmp(arg, "-release") == 0) {
+ options.release = argv[++i];
+ } else if (strcmp(arg, "-results") == 0) {
+ options.resultPath = realPath(argv[++i]);
+ } else if (strcmp(arg, "-baseline_diff_results") == 0) {
+ options.baselineDifferenceResultPath = realPath(argv[++i]);
+ } else if (strcmp(arg, "-baseline_copy_roots") == 0) {
+ options.baselineCopyRoots = true;
+ } else if (strcmp(arg, "-print_cdhashes") == 0) {
+ options.printCDHashes = true;
+ } else if (strcmp(arg, "-baseline_cache_map") == 0) {
+ std::string path = realPathOrExit("-baseline_cache_map", argv[++i]);
+ options.baselineCacheMapPaths.push_back(path);
+ } else if (strcmp(arg, "-baseline_cache_maps") == 0) {
+ std::string path = realPathOrExit("-baseline_cache_maps", argv[++i]);
+ options.baselineCacheMapDirPath = path;
+ } else if (strcmp(arg, "-arch") == 0) {
+ if ( ++i < argc ) {
+ options.cmdLineArchs.insert(argv[i]);
+ }
+ else {
+ fprintf(stderr, "-arch missing architecture name");
+ exit(EX_USAGE);
+ }
+ } else if (strcmp(arg, "-help") == 0) {
+ // no usage() to show, but having this allows clients to probe
+ // whether flags are supported by seeing if `-flag2check -help`
+ // exits with EXIT_SUCCESS or EX_USAGE
+ exit(EXIT_SUCCESS);
+ } else {
+ //usage();
+ fprintf(stderr, "unknown option: %s\n", arg);
+ exit(EX_USAGE);
+ }
+ } else {
+ fprintf(stderr, "unknown option: %s\n", arg);
+ exit(EX_USAGE);
+ }
+ }
+ (void)options.emitElidedDylibs; // not implemented yet
+
+ time_t mytime = time(0);
+ fprintf(stderr, "Started: %s", asctime(localtime(&mytime)));
+ processRoots(options.roots, tempRootsDir);
+
+ struct rlimit rl = { OPEN_MAX, OPEN_MAX };
+ (void)setrlimit(RLIMIT_NOFILE, &rl);
+
+ if (options.dylibCacheDir.empty() && options.artifactDir.empty() && options.release.empty()) {
+ fprintf(stderr, "you must specify either -dylib_cache, -artifact or -release\n");
+ exit(EX_USAGE);
+ } else if (!options.dylibCacheDir.empty() && !options.release.empty()) {
+ fprintf(stderr, "you may not use -dylib_cache and -release at the same time\n");
+ exit(EX_USAGE);
+ } else if (!options.dylibCacheDir.empty() && !options.artifactDir.empty()) {
+ fprintf(stderr, "you may not use -dylib_cache and -artifact at the same time\n");
+ exit(EX_USAGE);
+ }
+
+ if (jsonManifestPath.empty() && options.buildAllPath.empty()) {
+ fprintf(stderr, "Must specify a -json_manifest path OR a -build_all path\n");
+ exit(EX_USAGE);
+ }
+
+ if (!options.buildAllPath.empty()) {
+ if (!options.dstRoot.empty()) {
+ fprintf(stderr, "Cannot combine -dst_root and -build_all\n");
+ exit(EX_USAGE);
+ }
+ if (!jsonManifestPath.empty()) {
+ fprintf(stderr, "Cannot combine -json_manifest and -build_all\n");
+ exit(EX_USAGE);
+ }
+ if (!options.baselineDifferenceResultPath.empty()) {
+ fprintf(stderr, "Cannot combine -baseline_diff_results and -build_all\n");
+ exit(EX_USAGE);
+ }
+ if (options.baselineCopyRoots) {
+ fprintf(stderr, "Cannot combine -baseline_copy_roots and -build_all\n");
+ exit(EX_USAGE);
+ }
+ if (!options.baselineCacheMapPaths.empty()) {
+ fprintf(stderr, "Cannot combine -baseline_cache_map and -build_all\n");
+ exit(EX_USAGE);
+ }
+ if (!options.baselineCacheMapDirPath.empty()) {
+ fprintf(stderr, "Cannot combine -baseline_cache_maps and -build_all\n");
+ exit(EX_USAGE);
+ }
+ } else if (!options.listConfigs) {
+ if (options.dstRoot.empty()) {
+ fprintf(stderr, "Must specify a valid -dst_root OR -list_configs\n");
+ exit(EX_USAGE);
+ }
+
+ if (jsonManifestPath.empty()) {
+ fprintf(stderr, "Must specify a -json_manifest path OR -list_configs\n");
+ exit(EX_USAGE);
+ }
+ }
+
+ // Some options don't work with a JSON manifest
+ if (!jsonManifestPath.empty()) {
+ if (!options.resultPath.empty()) {
+ fprintf(stderr, "Cannot use -results with -json_manifest\n");
+ exit(EX_USAGE);
+ }
+ if (!options.baselineDifferenceResultPath.empty() && options.baselineCacheMapPaths.empty() && options.baselineCacheMapDirPath.empty()) {
+ fprintf(stderr, "Must use -baseline_cache_map/-baseline_cache_maps with -baseline_diff_results when using -json_manifest\n");
+ exit(EX_USAGE);
+ }
+ if (options.baselineCopyRoots && options.baselineCacheMapPaths.empty() && options.baselineCacheMapDirPath.empty()) {
+ fprintf(stderr, "Must use -baseline_cache_map/-baseline_cache_maps with -baseline_copy_roots when using -json_manifest\n");
+ exit(EX_USAGE);
+ }
+ } else {
+ if (!options.baselineCacheMapPaths.empty()) {
+ fprintf(stderr, "Cannot use -baseline_cache_map without -json_manifest\n");
+ exit(EX_USAGE);
+ }
+ if (!options.baselineCacheMapDirPath.empty()) {
+ fprintf(stderr, "Cannot use -baseline_cache_maps without -json_manifest\n");
+ exit(EX_USAGE);
+ }
+ }
+
+ if (!options.baselineCacheMapPaths.empty()) {
+ if (options.baselineDifferenceResultPath.empty() && !options.baselineCopyRoots) {
+ fprintf(stderr, "Must use -baseline_cache_map with -baseline_diff_results or -baseline_copy_roots\n");
+ exit(EX_USAGE);
+ }
+ }
+
+ if (!options.baselineCacheMapDirPath.empty()) {
+ if (options.baselineDifferenceResultPath.empty() && !options.baselineCopyRoots) {
+ fprintf(stderr, "Must use -baseline_cache_maps with -baseline_diff_results or -baseline_copy_roots\n");
+ exit(EX_USAGE);
+ }
+ }
+
+ // Find all the JSON files if we use -build_all
+ __block std::vector<std::string> jsonPaths;
+ if (!options.buildAllPath.empty()) {
+ struct stat stat_buf;
+ if (stat(options.buildAllPath.c_str(), &stat_buf) != 0) {
+ fprintf(stderr, "Could not find -build_all path '%s'\n", options.buildAllPath.c_str());
+ exit(EX_NOINPUT);
+ }
+
+ if ( (stat_buf.st_mode & S_IFMT) != S_IFDIR ) {
+ fprintf(stderr, "-build_all path is not a directory '%s'\n", options.buildAllPath.c_str());
+ exit(EX_DATAERR);
+ }
+
+ auto processFile = ^(const std::string& path, const struct stat& statBuf) {
+ if ( !endsWith(path, ".json") )
+ return;
+
+ jsonPaths.push_back(path);
+ };
+
+ iterateDirectoryTree("", options.buildAllPath,
+ ^(const std::string& dirPath) { return false; },
+ processFile, true /* process files */, true /* recurse */);
+
+ if (jsonPaths.empty()) {
+ fprintf(stderr, "Didn't find any .json files inside -build_all path: %s\n", options.buildAllPath.c_str());
+ exit(EX_DATAERR);
+ }
+
+ if (options.listConfigs) {
+ for (const std::string& path : jsonPaths) {
+ fprintf(stderr, "Found config: %s\n", path.c_str());
+ }
+ exit(EXIT_SUCCESS);
+ }
+ }
+
+ if (!options.artifactDir.empty()) {
+ // Find the dylib cache dir from inside the artifact dir
+ struct stat stat_buf;
+ if (stat(options.artifactDir.c_str(), &stat_buf) != 0) {
+ fprintf(stderr, "Could not find artifact path '%s'\n", options.artifactDir.c_str());
+ exit(EX_NOINPUT);
+ }
+ std::string dir = options.artifactDir + "/AppleInternal/Developer/DylibCaches";
+ if (stat(dir.c_str(), &stat_buf) != 0) {
+ fprintf(stderr, "Could not find artifact path '%s'\n", dir.c_str());
+ exit(EX_DATAERR);
+ }
+
+ if (!options.release.empty()) {
+ // Use the given release
+ options.dylibCacheDir = dir + "/" + options.release + ".dlc";
+ } else {
+ // Find a release directory
+ __block std::vector<std::string> subDirectories;
+ iterateDirectoryTree("", dir, ^(const std::string& dirPath) {
+ subDirectories.push_back(dirPath);
+ return false;
+ }, nullptr, false, false);
+
+ if (subDirectories.empty()) {
+ fprintf(stderr, "Could not find dlc subdirectories inside '%s'\n", dir.c_str());
+ exit(EX_DATAERR);
+ }
+
+ if (subDirectories.size() > 1) {
+ fprintf(stderr, "Found too many subdirectories inside artifact path '%s'. Use -release to select one\n", dir.c_str());
+ exit(EX_DATAERR);
+ }
+
+ options.dylibCacheDir = subDirectories.front();
+ }
+ }
+
+ if (options.dylibCacheDir.empty()) {
+ options.dylibCacheDir = std::string("/AppleInternal/Developer/DylibCaches/") + options.release + ".dlc";
+ }
+
+ //Move into the dir so we can use relative path manifests
+ if ( int result = chdir(options.dylibCacheDir.c_str()); result == -1 ) {
+ fprintf(stderr, "Couldn't cd in to dylib cache directory of '%s' because: %s\n",
+ options.dylibCacheDir.c_str(), strerror(errno));
+ }
+
+ if (!options.buildAllPath.empty()) {
+ bool requiresConcurrencyLimit = false;
+ dispatch_semaphore_t concurrencyLimit = NULL;
+ // Try build 1 cache per 8GB of RAM
+ uint64_t memSize = 0;
+ size_t sz = sizeof(memSize);
+ if ( sysctlbyname("hw.memsize", &memSize, &sz, NULL, 0) == 0 ) {
+ uint64_t maxThreads = std::max(memSize / 0x200000000ULL, 1ULL);
+ fprintf(stderr, "Detected %lldGb or less of memory, limiting concurrency to %lld threads\n",
+ memSize / (1 << 30), maxThreads);
+ requiresConcurrencyLimit = true;
+ concurrencyLimit = dispatch_semaphore_create(maxThreads);
+ }
+
+ __block int finishedCount = 0;
+ std::atomic_bool failedToBuildCache = { false };
+ __block auto& failedToBuildCacheRef = failedToBuildCache;
+ dispatch_apply(jsonPaths.size(), DISPATCH_APPLY_AUTO, ^(size_t index) {
+ // Horrible hack to limit concurrency in low spec build machines.
+ if (requiresConcurrencyLimit) { dispatch_semaphore_wait(concurrencyLimit, DISPATCH_TIME_FOREVER); }
+
+ const std::string& jsonPath = jsonPaths[index];
+ Diagnostics diags(options.debug);
+ buildCacheFromJSONManifest(diags, options, jsonPath);
+
+ if (diags.hasError()) {
+ fprintf(stderr, "dyld_shared_cache_builder: error: %s\n", diags.errorMessage().c_str());
+ failedToBuildCacheRef = true;
+ }
+
+ time_t endTime = time(0);
+ std::string timeString = asctime(localtime(&endTime));
+ timeString.pop_back();
+ fprintf(stderr, "Finished[% 4d/% 4d]: %s %s\n",
+ ++finishedCount, (int)jsonPaths.size(), timeString.c_str(), leafName(jsonPath));
+
+ if (requiresConcurrencyLimit) { dispatch_semaphore_signal(concurrencyLimit); }
+ });
+
+ if ( failedToBuildCacheRef )
+ return EXIT_FAILURE;
+ } else {
+ Diagnostics diags(options.debug);
+ buildCacheFromJSONManifest(diags, options, jsonManifestPath);
+
+ if (diags.hasError()) {
+ fprintf(stderr, "dyld_shared_cache_builder: error: %s\n", diags.errorMessage().c_str());
+ return EXIT_FAILURE;
+ }
+ }
+
+ Diagnostics diags;
+ const char* args[8];
+ args[0] = (char*)"/bin/rm";
+ args[1] = (char*)"-rf";
+ args[2] = (char*)tempRootsDir;
+ args[3] = nullptr;
+ (void)runCommandAndWait(diags, args);
+
+ if (diags.hasError()) {
+ // errors from our final rm -rf should just be warnings
+ fprintf(stderr, "dyld_shared_cache_builder: warning: %s\n", diags.errorMessage().c_str());
+ }
+
+ for (const std::string& warn : diags.warnings()) {
+ fprintf(stderr, "dyld_shared_cache_builder: warning: %s\n", warn.c_str());
+ }
+
+ // Finally, write the roots.txt to tell us which roots we pulled in
+ if (!options.dstRoot.empty())
+ writeRootList(options.dstRoot + "/System/Library/Caches/com.apple.dyld", options.roots);
+
+ return EXIT_SUCCESS;
+}