From f6853f40e5fa000acc8a62401b46ed89ca01809e Mon Sep 17 00:00:00 2001 From: Viola_Pioggia <114391708+ViolaPioggia@users.noreply.github.com> Date: Sun, 7 Jul 2024 19:23:53 +0800 Subject: [PATCH] feat: add job creation (#195) * feat: add job creation * style: license header && gofumpt * style: change code style * test: add job_test.go * style: change job template --- cmd/static/cmd.go | 17 ++ cmd/static/job_flags.go | 30 +++ config/argument.go | 2 + config/job.go | 40 ++++ go.mod | 4 +- go.sum | 26 ++- pkg/consts/const.go | 4 + pkg/job/job.go | 444 ++++++++++++++++++++++++++++++++++++++++ pkg/job/job_test.go | 260 +++++++++++++++++++++++ pkg/job/template.go | 98 +++++++++ 10 files changed, 920 insertions(+), 5 deletions(-) create mode 100644 cmd/static/job_flags.go create mode 100644 config/job.go create mode 100644 pkg/job/job.go create mode 100644 pkg/job/job_test.go create mode 100644 pkg/job/template.go diff --git a/cmd/static/cmd.go b/cmd/static/cmd.go index 9b3bf10a..95c0a5c9 100644 --- a/cmd/static/cmd.go +++ b/cmd/static/cmd.go @@ -24,6 +24,7 @@ import ( "github.com/cloudwego/cwgo/pkg/consts" "github.com/cloudwego/cwgo/pkg/curd/doc" "github.com/cloudwego/cwgo/pkg/fallback" + "github.com/cloudwego/cwgo/pkg/job" "github.com/cloudwego/cwgo/pkg/model" "github.com/cloudwego/cwgo/pkg/server" "github.com/urfave/cli/v2" @@ -95,6 +96,17 @@ func Init() *cli.App { return doc.Doc(globalArgs.DocArgument) }, }, + { + Name: JobName, + Usage: JobUsage, + Flags: jobFlags(), + Action: func(c *cli.Context) error { + if err := globalArgs.JobArgument.ParseCli(c); err != nil { + return err + } + return job.Job(globalArgs.JobArgument) + }, + }, { Name: ApiListName, Usage: ApiUsage, @@ -197,7 +209,12 @@ Examples: Examples: cwgo api --project_path ./ ` + JobName = "job" + JobUsage = `generate job code +Examples: + cwgo job --job_name jobOne --job_name jobTwo --module my_job +` FallbackName = "fallback" FallbackUsage = "fallback to hz or kitex" diff --git a/cmd/static/job_flags.go b/cmd/static/job_flags.go new file mode 100644 index 00000000..a5e4872d --- /dev/null +++ b/cmd/static/job_flags.go @@ -0,0 +1,30 @@ +/* + * Copyright 2024 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package static + +import ( + "github.com/cloudwego/cwgo/pkg/consts" + "github.com/urfave/cli/v2" +) + +func jobFlags() []cli.Flag { + return []cli.Flag{ + &cli.StringSliceFlag{Name: consts.JobName, Usage: "Specify the job name."}, + &cli.StringFlag{Name: consts.Module, Aliases: []string{"mod"}, Usage: "Specify the Go module name to generate go.mod."}, + &cli.StringFlag{Name: consts.OutDir, Usage: "Specify output directory, default is current dir."}, + } +} diff --git a/config/argument.go b/config/argument.go index 9b4b65e4..68ef14da 100644 --- a/config/argument.go +++ b/config/argument.go @@ -38,6 +38,7 @@ type Argument struct { *ClientArgument *ModelArgument *DocArgument + *JobArgument *ApiArgument *FallbackArgument } @@ -48,6 +49,7 @@ func NewArgument() *Argument { ClientArgument: NewClientArgument(), ModelArgument: NewModelArgument(), DocArgument: NewDocArgument(), + JobArgument: NewJobArgument(), ApiArgument: NewApiArgument(), FallbackArgument: NewFallbackArgument(), } diff --git a/config/job.go b/config/job.go new file mode 100644 index 00000000..caee0f6c --- /dev/null +++ b/config/job.go @@ -0,0 +1,40 @@ +/* + * Copyright 2024 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package config + +import ( + "github.com/cloudwego/cwgo/pkg/consts" + "github.com/urfave/cli/v2" +) + +type JobArgument struct { + GoMod string + PackagePrefix string + JobName []string + OutDir string +} + +func NewJobArgument() *JobArgument { + return &JobArgument{} +} + +func (j *JobArgument) ParseCli(ctx *cli.Context) error { + j.JobName = ctx.StringSlice(consts.JobName) + j.GoMod = ctx.String(consts.Module) + j.OutDir = ctx.String(consts.OutDir) + return nil +} diff --git a/go.mod b/go.mod index b2bdebd1..0a71d92c 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/cloudwego/kitex v0.9.1 github.com/cloudwego/thriftgo v0.3.10 github.com/fatih/camelcase v1.0.0 + github.com/stretchr/testify v1.8.4 github.com/urfave/cli/v2 v2.27.1 golang.org/x/tools v0.20.0 gorm.io/driver/mysql v1.5.6 @@ -26,13 +27,12 @@ require ( github.com/apache/thrift v0.13.0 // indirect github.com/benbjohnson/clock v1.1.0 // indirect github.com/bytedance/sonic/loader v0.1.1 // indirect - github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect - github.com/chenzhuoyu/iasm v0.9.1 // indirect github.com/cloudwego/base64x v0.1.4 // indirect github.com/cloudwego/fastpb v0.0.4 // indirect github.com/cloudwego/iasm v0.2.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dlclark/regexp2 v1.11.0 // indirect github.com/go-sql-driver/mysql v1.7.0 // indirect github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect diff --git a/go.sum b/go.sum index 6544edaa..ec78d315 100644 --- a/go.sum +++ b/go.sum @@ -5,17 +5,24 @@ git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3p github.com/Azure/azure-sdk-for-go/sdk/azcore v1.4.0/go.mod h1:ON4tFdPTwRcgWEaVDrN3584Ef+b7GgSJaXxe5fW9t4M= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.1/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.1 h1:/iHxaJhsFr0+xVFfbMr5vxz848jyiWuIEDhYq3y5odY= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.1/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 h1:vcYCAze6p19qBW7MhZybIsqD8sMV8js0NyQM8JDnVtg= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0/go.mod h1:OQeznEEkTZ9OrhHJoDD8ZDq51FHgXjqtP9z6bEwBq9U= github.com/Azure/azure-sdk-for-go/sdk/internal v1.1.2/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w= github.com/Azure/azure-sdk-for-go/sdk/internal v1.2.0/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 h1:sXr+ck84g/ZlZUOZiNELInmMgOsuGwdjjVkEIde0OtY= github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.0 h1:yfJe15aSwEQ6Oo6J+gdfdulPNoZ3TEhmbhLIoxZcA+U= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.0/go.mod h1:Q28U+75mpCaSCDowNEmhIo/rmgdkqmkmzI7N6TGR4UY= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v0.8.0 h1:T028gtTPiYt/RMUfs8nVsAL7FDQrfLlrm/NnRG/zcC4= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v0.8.0/go.mod h1:cw4zVQgBby0Z5f2v0itn6se2dDP17nTjbZFXW5uPyHA= github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0/go.mod h1:kgDmCTgBzIEPFElEF+FK0SdjAor06dRq2Go927dnQ6o= +github.com/AzureAD/microsoft-authentication-library-for-go v1.1.0 h1:HCc0+LpPfpCKs6LGGLAhwBARt9632unrVcI6i8s/8os= github.com/AzureAD/microsoft-authentication-library-for-go v1.1.0/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= @@ -45,7 +52,6 @@ github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1 github.com/bytedance/sonic v1.8.8/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= github.com/bytedance/sonic v1.10.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= -github.com/bytedance/sonic v1.11.2 h1:ywfwo0a/3j9HR8wsYGWsIWl2mvRsI950HyoxiBERw5A= github.com/bytedance/sonic v1.11.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= @@ -54,12 +60,10 @@ github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= -github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA= github.com/chenzhuoyu/iasm v0.0.0-20220818063314-28c361dae733/go.mod h1:wOQ0nsbeOLa2awv8bUYFW/EHXbjQMlZ10fAlXDB2sz8= github.com/chenzhuoyu/iasm v0.0.0-20230222070914-0b1b64b0e762/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= -github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0= github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= github.com/choleraehyq/pid v0.0.13/go.mod h1:uhzeFgxJZWQsZulelVQZwdASxQ9TIPZYL4TPkQMtL/U= github.com/choleraehyq/pid v0.0.15/go.mod h1:uhzeFgxJZWQsZulelVQZwdASxQ9TIPZYL4TPkQMtL/U= @@ -110,6 +114,7 @@ github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548 h1:iwZdTE0PVqJCos1v github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548/go.mod h1:e6NPNENfs9mPDVNRekM7lKScauxd5kXTr1Mfyig6TDM= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= @@ -142,6 +147,7 @@ github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE= github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA= github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= @@ -171,6 +177,7 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20220608213341-c488b8fa1db3/go.mod h1:gSuNB+gJaOiQKLEZ+q+PK9Mq3SOzhRcw2GsGS/FhYDk= @@ -232,9 +239,12 @@ github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZX github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= @@ -264,17 +274,21 @@ github.com/pingcap/log v0.0.0-20210625125904-98ed8e2eb1c7 h1:k2BbABz9+TNpYRwsCCF github.com/pingcap/log v0.0.0-20210625125904-98ed8e2eb1c7/go.mod h1:8AanEdAHATuRurdGxZXBz0At+9avep+ub7U1AGYLIMM= github.com/pingcap/tidb/parser v0.0.0-20230327100244-b67c0321c05a h1:GsiwtTVFsN+1bfeUaStwOoBzTC4tKpETiWmhxT55jAc= github.com/pingcap/tidb/parser v0.0.0-20230327100244-b67c0321c05a/go.mod h1:IxXRBZ14Of1KkR3NXEwsoKrM8JbkOIHJHpwS/Ad8vPY= +github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rakyll/statik v0.1.7/go.mod h1:AlZONWzMtEnMs7W4e/1LURLiI49pIMmp6V9Unghqrcc= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -299,6 +313,7 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/thrift-iterator/go v0.0.0-20190402154806-9b5a67519118/go.mod h1:60PRwE/TCI1UqLvn8v2pwAf6+yzTPLP/Ji5xaesWDqk= github.com/tidwall/gjson v1.9.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= @@ -324,6 +339,7 @@ go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= @@ -413,6 +429,7 @@ golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -528,6 +545,7 @@ google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoA google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= +google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f h1:BWUVssLB0HVOSY78gIdvk1dTVYtT1y8SBWtPYuTJ/6w= google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= @@ -535,6 +553,7 @@ google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQ google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.55.0-dev h1:b3WG8LoyS+X/C5ZbIWsJGjt8Hhqq0wUVX8+rPF/BHZo= google.golang.org/grpc v1.55.0-dev/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -554,6 +573,7 @@ google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= diff --git a/pkg/consts/const.go b/pkg/consts/const.go index 5767d1bd..f3ba8505 100644 --- a/pkg/consts/const.go +++ b/pkg/consts/const.go @@ -165,6 +165,10 @@ const ( MongoDb = "mongodb" ) +const ( + JobName = "job_name" +) + const ( BashAutocomplete = `#! /bin/bash diff --git a/pkg/job/job.go b/pkg/job/job.go new file mode 100644 index 00000000..78d17c68 --- /dev/null +++ b/pkg/job/job.go @@ -0,0 +1,444 @@ +/* + * Copyright 2024 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package job + +import ( + "bytes" + "errors" + "fmt" + "go/ast" + "go/parser" + "go/printer" + "go/token" + "os" + "path/filepath" + "strconv" + "strings" + "text/template" + + "github.com/cloudwego/cwgo/meta" + + "github.com/cloudwego/cwgo/config" + "github.com/cloudwego/cwgo/pkg/common/utils" + "github.com/cloudwego/cwgo/pkg/consts" + "github.com/cloudwego/kitex/tool/internal_pkg/log" + + "golang.org/x/tools/go/ast/astutil" +) + +func Job(c *config.JobArgument) error { + if err := check(c); err != nil { + return err + } + + err := generateJobFile(c.GoMod, c.PackagePrefix, c.JobName, c.OutDir) + if err != nil { + return err + } + + return nil +} + +func check(c *config.JobArgument) (err error) { + if len(c.JobName) == 0 { + return errors.New("job name is empty") + } + + c.OutDir, err = filepath.Abs(c.OutDir) + if err != nil { + return err + } + + gopath, err := utils.GetGOPATH() + if err != nil { + return fmt.Errorf("GET gopath failed: %s", err) + } + if gopath == "" { + return fmt.Errorf("GOPATH is not set") + } + + gosrc := filepath.Join(gopath, consts.Src) + gosrc, err = filepath.Abs(gosrc) + if err != nil { + log.Warn("Get GOPATH/src path failed:", err.Error()) + os.Exit(1) + } + curpath, err := filepath.Abs(consts.CurrentDir) + if err != nil { + log.Warn("Get current path failed:", err.Error()) + os.Exit(1) + } + + if strings.HasPrefix(curpath, gosrc) { + goPkg := "" + if goPkg, err = filepath.Rel(gosrc, curpath); err != nil { + log.Warn("Get GOPATH/src relpath failed:", err.Error()) + os.Exit(1) + } + + if c.GoMod == "" { + if utils.IsWindows() { + c.GoMod = strings.ReplaceAll(goPkg, consts.BackSlash, consts.Slash) + } else { + c.GoMod = goPkg + } + } + + if c.GoMod != "" { + if utils.IsWindows() { + goPkgSlash := strings.ReplaceAll(goPkg, consts.BackSlash, consts.Slash) + if goPkgSlash != c.GoMod { + return fmt.Errorf("module name: %s is not the same with GoPkg under GoPath: %s", c.GoMod, goPkgSlash) + } + } else { + if c.GoMod != goPkg { + return fmt.Errorf("module name: %s is not the same with GoPkg under GoPath: %s", c.GoMod, goPkg) + } + } + } + } + + if !strings.HasPrefix(curpath, gosrc) && c.GoMod == "" { + log.Warn("Outside of $GOPATH. Please specify a module name with the '-module' flag.") + os.Exit(1) + } + + if c.GoMod != "" { + module, path, ok := utils.SearchGoMod(curpath, true) + + if ok { + // go.mod exists + if module != c.GoMod { + log.Warnf("The module name given by the '-module' option ('%s') is not consist with the name defined in go.mod ('%s' from %s)\n", + c.GoMod, module, path) + os.Exit(1) + } + if c.PackagePrefix, err = filepath.Rel(path, c.OutDir); err != nil { + log.Warn("Get package prefix failed:", err.Error()) + os.Exit(1) + } + c.PackagePrefix = filepath.Join(c.GoMod, c.PackagePrefix) + } else { + if err = utils.InitGoMod(c.GoMod); err != nil { + log.Warn("Init go mod failed:", err.Error()) + os.Exit(1) + } + if c.PackagePrefix, err = filepath.Rel(curpath, c.OutDir); err != nil { + log.Warn("Get package prefix failed:", err.Error()) + os.Exit(1) + } + c.PackagePrefix = filepath.Join(c.GoMod, c.PackagePrefix) + } + } + + c.PackagePrefix = strings.ReplaceAll(c.PackagePrefix, consts.BackSlash, consts.Slash) + + return nil +} + +type JobsData struct { + JobInfos []JobInfo +} +type JobInfo struct { + JobName string + GoModule string + PackagePrefix string +} + +func addJobImportsAndRun(data string, jobs []JobInfo) (string, error) { + fSet := token.NewFileSet() + file, err := parser.ParseFile(fSet, "", data, parser.ParseComments) + if err != nil { + return "", err + } + + // Extract existing imports and Run function calls + existingJobs := make(map[string]bool) + ast.Inspect(file, func(n ast.Node) bool { + switch x := n.(type) { + case *ast.ImportSpec: + if path, err := strconv.Unquote(x.Path.Value); err == nil { + for _, job := range jobs { + if path == fmt.Sprintf(`"%s/%s/job"`, job.PackagePrefix, job.JobName) { + existingJobs[job.JobName] = true + } + } + } + case *ast.ExprStmt: + if call, ok := x.X.(*ast.CallExpr); ok { + if fun, ok := call.Fun.(*ast.SelectorExpr); ok { + if ident, ok := fun.X.(*ast.Ident); ok { + for _, job := range jobs { + if ident.Name == job.JobName && fun.Sel.Name == "Run" { + existingJobs[job.JobName] = true + } + } + } + } + } + } + return true + }) + + // Add missing imports + for _, job := range jobs { + if !existingJobs[job.JobName] { + astutil.AddNamedImport(fSet, file, job.JobName, fmt.Sprintf("%s/%s/job", job.PackagePrefix, job.JobName)) + } + } + + // Add missing Run calls + var runFunc *ast.FuncDecl + for _, decl := range file.Decls { + if fn, ok := decl.(*ast.FuncDecl); ok && fn.Name.Name == "Run" { + runFunc = fn + break + } + } + + if runFunc != nil && runFunc.Body != nil { + var newStatements []ast.Stmt + for _, job := range jobs { + if !existingJobs[job.JobName] { + newStatements = append(newStatements, createRunCall(job.JobName)...) + } + } + + // Find the wg.Wait() block and insert new statements before it + var insertIndex int + for i, stmt := range runFunc.Body.List { + if exprStmt, ok := stmt.(*ast.ExprStmt); ok { + if call, ok := exprStmt.X.(*ast.CallExpr); ok { + if fun, ok := call.Fun.(*ast.SelectorExpr); ok { + if fun.X.(*ast.Ident).Name == "wg" && fun.Sel.Name == "Wait" { + insertIndex = i + break + } + } + } + } + } + + // Insert new statements before wg.Wait() + runFunc.Body.List = append(runFunc.Body.List[:insertIndex], append(newStatements, runFunc.Body.List[insertIndex:]...)...) + } + + // Generate the modified code + buf := new(bytes.Buffer) + if err = printer.Fprint(buf, fSet, file); err != nil { + return "", err + } + + return buf.String(), nil +} + +func createRunCall(jobName string) []ast.Stmt { + return []ast.Stmt{ + &ast.ExprStmt{ + X: &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: ast.NewIdent("wg"), + Sel: ast.NewIdent("Add"), + }, + Args: []ast.Expr{ + &ast.BasicLit{ + Kind: token.INT, + Value: "1", + }, + }, + }, + }, + &ast.GoStmt{ + Call: &ast.CallExpr{ + Fun: &ast.FuncLit{ + Type: &ast.FuncType{ + Params: &ast.FieldList{}, + }, + Body: &ast.BlockStmt{ + List: []ast.Stmt{ + &ast.DeferStmt{ + Call: &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: ast.NewIdent("wg"), + Sel: ast.NewIdent("Done"), + }, + }, + }, + &ast.ExprStmt{ + X: &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: ast.NewIdent(jobName), + Sel: ast.NewIdent("Run"), + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func generateJobFile(GoModule, PackagePrefix string, jobNames []string, outDir string) error { + // Ensure the base output directory exists + err := os.MkdirAll(outDir, 0o755) + if err != nil { + return fmt.Errorf("failed to create output directory: %w", err) + } + + // Create cmd/main.go and overwrite each time + cmdDir := filepath.Join(outDir, "cmd") + err = os.MkdirAll(cmdDir, 0o755) + if err != nil { + return fmt.Errorf("failed to create cmd directory: %w", err) + } + + mainGoPath := filepath.Join(cmdDir, "main.go") + tmpl, err := template.New("job_main").Parse(jobMainTemplate) + if err != nil { + return err + } + + jobsInfo := &JobsData{ + JobInfos: make([]JobInfo, 0), + } + for _, v := range jobNames { + jobsInfo.JobInfos = append(jobsInfo.JobInfos, JobInfo{ + JobName: v, + GoModule: GoModule, + PackagePrefix: PackagePrefix, + }) + } + + var jobFileContent bytes.Buffer + data := struct { + PackagePrefix string + Version string + }{ + PackagePrefix: PackagePrefix, + Version: meta.Version, + } + err = tmpl.Execute(&jobFileContent, data) + if err != nil { + return err + } + err = utils.CreateFile(mainGoPath, jobFileContent.String()) + if err != nil { + return err + } + jobFileContent.Reset() + + // Create or append to schedule.go + scheduleGoPath := filepath.Join(outDir, "schedule.go") + scheduleTmpl, err := template.New("job_schedule").Parse(jobScheduleTemplate) + if err != nil { + return err + } + + if exist, _ := utils.PathExist(scheduleGoPath); !exist { + err = scheduleTmpl.Execute(&jobFileContent, jobsInfo) + if err != nil { + return err + } + err = utils.CreateFile(scheduleGoPath, jobFileContent.String()) + if err != nil { + return fmt.Errorf("failed to write schedule.go: %w", err) + } + jobFileContent.Reset() + } else { + src, err := utils.ReadFileContent(scheduleGoPath) + if err != nil { + return fmt.Errorf("failed to read schedule.go: %w", err) + } + + res, err := addJobImportsAndRun(string(src), jobsInfo.JobInfos) + if err != nil { + return err + } + + err = utils.CreateFile(scheduleGoPath, res) + if err != nil { + return err + } + } + + // Create or append to run.sh + scriptsDir := filepath.Join(outDir, "scripts") + err = os.MkdirAll(scriptsDir, 0o755) + if err != nil { + return fmt.Errorf("failed to create scripts directory: %w", err) + } + + // Create run.sh + runShPath := filepath.Join(scriptsDir, "run.sh") + scriptTmpl, err := template.New("job_script").Parse(scriptTemplate) + if err != nil { + return err + } + + if exist, _ := utils.PathExist(runShPath); !exist { + err = scriptTmpl.Execute(&jobFileContent, nil) + if err != nil { + return err + } + err = utils.CreateFile(runShPath, jobFileContent.String()) + if err != nil { + return err + } + jobFileContent.Reset() + } + + // Create job directories and files + for _, jobName := range jobNames { + jobDir := filepath.Join(outDir, jobName) + internalJobDir := filepath.Join(jobDir, "job") + + // Create directories + err = os.MkdirAll(internalJobDir, 0o755) + if err != nil { + return fmt.Errorf("failed to create internal job directory for %s: %w", jobName, err) + } + + // Create or append to job.go + jobFilePath := filepath.Join(internalJobDir, "job.go") + jobTmpl, err := template.New("job").Parse(jobTemplate) + if err != nil { + return err + } + + if exist, _ := utils.PathExist(jobFilePath); !exist { + jobInfo := struct { + JobName string + }{ + JobName: jobName, + } + + err = jobTmpl.Execute(&jobFileContent, jobInfo) + if err != nil { + return err + } + err = utils.CreateFile(jobFilePath, jobFileContent.String()) + if err != nil { + return err + } + jobFileContent.Reset() + } + } + + return nil +} diff --git a/pkg/job/job_test.go b/pkg/job/job_test.go new file mode 100644 index 00000000..dd9c7598 --- /dev/null +++ b/pkg/job/job_test.go @@ -0,0 +1,260 @@ +/* + * Copyright 2024 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package job + +import ( + "bytes" + "go/printer" + "go/token" + "os" + "os/exec" + "path/filepath" + "testing" + + "github.com/cloudwego/cwgo/config" + "github.com/stretchr/testify/assert" +) + +func TestJobValidCreation(t *testing.T) { + args := &config.JobArgument{ + JobName: []string{"job1"}, + GoMod: "github.com/cloudwego/cwgo", + PackagePrefix: "github.com/cloudwego/cwgo", + OutDir: "./test_out", + } + + err := Job(args) + assert.NoError(t, err) + + checkFiles := []string{ + "test_out/cmd/main.go", + "test_out/schedule.go", + "test_out/scripts/run.sh", + "test_out/job1/job/job.go", + } + + for _, file := range checkFiles { + _, err := os.Stat(file) + assert.NoError(t, err) + } + + contentChecks := map[string]string{ + "test_out/schedule.go": "job1.Run()", + } + + for file, content := range contentChecks { + data, err := os.ReadFile(file) + assert.NoError(t, err) + assert.Contains(t, string(data), content) + } + + err = os.RemoveAll(args.OutDir) + assert.NoError(t, err) +} + +func TestAddJobImportsAndRun(t *testing.T) { + original := ` +package schedule + +import ( + "sync" +) + +var wg sync.WaitGroup + +func Run() { + wg.Add(1) + go func() { + defer wg.Done() + }() + wg.Wait() +} +` + jobs := []JobInfo{ + {JobName: "job1", PackagePrefix: "github.com/cloudwego/cwgo"}, + {JobName: "job2", PackagePrefix: "github.com/cloudwego/cwgo"}, + } + + expected := `package schedule + +import ( + "sync" + job1 "github.com/cloudwego/cwgo/job1/job" + job2 "github.com/cloudwego/cwgo/job2/job" +) + +var wg sync.WaitGroup + +func Run() { + wg.Add(1) + go func() { + defer wg.Done() + }() + wg.Add(1) + go func() { + defer wg.Done() + job1.Run() + }() + wg.Add(1) + go func() { + defer wg.Done() + job2.Run() + }() + wg.Wait() +} +` + + result, err := addJobImportsAndRun(original, jobs) + assert.NoError(t, err) + assert.Equal(t, expected, result) +} + +func TestCreateRunCall(t *testing.T) { + jobName := "job1" + expected := `wg.Add(1) +go func() { + defer wg.Done() + job1.Run() +}()` + + result := createRunCall(jobName) + var buf bytes.Buffer + err := printer.Fprint(&buf, token.NewFileSet(), result) + assert.NoError(t, err) + assert.Equal(t, expected, buf.String()) +} + +func TestJobMissingJobName(t *testing.T) { + args := &config.JobArgument{ + JobName: []string{""}, + GoMod: "github.com/cloudwego/cwgo", + PackagePrefix: "github.com/cloudwego/cwgo", + OutDir: "./test_out", + } + + if os.Getenv("BE_CRASHER") == "1" { + err := Job(args) + assert.Error(t, err) + return + } + cmd := exec.Command(os.Args[0], "-test.run=TestJobMissingJobName") + cmd.Env = append(os.Environ(), "BE_CRASHER=1") + err := cmd.Run() + defer func() { + _ = os.RemoveAll(args.OutDir) + }() + if e, ok := err.(*exec.ExitError); ok && !e.Success() { + return + } + t.Logf("process ran with err %v, want exit status 1", err) +} + +func TestJobWithDifferentModule(t *testing.T) { + args := &config.JobArgument{ + JobName: []string{"job2"}, + GoMod: "github.com/cloudwego/another_test", + PackagePrefix: "github.com/cloudwego/another_test", + OutDir: "./another_test_out", + } + + if os.Getenv("BE_CRASHER") == "1" { + err := Job(args) + assert.Error(t, err) + return + } + cmd := exec.Command(os.Args[0], "-test.run=TestJobWithDifferentModule") + cmd.Env = append(os.Environ(), "BE_CRASHER=1") + err := cmd.Run() + if e, ok := err.(*exec.ExitError); ok && !e.Success() { + return + } + t.Logf("process ran with err %v, want exit status 1", err) +} + +func TestJobWithEmptyModule(t *testing.T) { + args := &config.JobArgument{ + JobName: []string{"job3"}, + GoMod: "", + PackagePrefix: "github.com/cloudwego/cwgo", + OutDir: "./test_out", + } + + if os.Getenv("BE_CRASHER") == "1" { + err := Job(args) + assert.Error(t, err) + return + } + cmd := exec.Command(os.Args[0], "-test.run=TestJobWithEmptyModule") + cmd.Env = append(os.Environ(), "BE_CRASHER=1") + err := cmd.Run() + if e, ok := err.(*exec.ExitError); ok && !e.Success() { + return + } + t.Logf("process ran with err %v, want exit status 1", err) +} + +func TestJobWithoutGoModInGOPATH(t *testing.T) { + args := &config.JobArgument{ + JobName: []string{"job3"}, + GoMod: "", + PackagePrefix: "github.com/cloudwego/cwgo", + OutDir: "./test_out", + } + + // Setup a temporary GOPATH + tmpGopath, err := os.MkdirTemp("", "gopath") + if err != nil { + t.Fatalf("Failed to create temp GOPATH: %v", err) + } + defer os.RemoveAll(tmpGopath) + + // Create a directory structure under GOPATH/src + projectPath := filepath.Join(tmpGopath, "src", "github.com", "cloudwego", "cwgo") + if err := os.MkdirAll(projectPath, 0o755); err != nil { + t.Fatalf("Failed to create project directory: %v", err) + } + + // Move test file to the project directory + currentTestFile := filepath.Join(projectPath, "job_test.go") + if err := os.Rename(os.Args[0], currentTestFile); err != nil { + t.Fatalf("Failed to move test file to project directory: %v", err) + } + + // Set the GOPATH environment variable + oldGopath := os.Getenv("GOPATH") + os.Setenv("GOPATH", tmpGopath) + defer os.Setenv("GOPATH", oldGopath) + + if os.Getenv("BE_CRASHER") == "1" { + err := Job(args) + assert.Error(t, err) + return + } + + // Run the test in a subprocess + cmd := exec.Command(os.Args[0], "-test.run=TestJobWithoutGoModInGOPATH") + cmd.Env = append(os.Environ(), "BE_CRASHER=1") + output, err := cmd.CombinedOutput() + + if err == nil { + t.Fatalf("Expected process to exit with an error, got nil. Output: %s", output) + } + + if exitErr, ok := err.(*exec.ExitError); ok && exitErr.ExitCode() != 0 { + t.Logf("Expected process to exit with status 0, got %d. Output: %s", exitErr.ExitCode(), output) + } +} diff --git a/pkg/job/template.go b/pkg/job/template.go new file mode 100644 index 00000000..1ae6b03b --- /dev/null +++ b/pkg/job/template.go @@ -0,0 +1,98 @@ +/* + * Copyright 2024 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package job + +const jobTemplate = `package job + +// Run is an example function job +func Run() { + // TODO: fill with your own logic + +} +` + +const jobMainTemplate = `// Code generated by cwgo ({{.Version}}). DO NOT EDIT. +package main + +import ( + "log" + schedule "{{.PackagePrefix}}" +) + +func main() { + err := schedule.Run() + if err != nil { + log.Fatalf("job failed: %v", err) + } +} + +` + +const jobScheduleTemplate = `package schedule + +import ( + "sync" + {{- range .JobInfos }} + {{.JobName}} "{{.PackagePrefix}}/{{.JobName}}/job" + {{- end }} +) + +func Run() error { + var wg sync.WaitGroup + + {{- range .JobInfos }} + wg.Add(1) + go func() { + defer wg.Done() + {{.JobName}}.Run() + }() + {{- end }} + + wg.Wait() + return nil +} + +` + +const scriptTemplate = `#!/bin/bash + +echo "Building job binary..." +go build -o job ../cmd/main.go + +if [ $? -ne 0 ]; then + echo "Error: Failed to build job." + exit 1 +fi + +echo "Running job..." +./job + +if [ $? -ne 0 ]; then + echo "Error: job execution failed." + exit 1 +fi + +echo "job Done." + +rm job + +if [ $? -ne 0 ]; then + echo "Error: Failed to remove job binary." + exit 1 +fi + +`