Skip to content

Commit 5fdbbff

Browse files
committed
[Workspace] Move ManagedDependencies to a sep class
1 parent 2b6ab59 commit 5fdbbff

File tree

3 files changed

+113
-113
lines changed

3 files changed

+113
-113
lines changed

Sources/Workspace/ManagedDependency.swift

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,3 +152,72 @@ extension ManagedDependency.State: JSONMappable, JSONSerializable {
152152
}
153153
}
154154
}
155+
156+
/// Represents a collection of managed dependency which are persisted on disk.
157+
public final class ManagedDependencies: SimplePersistanceProtocol {
158+
159+
/// The current state of managed dependencies.
160+
private var dependencyMap: [RepositorySpecifier: ManagedDependency]
161+
162+
/// Path to the state file.
163+
let statePath: AbsolutePath
164+
165+
/// persistence helper
166+
let persistence: SimplePersistence
167+
168+
init(dataPath: AbsolutePath, fileSystem: FileSystem) throws {
169+
let statePath = dataPath.appending(component: "dependencies-state.json")
170+
171+
self.dependencyMap = [:]
172+
self.statePath = statePath
173+
self.persistence = SimplePersistence(
174+
fileSystem: fileSystem,
175+
schemaVersion: 1,
176+
statePath: statePath)
177+
178+
// Load the state from disk, if possible.
179+
if try !self.persistence.restoreState(self) {
180+
var fileSystem = fileSystem
181+
try fileSystem.createDirectory(dataPath, recursive: true)
182+
// There was no state, write the default state immediately.
183+
try self.persistence.saveState(self)
184+
}
185+
}
186+
187+
public subscript(_ url: String) -> ManagedDependency? {
188+
return dependencyMap[RepositorySpecifier(url: url)]
189+
}
190+
191+
public subscript(_ repository: RepositorySpecifier) -> ManagedDependency? {
192+
get {
193+
return dependencyMap[repository]
194+
}
195+
set {
196+
dependencyMap[repository] = newValue
197+
}
198+
}
199+
200+
func reset() {
201+
dependencyMap = [:]
202+
}
203+
204+
func saveState() throws {
205+
try self.persistence.saveState(self)
206+
}
207+
208+
public var values: AnySequence<ManagedDependency> {
209+
return AnySequence<ManagedDependency>(dependencyMap.values)
210+
}
211+
212+
public func restore(from json: JSON) throws {
213+
self.dependencyMap = try Dictionary(items:
214+
json.get("dependencies").map{($0.repository, $0)}
215+
)
216+
}
217+
218+
public func toJSON() -> JSON {
219+
return JSON([
220+
"dependencies": values.toJSON(),
221+
])
222+
}
223+
}

Sources/Workspace/Workspace.swift

Lines changed: 24 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -229,16 +229,11 @@ public class Workspace {
229229
private let containerProvider: RepositoryPackageContainerProvider
230230

231231
/// The current state of managed dependencies.
232-
private(set) var dependencyMap: [RepositorySpecifier: ManagedDependency]
232+
let managedDependencies: ManagedDependencies
233233

234234
/// Enable prefetching containers in resolver.
235235
let enableResolverPrefetching: Bool
236236

237-
/// The known set of dependencies.
238-
public var dependencies: AnySequence<ManagedDependency> {
239-
return AnySequence<ManagedDependency>(dependencyMap.values)
240-
}
241-
242237
/// Create a new package workspace.
243238
///
244239
/// This will automatically load the persisted state for the package, if
@@ -282,19 +277,11 @@ public class Workspace {
282277
repositoryManager: repositoryManager, manifestLoader: manifestLoader, toolsVersionLoader: toolsVersionLoader)
283278
self.fileSystem = fileSystem
284279

285-
// Initialize the default state.
286-
self.dependencyMap = [:]
287-
288280
self.pinsStore = try PinsStore(pinsFile: pinsFile, fileSystem: self.fileSystem)
281+
self.managedDependencies = try ManagedDependencies(dataPath: dataPath, fileSystem: fileSystem)
289282

290283
// Ensure the cache path exists.
291284
try createCacheDirectories()
292-
293-
// Load the state from disk, if possible.
294-
if try !restoreState() {
295-
// There was no state, write the default state immediately.
296-
try saveState()
297-
}
298285
}
299286

300287
/// Create the cache directories.
@@ -327,7 +314,7 @@ public class Workspace {
327314
let protectedAssets = Set<String>([
328315
repositoryManager.path,
329316
checkoutsPath,
330-
statePath,
317+
managedDependencies.statePath,
331318
].map { path in
332319
// Assert that these are present inside data directory.
333320
assert(path.parentDirectory == dataPath)
@@ -345,7 +332,7 @@ public class Workspace {
345332

346333
/// Resets the entire workspace by removing the data directory.
347334
public func reset() throws {
348-
dependencyMap = [:]
335+
managedDependencies.reset()
349336
repositoryManager.reset()
350337
fileSystem.removeFileTree(dataPath)
351338
try createCacheDirectories()
@@ -447,10 +434,10 @@ public class Workspace {
447434
}
448435

449436
// Change its stated to edited.
450-
dependencyMap[dependency.repository] = dependency.makingEditable(
437+
managedDependencies[dependency.repository] = dependency.makingEditable(
451438
subpath: RelativePath(packageName), state: state)
452439
// Save the state.
453-
try saveState()
440+
try managedDependencies.saveState()
454441
}
455442

456443
/// Ends the edit mode of a dependency which is in edit mode.
@@ -498,9 +485,9 @@ public class Workspace {
498485
fileSystem.removeFileTree(editablesPath)
499486
}
500487
// Restore the dependency state.
501-
dependencyMap[dependency.repository] = dependency.basedOn
488+
managedDependencies[dependency.repository] = dependency.basedOn
502489
// Save the state.
503-
try saveState()
490+
try managedDependencies.saveState()
504491
}
505492

506493
/// Pins a package at a given state.
@@ -558,7 +545,7 @@ public class Workspace {
558545
try updateCheckouts(with: results)
559546

560547
// Get the updated dependency.
561-
let newDependency = dependencyMap[dependency.repository]!
548+
let newDependency = managedDependencies[dependency.repository]!
562549

563550
// Assert that the dependency is at the pinned checkout state now.
564551
if case .checkout(let checkoutState) = newDependency.state {
@@ -629,7 +616,7 @@ public class Workspace {
629616
/// - Throws: If the operation could not be satisfied.
630617
private func fetch(repository: RepositorySpecifier) throws -> AbsolutePath {
631618
// If we already have it, fetch to update the repo from its remote.
632-
if let dependency = dependencyMap[repository] {
619+
if let dependency = managedDependencies[repository] {
633620
let path = checkoutsPath.appending(dependency.subpath)
634621
// Fetch the checkout in case there are updates available.
635622
let workingRepo = try repositoryManager.provider.openCheckout(at: path)
@@ -679,10 +666,10 @@ public class Workspace {
679666
try workingRepo.checkout(revision: checkoutState.revision)
680667

681668
// Write the state record.
682-
dependencyMap[repository] = ManagedDependency(
669+
managedDependencies[repository] = ManagedDependency(
683670
repository: repository, subpath: path.relative(to: checkoutsPath),
684671
checkoutState: checkoutState)
685-
try saveState()
672+
try managedDependencies.saveState()
686673

687674
return path
688675
}
@@ -773,7 +760,7 @@ public class Workspace {
773760
// Otherwise, we need to repin only the previous pins.
774761
for pin in pinsStore.pins {
775762
// Check if this is a stray pin.
776-
guard let dependency = dependencyMap[pin.repository] else {
763+
guard let dependency = managedDependencies[pin.repository] else {
777764
// FIXME: Use diagnosics engine when we have that.
778765
delegate.warning(message: "Consider unpinning \(pin.package), it is pinned at \(pin.state.description) but the dependency is not present.")
779766
continue
@@ -832,7 +819,7 @@ public class Workspace {
832819
case .unversioned:
833820
// Right not it is only possible to get unversioned binding if
834821
// a dependency is in editable state.
835-
assert(dependencyMap[specifier]?.state.isCheckout == false)
822+
assert(managedDependencies[specifier]?.state.isCheckout == false)
836823
packageStateChanges[specifier] = .unchanged
837824

838825
case .revision(let identifier):
@@ -850,7 +837,7 @@ public class Workspace {
850837
}
851838

852839
// First check if we have this dependency.
853-
if let currentDependency = dependencyMap[specifier] {
840+
if let currentDependency = managedDependencies[specifier] {
854841
// If current state and new state are equal, we don't need
855842
// to do anything.
856843
let newState = CheckoutState(revision: revision, branch: branch)
@@ -865,7 +852,7 @@ public class Workspace {
865852
}
866853

867854
case .version(let version):
868-
if let currentDependency = dependencyMap[specifier] {
855+
if let currentDependency = managedDependencies[specifier] {
869856
if case .checkout(let checkoutState) = currentDependency.state, checkoutState.version == version {
870857
packageStateChanges[specifier] = .unchanged
871858
} else {
@@ -877,6 +864,7 @@ public class Workspace {
877864
}
878865
}
879866
// Set the state of any old package that might have been removed.
867+
let dependencies = managedDependencies.values
880868
for specifier in dependencies.lazy.map({$0.repository}) where packageStateChanges[specifier] == nil{
881869
packageStateChanges[specifier] = .removed
882870
}
@@ -912,7 +900,7 @@ public class Workspace {
912900
let dependencies = transitiveClosure(rootManifests.map{ KeyedPair($0, key: $0.url) }) { node in
913901
return node.item.package.dependencies.flatMap{ dependency in
914902
// Check if this dependency is available.
915-
guard let managedDependency = dependencyMap[RepositorySpecifier(url: dependency.url)] else {
903+
guard let managedDependency = managedDependencies[RepositorySpecifier(url: dependency.url)] else {
916904
return nil
917905
}
918906

@@ -954,15 +942,15 @@ public class Workspace {
954942
return KeyedPair(manifest, key: manifest.url)
955943
}
956944
}
957-
958-
return DependencyManifests(roots: rootManifests, dependencies: dependencies.map{ ($0.item, dependencyMap[RepositorySpecifier(url: $0.item.url)]!) })
945+
let deps = dependencies.map{ ($0.item, managedDependencies[$0.item.url]!) }
946+
return DependencyManifests(roots: rootManifests, dependencies: deps)
959947
}
960948

961949
/// Validates that all the edited dependencies are still present in the file system.
962950
/// If some edited dependency is removed from the file system, mark it as unedited and
963951
/// fallback on the original checkout.
964952
private func validateEditedPackages() throws {
965-
for dependency in dependencies {
953+
for dependency in managedDependencies.values {
966954

967955
let dependencyPath: AbsolutePath
968956

@@ -1067,7 +1055,7 @@ public class Workspace {
10671055

10681056
/// Removes the clone and checkout of the provided specifier.
10691057
func remove(specifier: RepositorySpecifier) throws {
1070-
guard var dependency = dependencyMap[specifier] else {
1058+
guard var dependency = managedDependencies[specifier] else {
10711059
fatalError("This should never happen, trying to remove \(specifier) which isn't in workspace")
10721060
}
10731061

@@ -1085,7 +1073,7 @@ public class Workspace {
10851073
delegate.removing(repository: dependency.repository.url)
10861074

10871075
// Remove the repository from dependencies.
1088-
dependencyMap[dependency.repository] = nil
1076+
managedDependencies[dependency.repository] = nil
10891077

10901078
// Remove the checkout.
10911079
let dependencyPath = checkoutsPath.appending(dependency.subpath)
@@ -1099,7 +1087,7 @@ public class Workspace {
10991087
try repositoryManager.remove(repository: dependency.repository)
11001088

11011089
// Save the state.
1102-
try saveState()
1090+
try managedDependencies.saveState()
11031091
}
11041092

11051093
/// Loads and returns the root manifests, if all manifests are loaded successfully.
@@ -1126,63 +1114,6 @@ public class Workspace {
11261114
package: $0, baseURL: $0.asString, manifestVersion: toolsVersion.manifestVersion)
11271115
}
11281116
}
1129-
1130-
// MARK: Persistence
1131-
1132-
// FIXME: A lot of the persistence mechanism here is copied from
1133-
// `RepositoryManager`. It would be nice to get actual infrastructure around
1134-
// persistence to handle the boilerplate parts.
1135-
1136-
private enum PersistenceError: Swift.Error {
1137-
/// The schema does not match the current version.
1138-
case invalidVersion
1139-
1140-
/// There was a missing or malformed key.
1141-
case unexpectedData
1142-
}
1143-
1144-
/// The current schema version for the persisted information.
1145-
///
1146-
/// We currently discard any restored state if we detect a schema change.
1147-
private static let currentSchemaVersion = 1
1148-
1149-
/// The path at which we persist the manager state.
1150-
var statePath: AbsolutePath {
1151-
return dataPath.appending(component: "workspace-state.json")
1152-
}
1153-
1154-
/// Restore the manager state from disk.
1155-
///
1156-
/// - Throws: A PersistenceError if the state was available, but could not
1157-
/// be restored.
1158-
///
1159-
/// - Returns: True if the state was restored, or false if the state wasn't
1160-
/// available.
1161-
private func restoreState() throws -> Bool {
1162-
// If the state doesn't exist, don't try to load and fail.
1163-
if !fileSystem.exists(statePath) {
1164-
return false
1165-
}
1166-
// Load the state.
1167-
let json = try JSON(bytes: try fileSystem.readFileContents(statePath))
1168-
guard try json.get("version") == Workspace.currentSchemaVersion else {
1169-
throw PersistenceError.invalidVersion
1170-
}
1171-
self.dependencyMap = try Dictionary(items:
1172-
json.get("dependencies").map{($0.repository, $0)}
1173-
)
1174-
return true
1175-
}
1176-
1177-
/// Write the manager state to disk.
1178-
private func saveState() throws {
1179-
let data = JSON([
1180-
"version": Workspace.currentSchemaVersion,
1181-
"dependencies": dependencies.toJSON(),
1182-
])
1183-
// FIXME: This should write atomically.
1184-
try fileSystem.writeFileContents(statePath, bytes: data.toBytes())
1185-
}
11861117
}
11871118

11881119
// FIXME: Lift these to Basic once proven useful.

0 commit comments

Comments
 (0)