diff --git a/cmd/gf/internal/cmd/cmd_build.go b/cmd/gf/internal/cmd/cmd_build.go index 2a0564026e4..edb7c382e96 100644 --- a/cmd/gf/internal/cmd/cmd_build.go +++ b/cmd/gf/internal/cmd/cmd_build.go @@ -44,8 +44,9 @@ type cBuild struct { } const ( - cBuildBrief = `cross-building go project for lots of platforms` - cBuildEg = ` + cBuildDefaultFile = "main.go" + cBuildBrief = `cross-building go project for lots of platforms` + cBuildEg = ` gf build main.go gf build main.go --ps public,template gf build main.go --cgo @@ -123,7 +124,7 @@ type cBuildInput struct { Arch string `short:"a" name:"arch" brief:"output binary architecture, multiple arch separated with ','"` System string `short:"s" name:"system" brief:"output binary system, multiple os separated with ','"` Output string `short:"o" name:"output" brief:"output binary path, used when building single binary file"` - Path string `short:"p" name:"path" brief:"output binary directory path, default is './temp'" d:"./temp"` + Path string `short:"p" name:"path" brief:"output binary directory path, default is '.'" d:"."` Extra string `short:"e" name:"extra" brief:"extra custom \"go build\" options"` Mod string `short:"m" name:"mod" brief:"like \"-mod\" option of \"go build\", use \"-m none\" to disable go module"` Cgo bool `short:"c" name:"cgo" brief:"enable or disable cgo feature, it's disabled in default" orphan:"true"` @@ -152,12 +153,13 @@ func (c cBuild) Index(ctx context.Context, in cBuildInput) (out *cBuildOutput, e var ( parser = gcmd.ParserFromCtx(ctx) - file = parser.GetArg(2).String() + file = in.File ) - if len(file) < 1 { + if file == "" { + file = parser.GetArg(2).String() // Check and use the main.go file. - if gfile.Exists("main.go") { - file = "main.go" + if gfile.Exists(cBuildDefaultFile) { + file = cBuildDefaultFile } else { mlog.Fatal("build file path is empty or main.go not found in current working directory") } @@ -239,13 +241,7 @@ func (c cBuild) Index(ctx context.Context, in cBuildInput) (out *cBuildOutput, e } else { genv.MustSet("CGO_ENABLED", "0") } - var ( - cmd = "" - ext = "" - ) for system, item := range platformMap { - cmd = "" - ext = "" if len(customSystems) > 0 && customSystems[0] != "all" && !gstr.InArray(customSystems, system) { continue } @@ -258,58 +254,22 @@ func (c cBuild) Index(ctx context.Context, in cBuildInput) (out *cBuildOutput, e // For example: // `gf build` // `gf build -o main.exe` - if runtime.GOOS == "windows" { - ext = ".exe" - } - var outputPath string - if len(in.Output) > 0 { - outputPath = "-o " + in.Output - } else { - outputPath = "-o " + in.Name + ext - } - cmd = fmt.Sprintf( - `go build %s -ldflags "%s" %s %s`, - outputPath, ldFlags, in.Extra, file, + c.doBinaryBuild( + ctx, file, + in.Output, in.Path, + runtime.GOOS, runtime.GOARCH, in.Name, ldFlags, in.Extra, + in.ExitWhenError, + true, ) } else { - // Cross-building, output the compiled binary to specified path. - if system == "windows" { - ext = ".exe" - } - genv.MustSet("GOOS", system) - genv.MustSet("GOARCH", arch) - - var outputPath string - if len(in.Output) > 0 { - outputPath = "-o " + in.Output - } else { - outputPath = fmt.Sprintf( - "-o %s/%s/%s%s", - in.Path, system+"_"+arch, in.Name, ext, - ) - } - cmd = fmt.Sprintf( - `go build %s -ldflags "%s" %s%s`, - outputPath, ldFlags, in.Extra, file, + c.doBinaryBuild( + ctx, file, + in.Output, in.Path, + system, arch, in.Name, ldFlags, in.Extra, + in.ExitWhenError, + false, ) } - mlog.Debug(fmt.Sprintf("build for GOOS=%s GOARCH=%s", system, arch)) - mlog.Debug(cmd) - // It's not necessary printing the complete command string. - cmdShow, _ := gregex.ReplaceString(`\s+(-ldflags ".+?")\s+`, " ", cmd) - mlog.Print(cmdShow) - if result, err := gproc.ShellExec(ctx, cmd); err != nil { - mlog.Printf( - "failed to build, os:%s, arch:%s, error:\n%s\n\n%s\n", - system, arch, gstr.Trim(result), - `you may use command option "--debug" to enable debug info and check the details`, - ) - if in.ExitWhenError { - os.Exit(1) - } - } else { - mlog.Debug(gstr.Trim(result)) - } // single binary building. if len(customSystems) == 0 && len(customArches) == 0 { goto buildDone @@ -322,6 +282,68 @@ buildDone: return } +func (c cBuild) doBinaryBuild( + ctx context.Context, + filePath string, + outputPath, dirPath string, + system, arch, name, ldFlags, extra string, + exitWhenError bool, + singleBuild bool, +) { + var ( + cmd string + ext string + ) + // Cross-building, output the compiled binary to specified path. + if system == "windows" { + ext = ".exe" + } + genv.MustSet("GOOS", system) + genv.MustSet("GOARCH", arch) + + if outputPath != "" { + outputPath = "-o " + outputPath + } else { + if dirPath == "" { + dirPath = "." + } else { + dirPath = gstr.TrimRight(dirPath, "/") + } + if singleBuild { + outputPath = fmt.Sprintf( + "-o %s/%s%s", + dirPath, name, ext, + ) + } else { + outputPath = fmt.Sprintf( + "-o %s/%s/%s%s", + dirPath, system+"_"+arch, name, ext, + ) + } + } + cmd = fmt.Sprintf( + `go build %s -ldflags "%s" %s%s`, + outputPath, ldFlags, extra, filePath, + ) + mlog.Debug(fmt.Sprintf("build for GOOS=%s GOARCH=%s", system, arch)) + mlog.Debug(cmd) + // It's not necessary printing the complete command string, filtering ldFlags. + cmdShow, _ := gregex.ReplaceString(`\s+(-ldflags ".+?")\s+`, " ", cmd) + mlog.Print(cmdShow) + if result, err := gproc.ShellExec(ctx, cmd); err != nil { + mlog.Printf( + "failed to build, os:%s, arch:%s, error:\n%s\n\n%s\n", + system, arch, gstr.Trim(result), + `you may use command option "--debug" to enable debug info and check the details`, + ) + if exitWhenError { + os.Exit(1) + } + } else { + mlog.Debug(gstr.Trim(result)) + } +} + // getBuildInVarStr retrieves and returns the custom build-in variables in configuration // file as json. func (c cBuild) getBuildInVarStr(ctx context.Context, in cBuildInput) string { diff --git a/cmd/gf/internal/cmd/cmd_z_unit_build_test.go b/cmd/gf/internal/cmd/cmd_z_unit_build_test.go new file mode 100644 index 00000000000..33925969189 --- /dev/null +++ b/cmd/gf/internal/cmd/cmd_z_unit_build_test.go @@ -0,0 +1,151 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package cmd + +import ( + "testing" + + "github.com/gogf/gf/v2/os/gfile" + "github.com/gogf/gf/v2/os/gproc" + "github.com/gogf/gf/v2/test/gtest" + "github.com/gogf/gf/v2/text/gstr" +) + +func Test_Build_Single(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + var ( + buildPath = gtest.DataPath(`build`, `single`) + pwd = gfile.Pwd() + binaryName = `t.test` + binaryPath = gtest.DataPath(`build`, `single`, binaryName) + f = cBuild{} + ) + defer gfile.Chdir(pwd) + defer gfile.Remove(binaryPath) + err := gfile.Chdir(buildPath) + t.AssertNil(err) + + t.Assert(gfile.Exists(binaryPath), false) + _, err = f.Index(ctx, cBuildInput{ + File: cBuildDefaultFile, + Name: binaryName, + }) + t.AssertNil(err) + t.Assert(gfile.Exists(binaryPath), true) + }) +} + +func Test_Build_Single_Output(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + var ( + buildPath = gtest.DataPath(`build`, `single`) + pwd = gfile.Pwd() + binaryName = `tt` + binaryDirPath = gtest.DataPath(`build`, `single`, `tt`) + binaryPath = gtest.DataPath(`build`, `single`, `tt`, binaryName) + f = cBuild{} + ) + defer gfile.Chdir(pwd) + defer gfile.Remove(binaryDirPath) + err := gfile.Chdir(buildPath) + t.AssertNil(err) + + t.Assert(gfile.Exists(binaryPath), false) + _, err = f.Index(ctx, cBuildInput{ + Output: "./tt/tt", + }) + t.AssertNil(err) + t.Assert(gfile.Exists(binaryPath), true) + }) +} + +func Test_Build_Single_Path(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + var ( + buildPath = gtest.DataPath(`build`, `single`) + pwd = gfile.Pwd() + dirName = "ttt" + binaryName = `main` + binaryDirPath = gtest.DataPath(`build`, `single`, dirName) + binaryPath = gtest.DataPath(`build`, `single`, dirName, binaryName) + f = cBuild{} + ) + defer gfile.Chdir(pwd) + defer gfile.Remove(binaryDirPath) + err := gfile.Chdir(buildPath) + t.AssertNil(err) + + t.Assert(gfile.Exists(binaryPath), false) + _, err = f.Index(ctx, cBuildInput{ + Path: "ttt", + }) + t.AssertNil(err) + t.Assert(gfile.Exists(binaryPath), true) + }) +} + +func Test_Build_Single_VarMap(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + var ( + buildPath = gtest.DataPath(`build`, `varmap`) + pwd = gfile.Pwd() + binaryName = `main` + binaryPath = gtest.DataPath(`build`, `varmap`, binaryName) + f = cBuild{} + ) + defer gfile.Chdir(pwd) + defer gfile.Remove(binaryPath) + err := gfile.Chdir(buildPath) + t.AssertNil(err) + + t.Assert(gfile.Exists(binaryPath), false) + _, err = f.Index(ctx, cBuildInput{ + VarMap: map[string]interface{}{ + "a": "1", + "b": "2", + }, + }) + t.AssertNil(err) + t.Assert(gfile.Exists(binaryPath), true) + + result, err := gproc.ShellExec(ctx, binaryPath) + t.AssertNil(err) + t.Assert(gstr.Contains(result, `a: 1`), true) + t.Assert(gstr.Contains(result, `b: 2`), true) + }) +} + +func Test_Build_Multiple(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + var ( + buildPath = gtest.DataPath(`build`, `multiple`) + pwd = gfile.Pwd() + binaryDirPath = gtest.DataPath(`build`, `multiple`, `temp`) + binaryPathLinux = gtest.DataPath(`build`, `multiple`, `temp`, `v1.1`, `linux_amd64`, `ttt`) + binaryPathWindows = gtest.DataPath(`build`, `multiple`, `temp`, `v1.1`, `windows_amd64`, `ttt.exe`) + f = cBuild{} + ) + defer gfile.Chdir(pwd) + defer gfile.Remove(binaryDirPath) + err := gfile.Chdir(buildPath) + t.AssertNil(err) + + t.Assert(gfile.Exists(binaryPathLinux), false) + t.Assert(gfile.Exists(binaryPathWindows), false) + _, err = f.Index(ctx, cBuildInput{ + File: "multiple.go", + Name: "ttt", + Version: "v1.1", + Arch: "amd64", + System: "linux, windows", + Path: "temp", + }) + t.AssertNil(err) + t.Assert(gfile.Exists(binaryPathLinux), true) + t.Assert(gfile.Exists(binaryPathWindows), true) + }) +} diff --git a/cmd/gf/internal/cmd/testdata/build/multiple/multiple.go b/cmd/gf/internal/cmd/testdata/build/multiple/multiple.go new file mode 100644 index 00000000000..79058077776 --- /dev/null +++ b/cmd/gf/internal/cmd/testdata/build/multiple/multiple.go @@ -0,0 +1,5 @@ +package main + +func main() { + +} diff --git a/cmd/gf/internal/cmd/testdata/build/single/main.go b/cmd/gf/internal/cmd/testdata/build/single/main.go new file mode 100644 index 00000000000..79058077776 --- /dev/null +++ b/cmd/gf/internal/cmd/testdata/build/single/main.go @@ -0,0 +1,5 @@ +package main + +func main() { + +} diff --git a/cmd/gf/internal/cmd/testdata/build/varmap/go.mod b/cmd/gf/internal/cmd/testdata/build/varmap/go.mod new file mode 100644 index 00000000000..74572920486 --- /dev/null +++ b/cmd/gf/internal/cmd/testdata/build/varmap/go.mod @@ -0,0 +1,12 @@ +module github.com/gogf/gf/cmd/gf/cmd/gf/testdata/vardump/v2 + +go 1.18 + +require github.com/gogf/gf/v2 v2.6.1 + +require ( + go.opentelemetry.io/otel v1.14.0 // indirect + go.opentelemetry.io/otel/trace v1.14.0 // indirect +) + +replace github.com/gogf/gf/v2 => ../../../../../../../ diff --git a/cmd/gf/internal/cmd/testdata/build/varmap/go.sum b/cmd/gf/internal/cmd/testdata/build/varmap/go.sum new file mode 100644 index 00000000000..56cf07dd733 --- /dev/null +++ b/cmd/gf/internal/cmd/testdata/build/varmap/go.sum @@ -0,0 +1,27 @@ +github.com/BurntSushi/toml v1.2.0 h1:Rt8g24XnyGTyglgET/PRUNlrUeu9F5L+7FilkXfZgs0= +github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/grokify/html-strip-tags-go v0.0.1 h1:0fThFwLbW7P/kOiTBs03FsJSV9RM2M/Q/MOnCQxKMo0= +github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +go.opentelemetry.io/otel v1.14.0 h1:/79Huy8wbf5DnIPhemGB+zEPVwnN6fuQybr/SRXa6hM= +go.opentelemetry.io/otel v1.14.0/go.mod h1:o4buv+dJzx8rohcUeRmWUZhqupFvzWis188WlggnNeU= +go.opentelemetry.io/otel/sdk v1.14.0 h1:PDCppFRDq8A1jL9v6KMI6dYesaq+DFcDZvjsoGvxGzY= +go.opentelemetry.io/otel/trace v1.14.0 h1:wp2Mmvj41tDsyAJXiWDWpfNsOiIyd38fy85pyKcFq/M= +go.opentelemetry.io/otel/trace v1.14.0/go.mod h1:8avnQLK+CG77yNLUae4ea2JDQ6iT+gozhnZjy/rw9G8= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/cmd/gf/internal/cmd/testdata/build/varmap/main.go b/cmd/gf/internal/cmd/testdata/build/varmap/main.go new file mode 100644 index 00000000000..788a885cddf --- /dev/null +++ b/cmd/gf/internal/cmd/testdata/build/varmap/main.go @@ -0,0 +1,13 @@ +package main + +import ( + "fmt" + + "github.com/gogf/gf/v2/os/gbuild" +) + +func main() { + for k, v := range gbuild.Data() { + fmt.Printf("%s: %v\n", k, v) + } +} diff --git a/os/gcmd/gcmd_parser.go b/os/gcmd/gcmd_parser.go index 40689732874..49cbcca8502 100644 --- a/os/gcmd/gcmd_parser.go +++ b/os/gcmd/gcmd_parser.go @@ -200,6 +200,9 @@ func (p *Parser) setOptionValue(name, value string) { // GetOpt returns the option value named `name` as gvar.Var. func (p *Parser) GetOpt(name string, def ...interface{}) *gvar.Var { + if p == nil { + return nil + } if v, ok := p.parsedOptions[name]; ok { return gvar.New(v) } @@ -211,11 +214,17 @@ func (p *Parser) GetOpt(name string, def ...interface{}) *gvar.Var { // GetOptAll returns all parsed options. func (p *Parser) GetOptAll() map[string]string { + if p == nil { + return nil + } return p.parsedOptions } // GetArg returns the argument at `index` as gvar.Var. func (p *Parser) GetArg(index int, def ...string) *gvar.Var { + if p == nil { + return nil + } if index >= 0 && index < len(p.parsedArgs) { return gvar.New(p.parsedArgs[index]) } @@ -227,6 +236,9 @@ func (p *Parser) GetArg(index int, def ...string) *gvar.Var { // GetArgAll returns all parsed arguments. func (p *Parser) GetArgAll() []string { + if p == nil { + return nil + } return p.parsedArgs }