| // Copyright 2026 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| // specfill checks the public SIMD API against internal/spec and reports |
| // signature mismatches, comment structure violations, and documentation diffs. |
| package main |
| |
| import ( |
| "bytes" |
| "errors" |
| "flag" |
| "fmt" |
| "os" |
| "os/exec" |
| "path/filepath" |
| "simd/archsimd/_gen/gentools" |
| "simd/archsimd/_gen/specdoc" |
| "simd/archsimd/_gen/specgen" |
| "strings" |
| ) |
| |
| func main() { |
| genOpts := gentools.RegisterFlags(nil) |
| noFillDoc := flag.Bool("no-fill-doc", false, "disable filling doc comments from spec") |
| noFillNames := flag.Bool("no-fill-names", false, "disable filling parameter and result names from spec") |
| |
| dirs := []string{ |
| filepath.Join(genOpts.GOROOT, "src/simd"), |
| filepath.Join(genOpts.GOROOT, "src/simd/archsimd"), |
| } |
| |
| flag.Usage = func() { |
| w := flag.CommandLine.Output() |
| fmt.Fprintf(w, "usage: specfill [flags] [files...]\n\n") |
| fmt.Fprintf(w, "If files are not given, specfill applies to all non-test, non-generated files in:\n") |
| for _, d := range dirs { |
| fmt.Fprintf(w, " %s\n", d) |
| } |
| fmt.Fprintln(w) |
| flag.CommandLine.PrintDefaults() |
| } |
| |
| flag.Parse() |
| |
| specDir := specgen.MustFindSpecDir(genOpts.GOROOT) |
| specFuncs, err := specgen.Load(specDir, nil) |
| if err != nil { |
| fmt.Fprintf(os.Stderr, "loading spec: %v\n", err) |
| os.Exit(1) |
| } |
| specIdx := specgen.NewIndex(specFuncs) |
| |
| var targetFiles []string |
| filesArgs := false |
| if flag.NArg() > 0 { |
| filesArgs = true |
| for _, arg := range flag.Args() { |
| p, err := filepath.Abs(arg) |
| if err != nil { |
| fmt.Fprintf(os.Stderr, "%s: %v\n", arg, err) |
| os.Exit(1) |
| } |
| targetFiles = append(targetFiles, p) |
| } |
| } else { |
| for _, dir := range dirs { |
| entries, err := os.ReadDir(dir) |
| if err != nil { |
| fmt.Fprintf(os.Stderr, "reading %s: %v\n", dir, err) |
| os.Exit(1) |
| } |
| for _, e := range entries { |
| if e.IsDir() || !strings.HasSuffix(e.Name(), ".go") || strings.HasSuffix(e.Name(), "_test.go") { |
| continue |
| } |
| targetFiles = append(targetFiles, filepath.Join(dir, e.Name())) |
| } |
| } |
| } |
| |
| var files gentools.Files |
| defer files.FlushOrExit() |
| |
| var combinedReport specdoc.Report |
| |
| for _, filePath := range targetFiles { |
| relPath, err := filepath.Rel(genOpts.GOROOT, filePath) |
| if err != nil { |
| relPath = filePath |
| } |
| srcRelPath, err := filepath.Rel(filepath.Join(genOpts.GOROOT, "src"), filePath) |
| if err != nil { |
| srcRelPath = strings.TrimPrefix(filepath.ToSlash(relPath), "src/") |
| } |
| src, err := os.ReadFile(filePath) |
| if err != nil { |
| fmt.Fprintf(os.Stderr, "reading %s: %v\n", filePath, err) |
| os.Exit(1) |
| } |
| gen, err := isGenerated(src) |
| if err != nil { |
| fmt.Fprintf(os.Stderr, "%s: %v\n", filePath, err) |
| os.Exit(1) |
| } |
| if gen { |
| if filesArgs { |
| fmt.Fprintf(os.Stderr, "%s: file is generated\n", filePath) |
| os.Exit(1) |
| } |
| continue |
| } |
| rewritten, err := specdoc.Fill(src, specIdx, specdoc.Options{ |
| Filename: filepath.ToSlash(relPath), |
| AllowDocRewrite: true, |
| NoFillDoc: *noFillDoc, |
| NoFillNames: *noFillNames, |
| }) |
| var rep *specdoc.Report |
| if errors.As(err, &rep) { |
| combinedReport.Merge(rep) |
| } else if err != nil { |
| fmt.Fprintf(os.Stderr, "checking %s: %v\n", filePath, err) |
| os.Exit(1) |
| } |
| if rewritten != nil && !bytes.Equal(src, rewritten) { |
| buf := files.NewGoFile(filepath.ToSlash(srcRelPath)) |
| buf.Write(rewritten) |
| } |
| } |
| |
| combinedReport.Print(os.Stderr) |
| |
| if genOpts.Write { |
| if err := checkCleanWorkingCopy(genOpts.GOROOT); err != nil { |
| fmt.Fprintf(os.Stderr, "refusing to write: %v\n", err) |
| os.Exit(1) |
| } |
| if !combinedReport.Empty() { |
| fmt.Fprintln(os.Stderr, "errors reported; not writing files") |
| os.Exit(1) |
| } |
| } |
| } |
| |
| // checkCleanWorkingCopy verifies that the working directory has no uncommitted |
| // changes before specfill rewrites files in place (-w). |
| // |
| // We rely on "git status --porcelain" rather than invoking "jj" commands directly. |
| // In a standard Git repository or a colocated jj/git repository, "git status" |
| // checks disk cleanliness without side-effects. |
| // |
| // # Interoperation with colocated jj repos |
| // |
| // Jujutsu keeps Git's HEAD and index in sync with the parent of the working-copy |
| // commit (@-). If the user starts from a fresh, empty commit (e.g. via "jj new"), |
| // the working copy matches Git's HEAD and git status reports clean. If there are |
| // modified or untracked files on disk (or if @ contains changes), git status |
| // reports them as uncommitted changes. By querying git rather than running "jj" |
| // commands (like "jj diff" or "jj status"), we avoid jj's snapshotting side-effects, |
| // such as automatically snapshotting dirty files into @, writing to the operation |
| // log, and auto-tracking newly created files. |
| func checkCleanWorkingCopy(dir string) error { |
| cmd := exec.Command("git", "status", "--porcelain") |
| cmd.Dir = dir |
| out, err := cmd.Output() |
| if err != nil { |
| // Not a git repository or git binary not available; skip the check. |
| return nil |
| } |
| if len(bytes.TrimSpace(out)) > 0 { |
| return fmt.Errorf("working copy has uncommitted changes:\n%s", out) |
| } |
| return nil |
| } |
| |
| // isGenerated reports whether src is a generated Go file. |
| // |
| // It checks for the standard "// Code generated by" comment marker at the beginning |
| // of the file or at the beginning of any line. If missing, it checks for |
| // "DO NOT EDIT" as a backstop; if found, it returns an error to catch non-standard |
| // or malformed generator headers rather than erroneously treating the file as hand-written. |
| func isGenerated(src []byte) (bool, error) { |
| const marker = "// Code generated by" |
| if bytes.HasPrefix(src, []byte(marker)) || bytes.Contains(src, []byte("\n"+marker)) { |
| return true, nil |
| } |
| if bytes.Contains(src, []byte("DO NOT EDIT")) { |
| return false, errors.New("file contains 'DO NOT EDIT' but lacks '// Code generated by' header") |
| } |
| return false, nil |
| } |