Skip to content

Fix Command Resolution #4038

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from

Conversation

SDGGiesbrecht
Copy link
Contributor

See #4028 for the impetus, particularly starting from this comment.

This implements the option labeled “3” in that thread, attempting to exactly match the API and usage described in SE‐0332. It does so by implicitly inserting every command plugin found in a first‐level dependency into the dependency graph.

Copy link
Contributor Author

@SDGGiesbrecht SDGGiesbrecht left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left some inline comments.

}
if includeCommands {
nodes.append(.implicitCommands(package: self.package))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is where the new resolution node is first inserted.

/// This type of node is equivalent to a product node in all respects,
/// except that the number and names of the products are unkown,
/// because the manifest contains no explicit reference to them.
case implicitCommands(package: PackageReference)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is too bad we need a whole new kind of node, but since we cannot know the product names before the dependency is fetched, we have no other choice.

case .specific(let set, let includeCommands):
filteredProducts = self.manifest.products.filter { product in
return set.contains(product.name)
|| (includeCommands && self.manifest.productIsCommandPlugin(product))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is where the products are loaded. We now load all command plugins no matter what, bypassing the filter that culls unreferenced products (when at the relevant level in the graph).

case .specific(let productFilter, let includeCommands):
let products = self.products.filter { product in
return productFilter.contains(product.name)
|| (includeCommands && productIsCommandPlugin(product))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we bypass the filter again, keeping around any dependencies of an implicit command plugin.

let dependencies = self.dependenciesRequired(for: targets, keepUnused: productFilter == .everything)
let dependencies = self.dependenciesRequired(
for: targets,
keepImplicitCommandPlugins: productFilter == .everything,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The manifest is a root if productFilter == .everything. Therefore its direct dependencies are the ones that can have implicit commands. If the product filter is anything else, then the manifest is already partway into the graph, and its dependencies are transitive.

case .specific(let set):
return "[\(set.sorted().joined(separator: ", "))]"
case .specific(let set, let includeCommands):
return "[\(set.sorted().joined(separator: ", "))\(includeCommands ? " (including commands)" : "")]"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This made test failures easier to read while debugging. It did not clash with any fixtures, so I left it.

@@ -241,26 +246,3 @@ extension ProductType: Codable {
}
}
}

extension ProductFilter: Codable {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The old Codable implementation here could not be added to in a backwards compatible way. I just removed it to let compiler synthesize it.

I do not know where or if this is actually used. We stopped recording filters in the pins file a while ago. Changing it did not flag on any tests. Do you think there is anywhere we need to bump a format version?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is stored anyway, though @neonichu or @tomerd would know better.

@@ -174,7 +174,7 @@ extension ProductFilter: JSONSerializable, JSONMappable {
switch self {
case .everything:
return "all".toJSON()
case .specific(let products):
case .specific(let products, _):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops. I forgot to come back to this. I will look at the performance tests tomorrow.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These performance tests are inoperable on main anyway, because the JSON files are out of date.

@@ -510,6 +510,18 @@ class SourceControlPackageContainerTests: XCTestCase {
)
}

let v5_2ProductMappingWithoutCommands: [String: ProductFilter] = [
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The differing context means the includeCommands need to be opposite what they were above, so they can no longer share the same variable.

// Ensure that the order of the manifests is stable.
// Ensure that the order of the manifests is stable from one run to the next (changes accross versions do not matter).
#if ENABLE_TARGET_BASED_DEPENDENCY_RESOLUTION
XCTAssertEqual(manifests.allDependencyManifests().map { $0.value.manifest.displayName }, ["Bam", "Baz", "Bar", "Foo"])
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new nodes altered the traversal order.

@SDGGiesbrecht SDGGiesbrecht mentioned this pull request Jan 21, 2022
11 tasks
@@ -174,14 +174,17 @@ extension ProductFilter: JSONSerializable, JSONMappable {
switch self {
case .everything:
return "all".toJSON()
case .specific(let products):
case .specific(var products, let includeCommands):
Copy link
Contributor Author

@SDGGiesbrecht SDGGiesbrecht Jan 21, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I patched this up to do something sane. It does not match the actual files any more than main does, but will at least be operable if someone makes a new file.

@abertelrud
Copy link
Contributor

Thank you very much @SDGGiesbrecht! Including for the detailed comments. I will start looking through it tonight, but might not wrap up until tomorrow. I really appreciate the fixes here.

Copy link
Contributor

@abertelrud abertelrud left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me (at least to the degree I'm familiar with the package resolution). Thanks for taking on these fixes — hopefully when target-based dependencies are enabled by default again, the unit tests will prevent any further regressions.

@@ -58,23 +58,38 @@ public enum DependencyResolutionNode {
/// to the information needed in order to provide actionable suggestions to help the user stitch up the dependency declarations properly.
case root(package: PackageReference)

// FIXME: If command plugins become explicit in the manifest, or are extricated from the main graph, `implicitCommands` should be removed.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@@ -131,23 +137,22 @@ public enum ProductFilter: Equatable, Hashable {
self = self.union(other)
}

public func contains(_ product: String) -> Bool {
public func contains(_ product: String, isCommandPlugin: (String) -> Bool) -> Bool {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense. Because SwiftPM doesn't yet have a versioned API I suspect that any clients could adapt, but passing in the closure makes sense too.

@@ -241,26 +246,3 @@ extension ProductType: Codable {
}
}
}

extension ProductFilter: Codable {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is stored anyway, though @neonichu or @tomerd would know better.

@@ -174,14 +174,17 @@ extension ProductFilter: JSONSerializable, JSONMappable {
switch self {
case .everything:
return "all".toJSON()
case .specific(let products):
case .specific(var products, let includeCommands):
if includeCommands { products.insert("_commands_") }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know it's a nitpick, but perhaps call this "command plugins" since command is a somewhat overloaded term?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which are you referring to? The label of the enumeration’s associated value, or the JSON placeholder string?

Copy link
Contributor

@abertelrud abertelrud Jan 22, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both actually, but the name of the placeholder is one that might show up in debug output (I assume?), so that's the more important one, I'd think.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@swift-ci please smoke test

Copy link
Contributor Author

@SDGGiesbrecht SDGGiesbrecht Jan 22, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did find‐and‐replace from “includeCommands” to “includeCommandPlugins” and from “_commands_” to “_command_plugins_”.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! Might save someone some confusion one day...

@abertelrud
Copy link
Contributor

@swift-ci please smoke test

@tomerd tomerd self-assigned this Jan 25, 2022
@tomerd
Copy link
Contributor

tomerd commented Jan 25, 2022

thanks @SDGGiesbrecht, need to review this in more details given the added complexity - which may be necessary, but still somewhat unfortunate

@SDGGiesbrecht
Copy link
Contributor Author

SDGGiesbrecht commented Feb 9, 2022

Something has changed in main since I submitted this, such that testTransitiveDependencySwitchWithSameIdentity() now crashes.

The following is able to “fix” it, but seems to indicate an underlying problem somewhere else:

// Tests/WorkspaceTests/WorkspaceTests.swift
     func testTransitiveDependencySwitchWithSameIdentity() throws {
     // ...
+        immortal = workspace._workspace?.repositoryManager.lookupSemaphore
     }
+    var immortal: Any?
// Sources/SPMTestSupport/MockWorkspace.swift
-    private var _workspace: Workspace?
+    public var _workspace: Workspace?
// Sources/Workspace/Workspace.swift
-    fileprivate var repositoryManager: RepositoryManager
+    public var repositoryManager: RepositoryManager
// Sources/SourceControl/RepositoryManager.swift
-    private let lookupSemaphore: DispatchSemaphore
+    public let lookupSemaphore: DispatchSemaphore

@SDGGiesbrecht
Copy link
Contributor Author

I moved the workaround from my last comment into the umbrella PR (#3838) in order to unblock work on its branch. The issue does not seem to be directly related to anything here.

This pull request does not require that other fix. Under legacy resolution this PR makes no difference and all remains green, and under target‐based resolution, It moves the current state from several tests broken to one test broken, which is a net improvement.

@SDGGiesbrecht
Copy link
Contributor Author

The above bug simply vanished. Some recent change to main must have fixed it.

@abertelrud
Copy link
Contributor

@swift-ci please smoke test

@SDGGiesbrecht
Copy link
Contributor Author

I was just pinged on the forums for an update on this. Are you able to clarify when “the next merge window” is?

@tomerd
Copy link
Contributor

tomerd commented May 20, 2022

thank you for your patience with this @SDGGiesbrecht, our hope to take the changes marked as "next" in the few weeks

@SDGGiesbrecht
Copy link
Contributor Author

@tomerd. A friendly ping one month later.

@SDGGiesbrecht
Copy link
Contributor Author

Pinging because another few weeks have passed. @tomerd, @abertelrud, @neonichu

@samdeane
Copy link

samdeane commented Jul 22, 2022

Is there a reason why this is stalled? For what it's worth, the status of this change came up in this discussion on the forums (particular from here onwards).

@SDGGiesbrecht
Copy link
Contributor Author

Pinging again. A total of six months have now elapsed. @tomerd, @abertelrud, @neonichu

@tomerd
Copy link
Contributor

tomerd commented Aug 4, 2022

thanks for your patience @SDGGiesbrecht, aware this is taking very long. we are discussing scope for the next release and this is something we are looking into

@SDGGiesbrecht
Copy link
Contributor Author

Work on SwiftPM seems to be picking up again after the 5.7 release. What came of your discussions? @tomerd

@malcommac
Copy link

Is there any schedule for this MR? Thanks

@SDGGiesbrecht
Copy link
Contributor Author

Closing. This and related PRs are stale and I do not have time to maintain them anymore. I will leave the related branches around for now in case anyone else wants to pick them up.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
next waiting for next merge window
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants