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  }