Skip to content

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

Open
wants to merge 20 commits into
base: main
Choose a base branch
from

Conversation

bripeticca
Copy link
Contributor

@bripeticca bripeticca commented Jun 19, 2025

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 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 SwiftCommands, and it is essentially a part of the state anyhow. TraitOptions is now included in the GlobalOptions for SwiftCommands 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.

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.

@bripeticca
Copy link
Contributor Author

@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.
@bripeticca
Copy link
Contributor Author

@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)
@bripeticca
Copy link
Contributor Author

@swift-ci please test

@bripeticca
Copy link
Contributor Author

@swift-ci please test

@bripeticca
Copy link
Contributor Author

@swift-ci please test

@bripeticca bripeticca marked this pull request as ready for review July 11, 2025 17:41
@bripeticca bripeticca requested a review from FranzBusch July 11, 2025 17:41
@bripeticca bripeticca changed the title [WIP] Fix for trait-guarded deps being included in resolution Fix for trait-guarded deps being included in resolution Jul 11, 2025
@bripeticca bripeticca changed the title Fix for trait-guarded deps being included in resolution Assure trait-guarded dependencies are not included in resolution; precompute enabled traits before resolution Jul 11, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant