mattermost/server/public/plugin/interface_generator/main.go
Christopher Poile 0502d6b3c5
[MM-68655] Surface RPC errors from plugin hooks (#36414)
* add WithRPCErr hooks (server-facing/internal only)

* zero _returns on RPC failure in WithRPCErr companions

Aligns the WithRPCErr template with the HooksRPCErr godoc contract: when
g.client.Call returns a transport error, gob may have partially decoded the
reply. Reassign _returns to a zero value before destructuring so callers always
receive zeroed outputs alongside a non-nil transport error.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* rename HooksRPCErr to HooksWithRPCErr for naming consistency

Every related symbol uses the WithRPCErr suffix (MessageHasBeenPostedWithRPCErr,
RunMultiPluginHookWithRPCErr, RunMultiHookWithRPCErr, etc.). Aligning the
interface name removes the only outlier and makes the convention uniform.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* rename rpcErrImpl to hooksWithRPCErrImpl

Mirrors the existing hooksImpl/Hooks naming pattern on hooksTimerLayer.

* add supervisor.HooksWithRPCErr() and drop runtime type assertion

The old path did rp.supervisor.Hooks().(HooksWithRPCErr) and handled the
"doesn't implement" branch — but that branch was structurally unreachable
(the compile-time `_ HooksWithRPCErr = (*hooksTimerLayer)(nil)` assertion
guards it).

Change supervisor.hooks from `Hooks` to the concrete `*hooksTimerLayer`
(which implements both interfaces, enforced at field assignment), add a
parallel HooksWithRPCErr() accessor, and call it directly. Hooks() keeps
its public Hooks-interface signature via implicit conversion at return.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* drop "implemented by" clause from HooksWithRPCErr godoc

Both hooksRPCClient and hooksTimerLayer satisfy the interface, and naming
implementations in interface godocs adds rot — the contract is what readers
need, not the list of wrappers.

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 10:41:11 -04:00

755 lines
22 KiB
Go

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package main
import (
"bytes"
"fmt"
"go/ast"
"go/parser"
"go/printer"
"go/token"
"log"
"os"
"os/exec"
"path/filepath"
"slices"
"strings"
"text/template"
"github.com/pkg/errors"
"golang.org/x/tools/imports"
)
var excludedPluginHooks = []string{
"ChannelMemberWillBeAdded",
"FileWillBeUploaded",
"TeamMemberWillBeAdded",
"Implemented",
"LoadPluginConfiguration",
"InstallPlugin",
"LogAuditRec",
"LogAuditRecWithLevel",
"LogDebug",
"LogError",
"LogInfo",
"LogWarn",
"MessageWillBePosted",
"MessageWillBeUpdated",
"MessagesWillBeConsumed",
"OnActivate",
"PluginHTTP",
"ServeHTTP",
"UploadData",
"ReceiveSharedChannelAttachmentSyncMsg",
"ServeMetrics",
}
type IHookEntry struct {
FuncName string
Args *ast.FieldList
Results *ast.FieldList
}
type PluginInterfaceInfo struct {
Hooks []IHookEntry
API []IHookEntry
FileSet *token.FileSet
}
func FieldListToFuncList(fieldList *ast.FieldList, fileset *token.FileSet) string {
result := []string{}
if fieldList == nil || len(fieldList.List) == 0 {
return "()"
}
for _, field := range fieldList.List {
typeNameBuffer := &bytes.Buffer{}
err := printer.Fprint(typeNameBuffer, fileset, field.Type)
if err != nil {
panic(err)
}
typeName := typeNameBuffer.String()
names := []string{}
for _, name := range field.Names {
names = append(names, name.Name)
}
result = append(result, strings.Join(names, ", ")+" "+typeName)
}
return "(" + strings.Join(result, ", ") + ")"
}
// FieldListToFuncListAppendErr renders the return signature with an extra trailing
// `error` (the RPC transport error). Used to emit *WithRPCErr companion signatures.
func FieldListToFuncListAppendErr(fieldList *ast.FieldList, fileset *token.FileSet) string {
if fieldList == nil || len(fieldList.List) == 0 {
return "error"
}
base := FieldListToFuncList(fieldList, fileset)
return "(" + base[1:len(base)-1] + ", error)"
}
func FieldListToNames(fieldList *ast.FieldList, variadicForm bool) string {
result := []string{}
if fieldList == nil || len(fieldList.List) == 0 {
return ""
}
for _, field := range fieldList.List {
for _, name := range field.Names {
paramName := name.Name
if _, ok := field.Type.(*ast.Ellipsis); ok && variadicForm {
paramName = fmt.Sprintf("%s...", paramName)
}
result = append(result, paramName)
}
}
return strings.Join(result, ", ")
}
func FieldListToEncodedErrors(structPrefix string, fieldList *ast.FieldList, fileset *token.FileSet) string {
result := []string{}
if fieldList == nil {
return ""
}
nextLetter := 'A'
for _, field := range fieldList.List {
typeNameBuffer := &bytes.Buffer{}
err := printer.Fprint(typeNameBuffer, fileset, field.Type)
if err != nil {
panic(err)
}
if typeNameBuffer.String() != "error" {
nextLetter++
continue
}
name := ""
if len(field.Names) == 0 {
name = string(nextLetter)
nextLetter++
} else {
for range field.Names {
name += string(nextLetter)
nextLetter++
}
}
result = append(result, structPrefix+name+" = encodableError("+structPrefix+name+")")
}
return strings.Join(result, "\n")
}
func FieldListDestruct(structPrefix string, fieldList *ast.FieldList, fileset *token.FileSet) string {
result := []string{}
if fieldList == nil || len(fieldList.List) == 0 {
return ""
}
nextLetter := 'A'
for _, field := range fieldList.List {
typeNameBuffer := &bytes.Buffer{}
err := printer.Fprint(typeNameBuffer, fileset, field.Type)
if err != nil {
panic(err)
}
typeName := typeNameBuffer.String()
suffix := ""
if strings.HasPrefix(typeName, "...") {
suffix = "..."
}
if len(field.Names) == 0 {
result = append(result, structPrefix+string(nextLetter)+suffix)
nextLetter++
} else {
for range field.Names {
result = append(result, structPrefix+string(nextLetter)+suffix)
nextLetter++
}
}
}
return strings.Join(result, ", ")
}
// FieldListDestructAppendErr is FieldListDestruct with an extra trailing `<prefix>RPCErr`
// variable name appended — used to bind the transport error in *WithRPCErr templates.
func FieldListDestructAppendErr(structPrefix string, fieldList *ast.FieldList, fileset *token.FileSet) string {
base := FieldListDestruct(structPrefix, fieldList, fileset)
rpcErr := structPrefix + "RPCErr"
if base == "" {
return rpcErr
}
return base + ", " + rpcErr
}
func FieldListToRecordSuccess(structPrefix string, fieldList *ast.FieldList) string {
if fieldList == nil || len(fieldList.List) == 0 {
return "true"
}
result := ""
nextLetter := 'A'
for _, field := range fieldList.List {
typeName := baseTypeName(field.Type)
if typeName == "error" || typeName == "AppError" {
result = structPrefix + string(nextLetter)
break
}
nextLetter++
}
if result == "" {
return "true"
}
return fmt.Sprintf("%s == nil", result)
}
// FieldListToRecordSuccessWithRPCErr renders the success-flag expression for a *WithRPCErr
// timer-layer call. It combines the RPC transport check with the base hook's app-error check
// (if any), so the metric counts a call as successful only when both the transport and the
// plugin's application-level return signal success.
func FieldListToRecordSuccessWithRPCErr(structPrefix string, fieldList *ast.FieldList) string {
rpcErr := structPrefix + "RPCErr == nil"
base := FieldListToRecordSuccess(structPrefix, fieldList)
if base == "true" {
return rpcErr
}
return rpcErr + " && " + base
}
func FieldListToStructList(fieldList *ast.FieldList, fileset *token.FileSet) string {
result := []string{}
if fieldList == nil || len(fieldList.List) == 0 {
return ""
}
nextLetter := 'A'
for _, field := range fieldList.List {
typeNameBuffer := &bytes.Buffer{}
err := printer.Fprint(typeNameBuffer, fileset, field.Type)
if err != nil {
panic(err)
}
typeName := typeNameBuffer.String()
if strings.HasPrefix(typeName, "...") {
typeName = strings.Replace(typeName, "...", "[]", 1)
}
if len(field.Names) == 0 {
result = append(result, string(nextLetter)+" "+typeName)
nextLetter++
} else {
for range field.Names {
result = append(result, string(nextLetter)+" "+typeName)
nextLetter++
}
}
}
return strings.Join(result, "\n\t")
}
func baseTypeName(x ast.Expr) string {
switch t := x.(type) {
case *ast.Ident:
return t.Name
case *ast.SelectorExpr:
if _, ok := t.X.(*ast.Ident); ok {
// only possible for qualified type names;
// assume type is imported
return t.Sel.Name
}
case *ast.ParenExpr:
return baseTypeName(t.X)
case *ast.StarExpr:
return baseTypeName(t.X)
}
return ""
}
func goList(dir string) ([]string, error) {
cmd := exec.Command("go", "list", "-f", "{{.Dir}}", dir)
bytes, err := cmd.Output()
if err != nil {
return nil, errors.Wrap(err, "Can't list packages")
}
return strings.Fields(string(bytes)), nil
}
func (info *PluginInterfaceInfo) addHookMethod(method *ast.Field) {
info.Hooks = append(info.Hooks, IHookEntry{
FuncName: method.Names[0].Name,
Args: method.Type.(*ast.FuncType).Params,
Results: method.Type.(*ast.FuncType).Results,
})
}
func (info *PluginInterfaceInfo) addAPIMethod(method *ast.Field) {
info.API = append(info.API, IHookEntry{
FuncName: method.Names[0].Name,
Args: method.Type.(*ast.FuncType).Params,
Results: method.Type.(*ast.FuncType).Results,
})
}
func (info *PluginInterfaceInfo) makeHookInspector() func(node ast.Node) bool {
return func(node ast.Node) bool {
if typeSpec, ok := node.(*ast.TypeSpec); ok {
if typeSpec.Name.Name == "Hooks" {
for _, method := range typeSpec.Type.(*ast.InterfaceType).Methods.List {
info.addHookMethod(method)
}
return false
} else if typeSpec.Name.Name == "API" {
for _, method := range typeSpec.Type.(*ast.InterfaceType).Methods.List {
info.addAPIMethod(method)
}
return false
}
}
return true
}
}
func getPluginInfo(dir string) (*PluginInterfaceInfo, error) {
pluginInfo := &PluginInterfaceInfo{
Hooks: make([]IHookEntry, 0),
FileSet: token.NewFileSet(),
}
packages, err := parser.ParseDir(pluginInfo.FileSet, dir, nil, parser.ParseComments)
if err != nil {
log.Println("Parser error in dir "+dir+": ", err)
return nil, err
}
for _, pkg := range packages {
if pkg.Name != "plugin" {
continue
}
for _, file := range pkg.Files {
ast.Inspect(file, pluginInfo.makeHookInspector())
}
}
return pluginInfo, nil
}
var hooksTemplate = `// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Code generated by "make pluginapi"
// DO NOT EDIT
package plugin
import (
"fmt"
"log"
saml2 "github.com/mattermost/gosaml2"
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/public/shared/mlog"
)
{{range .HooksMethods}}
func init() {
hookNameToId["{{.Name}}"] = {{.Name}}ID
}
type {{.Name | obscure}}Args struct {
{{structStyle .Params}}
}
type {{.Name | obscure}}Returns struct {
{{structStyle .Return}}
}
func (g *hooksRPCClient) {{.Name}}{{funcStyle .Params}} {{funcStyle .Return}} {
_args := &{{.Name | obscure}}Args{ {{valuesOnly .Params}} }
_returns := &{{.Name | obscure}}Returns{}
if g.implemented[{{.Name}}ID] {
if err := g.client.Call("Plugin.{{.Name}}", _args, _returns); err != nil {
g.log.Error("RPC call {{.Name}} to plugin failed.", mlog.Err(err))
}
}
{{ if .Return }} return {{destruct "_returns." .Return}} {{ end }}
}
// {{.Name}}WithRPCErr returns the same values as {{.Name}}, with an additional trailing error
// for the RPC transport — always the LAST return slot.
func (g *hooksRPCClient) {{.Name}}WithRPCErr{{funcStyle .Params}} {{funcStyleAppendErr .Return}} {
_args := &{{.Name | obscure}}Args{ {{valuesOnly .Params}} }
_returns := &{{.Name | obscure}}Returns{}
var _err error
if g.implemented[{{.Name}}ID] {
_err = g.client.Call("Plugin.{{.Name}}", _args, _returns)
if _err != nil {
// Reset _returns so partial gob decoding can't leak non-zero
// values past a transport failure (HooksWithRPCErr contract).
_returns = &{{.Name | obscure}}Returns{}
g.log.Debug("RPC call {{.Name}} to plugin failed.", mlog.Err(_err))
}
}
{{ if .Return }} return {{destruct "_returns." .Return}}, _err {{ else }} return _err {{ end }}
}
func (s *hooksRPCServer) {{.Name}}(args *{{.Name | obscure}}Args, returns *{{.Name | obscure}}Returns) error {
if hook, ok := s.impl.(interface {
{{.Name}}{{funcStyle .Params}} {{funcStyle .Return}}
}); ok {
{{if .Return}}{{destruct "returns." .Return}} = {{end}}hook.{{.Name}}({{destruct "args." .Params}})
{{if .Return}}{{encodeErrors "returns." .Return}}{{end -}}
} else {
return encodableError(fmt.Errorf("Hook {{.Name}} called but not implemented."))
}
return nil
}
{{end}}
// HooksWithRPCErr provides a WithRPCErr variant for every generated hook. The last error return
// is always the RPC transport error — if non-nil, the plugin's other return values are zero. For
// hooks whose base signature already returns error, the tuple is (originalReturns..., rpcErr)
// where the final slot is always transport.
//
// If the plugin does not implement the hook, the companion returns zero values and a nil error —
// indistinguishable from a successful invocation that returned zeros. Callers MUST gate on
// supervisor.Implements(<HookID>) (or use Environment.RunMultiPluginHookWithRPCErr, which gates
// by the iteration's hook ID — note that any *WithRPCErr method called on the closure's
// HooksWithRPCErr is independently subject to its own implemented-gate).
type HooksWithRPCErr interface {
{{range .HooksMethods}}
{{.Name}}WithRPCErr{{funcStyle .Params}} {{funcStyleAppendErr .Return}}
{{end}}
}
{{range .APIMethods}}
type {{.Name | obscure}}Args struct {
{{structStyle .Params}}
}
type {{.Name | obscure}}Returns struct {
{{structStyle .Return}}
}
func (g *apiRPCClient) {{.Name}}{{funcStyle .Params}} {{funcStyle .Return}} {
_args := &{{.Name | obscure}}Args{ {{valuesOnly .Params}} }
_returns := &{{.Name | obscure}}Returns{}
if err := g.client.Call("Plugin.{{.Name}}", _args, _returns); err != nil {
log.Printf("RPC call to {{.Name}} API failed: %s", err.Error())
}
{{ if .Return }} return {{destruct "_returns." .Return}} {{ end }}
}
func (s *apiRPCServer) {{.Name}}(args *{{.Name | obscure}}Args, returns *{{.Name | obscure}}Returns) error {
if hook, ok := s.impl.(interface {
{{.Name}}{{funcStyle .Params}} {{funcStyle .Return}}
}); ok {
{{if .Return}}{{destruct "returns." .Return}} = {{end}}hook.{{.Name}}({{destruct "args." .Params}})
{{if .Return}}{{encodeErrors "returns." .Return}}{{end -}}
} else {
return encodableError(fmt.Errorf("API {{.Name}} called but not implemented."))
}
return nil
}
{{end}}
`
var apiTimerLayerTemplate = `// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Code generated by "make pluginapi"
// DO NOT EDIT
package plugin
import (
"io"
"net/http"
timePkg "time"
"github.com/mattermost/mattermost/server/public/model"
)
type apiTimerLayer struct {
pluginID string
apiImpl API
metrics metricsInterface
}
func (api *apiTimerLayer) recordTime(startTime timePkg.Time, name string, success bool) {
if api.metrics != nil {
elapsedTime := float64(timePkg.Since(startTime)) / float64(timePkg.Second)
api.metrics.ObservePluginAPIDuration(api.pluginID, name, success, elapsedTime)
}
}
{{range .APIMethods}}
func (api *apiTimerLayer) {{.Name}}{{funcStyle .Params}} {{funcStyle .Return}} {
startTime := timePkg.Now()
{{ if .Return }} {{destruct "_returns" .Return}} := {{ end }} api.apiImpl.{{.Name}}({{valuesOnly .Params}})
api.recordTime(startTime, "{{.Name}}", {{ shouldRecordSuccess "_returns" .Return }})
{{ if .Return }} return {{destruct "_returns" .Return}} {{ end -}}
}
{{end}}
`
var hooksTimerLayerTemplate = `// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Code generated by "make pluginapi"
// DO NOT EDIT
package plugin
import (
"io"
"net/http"
timePkg "time"
saml2 "github.com/mattermost/gosaml2"
"github.com/mattermost/mattermost/server/public/model"
)
type hooksTimerLayer struct {
pluginID string
hooksImpl Hooks
hooksWithRPCErrImpl HooksWithRPCErr
metrics metricsInterface
}
func (hooks *hooksTimerLayer) recordTime(startTime timePkg.Time, name string, success bool) {
if hooks.metrics != nil {
elapsedTime := float64(timePkg.Since(startTime)) / float64(timePkg.Second)
hooks.metrics.ObservePluginHookDuration(hooks.pluginID, name, success, elapsedTime)
}
}
{{range .HooksMethods}}
func (hooks *hooksTimerLayer) {{.Name}}{{funcStyle .Params}} {{funcStyle .Return}} {
startTime := timePkg.Now()
{{ if .Return }} {{destruct "_returns" .Return}} := {{ end }} hooks.hooksImpl.{{.Name}}({{valuesOnly .Params}})
hooks.recordTime(startTime, "{{.Name}}", {{ shouldRecordSuccess "_returns" .Return }})
{{ if .Return }} return {{destruct "_returns" .Return}} {{end -}}
}
{{end}}
{{range .HooksMethodsRPCErr}}
func (hooks *hooksTimerLayer) {{.Name}}WithRPCErr{{funcStyle .Params}} {{funcStyleAppendErr .Return}} {
startTime := timePkg.Now()
{{destructAppendErr "_returns" .Return}} := hooks.hooksWithRPCErrImpl.{{.Name}}WithRPCErr({{valuesOnly .Params}})
hooks.recordTime(startTime, "{{.Name}}WithRPCErr", {{ shouldRecordSuccessWithRPCErr "_returns" .Return }})
return {{destructAppendErr "_returns" .Return}}
}
{{end}}
`
type MethodParams struct {
Name string
Params *ast.FieldList
Return *ast.FieldList
}
type HooksTemplateParams struct {
HooksMethods []MethodParams
HooksMethodsRPCErr []MethodParams
APIMethods []MethodParams
}
func generateHooksGlue(info *PluginInterfaceInfo) {
templateFunctions := map[string]any{
"funcStyle": func(fields *ast.FieldList) string { return FieldListToFuncList(fields, info.FileSet) },
"funcStyleAppendErr": func(fields *ast.FieldList) string { return FieldListToFuncListAppendErr(fields, info.FileSet) },
"structStyle": func(fields *ast.FieldList) string { return FieldListToStructList(fields, info.FileSet) },
"valuesOnly": func(fields *ast.FieldList) string { return FieldListToNames(fields, false) },
"encodeErrors": func(structPrefix string, fields *ast.FieldList) string {
return FieldListToEncodedErrors(structPrefix, fields, info.FileSet)
},
"destruct": func(structPrefix string, fields *ast.FieldList) string {
return FieldListDestruct(structPrefix, fields, info.FileSet)
},
"shouldRecordSuccess": func(structPrefix string, fields *ast.FieldList) string {
return FieldListToRecordSuccess(structPrefix, fields)
},
"obscure": func(name string) string {
return "Z_" + name
},
}
hooksTemplate, err := template.New("hooks").Funcs(templateFunctions).Parse(hooksTemplate)
if err != nil {
panic(err)
}
templateParams := HooksTemplateParams{}
for _, hook := range info.Hooks {
templateParams.HooksMethods = append(templateParams.HooksMethods, MethodParams{
Name: hook.FuncName,
Params: hook.Args,
Return: hook.Results,
})
}
for _, api := range info.API {
templateParams.APIMethods = append(templateParams.APIMethods, MethodParams{
Name: api.FuncName,
Params: api.Args,
Return: api.Results,
})
}
templateResult := &bytes.Buffer{}
err = hooksTemplate.Execute(templateResult, &templateParams)
if err != nil {
panic(err)
}
formatted, err := imports.Process("", templateResult.Bytes(), nil)
if err != nil {
panic(err)
}
if err := os.WriteFile(filepath.Join(getPluginPackageDir(), "client_rpc_generated.go"), formatted, 0664); err != nil {
panic(err)
}
}
func generatePluginTimerLayer(info *PluginInterfaceInfo) {
templateFunctions := map[string]any{
"funcStyle": func(fields *ast.FieldList) string { return FieldListToFuncList(fields, info.FileSet) },
"funcStyleAppendErr": func(fields *ast.FieldList) string { return FieldListToFuncListAppendErr(fields, info.FileSet) },
"structStyle": func(fields *ast.FieldList) string { return FieldListToStructList(fields, info.FileSet) },
"valuesOnly": func(fields *ast.FieldList) string { return FieldListToNames(fields, true) },
"destruct": func(structPrefix string, fields *ast.FieldList) string {
return FieldListDestruct(structPrefix, fields, info.FileSet)
},
"destructAppendErr": func(structPrefix string, fields *ast.FieldList) string {
return FieldListDestructAppendErr(structPrefix, fields, info.FileSet)
},
"shouldRecordSuccess": func(structPrefix string, fields *ast.FieldList) string {
return FieldListToRecordSuccess(structPrefix, fields)
},
"shouldRecordSuccessWithRPCErr": func(structPrefix string, fields *ast.FieldList) string {
return FieldListToRecordSuccessWithRPCErr(structPrefix, fields)
},
}
// Prepare template params. The timer layer wraps the full Hooks interface, so
// HooksMethods includes excluded hooks too. *WithRPCErr companions only exist
// for non-excluded hooks (see HooksWithRPCErr in client_rpc_generated.go), so the
// excluded subset is filtered into HooksMethodsRPCErr for that loop.
excluded := func(name string) bool { return slices.Contains(excludedPluginHooks, name) }
templateParams := HooksTemplateParams{}
for _, hook := range info.Hooks {
mp := MethodParams{
Name: hook.FuncName,
Params: hook.Args,
Return: hook.Results,
}
templateParams.HooksMethods = append(templateParams.HooksMethods, mp)
if !excluded(hook.FuncName) {
templateParams.HooksMethodsRPCErr = append(templateParams.HooksMethodsRPCErr, mp)
}
}
for _, api := range info.API {
templateParams.APIMethods = append(templateParams.APIMethods, MethodParams{
Name: api.FuncName,
Params: api.Args,
Return: api.Results,
})
}
pluginTemplates := map[string]string{
"api_timer_layer_generated.go": apiTimerLayerTemplate,
"hooks_timer_layer_generated.go": hooksTimerLayerTemplate,
}
for fileName, presetTemplate := range pluginTemplates {
parsedTemplate, err := template.New("hooks").Funcs(templateFunctions).Parse(presetTemplate)
if err != nil {
panic(err)
}
templateResult := &bytes.Buffer{}
err = parsedTemplate.Execute(templateResult, &templateParams)
if err != nil {
panic(err)
}
formatted, err := imports.Process("", templateResult.Bytes(), nil)
if err != nil {
panic(err)
}
if err := os.WriteFile(filepath.Join(getPluginPackageDir(), fileName), formatted, 0664); err != nil {
panic(err)
}
}
}
func getPluginPackageDir() string {
dirs, err := goList("github.com/mattermost/mattermost/server/public/plugin")
if err != nil {
panic(err)
} else if len(dirs) != 1 {
panic("More than one package dir, or no dirs!")
}
return dirs[0]
}
func removeExcluded(info *PluginInterfaceInfo, excluded []string) *PluginInterfaceInfo {
newIface := &PluginInterfaceInfo{
FileSet: info.FileSet,
}
toBeExcluded := func(item string) bool {
return slices.Contains(excluded, item)
}
hooksResult := make([]IHookEntry, 0, len(info.Hooks))
for _, hook := range info.Hooks {
if !toBeExcluded(hook.FuncName) {
hooksResult = append(hooksResult, hook)
}
}
newIface.Hooks = hooksResult
apiResult := make([]IHookEntry, 0, len(info.API))
for _, api := range info.API {
if !toBeExcluded(api.FuncName) {
apiResult = append(apiResult, api)
}
}
newIface.API = apiResult
return newIface
}
func main() {
pluginPackageDir := getPluginPackageDir()
forRPC, err := getPluginInfo(pluginPackageDir)
if err != nil {
fmt.Println("Unable to get plugin info: " + err.Error())
}
log.Println("Generating plugin hooks glue")
generateHooksGlue(removeExcluded(forRPC, excludedPluginHooks))
// Generate plugin timer layers
log.Println("Generating plugin timer glue")
forPlugins, err := getPluginInfo(pluginPackageDir)
if err != nil {
fmt.Println("Unable to get plugin info: " + err.Error())
}
generatePluginTimerLayer(forPlugins)
}