kythe.io@v0.0.68-0.20240422202219-7225dbc01741/kythe/go/platform/tools/kzip/createcmd/createcmd.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 createcmd provides the kzip command for creating simple kzip archives. 18 package createcmd // import "kythe.io/kythe/go/platform/tools/kzip/createcmd" 19 20 import ( 21 "context" 22 "flag" 23 "os" 24 "path/filepath" 25 26 "kythe.io/kythe/go/platform/kzip" 27 "kythe.io/kythe/go/platform/tools/kzip/flags" 28 "kythe.io/kythe/go/platform/vfs" 29 "kythe.io/kythe/go/util/cmdutil" 30 "kythe.io/kythe/go/util/flagutil" 31 "kythe.io/kythe/go/util/vnameutil" 32 33 "github.com/google/subcommands" 34 35 anypb "github.com/golang/protobuf/ptypes/any" 36 apb "kythe.io/kythe/proto/analysis_go_proto" 37 spb "kythe.io/kythe/proto/storage_go_proto" 38 ) 39 40 type createCommand struct { 41 cmdutil.Info 42 43 output string 44 rules vnameRules 45 46 uri kytheURI 47 source flagutil.StringSet 48 inputs flagutil.StringSet 49 hasError bool 50 argument repeatedString 51 outputKey string 52 workingDir string 53 entryContext string 54 environment repeatedEnv 55 details repeatedAny 56 encoding flags.EncodingFlag 57 } 58 59 // New creates a new subcommand for merging kzip files. 60 func New() subcommands.Command { 61 return &createCommand{ 62 Info: cmdutil.NewInfo("create", "create simple kzip archives", `[options] -- arguments* 63 64 Construct a kzip file written to -output with the vname specified by -uri. 65 Each of -source_file, -required_input and -details may be specified multiple times with each 66 occurrence of the flag being appended to the corresponding field in the compilation unit. 67 Directories specified in -source_file or -required_input will be added recursively. 68 69 Any additional positional arguments are included as arguments in the compilation unit. 70 `), 71 encoding: flags.EncodingFlag{Encoding: kzip.EncodingJSON}, 72 } 73 } 74 75 // SetFlags implements the subcommands interface and provides command-specific flags 76 // for creating kzip files. 77 func (c *createCommand) SetFlags(fs *flag.FlagSet) { 78 fs.StringVar(&c.output, "output", "", "Path for output kzip file (required)") 79 fs.Var(&c.rules, "rules", "Path to vnames.json file (optional)") 80 81 fs.Var(&c.uri, "uri", "A Kythe URI naming the compilation unit VName (required)") 82 fs.Var(&c.source, "source_file", "Repeated paths for input source files or directories (required)") 83 fs.Var(&c.inputs, "required_input", "Repeated paths for additional required inputs (optional)") 84 fs.BoolVar(&c.hasError, "has_compile_errors", false, "Whether this unit had compilation errors (optional)") 85 fs.Var(&c.argument, "argument", "Repeated arguments to add to compilation unit (optional)") 86 fs.StringVar(&c.outputKey, "output_key", "", "Name by which the output of this compilation is known to dependents (optional)") 87 fs.StringVar(&c.workingDir, "working_directory", "", "Absolute path of the directory from which the build tool was invoked (optional)") 88 fs.StringVar(&c.entryContext, "entry_context", "", "Language-specific context to provide the indexer (optional)") 89 fs.Var(&c.environment, "env", "Repeated KEY=VALUE pairs of environment variables to add to the compilation unit (optional)") 90 fs.Var(&c.details, "details", "Repeated JSON-encoded Any messages to embed as compilation details (optional)") 91 fs.Var(&c.encoding, "encoding", "Encoding to use on output, one of JSON, PROTO, or ALL") 92 } 93 94 // Execute implements the subcommands interface and creates the requested file. 95 func (c *createCommand) Execute(ctx context.Context, fs *flag.FlagSet, _ ...any) subcommands.ExitStatus { 96 switch { 97 case c.uri.Corpus == "": 98 return c.Fail("Missing required -uri") 99 case c.output == "": 100 return c.Fail("Missing required -output") 101 case c.source.Len() == 0: 102 return c.Fail("Missing required -source_file") 103 } 104 105 opt := kzip.WithEncoding(c.encoding.Encoding) 106 out, err := openWriter(ctx, c.output, opt) 107 if err != nil { 108 return c.Fail("Error opening -output: %v", err) 109 } 110 111 // Create a new compilation populating its VName with the values specified 112 // within the specified Kythe URI. 113 cb := compilationBuilder{&apb.CompilationUnit{ 114 VName: &spb.VName{ 115 Corpus: c.uri.Corpus, 116 Language: c.uri.Language, 117 Signature: c.uri.Signature, 118 Root: c.uri.Root, 119 Path: c.uri.Path, 120 }, 121 HasCompileErrors: c.hasError, 122 Argument: append(c.argument, fs.Args()...), 123 OutputKey: c.outputKey, 124 WorkingDirectory: c.workingDir, 125 EntryContext: c.entryContext, 126 Environment: c.environment.ToProto(), 127 Details: ([]*anypb.Any)(c.details), 128 }, out, &c.rules.Rules} 129 130 sources, err := cb.addFiles(ctx, c.source.Elements()) 131 if err != nil { 132 return c.Fail("Error adding source files: %v", err) 133 } 134 cb.unit.SourceFile = sources 135 136 if _, err = cb.addFiles(ctx, c.inputs.Elements()); err != nil { 137 return c.Fail("Error adding input files: %v", err) 138 } 139 140 if err := cb.done(); err != nil { 141 return c.Fail("Error writing compilation to -output: %v", err) 142 } 143 return subcommands.ExitSuccess 144 } 145 146 func openWriter(ctx context.Context, path string, opts ...kzip.WriterOption) (*kzip.Writer, error) { 147 out, err := vfs.Create(ctx, path) 148 if err != nil { 149 return nil, err 150 } 151 return kzip.NewWriteCloser(out, opts...) 152 } 153 154 type compilationBuilder struct { 155 unit *apb.CompilationUnit 156 out *kzip.Writer 157 rules *vnameutil.Rules 158 } 159 160 // addFiles adds the given files as required input. 161 // If the path is a directory, its contents are added recursively. 162 // Returns the paths of the non-directory files added. 163 func (cb *compilationBuilder) addFiles(ctx context.Context, paths []string) ([]string, error) { 164 var files []string 165 for _, path := range paths { 166 f, err := cb.addFile(ctx, path) 167 if err != nil { 168 return files, err 169 } 170 files = append(files, f...) 171 } 172 return files, nil 173 } 174 175 // addFile adds the given file as a required input. 176 // If the path is a directory, its contents are added recursively. 177 // Returns the paths of the non-directory files added. 178 func (cb *compilationBuilder) addFile(ctx context.Context, root string) ([]string, error) { 179 var files []string 180 err := vfs.Walk(ctx, root, func(path string, info os.FileInfo, err error) error { 181 if err != nil || info.IsDir() { 182 return err 183 } 184 input, err := vfs.Open(ctx, path) 185 if err != nil { 186 return err 187 } 188 defer input.Close() 189 190 digest, err := cb.out.AddFile(input) 191 if err != nil { 192 return err 193 } 194 195 path = cb.tryMakeRelative(path) 196 vname, ok := cb.rules.Apply(path) 197 if !ok { 198 vname = &spb.VName{ 199 Corpus: cb.unit.VName.Corpus, 200 Root: cb.unit.VName.Root, 201 Path: path, 202 } 203 } else if vname.Corpus == "" { 204 vname.Corpus = cb.unit.VName.Corpus 205 206 } 207 cb.unit.RequiredInput = append(cb.unit.RequiredInput, &apb.CompilationUnit_FileInput{ 208 VName: vname, 209 Info: &apb.FileInfo{ 210 Path: path, 211 Digest: digest, 212 }, 213 }) 214 files = append(files, path) 215 return nil 216 }) 217 return files, err 218 } 219 220 func (cb *compilationBuilder) done() error { 221 _, err := cb.out.AddUnit(cb.unit, nil) 222 if err != nil { 223 return err 224 } 225 cb.unit = nil 226 return cb.out.Close() 227 } 228 229 // tryeMakeRelative attempts to relativize path against unit.WorkingDirectory or CWD, 230 // returning path unmodified on failure. 231 func (cb *compilationBuilder) tryMakeRelative(path string) string { 232 abs, err := filepath.Abs(path) 233 if err != nil { 234 return path 235 } 236 var dir string 237 if cb.unit.WorkingDirectory != "" { 238 dir = cb.unit.WorkingDirectory 239 } else { 240 dir, err = filepath.Abs(".") 241 if err != nil { 242 return path 243 } 244 } 245 rel, err := filepath.Rel(dir, abs) 246 if err != nil { 247 return path 248 } 249 return rel 250 251 }