Skip to content

Commit 203ba32

Browse files
authored
Merge pull request #1015 from aciidb0mb3r/reloadable-result
[Workspace] Make managed dependencies reloadable
2 parents 4838288 + d9ae1cc commit 203ba32

File tree

2 files changed

+66
-25
lines changed

2 files changed

+66
-25
lines changed

Sources/Workspace/Workspace.swift

+46-5
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ public class Workspace {
229229
private let containerProvider: RepositoryPackageContainerProvider
230230

231231
/// The current state of managed dependencies.
232-
let managedDependencies: ManagedDependencies
232+
let managedDependencies: ReloadableResult<ManagedDependencies, AnyError>
233233

234234
/// Enable prefetching containers in resolver.
235235
let enableResolverPrefetching: Bool
@@ -278,7 +278,9 @@ public class Workspace {
278278
self.fileSystem = fileSystem
279279

280280
self.pinsStore = try PinsStore(pinsFile: pinsFile, fileSystem: self.fileSystem)
281-
self.managedDependencies = try ManagedDependencies(dataPath: dataPath, fileSystem: fileSystem)
281+
self.managedDependencies = ReloadableResult{
282+
try ManagedDependencies(dataPath: dataPath, fileSystem: fileSystem)
283+
}
282284

283285
// Ensure the cache path exists.
284286
try createCacheDirectories()
@@ -314,7 +316,7 @@ public class Workspace {
314316
let protectedAssets = Set<String>([
315317
repositoryManager.path,
316318
checkoutsPath,
317-
managedDependencies.statePath,
319+
try managedDependencies.dematerialize().statePath,
318320
].map { path in
319321
// Assert that these are present inside data directory.
320322
assert(path.parentDirectory == dataPath)
@@ -332,7 +334,7 @@ public class Workspace {
332334

333335
/// Resets the entire workspace by removing the data directory.
334336
public func reset() throws {
335-
managedDependencies.reset()
337+
try managedDependencies.dematerialize().reset()
336338
repositoryManager.reset()
337339
fileSystem.removeFileTree(dataPath)
338340
try createCacheDirectories()
@@ -433,6 +435,7 @@ public class Workspace {
433435
relative: false)
434436
}
435437

438+
let managedDependencies = try self.managedDependencies.dematerialize()
436439
// Change its stated to edited.
437440
managedDependencies[dependency.repository] = dependency.makingEditable(
438441
subpath: RelativePath(packageName), state: state)
@@ -484,6 +487,7 @@ public class Workspace {
484487
if fileSystem.exists(editablesPath), try fileSystem.getDirectoryContents(editablesPath).isEmpty {
485488
fileSystem.removeFileTree(editablesPath)
486489
}
490+
let managedDependencies = try self.managedDependencies.dematerialize()
487491
// Restore the dependency state.
488492
managedDependencies[dependency.repository] = dependency.basedOn
489493
// Save the state.
@@ -545,6 +549,7 @@ public class Workspace {
545549
try updateCheckouts(with: results)
546550

547551
// Get the updated dependency.
552+
let managedDependencies = try self.managedDependencies.dematerialize()
548553
let newDependency = managedDependencies[dependency.repository]!
549554

550555
// Assert that the dependency is at the pinned checkout state now.
@@ -615,6 +620,7 @@ public class Workspace {
615620
/// - Returns: The path of the local repository.
616621
/// - Throws: If the operation could not be satisfied.
617622
private func fetch(repository: RepositorySpecifier) throws -> AbsolutePath {
623+
let managedDependencies = try self.managedDependencies.dematerialize()
618624
// If we already have it, fetch to update the repo from its remote.
619625
if let dependency = managedDependencies[repository] {
620626
let path = checkoutsPath.appending(dependency.subpath)
@@ -666,6 +672,7 @@ public class Workspace {
666672
try workingRepo.checkout(revision: checkoutState.revision)
667673

668674
// Write the state record.
675+
let managedDependencies = try self.managedDependencies.dematerialize()
669676
managedDependencies[repository] = ManagedDependency(
670677
repository: repository, subpath: path.relative(to: checkoutsPath),
671678
checkoutState: checkoutState)
@@ -757,6 +764,7 @@ public class Workspace {
757764
return try pinAll(reset: true)
758765
}
759766

767+
let managedDependencies = try self.managedDependencies.dematerialize()
760768
// Otherwise, we need to repin only the previous pins.
761769
for pin in pinsStore.pins {
762770
// Check if this is a stray pin.
@@ -810,6 +818,7 @@ public class Workspace {
810818
updateBranches: Bool
811819
) throws -> [RepositorySpecifier: PackageStateChange] {
812820
var packageStateChanges = [RepositorySpecifier: PackageStateChange]()
821+
let managedDependencies = try self.managedDependencies.dematerialize()
813822
// Set the states from resolved dependencies results.
814823
for (specifier, binding) in resolvedDependencies {
815824
switch binding {
@@ -895,7 +904,10 @@ public class Workspace {
895904
/// This will load the manifests for the root package as well as all the
896905
/// current dependencies from the working checkouts.
897906
public func loadDependencyManifests(_ rootManifests: [Manifest]) -> DependencyManifests {
898-
907+
guard let managedDependencies = try? self.managedDependencies.dematerialize() else {
908+
// We need to capture the error here and continue.
909+
fatalError("unimplemented.")
910+
}
899911
// Compute the transitive closure of available dependencies.
900912
let dependencies = transitiveClosure(rootManifests.map{ KeyedPair($0, key: $0.url) }) { node in
901913
return node.item.package.dependencies.flatMap{ dependency in
@@ -950,6 +962,7 @@ public class Workspace {
950962
/// If some edited dependency is removed from the file system, mark it as unedited and
951963
/// fallback on the original checkout.
952964
private func validateEditedPackages() throws {
965+
let managedDependencies = try self.managedDependencies.dematerialize()
953966
for dependency in managedDependencies.values {
954967

955968
let dependencyPath: AbsolutePath
@@ -1055,6 +1068,7 @@ public class Workspace {
10551068

10561069
/// Removes the clone and checkout of the provided specifier.
10571070
func remove(specifier: RepositorySpecifier) throws {
1071+
let managedDependencies = try self.managedDependencies.dematerialize()
10581072
guard var dependency = managedDependencies[specifier] else {
10591073
fatalError("This should never happen, trying to remove \(specifier) which isn't in workspace")
10601074
}
@@ -1152,3 +1166,30 @@ extension Collection {
11521166
return (result, errors)
11531167
}
11541168
}
1169+
1170+
/// A result which can be reloaded.
1171+
///
1172+
/// It is useful for objects that holds a state on disk and needs to be
1173+
/// reloaded frequently.
1174+
final class ReloadableResult<Value, ErrorType: Swift.Error> {
1175+
1176+
/// The constructor closure for the value.
1177+
private let construct: () throws -> Value
1178+
1179+
/// Create a reloadable result.
1180+
init(_ construct: @escaping () throws -> Value) {
1181+
self.construct = construct
1182+
}
1183+
1184+
/// Load and return the result.
1185+
func result() -> Result<Value, ErrorType> {
1186+
return try! Result {
1187+
try self.construct()
1188+
}
1189+
}
1190+
1191+
/// Load and return the value.
1192+
func dematerialize() throws -> Value {
1193+
return try result().dematerialize()
1194+
}
1195+
}

Tests/WorkspaceTests/WorkspaceTests.swift

+20-20
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ extension Workspace {
8989
}
9090

9191
func getDependency(for url: AbsolutePath) -> ManagedDependency {
92-
return managedDependencies[url.asString]!
92+
return try! managedDependencies.dematerialize()[url.asString]!
9393
}
9494
}
9595

@@ -125,7 +125,7 @@ final class WorkspaceTests: XCTestCase {
125125
// Create the initial workspace.
126126
do {
127127
let workspace = try Workspace.createWith(rootPackage: path)
128-
XCTAssertEqual(workspace.managedDependencies.values.map{ $0.repository.url }, [])
128+
XCTAssertEqual(try workspace.managedDependencies.dematerialize().values.map{ $0.repository.url }, [])
129129

130130
// Do a low-level clone.
131131
let state = CheckoutState(revision: currentRevision)
@@ -136,8 +136,8 @@ final class WorkspaceTests: XCTestCase {
136136
// Re-open the workspace, and check we know the checkout version.
137137
do {
138138
let workspace = try Workspace.createWith(rootPackage: path)
139-
XCTAssertEqual(workspace.managedDependencies.values.map{ $0.repository }, [testRepoSpec])
140-
if let dependency = workspace.managedDependencies.values.first(where: { _ in true }) {
139+
XCTAssertEqual(try workspace.managedDependencies.dematerialize().values.map{ $0.repository }, [testRepoSpec])
140+
if let dependency = try workspace.managedDependencies.dematerialize().values.first(where: { _ in true }) {
141141
XCTAssertEqual(dependency.repository, testRepoSpec)
142142
XCTAssertEqual(dependency.checkoutState?.revision, currentRevision)
143143
}
@@ -152,9 +152,9 @@ final class WorkspaceTests: XCTestCase {
152152
let statePath: AbsolutePath
153153
do {
154154
let workspace = try Workspace.createWith(rootPackage: path)
155-
statePath = workspace.managedDependencies.statePath
156-
XCTAssertEqual(workspace.managedDependencies.values.map{ $0.repository }, [testRepoSpec])
157-
if let dependency = workspace.managedDependencies.values.first(where: { _ in true }) {
155+
statePath = try workspace.managedDependencies.dematerialize().statePath
156+
XCTAssertEqual(try workspace.managedDependencies.dematerialize().values.map{ $0.repository }, [testRepoSpec])
157+
if let dependency = try workspace.managedDependencies.dematerialize().values.first(where: { _ in true }) {
158158
XCTAssertEqual(dependency.repository, testRepoSpec)
159159
XCTAssertEqual(dependency.checkoutState?.revision, initialRevision)
160160
}
@@ -164,10 +164,10 @@ final class WorkspaceTests: XCTestCase {
164164
try removeFileTree(statePath)
165165
do {
166166
let workspace = try Workspace.createWith(rootPackage: path)
167-
XCTAssertEqual(workspace.managedDependencies.values.map{ $0.repository.url }, [])
167+
XCTAssertEqual(try workspace.managedDependencies.dematerialize().values.map{ $0.repository.url }, [])
168168
let state = CheckoutState(revision: currentRevision)
169169
_ = try workspace.clone(repository: testRepoSpec, at: state)
170-
XCTAssertEqual(workspace.managedDependencies.values.map{ $0.repository }, [testRepoSpec])
170+
XCTAssertEqual(try workspace.managedDependencies.dematerialize().values.map{ $0.repository }, [testRepoSpec])
171171
}
172172
}
173173
}
@@ -430,7 +430,7 @@ final class WorkspaceTests: XCTestCase {
430430
let workspace = try Workspace.createWith(rootPackage: path)
431431
let state = CheckoutState(revision: Revision(identifier: "initial"))
432432
let checkoutPath = try workspace.clone(repository: testRepoSpec, at: state)
433-
XCTAssertEqual(workspace.managedDependencies.values.map{ $0.repository }, [testRepoSpec])
433+
XCTAssertEqual(try workspace.managedDependencies.dematerialize().values.map{ $0.repository }, [testRepoSpec])
434434

435435
// Drop a build artifact in data directory.
436436
let buildArtifact = workspace.dataPath.appending(component: "test.o")
@@ -442,7 +442,7 @@ final class WorkspaceTests: XCTestCase {
442442

443443
try workspace.clean()
444444

445-
XCTAssertEqual(workspace.managedDependencies.values.map{ $0.repository }, [testRepoSpec])
445+
XCTAssertEqual(try workspace.managedDependencies.dematerialize().values.map{ $0.repository }, [testRepoSpec])
446446
XCTAssert(localFileSystem.exists(workspace.dataPath))
447447
// The checkout should be safe.
448448
XCTAssert(localFileSystem.exists(checkoutPath))
@@ -458,7 +458,7 @@ final class WorkspaceTests: XCTestCase {
458458
XCTAssertFalse(localFileSystem.exists(buildArtifact))
459459
XCTAssertFalse(localFileSystem.exists(checkoutPath))
460460
XCTAssertTrue(localFileSystem.exists(workspace.dataPath))
461-
XCTAssertTrue(workspace.managedDependencies.values.map{$0}.isEmpty)
461+
XCTAssertTrue(try workspace.managedDependencies.dematerialize().values.map{$0}.isEmpty)
462462
}
463463
}
464464

@@ -488,7 +488,7 @@ final class WorkspaceTests: XCTestCase {
488488
}
489489

490490
func getDependency(_ manifest: Manifest) -> ManagedDependency {
491-
return workspace.managedDependencies[manifest.url]!
491+
return try! workspace.managedDependencies.dematerialize()[manifest.url]!
492492
}
493493

494494
// Get the dependency for package A.
@@ -525,7 +525,7 @@ final class WorkspaceTests: XCTestCase {
525525
do {
526526
// Reopen workspace and check if we maintained the state.
527527
let workspace = try Workspace.createWith(rootPackage: path, manifestLoader: manifestGraph.manifestLoader, delegate: TestWorkspaceDelegate())
528-
let dependency = workspace.managedDependencies[RepositorySpecifier(url: aManifest.url)]!
528+
let dependency = try workspace.managedDependencies.dematerialize()[RepositorySpecifier(url: aManifest.url)]!
529529
XCTAssert(dependency.state == .edited)
530530
}
531531

@@ -558,7 +558,7 @@ final class WorkspaceTests: XCTestCase {
558558
return XCTFail("Expected manifest for package A not found")
559559
}
560560
func getDependency(_ manifest: Manifest) -> ManagedDependency {
561-
return workspace.managedDependencies[manifest.url]!
561+
return try! workspace.managedDependencies.dematerialize()[manifest.url]!
562562
}
563563
// Get the dependency for package A.
564564
let dependency = getDependency(aManifest)
@@ -614,7 +614,7 @@ final class WorkspaceTests: XCTestCase {
614614
return XCTFail("Expected manifest for package A not found")
615615
}
616616
func getDependency(_ manifest: Manifest) -> ManagedDependency {
617-
return workspace.managedDependencies[manifest.url]!
617+
return try! workspace.managedDependencies.dematerialize()[manifest.url]!
618618
}
619619
let dependency = getDependency(aManifest)
620620
// Put the dependency in edit mode.
@@ -1163,7 +1163,7 @@ final class WorkspaceTests: XCTestCase {
11631163
XCTAssertTrue(g.errors.isEmpty)
11641164
XCTAssert(g.lookup("A").version == "1.0.1")
11651165
// FIXME: We also cloned B because it has a pin.
1166-
XCTAssertNotNil(workspace.managedDependencies[manifestGraph.repo("B")])
1166+
XCTAssertNotNil(try workspace.managedDependencies.dematerialize()[manifestGraph.repo("B")])
11671167
}
11681168

11691169
do {
@@ -1175,7 +1175,7 @@ final class WorkspaceTests: XCTestCase {
11751175
XCTAssertTrue(g.errors.isEmpty)
11761176
XCTAssert(g.lookup("A").version == "1.0.1")
11771177
// This dependency should be removed on updating dependencies because it is not referenced anywhere.
1178-
XCTAssertNil(workspace.managedDependencies[manifestGraph.repo("B")])
1178+
XCTAssertNil(try workspace.managedDependencies.dematerialize()[manifestGraph.repo("B")])
11791179
}
11801180
}
11811181

@@ -1483,7 +1483,7 @@ final class WorkspaceTests: XCTestCase {
14831483

14841484
// Put A in edit mode.
14851485
let aManifest = try workspace.loadDependencyManifests().lookup(manifest: "A")!
1486-
let dependency = workspace.managedDependencies[RepositorySpecifier(url: aManifest.url)]!
1486+
let dependency = try workspace.managedDependencies.dematerialize()[RepositorySpecifier(url: aManifest.url)]!
14871487
try workspace.edit(dependency: dependency, packageName: aManifest.name, revision: dependency.checkoutState!.revision)
14881488

14891489
// We should retain the original pin for a package which is in edit mode.
@@ -1674,7 +1674,7 @@ final class WorkspaceTests: XCTestCase {
16741674
rootPackage: path, manifestLoader: manifestGraph.manifestLoader, delegate: TestWorkspaceDelegate())
16751675

16761676
func getDependency(_ manifest: Manifest) -> ManagedDependency {
1677-
return workspace.managedDependencies[manifest.url]!
1677+
return try! workspace.managedDependencies.dematerialize()[manifest.url]!
16781678
}
16791679

16801680
// Load the package graph.

0 commit comments

Comments
 (0)