11
11
import Basic
12
12
import PackageLoading
13
13
import PackageModel
14
+ import PackageGraph
14
15
import SourceControl
15
16
import Utility
16
17
@@ -20,14 +21,43 @@ public enum WorkspaceOperationError: Swift.Error {
20
21
case unavailableRepository
21
22
}
22
23
24
+ /// Convenience initializer for Dictionary.
25
+ //
26
+ // FIXME: Lift to Basic?
27
+ extension Dictionary {
28
+ init < S: Sequence > ( items: S ) where S. Iterator. Element == ( Key , Value ) {
29
+ var result = Dictionary . init ( )
30
+ for (key, value) in items {
31
+ result [ key] = value
32
+ }
33
+ self = result
34
+ }
35
+ }
36
+
37
+ /// The delegate interface used by the workspace to report status information.
38
+ public protocol WorkspaceDelegate : class {
39
+ /// The workspace is fetching additional repositories in support of
40
+ /// loading a complete package.
41
+ func fetchingMissingRepositories( _ urls: Set < String > )
42
+ }
43
+
23
44
/// A workspace represents the state of a working project directory.
24
45
///
25
- /// This class is responsible for managing the persistent working state of a
46
+ /// The workspace is responsible for managing the persistent working state of a
26
47
/// project directory (e.g., the active set of checked out repositories) and for
27
48
/// coordinating the changes to that state.
28
49
///
50
+ /// This class glues together the basic facilities provided by the dependency
51
+ /// resolution, source control, and package graph loading subsystems into a
52
+ /// cohesive interface for exposing the high-level operations for the package
53
+ /// manager to maintain working package directories.
54
+ ///
29
55
/// This class does *not* support concurrent operations.
30
56
public class Workspace {
57
+ /// An individual managed dependency.
58
+ ///
59
+ /// Each dependency will have a checkout containing the sources at a
60
+ /// particular revision, and may have an associated version.
31
61
public struct ManagedDependency {
32
62
/// The specifier for the dependency.
33
63
public let repository : RepositorySpecifier
@@ -98,7 +128,10 @@ public class Workspace {
98
128
] )
99
129
}
100
130
}
101
-
131
+
132
+ /// The delegate interface.
133
+ public let delegate : WorkspaceDelegate
134
+
102
135
/// The path of the root package.
103
136
public let rootPackagePath : AbsolutePath
104
137
@@ -136,8 +169,10 @@ public class Workspace {
136
169
public init (
137
170
rootPackage path: AbsolutePath ,
138
171
dataPath: AbsolutePath ? = nil ,
139
- manifestLoader: ManifestLoaderProtocol
172
+ manifestLoader: ManifestLoaderProtocol ,
173
+ delegate: WorkspaceDelegate
140
174
) throws {
175
+ self . delegate = delegate
141
176
self . rootPackagePath = path
142
177
self . dataPath = dataPath ?? path. appending ( component: " .build " )
143
178
self . manifestLoader = manifestLoader
@@ -268,6 +303,57 @@ public class Workspace {
268
303
return ( root: rootManifest, dependencies: dependencies. map { $0. item } )
269
304
}
270
305
306
+ /// Fetch and load the complete package at the given path.
307
+ ///
308
+ /// This will implicitly cause any dependencies not yet present in the
309
+ /// working checkouts to be resolved, cloned, and checked out.
310
+ ///
311
+ /// When fetching additional dependencies, the existing checkout versions
312
+ /// will never be re-bound (or even re-fetched) as a result of this
313
+ /// operation. This implies that the resulting local state may not match
314
+ /// what would be computed from a fresh clone, but this makes for a more
315
+ /// consistent command line development experience.
316
+ ///
317
+ /// - Returns: The loaded package graph.
318
+ /// - Throws: Rethrows errors from dependency resolution (if required) and package graph loading.
319
+ public func loadPackageGraph( ) throws -> PackageGraph {
320
+ // First, load the active manifest sets.
321
+ let ( rootManifest, currentExternalManifests) = try loadDependencyManifests ( )
322
+
323
+ // Check for missing checkouts.
324
+ let manifestsMap = Dictionary < String , Manifest > (
325
+ items: [ ( rootManifest. url, rootManifest) ] + currentExternalManifests. map { ( $0. url, $0) } )
326
+ let availableURLs = Set < String > ( manifestsMap. keys)
327
+ var requiredURLs = transitiveClosure ( [ rootManifest. url] ) { url in
328
+ guard let manifest = manifestsMap [ url] else { return [ ] }
329
+ return manifest. package . dependencies. map { $0. url }
330
+ }
331
+ requiredURLs. insert ( rootManifest. url)
332
+
333
+ // We should never have loaded a manifest we don't need.
334
+ assert ( availableURLs. isSubset ( of: requiredURLs) )
335
+
336
+ // If there are have missing URLs, we need to fetch them now.
337
+ let missingURLs = requiredURLs. subtracting ( availableURLs)
338
+ let externalManifests = currentExternalManifests
339
+ if !missingURLs. isEmpty {
340
+ // Inform the delegate.
341
+ delegate. fetchingMissingRepositories ( missingURLs)
342
+
343
+ // Perform dependency resolution using the constraint set induced by the active checkouts.
344
+ //
345
+ // FIXME: We are going to need to a way to tell the resolution
346
+ // algorithm that certain repositories are pinned to the current
347
+ // checkout. We might be able to do that simply by overriding the
348
+ // view presented by the repository container provider.
349
+
350
+ fatalError ( " FIXME: Unimplemented. " )
351
+ }
352
+
353
+ // We've loaded the complete set of manifests, load the graph.
354
+ return try PackageGraphLoader ( ) . load ( rootManifest: rootManifest, externalManifests: externalManifests)
355
+ }
356
+
271
357
// MARK: Persistence
272
358
273
359
// FIXME: A lot of the persistence mechanism here is copied from
0 commit comments