1111import TSCBasic
1212import PackageModel
1313import TSCUtility
14- import SPMLLBuild
1514import Foundation
1615public typealias FileSystem = TSCBasic . FileSystem
1716
@@ -137,6 +136,7 @@ public final class ManifestLoader: ManifestLoaderProtocol {
137136 }
138137 let cacheDir : AbsolutePath !
139138 let delegate : ManifestLoaderDelegate ?
139+ let cache : PersistentCacheProtocol ?
140140
141141 public init (
142142 manifestResources: ManifestResourceProvider ,
@@ -155,6 +155,11 @@ public final class ManifestLoader: ManifestLoaderProtocol {
155155 try ? localFileSystem. createDirectory ( cacheDir, recursive: true )
156156 }
157157 self . cacheDir = cacheDir. map ( resolveSymlinks)
158+
159+ self . cache = cacheDir. flatMap {
160+ // FIXME: It would be nice to emit a warning if we weren't able to create the cache.
161+ try ? SQLiteBackedPersistentCache ( cacheFilePath: $0. appending ( component: " manifest.db " ) )
162+ }
158163 }
159164
160165 @available ( * , deprecated)
@@ -465,16 +470,19 @@ public final class ManifestLoader: ManifestLoaderProtocol {
465470 pathOrContents = . path( inputPath)
466471 }
467472
468- if !self . isManifestCachingEnabled {
469- // Load directly if manifest caching is not enabled.
470- result = parse (
473+ if let cache = self . cache {
474+ let key = ManifestCacheKey (
471475 packageIdentity: packageIdentity,
472- pathOrContents: pathOrContents, toolsVersion: toolsVersion)
476+ pathOrContents: pathOrContents,
477+ toolsVersion: toolsVersion,
478+ env: ProcessEnv . vars,
479+ swiftpmVersion: Versioning . currentVersion. displayString
480+ )
481+ result = try loadManifestFromCache ( key: key, cache: cache)
473482 } else {
474- let key = ManifestLoadRule . RuleKey (
483+ result = parse (
475484 packageIdentity: packageIdentity,
476485 pathOrContents: pathOrContents, toolsVersion: toolsVersion)
477- result = try getEngine ( ) . build ( key: key)
478486 }
479487
480488 // Throw now if we weren't able to parse the manifest.
@@ -495,7 +503,68 @@ public final class ManifestLoader: ManifestLoaderProtocol {
495503 return parsedManifest
496504 }
497505
498- fileprivate struct ManifestParseResult : LLBuildValue {
506+ fileprivate func loadManifestFromCache(
507+ key: ManifestCacheKey ,
508+ cache: PersistentCacheProtocol
509+ ) throws -> ManifestParseResult {
510+ let keyHash = try key. computeHash ( )
511+ let cacheHit = try keyHash. withData {
512+ try cache. get ( key: $0)
513+ } . flatMap {
514+ try ? JSONDecoder ( ) . decode ( ManifestParseResult . self, from: $0)
515+ }
516+ if let result = cacheHit {
517+ return result
518+ }
519+
520+ let result = parse (
521+ packageIdentity: key. packageIdentity,
522+ pathOrContents: key. pathOrContents,
523+ toolsVersion: key. toolsVersion
524+ )
525+
526+ let encoder = JSONEncoder ( )
527+ if #available( macOS 10 . 15 , * ) {
528+ encoder. outputFormatting = [ . sortedKeys, . withoutEscapingSlashes]
529+ }
530+
531+ try keyHash. withData {
532+ try cache. put ( key: $0, value: encoder. encode ( result) )
533+ }
534+
535+ return result
536+ }
537+
538+ fileprivate struct ManifestCacheKey {
539+ let packageIdentity : String
540+ let pathOrContents : ManifestPathOrContents
541+ let toolsVersion : ToolsVersion
542+ let env : [ String : String ]
543+ let swiftpmVersion : String
544+
545+ func computeHash( ) throws -> ByteString {
546+ let stream = BufferedOutputByteStream ( )
547+ stream <<< packageIdentity
548+
549+ switch pathOrContents {
550+ case . path( let path) :
551+ stream <<< ( try localFileSystem. readFileContents ( path) )
552+ case . contents( let contents) :
553+ stream <<< contents
554+ }
555+
556+ stream <<< toolsVersion. description
557+
558+ for key in env. keys. sorted ( by: > ) {
559+ stream <<< key <<< env [ key] !
560+ }
561+ stream <<< swiftpmVersion
562+
563+ return SHA256 ( ) . hash ( stream. bytes)
564+ }
565+ }
566+
567+ fileprivate struct ManifestParseResult : Codable {
499568 var hasErrors : Bool {
500569 return parsedManifest == nil
501570 }
@@ -739,25 +808,6 @@ public final class ManifestLoader: ManifestLoaderProtocol {
739808 // Bin dir will be set when developing swiftpm without building all of the runtimes.
740809 return resources. binDir ?? resources. libDir. appending ( version. runtimeSubpath)
741810 }
742-
743- /// Returns the build engine.
744- private func getEngine( ) throws -> LLBuildEngine {
745- if let engine = _engine {
746- return engine
747- }
748-
749- let cacheDelegate = ManifestCacheDelegate ( )
750- let engine = LLBuildEngine ( delegate: cacheDelegate)
751- cacheDelegate. loader = self
752-
753- if isManifestCachingEnabled {
754- try localFileSystem. createDirectory ( cacheDir, recursive: true )
755- try engine. attachDB ( path: cacheDir. appending ( component: " manifest.db " ) . pathString)
756- }
757- _engine = engine
758- return engine
759- }
760- private var _engine : LLBuildEngine ?
761811}
762812
763813/// Returns the sandbox profile to be used when parsing manifest on macOS.
@@ -790,230 +840,12 @@ private func sandboxProfile(toolsVersion: ToolsVersion, cacheDirectories: [Absol
790840 return stream. bytes. description
791841}
792842
793- // MARK:- Caching support.
794-
795- final class ManifestCacheDelegate : LLBuildEngineDelegate {
796-
797- weak var loader : ManifestLoader !
798-
799- func lookupRule( rule: String , key: Key ) -> Rule {
800- switch rule {
801- case ManifestLoadRule . ruleName:
802- return ManifestLoadRule ( key, loader: loader)
803- case FileInfoRule . ruleName:
804- return FileInfoRule ( key)
805- case SwiftPMVersionRule . ruleName:
806- return SwiftPMVersionRule ( )
807- case ProcessEnvRule . ruleName:
808- return ProcessEnvRule ( )
809- default :
810- fatalError ( " Unknown rule \( rule) " )
811- }
812- }
813- }
814-
815- /// A rule to load a package manifest.
816- ///
817- /// The rule can currently only load manifests which are physically present on
818- /// the local file system. The rule will re-run if the manifest is modified.
819- final class ManifestLoadRule : LLBuildRule {
820-
821- fileprivate struct RuleKey : LLBuildKey {
822- typealias BuildValue = ManifestLoader . ManifestParseResult
823- typealias BuildRule = ManifestLoadRule
824-
825- let packageIdentity : String
826- let pathOrContents : ManifestPathOrContents
827- let toolsVersion : ToolsVersion
828- }
829-
830- override class var ruleName : String { return " \( ManifestLoadRule . self) " }
831-
832- private let key : RuleKey
833- private weak var loader : ManifestLoader !
834-
835- init ( _ key: Key , loader: ManifestLoader ) {
836- self . key = RuleKey ( key)
837- self . loader = loader
838- super. init ( )
839- }
840-
841- override func start( _ engine: LLTaskBuildEngine ) {
842- // FIXME: Ideally, we should expose an API in the manifest file to track individual
843- // environment variables instead of blindly invalidating when *anything* changes.
844- engine. taskNeedsInput ( ProcessEnvRule . RuleKey ( ) , inputID: 1 )
845-
846- engine. taskNeedsInput ( SwiftPMVersionRule . RuleKey ( ) , inputID: 2 )
847- if case . path( let path) = key. pathOrContents {
848- engine. taskNeedsInput ( FileInfoRule . RuleKey ( path: path) , inputID: 3 )
849- }
850- }
851-
852- override func isResultValid( _ priorValue: Value ) -> Bool {
853- // Always rebuild if we had a failure.
854- do {
855- let value = try RuleKey . BuildValue ( priorValue)
856- if value. hasErrors { return false }
857- } catch {
858- return false
859- }
860-
861- return super. isResultValid ( priorValue)
862- }
863-
864- override func inputsAvailable( _ engine: LLTaskBuildEngine ) {
865- let value = loader. parse (
866- packageIdentity: key. packageIdentity,
867- pathOrContents: key. pathOrContents, toolsVersion: key. toolsVersion)
868- engine. taskIsComplete ( value)
869- }
870- }
871-
872- // FIXME: Find a proper place for this rule.
873- /// A rule to compute the current process environment.
874- ///
875- /// This rule will always run.
876- final class ProcessEnvRule : LLBuildRule {
877-
878- struct RuleKey : LLBuildKey {
879- typealias BuildValue = RuleValue
880- typealias BuildRule = ProcessEnvRule
881- }
882-
883- struct RuleValue : LLBuildValue , Equatable {
884- let env : [ String : String ]
885- }
886-
887- override class var ruleName : String { return " \( ProcessEnvRule . self) " }
888-
889- override func isResultValid( _ priorValue: Value ) -> Bool {
890- // Always rebuild this rule.
891- return false
892- }
893-
894- override func inputsAvailable( _ engine: LLTaskBuildEngine ) {
895- let env = ProcessInfo . processInfo. environment
896- engine. taskIsComplete ( RuleValue ( env: env) )
897- }
898- }
899-
900- // FIXME: Find a proper place for this rule.
901- /// A rule to get file info of a file on disk.
902- final class FileInfoRule : LLBuildRule {
903-
904- struct RuleKey : LLBuildKey {
905- typealias BuildValue = RuleValue
906- typealias BuildRule = FileInfoRule
907-
908- let path : AbsolutePath
909- }
910-
911- typealias RuleValue = CodableResult < TSCBasic . FileInfo , StringError >
912-
913- override class var ruleName : String { return " \( FileInfoRule . self) " }
914-
915- private let key : RuleKey
916-
917- init ( _ key: Key ) {
918- self . key = RuleKey ( key)
919- super. init ( )
920- }
921-
922- override func isResultValid( _ priorValue: Value ) -> Bool {
923- let priorValue = try ? RuleValue ( priorValue)
924-
925- // Always rebuild if we had a failure.
926- if case . failure = priorValue? . result {
927- return false
928- }
929- return getFileInfo ( key. path) . result == priorValue? . result
930- }
931-
932- override func inputsAvailable( _ engine: LLTaskBuildEngine ) {
933- engine. taskIsComplete ( getFileInfo ( key. path) )
934- }
935-
936- private func getFileInfo( _ path: AbsolutePath ) -> RuleValue {
937- return RuleValue ( body: {
938- try localFileSystem. getFileInfo ( key. path)
939- } )
940- }
941- }
942-
943- // FIXME: Find a proper place for this rule.
944- /// A rule to compute the current version of the pacakge manager.
945- ///
946- /// This rule will always run.
947- final class SwiftPMVersionRule : LLBuildRule {
948-
949- struct RuleKey : LLBuildKey {
950- typealias BuildValue = RuleValue
951- typealias BuildRule = SwiftPMVersionRule
952- }
953-
954- struct RuleValue : LLBuildValue , Equatable {
955- let version : String
956- }
957-
958- override class var ruleName : String { return " \( SwiftPMVersionRule . self) " }
959-
960- override func isResultValid( _ priorValue: Value ) -> Bool {
961- // Always rebuild this rule.
962- return false
963- }
964-
965- override func inputsAvailable( _ engine: LLTaskBuildEngine ) {
966- // FIXME: We need to include git hash in the version
967- // string to make this rule more correct.
968- let version = Versioning . currentVersion. displayString
969- engine. taskIsComplete ( RuleValue ( version: version) )
970- }
971- }
972-
973843/// Enum to represent either the manifest path or its content.
974844private enum ManifestPathOrContents {
975845 case path( AbsolutePath )
976846 case contents( [ UInt8 ] )
977847}
978848
979- extension ManifestPathOrContents : Codable {
980- private enum CodingKeys : String , CodingKey {
981- case path
982- case contents
983- }
984-
985- init ( from decoder: Decoder ) throws {
986- let values = try decoder. container ( keyedBy: CodingKeys . self)
987- guard let key = values. allKeys. first ( where: values. contains) else {
988- throw DecodingError . dataCorrupted ( . init( codingPath: decoder. codingPath, debugDescription: " Did not find a matching key " ) )
989- }
990- switch key {
991- case . path:
992- var unkeyedValues = try values. nestedUnkeyedContainer ( forKey: key)
993- let a1 = try unkeyedValues. decode ( AbsolutePath . self)
994- self = . path( a1)
995- case . contents:
996- var unkeyedValues = try values. nestedUnkeyedContainer ( forKey: key)
997- let a1 = try unkeyedValues. decode ( [ UInt8 ] . self)
998- self = . contents( a1)
999- }
1000- }
1001-
1002- func encode( to encoder: Encoder ) throws {
1003- var container = encoder. container ( keyedBy: CodingKeys . self)
1004- switch self {
1005- case let . path( a1) :
1006- var unkeyedContainer = container. nestedUnkeyedContainer ( forKey: . path)
1007- try unkeyedContainer. encode ( a1)
1008- case let . contents( a1) :
1009- var unkeyedContainer = container. nestedUnkeyedContainer ( forKey: . contents)
1010- try unkeyedContainer. encode ( a1)
1011- }
1012- }
1013- }
1014-
1015- extension CodableResult : LLBuildValue { }
1016-
1017849extension TSCBasic . Diagnostic . Message {
1018850 static func duplicateTargetName( targetName: String ) -> Self {
1019851 . error( " duplicate target named ' \( targetName) ' " )
0 commit comments