diff options
Diffstat (limited to 'gazelle/python/parser.go')
-rw-r--r-- | gazelle/python/parser.go | 59 |
1 files changed, 32 insertions, 27 deletions
diff --git a/gazelle/python/parser.go b/gazelle/python/parser.go index 7f10a75..9b00b83 100644 --- a/gazelle/python/parser.go +++ b/gazelle/python/parser.go @@ -17,6 +17,7 @@ package python import ( "bufio" "context" + _ "embed" "encoding/json" "fmt" "io" @@ -26,59 +27,51 @@ import ( "strings" "sync" - "github.com/bazelbuild/rules_go/go/tools/bazel" "github.com/emirpasic/gods/sets/treeset" godsutils "github.com/emirpasic/gods/utils" ) var ( + parserCmd *exec.Cmd parserStdin io.WriteCloser parserStdout io.Reader parserMutex sync.Mutex ) func startParserProcess(ctx context.Context) { - parseScriptRunfile, err := bazel.Runfile("python/parse") - if err != nil { - log.Printf("failed to initialize parser: %v\n", err) - os.Exit(1) - } - - cmd := exec.CommandContext(ctx, parseScriptRunfile) - - cmd.Stderr = os.Stderr + // due to #691, we need a system interpreter to boostrap, part of which is + // to locate the hermetic interpreter. + parserCmd = exec.CommandContext(ctx, "python3", helperPath, "parse") + parserCmd.Stderr = os.Stderr - stdin, err := cmd.StdinPipe() + stdin, err := parserCmd.StdinPipe() if err != nil { log.Printf("failed to initialize parser: %v\n", err) os.Exit(1) } parserStdin = stdin - stdout, err := cmd.StdoutPipe() + stdout, err := parserCmd.StdoutPipe() if err != nil { log.Printf("failed to initialize parser: %v\n", err) os.Exit(1) } parserStdout = stdout - if err := cmd.Start(); err != nil { + if err := parserCmd.Start(); err != nil { log.Printf("failed to initialize parser: %v\n", err) os.Exit(1) } - - go func() { - if err := cmd.Wait(); err != nil { - log.Printf("failed to wait for parser: %v\n", err) - os.Exit(1) - } - }() } func shutdownParserProcess() { if err := parserStdin.Close(); err != nil { fmt.Fprintf(os.Stderr, "error closing parser: %v", err) } + + if err := parserCmd.Wait(); err != nil { + log.Printf("failed to wait for parser: %v\n", err) + } } // python3Parser implements a parser for Python files that extracts the modules @@ -108,7 +101,7 @@ func newPython3Parser( // parseSingle parses a single Python file and returns the extracted modules // from the import statements as well as the parsed comments. -func (p *python3Parser) parseSingle(pyFilename string) (*treeset.Set, error) { +func (p *python3Parser) parseSingle(pyFilename string) (*treeset.Set, map[string]*treeset.Set, error) { pyFilenames := treeset.NewWith(godsutils.StringComparator) pyFilenames.Add(pyFilename) return p.parse(pyFilenames) @@ -116,7 +109,7 @@ func (p *python3Parser) parseSingle(pyFilename string) (*treeset.Set, error) { // parse parses multiple Python files and returns the extracted modules from // the import statements as well as the parsed comments. -func (p *python3Parser) parse(pyFilenames *treeset.Set) (*treeset.Set, error) { +func (p *python3Parser) parse(pyFilenames *treeset.Set) (*treeset.Set, map[string]*treeset.Set, error) { parserMutex.Lock() defer parserMutex.Unlock() @@ -129,24 +122,28 @@ func (p *python3Parser) parse(pyFilenames *treeset.Set) (*treeset.Set, error) { } encoder := json.NewEncoder(parserStdin) if err := encoder.Encode(&req); err != nil { - return nil, fmt.Errorf("failed to parse: %w", err) + return nil, nil, fmt.Errorf("failed to parse: %w", err) } reader := bufio.NewReader(parserStdout) data, err := reader.ReadBytes(0) if err != nil { - return nil, fmt.Errorf("failed to parse: %w", err) + return nil, nil, fmt.Errorf("failed to parse: %w", err) } data = data[:len(data)-1] var allRes []parserResponse if err := json.Unmarshal(data, &allRes); err != nil { - return nil, fmt.Errorf("failed to parse: %w", err) + return nil, nil, fmt.Errorf("failed to parse: %w", err) } + mainModules := make(map[string]*treeset.Set, len(allRes)) for _, res := range allRes { + if res.HasMain { + mainModules[res.FileName] = treeset.NewWith(moduleComparator) + } annotations, err := annotationsFromComments(res.Comments) if err != nil { - return nil, fmt.Errorf("failed to parse annotations: %w", err) + return nil, nil, fmt.Errorf("failed to parse annotations: %w", err) } for _, m := range res.Modules { @@ -163,20 +160,28 @@ func (p *python3Parser) parse(pyFilenames *treeset.Set) (*treeset.Set, error) { } modules.Add(m) + if res.HasMain { + mainModules[res.FileName].Add(m) + } } } - return modules, nil + return modules, mainModules, nil } // parserResponse represents a response returned by the parser.py for a given // parsed Python module. type parserResponse struct { + // FileName of the parsed module + FileName string // The modules depended by the parsed module. Modules []module `json:"modules"` // The comments contained in the parsed module. This contains the // annotations as they are comments in the Python module. Comments []comment `json:"comments"` + // HasMain indicates whether the Python module has `if __name == "__main__"` + // at the top level + HasMain bool `json:"has_main"` } // module represents a fully-qualified, dot-separated, Python module as seen on |