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 }