-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Assure trait-guarded dependencies are not included in resolution; precompute enabled traits before resolution #8852
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
Conversation
@swift-ci please test |
- Create a 'precomputeTraits' method that cycles through the graph to unify all the traits - Modify the traits error message to include both the package identity and display name for better readability TODO - change instances of optional enabledTraits to non-optional, defaulting instead to ["default"] - assure that loaded manifests arent fetching guarded dependencies - this is contingent on how we are calculating the unified traits afterwards
Since there is a bit of redundancy when checking the conditions of an optional set of enabled traits, it makes more sense to simply have the set of enabled traits be non-optional, and to derive the checks for defaults by asserting whether the set contains the "default" keyword.
To help with the computation of traits, a wrapper struct for a dictionary of traits has been created wherein the default value when a key isn't found is no longer nil but instead ["default"], since if we haven't defined a set of explicitly enabled traits for a given package, then it should default to the defaults.
* Whenever this method is called, it will store a previously- created Workspace object in the SwiftCommandState. Some calls to this method were missing the traitConfiguration, thus instantiating the Workspace without the necessary trait config.
@swift-ci please test |
- Previous calls to getActiveWorkspace could have defaulted to using the default trait configuration (re: PluginCommand -> PluginDelegate.createSymbolGraphForPlugin case), therefore we should update the existing workspace's trait configuration if a new one is passed.
Since the trait configuration is used for a variety of swift package subcommands, it makes it a lot easier to add the trait options as a part of the GlobalOptions which is consumed by SwiftCommandState and persisted across all SwiftCommands. This also allows us to omit passing a trait configuration to the build system factory methods, and instead replacing it with a flag that can override the trait configuration to enable all traits for edge-case scenarios (e.g. fetching symbol graphs)
@swift-ci please test |
@swift-ci please test |
@swift-ci please test |
@swift-ci please 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.
Amazing! So great to see unused dependencies getting pruned
//===----------------------------------------------------------------------===// | ||
|
||
/// A wrapper for a dictionary that stores the transitively enabled traits for each package. | ||
public struct EnabledTraitsMap: ExpressibleByDictionaryLiteral { |
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.
Does this need to be public?
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'll double check, it may not need to be
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.
Ah yeah this does need to be public since it's used in public initializers in other packages.
Since we'll have others consuming the ModulesGraph load method, create a version of the method without the enabledTraitsMap parameter.
@swift-ci please test |
@swift-ci please test windows |
@@ -53,7 +53,7 @@ struct DumpSymbolGraph: AsyncSwiftCommand { | |||
// We turn build manifest caching off because we need the build plan. | |||
let buildSystem = try await swiftCommandState.createBuildSystem( | |||
// We are enabling all traits for dumping the symbol graph. | |||
traitConfiguration: .init(enableAllTraits: true), | |||
enableAllTraits: true, |
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.
question: what benefit does passing this argument have on the creation of a build system? Should the enableAllTraits
option be provided when building instead?
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 will affect how the SwiftCommandState
fetches and stores the currently active workspace, which is determined by its method SwiftCommandState.getActiveWorkspace
. When creating a build system, there are many calls to various SwiftCommandState
methods that call getActiveWorkspace
, and the workspace that is created in this method is stored in its _workspace
property.
The workspace is only ever created once in this method, and every subsequent call to this method checks whether the _workspace
property has a value. If so, it will return the workspace stored there.
The reason why we have enableAllTraits
propagated to this call is because we have special edge case scenarios where we would initially create a SwiftCommandState
with some trait configuration, but if we then make a call that wishes to dump the symbol graph (see PluginDelegate.createSymbolGraphForPlugin
), it will return a workspace that has whichever trait configuration we've initialized with the SwiftCommandState
, which isn't always what we'd want when requesting a dump of the symbol graph.
@@ -292,6 +292,8 @@ public final class SwiftCommandState { | |||
|
|||
package var preferredBuildConfiguration = BuildConfiguration.debug | |||
|
|||
public var traitConfiguration: TraitConfiguration |
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.
question: can this be a let
instead of var
?
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.
Indeed it can!
@@ -56,7 +56,7 @@ extension Package { | |||
public let kind: Kind | |||
|
|||
/// The dependencies traits configuration. | |||
@available(_PackageDescription, introduced: 999.0) | |||
@available(_PackageDescription, introduced: 6.1) |
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.
praise: nice!
], | ||
buildSystem: buildSystem, | ||
) | ||
// We expect no warnings to be produced. Specifically no unused dependency warnings. | ||
let unusedDependencyRegex = try Regex("warning: '.*': dependency '.*' is not used by any target") | ||
#expect(!stderr.contains(unusedDependencyRegex)) | ||
if buildSystem == .swiftbuild { | ||
print(stderr) |
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.
question: out of curiosity, why are we printing stderr
here? if it's meant to troubleshoot a failed expectation, we can add a comment to the #expect(...)
or #require(...)
See the expect(::sourcelocation:) API docs for details.
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.
Ah, this must've slipped by me. I was printing out stderr for debugging purposes. Thanks for catching this!
@swift-ci please test |
@swift-ci please test windows |
…compute enabled traits before resolution (swiftlang#8852) Trait-guarded dependencies were still being considered during dependency resolution, when they should be excluded if they aren't being used in any other scenario. Additionally, enabled traits should be pre-computed before entering the resolution stage in order to avoid possible race conditions when navigating through the dependency graph nodes. Since we have the `--experimental-prune-unused-dependencies` feature behind an experimental flag, we'll now consider an alternate path that will prune trait-guarded package dependencies from the dependency graph **_if and only if_** said dependency is not used in any other unguarded context. A dictionary wrapper titled `EnabledTraitsMap` is used to store the enabled traits per package for the package graph and is stored as a property within the `Workspace`, with some additional behaviour to return a `["default"]` set of traits if the package has not yet been stored in the dictionary, rather than returning `nil`. Following this behaviour, when passing a set of traits to methods that require them (e.g. for dependency computation, enabled trait computation, etc.) we now require that it is not Optional, since the checks done on a `nil` set of traits were redundant. SwiftCommandState now also stores a `TraitConfiguration`, since we'll want access to this across multiple `SwiftCommand`s, and it is essentially a part of the state anyhow. `TraitOptions` is now included in the `GlobalOptions` for `SwiftCommand`s to supplement this, so that when a `SwiftCommandState` is created we will have access to the user-passed enabled traits. These options, as entitled, are available globally across all the swift package commands, so previous properties that declared `TraitOptions` in these commands has been removed in favour of using it straight from the `GlobalOptions`. Trait-guarded dependencies are excluded from dependency resolution, and since traits are pre-computed there should no longer be an issue with race conditions for traits in resolution as well.
Trait-guarded dependencies were still being considered during dependency resolution, when they should be excluded if they aren't being used in any other scenario. Additionally, enabled traits should be pre-computed before entering the resolution stage in order to avoid possible race conditions when navigating through the dependency graph nodes.
Modifications:
Since we have the
--experimental-prune-unused-dependencies
feature behind an experimental flag, we'll now consider an alternate path that will prune trait-guarded package dependencies from the dependency graph if and only if said dependency is not used in any other unguarded context.A dictionary wrapper titled
EnabledTraitsMap
is used to store the enabled traits per package for the package graph and is stored as a property within theWorkspace
, with some additional behaviour to return a["default"]
set of traits if the package has not yet been stored in the dictionary, rather than returningnil
.Following this behaviour, when passing a set of traits to methods that require them (e.g. for dependency computation, enabled trait computation, etc.) we now require that it is not Optional, since the checks done on a
nil
set of traits were redundant.SwiftCommandState now also stores a
TraitConfiguration
, since we'll want access to this across multipleSwiftCommand
s, and it is essentially a part of the state anyhow.TraitOptions
is now included in theGlobalOptions
forSwiftCommand
s to supplement this, so that when aSwiftCommandState
is created we will have access to the user-passed enabled traits. These options, as entitled, are available globally across all the swift package commands, so previous properties that declaredTraitOptions
in these commands has been removed in favour of using it straight from theGlobalOptions
.Result:
Trait-guarded dependencies are excluded from dependency resolution, and since traits are pre-computed there should no longer be an issue with race conditions for traits in resolution as well.