github.com/btwiuse/jiri@v0.0.0-20191125065820-53353bcfef54/cmdline/testdata/gendoc.go (about) 1 // Copyright 2015 The Vanadium 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 // Command gendoc generates godoc comments describing the usage of tools based 6 // on the cmdline package. 7 // 8 // Usage: 9 // go run gendoc.go [flags] <pkg> [args] 10 // 11 // <pkg> is the package path for the tool. 12 // 13 // [args] are the arguments to pass to the tool to produce usage output. If no 14 // args are given, runs "<tool> help ..." 15 // 16 // The reason this command is located under a testdata directory is to enforce 17 // its idiomatic use via "go run". 18 // 19 // The gendoc command itself is not based on the cmdline library to avoid 20 // non-trivial bootstrapping. In particular, if the compilation of gendoc 21 // requires GOPATH to contain the vanadium Go workspaces, then running the 22 // gendoc command requires the jiri tool, which in turn may depend on the gendoc 23 // command. 24 package main 25 26 import ( 27 "bytes" 28 "flag" 29 "fmt" 30 "io/ioutil" 31 "os" 32 "os/exec" 33 "path/filepath" 34 "regexp" 35 "strings" 36 ) 37 38 var ( 39 flagEnv string 40 flagInstall string 41 flagOut string 42 flagTags string 43 ) 44 45 func main() { 46 flag.StringVar(&flagEnv, "env", "os", `Environment variables to set before running command. If "os", grabs vars from the underlying OS. If empty, doesn't set any vars. Otherwise vars are expected to be comma-separated entries of the form KEY1=VALUE1,KEY2=VALUE2,...`) 47 flag.StringVar(&flagInstall, "install", "", "Comma separated list of packages to install before running command. All commands that are built will be on the PATH.") 48 flag.StringVar(&flagOut, "out", "./doc.go", "Path to the output file.") 49 flag.StringVar(&flagTags, "tags", "", "Tags for go build, also added as build constraints in the generated output file.") 50 flag.Parse() 51 if err := generate(flag.Args()); err != nil { 52 fmt.Fprintln(os.Stderr, err) 53 os.Exit(1) 54 } 55 } 56 57 func generate(args []string) error { 58 if got, want := len(args), 1; got < want { 59 return fmt.Errorf("gendoc requires at least one argument\nusage: gendoc <pkg> [args]") 60 } 61 pkg, args := args[0], args[1:] 62 63 // Find out the binary name from the pkg name. 64 var listOut bytes.Buffer 65 listCmd := exec.Command("go", "list", pkg) 66 listCmd.Stdout = &listOut 67 if err := listCmd.Run(); err != nil { 68 return fmt.Errorf("%q failed: %v\n%v\n", strings.Join(listCmd.Args, " "), err, listOut.String()) 69 } 70 binName := filepath.Base(strings.TrimSpace(listOut.String())) 71 72 // Install all packages in a temporary directory. 73 tmpDir, err := ioutil.TempDir("", "") 74 if err != nil { 75 return fmt.Errorf("TempDir() failed: %v", err) 76 } 77 defer os.RemoveAll(tmpDir) 78 pkgs := []string{pkg} 79 if flagInstall != "" { 80 pkgs = append(pkgs, strings.Split(flagInstall, ",")...) 81 } 82 for _, installPkg := range pkgs { 83 installArgs := []string{"go", "install", "-tags=" + flagTags, installPkg} 84 installCmd := exec.Command("jiri", installArgs...) 85 installCmd.Env = append(os.Environ(), "GOBIN="+tmpDir) 86 if err := installCmd.Run(); err != nil { 87 return fmt.Errorf("%q failed: %v\n", strings.Join(installCmd.Args, " "), err) 88 } 89 } 90 91 // Run the binary to generate documentation. 92 var out bytes.Buffer 93 if len(args) == 0 { 94 args = []string{"help", "..."} 95 } 96 runCmd := exec.Command(filepath.Join(tmpDir, binName), args...) 97 runCmd.Stdout = &out 98 runCmd.Env = runEnviron(tmpDir) 99 if err := runCmd.Run(); err != nil { 100 return fmt.Errorf("%q failed: %v\n%v\n", strings.Join(runCmd.Args, " "), err, out.String()) 101 } 102 var tagsConstraint string 103 if flagTags != "" { 104 tagsConstraint = fmt.Sprintf("// +build %s\n\n", flagTags) 105 } 106 doc := fmt.Sprintf(`// Copyright 2015 The Vanadium Authors. All rights reserved. 107 // Use of this source code is governed by a BSD-style 108 // license that can be found in the LICENSE file. 109 110 // This file was auto-generated via go generate. 111 // DO NOT UPDATE MANUALLY 112 113 %s/* 114 %s*/ 115 package main 116 `, tagsConstraint, suppressParallelFlag(out.String())) 117 118 // Write the result to the output file. 119 path, perm := flagOut, os.FileMode(0644) 120 if err := ioutil.WriteFile(path, []byte(doc), perm); err != nil { 121 return fmt.Errorf("WriteFile(%v, %v) failed: %v\n", path, perm, err) 122 } 123 return nil 124 } 125 126 // suppressParallelFlag replaces the default value of the test.parallel flag 127 // with the literal string "<number of threads>". The default value of the 128 // test.parallel flag is GOMAXPROCS, which (since Go1.5) is set to the number 129 // of logical CPU threads on the current system. This causes problems with the 130 // vanadium-go-generate test, which requires that the output of gendoc is the 131 // same on all systems. 132 func suppressParallelFlag(input string) string { 133 pattern := regexp.MustCompile("(?m:(^ -test\\.parallel=)(?:\\d)+$)") 134 return pattern.ReplaceAllString(input, "$1<number of threads>") 135 } 136 137 // runEnviron returns the environment variables to use when running the command 138 // to retrieve full help information. 139 func runEnviron(binDir string) []string { 140 // Never return nil, which signals exec.Command to use os.Environ. 141 in, out := strings.Split(flagEnv, ","), make([]string, 0) 142 if flagEnv == "os" { 143 in = os.Environ() 144 } 145 updatedPath := false 146 for _, e := range in { 147 if e == "" { 148 continue 149 } 150 if strings.HasPrefix(e, "PATH=") { 151 e = "PATH=" + binDir + string(os.PathListSeparator) + e[5:] 152 updatedPath = true 153 } 154 out = append(out, e) 155 } 156 if !updatedPath { 157 out = append(out, "PATH="+binDir) 158 } 159 out = append(out, "CMDLINE_STYLE=godoc") 160 return out 161 }