2021-09-20 02:22:52 -04:00
package main
import (
2024-12-24 13:17:00 +08:00
"errors"
2021-09-27 01:45:02 -04:00
"flag"
2021-09-20 02:22:52 -04:00
"fmt"
"go/build"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
)
2026-05-29 23:04:59 +08:00
var (
directory = flag . String ( "pwd" , "" , "Working directory of Xray vformat." )
action = flag . String ( "mode" , "format" , "Execution mode. Default is 'format'.\n'format' formatting source files and save changes to files.\n'check' list all paths of improper formatted file.\n'dryrun' formatting source files and shows all diffs, but will not make any changes to files." )
)
var (
isCheck bool
isDryrun bool
isFormat bool
)
2021-09-27 01:45:02 -04:00
2021-09-20 02:22:52 -04:00
// envFile returns the name of the Go environment configuration file.
// Copy from https://github.com/golang/go/blob/c4f2a9788a7be04daf931ac54382fbe2cb754938/src/cmd/go/internal/cfg/cfg.go#L150-L166
func envFile ( ) ( string , error ) {
if file := os . Getenv ( "GOENV" ) ; file != "" {
if file == "off" {
2024-12-24 13:17:00 +08:00
return "" , errors . New ( "GOENV=off" )
2021-09-20 02:22:52 -04:00
}
return file , nil
}
dir , err := os . UserConfigDir ( )
if err != nil {
return "" , err
}
if dir == "" {
2024-12-24 13:17:00 +08:00
return "" , errors . New ( "missing user-config dir" )
2021-09-20 02:22:52 -04:00
}
return filepath . Join ( dir , "go" , "env" ) , nil
}
// GetRuntimeEnv returns the value of runtime environment variable,
// that is set by running following command: `go env -w key=value`.
func GetRuntimeEnv ( key string ) ( string , error ) {
file , err := envFile ( )
if err != nil {
return "" , err
}
if file == "" {
2024-12-24 13:17:00 +08:00
return "" , errors . New ( "missing runtime env file" )
2021-09-20 02:22:52 -04:00
}
var data [ ] byte
var runtimeEnv string
2021-09-29 02:49:34 +08:00
data , readErr := os . ReadFile ( file )
2021-09-20 02:22:52 -04:00
if readErr != nil {
return "" , readErr
}
envStrings := strings . Split ( string ( data ) , "\n" )
for _ , envItem := range envStrings {
envItem = strings . TrimSuffix ( envItem , "\r" )
envKeyValue := strings . Split ( envItem , "=" )
2021-10-12 11:29:22 -04:00
if len ( envKeyValue ) == 2 && strings . TrimSpace ( envKeyValue [ 0 ] ) == key {
2021-09-20 02:22:52 -04:00
runtimeEnv = strings . TrimSpace ( envKeyValue [ 1 ] )
}
}
return runtimeEnv , nil
}
// GetGOBIN returns GOBIN environment variable as a string. It will NOT be empty.
func GetGOBIN ( ) string {
// The one set by user explicitly by `export GOBIN=/path` or `env GOBIN=/path command`
GOBIN := os . Getenv ( "GOBIN" )
if GOBIN == "" {
var err error
// The one set by user by running `go env -w GOBIN=/path`
GOBIN , err = GetRuntimeEnv ( "GOBIN" )
if err != nil {
// The default one that Golang uses
return filepath . Join ( build . Default . GOPATH , "bin" )
}
if GOBIN == "" {
return filepath . Join ( build . Default . GOPATH , "bin" )
}
return GOBIN
}
return GOBIN
}
2021-10-12 11:29:22 -04:00
func Run ( binary string , args [ ] string ) ( [ ] byte , error ) {
2021-09-20 02:22:52 -04:00
cmd := exec . Command ( binary , args ... )
cmd . Env = append ( cmd . Env , os . Environ ( ) ... )
output , cmdErr := cmd . CombinedOutput ( )
if cmdErr != nil {
2021-10-12 11:29:22 -04:00
return nil , cmdErr
2021-09-20 02:22:52 -04:00
}
2021-10-12 11:29:22 -04:00
return output , nil
2021-09-20 02:22:52 -04:00
}
2026-05-29 23:04:59 +08:00
func RunMany ( binary string , args , files [ ] string ) bool {
fmt . Println ( "Processing with" , binary , args , "..." )
2021-10-12 11:29:22 -04:00
2026-05-29 23:04:59 +08:00
formatRequired := false
2021-10-12 11:29:22 -04:00
maxTasks := make ( chan struct { } , runtime . NumCPU ( ) )
2021-09-20 02:22:52 -04:00
for _ , file := range files {
2021-10-12 11:29:22 -04:00
maxTasks <- struct { } { }
go func ( file string ) {
output , err := Run ( binary , append ( args , file ) )
if err != nil {
fmt . Println ( err )
} else if len ( output ) > 0 {
fmt . Println ( string ( output ) )
2026-05-29 23:04:59 +08:00
formatRequired = true
2021-10-12 11:29:22 -04:00
}
<- maxTasks
} ( file )
2021-09-20 02:22:52 -04:00
}
2026-05-29 23:04:59 +08:00
return formatRequired
2021-09-20 02:22:52 -04:00
}
func main ( ) {
2021-09-27 01:45:02 -04:00
flag . Usage = func ( ) {
fmt . Fprintf ( flag . CommandLine . Output ( ) , "Usage of vformat:\n" )
flag . PrintDefaults ( )
}
flag . Parse ( )
if ! filepath . IsAbs ( * directory ) {
pwd , wdErr := os . Getwd ( )
if wdErr != nil {
fmt . Println ( "Can not get current working directory." )
os . Exit ( 1 )
}
* directory = filepath . Join ( pwd , * directory )
2021-09-20 02:22:52 -04:00
}
2026-05-29 23:04:59 +08:00
switch * action {
case "format" :
isFormat = true
case "check" :
isCheck = true
case "dryrun" :
isCheck = true
isDryrun = true
default :
fmt . Println ( "Unrecognized 'mode'. Will format all source files and save changes." )
isFormat = true
}
2021-09-27 01:45:02 -04:00
pwd := * directory
2021-09-20 02:22:52 -04:00
GOBIN := GetGOBIN ( )
binPath := os . Getenv ( "PATH" )
pathSlice := [ ] string { pwd , GOBIN , binPath }
binPath = strings . Join ( pathSlice , string ( os . PathListSeparator ) )
os . Setenv ( "PATH" , binPath )
suffix := ""
if runtime . GOOS == "windows" {
suffix = ".exe"
}
2022-05-18 15:26:34 +08:00
gofmt := "gofumpt" + suffix
2021-09-20 02:22:52 -04:00
if gofmtPath , err := exec . LookPath ( gofmt ) ; err != nil {
fmt . Println ( "Can not find" , gofmt , "in system path or current working directory." )
os . Exit ( 1 )
} else {
gofmt = gofmtPath
}
2021-10-12 11:29:22 -04:00
rawFilesSlice := make ( [ ] string , 0 , 1000 )
2021-09-27 01:45:02 -04:00
walkErr := filepath . Walk ( pwd , func ( path string , info os . FileInfo , err error ) error {
2021-09-20 02:22:52 -04:00
if err != nil {
fmt . Println ( err )
return err
}
if info . IsDir ( ) {
return nil
}
dir := filepath . Dir ( path )
filename := filepath . Base ( path )
if strings . HasSuffix ( filename , ".go" ) &&
! strings . HasSuffix ( filename , ".pb.go" ) &&
2021-10-12 11:29:22 -04:00
! strings . Contains ( dir , filepath . Join ( "testing" , "mocks" ) ) &&
! strings . Contains ( path , filepath . Join ( "main" , "distro" , "all" , "all.go" ) ) {
2021-09-20 02:22:52 -04:00
rawFilesSlice = append ( rawFilesSlice , path )
}
return nil
} )
if walkErr != nil {
fmt . Println ( walkErr )
os . Exit ( 1 )
}
2026-05-29 23:04:59 +08:00
if isFormat {
gofmtArgs := [ ] string {
"-l" , "-e" , "-w" ,
}
2021-09-20 02:22:52 -04:00
2026-05-29 23:04:59 +08:00
fmt . Println ( "Formatting Go source files..." )
RunMany ( gofmt , gofmtArgs , rawFilesSlice )
fmt . Println ( "Do NOT forget to commit file changes." )
2021-09-20 02:22:52 -04:00
}
2026-05-29 23:04:59 +08:00
if isCheck {
gofmtListArgs := [ ] string {
"-l" , "-e" ,
}
fmt . Println ( "Checking files thar are not properly formatted..." )
formatRequired := RunMany ( gofmt , gofmtListArgs , rawFilesSlice )
if formatRequired {
fmt . Println ( "Format problem(s) found." )
}
if isDryrun {
if formatRequired {
gofmtShowArgs := [ ] string {
"-d" , "-e" ,
}
RunMany ( gofmt , gofmtShowArgs , rawFilesSlice )
}
}
if formatRequired {
fmt . Println ( "Please run 'go install -v mvdan.cc/gofumpt@latest', then run 'go run ./infra/vformat/main.go' to format the Go source files." )
os . Exit ( 1 )
} else {
fmt . Println ( "All Go source file format check has been passed." )
}
}
2021-09-20 02:22:52 -04:00
}