Skip to content

Commit

Permalink
feat: 支持多启动入口的 Go 项目
Browse files Browse the repository at this point in the history
  • Loading branch information
link-duan committed Mar 15, 2023
1 parent 9cc47bf commit ca3828c
Show file tree
Hide file tree
Showing 12 changed files with 281 additions and 81 deletions.
97 changes: 52 additions & 45 deletions analyzer.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package eapi

import (
"errors"
"fmt"
"go/ast"
"go/build"
"go/token"
"go/types"
"io/fs"
"os"
"path/filepath"
"strings"
Expand Down Expand Up @@ -68,20 +70,6 @@ func (a *Analyzer) Depends(pkgNames ...string) *Analyzer {
return a
}

func (a *Analyzer) Load(packagePath string) {
packagePath, err := filepath.Abs(packagePath)
if err != nil {
panic("invalid package path: " + err.Error())
}

pkgList := a.load(packagePath)
for _, pkg := range pkgList {
a.loadDefinitionsFromPkg(pkg, pkg.Module.Dir)
}

a.packages = append(a.packages, pkgList...)
}

func (a *Analyzer) Process(packagePath string) *Analyzer {
if len(a.plugins) <= 0 {
panic("must register plugin before processing")
Expand All @@ -94,11 +82,12 @@ func (a *Analyzer) Process(packagePath string) *Analyzer {

pkgList := a.load(packagePath)
for _, pkg := range pkgList {
a.loadDefinitionsFromPkg(pkg, pkg.Module.Dir)
a.definitions = make(Definitions)
for _, p := range pkg {
a.loadDefinitionsFromPkg(p, p.Module.Dir)
}
a.processPkg(pkg)
}
a.packages = append(a.packages, pkgList...)

a.processPkg(packagePath)

return a
}
Expand All @@ -117,16 +106,30 @@ func (a *Analyzer) analyze(ctx *Context, node ast.Node) {
}
}

func (a *Analyzer) load(pkgPath string) []*packages.Package {
const entryPackageName = "command-line-arguments"

func (a *Analyzer) load(pkgPath string) [][]*packages.Package {
absPath, err := filepath.Abs(pkgPath)
if err != nil {
panic("invalid package path: " + pkgPath)
}

pkg, err := build.Default.ImportDir(absPath, build.ImportComment)
if err != nil {
panic("import directory failed: " + err.Error())
}
var pkgList []*build.Package
filepath.Walk(absPath, func(path string, info fs.FileInfo, err error) error {
if !info.IsDir() {
return nil
}
pkg, err := build.Default.ImportDir(path, build.ImportComment)
if err != nil {
var noGoErr = &build.NoGoError{}
if errors.As(err, &noGoErr) {
return nil
}
panic("import directory failed: " + err.Error())
}
pkgList = append(pkgList, pkg)
return filepath.SkipDir
})

config := &packages.Config{
Mode: packages.NeedName |
Expand All @@ -141,36 +144,40 @@ func (a *Analyzer) load(pkgPath string) []*packages.Package {
Tests: false,
Dir: absPath,
}
var files []string
for _, filename := range append(pkg.GoFiles, pkg.CgoFiles...) {
files = append(files, filepath.Join(pkgPath, filename))
}
res, err := packages.Load(config, files...)
if err != nil {
panic("load packages failed: " + err.Error())
}

// 前面的 packages.Load() 方法不能解析出以第一层的 Module
// 所以这里手动解析 go.mod
for _, p := range res {
if p.Module != nil {
continue
var res [][]*packages.Package
for _, pkg := range pkgList {
var files []string
for _, filename := range append(pkg.GoFiles, pkg.CgoFiles...) {
files = append(files, filepath.Join(pkg.Dir, filename))
}
packs, err := packages.Load(config, files...)
if err != nil {
panic("load packages failed: " + err.Error())
}

module := a.parseGoModule(pkgPath)
if module == nil {
panic("failed to parse go.mod file in " + pkgPath)
// 前面的 packages.Load() 方法不能解析出以第一层的 Module
// 所以这里手动解析 go.mod
for _, p := range packs {
if p.Module != nil {
continue
}

module := a.parseGoModule(pkgPath)
if module == nil {
panic("failed to parse go.mod file in " + pkgPath)
}
p.Module = module
p.PkgPath = entryPackageName
p.ID = module.Path
}
p.Module = module
p.PkgPath = module.Path
p.ID = module.Path
res = append(res, packs)
}

return res
}

func (a *Analyzer) processPkg(packagePath string) {
for _, pkg := range a.packages {
func (a *Analyzer) processPkg(pkgList []*packages.Package) {
for _, pkg := range pkgList {
moduleDir := pkg.Module.Dir
InspectPackage(pkg, func(pkg *packages.Package) bool {
if pkg.Module == nil || pkg.Module.Dir != moduleDir {
Expand Down
28 changes: 28 additions & 0 deletions analyzer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package eapi

import (
"testing"
)

func TestAnalyzer_load(t *testing.T) {
type args struct {
pkgPath string
}
tests := []struct {
name string
args args
}{
{
name: "multi entrypoint",
args: args{
pkgPath: "./testdata/multi_entry",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
a := &Analyzer{}
a.load(tt.args.pkgPath)
})
}
}
18 changes: 3 additions & 15 deletions plugins/echo/testdata/sample/docs/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -102,21 +102,9 @@
"sample_model.GoodsStatus": {
"description": "\u003ctable\u003e\u003ctr\u003e\u003cth\u003eValue\u003c/th\u003e\u003cth\u003eKey\u003c/th\u003e\u003cth\u003eDescription\u003c/th\u003e\u003c/tr\u003e\u003ctr\u003e\u003ctd\u003e\u003c/td\u003e\u003ctd\u003eGoodsOnSale\u003c/td\u003e\u003ctd\u003e\u003c/td\u003e\u003c/tr\u003e\u003ctr\u003e\u003ctd\u003e\u003c/td\u003e\u003ctd\u003eGoodsOffSale\u003c/td\u003e\u003ctd\u003e\u003c/td\u003e\u003c/tr\u003e\u003ctr\u003e\u003ctd\u003e\u003c/td\u003e\u003ctd\u003eGoodsOutOfStock\u003c/td\u003e\u003ctd\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e",
"enum": [
{
"key": "GoodsOnSale",
"value": 1,
"description": ""
},
{
"key": "GoodsOffSale",
"value": 2,
"description": ""
},
{
"key": "GoodsOutOfStock",
"value": 3,
"description": ""
}
1,
2,
3
],
"title": "ModelGoodsStatus",
"type": "integer"
Expand Down
1 change: 0 additions & 1 deletion plugins/echo/testdata/sample/frontend/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,4 +104,3 @@ export type ModelUploadFileRes = {
id?: number;
url?: string;
}

6 changes: 6 additions & 0 deletions plugins/gin/gin.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package gin

import (
"fmt"
"go/ast"
"go/token"
"go/types"
"os"
"path"
"regexp"
"strings"
Expand Down Expand Up @@ -167,6 +169,10 @@ func (e *Plugin) parseAPI(ctx *analyzer.Context, callExpr *ast.CallExpr, comment
}

handlerDef := ctx.GetDefinition(handlerFn.Pkg().Path(), handlerFn.Name())
if handlerDef == nil {
fmt.Fprintf(os.Stderr, "handler function %s.%s not found\n", handlerFn.Pkg().Path(), handlerFn.Name())
return
}
handlerFnDef, ok := handlerDef.(*analyzer.FuncDefinition)
if !ok {
return
Expand Down
24 changes: 4 additions & 20 deletions plugins/gin/testdata/server/docs/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -216,26 +216,10 @@
"server_pkg_view.ErrCode": {
"description": "\u003ctable\u003e\u003ctr\u003e\u003cth\u003eValue\u003c/th\u003e\u003cth\u003eKey\u003c/th\u003e\u003cth\u003eDescription\u003c/th\u003e\u003c/tr\u003e\u003ctr\u003e\u003ctd\u003e\u003c/td\u003e\u003ctd\u003eCodeNotFound\u003c/td\u003e\u003ctd\u003eResource not found\u003c/td\u003e\u003c/tr\u003e\u003ctr\u003e\u003ctd\u003e\u003c/td\u003e\u003ctd\u003eCodeCancled\u003c/td\u003e\u003ctd\u003eRequest canceld\u003c/td\u003e\u003c/tr\u003e\u003ctr\u003e\u003ctd\u003e\u003c/td\u003e\u003ctd\u003eCodeUnknown\u003c/td\u003e\u003ctd\u003e\u003c/td\u003e\u003c/tr\u003e\u003ctr\u003e\u003ctd\u003e\u003c/td\u003e\u003ctd\u003eCodeInvalidArgument\u003c/td\u003e\u003ctd\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e",
"enum": [
{
"key": "CodeNotFound",
"value": 10000,
"description": "Resource not found"
},
{
"key": "CodeCancled",
"value": 10001,
"description": "Request canceld"
},
{
"key": "CodeUnknown",
"value": 10002,
"description": ""
},
{
"key": "CodeInvalidArgument",
"value": 10003,
"description": ""
}
10000,
10001,
10002,
10003
],
"ext": {
"type": "enum",
Expand Down
16 changes: 16 additions & 0 deletions testdata/multi_entry/app_a/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package main

import (
"net/http"

"github.com/gin-gonic/gin"
)

func main() {
r := gin.New()
r.GET("/app-a/hello", handleHello)
}

func handleHello(c *gin.Context) {
c.JSON(http.StatusOK, "world")
}
16 changes: 16 additions & 0 deletions testdata/multi_entry/app_b/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package main

import (
"net/http"

"github.com/gin-gonic/gin"
)

func main() {
r := gin.New()
r.GET("/app-b/hello", handleHello)
}

func handleHello(c *gin.Context) {
c.JSON(http.StatusOK, "world")
}
42 changes: 42 additions & 0 deletions testdata/multi_entry/docs/openapi.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"components": {},
"info": {
"title": "",
"version": ""
},
"openapi": "3.0.3",
"paths": {
"/app-a/hello": {
"get": {
"operationId": "main.handleHello",
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"type": "string"
}
}
}
}
}
}
},
"/app-b/hello": {
"get": {
"operationId": "main.handleHello",
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"type": "string"
}
}
}
}
}
}
}
}
}
2 changes: 2 additions & 0 deletions testdata/multi_entry/eapi.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
dir: .
plugin: gin
31 changes: 31 additions & 0 deletions testdata/multi_entry/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
module multi_entry

go 1.20

require github.com/gin-gonic/gin v1.9.0

require (
github.com/bytedance/sonic v1.8.0 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.11.2 // indirect
github.com/goccy/go-json v0.10.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.9 // indirect
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
golang.org/x/crypto v0.5.0 // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/text v0.7.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
Loading

0 comments on commit ca3828c

Please sign in to comment.