-
Notifications
You must be signed in to change notification settings - Fork 2k
Extract ResolvePackages #830
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
Merged
Merged
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
afd3e1d
scope packages doesn't need the whole context
a4596ec
Needs cleanup, but have extracted the filtered packages logic
5aaf196
Move to scope opts
06fb2e8
Add ResolveScope test. Does not pass yet
f9a1abb
More tests for scope, as well as some tweaks to run options for backw…
5e9a26d
Fix scm root path
4324154
Merge branch 'main' into gsoltis/extract_resolve_packages
kodiakhq[bot] e8449dc
Merge branch 'main' into gsoltis/extract_resolve_packages
0421391
Merge branch 'main' into gsoltis/extract_resolve_packages
6368351
Merge branch 'main' into gsoltis/extract_resolve_packages
98972b3
Merge branch 'main' into gsoltis/extract_resolve_packages
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -27,10 +27,10 @@ import ( | |
"github.com/vercel/turborepo/cli/internal/logstreamer" | ||
"github.com/vercel/turborepo/cli/internal/process" | ||
"github.com/vercel/turborepo/cli/internal/scm" | ||
"github.com/vercel/turborepo/cli/internal/scope" | ||
"github.com/vercel/turborepo/cli/internal/ui" | ||
"github.com/vercel/turborepo/cli/internal/util" | ||
"github.com/vercel/turborepo/cli/internal/util/browser" | ||
"github.com/vercel/turborepo/cli/internal/util/filter" | ||
|
||
"github.com/pyr-sh/dag" | ||
|
||
|
@@ -154,147 +154,18 @@ func (c *RunCommand) Run(args []string) int { | |
return 1 | ||
} | ||
|
||
gitRepoRoot, err := fs.FindupFrom(".git", runOptions.cwd) | ||
scmInstance, err := scm.FromInRepo(runOptions.cwd) | ||
if err != nil { | ||
c.logWarning(c.Config.Logger, "Cannot find a .git folder in current working directory or in any parent directories. Falling back to manual file hashing (which may be slower). If you are running this build in a pruned directory, you can ignore this message. Otherwise, please initialize a git repository in the root of your monorepo.", err) | ||
} | ||
git, err := scm.NewFallback(filepath.Dir(gitRepoRoot)) | ||
if err != nil { | ||
c.logWarning(c.Config.Logger, "", err) | ||
} | ||
|
||
ignoreGlob, err := filter.Compile(runOptions.ignore) | ||
if err != nil { | ||
c.logError(c.Config.Logger, "", fmt.Errorf("invalid ignore globs: %w", err)) | ||
return 1 | ||
} | ||
globalDepsGlob, err := filter.Compile(runOptions.globalDeps) | ||
if err != nil { | ||
c.logError(c.Config.Logger, "", fmt.Errorf("invalid global deps glob: %w", err)) | ||
return 1 | ||
} | ||
hasRepoGlobalFileChanged := false | ||
var changedFiles []string | ||
if runOptions.since != "" { | ||
changedFiles = git.ChangedFiles(runOptions.since, true, runOptions.cwd) | ||
} | ||
|
||
ignoreSet := make(util.Set) | ||
if globalDepsGlob != nil { | ||
for _, f := range changedFiles { | ||
if globalDepsGlob.Match(f) { | ||
hasRepoGlobalFileChanged = true | ||
break | ||
} | ||
} | ||
} | ||
|
||
if ignoreGlob != nil { | ||
for _, f := range changedFiles { | ||
if ignoreGlob.Match(f) { | ||
ignoreSet.Add(f) | ||
} | ||
} | ||
} | ||
|
||
filteredChangedFiles := make(util.Set) | ||
// Ignore any changed files in the ignore set | ||
for _, c := range changedFiles { | ||
if !ignoreSet.Includes(c) { | ||
filteredChangedFiles.Add(c) | ||
} | ||
} | ||
|
||
changedPackages := make(util.Set) | ||
// Be specific with the changed packages only if no repo-wide changes occurred | ||
if !hasRepoGlobalFileChanged { | ||
for k, pkgInfo := range ctx.PackageInfos { | ||
partialPath := pkgInfo.Dir | ||
if filteredChangedFiles.Some(func(v interface{}) bool { | ||
return strings.HasPrefix(fmt.Sprintf("%v", v), partialPath) // true | ||
}) { | ||
changedPackages.Add(k) | ||
} | ||
if errors.Is(err, scm.ErrFallback) { | ||
c.logWarning(c.Config.Logger, "", err) | ||
} else { | ||
c.logError(c.Config.Logger, "", fmt.Errorf("failed to create SCM: %w", err)) | ||
return 1 | ||
} | ||
} | ||
|
||
// Scoped packages | ||
// Unwind scope globs | ||
scopePkgs, err := getScopedPackages(ctx, runOptions.scope) | ||
filteredPkgs, err := scope.ResolvePackages(runOptions.ScopeOpts(), scmInstance, ctx, c.Ui, c.Config.Logger) | ||
if err != nil { | ||
c.logError(c.Config.Logger, "", fmt.Errorf("invalid scope: %w", err)) | ||
return 1 | ||
} | ||
|
||
// Filter Packages | ||
filteredPkgs := make(util.Set) | ||
|
||
// If both scoped and since are specified, we have to merge two lists: | ||
// 1. changed packages that ARE themselves the scoped packages | ||
// 2. changed package consumers (package dependents) that are within the scoped subgraph | ||
if scopePkgs.Len() > 0 && changedPackages.Len() > 0 { | ||
filteredPkgs = scopePkgs.Intersection(changedPackages) | ||
for _, changed := range changedPackages { | ||
descenders, err := ctx.TopologicalGraph.Descendents(changed) | ||
if err != nil { | ||
c.logError(c.Config.Logger, "", fmt.Errorf("could not determine dependency: %w", err)) | ||
return 1 | ||
} | ||
|
||
filteredPkgs.Add(changed) | ||
for _, d := range descenders { | ||
filteredPkgs.Add(d) | ||
} | ||
} | ||
c.Ui.Output(fmt.Sprintf(ui.Dim("• Packages changed since %s in scope: %s"), runOptions.since, strings.Join(filteredPkgs.UnsafeListOfStrings(), ", "))) | ||
} else if changedPackages.Len() > 0 { | ||
filteredPkgs = changedPackages | ||
c.Ui.Output(fmt.Sprintf(ui.Dim("• Packages changed since %s: %s"), runOptions.since, strings.Join(filteredPkgs.UnsafeListOfStrings(), ", "))) | ||
} else if scopePkgs.Len() > 0 { | ||
filteredPkgs = scopePkgs | ||
} else if runOptions.since == "" { | ||
for _, f := range ctx.PackageNames { | ||
filteredPkgs.Add(f) | ||
} | ||
} | ||
|
||
if runOptions.includeDependents { | ||
// perf??? this is duplicative from the step above | ||
for _, pkg := range filteredPkgs { | ||
descenders, err := ctx.TopologicalGraph.Descendents(pkg) | ||
if err != nil { | ||
c.logError(c.Config.Logger, "", fmt.Errorf("error calculating affected packages: %w", err)) | ||
return 1 | ||
} | ||
c.Config.Logger.Debug("dependents", "pkg", pkg, "value", descenders.List()) | ||
for _, d := range descenders { | ||
// we need to exclude the fake root node | ||
// since it is not a real package | ||
if d != ctx.RootNode { | ||
filteredPkgs.Add(d) | ||
} | ||
} | ||
} | ||
c.Config.Logger.Debug("running with dependents") | ||
} | ||
|
||
if runOptions.includeDependencies { | ||
for _, pkg := range filteredPkgs { | ||
ancestors, err := ctx.TopologicalGraph.Ancestors(pkg) | ||
if err != nil { | ||
c.logError(c.Config.Logger, "", fmt.Errorf("error getting dependency %v", err)) | ||
return 1 | ||
} | ||
c.Config.Logger.Debug("dependencies", "pkg", pkg, "value", ancestors.List()) | ||
for _, d := range ancestors { | ||
// we need to exclude the fake root node | ||
// since it is not a real package | ||
if d != ctx.RootNode { | ||
filteredPkgs.Add(d) | ||
} | ||
} | ||
} | ||
c.Config.Logger.Debug(ui.Dim("running with dependencies")) | ||
c.logError(c.Config.Logger, "", fmt.Errorf("failed resolve packages to run %v", err)) | ||
} | ||
c.Config.Logger.Debug("global hash", "value", ctx.GlobalHash) | ||
packagesInScope := filteredPkgs.UnsafeListOfStrings() | ||
|
@@ -798,6 +669,18 @@ type RunOptions struct { | |
only bool | ||
} | ||
|
||
func (ro *RunOptions) ScopeOpts() *scope.Opts { | ||
return &scope.Opts{ | ||
IncludeDependencies: ro.includeDependencies, | ||
IncludeDependents: ro.includeDependents, | ||
Patterns: ro.scope, | ||
Since: ro.since, | ||
Cwd: ro.cwd, | ||
IgnorePatterns: ro.ignore, | ||
GlobalDepPatterns: ro.globalDeps, | ||
} | ||
} | ||
|
||
func getDefaultRunOptions() *RunOptions { | ||
return &RunOptions{ | ||
bail: true, | ||
|
@@ -829,6 +712,11 @@ func parseRunArgs(args []string, output cli.Ui) (*RunOptions, error) { | |
|
||
unresolvedCacheFolder := filepath.FromSlash("./node_modules/.cache/turbo") | ||
|
||
// --scope and --since implies --include-dependencies for backwards compatibility | ||
// When we switch to cobra we will need to track if it's been set manually. Currently | ||
// it's only possible to set to true, but in the future a user could theoretically set | ||
// it to false and override the default behavior. | ||
includDepsSet := false | ||
for argIndex, arg := range args { | ||
if arg == "--" { | ||
runOptions.passThroughArgs = args[argIndex+1:] | ||
|
@@ -902,8 +790,10 @@ func parseRunArgs(args []string, output cli.Ui) (*RunOptions, error) { | |
case strings.HasPrefix(arg, "--includeDependencies"): | ||
output.Warn("[WARNING] The --includeDependencies flag has renamed to --include-dependencies for consistency. Please use `--include-dependencies` instead") | ||
runOptions.includeDependencies = true | ||
includDepsSet = true | ||
case strings.HasPrefix(arg, "--include-dependencies"): | ||
runOptions.includeDependencies = true | ||
includDepsSet = true | ||
case strings.HasPrefix(arg, "--only"): | ||
runOptions.only = true | ||
case strings.HasPrefix(arg, "--team"): | ||
|
@@ -919,6 +809,9 @@ func parseRunArgs(args []string, output cli.Ui) (*RunOptions, error) { | |
} | ||
} | ||
} | ||
if len(runOptions.scope) != 0 && runOptions.since != "" && !includDepsSet { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why not just --scope or --since? |
||
runOptions.includeDependencies = true | ||
} | ||
|
||
// Force streaming output in CI/CD non-interactive mode | ||
if !ui.IsTTY || ui.IsCI { | ||
|
@@ -931,44 +824,6 @@ func parseRunArgs(args []string, output cli.Ui) (*RunOptions, error) { | |
return runOptions, nil | ||
} | ||
|
||
// getScopedPackages returns a set of package names in scope for a given list of glob patterns | ||
func getScopedPackages(ctx *context.Context, scopePatterns []string) (scopePkgs util.Set, err error) { | ||
if err != nil { | ||
return nil, fmt.Errorf("invalid glob pattern %w", err) | ||
} | ||
var scopedPkgs = make(util.Set) | ||
if len(scopePatterns) == 0 { | ||
return scopePkgs, nil | ||
} | ||
|
||
include := make([]string, 0, len(scopePatterns)) | ||
exclude := make([]string, 0, len(scopePatterns)) | ||
|
||
for _, pattern := range scopePatterns { | ||
if strings.HasPrefix(pattern, "!") { | ||
exclude = append(exclude, pattern[1:]) | ||
} else { | ||
include = append(include, pattern) | ||
} | ||
} | ||
|
||
glob, err := filter.NewIncludeExcludeFilter(include, exclude) | ||
if err != nil { | ||
return nil, err | ||
} | ||
for _, f := range ctx.PackageNames { | ||
if glob.Match(f) { | ||
scopedPkgs.Add(f) | ||
} | ||
} | ||
|
||
if len(include) > 0 && scopedPkgs.Len() == 0 { | ||
return nil, errors.Errorf("No packages found matching the provided scope pattern.") | ||
} | ||
|
||
return scopedPkgs, nil | ||
} | ||
|
||
// logError logs an error and outputs it to the UI. | ||
func (c *RunCommand) logError(log hclog.Logger, prefix string, err error) { | ||
log.Error(prefix, "error", err) | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
should it be --scope or --since?
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.
--since
-only was previously handled here: https://github.com/vercel/turborepo/blob/main/cli/internal/run/run.go#L250It does not pull in dependencies.
--scope
-only was previously handled here: https://github.com/vercel/turborepo/blob/main/cli/internal/run/run.go#L253It also does not pull in dependencies.
It was only when they were both specified, and there were actual changed packages, that dependencies were included: https://github.com/vercel/turborepo/blob/main/cli/internal/run/run.go#L238
This amounts to building all changed packages and their dependencies, regardless of specified scope. I don't think this behavior is correct, but I'm not sure if we want to stop building dependencies in that case since users might be relying on it? Although they are somewhat arbitrary...
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.
Right, what I'm saying is that I think in addition to these changes, passing either
--scope
or--since
should automatically--include-dependencies
and--no-deps
flags (breaking behavior).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.
Oh, gotcha. Yeah, we can do that.
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.
As mentioned here, we're going to keep the behavior in this PR, without expanding it to "
--scope
or--since
triggers--include-dependencies
. This closely mirrors existing behavior. We can then look at alternate ways of specifying packages.