Skip to content

Commit 9c661fa

Browse files
authored
Merge pull request #1163 from aciidb0mb3r/auto-resolve
[Workspace] Move pin subcommand to resolve
2 parents 0fd405c + 3cd4a45 commit 9c661fa

File tree

5 files changed

+114
-138
lines changed

5 files changed

+114
-138
lines changed

Sources/Commands/SwiftPackageTool.swift

Lines changed: 37 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -82,11 +82,27 @@ public class SwiftPackageTool: SwiftTool<PackageToolOptions> {
8282

8383
case .fetch:
8484
diagnostics.emit(data: FetchDeprecatedDiagnostic())
85-
fallthrough
85+
let workspace = try getActiveWorkspace()
86+
workspace.resolve(root: try getWorkspaceRoot(), diagnostics: diagnostics)
8687

8788
case .resolve:
89+
let resolveOptions = options.resolveOptions
8890
let workspace = try getActiveWorkspace()
89-
workspace.resolve(root: try getWorkspaceRoot(), diagnostics: diagnostics)
91+
let root = try getWorkspaceRoot()
92+
93+
// If a package is provided, use that to resolve the dependencies.
94+
if let packageName = resolveOptions.packageName {
95+
return try workspace.resolve(
96+
packageName: packageName,
97+
root: getWorkspaceRoot(),
98+
version: resolveOptions.version.flatMap(Version.init(string:)),
99+
branch: resolveOptions.branch,
100+
revision: resolveOptions.revision,
101+
diagnostics: diagnostics)
102+
}
103+
104+
// Otherwise, run a normal resolve.
105+
workspace.resolve(root: root, diagnostics: diagnostics)
90106

91107
case .edit:
92108
let packageName = options.editOptions.packageName!
@@ -180,38 +196,9 @@ public class SwiftPackageTool: SwiftTool<PackageToolOptions> {
180196

181197
case .help:
182198
parser.printUsage(on: stdoutStream)
183-
184-
case .pin:
185-
let pinOptions = options.pinOptions
186-
// Ensure we have the package name.
187-
guard let packageName = pinOptions.packageName else {
188-
throw PackageToolOperationError.insufficientOptions(usage: pinUsage)
189-
}
190-
191-
// Load the package graph.
192-
try loadPackageGraph()
193-
194-
let workspace = try getActiveWorkspace()
195-
196-
// Pin the dependency.
197-
try workspace.pin(
198-
packageName: packageName,
199-
root: getWorkspaceRoot(),
200-
version: pinOptions.version.flatMap(Version.init(string:)),
201-
branch: pinOptions.branch,
202-
revision: pinOptions.revision,
203-
diagnostics: diagnostics)
204199
}
205200
}
206201

207-
var pinUsage: String {
208-
let stream = BufferedOutputByteStream()
209-
stream <<< "Expected package pin format:\n"
210-
stream <<< "swift package pin (--all | <packageName> [--version <version>])\n"
211-
stream <<< "Note: Either provide a package to pin or provide pin all option to pin all dependencies."
212-
return stream.bytes.asString!
213-
}
214-
215202
override class func defineArguments(parser: ArgumentParser, binder: ArgumentBinder<PackageToolOptions>) {
216203
binder.bind(
217204
option: parser.add(option: "--version", kind: Bool.self),
@@ -251,12 +238,11 @@ public class SwiftPackageTool: SwiftTool<PackageToolOptions> {
251238

252239
parser.add(subparser: PackageMode.clean.rawValue, overview: "Delete build artifacts")
253240
parser.add(subparser: PackageMode.fetch.rawValue, overview: "")
254-
parser.add(subparser: PackageMode.resolve.rawValue, overview: "Resolve package dependencies")
255241
parser.add(subparser: PackageMode.reset.rawValue, overview: "Reset the complete cache/build directory")
256242

257-
let resolveParser = parser.add(subparser: PackageMode.resolveTool.rawValue, overview: "")
243+
let resolveToolParser = parser.add(subparser: PackageMode.resolveTool.rawValue, overview: "")
258244
binder.bind(
259-
option: resolveParser.add(
245+
option: resolveToolParser.add(
260246
option: "--type", kind: PackageToolOptions.ResolveToolMode.self,
261247
usage: "text|json"),
262248
to: { $0.resolveToolMode = $1 })
@@ -332,29 +318,29 @@ public class SwiftPackageTool: SwiftTool<PackageToolOptions> {
332318
$0.outputPath = $3?.path
333319
})
334320

335-
let pinParser = parser.add(
336-
subparser: PackageMode.pin.rawValue,
337-
overview: "Perform pinning operations on a package.")
321+
let resolveParser = parser.add(
322+
subparser: PackageMode.resolve.rawValue,
323+
overview: "Resolve package dependencies")
338324
binder.bind(
339-
positional: pinParser.add(
325+
positional: resolveParser.add(
340326
positional: "name", kind: String.self, optional: true,
341-
usage: "The name of the package to pin"),
342-
to: { $0.pinOptions.packageName = $1 })
327+
usage: "The name of the package to resolve"),
328+
to: { $0.resolveOptions.packageName = $1 })
343329

344330
binder.bind(
345-
pinParser.add(
331+
resolveParser.add(
346332
option: "--version", kind: String.self,
347-
usage: "The version to pin at"),
348-
pinParser.add(
333+
usage: "The version to resolve at"),
334+
resolveParser.add(
349335
option: "--branch", kind: String.self,
350-
usage: "The branch to pin at"),
351-
pinParser.add(
336+
usage: "The branch to resolve at"),
337+
resolveParser.add(
352338
option: "--revision", kind: String.self,
353-
usage: "The revision to pin at"),
339+
usage: "The revision to resolve at"),
354340
to: {
355-
$0.pinOptions.version = $1
356-
$0.pinOptions.branch = $2
357-
$0.pinOptions.revision = $3 })
341+
$0.resolveOptions.version = $1
342+
$0.resolveOptions.branch = $2
343+
$0.resolveOptions.revision = $3 })
358344

359345
binder.bind(
360346
parser: parser,
@@ -384,13 +370,13 @@ public class PackageToolOptions: ToolOptions {
384370
var outputPath: AbsolutePath?
385371
var xcodeprojOptions = XcodeprojOptions()
386372

387-
struct PinOptions {
373+
struct ResolveOptions {
388374
var packageName: String?
389375
var version: String?
390376
var revision: String?
391377
var branch: String?
392378
}
393-
var pinOptions = PinOptions()
379+
var resolveOptions = ResolveOptions()
394380

395381
enum ResolveToolMode: String {
396382
case text
@@ -414,7 +400,6 @@ public enum PackageMode: String, StringEnumArgument {
414400
case fetch
415401
case generateXcodeproj = "generate-xcodeproj"
416402
case initPackage = "init"
417-
case pin
418403
case reset
419404
case resolve
420405
case resolveTool = "resolve-tool"

Sources/Workspace/PinsStore.swift

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -99,10 +99,17 @@ public final class PinsStore {
9999
)
100100
}
101101

102-
/// Pin a managed dependency at its checkout state.
103-
///
104-
/// This method does nothing if the dependency is in edited state.
105-
func pin(_ dependency: ManagedDependency) {
102+
/// Add a pin.
103+
///
104+
/// This will replace any previous pin with same package name.
105+
public func add(_ pin: Pin) {
106+
pinsMap[pin.package] = pin
107+
}
108+
109+
/// Pin a managed dependency at its checkout state.
110+
///
111+
/// This method does nothing if the dependency is in edited state.
112+
func pin(_ dependency: ManagedDependency) {
106113

107114
// Get the checkout state.
108115
let checkoutState: CheckoutState
@@ -117,7 +124,7 @@ public final class PinsStore {
117124
package: dependency.name,
118125
repository: dependency.repository,
119126
state: checkoutState)
120-
}
127+
}
121128

122129
/// Unpin all of the currently pinnned dependencies.
123130
///

Sources/Workspace/Workspace.swift

Lines changed: 50 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -339,22 +339,21 @@ extension Workspace {
339339
try unedit(dependency: dependency, forceRemove: forceRemove)
340340
}
341341

342-
/// Pins a package at a given state.
342+
/// Resolve a package at the given state.
343343
///
344344
/// Only one of version, branch and revision will be used and in the same
345345
/// order. If none of these is provided, the dependency will be pinned at
346346
/// the current checkout state.
347347
///
348348
/// - Parameters:
349-
/// - dependency: The dependency to pin.
350-
/// - packageName: The name of the package which is being pinned.
349+
/// - packageName: The name of the package which is being resolved.
351350
/// - root: The workspace's root input.
352351
/// - version: The version to pin at.
353352
/// - branch: The branch to pin at.
354353
/// - revision: The revision to pin at.
355354
/// - diagnostics: The diagnostics engine that reports errors, warnings
356355
/// and notes.
357-
public func pin(
356+
public func resolve(
358357
packageName: String,
359358
root: WorkspaceRoot,
360359
version: Version? = nil,
@@ -371,72 +370,27 @@ extension Workspace {
371370
return diagnostics.emit(error)
372371
}
373372

374-
// Compute the requirement.
375-
let requirement: RepositoryPackageConstraint.Requirement
373+
// Compute the checkout state.
374+
//
375+
// We use a dummy revision in case of version and branch because we
376+
// might not know the needed revision at this point.
377+
let checkoutState: CheckoutState
376378
if let version = version {
377-
requirement = .versionSet(.exact(version))
379+
checkoutState = CheckoutState(revision: Revision(identifier: ""), version: version)
378380
} else if let branch = branch {
379-
requirement = .revision(branch)
381+
checkoutState = CheckoutState(revision: Revision(identifier: ""), branch: branch)
380382
} else if let revision = revision {
381-
requirement = .revision(revision)
383+
checkoutState = CheckoutState(revision: Revision(identifier: revision))
382384
} else {
383-
requirement = currentState.requirement()
384-
}
385-
386-
// Load the root manifests and currently checked out manifests.
387-
let rootManifests = loadRootManifests(packages: root.packages, diagnostics: diagnostics)
388-
389-
// Load the current manifests.
390-
let graphRoot = PackageGraphRoot(manifests: rootManifests, dependencies: root.dependencies)
391-
let currentManifests = loadDependencyManifests(root: graphRoot, diagnostics: diagnostics)
392-
393-
// Abort if we're unable to load the pinsStore or have any diagnostics.
394-
guard let pinsStore = diagnostics.wrap({ try self.pinsStore.load() }) else {
395-
return
385+
checkoutState = currentState
396386
}
397387

398-
// Ensure we don't have any error at this point.
399-
guard !diagnostics.hasErrors else { return }
388+
// Create a pin with above checkout state.
389+
let pin = PinsStore.Pin(
390+
package: dependency.name, repository: dependency.repository, state: checkoutState)
400391

401-
// Compute constraints with the new pin and try to resolve
402-
// dependencies. We only commit the pin if the dependencies can be
403-
// resolved with new constraints.
404-
//
405-
// The constraints consist of three things:
406-
// * Unversioned constraints for edited packages.
407-
// * Root manifest contraints.
408-
// * Exisiting pins except the dependency we're currently pinning.
409-
// * The constraint for the new pin we're trying to add.
410-
var constraints = currentManifests.editedPackagesConstraints()
411-
constraints += graphRoot.constraints
412-
413-
var pins = pinsStore.createConstraints().filter({ $0.identifier != dependency.repository })
414-
pins.append(
415-
RepositoryPackageConstraint(
416-
container: dependency.repository, requirement: requirement))
417-
418-
// Resolve the dependencies.
419-
let results = resolveDependencies(dependencies: constraints, pins: pins, diagnostics: diagnostics)
420-
guard !diagnostics.hasErrors else { return }
421-
422-
// Update the checkouts based on new dependency resolution.
423-
updateCheckouts(with: results, diagnostics: diagnostics)
424-
guard !diagnostics.hasErrors else { return }
425-
426-
// Get the updated dependency.
427-
let newDependency = managedDependencies[dependency.repository]!
428-
429-
// Assert that the dependency is at the pinned checkout state now.
430-
if case .checkout(let checkoutState) = newDependency.state {
431-
assert(checkoutState.requirement() == requirement)
432-
} else {
433-
assertionFailure()
434-
}
435-
436-
// Update the pins store.
437-
self.pinAll(
438-
pinsStore: pinsStore,
439-
diagnostics: diagnostics)
392+
// Run the resolution.
393+
_resolve(root: root, extraPins: [pin], diagnostics: diagnostics)
440394
}
441395

442396
/// Cleans the build artefacts from workspace data.
@@ -574,7 +528,7 @@ extension Workspace {
574528
root: WorkspaceRoot,
575529
diagnostics: DiagnosticsEngine
576530
) {
577-
_ = _resolve(root: root, diagnostics: diagnostics)
531+
_resolve(root: root, diagnostics: diagnostics)
578532
}
579533

580534
/// Load the package graph data.
@@ -910,10 +864,18 @@ extension Workspace {
910864
extension Workspace {
911865

912866
/// Implementation of resolve(root:diagnostics:).
867+
///
868+
/// The extra pins will override the pin of the same package in the
869+
/// pinsStore. It is useful in situations where a requirement is being
870+
/// imposed outside of manifest and pins file. E.g., when using a command
871+
/// like `$ swift package resolve foo --version 1.0.0`.
872+
@discardableResult
913873
fileprivate func _resolve(
914874
root: WorkspaceRoot,
875+
extraPins: [PinsStore.Pin] = [],
915876
diagnostics: DiagnosticsEngine
916877
) -> DependencyManifests {
878+
917879
// Ensure the cache path exists and validate that edited dependencies.
918880
createCacheDirectories(with: diagnostics)
919881

@@ -937,11 +899,16 @@ extension Workspace {
937899

938900
// Compute if we need to run the resolver.
939901
if missingURLs.isEmpty {
902+
// Use root constraints, dependency manifest constraints and extra
903+
// pins to compute if a new resolution is required.
940904
let dependencies = graphRoot.constraints + currentManifests.dependencyConstraints()
905+
extraPins.forEach(pinsStore.add)
906+
941907
let result = isResolutionRequired(dependencies: dependencies, pinsStore: pinsStore)
942908

943-
// We're done if we don't need resolution.
944-
guard result.resolve else {
909+
// If we don't need resolution, just validate pinsStore and return.
910+
if !result.resolve {
911+
validatePinsStore(with: diagnostics)
945912
return currentManifests
946913
}
947914

@@ -1045,6 +1012,23 @@ extension Workspace {
10451012
return (false, [])
10461013
}
10471014

1015+
/// Validates that each checked out managed dependency has an entry in pinsStore.
1016+
private func validatePinsStore(with diagnostics: DiagnosticsEngine) {
1017+
guard let pinsStore = diagnostics.wrap({ try pinsStore.load() }) else {
1018+
return
1019+
}
1020+
1021+
for dependency in managedDependencies.values {
1022+
switch dependency.state {
1023+
case .checkout: break
1024+
case .edited: continue
1025+
}
1026+
// If we find any checkout that is not in pins store, invoke pin all and return.
1027+
if pinsStore.pinsMap[dependency.name] == nil {
1028+
return self.pinAll(pinsStore: pinsStore, diagnostics: diagnostics)
1029+
}
1030+
}
1031+
}
10481032

10491033
/// This enum represents state of an external package.
10501034
fileprivate enum PackageStateChange {

0 commit comments

Comments
 (0)