| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339 |
- // 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 (
- "context"
- "encoding/json"
- "fmt"
- "os"
- "regexp"
- "runtime"
- "strings"
- "github.com/gogf/gf/v2/encoding/gbase64"
- "github.com/gogf/gf/v2/frame/g"
- "github.com/gogf/gf/v2/os/gcmd"
- "github.com/gogf/gf/v2/os/genv"
- "github.com/gogf/gf/v2/os/gfile"
- "github.com/gogf/gf/v2/os/gproc"
- "github.com/gogf/gf/v2/os/gtime"
- "github.com/gogf/gf/v2/text/gregex"
- "github.com/gogf/gf/v2/text/gstr"
- "github.com/gogf/gf/v2/util/gtag"
- "github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
- )
- var (
- Build = cBuild{
- nodeNameInConfigFile: "gfcli.build",
- packedGoFileName: "internal/packed/build_pack_data.go",
- }
- )
- type cBuild struct {
- g.Meta `name:"build" brief:"{cBuildBrief}" dc:"{cBuildDc}" eg:"{cBuildEg}" ad:"{cBuildAd}"`
- nodeNameInConfigFile string // nodeNameInConfigFile is the node name for compiler configurations in configuration file.
- packedGoFileName string // packedGoFileName specifies the file name for packing common folders into one single go file.
- }
- const (
- cBuildBrief = `cross-building go project for lots of platforms`
- cBuildEg = `
- gf build main.go
- gf build main.go --pack public,template
- gf build main.go --cgo
- gf build main.go -m none
- gf build main.go -n my-app -a all -s all
- gf build main.go -n my-app -a amd64,386 -s linux -p .
- gf build main.go -n my-app -v 1.0 -a amd64,386 -s linux,windows,darwin -p ./docker/bin
- `
- cBuildDc = `
- The "build" command is most commonly used command, which is designed as a powerful wrapper for
- "go build" command for convenience cross-compiling usage.
- It provides much more features for building binary:
- 1. Cross-Compiling for many platforms and architectures.
- 2. Configuration file support for compiling.
- 3. Build-In Variables.
- `
- cBuildAd = `
- PLATFORMS
- darwin amd64,arm64
- freebsd 386,amd64,arm
- linux 386,amd64,arm,arm64,ppc64,ppc64le,mips,mipsle,mips64,mips64le
- netbsd 386,amd64,arm
- openbsd 386,amd64,arm
- windows 386,amd64
- `
- // https://golang.google.cn/doc/install/source
- cBuildPlatforms = `
- darwin amd64
- darwin arm64
- ios amd64
- ios arm64
- freebsd 386
- freebsd amd64
- freebsd arm
- linux 386
- linux amd64
- linux arm
- linux arm64
- linux ppc64
- linux ppc64le
- linux mips
- linux mipsle
- linux mips64
- linux mips64le
- netbsd 386
- netbsd amd64
- netbsd arm
- openbsd 386
- openbsd amd64
- openbsd arm
- windows 386
- windows amd64
- android arm
- dragonfly amd64
- plan9 386
- plan9 amd64
- solaris amd64
- `
- )
- func init() {
- gtag.Sets(g.MapStrStr{
- `cBuildBrief`: cBuildBrief,
- `cBuildDc`: cBuildDc,
- `cBuildEg`: cBuildEg,
- `cBuildAd`: cBuildAd,
- })
- }
- type cBuildInput struct {
- g.Meta `name:"build" config:"gfcli.build"`
- File string `name:"FILE" arg:"true" brief:"building file path"`
- Name string `short:"n" name:"name" brief:"output binary name"`
- Version string `short:"v" name:"version" brief:"output binary version"`
- 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"`
- 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"`
- VarMap g.Map `short:"r" name:"varMap" brief:"custom built embedded variable into binary"`
- PackSrc string `short:"ps" name:"packSrc" brief:"pack one or more folders into one go file before building"`
- PackDst string `short:"pd" name:"packDst" brief:"temporary go file path for pack, this go file will be automatically removed after built" d:"internal/packed/build_pack_data.go"`
- ExitWhenError bool `short:"ew" name:"exitWhenError" brief:"exit building when any error occurs, specially for multiple arch and system buildings. default is false" orphan:"true"`
- DumpENV bool `short:"de" name:"dumpEnv" brief:"dump current go build environment before building binary" orphan:"true"`
- }
- type cBuildOutput struct{}
- func (c cBuild) Index(ctx context.Context, in cBuildInput) (out *cBuildOutput, err error) {
- // print used go env
- if in.DumpENV {
- _, _ = Env.Index(ctx, cEnvInput{})
- }
- mlog.SetHeaderPrint(true)
- mlog.Debugf(`build command input: %+v`, in)
- // Necessary check.
- if gproc.SearchBinary("go") == "" {
- mlog.Fatalf(`command "go" not found in your environment, please install golang first to proceed this command`)
- }
- var (
- parser = gcmd.ParserFromCtx(ctx)
- file = parser.GetArg(2).String()
- )
- if len(file) < 1 {
- // Check and use the main.go file.
- if gfile.Exists("main.go") {
- file = "main.go"
- } else {
- mlog.Fatal("build file path cannot be empty")
- }
- }
- if in.Name == "" {
- in.Name = gfile.Name(file)
- }
- if len(in.Name) < 1 || in.Name == "*" {
- mlog.Fatal("name cannot be empty")
- }
- if in.Mod != "" && in.Mod != "none" {
- mlog.Debugf(`mod is %s`, in.Mod)
- if in.Extra == "" {
- in.Extra = fmt.Sprintf(`-mod=%s`, in.Mod)
- } else {
- in.Extra = fmt.Sprintf(`-mod=%s %s`, in.Mod, in.Extra)
- }
- }
- if in.Extra != "" {
- in.Extra += " "
- }
- var (
- customSystems = gstr.SplitAndTrim(in.System, ",")
- customArches = gstr.SplitAndTrim(in.Arch, ",")
- )
- if len(in.Version) > 0 {
- in.Path += "/" + in.Version
- }
- // System and arch checks.
- var (
- spaceRegex = regexp.MustCompile(`\s+`)
- platformMap = make(map[string]map[string]bool)
- )
- for _, line := range strings.Split(strings.TrimSpace(cBuildPlatforms), "\n") {
- line = gstr.Trim(line)
- line = spaceRegex.ReplaceAllString(line, " ")
- var (
- array = strings.Split(line, " ")
- system = strings.TrimSpace(array[0])
- arch = strings.TrimSpace(array[1])
- )
- if platformMap[system] == nil {
- platformMap[system] = make(map[string]bool)
- }
- platformMap[system][arch] = true
- }
- // Auto packing.
- if in.PackSrc != "" {
- if in.PackDst == "" {
- mlog.Fatal(`parameter "packDst" should not be empty when "packSrc" is used`)
- }
- if gfile.Exists(in.PackDst) && !gfile.IsFile(in.PackDst) {
- mlog.Fatalf(`parameter "packDst" path "%s" should be type of file not directory`, in.PackDst)
- }
- if !gfile.Exists(in.PackDst) {
- // Remove the go file that is automatically packed resource.
- defer func() {
- _ = gfile.Remove(in.PackDst)
- mlog.Printf(`remove the automatically generated resource go file: %s`, in.PackDst)
- }()
- }
- // remove black space in separator.
- in.PackSrc, _ = gregex.ReplaceString(`,\s+`, `,`, in.PackSrc)
- packCmd := fmt.Sprintf(`gf pack %s %s --keepPath=true`, in.PackSrc, in.PackDst)
- mlog.Print(packCmd)
- gproc.MustShellRun(ctx, packCmd)
- }
- // Injected information by building flags.
- ldFlags := fmt.Sprintf(
- `-X 'github.com/gogf/gf/v2/os/gbuild.builtInVarStr=%v'`,
- c.getBuildInVarStr(ctx, in),
- )
- // start building
- mlog.Print("start building...")
- if in.Cgo {
- genv.MustSet("CGO_ENABLED", "1")
- } 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
- }
- for arch := range item {
- if len(customArches) > 0 && customArches[0] != "all" && !gstr.InArray(customArches, arch) {
- continue
- }
- if len(customSystems) == 0 && len(customArches) == 0 {
- if runtime.GOOS == "windows" {
- ext = ".exe"
- }
- // Single binary building, output the binary to current working folder.
- output := ""
- if len(in.Output) > 0 {
- output = "-o " + in.Output + ext
- } else {
- output = "-o " + in.Name + ext
- }
- cmd = fmt.Sprintf(`go build %s -ldflags "%s" %s %s`, output, ldFlags, in.Extra, file)
- } else {
- // Cross-building, output the compiled binary to specified path.
- if system == "windows" {
- ext = ".exe"
- }
- genv.MustSet("GOOS", system)
- genv.MustSet("GOARCH", arch)
- cmd = fmt.Sprintf(
- `go build -o %s/%s/%s%s -ldflags "%s" %s%s`,
- in.Path, system+"_"+arch, in.Name, ext, ldFlags, in.Extra, file,
- )
- }
- 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
- }
- }
- }
- buildDone:
- mlog.Print("done!")
- return
- }
- // getBuildInVarStr retrieves and returns the custom build-in variables in configuration
- // file as json.
- func (c cBuild) getBuildInVarStr(ctx context.Context, in cBuildInput) string {
- buildInVarMap := in.VarMap
- if buildInVarMap == nil {
- buildInVarMap = make(g.Map)
- }
- buildInVarMap["builtGit"] = c.getGitCommit(ctx)
- buildInVarMap["builtTime"] = gtime.Now().String()
- b, err := json.Marshal(buildInVarMap)
- if err != nil {
- mlog.Fatal(err)
- }
- return gbase64.EncodeToString(b)
- }
- // getGitCommit retrieves and returns the latest git commit hash string if present.
- func (c cBuild) getGitCommit(ctx context.Context) string {
- if gproc.SearchBinary("git") == "" {
- return ""
- }
- var (
- cmd = `git log -1 --format="%cd %H" --date=format:"%Y-%m-%d %H:%M:%S"`
- s, _ = gproc.ShellExec(ctx, cmd)
- )
- mlog.Debug(cmd)
- if s != "" {
- if !gstr.Contains(s, "fatal") {
- return gstr.Trim(s)
- }
- }
- return ""
- }
|