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,69 @@ 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
+ let env = ProcessEnv.vars
559
+ for key in env.keys.sorted(by: >) {
560
+ stream <<< key <<< env[key]!
561
+ }
562
+ stream <<< swiftpmVersion
563
+
564
+ return SHA256().hash(stream.bytes)
565
+ }
566
+ }
567
+
568
+ fileprivate struct ManifestParseResult: Codable {
499
569
var hasErrors: Bool {
500
570
return parsedManifest == nil
501
571
}
@@ -739,25 +809,6 @@ public final class ManifestLoader: ManifestLoaderProtocol {
739
809
// Bin dir will be set when developing swiftpm without building all of the runtimes.
740
810
return resources.binDir ?? resources.libDir.appending(version.runtimeSubpath)
741
811
}
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
812
}
762
813
763
814
/// Returns the sandbox profile to be used when parsing manifest on macOS.
@@ -790,230 +841,12 @@ private func sandboxProfile(toolsVersion: ToolsVersion, cacheDirectories: [Absol
790
841
return stream.bytes.description
791
842
}
792
843
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
844
/// Enum to represent either the manifest path or its content.
974
845
private enum ManifestPathOrContents {
975
846
case path(AbsolutePath)
976
847
case contents([UInt8])
977
848
}
978
849
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
850
extension TSCBasic.Diagnostic.Message {
1018
851
static func duplicateTargetName(targetName: String) -> Self {
1019
852
.error("duplicate target named '\(targetName)'")
0 commit comments