Skip to content
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

Synthesize a command for a Run #4499

Merged
merged 3 commits into from
Apr 10, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 5 additions & 1 deletion cli/internal/run/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,9 @@ func optsFromArgs(args *turbostate.ParsedArgsFromRust) (*Opts, error) {

opts := getDefaultOptions()
// aliases := make(map[string]string)
scope.OptsFromArgs(&opts.scopeOpts, args)
if err := scope.OptsFromArgs(&opts.scopeOpts, args); err != nil {
return nil, err
}

// Cache flags
opts.clientOpts.Timeout = args.RemoteCacheTimeout
Expand Down Expand Up @@ -358,6 +360,7 @@ func (r *run) run(ctx gocontext.Context, targets []string) error {
startAt,
r.base.UI,
r.base.RepoRoot,
rs.Opts.scopeOpts.PackageInferenceRoot,
r.base.TurboVersion,
r.base.APIClient,
rs.Opts.runOpts,
Expand All @@ -369,6 +372,7 @@ func (r *run) run(ctx gocontext.Context, targets []string) error {
globalHashable.globalCacheKey,
globalHashable.pipeline,
),
rs.Opts.SynthesizeCommand(rs.Targets),
)

// Dry Run
Expand Down
32 changes: 32 additions & 0 deletions cli/internal/run/run_spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
package run

import (
"strings"

"github.com/vercel/turbo/cli/internal/cache"
"github.com/vercel/turbo/cli/internal/client"
"github.com/vercel/turbo/cli/internal/runcache"
Expand Down Expand Up @@ -45,6 +47,36 @@ type Opts struct {
scopeOpts scope.Opts
}

// SynthesizeCommand produces a command that produces an equivalent set of packages, tasks,
// and task arguments to what the current set of opts selects.
func (o *Opts) SynthesizeCommand(tasks []string) string {
cmd := "turbo run"
cmd += " " + strings.Join(tasks, " ")
for _, filterPattern := range o.scopeOpts.FilterPatterns {
cmd += " --filter=" + filterPattern
}
for _, filterPattern := range o.scopeOpts.LegacyFilter.AsFilterPatterns() {
cmd += " --filter=" + filterPattern
}
if o.runOpts.Parallel {
cmd += " --parallel"
}
if o.runOpts.ContinueOnError {
cmd += " --continue"
}
if o.runOpts.DryRun {
if o.runOpts.DryRunJSON {
cmd += " --dry=json"
} else {
cmd += " --dry"
}
}
if len(o.runOpts.PassThroughArgs) > 0 {
cmd += " -- " + strings.Join(o.runOpts.PassThroughArgs, " ")
}
return cmd
}

// getDefaultOptions returns the default set of Opts for every run
func getDefaultOptions() *Opts {
return &Opts{
Expand Down
107 changes: 107 additions & 0 deletions cli/internal/run/run_spec_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package run

import (
"testing"

"github.com/vercel/turbo/cli/internal/scope"
"github.com/vercel/turbo/cli/internal/util"
)

func TestSynthesizeCommand(t *testing.T) {
testCases := []struct {
filterPatterns []string
legacyFilter scope.LegacyFilter
passThroughArgs []string
parallel bool
continueOnError bool
dryRun bool
dryRunJSON bool
tasks []string
expected string
}{
{
filterPatterns: []string{"my-app"},
tasks: []string{"build"},
expected: "turbo run build --filter=my-app",
},
{
filterPatterns: []string{"my-app"},
tasks: []string{"build"},
passThroughArgs: []string{"-v", "--foo=bar"},
expected: "turbo run build --filter=my-app -- -v --foo=bar",
},
{
legacyFilter: scope.LegacyFilter{
Entrypoints: []string{"my-app"},
SkipDependents: true,
},
tasks: []string{"build"},
passThroughArgs: []string{"-v", "--foo=bar"},
expected: "turbo run build --filter=my-app -- -v --foo=bar",
},
{
legacyFilter: scope.LegacyFilter{
Entrypoints: []string{"my-app"},
SkipDependents: true,
},
filterPatterns: []string{"other-app"},
tasks: []string{"build"},
passThroughArgs: []string{"-v", "--foo=bar"},
expected: "turbo run build --filter=other-app --filter=my-app -- -v --foo=bar",
},
{
legacyFilter: scope.LegacyFilter{
Entrypoints: []string{"my-app"},
IncludeDependencies: true,
Since: "some-ref",
},
filterPatterns: []string{"other-app"},
tasks: []string{"build"},
expected: "turbo run build --filter=other-app --filter=...my-app...[some-ref]...",
},
{
filterPatterns: []string{"my-app"},
tasks: []string{"build"},
parallel: true,
continueOnError: true,
expected: "turbo run build --filter=my-app --parallel --continue",
},
{
filterPatterns: []string{"my-app"},
tasks: []string{"build"},
dryRun: true,
expected: "turbo run build --filter=my-app --dry",
},
{
filterPatterns: []string{"my-app"},
tasks: []string{"build"},
dryRun: true,
dryRunJSON: true,
expected: "turbo run build --filter=my-app --dry=json",
},
}

for _, testCase := range testCases {
testCase := testCase
t.Run(testCase.expected, func(t *testing.T) {
o := Opts{
scopeOpts: scope.Opts{
FilterPatterns: testCase.filterPatterns,
LegacyFilter: testCase.legacyFilter,
},
runOpts: util.RunOpts{
PassThroughArgs: testCase.passThroughArgs,
Parallel: testCase.parallel,
ContinueOnError: testCase.continueOnError,
DryRun: testCase.dryRun,
DryRunJSON: testCase.dryRunJSON,
},
}
cmd := o.SynthesizeCommand(testCase.tasks)
if cmd != testCase.expected {
t.Errorf("SynthesizeCommand() got %v, want %v", cmd, testCase.expected)
}
})
}

}
48 changes: 21 additions & 27 deletions cli/internal/runsummary/run_summary.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
"encoding/json"
"fmt"
"path/filepath"
"strings"
"sync"
"time"

Expand Down Expand Up @@ -41,14 +40,16 @@
// Meta is a wrapper around the serializable RunSummary, with some extra information
// about the Run and references to other things that we need.
type Meta struct {
RunSummary *RunSummary
ui cli.Ui
repoRoot turbopath.AbsoluteSystemPath // used to write run summary
singlePackage bool
shouldSave bool
apiClient *client.APIClient
spaceID string
runType runType
RunSummary *RunSummary
ui cli.Ui
repoRoot turbopath.AbsoluteSystemPath // used to write run summary
repoPath turbopath.RelativeSystemPath
singlePackage bool
shouldSave bool
apiClient *client.APIClient
spaceID string
runType runType
synthesizedCommand string
}

// RunSummary contains a summary of what happens in the `turbo run` command and why.
Expand All @@ -67,11 +68,13 @@
startAt time.Time,
ui cli.Ui,
repoRoot turbopath.AbsoluteSystemPath,
repoPath turbopath.RelativeSystemPath,

Check warning on line 71 in cli/internal/runsummary/run_summary.go

View workflow job for this annotation

GitHub Actions / Go linting

unused-parameter: parameter 'repoPath' seems to be unused, consider removing or renaming it as _ (revive)
turboVersion string,
apiClient *client.APIClient,
runOpts util.RunOpts,
packages []string,
globalHashSummary *GlobalHashSummary,
synthesizedCommand string,
) Meta {
singlePackage := runOpts.SinglePackage
profile := runOpts.Profile
Expand All @@ -98,13 +101,14 @@
Tasks: []*TaskSummary{},
GlobalHashSummary: globalHashSummary,
},
ui: ui,
runType: runType,
repoRoot: repoRoot,
singlePackage: singlePackage,
shouldSave: shouldSave,
apiClient: apiClient,
spaceID: spaceID,
ui: ui,
runType: runType,
repoRoot: repoRoot,
singlePackage: singlePackage,
shouldSave: shouldSave,
apiClient: apiClient,
spaceID: spaceID,
synthesizedCommand: synthesizedCommand,
}
}

Expand Down Expand Up @@ -181,16 +185,6 @@
return summary.ExecutionSummary.run(taskID)
}

// command returns a best guess command for the entire Run.
// TODO: we should thread this through from the entry point rather than make it up
func (summary *RunSummary) command() string {
taskNames := make(util.Set, len(summary.Tasks))
for _, task := range summary.Tasks {
taskNames.Add(task.Task)
}
return fmt.Sprintf("turbo run %s", strings.Join(taskNames.UnsafeListOfStrings(), " "))
}

// Save saves the run summary to a file
func (rsm *Meta) save() error {
json, err := rsm.FormatJSON()
Expand Down Expand Up @@ -219,7 +213,7 @@
// can happen when the Run actually starts, so we can send updates to Vercel as the tasks progress.
runsURL := fmt.Sprintf(runsEndpoint, rsm.spaceID)
var runID string
payload := newVercelRunCreatePayload(rsm.RunSummary)
payload := rsm.newVercelRunCreatePayload()
if startPayload, err := json.Marshal(payload); err == nil {
if resp, err := rsm.apiClient.JSONPost(runsURL, startPayload); err != nil {
errs = append(errs, err)
Expand Down
35 changes: 20 additions & 15 deletions cli/internal/runsummary/vercel.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ type vercelRunPayload struct {
ID string `json:"vercelId,omitempty"`

// StartTime is when this run was started
StartTime int `json:"startTime,omitempty"`
StartTime int64 `json:"startTime,omitempty"`

// EndTime is when this run ended. We will never be submitting start and endtime at the same time.
EndTime int `json:"endTime,omitempty"`
EndTime int64 `json:"endTime,omitempty"`

// Status is
Status string `json:"status,omitempty"`
Expand All @@ -30,6 +30,10 @@ type vercelRunPayload struct {
// The command that kicked off the turbo run
Command string `json:"command,omitempty"`

// RepositoryPath is the relative directory from the turborepo root to where
// the command was invoked.
RepositoryPath string `json:"repositoryPath,omitempty"`

// Context is the host on which this Run was executed (e.g. Vercel)
Context string `json:"context,omitempty"`

Expand All @@ -49,31 +53,32 @@ type vercelTask struct {
Name string `json:"name,omitempty"`
Workspace string `json:"workspace,omitempty"`
Hash string `json:"hash,omitempty"`
StartTime int `json:"startTime,omitempty"`
EndTime int `json:"endTime,omitempty"`
StartTime int64 `json:"startTime,omitempty"`
EndTime int64 `json:"endTime,omitempty"`
Cache vercelCacheStatus `json:"cache,omitempty"`
ExitCode int `json:"exitCode,omitempty"`
Dependencies []string `json:"dependencies,omitempty"`
Dependents []string `json:"dependents,omitempty"`
}

func newVercelRunCreatePayload(runsummary *RunSummary) *vercelRunPayload {
startTime := int(runsummary.ExecutionSummary.startedAt.UnixMilli())
var context = "LOCAL"
func (rsm *Meta) newVercelRunCreatePayload() *vercelRunPayload {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it's going into the spaces payload, it should be part of the Run Summary. It seems like good information regardless. If you're good with that, we can merge this in and I'll update in my PR

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added to execution summary

startTime := rsm.RunSummary.ExecutionSummary.startedAt.UnixMilli()
context := "LOCAL"
if name := ci.Constant(); name != "" {
context = name
}
return &vercelRunPayload{
StartTime: startTime,
Status: "running",
Command: runsummary.command(),
Type: "TURBO",
Context: context,
StartTime: startTime,
Status: "running",
Command: rsm.synthesizedCommand,
RepositoryPath: rsm.repoPath.ToString(),
Type: "TURBO",
Context: context,
}
}

func newVercelDonePayload(runsummary *RunSummary) *vercelRunPayload {
endTime := int(runsummary.ExecutionSummary.endedAt.UnixMilli())
endTime := runsummary.ExecutionSummary.endedAt.UnixMilli()
return &vercelRunPayload{
Status: "completed",
EndTime: endTime,
Expand All @@ -90,8 +95,8 @@ func newVercelTaskPayload(taskSummary *TaskSummary) *vercelTask {
status = "HIT"
}

startTime := int(taskSummary.Execution.startAt.UnixMilli())
endTime := int(taskSummary.Execution.endTime().UnixMilli())
startTime := taskSummary.Execution.startAt.UnixMilli()
endTime := taskSummary.Execution.endTime().UnixMilli()

return &vercelTask{
Key: taskSummary.TaskID,
Expand Down