kythe.io@v0.0.68-0.20240422202219-7225dbc01741/kythe/go/platform/tools/kzip/viewcmd/viewcmd.go (about) 1 /* 2 * Copyright 2019 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 // Package viewcmd displays the contents of compilation units stored in .kzip files. 18 package viewcmd // import "kythe.io/kythe/go/platform/tools/kzip/viewcmd" 19 20 import ( 21 "context" 22 "flag" 23 "fmt" 24 "io/ioutil" 25 "os" 26 "path/filepath" 27 "strings" 28 "time" 29 30 "kythe.io/kythe/go/platform/kzip" 31 "kythe.io/kythe/go/platform/vfs" 32 "kythe.io/kythe/go/util/cmdutil" 33 "kythe.io/kythe/go/util/log" 34 35 "github.com/google/subcommands" 36 "golang.org/x/sync/errgroup" 37 "golang.org/x/sync/semaphore" 38 "google.golang.org/protobuf/encoding/protojson" 39 "google.golang.org/protobuf/proto" 40 ) 41 42 type cmd struct { 43 cmdutil.Info 44 extractDir string 45 } 46 47 // New returns an implementation of the "view" subcommand. 48 func New() subcommands.Command { 49 const usage = `Usage: view [options] <file-path>... 50 51 Print or extract compilation units stored in .kzip files. By default, the 52 compilation unit is printed to stdout in JSON format. 53 54 With -extract the compilation record and the full contents of all the required 55 input files are extracted into the named directory, preserving the path 56 structure specified in the compilation record.` 57 58 return &cmd{ 59 Info: cmdutil.NewInfo("view", "view compilation units stored in .kzip files", usage), 60 } 61 } 62 63 // SetFlags implements part of subcommands.Command. 64 func (c *cmd) SetFlags(fs *flag.FlagSet) { 65 fs.StringVar(&c.extractDir, "extract", "", "Extract files to this directory") 66 } 67 68 // Execute implements part of subcommands.Command. 69 func (c *cmd) Execute(ctx context.Context, fs *flag.FlagSet, args ...any) subcommands.ExitStatus { 70 var hasErrors bool 71 for _, path := range fs.Args() { 72 ext := filepath.Ext(path) 73 base := filepath.Base(strings.TrimSuffix(path, ext)) 74 75 f, err := vfs.Open(ctx, path) 76 if err != nil { 77 log.ErrorContextf(ctx, "opening .kzip file: %v", err) 78 hasErrors = true 79 continue 80 } 81 defer f.Close() 82 83 err = kzip.Scan(f, func(r *kzip.Reader, unit *kzip.Unit) error { 84 if err := c.writeUnit(base+"-"+unit.Digest, unit.Proto); err != nil { 85 return fmt.Errorf("writing unit: %v", err) 86 } else if fd, err := c.kzipFiles(r, unit); err != nil { 87 return fmt.Errorf("extracting files: %v", err) 88 } else { 89 return c.writeFiles(fd) 90 } 91 }) 92 if err != nil { 93 log.ErrorContextf(ctx, "scanning .kzip file: %v", err) 94 hasErrors = true 95 } 96 } 97 if hasErrors { 98 return subcommands.ExitFailure 99 } 100 return subcommands.ExitSuccess 101 } 102 103 var marshaler = &protojson.MarshalOptions{UseProtoNames: true} 104 105 func (c *cmd) writeUnit(base string, msg proto.Message) error { 106 if c.extractDir == "" { 107 rec, err := marshaler.Marshal(msg) 108 if err != nil { 109 return err 110 } 111 _, err = os.Stdout.Write(rec) 112 if err == nil { 113 fmt.Println() 114 } 115 return err 116 } 117 if err := os.MkdirAll(c.extractDir, 0750); err != nil { 118 return fmt.Errorf("creating output directory: %v", err) 119 } 120 f, err := os.Create(filepath.Join(c.extractDir, base+".unit")) 121 if err != nil { 122 return err 123 } 124 rec, err := marshaler.Marshal(msg) 125 if err != nil { 126 f.Close() 127 return err 128 } 129 _, err = f.Write(rec) 130 cerr := f.Close() 131 if err != nil { 132 return err 133 } 134 return cerr 135 } 136 137 func (c *cmd) writeFiles(fd []fileData) error { 138 if c.extractDir == "" { 139 return nil 140 } 141 g, ctx := errgroup.WithContext(context.Background()) 142 start := time.Now() 143 defer func() { log.Infof("Extracted %d files in %v", len(fd), time.Since(start)) }() 144 sem := semaphore.NewWeighted(64) // limit concurrency on large compilations 145 for _, file := range fd { 146 file := file 147 g.Go(func() error { 148 sem.Acquire(ctx, 1) 149 defer sem.Release(1) 150 151 path := filepath.Join(c.extractDir, file.path) 152 dir := filepath.Dir(path) 153 if err := os.MkdirAll(dir, 0750); err != nil { 154 return err 155 } 156 return ioutil.WriteFile(path, file.data, 0644) 157 }) 158 } 159 return g.Wait() 160 } 161 162 type fileData struct { 163 path string 164 data []byte 165 } 166 167 // kzipFiles extracts the file paths and contents from a kzip compilation. 168 func (c *cmd) kzipFiles(r *kzip.Reader, unit *kzip.Unit) ([]fileData, error) { 169 out := make([]fileData, len(unit.Proto.RequiredInput)) 170 for i, ri := range unit.Proto.RequiredInput { 171 info := ri.GetInfo() 172 out[i].path = info.GetPath() 173 data, err := r.ReadAll(info.GetDigest()) 174 if err != nil { 175 return nil, err 176 } 177 out[i].data = data 178 } 179 return out, nil 180 }