-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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
The head ref may contain hidden characters: "commands\u2010pr"
Fix Command Resolution #4038
Conversation
There was a problem hiding this 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)) |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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)) |
There was a problem hiding this comment.
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).
Sources/PackageModel/Manifest.swift
Outdated
case .specific(let productFilter, let includeCommands): | ||
let products = self.products.filter { product in | ||
return productFilter.contains(product.name) | ||
|| (includeCommands && productIsCommandPlugin(product)) |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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.
Sources/PackageModel/Product.swift
Outdated
case .specific(let set): | ||
return "[\(set.sorted().joined(separator: ", "))]" | ||
case .specific(let set, let includeCommands): | ||
return "[\(set.sorted().joined(separator: ", "))\(includeCommands ? " (including commands)" : "")]" |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@ -174,7 +174,7 @@ extension ProductFilter: JSONSerializable, JSONMappable { | |||
switch self { | |||
case .everything: | |||
return "all".toJSON() | |||
case .specific(let products): | |||
case .specific(let products, _): |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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] = [ |
There was a problem hiding this comment.
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"]) |
There was a problem hiding this comment.
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.
@@ -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): |
There was a problem hiding this comment.
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.
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. |
There was a problem hiding this 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. |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@ -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_") } |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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_
”.
There was a problem hiding this comment.
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...
@swift-ci please smoke test |
thanks @SDGGiesbrecht, need to review this in more details given the added complexity - which may be necessary, but still somewhat unfortunate |
Something has changed in 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 |
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. |
The above bug simply vanished. Some recent change to |
@swift-ci please smoke test |
I was just pinged on the forums for an update on this. Are you able to clarify when “the next merge window” is? |
thank you for your patience with this @SDGGiesbrecht, our hope to take the changes marked as "next" in the few weeks |
@tomerd. A friendly ping one month later. |
Pinging because another few weeks have passed. @tomerd, @abertelrud, @neonichu |
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). |
Pinging again. A total of six months have now elapsed. @tomerd, @abertelrud, @neonichu |
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 |
Work on SwiftPM seems to be picking up again after the 5.7 release. What came of your discussions? @tomerd |
Is there any schedule for this MR? Thanks |
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. |
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.