├── .gitignore ├── LICENSE ├── README.md ├── build └── build.json ├── cmd ├── cmd.go └── gber │ └── gber.go ├── docs └── README_CN.md ├── go.mod ├── go.sum ├── internal ├── builder │ ├── build.go │ ├── build_args.go │ ├── build_info.go │ ├── osslsigncode.go │ ├── upx.go │ ├── xgo.go │ └── zip.go └── utils │ └── utils.go └── main.go /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | build/gber_* 3 | *.zip 4 | cmd/gber/gber 5 | cmd/gber/gber.exe 6 | build/darwin-* 7 | build/linux-* 8 | build/windows-* 9 | /gber -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 gvcgo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [中文](https://github.com/gvcgo/gobuilder/blob/main/docs/README_CN.md) | [En](https://github.com/gvcgo/gobuilder) 2 | ### What's gobuilder? 3 | 4 | Gobuilder is a tool for building Go binaries. It is similar to the Go tool, but it supports building multiple binaries at once and supports custom build configurations without creating any script. 5 | 6 | ### Features 7 | 8 | - Builds binaries for any platform from go source code. 9 | - Cross-compilation for CGO using **xgo**. (optional) 10 | - Packs binaries with **UPX**. (optional) 11 | - Obfuscate binaries with **garble** for windows. (optional) 12 | - Sign windows exe with **osslsigncode**. (optional) 13 | - Zip binaries automatically. 14 | - Builds binaries at anywhere in a go project. 15 | - Remembers the build operations forever. 16 | - No script is needed. 17 | - Keep source code tidy. A dir named **build** is created, all the related files are restored in **build**. 18 | 19 | **Note**: You can install **upx** and **go compiler** using [VMR](https://github.com/gvcgo/version-manager). **osslsigncode** needs manuall compilation. **garble** and **xgo** can be installed using **go install xxx**. 20 | **xgo docker image** is available at **ghcr.io/crazy-max/xgo** or **crazymax/xgo**. 21 | 22 | For self-signed certificate for Windows, see [here](https://stackoverflow.com/questions/84847/how-do-i-create-a-self-signed-certificate-for-code-signing-on-windows). 23 | 24 | ```bash 25 | New-SelfSignedCertificate -Type Custom -Subject "CN=, O=, C=CN, L=, S=" -KeyUsage DigitalSignature -FriendlyName "MailTool" -CertStoreLocation "Cert:\CurrentUser\My" -TextExtension @("2.5.29.37={text}1.3.6.1.5.5.7.3.3", "2.5.29.19={text}") -NotAfter (Get-Date).AddYears(10) 26 | ``` 27 | 28 | ### How to use? 29 | 30 | - Install 31 | 32 | ```bash 33 | go install github.com/gvcgo/gobuilder/cmd/gber@v0.1.5 34 | ``` 35 | 36 | - Usage 37 | 38 | ```bash 39 | gber build 40 | ``` 41 | 42 | **Note**: If you need to inject variables when building go source code, "$" should be replaced with "#". 43 | ```bash 44 | # original 45 | gber build -ldflags "-X main.GitTag=$(git describe --abbrev=0 --tags) -X main.GitHash=$(git show -s --format=%H) -s -w" ./cmd/vmr/ 46 | 47 | # replaced 48 | gber build -ldflags "-X main.GitTag=#(git describe --abbrev=0 --tags) -X main.GitHash=#(git show -s --format=%H) -s -w" ./cmd/vmr 49 | ``` 50 | 51 | ### Demo 52 | 53 | compiling [vmr](https://github.com/gvcgo/version-manager) for different platforms and architectures. 54 | 55 | ![vmr_compilation](https://cdn.jsdelivr.net/gh/moqsien/conf_backup@main/gber.gif) 56 | 57 | ### Dependencies 58 | 59 | - [go compiler](https://go.dev/dl/) (required) 60 | - [garble](https://github.com/burrowers/garble) (optional) 61 | - [osslsigncode](https://github.com/mtrojnar/osslsigncode) (optional) 62 | - [upx](https://github.com/upx/upx) (optional) 63 | - [xgo](https://github.com/crazy-max/xgo) (optional) 64 | -------------------------------------------------------------------------------- /build/build.json: -------------------------------------------------------------------------------- 1 | { 2 | "work_dir": "/home/moqsien/projects/go/src/gvcgo/gobuilder", 3 | "arch_os_list": [ 4 | "darwin/amd64", 5 | "darwin/arm64", 6 | "linux/amd64", 7 | "linux/arm64", 8 | "windows/amd64", 9 | "windows/arm64" 10 | ], 11 | "build_args": [ 12 | "./cmd/gber" 13 | ], 14 | "enable_cgo_with_xgo": false, 15 | "xgo_image": "", 16 | "xgo_deps": "", 17 | "xgo_deps_args": "", 18 | "enable_zip": true, 19 | "enable_garble": false, 20 | "enable_upx": false, 21 | "enable_osslsigncode": false, 22 | "ossl_pfx_file_path": "", 23 | "ossl_pfx_password": "", 24 | "ossl_pfx_company": "", 25 | "ossl_pfx_website": "" 26 | } -------------------------------------------------------------------------------- /cmd/cmd.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | 8 | "github.com/gvcgo/gobuilder/internal/builder" 9 | "github.com/gvcgo/gobuilder/internal/utils" 10 | "github.com/gvcgo/goutils/pkgs/gtea/confirm" 11 | "github.com/gvcgo/goutils/pkgs/gtea/gprint" 12 | "github.com/gvcgo/goutils/pkgs/gutils" 13 | "github.com/spf13/cobra" 14 | ) 15 | 16 | var GroupID string = "gber" 17 | 18 | type Cli struct { 19 | rootCmd *cobra.Command 20 | gitTag string 21 | gitHash string 22 | } 23 | 24 | func NewCli(gitTag, gitHash string) (c *Cli) { 25 | c = &Cli{ 26 | rootCmd: &cobra.Command{ 27 | Short: "A enhanced go builder.", 28 | Long: "gber --flags .", 29 | }, 30 | gitTag: gitTag, 31 | gitHash: gitHash, 32 | } 33 | c.rootCmd.AddGroup(&cobra.Group{ID: GroupID, Title: "Command list: "}) 34 | c.initiate() 35 | return 36 | } 37 | 38 | func (c *Cli) initiate() { 39 | c.rootCmd.AddCommand(&cobra.Command{ 40 | Use: "build", 41 | Aliases: []string{"b"}, 42 | Short: "Builds a go project.", 43 | Long: "Example: gber build --flags .", 44 | GroupID: GroupID, 45 | DisableFlagParsing: true, 46 | Run: func(cmd *cobra.Command, args []string) { 47 | bd := builder.NewBuilder() 48 | bd.Build() 49 | }, 50 | }) 51 | 52 | c.rootCmd.AddCommand(&cobra.Command{ 53 | Use: "version", 54 | Aliases: []string{"v"}, 55 | Short: "Shows version info go gber.", 56 | GroupID: GroupID, 57 | Run: func(cmd *cobra.Command, args []string) { 58 | if len(c.gitHash) > 7 { 59 | c.gitHash = c.gitHash[:8] 60 | } 61 | if c.gitTag != "" && c.gitHash != "" { 62 | fmt.Println(gprint.CyanStr("%s(%s)", c.gitTag, c.gitHash)) 63 | } 64 | }, 65 | }) 66 | 67 | c.rootCmd.AddCommand(&cobra.Command{ 68 | Use: "clear", 69 | Aliases: []string{"c"}, 70 | Short: "Clears the build directory.", 71 | GroupID: GroupID, 72 | Run: func(cmd *cobra.Command, args []string) { 73 | cwd, _ := os.Getwd() 74 | pd := utils.FindGoProjectDir(cwd) 75 | buildDir := filepath.Join(pd, "build") 76 | if ok, _ := gutils.PathIsExist(buildDir); ok { 77 | cfm := confirm.NewConfirmation(confirm.WithPrompt(fmt.Sprintf("Do you really mean to clear %s?", buildDir))) 78 | cfm.Run() 79 | 80 | if cfm.Result() { 81 | os.RemoveAll(buildDir) 82 | } 83 | } 84 | }, 85 | }) 86 | } 87 | 88 | func (c *Cli) Run() { 89 | if err := c.rootCmd.Execute(); err != nil { 90 | gprint.PrintError("%+v", err) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /cmd/gber/gber.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/gvcgo/gobuilder/cmd" 5 | ) 6 | 7 | var ( 8 | GitTag string 9 | GitHash string 10 | ) 11 | 12 | func main() { 13 | cli := cmd.NewCli(GitTag, GitHash) 14 | cli.Run() 15 | } 16 | -------------------------------------------------------------------------------- /docs/README_CN.md: -------------------------------------------------------------------------------- 1 | [中文](https://github.com/gvcgo/gobuilder/blob/main/docs/README_CN.md) | [En](https://github.com/gvcgo/gobuilder) 2 | ### 何为gobuilder? 3 | 4 | gobuilder是一个用于编译go项目的工具。它功能上与go build类似,但是做了增强。能够同时编译到不同平台和架构,也不需要单独写脚本。 5 | 6 | ### 功能特点 7 | 8 | - 同时编译到go build支持的任何一个或者多个平台; 9 | - 使用**xgo**对CGO进行交叉编译(可选); 10 | - 使用**UPX**对binary进行压缩(可选); 11 | - 使用**garble**对windows可执行文件进行混淆(可选); 12 | - 使用**osslsigncode**对windows可执行文件进行数字签名(可选); 13 | - 自动对binary进行zip压缩打包(可选); 14 | - 在go项目下的任何文件夹中,都可以一键编译该项目; 15 | - 记住编译参数,后续任何时间再编译时,无需要输入任何参数; 16 | - 无需编写任何脚本; 17 | - 整洁,会在项目主目录下创建build文件夹,编译配置文件build.json以及二进制文件、压缩文件均存放在此处; 18 | 19 | **注意**: 建议使用[VMR](https://github.com/gvcgo/version-manager)安装**upx** 和 **go compiler**。 **osslsigncode** 的安装则需要手动编译。 **garble** 和 **xgo** 可以通过 **go install xxx**来安装。 **xgo docker镜像** 是 **ghcr.io/crazy-max/xgo** 或 **crazymax/xgo**。 20 | 21 | windows自签名证书生成方法,详见[这里](https://blog.csdn.net/Think88666/article/details/125947720)。 22 | 23 | ```bash 24 | New-SelfSignedCertificate -Type Custom -Subject "CN=姓名, O=公司名称, C=CN, L=上海, S=上海" -KeyUsage DigitalSignature -FriendlyName "MailTool" -CertStoreLocation "Cert:\CurrentUser\My" -TextExtension @("2.5.29.37={text}1.3.6.1.5.5.7.3.3", "2.5.29.19={text}") -NotAfter (Get-Date).AddYears(10) 25 | ``` 26 | 27 | ### 如何使用? 28 | 29 | - 安装 30 | 31 | ```bash 32 | go install github.com/gvcgo/gobuilder/cmd/gber@v0.1.5 33 | ``` 34 | 35 | - 使用方法 36 | 37 | ```bash 38 | gber build 39 | ``` 40 | 41 | **注意**: 如果你需要在编译时动态地注入一些变量,也许你需要将"$"符号替换为"#"符号,以此避免$()中的命令立即执行,gber会自动识别这类替换。例如: 42 | 43 | ```bash 44 | # original 45 | gber build -ldflags "-X main.GitTag=$(git describe --abbrev=0 --tags) -X main.GitHash=$(git show -s --format=%H) -s -w" ./cmd/vmr/ 46 | 47 | # replaced 48 | gber build -ldflags "-X main.GitTag=#(git describe --abbrev=0 --tags) -X main.GitHash=#(git show -s --format=%H) -s -w" ./cmd/vmr 49 | ``` 50 | 51 | ### 演示 52 | 53 | 一键编译 [vmr](https://github.com/gvcgo/version-manager)到不同平台。 54 | 55 | ![vmr_compilation](https://cdn.jsdelivr.net/gh/moqsien/conf_backup@main/gber.gif) 56 | 57 | ### 项目依赖 58 | 59 | - [go compiler](https://go.dev/dl/) (必需) 60 | - [garble](https://github.com/burrowers/garble) (可选) 61 | - [osslsigncode](https://github.com/mtrojnar/osslsigncode) (可选) 62 | - [upx](https://github.com/upx/upx) (可选) 63 | - [xgo](https://github.com/crazy-max/xgo) (可选) 64 | 65 | **注意**:xgo的docker镜像,国内可以使用**ghcr.m.daocloud.io/crazy-max/xgo**加速安装。 66 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gvcgo/gobuilder 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/charmbracelet/bubbles v0.16.1 7 | github.com/gvcgo/goutils v0.9.9 8 | github.com/spf13/cobra v1.8.0 9 | ) 10 | 11 | require ( 12 | github.com/atotto/clipboard v0.1.4 // indirect 13 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect 14 | github.com/charmbracelet/bubbletea v0.24.2 // indirect 15 | github.com/charmbracelet/lipgloss v0.8.0 // indirect 16 | github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect 17 | github.com/erikgeiser/promptkit v0.9.0 // indirect 18 | github.com/gogf/gf/v2 v2.6.1 // indirect 19 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 20 | github.com/lucasb-eyer/go-colorful v1.2.0 // indirect 21 | github.com/mattn/go-isatty v0.0.20 // indirect 22 | github.com/mattn/go-localereader v0.0.1 // indirect 23 | github.com/mattn/go-runewidth v0.0.15 // indirect 24 | github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect 25 | github.com/muesli/cancelreader v0.2.2 // indirect 26 | github.com/muesli/reflow v0.3.0 // indirect 27 | github.com/muesli/termenv v0.15.2 // indirect 28 | github.com/rivo/uniseg v0.4.4 // indirect 29 | github.com/sahilm/fuzzy v0.1.0 // indirect 30 | github.com/spf13/pflag v1.0.5 // indirect 31 | go.opentelemetry.io/otel v1.15.1 // indirect 32 | go.opentelemetry.io/otel/trace v1.15.1 // indirect 33 | golang.org/x/sync v0.3.0 // indirect 34 | golang.org/x/sys v0.15.0 // indirect 35 | golang.org/x/term v0.15.0 // indirect 36 | golang.org/x/text v0.14.0 // indirect 37 | ) 38 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= 2 | github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= 3 | github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= 4 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= 5 | github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= 6 | github.com/charmbracelet/bubbles v0.16.1 h1:6uzpAAaT9ZqKssntbvZMlksWHruQLNxg49H5WdeuYSY= 7 | github.com/charmbracelet/bubbles v0.16.1/go.mod h1:2QCp9LFlEsBQMvIYERr7Ww2H2bA7xen1idUDIzm/+Xc= 8 | github.com/charmbracelet/bubbletea v0.24.2 h1:uaQIKx9Ai6Gdh5zpTbGiWpytMU+CfsPp06RaW2cx/SY= 9 | github.com/charmbracelet/bubbletea v0.24.2/go.mod h1:XdrNrV4J8GiyshTtx3DNuYkR1FDaJmO3l2nejekbsgg= 10 | github.com/charmbracelet/lipgloss v0.8.0 h1:IS00fk4XAHcf8uZKc3eHeMUTCxUH6NkaTrdyCQk84RU= 11 | github.com/charmbracelet/lipgloss v0.8.0/go.mod h1:p4eYUZZJ/0oXTuCQKFF8mqyKCz0ja6y+7DniDDw5KKU= 12 | github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME= 13 | github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY= 14 | github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= 15 | github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 16 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 17 | github.com/erikgeiser/promptkit v0.9.0 h1:3qL1mS/ntCrXdb8sTP/ka82CJ9kEQaGuYXNrYJkWYBc= 18 | github.com/erikgeiser/promptkit v0.9.0/go.mod h1:pU9dtogSe3Jlc2AY77EP7R4WFP/vgD4v+iImC83KsCo= 19 | github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= 20 | github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= 21 | github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= 22 | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 23 | github.com/gogf/gf/v2 v2.6.1 h1:n/cfXM506WjhPa6Z1CEDuHNM1XZ7C8JzSDPn2AfuxgQ= 24 | github.com/gogf/gf/v2 v2.6.1/go.mod h1:x2XONYcI4hRQ/4gMNbWHmZrNzSEIg20s2NULbzom5k0= 25 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 26 | github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= 27 | github.com/grokify/html-strip-tags-go v0.0.1 h1:0fThFwLbW7P/kOiTBs03FsJSV9RM2M/Q/MOnCQxKMo0= 28 | github.com/gvcgo/goutils v0.9.9 h1:bMyABYsobwcDjnB7c1pt7ZCWilslQAPpUqIL0eOsSFk= 29 | github.com/gvcgo/goutils v0.9.9/go.mod h1:+05QX0cRkWKE00MYWOjQld8PHJonsJVAN8srxYGMrpA= 30 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 31 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 32 | github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= 33 | github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= 34 | github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= 35 | github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= 36 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 37 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 38 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 39 | github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= 40 | github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= 41 | github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= 42 | github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= 43 | github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 44 | github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= 45 | github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= 46 | github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= 47 | github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= 48 | github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= 49 | github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= 50 | github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= 51 | github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= 52 | github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= 53 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 54 | github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 55 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 56 | github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= 57 | github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 58 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 59 | github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI= 60 | github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= 61 | github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= 62 | github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= 63 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 64 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 65 | github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= 66 | go.opentelemetry.io/otel v1.15.1 h1:3Iwq3lfRByPaws0f6bU3naAqOR1n5IeDWd9390kWHa8= 67 | go.opentelemetry.io/otel v1.15.1/go.mod h1:mHHGEHVDLal6YrKMmk9LqC4a3sF5g+fHfrttQIB1NTc= 68 | go.opentelemetry.io/otel/sdk v1.14.0 h1:PDCppFRDq8A1jL9v6KMI6dYesaq+DFcDZvjsoGvxGzY= 69 | go.opentelemetry.io/otel/trace v1.15.1 h1:uXLo6iHJEzDfrNC0L0mNjItIp06SyaBQxu5t3xMlngY= 70 | go.opentelemetry.io/otel/trace v1.15.1/go.mod h1:IWdQG/5N1x7f6YUlmdLeJvH9yxtuJAfc4VW5Agv9r/8= 71 | golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= 72 | golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= 73 | golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 74 | golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 75 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 76 | golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= 77 | golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 78 | golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= 79 | golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= 80 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= 81 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 82 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 83 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 84 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 85 | -------------------------------------------------------------------------------- /internal/builder/build.go: -------------------------------------------------------------------------------- 1 | package builder 2 | 3 | import ( 4 | "encoding/json" 5 | "os" 6 | "path/filepath" 7 | "strings" 8 | 9 | "github.com/gvcgo/gobuilder/internal/utils" 10 | "github.com/gvcgo/goutils/pkgs/gtea/gprint" 11 | "github.com/gvcgo/goutils/pkgs/gutils" 12 | ) 13 | 14 | func IsGoCompilerInstalled() bool { 15 | _, err := gutils.ExecuteSysCommand(true, "", "go", "version") 16 | return err == nil 17 | } 18 | 19 | const ( 20 | ConfFileName = "build.json" 21 | ) 22 | 23 | type Builder struct { 24 | WorkDir string `json:"work_dir"` 25 | ArchOSList []string `json:"arch_os_list"` 26 | BuildArgs []string `json:"build_args"` 27 | EnableCGoWithXGo bool `json:"enable_cgo_with_xgo"` 28 | XGoImage string `json:"xgo_image"` 29 | XGoDeps string `json:"xgo_deps"` 30 | XGoDepsArgs string `json:"xgo_deps_args"` 31 | EnableZip bool `json:"enable_zip"` 32 | EnableGarble bool `json:"enable_garble"` 33 | EnableUPX bool `json:"enable_upx"` 34 | EnableOsslsigncode bool `json:"enable_osslsigncode"` 35 | OsslPfxFilePath string `json:"ossl_pfx_file_path"` 36 | OsslPfxPassword string `json:"ossl_pfx_password"` 37 | OsslPfxCompany string `json:"ossl_pfx_company"` 38 | OsslPfxWebsite string `json:"ossl_pfx_website"` 39 | } 40 | 41 | func NewBuilder() (b *Builder) { 42 | b = &Builder{ 43 | ArchOSList: []string{}, 44 | BuildArgs: []string{}, 45 | } 46 | b.LoadConf() 47 | return b 48 | } 49 | 50 | func (b *Builder) ProjectDir() string { 51 | var projectDir string 52 | cwd, _ := os.Getwd() 53 | if cwd != "" { 54 | projectDir = utils.FindGoProjectDir(cwd) 55 | } 56 | return projectDir 57 | } 58 | 59 | func (b *Builder) LoadConf() { 60 | projectDir := b.ProjectDir() 61 | if projectDir == "" { 62 | gprint.PrintError("not found go project") 63 | os.Exit(1) 64 | } 65 | buildConf := filepath.Join(projectDir, "build", ConfFileName) 66 | if ok, _ := gutils.PathIsExist(buildConf); ok { 67 | data, _ := os.ReadFile(buildConf) 68 | if err := json.Unmarshal(data, b); err != nil { 69 | gprint.PrintError("Failed to load build config file: %+v", err) 70 | os.Exit(1) 71 | } 72 | } else { 73 | b.saveBuilder(buildConf) 74 | } 75 | } 76 | 77 | func (b *Builder) build(osInfo, archInfo string) { 78 | gprint.PrintInfo("Building for %s/%s...", osInfo, archInfo) 79 | inputArgs, binDir, binName := b.PrepareArgs(osInfo, archInfo) 80 | 81 | compiler := []string{ 82 | "go", 83 | "build", 84 | } 85 | 86 | if b.EnableGarble { 87 | // Enable garble 88 | compiler = []string{"garble", "-literals", "-tiny", "-seed=random", "build"} 89 | } 90 | 91 | args := append(compiler, inputArgs...) 92 | b.handleInjections(args) 93 | 94 | os.Setenv("GOOS", osInfo) 95 | os.Setenv("GOARCH", archInfo) 96 | os.Setenv("CGO_ENABLED", "0") // disable CGO by default. 97 | 98 | // CGO with xgo 99 | if b.EnableCGoWithXGo { 100 | args = b.UseXGO(osInfo, archInfo, binDir, binName, args) 101 | } 102 | 103 | if _, err := gutils.ExecuteSysCommand(false, b.WorkDir, args...); err != nil { 104 | gprint.PrintError("Failed to build binaries: %+v", err) 105 | os.Exit(1) 106 | } 107 | 108 | if b.EnableCGoWithXGo { 109 | b.FixBinaryName(osInfo, archInfo, binDir, binName) 110 | } 111 | 112 | // UPX 113 | b.PackWithUPX(osInfo, archInfo, binDir, binName) 114 | 115 | // Osslsigncode 116 | b.SignWithOsslsigncode(osInfo, archInfo, binDir, binName) 117 | 118 | // Zip 119 | b.Zip(osInfo, archInfo, binDir, binName) 120 | } 121 | 122 | func (b *Builder) Build() { 123 | if !IsGoCompilerInstalled() { 124 | gprint.PrintError("go compiler is not installed.") 125 | return 126 | } 127 | 128 | if len(b.ArchOSList) == 0 { 129 | return 130 | } 131 | for _, osArch := range b.ArchOSList { 132 | sList := strings.Split(osArch, "/") 133 | b.build(sList[0], sList[1]) 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /internal/builder/build_args.go: -------------------------------------------------------------------------------- 1 | package builder 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | "regexp" 8 | "strings" 9 | 10 | "github.com/gvcgo/goutils/pkgs/gutils" 11 | ) 12 | 13 | const ( 14 | winSuffix string = ".exe" 15 | ) 16 | 17 | /* 18 | prepare build args 19 | */ 20 | func (b *Builder) handleInjections(args []string) { 21 | // handle injection args 22 | cmdReg := regexp.MustCompile(`(\$\([\w\W]+?\))`) 23 | for idx, v := range args { 24 | l := cmdReg.FindAllString(v, -1) 25 | for _, cc := range l { 26 | ccc := strings.TrimLeft(cc, "$(") 27 | ccc = strings.TrimRight(ccc, ")") 28 | bf, _ := gutils.ExecuteSysCommand(true, b.WorkDir, strings.Split(ccc, " ")...) 29 | v = strings.ReplaceAll(v, cc, bf.String()) 30 | } 31 | if len(l) > 0 { 32 | args[idx] = v 33 | } 34 | } 35 | } 36 | 37 | func (b *Builder) PrepareArgs(osInfo, archInfo string) (args []string, targetDir, binName string) { 38 | inputArgs := append([]string{}, b.BuildArgs...) // deepcopy 39 | 40 | if len(inputArgs) == 0 { 41 | inputArgs = append(inputArgs, b.WorkDir) 42 | } 43 | 44 | // main func position. 45 | lastArg := inputArgs[len(inputArgs)-1] 46 | 47 | if !strings.HasPrefix(lastArg, string([]rune{filepath.Separator})) && !strings.HasPrefix(lastArg, ".") { 48 | inputArgs = append(inputArgs, b.WorkDir) 49 | lastArg = b.WorkDir 50 | } else if lastArg == "." && b.WorkDir != "" { 51 | inputArgs[len(inputArgs)-1] = b.WorkDir 52 | lastArg = b.WorkDir 53 | } else if lastArg == ".." && b.WorkDir != "" { 54 | inputArgs[len(inputArgs)-1] = filepath.Dir(b.WorkDir) 55 | lastArg = b.WorkDir 56 | } 57 | 58 | for idx, arg := range b.BuildArgs { 59 | if arg == "-o" && len(b.BuildArgs) > idx+1 { 60 | inputArgs = append(inputArgs[:idx], inputArgs[idx+2:]...) 61 | // If binName has been specified. 62 | binName = filepath.Base(b.BuildArgs[idx+1]) 63 | } 64 | } 65 | 66 | if binName == "" { 67 | binName = filepath.Base(lastArg) 68 | } 69 | 70 | targetDir = filepath.Join(b.ProjectDir(), "build", fmt.Sprintf("%s-%s", osInfo, archInfo)) 71 | os.MkdirAll(targetDir, os.ModePerm) 72 | 73 | target := targetDir 74 | if binName != "" { 75 | if osInfo == gutils.Windows && !strings.HasSuffix(binName, winSuffix) { 76 | binName += winSuffix 77 | } 78 | target = filepath.Join(targetDir, binName) 79 | } 80 | 81 | if len(inputArgs) == 1 { 82 | inputArgs = append([]string{"-o", target}, inputArgs...) 83 | return inputArgs, targetDir, binName 84 | } 85 | 86 | maxIndex := len(inputArgs) - 1 87 | front := inputArgs[:maxIndex] 88 | // incase overwrite 89 | back := append([]string{}, inputArgs[maxIndex:]...) 90 | inputArgs = append(append(front, "-o", target), back...) 91 | return inputArgs, targetDir, binName 92 | } 93 | -------------------------------------------------------------------------------- /internal/builder/build_info.go: -------------------------------------------------------------------------------- 1 | package builder 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "os" 7 | "path/filepath" 8 | "runtime" 9 | "strings" 10 | 11 | "github.com/charmbracelet/bubbles/textinput" 12 | "github.com/gvcgo/gobuilder/internal/utils" 13 | "github.com/gvcgo/goutils/pkgs/gtea/confirm" 14 | "github.com/gvcgo/goutils/pkgs/gtea/gprint" 15 | "github.com/gvcgo/goutils/pkgs/gtea/input" 16 | "github.com/gvcgo/goutils/pkgs/gtea/selector" 17 | "github.com/gvcgo/goutils/pkgs/gutils" 18 | ) 19 | 20 | func (b *Builder) saveBuilder(buildConfPath string) { 21 | buildDir := filepath.Dir(buildConfPath) 22 | if ok, _ := gutils.PathIsExist(buildDir); !ok { 23 | os.MkdirAll(buildDir, os.ModePerm) 24 | } 25 | 26 | // Choose target Os/Arch 27 | b.chooseArchOs() 28 | 29 | if len(b.ArchOSList) == 0 { 30 | gprint.PrintError("No target Os/Arch chosen.") 31 | os.Exit(1) 32 | } 33 | 34 | // Enable CGO with xgo or not. 35 | b.enableCGO() 36 | 37 | // Enable zip after compilation. 38 | b.enableZip() 39 | 40 | // Enable upx or not. 41 | b.enableUpx() 42 | 43 | // Enable garble or not. 44 | b.enableGarble() 45 | 46 | // Enable osslsigncode or not. 47 | b.enableOsslsigncode() 48 | 49 | // process build args. 50 | b.processArgs() 51 | 52 | // process work dir. 53 | b.processWorkDir() 54 | 55 | // save conf file. 56 | b.saveConf(buildConfPath) 57 | } 58 | 59 | func (b *Builder) chooseArchOs() { 60 | items := selector.NewItemList() 61 | items.Add("Current Os/Arch Only", fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH)) 62 | items.Add("Frequently Used Os/Arch", "frequently") 63 | others := utils.GetOtherArchOS() 64 | for _, osArch := range others { 65 | if osArch != "" { 66 | items.Add(osArch, osArch) 67 | } 68 | } 69 | sel := selector.NewSelector( 70 | items, 71 | selector.WithTitle("Select your target Os/Arch: "), 72 | selector.WidthEnableMulti(true), 73 | selector.WithEnbleInfinite(true), 74 | selector.WithWidth(40), 75 | selector.WithHeight(20), 76 | ) 77 | sel.Run() 78 | 79 | targetOsArch := sel.Values() 80 | if len(targetOsArch) > 0 { 81 | b.ArchOSList = []string{} 82 | } 83 | for _, osArch := range targetOsArch { 84 | if oa, ok := osArch.(string); ok { 85 | if oa == "frequently" { 86 | b.ArchOSList = append(b.ArchOSList, utils.GetCommanlyUsedArchOS()...) 87 | } else { 88 | b.ArchOSList = append(b.ArchOSList, oa) 89 | } 90 | } 91 | } 92 | } 93 | 94 | func (b *Builder) enableCGO() { 95 | cfm := confirm.NewConfirmation(confirm.WithPrompt("To enable CGO with [xgo] or not?")) 96 | cfm.Run() 97 | b.EnableCGoWithXGo = cfm.Result() 98 | } 99 | 100 | func (b *Builder) enableZip() { 101 | cfm := confirm.NewConfirmation(confirm.WithPrompt("To zip binaries or not?")) 102 | cfm.Run() 103 | b.EnableZip = cfm.Result() 104 | } 105 | 106 | func (b *Builder) enableUpx() { 107 | cfm := confirm.NewConfirmation(confirm.WithPrompt("To pack binaries with UPX or not?")) 108 | cfm.Run() 109 | b.EnableUPX = cfm.Result() 110 | } 111 | 112 | func (b *Builder) enableGarble() { 113 | cfm := confirm.NewConfirmation(confirm.WithPrompt("Use garble to obfuscate for the binaries or not?")) 114 | cfm.Run() 115 | b.EnableGarble = cfm.Result() 116 | } 117 | 118 | func (b *Builder) enableOsslsigncode() { 119 | cfm := confirm.NewConfirmation(confirm.WithPrompt("Use osslsigncode to sign the binaries or not?")) 120 | cfm.Run() 121 | b.EnableOsslsigncode = cfm.Result() 122 | 123 | if b.EnableOsslsigncode { 124 | mInput := input.NewMultiInput() 125 | var ( 126 | pfxFilePath string = "Pfx file path" 127 | pfxPassword string = "Pfx password" 128 | pfxCompany string = "Pfx company" 129 | pfxWebsite string = "Pfx website" 130 | ) 131 | mInput.AddOneItem(pfxFilePath, input.MWithWidth(120)) 132 | mInput.AddOneItem(pfxPassword, input.MWithWidth(60), input.MWithEchoMode(textinput.EchoPassword), input.MWithEchoChar("*")) 133 | mInput.AddOneItem(pfxCompany, input.MWithWidth(60)) 134 | mInput.AddOneItem(pfxWebsite, input.MWithWidth(120)) 135 | mInput.Run() 136 | 137 | result := mInput.Values() 138 | b.OsslPfxFilePath = result[pfxFilePath] 139 | b.OsslPfxPassword = result[pfxPassword] 140 | b.OsslPfxCompany = result[pfxCompany] 141 | b.OsslPfxWebsite = result[pfxWebsite] 142 | if b.OsslPfxFilePath == "" { 143 | b.EnableOsslsigncode = false 144 | } 145 | } 146 | } 147 | 148 | func (b *Builder) processArgs() { 149 | args := []string{} 150 | if len(os.Args) > 2 { 151 | args = os.Args[2:] 152 | } 153 | if len(args) == 0 { 154 | return 155 | } 156 | 157 | for idx, v := range args { 158 | v = strings.ReplaceAll(v, "#", "$") 159 | args[idx] = v 160 | } 161 | 162 | b.BuildArgs = args 163 | } 164 | 165 | func (b *Builder) processWorkDir() { 166 | cwd, _ := os.Getwd() 167 | b.WorkDir = cwd 168 | } 169 | 170 | func (b *Builder) saveConf(buildConfPath string) { 171 | data, _ := json.MarshalIndent(b, "", " ") 172 | if len(data) > 0 { 173 | os.WriteFile(buildConfPath, data, os.ModePerm) 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /internal/builder/osslsigncode.go: -------------------------------------------------------------------------------- 1 | package builder 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | "runtime" 8 | 9 | "github.com/gvcgo/goutils/pkgs/gtea/gprint" 10 | "github.com/gvcgo/goutils/pkgs/gutils" 11 | ) 12 | 13 | func IsOsslsigncodeInstalled() (ok bool) { 14 | binName := "osslsigncode" 15 | if runtime.GOOS == gutils.Windows { 16 | binName += ".exe" 17 | } 18 | _, err := gutils.ExecuteSysCommand(true, "", binName, "--version") 19 | return err == nil 20 | } 21 | 22 | func (b *Builder) SignWithOsslsigncode(osInfo, archInfo, binDir, binName string) { 23 | if !b.EnableOsslsigncode { 24 | return 25 | } 26 | 27 | // Only sign windows binaries. 28 | if osInfo != gutils.Windows { 29 | gprint.PrintWarning("only sign windows binaries.") 30 | return 31 | } 32 | 33 | if !IsOsslsigncodeInstalled() { 34 | return 35 | } 36 | if ok, _ := gutils.PathIsExist(b.OsslPfxFilePath); !ok || b.OsslPfxPassword == "" { 37 | return 38 | } 39 | 40 | gprint.PrintInfo("Signing with osslsigncode...") 41 | binPath := filepath.Join(binDir, binName) 42 | signedBinPath := filepath.Join(binDir, fmt.Sprintf("signed_%s", binName)) 43 | 44 | /* 45 | osslsigncode sign -addUnauthenticatedBlob -pkcs12 46 | /home/moqsien/golang/src/gvcgo/version-manager/scripts/vmr.pfx 47 | -pass Vmr2024 -n "GVC" -i https://github.com/gvcgo/ -in vmr.exe -out vmr_signed.exe 48 | */ 49 | _, err := gutils.ExecuteSysCommand( 50 | true, 51 | binDir, 52 | "osslsigncode", 53 | "sign", 54 | "-addUnauthenticatedBlob", 55 | "-pkcs12", 56 | b.OsslPfxFilePath, 57 | "-pass", 58 | b.OsslPfxPassword, 59 | "-n", 60 | b.OsslPfxCompany, 61 | "-i", 62 | b.OsslPfxWebsite, 63 | "-in", 64 | binPath, 65 | "-out", 66 | signedBinPath, 67 | ) 68 | if err != nil { 69 | gprint.PrintError("Failed to sign binary: %+v", err) 70 | os.RemoveAll(signedBinPath) 71 | return 72 | } 73 | os.RemoveAll(binPath) 74 | os.Rename(signedBinPath, binPath) 75 | } 76 | -------------------------------------------------------------------------------- /internal/builder/upx.go: -------------------------------------------------------------------------------- 1 | package builder 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | 8 | "github.com/gvcgo/goutils/pkgs/gtea/gprint" 9 | "github.com/gvcgo/goutils/pkgs/gutils" 10 | ) 11 | 12 | func IsUPXInstalled() bool { 13 | _, err := gutils.ExecuteSysCommand(true, "", "upx", "--version") 14 | return err == nil 15 | } 16 | 17 | func (b *Builder) PackWithUPX(osInfo, archInfo, binDir, binName string) { 18 | if !b.EnableUPX { 19 | return 20 | } 21 | 22 | if !IsUPXInstalled() { 23 | gprint.PrintWarning("upx is not found!") 24 | return 25 | } 26 | 27 | // UPX cannot pack binaries for MacOS. Segment fault occurrs. 28 | if osInfo == gutils.Darwin || (osInfo == gutils.Windows && archInfo != "amd64") { 29 | gprint.PrintWarning("pack with UPX is not supported for %s/%s", osInfo, archInfo) 30 | return 31 | } 32 | 33 | fmt.Println(gprint.YellowStr("Packing with UPX...")) 34 | 35 | binPath := filepath.Join(binDir, binName) 36 | packedBinPath := filepath.Join(binDir, fmt.Sprintf("packed_%s", binName)) 37 | 38 | _, err := gutils.ExecuteSysCommand(true, binDir, "upx", "-9", "-o", packedBinPath, binPath) 39 | if err != nil { 40 | gprint.PrintError("Failed to pack binary: %+v", err) 41 | os.RemoveAll(packedBinPath) 42 | return 43 | } 44 | os.RemoveAll(binPath) 45 | os.Rename(packedBinPath, binPath) 46 | } 47 | -------------------------------------------------------------------------------- /internal/builder/xgo.go: -------------------------------------------------------------------------------- 1 | package builder 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | "strings" 8 | 9 | "github.com/gvcgo/gobuilder/internal/utils" 10 | "github.com/gvcgo/goutils/pkgs/gtea/gprint" 11 | "github.com/gvcgo/goutils/pkgs/gutils" 12 | ) 13 | 14 | /* Use xgo for CGO cross-compile. 15 | https://github.com/crazy-max/xgo 16 | 17 | crazymax/xgo 18 | ghcr.io/crazy-max/xgo 19 | */ 20 | 21 | func FindGoProxy() (p string) { 22 | return os.Getenv("GOPROXY") 23 | } 24 | 25 | func IsXgoInstalled() bool { 26 | homeDir, _ := os.UserHomeDir() 27 | _, err := gutils.ExecuteSysCommand(true, homeDir, "xgo", "-h") 28 | return err == nil 29 | } 30 | 31 | func FindXgoDockerImage() (imgName string) { 32 | b, _ := gutils.ExecuteSysCommand(true, "", "docker", "images") 33 | for _, line := range strings.Split(b.String(), "\n") { 34 | if strings.Contains(line, "crazy-max/xgo") { 35 | sList := strings.Split(line, " ") 36 | return sList[0] 37 | } 38 | } 39 | return 40 | } 41 | 42 | func (b *Builder) UseXGO(osInfo, archInfo, binDir, binName string, oldArgs []string) (newArgs []string) { 43 | if !IsXgoInstalled() { 44 | gprint.PrintWarning("xgo is not installed.") 45 | os.Exit(1) 46 | } 47 | imgName := b.XGoImage 48 | if imgName == "" { 49 | imgName = FindXgoDockerImage() 50 | } 51 | if imgName == "" { 52 | gprint.PrintWarning("xgo docker image is not found.") 53 | os.Exit(1) 54 | } 55 | 56 | goProxy := FindGoProxy() 57 | 58 | var ( 59 | ldflags string 60 | vv string 61 | xx string 62 | trimpath string 63 | ) 64 | 65 | for idx, arg := range oldArgs { 66 | if arg == "-ldflags" && idx < len(oldArgs)-1 { 67 | ldflags = oldArgs[idx+1] 68 | } else if strings.HasPrefix(arg, "-ldflags=") { 69 | sList := strings.Split(arg, "=") 70 | ldflags = sList[1] 71 | } else if arg == "-v" { 72 | vv = "-v" 73 | } else if arg == "-x" { 74 | xx = "-x" 75 | } else if arg == "-trimpath" { 76 | trimpath = "-trimpath" 77 | } 78 | } 79 | 80 | importDir := strings.ReplaceAll(oldArgs[len(oldArgs)-1], b.WorkDir, "") 81 | if importDir == "" { 82 | importDir = "." 83 | } 84 | 85 | targets := fmt.Sprintf("%s/%s", osInfo, archInfo) 86 | 87 | newArgs = append(newArgs, "xgo", "-race") 88 | if b.XGoDeps != "" { 89 | newArgs = append(newArgs, fmt.Sprintf(`-deps=%s`, b.XGoDeps)) 90 | } 91 | if b.XGoDepsArgs != "" { 92 | newArgs = append(newArgs, fmt.Sprintf(`-depsargs=%s`, b.XGoDepsArgs)) 93 | } 94 | 95 | destDir := strings.ReplaceAll(binDir, b.WorkDir, "") 96 | newArgs = append(newArgs, fmt.Sprintf(`-dest=%s`, strings.TrimPrefix(destDir, utils.GetPathSeparator()))) 97 | 98 | newArgs = append(newArgs, fmt.Sprintf(`-docker-image=%s`, imgName)) 99 | 100 | if goProxy != "" { 101 | newArgs = append(newArgs, fmt.Sprintf(`-goproxy=%s`, goProxy)) 102 | } 103 | 104 | if ldflags != "" { 105 | newArgs = append(newArgs, fmt.Sprintf(`-ldflags=%s`, ldflags)) 106 | } 107 | 108 | newArgs = append(newArgs, fmt.Sprintf(`-out=%s`, binName)) 109 | 110 | newArgs = append(newArgs, fmt.Sprintf(`-targets=%s`, targets)) 111 | 112 | if trimpath != "" { 113 | newArgs = append(newArgs, trimpath) 114 | } 115 | 116 | if vv != "" { 117 | newArgs = append(newArgs, vv) 118 | } 119 | 120 | if xx != "" { 121 | newArgs = append(newArgs, xx) 122 | } 123 | 124 | newArgs = append(newArgs, importDir) 125 | 126 | fmt.Println(newArgs) 127 | return 128 | } 129 | 130 | func (b *Builder) FixBinaryName(osInfo, archInfo, binDir, binName string) { 131 | dList, _ := os.ReadDir(binDir) 132 | for _, d := range dList { 133 | if !d.IsDir() && strings.Contains(d.Name(), binName) && strings.Contains(d.Name(), osInfo) { 134 | binPath := filepath.Join(binDir, d.Name()) 135 | newBinPath := filepath.Join(binDir, binName) 136 | os.Rename(binPath, newBinPath) 137 | } 138 | } 139 | if osInfo != gutils.Windows { 140 | user := os.Getenv("USER") 141 | if user == "" { 142 | return 143 | } 144 | gutils.ExecuteSysCommand(true, b.WorkDir, "chown", user, filepath.Join(binDir, binName)) 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /internal/builder/zip.go: -------------------------------------------------------------------------------- 1 | package builder 2 | 3 | import ( 4 | "archive/zip" 5 | "fmt" 6 | "io" 7 | "os" 8 | "path/filepath" 9 | "strings" 10 | 11 | "github.com/gvcgo/goutils/pkgs/gtea/gprint" 12 | ) 13 | 14 | func (b *Builder) zipDir(src, dst, binName string) (err error) { 15 | fr, err := os.Open(src) 16 | if err != nil { 17 | return 18 | } 19 | defer fr.Close() 20 | 21 | info, err := fr.Stat() 22 | if err != nil || info.IsDir() { 23 | return err 24 | } 25 | header, err := zip.FileInfoHeader(info) 26 | if err != nil { 27 | return err 28 | } 29 | 30 | fw, err := os.Create(dst) 31 | if err != nil { 32 | return 33 | } 34 | defer fw.Close() 35 | header.Name = binName 36 | header.Method = zip.Deflate 37 | zw := zip.NewWriter(fw) 38 | writer, err := zw.CreateHeader(header) 39 | if err != nil { 40 | return 41 | } 42 | defer zw.Close() 43 | 44 | if _, err = io.Copy(writer, fr); err != nil { 45 | return 46 | } 47 | return nil 48 | } 49 | 50 | func (b *Builder) Zip(osInfo, archInfo, binDir, binName string) { 51 | if !b.EnableZip { 52 | return 53 | } 54 | fmt.Println(gprint.YellowStr("Zipping binaries...")) 55 | 56 | binPath := filepath.Join(binDir, binName) 57 | dirPrefix := strings.Split(binName, ".")[0] 58 | zipPath := filepath.Join(filepath.Dir(binDir), fmt.Sprintf("%s_%s-%s.zip", dirPrefix, osInfo, archInfo)) 59 | b.zipDir(binPath, zipPath, binName) 60 | } 61 | -------------------------------------------------------------------------------- /internal/utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "path/filepath" 5 | "strings" 6 | 7 | "github.com/gvcgo/goutils/pkgs/gutils" 8 | ) 9 | 10 | func FindGoProjectDir(dirName ...string) string { 11 | var currentDir string 12 | sep := string([]rune{filepath.Separator}) 13 | if len(dirName) > 0 && strings.Trim(dirName[0], sep) != "" { 14 | currentDir = dirName[0] 15 | } else { 16 | return currentDir 17 | } 18 | 19 | modPath := filepath.Join(currentDir, "go.mod") 20 | 21 | if ok, _ := gutils.PathIsExist(modPath); ok { 22 | return currentDir 23 | } else { 24 | parentDir := filepath.Dir(currentDir) 25 | return FindGoProjectDir(parentDir) 26 | } 27 | } 28 | 29 | func GetCommanlyUsedArchOS() []string { 30 | return []string{ 31 | "darwin/amd64", 32 | "darwin/arm64", 33 | "linux/amd64", 34 | "linux/arm64", 35 | "windows/amd64", 36 | "windows/arm64", 37 | } 38 | } 39 | 40 | func isCommanlyUsed(archOS string) bool { 41 | cList := GetCommanlyUsedArchOS() 42 | for _, v := range cList { 43 | if v == archOS { 44 | return true 45 | } 46 | } 47 | return false 48 | } 49 | 50 | func GetOtherArchOS() []string { 51 | buff, _ := gutils.ExecuteSysCommand(true, "", "go", "tool", "dist", "list") 52 | aoList := strings.Split(buff.String(), "\n") 53 | r := []string{} 54 | for _, v := range aoList { 55 | if isCommanlyUsed(v) { 56 | continue 57 | } 58 | r = append(r, v) 59 | } 60 | return r 61 | } 62 | 63 | func GetPathSeparator() string { 64 | return string([]rune{filepath.Separator}) 65 | } 66 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/gvcgo/gobuilder/internal/builder" 7 | ) 8 | 9 | func main() { 10 | imgName := builder.FindXgoDockerImage() 11 | fmt.Println(imgName) 12 | } 13 | --------------------------------------------------------------------------------