这是indexloc提供的服务,不要输入任何密码
Skip to content

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 11 commits into from
Mar 8, 2022
4 changes: 3 additions & 1 deletion cli/internal/core/scheduler.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ package core

import (
"fmt"
"github.com/vercel/turborepo/cli/internal/util"
"strings"

"github.com/vercel/turborepo/cli/internal/util"

"github.com/pyr-sh/dag"
)

Expand Down Expand Up @@ -63,6 +64,7 @@ type SchedulerExecutionOptions struct {
func (p *scheduler) Prepare(options *SchedulerExecutionOptions) error {
pkgs := options.Packages
if len(pkgs) == 0 {
// TODO(gsoltis): Is this behavior only used in tests?
for _, v := range p.TopologicGraph.Vertices() {
pkgs = append(pkgs, dag.VertexName(v))
}
Expand Down
207 changes: 31 additions & 176 deletions cli/internal/run/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Copy link
Contributor

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?

Copy link
Contributor Author

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#L250
It does not pull in dependencies.

--scope-only was previously handled here: https://github.com/vercel/turborepo/blob/main/cli/internal/run/run.go#L253
It 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...

Copy link
Contributor

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).

Copy link
Contributor Author

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.

Copy link
Contributor Author

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.

// 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:]
Expand Down Expand Up @@ -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"):
Expand All @@ -919,6 +809,9 @@ func parseRunArgs(args []string, output cli.Ui) (*RunOptions, error) {
}
}
}
if len(runOptions.scope) != 0 && runOptions.since != "" && !includDepsSet {
Copy link
Contributor

@jaredpalmer jaredpalmer Mar 7, 2022

Choose a reason for hiding this comment

The 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 {
Expand All @@ -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)
Expand Down
75 changes: 16 additions & 59 deletions cli/internal/run/run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@ import (
"testing"

"github.com/mitchellh/cli"
"github.com/vercel/turborepo/cli/internal/context"
"github.com/vercel/turborepo/cli/internal/fs"
"github.com/vercel/turborepo/cli/internal/util"

"github.com/stretchr/testify/assert"
)
Expand Down Expand Up @@ -148,6 +146,22 @@ func TestParseConfig(t *testing.T) {
passThroughArgs: []string{},
},
},
{
"since and scope imply including dependencies for backwards compatibility",
[]string{"foo", "--scope=bar", "--since=some-ref"},
&RunOptions{
includeDependents: true,
stream: true,
bail: true,
concurrency: 10,
includeDependencies: true,
cache: true,
cwd: defaultCwd,
cacheFolder: defaultCacheFolder,
scope: []string{"bar"},
since: "some-ref",
},
},
}

ui := &cli.BasicUi{
Expand All @@ -168,63 +182,6 @@ func TestParseConfig(t *testing.T) {
}
}

func TestScopedPackages(t *testing.T) {
cases := []struct {
Name string
Ctx *context.Context
Pattern []string
Expected util.Set
}{
{
"starts with @",
&context.Context{
PackageNames: []string{"@sample/app", "sample-app", "jared"},
},
[]string{"@sample/*"},
util.Set{"@sample/app": "@sample/app"},
},
{
"return an array of matches",
&context.Context{
PackageNames: []string{"foo", "bar", "baz"},
},
[]string{"f*"},
util.Set{"foo": "foo"},
},
{
"return an array of matches",
&context.Context{
PackageNames: []string{"foo", "bar", "baz"},
},
[]string{"f*", "bar"},
util.Set{"bar": "bar", "foo": "foo"},
},
{
"return matches in the order the list were defined",
&context.Context{
PackageNames: []string{"foo", "bar", "baz"},
},
[]string{"*a*", "!f*"},
util.Set{"bar": "bar", "baz": "baz"},
},
}

for i, tc := range cases {
t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) {
actual, err := getScopedPackages(tc.Ctx, tc.Pattern)
if err != nil {
t.Fatalf("invalid scope parse: %#v", err)
}
assert.EqualValues(t, tc.Expected, actual)
})
}

t.Run(fmt.Sprintf("%d-%s", len(cases), "throws an error if no package matches the provided scope pattern"), func(t *testing.T) {
_, err := getScopedPackages(&context.Context{PackageNames: []string{"foo", "bar"}}, []string{"baz"})
assert.Error(t, err)
})
}

func TestGetTargetsFromArguments(t *testing.T) {
type args struct {
arguments []string
Expand Down
Loading