kythe.io@v0.0.68-0.20240422202219-7225dbc01741/kythe/go/extractors/cmd/gotool/gotool.go (about)

     1  /*
     2   * Copyright 2015 The Kythe Authors. All rights reserved.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *   http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  // Binary gotool extracts Kythe compilation information for Go packages named
    18  // by import path on the command line.  The output compilations are written
    19  // into a kzip.
    20  package main
    21  
    22  import (
    23  	"bytes"
    24  	"context"
    25  	"flag"
    26  	"fmt"
    27  	"go/build"
    28  	"os"
    29  	"path/filepath"
    30  	"strings"
    31  
    32  	"kythe.io/kythe/go/extractors/golang"
    33  	"kythe.io/kythe/go/platform/analysis"
    34  	"kythe.io/kythe/go/platform/kzip"
    35  	"kythe.io/kythe/go/platform/vfs"
    36  	"kythe.io/kythe/go/util/flagutil"
    37  	"kythe.io/kythe/go/util/log"
    38  	"kythe.io/kythe/go/util/vnameutil"
    39  	apb "kythe.io/kythe/proto/analysis_go_proto"
    40  )
    41  
    42  var (
    43  	bc = build.Default // A shallow copy of the default build settings
    44  
    45  	corpus     = flag.String("corpus", "", "Default corpus name to use")
    46  	rulesFile  = flag.String("rules", "", "Path to vnames.json file that maps file paths to output corpus, root, and path.")
    47  	outputPath = flag.String("output", "", "KZip output path")
    48  	extraFiles = flag.String("extra_files", "", "Additional files to include in each compilation (CSV)")
    49  	byDir      = flag.Bool("bydir", false, "Import by directory rather than import path")
    50  	keepGoing  = flag.Bool("continue", false, "Continue past errors")
    51  	verbose    = flag.Bool("v", false, "Enable verbose logging")
    52  
    53  	canonicalizePackageCorpus = flag.Bool("canonicalize_package_corpus", false, "Whether to use a package's canonical repository root URL as their corpus")
    54  	useDefaultCorpusForStdLib = flag.Bool("use_default_corpus_for_stdlib", false, "By default, go stdlib files are given the 'golang.org' corpus. If this flag is enabled, they will instead be assigned the corpus from the --corpus flag.")
    55  	useDefaultCorpusForDeps   = flag.Bool("use_default_corpus_for_deps", false, "By default, imported modules are assigned a corpus based on their import path. If this flag is enabled, they will instead be assigned the corpus from the --corpus flag and a root corresponding the their import path.")
    56  
    57  	buildTags flagutil.StringList
    58  )
    59  
    60  func init() {
    61  	flag.Usage = func() {
    62  		fmt.Fprintf(os.Stderr, `Usage: %s [options] <import-path>...
    63  Extract Kythe compilation records from Go import paths specified on the command line.
    64  Output is written to a .kzip file specified by --output.
    65  
    66  Options:
    67  `, filepath.Base(os.Args[0]))
    68  		flag.PrintDefaults()
    69  	}
    70  
    71  	// Attach flags to the various parts of the go/build context we are using.
    72  	// These will override the system defaults from the environment.
    73  	flag.StringVar(&bc.GOARCH, "goarch", bc.GOARCH, "Go system architecture tag")
    74  	flag.StringVar(&bc.GOOS, "goos", bc.GOOS, "Go operating system tag")
    75  	flag.StringVar(&bc.GOPATH, "gopath", bc.GOPATH, "Go library path")
    76  	flag.StringVar(&bc.GOROOT, "goroot", bc.GOROOT, "Go system root")
    77  	flag.BoolVar(&bc.CgoEnabled, "gocgo", bc.CgoEnabled, "Whether to allow cgo")
    78  	flag.StringVar(&bc.Compiler, "gocompiler", bc.Compiler, "Which Go compiler to use")
    79  	flag.Var(&buildTags, "buildtags", "Comma-separated list of Go +build tags to enable during extraction.")
    80  
    81  	// TODO(fromberger): Attach flags to the build and release tags (maybe).
    82  }
    83  
    84  func maybeFatal(msg string, args ...any) {
    85  	log.Errorf(msg, args...)
    86  	if !*keepGoing {
    87  		os.Exit(1)
    88  	}
    89  }
    90  
    91  func maybeLog(msg string, args ...any) {
    92  	if *verbose {
    93  		log.Infof(msg, args...)
    94  	}
    95  }
    96  
    97  func main() {
    98  	flag.Parse()
    99  
   100  	bc.BuildTags = buildTags
   101  
   102  	if *outputPath == "" {
   103  		log.Fatal("You must provide a non-empty --output path")
   104  	}
   105  
   106  	// Rules for rewriting package and file VNames.
   107  	var rules vnameutil.Rules
   108  	if *rulesFile != "" {
   109  		var err error
   110  		rules, err = vnameutil.LoadRules(*rulesFile)
   111  		if err != nil {
   112  			log.Fatalf("loading rules file: %v", err)
   113  		}
   114  	}
   115  
   116  	ctx := context.Background()
   117  	ext := &golang.Extractor{
   118  		BuildContext: bc,
   119  
   120  		PackageVNameOptions: golang.PackageVNameOptions{
   121  			DefaultCorpus:             *corpus,
   122  			Rules:                     rules,
   123  			CanonicalizePackageCorpus: *canonicalizePackageCorpus,
   124  			RootDirectory:             os.Getenv("KYTHE_ROOT_DIRECTORY"),
   125  			UseDefaultCorpusForStdLib: *useDefaultCorpusForStdLib,
   126  			UseDefaultCorpusForDeps:   *useDefaultCorpusForDeps,
   127  		},
   128  	}
   129  	if *extraFiles != "" {
   130  		ext.ExtraFiles = strings.Split(*extraFiles, ",")
   131  		for i, path := range ext.ExtraFiles {
   132  			var err error
   133  			ext.ExtraFiles[i], err = filepath.Abs(path)
   134  			if err != nil {
   135  				log.Fatalf("Error finding absolute path of %s: %v", path, err)
   136  			}
   137  		}
   138  	}
   139  
   140  	locate := ext.Locate
   141  	if *byDir {
   142  		locate = func(path string) ([]*golang.Package, error) {
   143  			pkg, err := ext.ImportDir(path)
   144  			if err != nil {
   145  				return nil, err
   146  			}
   147  			return []*golang.Package{pkg}, nil
   148  		}
   149  	}
   150  	for _, path := range flag.Args() {
   151  		pkgs, err := locate(path)
   152  		if err != nil {
   153  			maybeFatal("Error locating %q: %v", path, err)
   154  		}
   155  		for _, pkg := range pkgs {
   156  			maybeLog("Found %q in %s", pkg.Path, pkg.BuildPackage.Dir)
   157  		}
   158  	}
   159  
   160  	if err := ext.Extract(); err != nil {
   161  		maybeFatal("Error in extraction: %v", err)
   162  	}
   163  
   164  	maybeLog("Writing %d package(s) to %q", len(ext.Packages), *outputPath)
   165  	w, err := kzipWriter(ctx, *outputPath)
   166  	if err != nil {
   167  		maybeFatal("Error creating kzip writer: %v", err)
   168  	}
   169  	for _, pkg := range ext.Packages {
   170  		maybeLog("Package %q:\n\t// %s", pkg.Path, pkg.BuildPackage.Doc)
   171  		if err := pkg.EachUnit(ctx, func(cu *apb.CompilationUnit, fetcher analysis.Fetcher) error {
   172  			if _, err := w.AddUnit(cu, nil); err != nil {
   173  				return err
   174  			}
   175  			for _, ri := range cu.RequiredInput {
   176  				fd, err := fetcher.Fetch(ri.Info.Path, ri.Info.Digest)
   177  				if err != nil {
   178  					return err
   179  				}
   180  				if _, err := w.AddFile(bytes.NewReader(fd)); err != nil {
   181  					return err
   182  				}
   183  			}
   184  			return nil
   185  		}); err != nil {
   186  			maybeFatal("Error writing %q: %v", pkg.Path, err)
   187  		}
   188  	}
   189  	if err := w.Close(); err != nil {
   190  		maybeFatal("Error closing output: %v", err)
   191  	}
   192  }
   193  
   194  func kzipWriter(ctx context.Context, path string) (*kzip.Writer, error) {
   195  	if err := vfs.MkdirAll(ctx, filepath.Dir(path), 0755); err != nil {
   196  		log.Fatalf("Unable to create output directory: %v", err)
   197  	}
   198  	f, err := vfs.Create(ctx, path)
   199  	if err != nil {
   200  		log.Fatalf("Unable to create output file: %v", err)
   201  	}
   202  	return kzip.NewWriteCloser(f)
   203  }