Commit a9213c23 authored by vipwzw's avatar vipwzw

add gencode tool

parent c3d16139
// Copyright Fuzamei Corp. 2018 All Rights Reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package commands
import (
"fmt"
"github.com/33cn/chain33/cmd/tools/strategy"
"github.com/33cn/chain33/cmd/tools/types"
"github.com/spf13/cobra"
)
//GenDappCmd advance cmd
func GenDappCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "gendapp",
Short: "Auto generate dapp basic code",
Run: genDapp,
}
addGenDappFlag(cmd)
return cmd
}
func addGenDappFlag(cmd *cobra.Command) {
cmd.Flags().StringP("name", "n", "", "dapp name")
cmd.MarkFlagRequired("name")
cmd.Flags().StringP("proto", "p", "", "dapp protobuf file path")
cmd.MarkFlagRequired("proto")
cmd.Flags().StringP("output", "o", "", "go package for output (default github.com/33cn/plugin/plugin/dapp/)")
}
func genDapp(cmd *cobra.Command, args []string) {
dappName, _ := cmd.Flags().GetString("name")
outDir, _ := cmd.Flags().GetString("output")
propFile, _ := cmd.Flags().GetString("proto")
s := strategy.New(types.KeyGenDapp)
if s == nil {
fmt.Println(types.KeyGenDapp, "Not support")
return
}
s.SetParam(types.KeyExecutorName, dappName)
s.SetParam(types.KeyDappOutDir, outDir)
s.SetParam(types.KeyProtobufFile, propFile)
s.Run()
}
// Copyright Fuzamei Corp. 2018 All Rights Reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package base
var (
//CodeFileManager 类型文件
CodeFileManager = map[string][]ICodeFile{}
)
// ICodeFile code file interface
type ICodeFile interface {
GetCodeType() string
GetDirName() string
GetFiles() map[string]string //key:filename, val:file content
GetReplaceTags() []string
}
//RegisterCodeFile regeister code file
func RegisterCodeFile(filer ICodeFile) {
codeType := filer.GetCodeType()
fileArr := CodeFileManager[codeType]
fileArr = append(fileArr, filer)
CodeFileManager[codeType] = fileArr
}
// CodeFile 基础类
type CodeFile struct {
}
//GetCodeType get cody type
func (CodeFile) GetCodeType() string {
return ""
}
//GetDirName get directory name
func (CodeFile) GetDirName() string {
return ""
}
//GetFiles get files
func (CodeFile) GetFiles() map[string]string {
return nil
}
//GetReplaceTags get replace tags
func (CodeFile) GetReplaceTags() []string {
return nil
}
// Copyright Fuzamei Corp. 2018 All Rights Reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package base
// DappCodeFile dapp code source
type DappCodeFile struct {
CodeFile
}
// GetCodeType get code type
func (DappCodeFile) GetCodeType() string {
return "dapp"
}
// Copyright Fuzamei Corp. 2018 All Rights Reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package gencode
const (
//ProtoFileAppendService proto文件service
ProtoFileAppendService = `
service ${EXECNAME} {
}`
)
// Copyright Fuzamei Corp. 2018 All Rights Reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package cmd
import (
"github.com/33cn/chain33/cmd/tools/gencode/base"
)
func init() {
base.RegisterCodeFile(cmdCodeFile{})
}
type cmdCodeFile struct {
base.DappCodeFile
}
func (cmdCodeFile) GetDirName() string {
return "cmd"
}
func (cmdCodeFile) GetFiles() map[string]string {
return map[string]string{
buildShellName: buildShellContent,
makeFIleName: makeFileContent,
}
}
var (
buildShellName = "build.sh"
buildShellContent = `#!/bin/sh
# 官方ci集成脚本
strpwd=$(pwd)
strcmd=${strpwd##*dapp/}
strapp=${strcmd%/cmd*}
OUT_DIR="${1}/$strapp"
#FLAG=$2
mkdir -p "${OUT_DIR}"
cp ./build/* "${OUT_DIR}"
`
makeFIleName = "Makefile"
makeFileContent = `all:
chmod +x ./build.sh
./build.sh $(OUT) $(FLAG)
`
)
// Copyright Fuzamei Corp. 2018 All Rights Reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package commands
import (
"github.com/33cn/chain33/cmd/tools/gencode/base"
"github.com/33cn/chain33/cmd/tools/types"
)
func init() {
base.RegisterCodeFile(commandsCodeFile{})
}
type commandsCodeFile struct {
base.DappCodeFile
}
func (c commandsCodeFile) GetDirName() string {
return "commands"
}
func (c commandsCodeFile) GetFiles() map[string]string {
return map[string]string{
commandsFileName: commandsFileContent,
}
}
func (c commandsCodeFile) GetReplaceTags() []string {
return []string{types.TagExecName}
}
var (
commandsFileName = "commands.go"
commandsFileContent = `/*Package commands implement dapp client commands*/
package commands
import "github.com/spf13/cobra"
/*
* 实现合约对应客户端
*/
// Cmd ${EXECNAME} client command
func Cmd() *cobra.Command {
cmd := &cobra.Command{
Use: "${EXECNAME}",
Short: "${EXECNAME} command",
Args: cobra.MinimumNArgs(1),
}
cmd.AddCommand(
//add sub command
)
return cmd
}
`
)
// Copyright Fuzamei Corp. 2018 All Rights Reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package executor
import (
"github.com/33cn/chain33/cmd/tools/gencode/base"
"github.com/33cn/chain33/cmd/tools/types"
)
func init() {
base.RegisterCodeFile(execCode{})
base.RegisterCodeFile(execLocalCode{})
base.RegisterCodeFile(execDelLocalCode{})
}
type execCode struct {
executorCodeFile
}
func (execCode) GetFiles() map[string]string {
return map[string]string{
execName: execContent,
}
}
func (execCode) GetReplaceTags() []string {
return []string{types.TagExecName, types.TagImportPath, types.TagClassName, types.TagExecFileContent}
}
type execLocalCode struct {
executorCodeFile
}
func (execLocalCode) GetFiles() map[string]string {
return map[string]string{
execLocalName: execLocalContent,
}
}
func (execLocalCode) GetReplaceTags() []string {
return []string{types.TagExecName, types.TagImportPath, types.TagExecLocalFileContent}
}
type execDelLocalCode struct {
executorCodeFile
}
func (execDelLocalCode) GetFiles() map[string]string {
return map[string]string{
execDelName: execDelContent,
}
}
func (execDelLocalCode) GetReplaceTags() []string {
return []string{types.TagExecName, types.TagImportPath, types.TagExecDelLocalFileContent}
}
var (
execName = "exec.go"
execContent = `package executor
import (
ptypes "${IMPORTPATH}/${EXECNAME}/types"
"github.com/33cn/chain33/types"
)
/*
* 实现交易的链上执行接口
* 关键数据上链(statedb)并生成交易回执(log)
*/
${EXECFILECONTENT}`
execLocalName = "exec_local.go"
execLocalContent = `package executor
import (
ptypes "${IMPORTPATH}/${EXECNAME}/types"
"github.com/33cn/chain33/types"
)
/*
* 实现交易相关数据本地执行,数据不上链
* 非关键数据,本地存储(localDB), 用于辅助查询,效率高
*/
${EXECLOCALFILECONTENT}`
execDelName = "exec_del_local.go"
execDelContent = `package executor
import (
ptypes "${IMPORTPATH}/${EXECNAME}/types"
"github.com/33cn/chain33/types"
)
/*
* 实现区块回退时本地执行的数据清除
*/
${EXECDELLOCALFILECONTENT}`
)
// Copyright Fuzamei Corp. 2018 All Rights Reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package executor
import (
"github.com/33cn/chain33/cmd/tools/gencode/base"
"github.com/33cn/chain33/cmd/tools/types"
)
func init() {
base.RegisterCodeFile(executorCodeFile{})
}
type executorCodeFile struct {
base.DappCodeFile
}
func (c executorCodeFile) GetDirName() string {
return "executor"
}
func (c executorCodeFile) GetFiles() map[string]string {
return map[string]string{
executorName: executorContent,
kvName: kvContent,
}
}
func (c executorCodeFile) GetReplaceTags() []string {
return []string{types.TagExecName, types.TagImportPath, types.TagClassName}
}
var (
executorName = "${EXECNAME}.go"
executorContent = `package executor
import (
log "github.com/33cn/chain33/common/log/log15"
ptypes "${IMPORTPATH}/${EXECNAME}/types"
drivers "github.com/33cn/chain33/system/dapp"
"github.com/33cn/chain33/types"
)
/*
* 执行器相关定义
* 重载基类相关接口
*/
var (
//日志
elog = log.New("module", "execs.${EXECNAME}")
)
var driverName = ptypes.${CLASSNAME}X
func init() {
ety := types.LoadExecutorType(driverName)
ety.InitFuncList(types.ListMethod(&${EXECNAME}{}))
}
// Init register dapp
func Init(name string, sub []byte) {
drivers.Register(GetName(), new${CLASSNAME}, types.GetDappFork(driverName, "Enable"))
}
type ${EXECNAME} struct {
drivers.DriverBase
}
func new${CLASSNAME}() drivers.Driver {
t := &${EXECNAME}{}
t.SetChild(t)
t.SetExecutorType(types.LoadExecutorType(driverName))
return t
}
// GetName get driver name
func GetName() string {
return new${CLASSNAME}().GetName()
}
func (*${EXECNAME}) GetDriverName() string {
return driverName
}
// CheckTx 实现自定义检验交易接口,供框架调用
func (*${EXECNAME}) CheckTx(tx *types.Transaction, index int) error {
// implement code
return nil
}
`
kvName = "kv.go"
kvContent = `package executor
/*
* 用户合约存取kv数据时,key值前缀需要满足一定规范
* 即key = keyPrefix + userKey
* 需要字段前缀查询时,使用’-‘作为分割符号
*/
var (
//KeyPrefixStateDB state db key必须前缀
KeyPrefixStateDB = "mavl-${EXECNAME}-"
//KeyPrefixLocalDB local db的key必须前缀
KeyPrefixLocalDB = "LODB-${EXECNAME}-"
)
`
)
// Copyright Fuzamei Corp. 2018 All Rights Reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package dappcode
import (
_ "github.com/33cn/chain33/cmd/tools/gencode/dappcode/cmd" //init cmd
_ "github.com/33cn/chain33/cmd/tools/gencode/dappcode/commands" // init command
_ "github.com/33cn/chain33/cmd/tools/gencode/dappcode/executor" // init executor
_ "github.com/33cn/chain33/cmd/tools/gencode/dappcode/proto" // init proto
_ "github.com/33cn/chain33/cmd/tools/gencode/dappcode/rpc" // init rpc
_ "github.com/33cn/chain33/cmd/tools/gencode/dappcode/types" // init types
)
// Copyright Fuzamei Corp. 2018 All Rights Reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package dappcode
import (
"github.com/33cn/chain33/cmd/tools/gencode/base"
"github.com/33cn/chain33/cmd/tools/types"
)
func init() {
base.RegisterCodeFile(pluginCodeFile{})
}
type pluginCodeFile struct {
base.DappCodeFile
}
func (c pluginCodeFile) GetFiles() map[string]string {
return map[string]string{
pluginName: pluginContent,
}
}
func (c pluginCodeFile) GetReplaceTags() []string {
return []string{types.TagExecName, types.TagImportPath, types.TagClassName}
}
var (
pluginName = "plugin.go"
pluginContent = `
package ${EXECNAME}
import (
"${IMPORTPATH}/${EXECNAME}/commands"
"${IMPORTPATH}/${EXECNAME}/types"
"${IMPORTPATH}/${EXECNAME}/executor"
"${IMPORTPATH}/${EXECNAME}/rpc"
"github.com/33cn/chain33/pluginmgr"
)
/*
* 初始化dapp相关的组件
*/
func init() {
pluginmgr.Register(&pluginmgr.PluginBase{
Name: types.${CLASSNAME}X,
ExecName: executor.GetName(),
Exec: executor.Init,
Cmd: commands.Cmd,
RPC: rpc.Init,
})
}`
)
// Copyright Fuzamei Corp. 2018 All Rights Reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package proto
import (
"github.com/33cn/chain33/cmd/tools/gencode/base"
"github.com/33cn/chain33/cmd/tools/types"
)
func init() {
base.RegisterCodeFile(protoBase{})
base.RegisterCodeFile(protoFile{})
}
type protoBase struct {
base.DappCodeFile
}
func (protoBase) GetDirName() string {
return "proto"
}
func (protoBase) GetFiles() map[string]string {
return map[string]string{
protoShellName: protoShellContent,
makeName: makeContent,
}
}
type protoFile struct {
protoBase
}
func (protoFile) GetFiles() map[string]string {
return map[string]string{
protoFileName: protoFileContent,
}
}
func (protoFile) GetReplaceTags() []string {
return []string{types.TagProtoFileContent, types.TagProtoFileAppend, types.TagExecName}
}
var (
protoShellName = "create_protobuf.sh"
protoShellContent = `#!/bin/sh
# proto生成命令,将pb.go文件生成到types目录下
protoc --go_out=plugins=grpc:../types ./*.proto
`
makeName = "Makefile"
makeContent = `all:
./create_protobuf.sh
`
protoFileName = "${EXECNAME}.proto"
protoFileContent = `${PROTOFILECONTENT}
${PROTOFILEAPPEND}`
)
// Copyright Fuzamei Corp. 2018 All Rights Reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package rpc
import (
"github.com/33cn/chain33/cmd/tools/gencode/base"
"github.com/33cn/chain33/cmd/tools/types"
)
func init() {
base.RegisterCodeFile(rpcCodeFile{})
}
type rpcCodeFile struct {
base.DappCodeFile
}
func (c rpcCodeFile) GetDirName() string {
return "rpc"
}
func (c rpcCodeFile) GetFiles() map[string]string {
return map[string]string{
rpcName: rpcContent,
typesName: typesContent,
}
}
func (c rpcCodeFile) GetReplaceTags() []string {
return []string{types.TagExecName, types.TagImportPath, types.TagClassName}
}
var (
rpcName = "rpc.go"
rpcContent = `package rpc
/*
* 实现json rpc和grpc service接口
* json rpc用Jrpc结构作为接收实例
* grpc使用channelClient结构作为接收实例
*/
`
typesName = "types.go"
typesContent = `package rpc
import (
ptypes "${IMPORTPATH}/${EXECNAME}/types"
rpctypes "github.com/33cn/chain33/rpc/types"
)
/*
* rpc相关结构定义和初始化
*/
// 实现grpc的service接口
type channelClient struct {
rpctypes.ChannelClient
}
// Jrpc 实现json rpc调用实例
type Jrpc struct {
cli *channelClient
}
// Grpc grpc
type Grpc struct {
*channelClient
}
// Init init rpc
func Init(name string, s rpctypes.RPCServer) {
cli := &channelClient{}
grpc := &Grpc{channelClient: cli}
cli.Init(name, s, &Jrpc{cli: cli}, grpc)
//存在grpc service时注册grpc server,需要生成对应的pb.go文件
ptypes.Register${CLASSNAME}Server(s.GRPC(), grpc)
}`
)
// Copyright Fuzamei Corp. 2018 All Rights Reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package types
import (
"github.com/33cn/chain33/cmd/tools/gencode/base"
"github.com/33cn/chain33/cmd/tools/types"
)
func init() {
base.RegisterCodeFile(typesCode{})
}
type typesCode struct {
base.DappCodeFile
}
func (c typesCode) GetDirName() string {
return "types"
}
func (c typesCode) GetFiles() map[string]string {
return map[string]string{
typesName: typesContent,
}
}
func (c typesCode) GetReplaceTags() []string {
return []string{types.TagExecName, types.TagClassName,
types.TagActionIDText, types.TagTyLogActionType,
types.TagLogMapText, types.TagTypeMapText}
}
var (
typesName = "${EXECNAME}.go"
typesContent = `package types
import (
"encoding/json"
"github.com/33cn/chain33/types"
)
/*
* 交易相关类型定义
* 交易action通常有对应的log结构,用于交易回执日志记录
* 每一种action和log需要用id数值和name名称加以区分
*/
// action类型id值
${ACTIONIDTEXT}
// log类型id值
${TYLOGACTIONTYPE}
var (
//${CLASSNAME}X 执行器名称定义
${CLASSNAME}X = "${EXECNAME}"
//定义action的name和id
actionMap = ${TYPEMAPTEXT}
//定义log的id和具体log类型及名称,填入具体自定义log类型
logMap = ${LOGMAPTEXT}
)
func init() {
types.AllowUserExec = append(types.AllowUserExec, []byte(${CLASSNAME}X))
types.RegistorExecutor(${CLASSNAME}X, newType())
}
type ${EXECNAME}Type struct {
types.ExecTypeBase
}
func newType() *${EXECNAME}Type {
c := &${EXECNAME}Type{}
c.SetChild(c)
return c
}
// GetPayload 获取合约action结构
func (t *${EXECNAME}Type) GetPayload() types.Message {
return &${CLASSNAME}Action{}
}
// GeTypeMap 获取合约action的id和name信息
func (t *${EXECNAME}Type) GetTypeMap() map[string]int32 {
return actionMap
}
// GetLogMap 获取合约log相关信息
func (t *${EXECNAME}Type) GetLogMap() map[int64]*types.LogInfo {
return logMap
}
// CreateTx 重载基类接口,实现本合约交易创建,供框架调用
func (t *${EXECNAME}Type) CreateTx(action string, message json.RawMessage) (*types.Transaction, error) {
var tx *types.Transaction
// pseudo code
//if action == someAction
//return new tx
return tx, types.ErrNotSupport
}
`
)
// Copyright Fuzamei Corp. 2018 All Rights Reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package gencode
import (
"github.com/33cn/chain33/cmd/tools/gencode/base"
_ "github.com/33cn/chain33/cmd/tools/gencode/dappcode" //init dapp code
)
//GetCodeFilesWithType get code file with type
func GetCodeFilesWithType(typeName string) []base.ICodeFile {
if fileArr, ok := base.CodeFileManager[typeName]; ok {
return fileArr
}
return nil
}
# chain33 gendapp
根据定义的合约protobuf原型文件,自动生成chain33 dapp基本代码
### 编译
```
//本地存在chain33代码,该步骤可省略
$ git clone https://github.com/33cn/chain33.git $GOPATH/src/github.com/33cn/chain33
//编译chain33 tools
$ go build -i -o $GOPATH/bin/chain33-tool github.com/33cn/chain33/cmd/tools
```
### 使用
```
//查看命令使用方法
$ chain33-tool gendapp --help
Usage:
tools gendapp [flags]
Flags:
-h, --help help for gendapp
-n, --name string dapp name
-o, --output string go package for output (default github.com/33cn/plugin/plugin/dapp/)
-p, --proto string dapp protobuf file path
```
* -n 指定合约名字,不能含有空格和特殊字符
* -p 指定合约的protobuf文件
* -o 生成代码的输出目录路径,此处是go包路径,及相对于$GOPATH/src的路径,
默认为官方项目路径($GOPATH/src/github.com/33cn/plugin/plugin/dapp/)
举例:
```
// 默认路径生成名为demo的合约代码
$ chain33-tool gendapp -n demo -p ./demo.proto
// 指定输出包路径
$ chain33-tool gendapp -n demo -p ./demo.proto -o github.com/33cn/chain33/plugin/dapp/
//生成proto
cd proto && chmod +x ./create_protobuf.sh && make
```
### proto规范
* 定义合约交易行为结构,采用**oneof value**形式,且名称必须为**NameAction**格式,
如demo合约,定义echo和hello两种交易行为
```
message DemoAction {
oneof value {
DemoHello hello = 1;
DemoEcho echo = 2;
}
int32 ty = 3;
}
```
* 定义service,直接以合约名作为名称
```
service demo {
}
```
### 代码
#####目录结构,以demo合约为例
```
demo
├── cmd //包含官方ci集成相关脚本
│   ├── build.sh
│   └── Makefile
├── commands //合约客户端模块
│   └── commands.go
├── executor //执行器模块
│   ├── demo.go
│   ├── exec_del_local.go
│   ├── exec.go
│   ├── exec_local.go
│   └── kv.go
├── plugin.go
├── proto //proto文件及生成pb.go命令
│   ├── create_protobuf.sh
│   ├── demo.proto
│   └── Makefile
├── rpc //rpc模块
│   ├── rpc.go
│   └── types.go
└── types //类型模块
└── demo.go
```
##### 后续开发
在生成代码基础上,需要实现交易创建,执行,及所需rpc服务,初次开发可以参考官方的echo合约
> github.com/33cn/plugin/plugin/dapp/echo
// Copyright Fuzamei Corp. 2018 All Rights Reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package strategy
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/33cn/chain33/cmd/tools/tasks"
"github.com/33cn/chain33/cmd/tools/types"
"github.com/33cn/chain33/cmd/tools/util"
)
type genDappStrategy struct {
strategyBasic
dappName string
dappDir string
dappProto string
packagePath string
}
func (ad *genDappStrategy) Run() error {
fmt.Println("Begin generate chain33 dapp code.")
defer fmt.Println("End generate chain33 dapp code.")
if !ad.initMember() {
return fmt.Errorf("InitError")
}
return ad.runImpl()
}
func (ad *genDappStrategy) checkParamValid() bool {
return true
}
func (ad *genDappStrategy) initMember() bool {
dappName, _ := ad.getParam(types.KeyExecutorName)
outDir, _ := ad.getParam(types.KeyDappOutDir)
protoPath, _ := ad.getParam(types.KeyProtobufFile)
//统一转为小写字母
dappName = strings.ToLower(dappName)
if strings.Contains(dappName, " ") {
mlog.Error("InitGenDapp", "Err", "invalid dapp name", "name", dappName)
return false
}
goPath := os.Getenv("GOPATH")
if goPath == "" {
mlog.Error("InitGenDapp", "Err", "$GOPATH not exist")
return false
}
// 默认输出到plugin项目的plugin/dapp/目录下
if outDir == "" {
outDir = filepath.Join("github.com", "33cn", "plugin", "plugin", "dapp")
}
//兼容win 反斜杠路径
packPath := strings.Replace(filepath.Join(outDir), string(filepath.Separator), "/", -1)
//绝对路径
dappRootDir := filepath.Join(goPath, "src", outDir, dappName)
//check dapp output directory exist
if util.CheckPathExisted(dappRootDir) {
mlog.Error("InitGenDapp", "Err", "generate dapp directory exist", "Dir", dappRootDir)
return false
}
if protoPath != "" {
bExist, _ := util.CheckFileExists(protoPath)
if !bExist {
mlog.Error("InitGenDapp", "Err", "specified proto file not exist", "ProtoFile", protoPath)
return false
}
}
err := os.MkdirAll(dappRootDir, os.ModePerm)
if err != nil {
mlog.Error("GenDappDir", "Err", err, "dir", dappRootDir)
return false
}
ad.dappName = dappName
ad.dappDir = dappRootDir
ad.dappProto = protoPath
ad.packagePath = packPath
return true
}
func (ad *genDappStrategy) runImpl() error {
var err error
tashSlice := ad.buildTask()
for _, task := range tashSlice {
err = task.Execute()
if err != nil {
mlog.Error("GenDappExecTaskFailed.", "error", err, "taskname", task.GetName())
break
}
}
return err
}
func (ad *genDappStrategy) buildTask() []tasks.Task {
taskSlice := make([]tasks.Task, 0)
taskSlice = append(taskSlice,
&tasks.GenDappCodeTask{
DappName: ad.dappName,
DappDir: ad.dappDir,
ProtoFile: ad.dappProto,
PackagePath: ad.packagePath,
},
&tasks.FormatDappSourceTask{
OutputFolder: ad.dappDir,
},
)
return taskSlice
}
// Copyright Fuzamei Corp. 2018 All Rights Reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package tasks
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/33cn/chain33/cmd/tools/gencode"
"github.com/33cn/chain33/cmd/tools/types"
util2 "github.com/33cn/chain33/util"
"github.com/33cn/chain33/cmd/tools/util"
)
// GenDappCodeTask 通过生成好的pb.go和预先设计的模板,生成反射程序源码
type GenDappCodeTask struct {
TaskBase
DappName string
DappDir string
ProtoFile string
PackagePath string
replacePairs map[string]string
}
//GetName 获取name
func (c *GenDappCodeTask) GetName() string {
return "GenDappCodeTask"
}
//Execute 执行
func (c *GenDappCodeTask) Execute() error {
mlog.Info("Execute generate dapp code task.")
c.replacePairs = make(map[string]string)
pbContext, err := util.ReadFile(c.ProtoFile)
if err != nil {
mlog.Error("ReadProtoFile", "Err", err.Error(), "proto", c.ProtoFile)
return fmt.Errorf("ReadProtoFileErr:%s", err.Error())
}
pbContent := string(pbContext)
if err = c.calcReplacePairs(pbContent); err != nil {
mlog.Error("CalcReplacePairs", "Err", err.Error())
return fmt.Errorf("CalcReplacePairsErr:%s", err.Error())
}
if err = c.genDappCode(); err != nil {
return fmt.Errorf("GenDappCodeErr:%s", err.Error())
}
return err
}
func (c *GenDappCodeTask) calcReplacePairs(pbContent string) error {
dapp := strings.ToLower(c.DappName)
className, _ := util2.MakeStringToUpper(dapp, 0, 1)
c.replacePairs[types.TagExecName] = dapp
c.replacePairs[types.TagClassName] = className
c.replacePairs[types.TagImportPath] = c.PackagePath
pbAppend := gencode.ProtoFileAppendService
if strings.Contains(pbContent, "service") {
pbAppend = ""
}
c.replacePairs[types.TagProtoFileContent] = pbContent
c.replacePairs[types.TagProtoFileAppend] = pbAppend
actionName := className + "Action"
actionInfos, err := readDappActionFromProto(pbContent, actionName)
if err != nil {
return fmt.Errorf("ReadProtoActionErr:%s", err.Error())
}
//exec
c.replacePairs[types.TagExecFileContent] = formatExecContent(actionInfos, dapp)
c.replacePairs[types.TagExecLocalFileContent] = formatExecLocalContent(actionInfos, dapp)
c.replacePairs[types.TagExecDelLocalFileContent] = formatExecDelLocalContent(actionInfos, dapp)
//types
c.replacePairs[types.TagTyLogActionType] = buildActionLogTypeText(actionInfos, className)
c.replacePairs[types.TagActionIDText] = buildActionIDText(actionInfos, className)
c.replacePairs[types.TagLogMapText] = buildLogMapText()
c.replacePairs[types.TagTypeMapText] = buildTypeMapText(actionInfos, className)
return nil
}
func (c *GenDappCodeTask) genDappCode() error {
codeTypes := gencode.GetCodeFilesWithType("dapp")
for _, code := range codeTypes {
dirPath := filepath.Join(c.DappDir, code.GetDirName())
_ = os.Mkdir(dirPath, os.ModePerm)
files := code.GetFiles()
tags := code.GetReplaceTags()
for name, content := range files {
for _, tag := range tags {
name = strings.Replace(name, tag, c.replacePairs[tag], -1)
content = strings.Replace(content, tag, c.replacePairs[tag], -1)
}
_, err := util.WriteStringToFile(filepath.Join(dirPath, name), content)
if err != nil {
mlog.Error("GenNewCodeFile", "Err", err.Error(), "CodeFile", filepath.Join(dirPath, name))
return err
}
}
}
return nil
}
// Copyright Fuzamei Corp. 2018 All Rights Reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package tasks
import (
"fmt"
"regexp"
"strings"
sysutil "github.com/33cn/chain33/util"
)
type actionInfoItem struct {
memberName string
memberType string
}
/**
通过正则获取Action的成员变量名和类型,其具体操作步骤如下:
1. 读取需要解析的proto文件
2. 通过搜索,定位到指定Action的起始为止
3. 使用正则获取该Action中的oneof Value的内容
4. 使用正则解析oneof Value中的内容,获取变量名和类型名
5. 将获取到的变量名去除空格,并将首字母大写
*/
func readDappActionFromProto(protoContent, actionName string) ([]*actionInfoItem, error) {
// 如果文件中含有与ActionName部分匹配的文字,则会造成搜索到多个
index := strings.Index(protoContent, actionName)
if index < 0 {
return nil, fmt.Errorf("action %s Not Existed", actionName)
}
expr := fmt.Sprintf(`\s*oneof\s+value\s*{\s+([\w\s=;]*)\}`)
reg := regexp.MustCompile(expr)
oneOfValueStrs := reg.FindAllStringSubmatch(protoContent, index)
expr = fmt.Sprintf(`\s+(\w+)([\s\w]+)=\s+(\d+);`)
reg = regexp.MustCompile(expr)
members := reg.FindAllStringSubmatch(oneOfValueStrs[0][0], -1)
actionInfos := make([]*actionInfoItem, 0)
for _, member := range members {
memberType := strings.Replace(member[1], " ", "", -1)
memberName := strings.Replace(member[2], " ", "", -1)
// 根据proto生成pb.go的规则,成员变量首字母必须大写
memberName, _ = sysutil.MakeStringToUpper(memberName, 0, 1)
actionInfos = append(actionInfos, &actionInfoItem{
memberName: memberName,
memberType: memberType,
})
}
if len(actionInfos) == 0 {
return nil, fmt.Errorf("can Not Find %s Member Info", actionName)
}
return actionInfos, nil
}
func formatExecContent(infos []*actionInfoItem, dappName string) string {
fnFmtStr := `func (c *%s) Exec_%s(payload *ptypes.%s, tx *types.Transaction, index int) (*types.Receipt, error) {
//implement code
return &types.Receipt{}, nil
}
`
content := ""
for _, info := range infos {
content += fmt.Sprintf(fnFmtStr, dappName, info.memberName, info.memberType)
}
return content
}
func formatExecLocalContent(infos []*actionInfoItem, dappName string) string {
fnFmtStr := `func (c *%s) ExecLocal_%s(payload *ptypes.%s, tx *types.Transaction, receiptData *types.ReceiptData, index int) (*types.LocalDBSet, error) {
//implement code
return &types.LocalDBSet{}, nil
}
`
content := ""
for _, info := range infos {
content += fmt.Sprintf(fnFmtStr, dappName, info.memberName, info.memberType)
}
return content
}
func formatExecDelLocalContent(infos []*actionInfoItem, dappName string) string {
fnFmtStr := `func (c *%s) ExecDelLocal_%s(payload *ptypes.%s, tx *types.Transaction, receiptData *types.ReceiptData, index int) (*types.LocalDBSet, error) {
//implement code
return &types.LocalDBSet{}, nil
}
`
content := ""
for _, info := range infos {
content += fmt.Sprintf(fnFmtStr, dappName, info.memberName, info.memberType)
}
return content
}
// 组成规则是 TyLog+ActionName + ActionMemberName
func buildActionLogTypeText(infos []*actionInfoItem, className string) (text string) {
items := fmt.Sprintf("TyLog%sUnknown = iota\n", className)
for _, info := range infos {
items += fmt.Sprintf("TyLog%s%s\n", className, info.memberName)
}
text = fmt.Sprintf("const (\n%s)\n", items)
return
}
// 组成规则是 ActionName + ActionMemberName
func buildActionIDText(infos []*actionInfoItem, className string) (text string) {
var items string
for index, info := range infos {
items += fmt.Sprintf("%sAction%s = %d\n", className, info.memberName, index)
}
text = fmt.Sprintf("const (\n%s)\n", items)
return
}
// 返回 map[string]int32
func buildTypeMapText(infos []*actionInfoItem, className string) (text string) {
var items string
for _, info := range infos {
items += fmt.Sprintf("\"%s\": %sAction%s,\n", info.memberName, className, info.memberName)
}
text = fmt.Sprintf("map[string]int32{\n%s}", items)
return
}
// 返回 map[string]*types.LogInfo
func buildLogMapText() (text string) {
text = fmt.Sprintf("map[int64]*types.LogInfo{\n\t//pseudo code\n\t//LogID: {Ty: refelct.TypeOf(LogStruct), Name: LogName},\n}")
return
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment