|
| 1 | +package main |
| 2 | + |
| 3 | +import ( |
| 4 | + "errors" |
| 5 | + "flag" |
| 6 | + "fmt" |
| 7 | + "os" |
| 8 | + "strings" |
| 9 | + |
| 10 | + "github.com/sourcegraph/sourcegraph/lib/codeintel/upload" |
| 11 | + "github.com/sourcegraph/src-cli/internal/codeintel" |
| 12 | +) |
| 13 | + |
| 14 | +var lsifUploadFlags struct { |
| 15 | + file string |
| 16 | + |
| 17 | + // UploadRecordOptions |
| 18 | + repo string |
| 19 | + commit string |
| 20 | + root string |
| 21 | + indexer string |
| 22 | + associatedIndexID int |
| 23 | + |
| 24 | + // SourcegraphInstanceOptions |
| 25 | + uploadRoute string |
| 26 | + gitHubToken string |
| 27 | + maxPayloadSizeMb int64 |
| 28 | + |
| 29 | + // Output and error behavior |
| 30 | + ignoreUploadFailures bool |
| 31 | + noProgress bool |
| 32 | + verbosity int |
| 33 | + json bool |
| 34 | + open bool |
| 35 | +} |
| 36 | + |
| 37 | +var lsifUploadFlagSet = flag.NewFlagSet("upload", flag.ExitOnError) |
| 38 | + |
| 39 | +func init() { |
| 40 | + lsifUploadFlagSet.StringVar(&lsifUploadFlags.file, "file", "./dump.lsif", `The path to the LSIF dump file.`) |
| 41 | + |
| 42 | + // UploadRecordOptions |
| 43 | + lsifUploadFlagSet.StringVar(&lsifUploadFlags.repo, "repo", "", `The name of the repository (e.g. github.com/gorilla/mux). By default, derived from the origin remote.`) |
| 44 | + lsifUploadFlagSet.StringVar(&lsifUploadFlags.commit, "commit", "", `The 40-character hash of the commit. Defaults to the currently checked-out commit.`) |
| 45 | + lsifUploadFlagSet.StringVar(&lsifUploadFlags.root, "root", "", `The path in the repository that matches the LSIF projectRoot (e.g. cmd/project1). Defaults to the directory where the dump file is located.`) |
| 46 | + lsifUploadFlagSet.StringVar(&lsifUploadFlags.indexer, "indexer", "", `The name of the indexer that generated the dump. This will override the 'toolInfo.name' field in the metadata vertex of the LSIF dump file. This must be supplied if the indexer does not set this field (in which case the upload will fail with an explicit message).`) |
| 47 | + lsifUploadFlagSet.IntVar(&lsifUploadFlags.associatedIndexID, "associated-index-id", -1, "ID of the associated index record for this upload. For internal use only.") |
| 48 | + |
| 49 | + // SourcegraphInstanceOptions |
| 50 | + lsifUploadFlagSet.StringVar(&lsifUploadFlags.uploadRoute, "upload-route", "/.api/lsif/upload", "The path of the upload route. For internal use only.") |
| 51 | + lsifUploadFlagSet.StringVar(&lsifUploadFlags.gitHubToken, "github-token", "", `A GitHub access token with 'public_repo' scope that Sourcegraph uses to verify you have access to the repository.`) |
| 52 | + lsifUploadFlagSet.Int64Var(&lsifUploadFlags.maxPayloadSizeMb, "max-payload-size", 100, `The maximum upload size (in megabytes). Indexes exceeding this limit will be uploaded over multiple HTTP requests.`) |
| 53 | + |
| 54 | + // Output and error behavior |
| 55 | + lsifUploadFlagSet.BoolVar(&lsifUploadFlags.ignoreUploadFailures, "ignore-upload-failure", false, `Exit with status code zero on upload failure.`) |
| 56 | + lsifUploadFlagSet.BoolVar(&lsifUploadFlags.noProgress, "no-progress", false, `Do not display progress updates.`) |
| 57 | + lsifUploadFlagSet.IntVar(&lsifUploadFlags.verbosity, "trace", 0, "-trace=0 shows no logs; -trace=1 shows requests and response metadata; -trace=2 shows headers, -trace=3 shows response body") |
| 58 | + lsifUploadFlagSet.BoolVar(&lsifUploadFlags.json, "json", false, `Output relevant state in JSON on success.`) |
| 59 | + lsifUploadFlagSet.BoolVar(&lsifUploadFlags.open, "open", false, `Open the LSIF upload page in your browser.`) |
| 60 | +} |
| 61 | + |
| 62 | +// parseAndValidateLSIFUploadFlags calls lsifUploadFlagSet.Parse, then infers values for |
| 63 | +// missing flags, normalizes supplied values, and validates the state of the lsifUploadFlags |
| 64 | +// object. |
| 65 | +// |
| 66 | +// On success, the global lsifUploadFlags object will be populated with valid values. An |
| 67 | +// error is returned on failure. |
| 68 | +func parseAndValidateLSIFUploadFlags(args []string) error { |
| 69 | + if err := lsifUploadFlagSet.Parse(args); err != nil { |
| 70 | + return err |
| 71 | + } |
| 72 | + |
| 73 | + if inferenceErrors := inferMissingLSIFUploadFlags(); len(inferenceErrors) > 0 { |
| 74 | + return errorWithHint{ |
| 75 | + err: inferenceErrors[0].err, hint: strings.Join([]string{ |
| 76 | + fmt.Sprintf( |
| 77 | + "Unable to determine %s from environment. Check your working directory or supply -%s={value} explicitly", |
| 78 | + inferenceErrors[0].argument, |
| 79 | + inferenceErrors[0].argument, |
| 80 | + ), |
| 81 | + }, "\n"), |
| 82 | + } |
| 83 | + } |
| 84 | + |
| 85 | + if err := validateLSIFUploadFlags(); err != nil { |
| 86 | + return err |
| 87 | + } |
| 88 | + |
| 89 | + return nil |
| 90 | +} |
| 91 | + |
| 92 | +type argumentInferenceError struct { |
| 93 | + argument string |
| 94 | + err error |
| 95 | +} |
| 96 | + |
| 97 | +// inferMissingLSIFUploadFlags updates the flags values which were not explicitly |
| 98 | +// supplied by the user with default values inferred from the current git state and |
| 99 | +// filesystem. |
| 100 | +// |
| 101 | +// Note: This function must not be called before lsifUploadFlagSet.Parse. |
| 102 | +func inferMissingLSIFUploadFlags() (inferErrors []argumentInferenceError) { |
| 103 | + if _, err := os.Stat(lsifUploadFlags.file); os.IsNotExist(err) { |
| 104 | + inferErrors = append(inferErrors, argumentInferenceError{"file", err}) |
| 105 | + } |
| 106 | + |
| 107 | + if err := inferUnsetFlag("repo", &lsifUploadFlags.repo, codeintel.InferRepo); err != nil { |
| 108 | + inferErrors = append(inferErrors, *err) |
| 109 | + } |
| 110 | + if err := inferUnsetFlag("commit", &lsifUploadFlags.commit, codeintel.InferCommit); err != nil { |
| 111 | + inferErrors = append(inferErrors, *err) |
| 112 | + } |
| 113 | + if err := inferUnsetFlag("root", &lsifUploadFlags.root, inferIndexRoot); err != nil { |
| 114 | + inferErrors = append(inferErrors, *err) |
| 115 | + } |
| 116 | + if err := inferUnsetFlag("indexer", &lsifUploadFlags.indexer, readIndexerName); err != nil { |
| 117 | + inferErrors = append(inferErrors, *err) |
| 118 | + } |
| 119 | + |
| 120 | + return inferErrors |
| 121 | +} |
| 122 | + |
| 123 | +// inferUnsetFlag conditionally updates the value of the given pointer with the |
| 124 | +// return value of the given function. If the flag with the given name was supplied |
| 125 | +// by the user, then this function no-ops. An argumentInferenceError is returned if |
| 126 | +// the given function returns an error. |
| 127 | +// |
| 128 | +// Note: This function must not be called before lsifUploadFlagSet.Parse. |
| 129 | +func inferUnsetFlag(name string, target *string, f func() (string, error)) *argumentInferenceError { |
| 130 | + if isFlagSet(lsifUploadFlagSet, name) { |
| 131 | + return nil |
| 132 | + } |
| 133 | + |
| 134 | + value, err := f() |
| 135 | + if err != nil { |
| 136 | + return &argumentInferenceError{name, err} |
| 137 | + } |
| 138 | + |
| 139 | + *target = value |
| 140 | + return nil |
| 141 | +} |
| 142 | + |
| 143 | +// isFlagSet returns true if the flag with the given name was supplied by the user. |
| 144 | +// This lets us distinguish between zero-values (empty strings) and void values without |
| 145 | +// requiring pointers and adding a layer of indirection deeper in the program. |
| 146 | +func isFlagSet(fs *flag.FlagSet, name string) (found bool) { |
| 147 | + fs.Visit(func(f *flag.Flag) { |
| 148 | + if f.Name == name { |
| 149 | + found = true |
| 150 | + } |
| 151 | + }) |
| 152 | + |
| 153 | + return found |
| 154 | +} |
| 155 | + |
| 156 | +// inferIndexRoot returns the root directory based on the configured index file path. |
| 157 | +// |
| 158 | +// Note: This function must not be called before lsifUploadFlagSet.Parse. |
| 159 | +func inferIndexRoot() (string, error) { |
| 160 | + return codeintel.InferRoot(lsifUploadFlags.file) |
| 161 | +} |
| 162 | + |
| 163 | +// readIndexerName returns the indexer name read from the configured index file. |
| 164 | +// |
| 165 | +// Note: This function must not be called before lsifUploadFlagSet.Parse. |
| 166 | +func readIndexerName() (string, error) { |
| 167 | + file, err := os.Open(lsifUploadFlags.file) |
| 168 | + if err != nil { |
| 169 | + return "", err |
| 170 | + } |
| 171 | + defer file.Close() |
| 172 | + |
| 173 | + return upload.ReadIndexerName(file) |
| 174 | +} |
| 175 | + |
| 176 | +// validateLSIFUploadFlags returns an error if any of the parsed flag values are illegal. |
| 177 | +// |
| 178 | +// Note: This function must not be called before lsifUploadFlagSet.Parse. |
| 179 | +func validateLSIFUploadFlags() error { |
| 180 | + lsifUploadFlags.root = codeintel.SanitizeRoot(lsifUploadFlags.root) |
| 181 | + |
| 182 | + if strings.HasPrefix(lsifUploadFlags.root, "..") { |
| 183 | + return errors.New("root must not be outside of repository") |
| 184 | + } |
| 185 | + |
| 186 | + if lsifUploadFlags.maxPayloadSizeMb < 25 { |
| 187 | + return errors.New("max-payload-size must be at least 25 (MB)") |
| 188 | + } |
| 189 | + |
| 190 | + return nil |
| 191 | +} |
0 commit comments