Source file src/simd/archsimd/_gen/cmd/specfill/main.go

     1  // Copyright 2026 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // specfill checks the public SIMD API against internal/spec and reports
     6  // signature mismatches, comment structure violations, and documentation diffs.
     7  package main
     8  
     9  import (
    10  	"bytes"
    11  	"errors"
    12  	"flag"
    13  	"fmt"
    14  	"os"
    15  	"os/exec"
    16  	"path/filepath"
    17  	"simd/archsimd/_gen/gentools"
    18  	"simd/archsimd/_gen/specdoc"
    19  	"simd/archsimd/_gen/specgen"
    20  	"strings"
    21  )
    22  
    23  func main() {
    24  	genOpts := gentools.RegisterFlags(nil)
    25  	noFillDoc := flag.Bool("no-fill-doc", false, "disable filling doc comments from spec")
    26  	noFillNames := flag.Bool("no-fill-names", false, "disable filling parameter and result names from spec")
    27  
    28  	dirs := []string{
    29  		filepath.Join(genOpts.GOROOT, "src/simd"),
    30  		filepath.Join(genOpts.GOROOT, "src/simd/archsimd"),
    31  	}
    32  
    33  	flag.Usage = func() {
    34  		w := flag.CommandLine.Output()
    35  		fmt.Fprintf(w, "usage: specfill [flags] [files...]\n\n")
    36  		fmt.Fprintf(w, "If files are not given, specfill applies to all non-test, non-generated files in:\n")
    37  		for _, d := range dirs {
    38  			fmt.Fprintf(w, "  %s\n", d)
    39  		}
    40  		fmt.Fprintln(w)
    41  		flag.CommandLine.PrintDefaults()
    42  	}
    43  
    44  	flag.Parse()
    45  
    46  	specDir := specgen.MustFindSpecDir(genOpts.GOROOT)
    47  	specFuncs, err := specgen.Load(specDir, nil)
    48  	if err != nil {
    49  		fmt.Fprintf(os.Stderr, "loading spec: %v\n", err)
    50  		os.Exit(1)
    51  	}
    52  	specIdx := specgen.NewIndex(specFuncs)
    53  
    54  	var targetFiles []string
    55  	filesArgs := false
    56  	if flag.NArg() > 0 {
    57  		filesArgs = true
    58  		for _, arg := range flag.Args() {
    59  			p, err := filepath.Abs(arg)
    60  			if err != nil {
    61  				fmt.Fprintf(os.Stderr, "%s: %v\n", arg, err)
    62  				os.Exit(1)
    63  			}
    64  			targetFiles = append(targetFiles, p)
    65  		}
    66  	} else {
    67  		for _, dir := range dirs {
    68  			entries, err := os.ReadDir(dir)
    69  			if err != nil {
    70  				fmt.Fprintf(os.Stderr, "reading %s: %v\n", dir, err)
    71  				os.Exit(1)
    72  			}
    73  			for _, e := range entries {
    74  				if e.IsDir() || !strings.HasSuffix(e.Name(), ".go") || strings.HasSuffix(e.Name(), "_test.go") {
    75  					continue
    76  				}
    77  				targetFiles = append(targetFiles, filepath.Join(dir, e.Name()))
    78  			}
    79  		}
    80  	}
    81  
    82  	var files gentools.Files
    83  	defer files.FlushOrExit()
    84  
    85  	var combinedReport specdoc.Report
    86  
    87  	for _, filePath := range targetFiles {
    88  		relPath, err := filepath.Rel(genOpts.GOROOT, filePath)
    89  		if err != nil {
    90  			relPath = filePath
    91  		}
    92  		srcRelPath, err := filepath.Rel(filepath.Join(genOpts.GOROOT, "src"), filePath)
    93  		if err != nil {
    94  			srcRelPath = strings.TrimPrefix(filepath.ToSlash(relPath), "src/")
    95  		}
    96  		src, err := os.ReadFile(filePath)
    97  		if err != nil {
    98  			fmt.Fprintf(os.Stderr, "reading %s: %v\n", filePath, err)
    99  			os.Exit(1)
   100  		}
   101  		gen, err := isGenerated(src)
   102  		if err != nil {
   103  			fmt.Fprintf(os.Stderr, "%s: %v\n", filePath, err)
   104  			os.Exit(1)
   105  		}
   106  		if gen {
   107  			if filesArgs {
   108  				fmt.Fprintf(os.Stderr, "%s: file is generated\n", filePath)
   109  				os.Exit(1)
   110  			}
   111  			continue
   112  		}
   113  		rewritten, err := specdoc.Fill(src, specIdx, specdoc.Options{
   114  			Filename:        filepath.ToSlash(relPath),
   115  			AllowDocRewrite: true,
   116  			NoFillDoc:       *noFillDoc,
   117  			NoFillNames:     *noFillNames,
   118  		})
   119  		var rep *specdoc.Report
   120  		if errors.As(err, &rep) {
   121  			combinedReport.Merge(rep)
   122  		} else if err != nil {
   123  			fmt.Fprintf(os.Stderr, "checking %s: %v\n", filePath, err)
   124  			os.Exit(1)
   125  		}
   126  		if rewritten != nil && !bytes.Equal(src, rewritten) {
   127  			buf := files.NewGoFile(filepath.ToSlash(srcRelPath))
   128  			buf.Write(rewritten)
   129  		}
   130  	}
   131  
   132  	combinedReport.Print(os.Stderr)
   133  
   134  	if genOpts.Write {
   135  		if err := checkCleanWorkingCopy(genOpts.GOROOT); err != nil {
   136  			fmt.Fprintf(os.Stderr, "refusing to write: %v\n", err)
   137  			os.Exit(1)
   138  		}
   139  		if !combinedReport.Empty() {
   140  			fmt.Fprintln(os.Stderr, "errors reported; not writing files")
   141  			os.Exit(1)
   142  		}
   143  	}
   144  }
   145  
   146  // checkCleanWorkingCopy verifies that the working directory has no uncommitted
   147  // changes before specfill rewrites files in place (-w).
   148  //
   149  // We rely on "git status --porcelain" rather than invoking "jj" commands directly.
   150  // In a standard Git repository or a colocated jj/git repository, "git status"
   151  // checks disk cleanliness without side-effects.
   152  //
   153  // # Interoperation with colocated jj repos
   154  //
   155  // Jujutsu keeps Git's HEAD and index in sync with the parent of the working-copy
   156  // commit (@-). If the user starts from a fresh, empty commit (e.g. via "jj new"),
   157  // the working copy matches Git's HEAD and git status reports clean. If there are
   158  // modified or untracked files on disk (or if @ contains changes), git status
   159  // reports them as uncommitted changes. By querying git rather than running "jj"
   160  // commands (like "jj diff" or "jj status"), we avoid jj's snapshotting side-effects,
   161  // such as automatically snapshotting dirty files into @, writing to the operation
   162  // log, and auto-tracking newly created files.
   163  func checkCleanWorkingCopy(dir string) error {
   164  	cmd := exec.Command("git", "status", "--porcelain")
   165  	cmd.Dir = dir
   166  	out, err := cmd.Output()
   167  	if err != nil {
   168  		// Not a git repository or git binary not available; skip the check.
   169  		return nil
   170  	}
   171  	if len(bytes.TrimSpace(out)) > 0 {
   172  		return fmt.Errorf("working copy has uncommitted changes:\n%s", out)
   173  	}
   174  	return nil
   175  }
   176  
   177  // isGenerated reports whether src is a generated Go file.
   178  //
   179  // It checks for the standard "// Code generated by" comment marker at the beginning
   180  // of the file or at the beginning of any line. If missing, it checks for
   181  // "DO NOT EDIT" as a backstop; if found, it returns an error to catch non-standard
   182  // or malformed generator headers rather than erroneously treating the file as hand-written.
   183  func isGenerated(src []byte) (bool, error) {
   184  	const marker = "// Code generated by"
   185  	if bytes.HasPrefix(src, []byte(marker)) || bytes.Contains(src, []byte("\n"+marker)) {
   186  		return true, nil
   187  	}
   188  	if bytes.Contains(src, []byte("DO NOT EDIT")) {
   189  		return false, errors.New("file contains 'DO NOT EDIT' but lacks '// Code generated by' header")
   190  	}
   191  	return false, nil
   192  }
   193  

View as plain text