From c299490dd5da9c5625b52ab1044ffd4f2a2e6ce2 Mon Sep 17 00:00:00 2001 From: Jared Palmer Date: Tue, 2 Nov 2021 09:31:32 -0400 Subject: [PATCH 1/4] Convert PNPM's ParseTargetSelector to Go --- internal/run/parse_target_selector.go | 116 +++++++++ internal/run/parse_target_selector_test.go | 270 +++++++++++++++++++++ 2 files changed, 386 insertions(+) create mode 100644 internal/run/parse_target_selector.go create mode 100644 internal/run/parse_target_selector_test.go diff --git a/internal/run/parse_target_selector.go b/internal/run/parse_target_selector.go new file mode 100644 index 0000000000000..2e5fe26d407cf --- /dev/null +++ b/internal/run/parse_target_selector.go @@ -0,0 +1,116 @@ +package run + +import ( + "path/filepath" + "regexp" + "strings" +) + +type TargetSelector struct { + includeDependencies bool + includeDependents bool + exclude bool + excludeSelf bool + followProdDepsOnly bool + parentDir string + namePattern string + diff string +} + +// ParseTargetSelectors is a function that returns PNPM compatible --filter command line flags +func ParseTargetSelector(rawSelector string, prefix string) (TargetSelector, error) { + exclude := false + firstChar := rawSelector[0] + selector := rawSelector + if firstChar == '!' { + selector = selector[1:] + exclude = true + } + excludeSelf := false + includeDependencies := strings.HasSuffix(selector, "...") + if includeDependencies { + selector = selector[:len(selector)-3] + if strings.HasSuffix(selector, "^") { + excludeSelf = true + selector = selector[:len(selector)-1] + } + } + includeDependents := strings.HasPrefix(selector, "...") + if includeDependents { + selector = selector[3:] + if strings.HasPrefix(selector, "^") { + excludeSelf = true + selector = selector[1:] + } + } + regex := regexp.MustCompile(`^([^.][^{}[\]]*)?(\{[^}]+\})?(\[[^\]]+\])?$`) + matches := regex.FindAllStringSubmatch(selector, -1) + + diff := "" + parentDir := "" + namePattern := "" + + if len(matches) == 0 { + if isSelectorByLocation(selector) { + return TargetSelector{ + diff: diff, + exclude: exclude, + excludeSelf: false, + includeDependencies: includeDependencies, + includeDependents: includeDependents, + namePattern: namePattern, + parentDir: filepath.Join(prefix, selector), + }, nil + } + return TargetSelector{ + diff: diff, + exclude: exclude, + excludeSelf: excludeSelf, + includeDependencies: includeDependencies, + includeDependents: includeDependents, + namePattern: selector, + parentDir: parentDir, + }, nil + } + + if len(matches) > 0 && len(matches[0]) > 0 { + if len(matches[0][1]) > 0 { + namePattern = matches[0][1] + } + if len(matches[0][2]) > 0 { + parentDir = filepath.Join(prefix, matches[0][2][1:len(matches[0][2])-1]) + } + if len(matches[0][3]) > 0 { + diff = matches[0][3][1 : len(matches[0][3])-1] + } + } + + return TargetSelector{ + diff: diff, + exclude: exclude, + excludeSelf: excludeSelf, + includeDependencies: includeDependencies, + includeDependents: includeDependents, + namePattern: namePattern, + parentDir: parentDir, + }, nil +} + +// isSelectorByLocation returns true if the selector is by filesystem location +func isSelectorByLocation(rawSelector string) bool { + if rawSelector[0:1] != "." { + return false + } + + // . or ./ or .\ + if len(rawSelector) == 1 || rawSelector[1:2] == "/" || rawSelector[1:2] == "\\" { + return true + } + + if rawSelector[1:2] != "." { + return false + } + + // .. or ../ or ..\ + return len(rawSelector) == 2 || rawSelector[2:3] == "/" || rawSelector[2:3] == "\\" +} diff --git a/internal/run/parse_target_selector_test.go b/internal/run/parse_target_selector_test.go new file mode 100644 index 0000000000000..3c06e2aaf8df9 --- /dev/null +++ b/internal/run/parse_target_selector_test.go @@ -0,0 +1,270 @@ +package run + +import ( + "reflect" + "testing" +) + +func TestParseTargetSelector(t *testing.T) { + type args struct { + rawSelector string + prefix string + } + tests := []struct { + name string + args args + want TargetSelector + wantErr bool + }{ + { + "foo", + args{"foo", "."}, + TargetSelector{ + diff: "", + exclude: false, + excludeSelf: false, + includeDependencies: false, + includeDependents: false, + namePattern: "foo", + parentDir: "", + }, + false, + }, + { + "foo...", + args{"foo...", "."}, + TargetSelector{ + diff: "", + exclude: false, + excludeSelf: false, + includeDependencies: true, + includeDependents: false, + namePattern: "foo", + parentDir: "", + }, + false, + }, + { + "...foo", + args{"...foo", "."}, + TargetSelector{ + diff: "", + exclude: false, + excludeSelf: false, + includeDependencies: false, + includeDependents: true, + namePattern: "foo", + parentDir: "", + }, + false, + }, + { + "...foo...", + args{"...foo...", "."}, + TargetSelector{ + diff: "", + exclude: false, + excludeSelf: false, + includeDependencies: true, + includeDependents: true, + namePattern: "foo", + parentDir: "", + }, + false, + }, + { + "foo^...", + args{"foo^...", "."}, + TargetSelector{ + diff: "", + exclude: false, + excludeSelf: true, + includeDependencies: true, + includeDependents: false, + namePattern: "foo", + parentDir: "", + }, + false, + }, + { + "...^foo", + args{"...^foo", "."}, + TargetSelector{ + diff: "", + exclude: false, + excludeSelf: true, + includeDependencies: false, + includeDependents: true, + namePattern: "foo", + parentDir: "", + }, + false, + }, + { + "./foo", + args{"./foo", "./"}, + TargetSelector{ + diff: "", + exclude: false, + excludeSelf: false, + includeDependencies: false, + includeDependents: false, + namePattern: "", + parentDir: "foo", + }, + false, + }, + { + "../foo", + args{"../foo", "."}, + TargetSelector{ + diff: "", + exclude: false, + excludeSelf: false, + includeDependencies: false, + includeDependents: false, + namePattern: "", + parentDir: "../foo", + }, + false, + }, + { + "...{./foo}", + args{"...{./foo}", "."}, + TargetSelector{ + diff: "", + exclude: false, + excludeSelf: false, + includeDependencies: false, + includeDependents: true, + namePattern: "", + parentDir: "foo", + }, + false, + }, + { + ".", + args{".", "."}, + TargetSelector{ + diff: "", + exclude: false, + excludeSelf: false, + includeDependencies: false, + includeDependents: false, + namePattern: "", + parentDir: ".", + }, + false, + }, + { + "..", + args{"..", "."}, + TargetSelector{ + diff: "", + exclude: false, + excludeSelf: false, + includeDependencies: false, + includeDependents: false, + namePattern: "", + parentDir: "..", + }, + false, + }, + { + "[master]", + args{"[master]", "."}, + TargetSelector{ + diff: "master", + exclude: false, + excludeSelf: false, + includeDependencies: false, + includeDependents: false, + namePattern: "", + parentDir: "", + }, + false, + }, + { + "{foo}[master]", + args{"{foo}[master]", "."}, + TargetSelector{ + diff: "master", + exclude: false, + excludeSelf: false, + includeDependencies: false, + includeDependents: false, + namePattern: "", + parentDir: "foo", + }, + false, + }, + { + "pattern{foo}[master]", + args{"pattern{foo}[master]", "."}, + TargetSelector{ + diff: "master", + exclude: false, + excludeSelf: false, + includeDependencies: false, + includeDependents: false, + namePattern: "pattern", + parentDir: "foo", + }, + false, + }, + { + "[master]...", + args{"[master]...", "."}, + TargetSelector{ + diff: "master", + exclude: false, + excludeSelf: false, + includeDependencies: true, + includeDependents: false, + namePattern: "", + parentDir: "", + }, + false, + }, + { + "...[master]", + args{"...[master]", "."}, + TargetSelector{ + diff: "master", + exclude: false, + excludeSelf: false, + includeDependencies: false, + includeDependents: true, + namePattern: "", + parentDir: "", + }, + false, + }, + { + "...[master]...", + args{"...[master]...", "."}, + TargetSelector{ + diff: "master", + exclude: false, + excludeSelf: false, + includeDependencies: true, + includeDependents: true, + namePattern: "", + parentDir: "", + }, + false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ParseTargetSelector(tt.args.rawSelector, tt.args.prefix) + if (err != nil) != tt.wantErr { + t.Errorf("ParseTargetSelector() error = %#v, wantErr %#v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("ParseTargetSelector() = %#v, want %#v", got, tt.want) + } + }) + } +} From 5e1930238e1754374e4a7f3291ca9e391e0e4e1b Mon Sep 17 00:00:00 2001 From: Jared Palmer Date: Thu, 11 Nov 2021 16:11:10 -0500 Subject: [PATCH 2/4] Allow pass through args --- internal/run/run.go | 10 +++++++--- internal/run/run_test.go | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/internal/run/run.go b/internal/run/run.go index 7f3f070bafd3c..f5f621668c0c8 100644 --- a/internal/run/run.go +++ b/internal/run/run.go @@ -667,7 +667,8 @@ type RunOptions struct { // Cache folder cacheFolder string // Immediately exit on task failure - bail bool + bail bool + passThroughArgs []string } func getDefaultRunOptions() *RunOptions { @@ -694,8 +695,11 @@ func parseRunArgs(args []string, cwd string) (*RunOptions, error) { unresolvedCacheFolder := "./node_modules/.cache/turbo" - for _, arg := range args { - if strings.HasPrefix(arg, "--") { + for argIndex, arg := range args { + if arg == "--" { + runOptions.passThroughArgs = args[argIndex+1:] + break + } else if strings.HasPrefix(arg, "--") { switch { case strings.HasPrefix(arg, "--since="): if len(arg[len("--since="):]) > 1 { diff --git a/internal/run/run_test.go b/internal/run/run_test.go index 4a356d5c81020..5fc431c91df14 100644 --- a/internal/run/run_test.go +++ b/internal/run/run_test.go @@ -97,6 +97,40 @@ func TestParseConfig(t *testing.T) { cacheFolder: "node_modules/.cache/turbo", }, }, + { + "passThroughArgs", + []string{"foo", "--graph=g.png", "--", "--boop", "zoop"}, + &RunOptions{ + deps: true, + stream: true, + bail: true, + dotGraph: "g.png", + concurrency: 10, + ancestors: false, + cache: true, + forceExecution: false, + profile: "", + cacheFolder: "node_modules/.cache/turbo", + passThroughArgs: []string{"--boop", "zoop"}, + }, + }, + { + "Empty passThroughArgs", + []string{"foo", "--graph=g.png", "--"}, + &RunOptions{ + deps: true, + stream: true, + bail: true, + dotGraph: "g.png", + concurrency: 10, + ancestors: false, + cache: true, + forceExecution: false, + profile: "", + cacheFolder: "node_modules/.cache/turbo", + passThroughArgs: []string{}, + }, + }, } for i, tc := range cases { From 51d54b6a78f441b0011ddb1621b217593d6e13f1 Mon Sep 17 00:00:00 2001 From: Jared Palmer Date: Mon, 15 Nov 2021 13:32:57 -0500 Subject: [PATCH 3/4] Remove pnpm filter --- internal/run/parse_target_selector.go | 116 --------- internal/run/parse_target_selector_test.go | 270 --------------------- 2 files changed, 386 deletions(-) delete mode 100644 internal/run/parse_target_selector.go delete mode 100644 internal/run/parse_target_selector_test.go diff --git a/internal/run/parse_target_selector.go b/internal/run/parse_target_selector.go deleted file mode 100644 index 2e5fe26d407cf..0000000000000 --- a/internal/run/parse_target_selector.go +++ /dev/null @@ -1,116 +0,0 @@ -package run - -import ( - "path/filepath" - "regexp" - "strings" -) - -type TargetSelector struct { - includeDependencies bool - includeDependents bool - exclude bool - excludeSelf bool - followProdDepsOnly bool - parentDir string - namePattern string - diff string -} - -// ParseTargetSelectors is a function that returns PNPM compatible --filter command line flags -func ParseTargetSelector(rawSelector string, prefix string) (TargetSelector, error) { - exclude := false - firstChar := rawSelector[0] - selector := rawSelector - if firstChar == '!' { - selector = selector[1:] - exclude = true - } - excludeSelf := false - includeDependencies := strings.HasSuffix(selector, "...") - if includeDependencies { - selector = selector[:len(selector)-3] - if strings.HasSuffix(selector, "^") { - excludeSelf = true - selector = selector[:len(selector)-1] - } - } - includeDependents := strings.HasPrefix(selector, "...") - if includeDependents { - selector = selector[3:] - if strings.HasPrefix(selector, "^") { - excludeSelf = true - selector = selector[1:] - } - } - regex := regexp.MustCompile(`^([^.][^{}[\]]*)?(\{[^}]+\})?(\[[^\]]+\])?$`) - matches := regex.FindAllStringSubmatch(selector, -1) - - diff := "" - parentDir := "" - namePattern := "" - - if len(matches) == 0 { - if isSelectorByLocation(selector) { - return TargetSelector{ - diff: diff, - exclude: exclude, - excludeSelf: false, - includeDependencies: includeDependencies, - includeDependents: includeDependents, - namePattern: namePattern, - parentDir: filepath.Join(prefix, selector), - }, nil - } - return TargetSelector{ - diff: diff, - exclude: exclude, - excludeSelf: excludeSelf, - includeDependencies: includeDependencies, - includeDependents: includeDependents, - namePattern: selector, - parentDir: parentDir, - }, nil - } - - if len(matches) > 0 && len(matches[0]) > 0 { - if len(matches[0][1]) > 0 { - namePattern = matches[0][1] - } - if len(matches[0][2]) > 0 { - parentDir = filepath.Join(prefix, matches[0][2][1:len(matches[0][2])-1]) - } - if len(matches[0][3]) > 0 { - diff = matches[0][3][1 : len(matches[0][3])-1] - } - } - - return TargetSelector{ - diff: diff, - exclude: exclude, - excludeSelf: excludeSelf, - includeDependencies: includeDependencies, - includeDependents: includeDependents, - namePattern: namePattern, - parentDir: parentDir, - }, nil -} - -// isSelectorByLocation returns true if the selector is by filesystem location -func isSelectorByLocation(rawSelector string) bool { - if rawSelector[0:1] != "." { - return false - } - - // . or ./ or .\ - if len(rawSelector) == 1 || rawSelector[1:2] == "/" || rawSelector[1:2] == "\\" { - return true - } - - if rawSelector[1:2] != "." { - return false - } - - // .. or ../ or ..\ - return len(rawSelector) == 2 || rawSelector[2:3] == "/" || rawSelector[2:3] == "\\" -} diff --git a/internal/run/parse_target_selector_test.go b/internal/run/parse_target_selector_test.go deleted file mode 100644 index 3c06e2aaf8df9..0000000000000 --- a/internal/run/parse_target_selector_test.go +++ /dev/null @@ -1,270 +0,0 @@ -package run - -import ( - "reflect" - "testing" -) - -func TestParseTargetSelector(t *testing.T) { - type args struct { - rawSelector string - prefix string - } - tests := []struct { - name string - args args - want TargetSelector - wantErr bool - }{ - { - "foo", - args{"foo", "."}, - TargetSelector{ - diff: "", - exclude: false, - excludeSelf: false, - includeDependencies: false, - includeDependents: false, - namePattern: "foo", - parentDir: "", - }, - false, - }, - { - "foo...", - args{"foo...", "."}, - TargetSelector{ - diff: "", - exclude: false, - excludeSelf: false, - includeDependencies: true, - includeDependents: false, - namePattern: "foo", - parentDir: "", - }, - false, - }, - { - "...foo", - args{"...foo", "."}, - TargetSelector{ - diff: "", - exclude: false, - excludeSelf: false, - includeDependencies: false, - includeDependents: true, - namePattern: "foo", - parentDir: "", - }, - false, - }, - { - "...foo...", - args{"...foo...", "."}, - TargetSelector{ - diff: "", - exclude: false, - excludeSelf: false, - includeDependencies: true, - includeDependents: true, - namePattern: "foo", - parentDir: "", - }, - false, - }, - { - "foo^...", - args{"foo^...", "."}, - TargetSelector{ - diff: "", - exclude: false, - excludeSelf: true, - includeDependencies: true, - includeDependents: false, - namePattern: "foo", - parentDir: "", - }, - false, - }, - { - "...^foo", - args{"...^foo", "."}, - TargetSelector{ - diff: "", - exclude: false, - excludeSelf: true, - includeDependencies: false, - includeDependents: true, - namePattern: "foo", - parentDir: "", - }, - false, - }, - { - "./foo", - args{"./foo", "./"}, - TargetSelector{ - diff: "", - exclude: false, - excludeSelf: false, - includeDependencies: false, - includeDependents: false, - namePattern: "", - parentDir: "foo", - }, - false, - }, - { - "../foo", - args{"../foo", "."}, - TargetSelector{ - diff: "", - exclude: false, - excludeSelf: false, - includeDependencies: false, - includeDependents: false, - namePattern: "", - parentDir: "../foo", - }, - false, - }, - { - "...{./foo}", - args{"...{./foo}", "."}, - TargetSelector{ - diff: "", - exclude: false, - excludeSelf: false, - includeDependencies: false, - includeDependents: true, - namePattern: "", - parentDir: "foo", - }, - false, - }, - { - ".", - args{".", "."}, - TargetSelector{ - diff: "", - exclude: false, - excludeSelf: false, - includeDependencies: false, - includeDependents: false, - namePattern: "", - parentDir: ".", - }, - false, - }, - { - "..", - args{"..", "."}, - TargetSelector{ - diff: "", - exclude: false, - excludeSelf: false, - includeDependencies: false, - includeDependents: false, - namePattern: "", - parentDir: "..", - }, - false, - }, - { - "[master]", - args{"[master]", "."}, - TargetSelector{ - diff: "master", - exclude: false, - excludeSelf: false, - includeDependencies: false, - includeDependents: false, - namePattern: "", - parentDir: "", - }, - false, - }, - { - "{foo}[master]", - args{"{foo}[master]", "."}, - TargetSelector{ - diff: "master", - exclude: false, - excludeSelf: false, - includeDependencies: false, - includeDependents: false, - namePattern: "", - parentDir: "foo", - }, - false, - }, - { - "pattern{foo}[master]", - args{"pattern{foo}[master]", "."}, - TargetSelector{ - diff: "master", - exclude: false, - excludeSelf: false, - includeDependencies: false, - includeDependents: false, - namePattern: "pattern", - parentDir: "foo", - }, - false, - }, - { - "[master]...", - args{"[master]...", "."}, - TargetSelector{ - diff: "master", - exclude: false, - excludeSelf: false, - includeDependencies: true, - includeDependents: false, - namePattern: "", - parentDir: "", - }, - false, - }, - { - "...[master]", - args{"...[master]", "."}, - TargetSelector{ - diff: "master", - exclude: false, - excludeSelf: false, - includeDependencies: false, - includeDependents: true, - namePattern: "", - parentDir: "", - }, - false, - }, - { - "...[master]...", - args{"...[master]...", "."}, - TargetSelector{ - diff: "master", - exclude: false, - excludeSelf: false, - includeDependencies: true, - includeDependents: true, - namePattern: "", - parentDir: "", - }, - false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := ParseTargetSelector(tt.args.rawSelector, tt.args.prefix) - if (err != nil) != tt.wantErr { - t.Errorf("ParseTargetSelector() error = %#v, wantErr %#v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("ParseTargetSelector() = %#v, want %#v", got, tt.want) - } - }) - } -} From 90016f1f40783773d0ee45f99b0d5ed88f6aae32 Mon Sep 17 00:00:00 2001 From: Jared Palmer Date: Mon, 15 Nov 2021 13:42:46 -0500 Subject: [PATCH 4/4] Hash and pass through extra cli args --- internal/run/run.go | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/internal/run/run.go b/internal/run/run.go index 24d375981bf20..80ce0668e5652 100644 --- a/internal/run/run.go +++ b/internal/run/run.go @@ -417,13 +417,15 @@ func (c *RunCommand) Run(args []string) int { pipeline = altpipe } hashable := struct { - Hash string - Task string - Outputs []string + Hash string + Task string + Outputs []string + PassThruArgs []string }{ - Hash: pack.Hash, - Task: task, - Outputs: pipeline.Outputs, + Hash: pack.Hash, + Task: task, + Outputs: pipeline.Outputs, + PassThruArgs: runOptions.passThroughArgs, } hash, err := fs.HashObject(hashable) targetLogger.Debug("task hash", "value", hash) @@ -478,6 +480,7 @@ func (c *RunCommand) Run(args []string) int { targetUi.Output(fmt.Sprintf("cache miss, executing %s", ui.Dim(hash))) } argsactual := append([]string{"run"}, task) + argsactual = append(argsactual, runOptions.passThroughArgs...) // @TODO: @jaredpalmer fix this hack to get the package manager's name cmd := exec.Command(strings.TrimPrefix(ctx.Backend.Name, "nodejs-"), argsactual...) cmd.Dir = pack.Dir @@ -693,16 +696,17 @@ type RunOptions struct { func getDefaultRunOptions() *RunOptions { return &RunOptions{ - bail: true, - deps: true, - parallel: false, - concurrency: 10, - dotGraph: "", - ancestors: false, - cache: true, - profile: "", // empty string does no tracing - forceExecution: false, - stream: true, + bail: true, + deps: true, + parallel: false, + concurrency: 10, + dotGraph: "", + ancestors: false, + cache: true, + profile: "", // empty string does no tracing + forceExecution: false, + stream: true, + passThroughArgs: []string{}, } }