aboutsummaryrefslogtreecommitdiff
path: root/gazelle/python/parser.go
diff options
context:
space:
mode:
Diffstat (limited to 'gazelle/python/parser.go')
-rw-r--r--gazelle/python/parser.go59
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