diff --git a/staging/src/k8s.io/code-generator/pkg/codegen/helpers/generic.go b/staging/src/k8s.io/code-generator/pkg/codegen/helpers/generic.go index 9d4ce83f0eb69..c8a162d678f5a 100644 --- a/staging/src/k8s.io/code-generator/pkg/codegen/helpers/generic.go +++ b/staging/src/k8s.io/code-generator/pkg/codegen/helpers/generic.go @@ -21,11 +21,12 @@ import ( "fmt" "github.com/spf13/pflag" "k8s.io/code-generator/pkg/fs" - "k8s.io/code-generator/pkg/osbin/git" + "k8s.io/code-generator/pkg/fs/gosrc" "k8s.io/code-generator/pkg/osbin/golang" "k8s.io/klog/v2" "os" "path" + "regexp" ) type genFn func(fs *pflag.FlagSet, args []string) error @@ -45,22 +46,8 @@ func (gc genConf) suffix() string { return gc.fileSuffix } -type pathspecFn func(*Args) string - -func globPathspec(globFmt string) pathspecFn { - return func(args *Args) string { - return fmt.Sprintf(globFmt, args.root) - } -} - -func golangPathspec(args *Args) string { - return globPathspec(":(glob)%s/**/*.go")(args) -} - -func zzGeneratedPathspec(name string) pathspecFn { - return globPathspec( - fmt.Sprintf(":(glob)%%s/**/zz_generated.%s.go", name), - ) +func zzGeneratedPathspec(name string) string { + return fmt.Sprintf("zz_generated.%s.go", name) } func (g *Generator) generateGen(args *Args, gc genConf) error { @@ -88,11 +75,10 @@ func (g *Generator) generateGen(args *Args, gc genConf) error { func collectPackages(args *Args, conf genConf) ([]string, error) { var inputPkgs = make(map[string]bool, 1) - if files, err := git.Grep( - conf.searchPattern, - golangPathspec(args), - git.WithList, - ); err != nil { + matcher := gosrc.FileContains(regexp.MustCompile( + regexp.QuoteMeta(conf.searchPattern), + )) + if files, err := gosrc.Find(args.root, matcher); err != nil { return nil, err } else { klog.V(3).Infof("Found %d files with %s-gen tags", @@ -119,13 +105,14 @@ func collectPackages(args *Args, conf genConf) ([]string, error) { } func deleteGenerated(args *Args, gc genConf) error { - pathspec := zzGeneratedPathspec(gc.suffix())(args) - if genFiles, err := git.Find(pathspec); err != nil { + pathspec := zzGeneratedPathspec(gc.suffix()) + if genFiles, err := gosrc.Find(args.root, gosrc.FileEndsWith(pathspec)); err != nil { return err } else { klog.V(2).Infof("Deleting %d existing %s helpers", len(genFiles), gc.name) - for _, file := range genFiles { + for i := len(genFiles) - 1; i >= 0; i-- { + file := genFiles[i] if _, oserr := os.Stat(file); oserr != nil && os.IsNotExist(oserr) { continue } @@ -193,6 +180,9 @@ func resolvePackage(file string) (string, error) { return foundPkg, nil } +// asBinary runs the given function as if it were the given binary. +// It sets os.Args[0] to binName, and restores it when done. This is required +// so the generator can identify itself as it was executed directly. func asBinary(binName string, fn func() error) error { curr := os.Args[0] defer func() { diff --git a/staging/src/k8s.io/code-generator/pkg/fs/gosrc/search.go b/staging/src/k8s.io/code-generator/pkg/fs/gosrc/search.go new file mode 100644 index 0000000000000..dfef2a70885e7 --- /dev/null +++ b/staging/src/k8s.io/code-generator/pkg/fs/gosrc/search.go @@ -0,0 +1,103 @@ +/* +Copyright 2023 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package gosrc + +import ( + "bufio" + "io/fs" + "os" + "path" + "path/filepath" + "regexp" + "strings" +) + +// Find returns the list of files matching the given matchers. The search is +// performed recursively from the given root directory. The file is only +// considered if it matches all the given matchers. +func Find(root string, matchers ...FileMatcher) ([]string, error) { + var files []string + err := filepath.WalkDir(root, func(p string, d fs.DirEntry, err error) error { + if err != nil { + return filepath.SkipDir + } + n := path.Base(p) + if !(d.Type().IsRegular() && + strings.HasSuffix(n, ".go") && + !strings.HasSuffix(n, "_test.go")) { + return nil + } + for _, m := range matchers { + if !m.MatchFile(p) { + return nil + } + } + files = append(files, p) + return nil + + }) + return files, err +} + +// FileEndsWith returns a FileMatcher that matches files with the given suffix. +func FileEndsWith(suffix string) FileMatcher { + return FileMatcherFunc(func(fp string) bool { + return strings.HasSuffix(fp, suffix) + }) +} + +// FileContains returns a FileMatcher that matches files containing the given +// pattern. +func FileContains(pattern *regexp.Regexp) FileMatcher { + return FileMatcherFunc(func(fp string) bool { + return match(pattern, fp) + }) +} + +// FileMatcher matches files. +type FileMatcher interface { + // MatchFile returns true if the given file path matches. + MatchFile(string) bool +} + +// FileMatcherFunc is a function that matches files. +type FileMatcherFunc func(string) bool + +// MatchFile implements FileMatcher. +func (f FileMatcherFunc) MatchFile(fp string) bool { + return f(fp) +} + +func match(pattern *regexp.Regexp, fp string) bool { + // instead of reading the whole file into memory and matching against + // the whole file contents. + // The following code is similar to the following shell command: + // grep -q -e "$pattern" "$fp" + // The -q flag is used to suppress the output. + f, err := os.Open(fp) + if err != nil { + return false + } + defer f.Close() + scanner := bufio.NewScanner(f) + for scanner.Scan() { + if pattern.MatchString(scanner.Text()) { + return true + } + } + return false +} diff --git a/staging/src/k8s.io/code-generator/pkg/fs/rootdir.go b/staging/src/k8s.io/code-generator/pkg/fs/rootdir.go index 1115793d03daa..d3094df92db8c 100644 --- a/staging/src/k8s.io/code-generator/pkg/fs/rootdir.go +++ b/staging/src/k8s.io/code-generator/pkg/fs/rootdir.go @@ -1,17 +1,17 @@ /* - Copyright 2023 The Kubernetes Authors. +Copyright 2023 The Kubernetes Authors. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 + http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. */ package fs diff --git a/staging/src/k8s.io/code-generator/pkg/osbin/git/find.go b/staging/src/k8s.io/code-generator/pkg/osbin/git/find.go deleted file mode 100644 index d0bd6436de5e3..0000000000000 --- a/staging/src/k8s.io/code-generator/pkg/osbin/git/find.go +++ /dev/null @@ -1,70 +0,0 @@ -/* -Copyright 2023 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package git - -import ( - "errors" - "io" - "k8s.io/klog/v2" - "os" - "os/exec" - "strings" -) - -// FindOpt is a functional option for the Find func. -type FindOpt func(*FindOpts) - -// FindOpts are options for the Find func. -type FindOpts struct { - Err io.Writer -} - -// Find runs git ls-files with the given options, and returns the output or -// the exit error. -// -// TODO: Consider rewriting this in native Go. -func Find(pathspec string, opts ...FindOpt) ([]string, error) { - o := &FindOpts{} - for _, opt := range opts { - opt(o) - } - args := []string{ - // Similar to find but faster and easier to understand. We want to - // include modified and untracked files because this might be running - // against code which is not tracked by git yet. - "ls-files", - "--cached", "--modified", "--others", - "--exclude-standard", "-z", - "--", pathspec, - } - cmd := exec.Command("git", args...) - if o.Err != nil { - cmd.Stderr = o.Err - } - cmd.Env = append(os.Environ(), "LANG=C.UTF-8") - klog.V(3).Infof("Running: %q", cmd) - bytes, err := cmd.Output() - if err != nil { - var ee *exec.ExitError - if errors.As(err, &ee) { - klog.Errorf("git ls-files stderr: %s", string(ee.Stderr)) - } - return nil, err - } - out := strings.Trim(string(bytes), "\n\x00") - return strings.Split(out, "\x00"), nil -} diff --git a/staging/src/k8s.io/code-generator/pkg/osbin/git/grep.go b/staging/src/k8s.io/code-generator/pkg/osbin/git/grep.go deleted file mode 100644 index 476a347ea6c04..0000000000000 --- a/staging/src/k8s.io/code-generator/pkg/osbin/git/grep.go +++ /dev/null @@ -1,78 +0,0 @@ -/* -Copyright 2023 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package git - -import ( - "errors" - "io" - "k8s.io/klog/v2" - "os" - "os/exec" - "strings" -) - -// GrepOpt is an functional option for the git grep command. -type GrepOpt func(*GrepOpts) - -// GrepOpts are options for the git grep command. -type GrepOpts struct { - List bool - Err io.Writer -} - -// WithList is a GrepOpt that sets the --files-with-matches flag. -func WithList(o *GrepOpts) { - o.List = true -} - -// Grep runs the git grep command, and returns the output or the exit error. -// -// TODO: Consider rewriting this in native Go. -func Grep(pattern, pathspec string, opts ...GrepOpt) ([]string, error) { - o := &GrepOpts{} - for _, opt := range opts { - opt(o) - } - args := []string{ - // We want to include modified and untracked files because this might be - // running against code which is not tracked by git yet. - "grep", "--no-color", "--untracked", "--null", - } - if o.List { - args = append(args, "--files-with-matches") - } - args = append(args, - "-e", pattern, - "--", pathspec, - ) - cmd := exec.Command("git", args...) - if o.Err != nil { - cmd.Stderr = o.Err - } - cmd.Env = append(os.Environ(), "LANG=C.UTF-8") - klog.V(3).Infof("Running: %q", cmd) - bytes, err := cmd.Output() - if err != nil { - var ee *exec.ExitError - if errors.As(err, &ee) { - klog.Errorf("git grep stderr: %s", string(ee.Stderr)) - } - return nil, err - } - out := strings.Trim(string(bytes), "\n\x00") - return strings.Split(out, "\x00"), nil -}