Skip to content

Commit 1c70d53

Browse files
authored
[language-platform] update src-cli to handle scip file upload (#897)
1 parent 1e9a0cf commit 1c70d53

File tree

5 files changed

+205
-46
lines changed

5 files changed

+205
-46
lines changed

CHANGELOG.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ All notable changes to `src-cli` are documented in this file.
1313

1414
### Added
1515

16+
- `src codeintel upload` will now upload SCIP indexes (over LSIF indexes) when the target instance supports it. [#897](https://github.com/sourcegraph/src-cli/pull/897)
17+
1618
### Changed
1719

1820
### Fixed
@@ -36,7 +38,7 @@ All notable changes to `src-cli` are documented in this file.
3638

3739
### Fixed
3840

39-
- Fix network timeout in `src users clean` occuring in instances with many users [#901](https://github.com/sourcegraph/src-cli/pull/901)
41+
- Fix network timeout in `src users clean` occuring in instances with many users [#901](https://github.com/sourcegraph/src-cli/pull/901)
4042
- Aligned parsing of spec file parameter of `src batch repos` with other commands. [#919](https://github.com/sourcegraph/src-cli/pull/919)
4143
- Remove empty log outputs during batch spec execution. [#923](https://github.com/sourcegraph/src-cli/pull/923)
4244

@@ -135,7 +137,6 @@ No noteworthy changes, mechanical release to match Sourcegraph release.
135137

136138
- INTERNAL ONLY: Fixed src batch exec not logging errors.
137139

138-
139140
## 3.42.2
140141

141142
### Fixed
@@ -179,7 +180,6 @@ No noteworthy changes, mechanical release to match Sourcegraph release.
179180

180181
- The preview link shown when running `src batch remote` to create a new batch change no longer 404s. [sourcegraph/src-cli](https://github.com/sourcegraph/src-cli/pull/787)
181182

182-
183183
## 3.40.11
184184

185185
### Changed

cmd/src/code_intel_upload.go

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"io"
1010
"net/url"
1111
"os"
12+
"path/filepath"
1213
"strings"
1314
"time"
1415

@@ -70,7 +71,12 @@ Examples:
7071
func handleCodeIntelUpload(args []string) error {
7172
ctx := context.Background()
7273

73-
out, err := parseAndValidateCodeIntelUploadFlags(args)
74+
isSCIPAvailable, err := isSCIPAvailable()
75+
if err != nil {
76+
return err
77+
}
78+
79+
out, err := parseAndValidateCodeIntelUploadFlags(args, isSCIPAvailable)
7480
if !codeintelUploadFlags.json {
7581
if out != nil {
7682
printInferredArguments(out)
@@ -88,7 +94,7 @@ func handleCodeIntelUpload(args []string) error {
8894
Flags: codeintelUploadFlags.apiFlags,
8995
})
9096

91-
uploadID, err := upload.UploadIndex(ctx, codeintelUploadFlags.file, client, codeintelUploadOptions(out))
97+
uploadID, err := upload.UploadIndex(ctx, codeintelUploadFlags.file, client, codeintelUploadOptions(out, isSCIPAvailable))
9298
if err != nil {
9399
return handleUploadError(out, err)
94100
}
@@ -132,12 +138,19 @@ func handleCodeIntelUpload(args []string) error {
132138
}
133139

134140
// codeintelUploadOptions creates a set of upload options given the values in the flags.
135-
func codeintelUploadOptions(out *output.Output) upload.UploadOptions {
141+
func codeintelUploadOptions(out *output.Output, isSCIPAvailable bool) upload.UploadOptions {
136142
var associatedIndexID *int
137143
if codeintelUploadFlags.associatedIndexID != -1 {
138144
associatedIndexID = &codeintelUploadFlags.associatedIndexID
139145
}
140146

147+
cfg.AdditionalHeaders["Content-Type"] = "application/x-ndjson+lsif"
148+
path := codeintelUploadFlags.uploadRoute
149+
if isSCIPAvailable && filepath.Ext(codeintelUploadFlags.file) == ".scip" {
150+
cfg.AdditionalHeaders["Content-Type"] = "application/x-protobuf+scip"
151+
path = "/.api/scip/upload"
152+
}
153+
141154
logger := upload.NewRequestLogger(
142155
os.Stdout,
143156
// Don't need to check upper bounds as we only compare verbosity ranges
@@ -162,7 +175,7 @@ func codeintelUploadOptions(out *output.Output) upload.UploadOptions {
162175
AdditionalHeaders: cfg.AdditionalHeaders,
163176
MaxRetries: 5,
164177
RetryInterval: time.Second,
165-
Path: codeintelUploadFlags.uploadRoute,
178+
Path: path,
166179
MaxPayloadSizeBytes: codeintelUploadFlags.maxPayloadSizeMb * 1000 * 1000,
167180
GitHubToken: codeintelUploadFlags.gitHubToken,
168181
GitLabToken: codeintelUploadFlags.gitLabToken,

cmd/src/code_intel_upload_flags.go

Lines changed: 167 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,23 @@
11
package main
22

33
import (
4+
"context"
45
"flag"
56
"fmt"
7+
"net/http"
68
"os"
79
"path"
810
"path/filepath"
911
"strings"
12+
"time"
1013

14+
"github.com/sourcegraph/scip/bindings/go/scip"
15+
libscip "github.com/sourcegraph/sourcegraph/lib/codeintel/lsif/scip"
16+
"github.com/sourcegraph/sourcegraph/lib/codeintel/upload"
1117
"github.com/sourcegraph/sourcegraph/lib/errors"
1218
"github.com/sourcegraph/sourcegraph/lib/output"
1319
"google.golang.org/protobuf/proto"
1420

15-
"github.com/sourcegraph/scip/bindings/go/scip"
16-
"github.com/sourcegraph/sourcegraph/lib/codeintel/upload"
17-
1821
"github.com/sourcegraph/src-cli/internal/api"
1922
"github.com/sourcegraph/src-cli/internal/codeintel"
2023
)
@@ -53,10 +56,15 @@ var (
5356
// Used to include the insecure-skip-verify flag in the help output, as we don't use any of the
5457
// other api.Client methods, so only the insecureSkipVerify flag is relevant here.
5558
dummyflag bool
59+
60+
// Used to skip the LSIF -> SCIP conversion during the migration. Not expected to be used outside
61+
// of codeintel-qa pipelines and not expected to last much longer than a few releases while we
62+
// deprecate all LSIF code paths.
63+
skipConversionToSCIP bool
5664
)
5765

5866
func init() {
59-
codeintelUploadFlagSet.StringVar(&codeintelUploadFlags.file, "file", "./dump.lsif", `The path to the LSIF dump file.`)
67+
codeintelUploadFlagSet.StringVar(&codeintelUploadFlags.file, "file", "", `The path to the LSIF dump file.`)
6068

6169
// UploadRecordOptions
6270
codeintelUploadFlagSet.StringVar(&codeintelUploadFlags.repo, "repo", "", `The name of the repository (e.g. github.com/gorilla/mux). By default, derived from the origin remote.`)
@@ -81,6 +89,9 @@ func init() {
8189
codeintelUploadFlagSet.BoolVar(&codeintelUploadFlags.json, "json", false, `Output relevant state in JSON on success.`)
8290
codeintelUploadFlagSet.BoolVar(&codeintelUploadFlags.open, "open", false, `Open the LSIF upload page in your browser.`)
8391
codeintelUploadFlagSet.BoolVar(&dummyflag, "insecure-skip-verify", false, "Skip validation of TLS certificates against trusted chains")
92+
93+
// Testing flags
94+
codeintelUploadFlagSet.BoolVar(&skipConversionToSCIP, "skip-scip", false, "Skip converting LSIF index to SCIP if the instance supports it; this option should only used for debugging")
8495
}
8596

8697
// parseAndValidateCodeIntelUploadFlags calls codeintelUploadFlagset.Parse, then infers values for
@@ -89,7 +100,7 @@ func init() {
89100
//
90101
// On success, the global codeintelUploadFlags object will be populated with valid values. An
91102
// error is returned on failure.
92-
func parseAndValidateCodeIntelUploadFlags(args []string) (*output.Output, error) {
103+
func parseAndValidateCodeIntelUploadFlags(args []string, isSCIPAvailable bool) (*output.Output, error) {
93104
if err := codeintelUploadFlagSet.Parse(args); err != nil {
94105
return nil, err
95106
}
@@ -112,22 +123,43 @@ func parseAndValidateCodeIntelUploadFlags(args []string) (*output.Output, error)
112123
return nil, err
113124
}
114125

115-
if err := handleSCIP(out); err != nil {
116-
return nil, err
126+
if !isFlagSet(codeintelUploadFlagSet, "file") {
127+
defaultFile, err := inferDefaultFile()
128+
if err != nil {
129+
return nil, err
130+
}
131+
codeintelUploadFlags.file = defaultFile
117132
}
118133

119-
if inferenceErrors := inferMissingCodeIntelUploadFlags(); len(inferenceErrors) > 0 {
120-
return nil, errorWithHint{
121-
err: inferenceErrors[0].err, hint: strings.Join([]string{
122-
fmt.Sprintf(
123-
"Unable to determine %s from environment. Check your working directory or supply -%s={value} explicitly",
124-
inferenceErrors[0].argument,
125-
inferenceErrors[0].argument,
126-
),
127-
}, "\n"),
134+
// Check to see if input file exists
135+
if _, err := os.Stat(codeintelUploadFlags.file); os.IsNotExist(err) {
136+
if !isFlagSet(codeintelUploadFlagSet, "file") {
137+
return nil, formatInferenceError(argumentInferenceError{"file", err})
138+
}
139+
140+
return nil, errors.Newf("file %q does not exist", codeintelUploadFlags.file)
141+
}
142+
143+
if !isSCIPAvailable {
144+
if err := handleSCIP(out); err != nil {
145+
return nil, err
146+
}
147+
} else {
148+
if err := handleLSIF(out); err != nil {
149+
return nil, err
128150
}
129151
}
130152

153+
// Check for new file existence after transformation
154+
if _, err := os.Stat(codeintelUploadFlags.file); os.IsNotExist(err) {
155+
return nil, errors.Newf("file %q does not exist", codeintelUploadFlags.file)
156+
}
157+
158+
// Infer the remaining default arguments (may require reading from new file)
159+
if inferenceErrors := inferMissingCodeIntelUploadFlags(); len(inferenceErrors) > 0 {
160+
return nil, formatInferenceError(inferenceErrors[0])
161+
}
162+
131163
if err := validateCodeIntelUploadFlags(); err != nil {
132164
return nil, err
133165
}
@@ -151,6 +183,22 @@ func codeintelUploadOutput() (out *output.Output) {
151183
})
152184
}
153185

186+
func isSCIPAvailable() (bool, error) {
187+
client := cfg.apiClient(codeintelUploadFlags.apiFlags, codeintelUploadFlagSet.Output())
188+
req, err := client.NewHTTPRequest(context.Background(), "HEAD", "/.api/scip/upload", nil)
189+
if err != nil {
190+
return false, err
191+
}
192+
193+
resp, err := client.Do(req)
194+
if err != nil {
195+
return false, err
196+
}
197+
defer resp.Body.Close()
198+
199+
return resp.StatusCode == http.StatusOK, nil
200+
}
201+
154202
type argumentInferenceError struct {
155203
argument string
156204
err error
@@ -174,7 +222,7 @@ func replaceBaseName(oldPath string, newBaseName string) string {
174222
func handleSCIP(out *output.Output) error {
175223
fileExt := path.Ext(codeintelUploadFlags.file)
176224
if len(fileExt) == 0 {
177-
return errors.Newf("missing file extension for %s; expected .scip", codeintelUploadFlags.file)
225+
return errors.Newf("missing file extension for %s; expected .scip or .lsif", codeintelUploadFlags.file)
178226
}
179227
inputFile := codeintelUploadFlags.file
180228
if fileExt == ".scip" || fileExt == ".lsif-typed" {
@@ -251,16 +299,114 @@ func convertSCIPToLSIFGraph(out *output.Output, inputFile, outputFile string) er
251299
return nil
252300
}
253301

302+
func inferDefaultFile() (string, error) {
303+
hasSCIP := true
304+
const scipFilename = "index.scip"
305+
if _, err := os.Stat(scipFilename); err != nil {
306+
if os.IsNotExist(err) {
307+
hasSCIP = false
308+
} else {
309+
return "", err
310+
}
311+
}
312+
313+
hasLSIF := true
314+
const lsifFilename = "dump.lsif"
315+
if _, err := os.Stat(lsifFilename); err != nil {
316+
if os.IsNotExist(err) {
317+
hasLSIF = false
318+
} else {
319+
return "", err
320+
}
321+
}
322+
323+
if hasSCIP && hasLSIF {
324+
return "", errors.Newf("both %s and %s exists - cannot determine unambiguous choice", scipFilename, lsifFilename)
325+
}
326+
if !(hasSCIP || hasLSIF) {
327+
return "", formatInferenceError(argumentInferenceError{"file", errors.Newf("neither %s nor %s exists", scipFilename, lsifFilename)})
328+
}
329+
330+
if hasSCIP {
331+
return scipFilename, nil
332+
}
333+
334+
return lsifFilename, nil
335+
}
336+
337+
func formatInferenceError(inferenceErr argumentInferenceError) error {
338+
return errorWithHint{
339+
err: inferenceErr.err, hint: strings.Join([]string{
340+
fmt.Sprintf(
341+
"Unable to determine %s from environment. Check your working directory or supply -%s={value} explicitly",
342+
inferenceErr.argument,
343+
inferenceErr.argument,
344+
),
345+
}, "\n"),
346+
}
347+
}
348+
349+
func handleLSIF(out *output.Output) error {
350+
if skipConversionToSCIP {
351+
return nil
352+
}
353+
354+
fileExt := path.Ext(codeintelUploadFlags.file)
355+
if len(fileExt) == 0 {
356+
return errors.Newf("missing file extension for %s; expected .scip or .lsif", codeintelUploadFlags.file)
357+
}
358+
359+
if fileExt == ".lsif" || fileExt == ".dump" {
360+
inputFile := codeintelUploadFlags.file
361+
outputFile := replaceExtension(inputFile, ".scip")
362+
codeintelUploadFlags.file = outputFile
363+
return convertLSIFToSCIP(out, inputFile, outputFile)
364+
}
365+
366+
return nil
367+
}
368+
369+
// Reads the LSIF encoded input file and writes the corresponding SCIP encoded output file.
370+
func convertLSIFToSCIP(out *output.Output, inputFile, outputFile string) error {
371+
if out != nil {
372+
out.Writef("%s Converting %s into %s", output.EmojiInfo, inputFile, outputFile)
373+
}
374+
375+
ctx := context.Background()
376+
uploadID := -time.Now().Nanosecond()
377+
root := codeintelUploadFlags.root
378+
379+
if !isFlagSet(codeintelUploadFlagSet, "root") {
380+
// Best-effort infer the root; we have a strange cyclic init order where we're
381+
// currently trying to determine the filename that determines the root, but we
382+
// need the root when converting from LSIF to SCIP.
383+
root, _ = inferIndexRoot()
384+
}
385+
386+
rc, err := os.Open(inputFile)
387+
if err != nil {
388+
return err
389+
}
390+
defer rc.Close()
391+
392+
index, err := libscip.ConvertLSIF(ctx, uploadID, rc, root)
393+
if err != nil {
394+
return err
395+
}
396+
serialized, err := proto.Marshal(index)
397+
if err != nil {
398+
return err
399+
}
400+
401+
return os.WriteFile(outputFile, serialized, os.ModePerm)
402+
}
403+
254404
// inferMissingCodeIntelUploadFlags updates the flags values which were not explicitly
255405
// supplied by the user with default values inferred from the current git state and
256406
// filesystem.
257407
//
258408
// Note: This function must not be called before codeintelUploadFlagset.Parse.
259409
func inferMissingCodeIntelUploadFlags() (inferErrors []argumentInferenceError) {
260-
if _, err := os.Stat(codeintelUploadFlags.file); os.IsNotExist(err) {
261-
inferErrors = append(inferErrors, argumentInferenceError{"file", err})
262-
}
263-
264410
indexerName, indexerVersion, readIndexerNameAndVersionErr := readIndexerNameAndVersion()
265411
getIndexerName := func() (string, error) { return indexerName, readIndexerNameAndVersionErr }
266412
getIndexerVersion := func() (string, error) { return indexerVersion, readIndexerNameAndVersionErr }

go.mod

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ require (
2121
github.com/sourcegraph/go-diff v0.6.1
2222
github.com/sourcegraph/jsonx v0.0.0-20200629203448-1a936bd500cf
2323
github.com/sourcegraph/scip v0.2.3
24-
github.com/sourcegraph/sourcegraph/lib v0.0.0-20230110145349-bfa074f6c6d7
24+
github.com/sourcegraph/sourcegraph/lib v0.0.0-20230113014457-46187b849239
2525
github.com/stretchr/testify v1.8.0
26-
golang.org/x/net v0.2.0
26+
golang.org/x/net v0.5.0
2727
golang.org/x/sync v0.1.0
2828
google.golang.org/api v0.102.0
2929
google.golang.org/protobuf v1.28.1
@@ -114,10 +114,10 @@ require (
114114
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect
115115
golang.org/x/mod v0.7.0 // indirect
116116
golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect
117-
golang.org/x/sys v0.2.0 // indirect
118-
golang.org/x/term v0.2.0 // indirect
119-
golang.org/x/text v0.4.0 // indirect
120-
golang.org/x/tools v0.3.0 // indirect
117+
golang.org/x/sys v0.4.0 // indirect
118+
golang.org/x/term v0.4.0 // indirect
119+
golang.org/x/text v0.6.0 // indirect
120+
golang.org/x/tools v0.5.0 // indirect
121121
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
122122
google.golang.org/appengine v1.6.7 // indirect
123123
google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e // indirect

0 commit comments

Comments
 (0)