1 /* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil -*-
3 * Copyright (c) 2017 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 "mrm_shared_cache_builder.h"
26 #include "SharedCacheBuilder.h"
27 #include "ClosureFileSystem.h"
28 #include "FileUtils.h"
29 #include "JSONReader.h"
37 static const uint64_t kMinBuildVersion
= 1; //The minimum version BuildOptions struct we can support
38 static const uint64_t kMaxBuildVersion
= 2; //The maximum version BuildOptions struct we can support
40 static const uint32_t MajorVersion
= 1;
41 static const uint32_t MinorVersion
= 2;
49 const uint64_t length
;
55 class FileSystemMRM
: public FileSystem
{
57 FileSystemMRM() : FileSystem() { }
59 bool getRealPath(const char possiblePath
[MAXPATHLEN
], char realPath
[MAXPATHLEN
]) const override
{
61 std::string resolvedPath
= symlinkResolver
.realPath(diag
, possiblePath
);
62 if (diag
.hasError()) {
63 diag
.verbose("MRM error: %s\n", diag
.errorMessage().c_str());
68 // FIXME: Should we only return real paths of files which point to macho's? For now that is what we are doing
69 auto it
= fileMap
.find(resolvedPath
);
70 if (it
== fileMap
.end())
73 memcpy(realPath
, resolvedPath
.c_str(), std::min((size_t)MAXPATHLEN
, resolvedPath
.size() + 1));
77 bool loadFile(const char* path
, LoadedFileInfo
& info
, char realerPath
[MAXPATHLEN
], void (^error
)(const char* format
, ...)) const override
{
79 std::string resolvedPath
= symlinkResolver
.realPath(diag
, path
);
80 if (diag
.hasError()) {
81 diag
.verbose("MRM error: %s\n", diag
.errorMessage().c_str());
86 auto it
= fileMap
.find(resolvedPath
);
87 if (it
== fileMap
.end())
90 if (resolvedPath
== path
)
93 memcpy(realerPath
, resolvedPath
.c_str(), std::min((size_t)MAXPATHLEN
, resolvedPath
.size() + 1));
95 // The file exists at this exact path. Lets use it!
96 const FileInfo
& fileInfo
= files
[it
->second
];
98 info
.fileContent
= fileInfo
.data
;
99 info
.fileContentLen
= fileInfo
.length
;
100 info
.sliceOffset
= 0;
101 info
.sliceLen
= fileInfo
.length
;
102 info
.isOSBinary
= true;
103 info
.inode
= fileInfo
.inode
;
104 info
.mtime
= fileInfo
.mtime
;
105 info
.unload
= nullptr;
110 void unloadFile(const LoadedFileInfo
& info
) const override
{
115 void unloadPartialFile(LoadedFileInfo
& info
, uint64_t keepStartOffset
, uint64_t keepLength
) const override
{
116 // Note we don't actually unload the data here, but we do want to update the offsets for other data structures to track where we are
117 info
.fileContent
= (const void*)((char*)info
.fileContent
+ keepStartOffset
);
118 info
.fileContentLen
= keepLength
;
121 bool fileExists(const char* path
, uint64_t* inode
=nullptr, uint64_t* mtime
=nullptr,
122 bool* issetuid
=nullptr, bool* inodesMatchRuntime
= nullptr) const override
{
124 std::string resolvedPath
= symlinkResolver
.realPath(diag
, path
);
125 if (diag
.hasError()) {
126 diag
.verbose("MRM error: %s\n", diag
.errorMessage().c_str());
131 auto it
= fileMap
.find(resolvedPath
);
132 if (it
== fileMap
.end())
135 // The file exists at this exact path. Lets use it!
136 const FileInfo
& fileInfo
= files
[it
->second
];
138 *inode
= fileInfo
.inode
;
140 *mtime
= fileInfo
.mtime
;
143 if (inodesMatchRuntime
)
144 *inodesMatchRuntime
= false;
149 bool addFile(const char* path
, uint8_t* data
, uint64_t size
, Diagnostics
& diag
, FileFlags fileFlags
) {
150 auto iteratorAndInserted
= fileMap
.insert(std::make_pair(path
, files
.size()));
151 if (!iteratorAndInserted
.second
) {
152 diag
.error("Already have content for path: '%s'", path
);
156 symlinkResolver
.addFile(diag
, path
);
160 // on iOS, inode is just a placeholder
161 // Note its safe to just use the index here as we only compare it during closure building
162 // and never record it in the closures
163 uint64_t inode
= files
.size() + 1;
166 files
.push_back((FileInfo
){ path
, data
, size
, fileFlags
, mtime
, inode
});
170 bool addSymlink(const char* fromPath
, const char* toPath
, Diagnostics
& diag
) {
171 symlinkResolver
.addSymlink(diag
, fromPath
, toPath
);
172 return !diag
.hasError();
175 void forEachFileInfo(std::function
<void(const char* path
, FileFlags fileFlags
)> lambda
) {
176 for (const FileInfo
& fileInfo
: files
)
177 lambda(fileInfo
.path
, fileInfo
.flags
);
180 size_t fileCount() const {
184 std::vector
<DyldSharedCache::FileAlias
> getResolvedSymlinks(Diagnostics
& diag
) {
185 return symlinkResolver
.getResolvedSymlinks(diag
);
189 std::vector
<FileInfo
> files
;
190 std::map
<std::string
, uint64_t> fileMap
;
191 SymlinkResolver symlinkResolver
;
194 } // namespace closure
197 struct BuildInstance
{
198 std::unique_ptr
<DyldSharedCache::CreateOptions
> options
;
199 std::unique_ptr
<SharedCacheBuilder
> builder
;
200 std::vector
<CacheBuilder::InputFile
> inputFiles
;
201 std::vector
<const char*> errors
;
202 std::vector
<const char*> warnings
;
203 std::vector
<std::string
> errorStrings
; // Owns the data for the errors
204 std::vector
<std::string
> warningStrings
; // Owns the data for the warnings
205 uint8_t* cacheData
= nullptr;
206 uint64_t cacheSize
= 0;
208 std::string macOSMap
; // For compatibility with update_dyld_shared_cache's .map file
209 std::string macOSMapPath
; // Owns the string for the path
210 std::string cdHash
; // Owns the data for the cdHash
211 std::string cdHashType
; // Owns the data for the cdHashType
212 std::string uuid
; // Owns the data for the uuid
215 struct BuildFileResult
{
221 struct TranslationResult
{
226 bool bufferWasMalloced
;
229 struct MRMSharedCacheBuilder
{
230 MRMSharedCacheBuilder(const BuildOptions_v1
* options
);
231 const BuildOptions_v1
* options
;
232 dyld3::closure::FileSystemMRM fileSystem
;
234 std::string dylibOrderFileData
;
235 std::string dirtyDataOrderFileData
;
236 void* objcOptimizationsFileData
;
237 size_t objcOptimizationsFileLength
;
239 // An array of builders and their options as we may have more than one builder for a given device variant.
240 std::vector
<BuildInstance
> builders
;
242 // The paths in all of the caches
243 // We keep this here to own the std::string path data
244 std::map
<std::string
, std::unordered_set
<const BuildInstance
*>> dylibsInCaches
;
246 // The results from all of the builders
247 // We keep this in a vector to own the data.
248 std::vector
<FileResult
*> fileResults
;
249 std::vector
<FileResult
> fileResultStorage
;
250 std::vector
<std::pair
<uint64_t, bool>> fileResultBuffers
;
252 // The results from all of the builders
253 // We keep this in a vector to own the data.
254 std::vector
<CacheResult
*> cacheResults
;
255 std::vector
<CacheResult
> cacheResultStorage
;
258 // The files to remove. These are in every copy of the caches we built
259 std::vector
<const char*> filesToRemove
;
261 std::vector
<const char*> errors
;
262 std::vector
<std::string
> errorStorage
;
263 pthread_mutex_t lock
;
271 State state
= AcceptingFiles
;
273 void runSync(void (^block
)()) {
274 pthread_mutex_lock(&lock
);
276 pthread_mutex_unlock(&lock
);
279 __attribute__((format(printf
, 2, 3)))
280 void error(const char* format
, ...) {
282 va_start(list
, format
);
284 diag
.error(format
, list
);
287 errorStorage
.push_back(diag
.errorMessage());
288 errors
.push_back(errorStorage
.back().data());
292 MRMSharedCacheBuilder::MRMSharedCacheBuilder(const BuildOptions_v1
* options
)
294 , lock(PTHREAD_MUTEX_INITIALIZER
)
295 , objcOptimizationsFileData(nullptr)
296 , objcOptimizationsFileLength(0)
301 void validiateBuildOptions(const BuildOptions_v1
* options
, MRMSharedCacheBuilder
& builder
) {
302 if (options
->version
< kMinBuildVersion
) {
303 builder
.error("Builder version %llu is less than minimum supported version of %llu", options
->version
, kMinBuildVersion
);
305 if (options
->version
> kMaxBuildVersion
) {
306 builder
.error("Builder version %llu is greater than maximum supported version of %llu", options
->version
, kMaxBuildVersion
);
308 if (!options
->updateName
) {
309 builder
.error("updateName must not be null");
311 if (!options
->deviceName
) {
312 builder
.error("deviceName must not be null");
314 switch (options
->disposition
) {
315 case Disposition::Unknown
:
316 case Disposition::InternalDevelopment
:
317 case Disposition::Customer
:
318 case Disposition::InternalMinDevelopment
:
321 builder
.error("unknown disposition value");
324 switch (options
->platform
) {
325 case Platform::unknown
:
326 builder
.error("platform must not be unknown");
328 case Platform::macOS
:
331 case Platform::watchOS
:
332 case Platform::bridgeOS
:
333 case Platform::iOSMac
:
334 case Platform::iOS_simulator
:
335 case Platform::tvOS_simulator
:
336 case Platform::watchOS_simulator
:
339 builder
.error("unknown platform value");
342 if (!options
->archs
) {
343 builder
.error("archs must not be null");
345 if (!options
->numArchs
) {
346 builder
.error("numArchs must not be 0");
350 void getVersion(uint32_t *major
, uint32_t *minor
) {
351 *major
= MajorVersion
;
352 *minor
= MinorVersion
;
355 struct MRMSharedCacheBuilder
* createSharedCacheBuilder(const BuildOptions_v1
* options
) {
356 MRMSharedCacheBuilder
* builder
= new MRMSharedCacheBuilder(options
);
358 // Check the option struct values are valid
359 validiateBuildOptions(options
, *builder
);
364 bool addFile(struct MRMSharedCacheBuilder
* builder
, const char* path
, uint8_t* data
, uint64_t size
, FileFlags fileFlags
) {
365 __block
bool success
= false;
366 builder
->runSync(^() {
367 if (builder
->state
!= MRMSharedCacheBuilder::AcceptingFiles
) {
368 builder
->error("Cannot add file: '%s' as we have already started building", path
);
371 size_t pathLength
= strlen(path
);
372 if (pathLength
== 0) {
373 builder
->error("Empty path");
376 if (pathLength
>= MAXPATHLEN
) {
377 builder
->error("Path is too long: '%s'", path
);
380 if (data
== nullptr) {
381 builder
->error("Data cannot be null for file: '%s'", path
);
387 case ShouldBeExcludedFromCacheIfUnusedLeaf
:
388 case RequiredClosure
:
391 builder
->dylibOrderFileData
= std::string((char*)data
, size
);
394 case DirtyDataOrderFile
:
395 builder
->dirtyDataOrderFileData
= std::string((char*)data
, size
);
398 case ObjCOptimizationsFile
:
399 builder
->objcOptimizationsFileData
= data
;
400 builder
->objcOptimizationsFileLength
= size
;
404 builder
->error("unknown file flags value");
408 if (!builder
->fileSystem
.addFile(path
, data
, size
, diag
, fileFlags
)) {
409 builder
->errorStorage
.push_back(diag
.errorMessage());
410 builder
->errors
.push_back(builder
->errorStorage
.back().data());
418 bool addSymlink(struct MRMSharedCacheBuilder
* builder
, const char* fromPath
, const char* toPath
) {
419 __block
bool success
= false;
420 builder
->runSync(^() {
421 if (builder
->state
!= MRMSharedCacheBuilder::AcceptingFiles
) {
422 builder
->error("Cannot add file: '%s' as we have already started building", fromPath
);
425 size_t pathLength
= strlen(fromPath
);
426 if (pathLength
== 0) {
427 builder
->error("Empty path");
430 if (pathLength
>= MAXPATHLEN
) {
431 builder
->error("Path is too long: '%s'", fromPath
);
435 if (!builder
->fileSystem
.addSymlink(fromPath
, toPath
, diag
)) {
436 builder
->errorStorage
.push_back(diag
.errorMessage());
437 builder
->errors
.push_back(builder
->errorStorage
.back().data());
445 static DyldSharedCache::LocalSymbolsMode
platformExcludeLocalSymbols(Platform platform
) {
447 case Platform::unknown
:
448 case Platform::macOS
:
449 return DyldSharedCache::LocalSymbolsMode::keep
;
452 case Platform::watchOS
:
453 case Platform::bridgeOS
:
454 return DyldSharedCache::LocalSymbolsMode::unmap
;
455 case Platform::iOSMac
:
456 case Platform::iOS_simulator
:
457 case Platform::tvOS_simulator
:
458 case Platform::watchOS_simulator
:
459 return DyldSharedCache::LocalSymbolsMode::keep
;
463 static DyldSharedCache::LocalSymbolsMode
excludeLocalSymbols(const BuildOptions_v1
* options
) {
464 if ( options
->version
>= 2 ) {
465 const BuildOptions_v2
* v2
= (const BuildOptions_v2
*)options
;
466 if ( v2
->optimizeForSize
)
467 return DyldSharedCache::LocalSymbolsMode::strip
;
470 // Old build options always use the platform default
471 return platformExcludeLocalSymbols(options
->platform
);
474 static bool optimizeDyldDlopens(const BuildOptions_v1
* options
) {
475 // Old builds always default to dyld3 optimisations
476 if ( options
->version
< 2 ) {
480 // If we want to optimize for size instead of speed, then disable dyld3 dlopen closures
481 const BuildOptions_v2
* v2
= (const BuildOptions_v2
*)options
;
482 return !v2
->optimizeForSize
;
485 static DyldSharedCache::CodeSigningDigestMode
platformCodeSigningDigestMode(Platform platform
) {
487 case Platform::unknown
:
488 case Platform::macOS
:
491 return DyldSharedCache::SHA256only
;
492 case Platform::watchOS
:
493 return DyldSharedCache::Agile
;
494 case Platform::bridgeOS
:
495 case Platform::iOSMac
:
496 case Platform::iOS_simulator
:
497 case Platform::tvOS_simulator
:
498 case Platform::watchOS_simulator
:
499 return DyldSharedCache::SHA256only
;
503 static bool platformIsForSimulator(Platform platform
) {
505 case Platform::unknown
:
506 case Platform::macOS
:
509 case Platform::watchOS
:
510 case Platform::bridgeOS
:
511 case Platform::iOSMac
:
513 case Platform::iOS_simulator
:
514 case Platform::tvOS_simulator
:
515 case Platform::watchOS_simulator
:
520 static const char* dispositionName(Disposition disposition
) {
521 switch (disposition
) {
522 case Disposition::Unknown
:
524 case Disposition::InternalDevelopment
:
526 case Disposition::Customer
:
528 case Disposition::InternalMinDevelopment
:
529 return "InternalMinDevelopment";
533 // This is a JSON file containing the list of classes for which
534 // we should try to build IMP caches.
535 dyld3::json::Node
parseObjcOptimizationsFile(Diagnostics
& diags
, const void* data
, size_t length
) {
536 return dyld3::json::readJSON(diags
, data
, length
);
539 bool runSharedCacheBuilder(struct MRMSharedCacheBuilder
* builder
) {
540 __block
bool success
= false;
541 builder
->runSync(^() {
542 if (builder
->state
!= MRMSharedCacheBuilder::AcceptingFiles
) {
543 builder
->error("Builder has already been run");
546 builder
->state
= MRMSharedCacheBuilder::Building
;
547 if (builder
->fileSystem
.fileCount() == 0) {
548 builder
->error("Cannot run builder with no files");
551 __block Diagnostics diag
;
552 std::vector
<DyldSharedCache::FileAlias
> aliases
= builder
->fileSystem
.getResolvedSymlinks(diag
);
553 if (diag
.hasError()) {
554 diag
.verbose("Symlink resolver error: %s\n", diag
.errorMessage().c_str());
557 if (!builder
->errors
.empty()) {
558 builder
->error("Skipping running shared cache builder due to previous errors");
562 __block
std::vector
<SharedCacheBuilder::InputFile
> inputFiles
;
563 builder
->fileSystem
.forEachFileInfo(^(const char* path
, FileFlags fileFlags
) {
564 SharedCacheBuilder::InputFile::State state
= SharedCacheBuilder::InputFile::Unset
;
566 case FileFlags::NoFlags
:
567 state
= SharedCacheBuilder::InputFile::Unset
;
569 case FileFlags::MustBeInCache
:
570 state
= SharedCacheBuilder::InputFile::MustBeIncluded
;
572 case FileFlags::ShouldBeExcludedFromCacheIfUnusedLeaf
:
573 state
= SharedCacheBuilder::InputFile::MustBeExcludedIfUnused
;
575 case FileFlags::RequiredClosure
:
576 state
= SharedCacheBuilder::InputFile::MustBeIncluded
;
578 case FileFlags::DylibOrderFile
:
579 case FileFlags::DirtyDataOrderFile
:
580 case FileFlags::ObjCOptimizationsFile
:
581 builder
->error("Order files should not be in the file system");
584 inputFiles
.emplace_back((SharedCacheBuilder::InputFile
){ path
, state
});
587 auto addCacheConfiguration
= ^(bool isOptimized
) {
588 for (uint64_t i
= 0; i
!= builder
->options
->numArchs
; ++i
) {
589 // HACK: Skip i386 for macOS
590 if ( (builder
->options
->platform
== Platform::macOS
) && (strcmp(builder
->options
->archs
[i
], "i386") == 0 ) )
592 auto options
= std::make_unique
<DyldSharedCache::CreateOptions
>((DyldSharedCache::CreateOptions
){});
593 const char *cacheSuffix
= (isOptimized
? "" : ".development");
594 if ( builder
->options
->platform
== Platform::macOS
)
596 std::string runtimePath
= (builder
->options
->platform
== Platform::macOS
) ? MACOSX_MRM_DYLD_SHARED_CACHE_DIR
: IPHONE_DYLD_SHARED_CACHE_DIR
;
597 options
->outputFilePath
= runtimePath
+ "dyld_shared_cache_" + builder
->options
->archs
[i
] + cacheSuffix
;
598 options
->outputMapFilePath
= options
->outputFilePath
+ ".json";
599 options
->archs
= &dyld3::GradedArchs::forName(builder
->options
->archs
[i
]);
600 options
->platform
= (dyld3::Platform
)builder
->options
->platform
;
601 options
->localSymbolMode
= excludeLocalSymbols(builder
->options
);
602 options
->optimizeStubs
= isOptimized
;
603 options
->optimizeDyldDlopens
= optimizeDyldDlopens(builder
->options
);
604 options
->optimizeDyldLaunches
= true;
605 options
->codeSigningDigestMode
= platformCodeSigningDigestMode(builder
->options
->platform
);
606 options
->dylibsRemovedDuringMastering
= true;
607 options
->inodesAreSameAsRuntime
= false;
608 options
->cacheSupportsASLR
= true;
609 options
->forSimulator
= platformIsForSimulator(builder
->options
->platform
);
610 options
->isLocallyBuiltCache
= builder
->options
->isLocallyBuiltCache
;
611 options
->verbose
= builder
->options
->verboseDiagnostics
;
612 options
->evictLeafDylibsOnOverflow
= true;
613 options
->loggingPrefix
= std::string(builder
->options
->deviceName
) + dispositionName(builder
->options
->disposition
) + "." + builder
->options
->archs
[i
] + cacheSuffix
;
614 options
->dylibOrdering
= parseOrderFile(builder
->dylibOrderFileData
);
615 options
->dirtyDataSegmentOrdering
= parseOrderFile(builder
->dirtyDataOrderFileData
);
616 options
->objcOptimizations
= parseObjcOptimizationsFile(diag
, builder
->objcOptimizationsFileData
, builder
->objcOptimizationsFileLength
);
618 auto cacheBuilder
= std::make_unique
<SharedCacheBuilder
>(*options
.get(), builder
->fileSystem
);
619 builder
->builders
.emplace_back((BuildInstance
) { std::move(options
), std::move(cacheBuilder
), inputFiles
});
623 // Enqueue a cache for each configuration
624 switch (builder
->options
->disposition
) {
625 case Disposition::Unknown
:
626 case Disposition::InternalDevelopment
:
627 // HACK: MRM for the mac should only get development, even if it requested both
628 if (builder
->options
->platform
== Platform::macOS
) {
629 addCacheConfiguration(false);
631 addCacheConfiguration(false);
632 addCacheConfiguration(true);
635 case Disposition::Customer
:
636 addCacheConfiguration(true);
638 case Disposition::InternalMinDevelopment
:
639 addCacheConfiguration(false);
643 // FIXME: This step can run in parallel.
644 for (auto& buildInstance
: builder
->builders
) {
645 SharedCacheBuilder
* cacheBuilder
= buildInstance
.builder
.get();
646 cacheBuilder
->build(buildInstance
.inputFiles
, aliases
);
648 // First put the warnings in to a vector to own them.
649 buildInstance
.warningStrings
.reserve(cacheBuilder
->warnings().size());
650 for (const std::string
& warning
: cacheBuilder
->warnings())
651 buildInstance
.warningStrings
.push_back(warning
);
653 // Then copy to a vector to reference the owner
654 buildInstance
.warnings
.reserve(buildInstance
.warningStrings
.size());
655 for (const std::string
& warning
: buildInstance
.warningStrings
)
656 buildInstance
.warnings
.push_back(warning
.c_str());
658 if (!cacheBuilder
->errorMessage().empty()) {
659 // First put the errors in to a vector to own them.
660 buildInstance
.errorStrings
.push_back(cacheBuilder
->errorMessage());
662 // Then copy to a vector to reference the owner
663 buildInstance
.errors
.reserve(buildInstance
.errorStrings
.size());
664 for (const std::string
& error
: buildInstance
.errorStrings
)
665 buildInstance
.errors
.push_back(error
.c_str());
668 if (cacheBuilder
->errorMessage().empty()) {
669 cacheBuilder
->writeBuffer(buildInstance
.cacheData
, buildInstance
.cacheSize
);
670 buildInstance
.jsonMap
= cacheBuilder
->getMapFileJSONBuffer(builder
->options
->deviceName
);
671 if ( buildInstance
.options
->platform
== dyld3::Platform::macOS
) {
672 // For compatibility with update_dyld_shared_cache, put a .map file next to the shared cache
673 buildInstance
.macOSMap
= cacheBuilder
->getMapFileBuffer();
674 buildInstance
.macOSMapPath
= buildInstance
.options
->outputFilePath
+ ".map";
676 buildInstance
.cdHash
= cacheBuilder
->cdHashFirst();
677 buildInstance
.uuid
= cacheBuilder
->uuid();
678 switch (buildInstance
.options
->codeSigningDigestMode
) {
679 case DyldSharedCache::SHA256only
:
680 buildInstance
.cdHashType
= "sha256";
682 case DyldSharedCache::SHA1only
:
683 buildInstance
.cdHashType
= "sha1";
685 case DyldSharedCache::Agile
:
686 buildInstance
.cdHashType
= "sha1";
690 // Track the dylibs which were included in this cache
691 cacheBuilder
->forEachCacheDylib(^(const std::string
&path
) {
692 builder
->dylibsInCaches
[path
.c_str()].insert(&buildInstance
);
694 cacheBuilder
->forEachCacheSymlink(^(const std::string
&path
) {
695 builder
->dylibsInCaches
[path
.c_str()].insert(&buildInstance
);
698 // Free the cache builder now so that we don't keep too much memory resident
699 cacheBuilder
->deleteBuffer();
700 buildInstance
.builder
.reset();
704 // Now that we have run all of the builds, collect the results
705 // First push file results for each of the shared caches we built
706 for (auto& buildInstance
: builder
->builders
) {
707 CacheResult cacheBuildResult
;
708 cacheBuildResult
.version
= 1;
709 cacheBuildResult
.loggingPrefix
= buildInstance
.options
->loggingPrefix
.c_str();
710 cacheBuildResult
.deviceConfiguration
= buildInstance
.options
->loggingPrefix
.c_str();
711 cacheBuildResult
.warnings
= buildInstance
.warnings
.empty() ? nullptr : buildInstance
.warnings
.data();
712 cacheBuildResult
.numWarnings
= buildInstance
.warnings
.size();
713 cacheBuildResult
.errors
= buildInstance
.errors
.empty() ? nullptr : buildInstance
.errors
.data();
714 cacheBuildResult
.numErrors
= buildInstance
.errors
.size();
715 cacheBuildResult
.uuidString
= buildInstance
.uuid
.c_str();
716 cacheBuildResult
.mapJSON
= buildInstance
.jsonMap
.c_str();
718 builder
->cacheResultStorage
.emplace_back(cacheBuildResult
);
720 if (!buildInstance
.errors
.empty())
723 FileResult cacheFileResult
;
724 cacheFileResult
.version
= 1;
725 cacheFileResult
.path
= buildInstance
.options
->outputFilePath
.c_str();
726 cacheFileResult
.behavior
= AddFile
;
727 cacheFileResult
.data
= buildInstance
.cacheData
;
728 cacheFileResult
.size
= buildInstance
.cacheSize
;
729 cacheFileResult
.hashArch
= buildInstance
.options
->archs
->name();
730 cacheFileResult
.hashType
= buildInstance
.cdHashType
.c_str();
731 cacheFileResult
.hash
= buildInstance
.cdHash
.c_str();
733 builder
->fileResultBuffers
.push_back({ builder
->fileResultStorage
.size(), true });
734 builder
->fileResultStorage
.emplace_back(cacheFileResult
);
736 // Add a file result for the .map file
737 if ( !buildInstance
.macOSMap
.empty() ) {
738 FileResult cacheFileResult
;
739 cacheFileResult
.version
= 1;
740 cacheFileResult
.path
= buildInstance
.macOSMapPath
.c_str();
741 cacheFileResult
.behavior
= AddFile
;
742 cacheFileResult
.data
= (const uint8_t*)buildInstance
.macOSMap
.data();
743 cacheFileResult
.size
= buildInstance
.macOSMap
.size();
744 cacheFileResult
.hashArch
= buildInstance
.options
->archs
->name();
745 cacheFileResult
.hashType
= buildInstance
.cdHashType
.c_str();
746 cacheFileResult
.hash
= buildInstance
.cdHash
.c_str();
748 builder
->fileResultStorage
.emplace_back(cacheFileResult
);
752 // Copy from the storage to the vector we can return to the API.
753 for (auto &fileResult
: builder
->fileResultStorage
)
754 builder
->fileResults
.push_back(&fileResult
);
755 for (auto &cacheResult
: builder
->cacheResultStorage
)
756 builder
->cacheResults
.push_back(&cacheResult
);
759 // Add entries to tell us to remove all of the dylibs from disk which are in every cache.
760 const size_t numCaches
= builder
->builders
.size();
761 for (const auto& dylibAndCount
: builder
->dylibsInCaches
) {
762 const char* pathToRemove
= dylibAndCount
.first
.c_str();
764 if ( builder
->options
->platform
== Platform::macOS
) {
765 // macOS has to leave the simulator support binaries on disk
766 if ( strcmp(pathToRemove
, "/usr/lib/system/libsystem_kernel.dylib") == 0 )
768 if ( strcmp(pathToRemove
, "/usr/lib/system/libsystem_platform.dylib") == 0 )
770 if ( strcmp(pathToRemove
, "/usr/lib/system/libsystem_pthread.dylib") == 0 )
774 if (dylibAndCount
.second
.size() == numCaches
) {
775 builder
->filesToRemove
.push_back(pathToRemove
);
777 // File is not in every cache, so likely has perhaps only x86_64h slice
778 // but we built both x86_64 and x86_64h caches.
779 // We may still delete it if its in all caches it's eligible for, ie, we
780 // assume the cache builder knows about all possible arch's on the system and
781 // can delete anything it knows can't run
782 bool canDeletePath
= true;
783 for (auto& buildInstance
: builder
->builders
) {
784 if ( dylibAndCount
.second
.count(&buildInstance
) != 0 )
786 // This builder didn't get this image. See if the image was ineligible
787 // based on slide, ie, that dyld at runtime couldn't load this anyway, so
788 // so removing it from disk won't hurt
789 Diagnostics loaderDiag
;
790 const dyld3::GradedArchs
* archs
= buildInstance
.options
->archs
;
791 dyld3::Platform platform
= buildInstance
.options
->platform
;
792 char realerPath
[MAXPATHLEN
];
793 dyld3::closure::LoadedFileInfo fileInfo
= dyld3::MachOAnalyzer::load(loaderDiag
, builder
->fileSystem
,
794 pathToRemove
, *archs
, platform
, realerPath
);
795 if ( (platform
== dyld3::Platform::macOS
) && loaderDiag
.hasError() ) {
796 // Try again with iOSMac
797 loaderDiag
.clearError();
798 fileInfo
= dyld3::MachOAnalyzer::load(loaderDiag
, builder
->fileSystem
,
799 pathToRemove
, *archs
, dyld3::Platform::iOSMac
, realerPath
);
802 // We don't need the file content now, as we only needed to know if this file could be loaded
803 builder
->fileSystem
.unloadFile(fileInfo
);
805 if ( loaderDiag
.hasError() || (fileInfo
.fileContent
== nullptr) ) {
806 // This arch/platform combination couldn't load this path, so we can remove it
810 // This arch was compatible, so the dylib was rejected from this cache for some other reason, eg,
811 // cache overflow. We need to keep it on-disk
812 canDeletePath
= false;
816 builder
->filesToRemove
.push_back(pathToRemove
);
820 // Quit if we had any errors.
821 for (auto& buildInstance
: builder
->builders
) {
822 if (!buildInstance
.errors
.empty())
826 builder
->state
= MRMSharedCacheBuilder::FinishedBuilding
;
832 const char* const* getErrors(const struct MRMSharedCacheBuilder
* builder
, uint64_t* errorCount
) {
833 if (builder
->errors
.empty())
835 *errorCount
= builder
->errors
.size();
836 return builder
->errors
.data();
839 const struct FileResult
* const* getFileResults(struct MRMSharedCacheBuilder
* builder
, uint64_t* resultCount
) {
840 if (builder
->fileResults
.empty())
842 *resultCount
= builder
->fileResults
.size();
843 return builder
->fileResults
.data();
846 const struct CacheResult
* const* getCacheResults(struct MRMSharedCacheBuilder
* builder
, uint64_t* resultCount
) {
847 if (builder
->cacheResults
.empty())
849 *resultCount
= builder
->cacheResults
.size();
850 return builder
->cacheResults
.data();
853 const char* const* getFilesToRemove(const struct MRMSharedCacheBuilder
* builder
, uint64_t* fileCount
) {
854 if (builder
->filesToRemove
.empty())
856 *fileCount
= builder
->filesToRemove
.size();
857 return builder
->filesToRemove
.data();
860 void destroySharedCacheBuilder(struct MRMSharedCacheBuilder
* builder
) {
861 for (auto &indexAndIsDataMalloced
: builder
->fileResultBuffers
) {
862 FileResult
& fileResult
= builder
->fileResultStorage
[indexAndIsDataMalloced
.first
];
863 if (indexAndIsDataMalloced
.second
) {
864 free((void*)fileResult
.data
);
866 vm_deallocate(mach_task_self(), (vm_address_t
)fileResult
.data
, fileResult
.size
);
868 fileResult
.data
= nullptr;