mirror of
https://github.com/httprunner/httprunner.git
synced 2026-05-14 17:07:36 +08:00
201 lines
5.6 KiB
Go
201 lines
5.6 KiB
Go
package hrp
|
|
|
|
import (
|
|
"bufio"
|
|
_ "embed"
|
|
"fmt"
|
|
"html/template"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/httprunner/funplugin/shared"
|
|
"github.com/pkg/errors"
|
|
"github.com/rs/zerolog/log"
|
|
|
|
"github.com/httprunner/httprunner/v4/hrp/internal/builtin"
|
|
"github.com/httprunner/httprunner/v4/hrp/internal/version"
|
|
)
|
|
|
|
//go:embed internal/scaffold/templates/plugin/debugtalkPythonTemplate
|
|
var pyTemplate string
|
|
|
|
//go:embed internal/scaffold/templates/plugin/debugtalkGoTemplate
|
|
var goTemplate string
|
|
|
|
// regex for finding all function names
|
|
var (
|
|
regexPyFunctionName = regexp.MustCompile(`def ([a-zA-Z_]\w*)\(.*\)`)
|
|
regexGoFunctionName = regexp.MustCompile(`func ([A-Z][a-zA-Z_]\w*)\(.*\)`)
|
|
)
|
|
|
|
type pluginTemplateContent struct {
|
|
Version string // hrp version
|
|
FunctionNames []string // function names
|
|
}
|
|
|
|
func findAllFunctionNames(t *regexp.Regexp, path string) (functionNames []string, err error) {
|
|
log.Info().Str("path", path).Msg("find all function names from plugin file")
|
|
|
|
content, err := os.ReadFile(path)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("failed to read file")
|
|
return nil, errors.Wrap(err, "read file failed")
|
|
}
|
|
|
|
// find all function names
|
|
functionNameSlice := t.FindAllStringSubmatch(string(content), -1)
|
|
for _, elem := range functionNameSlice {
|
|
name := strings.Trim(elem[1], " ")
|
|
functionNames = append(functionNames, name)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func generate(data *pluginTemplateContent, tmpl, output string) error {
|
|
file, err := os.Create(output)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("open output file failed")
|
|
return err
|
|
}
|
|
defer file.Close()
|
|
|
|
writer := bufio.NewWriter(file)
|
|
err = template.Must(template.New("debugtalk").Parse(tmpl)).Execute(writer, data)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("execute template parsing failed")
|
|
return err
|
|
}
|
|
|
|
err = writer.Flush()
|
|
if err == nil {
|
|
log.Info().Str("output", output).Msg("generate debugtalk success")
|
|
} else {
|
|
log.Error().Str("output", output).Msg("generate debugtalk failed")
|
|
}
|
|
return err
|
|
}
|
|
|
|
// buildGo builds debugtalk.go to debugtalk.bin
|
|
func buildGo(path string, output string) error {
|
|
functionNames, err := findAllFunctionNames(regexGoFunctionName, path)
|
|
if err != nil {
|
|
return errors.Wrap(err, "find all function names failed")
|
|
}
|
|
// filter main and init function
|
|
var filteredFunctionNames []string
|
|
for _, name := range functionNames {
|
|
if name == "main" || name == "init" {
|
|
continue
|
|
}
|
|
filteredFunctionNames = append(filteredFunctionNames, name)
|
|
}
|
|
|
|
templateContent := &pluginTemplateContent{
|
|
Version: version.VERSION,
|
|
FunctionNames: filteredFunctionNames,
|
|
}
|
|
|
|
pluginDir := filepath.Dir(path)
|
|
err = generate(templateContent, goTemplate, filepath.Join(pluginDir, PluginGoSourceGenFile))
|
|
if err != nil {
|
|
return errors.Wrap(err, "generate hashicorp plugin failed")
|
|
}
|
|
|
|
// check go sdk in tempDir
|
|
if err := builtin.ExecCommandInDir(exec.Command("go", "version"), pluginDir); err != nil {
|
|
return errors.Wrap(err, "go sdk not installed")
|
|
}
|
|
|
|
if !builtin.IsFilePathExists(filepath.Join(pluginDir, "go.mod")) {
|
|
// create go mod
|
|
if err := builtin.ExecCommandInDir(exec.Command("go", "mod", "init", "main"), pluginDir); err != nil {
|
|
return err
|
|
}
|
|
|
|
// download plugin dependency
|
|
// funplugin version should be locked
|
|
funplugin := fmt.Sprintf("github.com/httprunner/funplugin@%s", shared.Version)
|
|
if err := builtin.ExecCommandInDir(exec.Command("go", "get", funplugin), pluginDir); err != nil {
|
|
return errors.Wrap(err, "go get funplugin failed")
|
|
}
|
|
}
|
|
|
|
// add missing and remove unused modules
|
|
if err := builtin.ExecCommandInDir(exec.Command("go", "mod", "tidy"), pluginDir); err != nil {
|
|
return errors.Wrap(err, "go mod tidy failed")
|
|
}
|
|
|
|
// specify output file path
|
|
if output == "" {
|
|
dir, _ := os.Getwd()
|
|
output = filepath.Join(dir, PluginHashicorpGoBuiltFile)
|
|
} else if builtin.IsFolderPathExists(output) {
|
|
output = filepath.Join(output, PluginHashicorpGoBuiltFile)
|
|
}
|
|
outputPath, _ := filepath.Abs(output)
|
|
|
|
// build go plugin to debugtalk.bin
|
|
cmd := exec.Command("go", "build", "-o", outputPath, PluginGoSourceGenFile, filepath.Base(path))
|
|
if err := builtin.ExecCommandInDir(cmd, pluginDir); err != nil {
|
|
return errors.Wrap(err, "go build plugin failed")
|
|
}
|
|
log.Info().Str("output", outputPath).Str("plugin", path).Msg("build go plugin successfully")
|
|
return nil
|
|
}
|
|
|
|
// buildPy completes funppy information in debugtalk.py
|
|
func buildPy(path string, output string) error {
|
|
// check the syntax of debugtalk.py
|
|
err := builtin.ExecCommand("python3", "-m", "py_compile", path)
|
|
if err != nil {
|
|
return errors.Wrap(err, "python plugin syntax invalid")
|
|
}
|
|
|
|
functionNames, err := findAllFunctionNames(regexPyFunctionName, path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
templateContent := &pluginTemplateContent{
|
|
Version: version.VERSION,
|
|
FunctionNames: functionNames,
|
|
}
|
|
|
|
// specify output file path
|
|
if output == "" {
|
|
dir, _ := os.Getwd()
|
|
output = filepath.Join(dir, PluginPySourceGenFile)
|
|
} else if builtin.IsFolderPathExists(output) {
|
|
output = filepath.Join(output, PluginPySourceGenFile)
|
|
}
|
|
|
|
// generate .debugtalk_gen.py
|
|
err = generate(templateContent, pyTemplate, output)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
log.Info().Str("output", output).Str("plugin", path).Msg("build python plugin successfully")
|
|
return nil
|
|
}
|
|
|
|
func BuildPlugin(path string, output string) (err error) {
|
|
ext := filepath.Ext(path)
|
|
switch ext {
|
|
case ".py":
|
|
err = buildPy(path, output)
|
|
case ".go":
|
|
err = buildGo(path, output)
|
|
default:
|
|
return errors.New("type error, expected .py or .go")
|
|
}
|
|
if err != nil {
|
|
log.Error().Err(err).Str("path", path).Msg("build plugin failed")
|
|
os.Exit(1)
|
|
}
|
|
return nil
|
|
}
|