11
11
import TSCBasic
12
12
import PackageModel
13
13
import TSCUtility
14
- import SPMLLBuild
15
14
import Foundation
16
15
public typealias FileSystem = TSCBasic . FileSystem
17
16
@@ -137,6 +136,7 @@ public final class ManifestLoader: ManifestLoaderProtocol {
137
136
}
138
137
let cacheDir : AbsolutePath !
139
138
let delegate : ManifestLoaderDelegate ?
139
+ let cache : PersistentCacheProtocol ?
140
140
141
141
public init (
142
142
manifestResources: ManifestResourceProvider ,
@@ -155,6 +155,11 @@ public final class ManifestLoader: ManifestLoaderProtocol {
155
155
try ? localFileSystem. createDirectory ( cacheDir, recursive: true )
156
156
}
157
157
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
+ }
158
163
}
159
164
160
165
@available ( * , deprecated)
@@ -465,16 +470,19 @@ public final class ManifestLoader: ManifestLoaderProtocol {
465
470
pathOrContents = . path( inputPath)
466
471
}
467
472
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 (
471
475
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)
473
482
} else {
474
- let key = ManifestLoadRule . RuleKey (
483
+ result = parse (
475
484
packageIdentity: packageIdentity,
476
485
pathOrContents: pathOrContents, toolsVersion: toolsVersion)
477
- result = try getEngine ( ) . build ( key: key)
478
486
}
479
487
480
488
// Throw now if we weren't able to parse the manifest.
@@ -495,7 +503,68 @@ public final class ManifestLoader: ManifestLoaderProtocol {
495
503
return parsedManifest
496
504
}
497
505
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 {
499
568
var hasErrors : Bool {
500
569
return parsedManifest == nil
501
570
}
@@ -739,25 +808,6 @@ public final class ManifestLoader: ManifestLoaderProtocol {
739
808
// Bin dir will be set when developing swiftpm without building all of the runtimes.
740
809
return resources. binDir ?? resources. libDir. appending ( version. runtimeSubpath)
741
810
}
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 ?
761
811
}
762
812
763
813
/// Returns the sandbox profile to be used when parsing manifest on macOS.
@@ -790,230 +840,12 @@ private func sandboxProfile(toolsVersion: ToolsVersion, cacheDirectories: [Absol
790
840
return stream. bytes. description
791
841
}
792
842
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
-
973
843
/// Enum to represent either the manifest path or its content.
974
844
private enum ManifestPathOrContents {
975
845
case path( AbsolutePath )
976
846
case contents( [ UInt8 ] )
977
847
}
978
848
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
-
1017
849
extension TSCBasic . Diagnostic . Message {
1018
850
static func duplicateTargetName( targetName: String ) -> Self {
1019
851
. error( " duplicate target named ' \( targetName) ' " )
0 commit comments